diff options
Diffstat (limited to 'drivers/comedi')
206 files changed, 129658 insertions, 0 deletions
diff --git a/drivers/comedi/Kconfig b/drivers/comedi/Kconfig new file mode 100644 index 000000000..3cb61fa2c --- /dev/null +++ b/drivers/comedi/Kconfig @@ -0,0 +1,1355 @@ +# SPDX-License-Identifier: GPL-2.0 +config COMEDI + tristate "Data acquisition support (comedi)" + help + Enable support for a wide range of data acquisition devices + for Linux. + +if COMEDI + +config COMEDI_DEBUG + bool "Comedi debugging" + help + This is an option for use by developers; most people should + say N here. This enables comedi core and driver debugging. + +config COMEDI_DEFAULT_BUF_SIZE_KB + int "Comedi default initial asynchronous buffer size in KiB" + default "2048" + help + This is the default asynchronous buffer size which is used for + commands running in the background in kernel space. This + defaults to 2048 KiB of memory so that a 16 channel card + running at 10 kHz has of 2-4 seconds of buffer. + +config COMEDI_DEFAULT_BUF_MAXSIZE_KB + int "Comedi default maximum asynchronous buffer size in KiB" + default "20480" + help + This is the default maximum asynchronous buffer size which can + be requested by a userspace program without root privileges. + This is set to 20480 KiB so that a fast I/O card with 16 + channels running at 100 kHz has 2-4 seconds of buffer. + +menuconfig COMEDI_MISC_DRIVERS + bool "Comedi misc drivers" + help + Enable comedi misc drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about misc non-hardware comedi drivers. + +if COMEDI_MISC_DRIVERS + +config COMEDI_BOND + tristate "Comedi device bonding support" + select COMEDI_KCOMEDILIB + help + Enable support for a driver to 'bond' (merge) multiple subdevices + from multiple devices together as one. + + Currently, it only handles digital I/O subdevices. + + To compile this driver as a module, choose M here: the module will be + called comedi_bond. + +config COMEDI_TEST + tristate "Fake waveform generator support" + help + Enable support for the fake waveform generator. + This driver is mainly for testing purposes, but can also be used to + generate sample waveforms on systems that don't have data acquisition + hardware. + + To compile this driver as a module, choose M here: the module will be + called comedi_test. + +config COMEDI_PARPORT + tristate "Parallel port support" + help + Enable support for the standard parallel port. + A cheap and easy way to get a few more digital I/O lines. Steal + additional parallel ports from old computers or your neighbors' + computers. + + To compile this driver as a module, choose M here: the module will be + called comedi_parport. + +config COMEDI_SSV_DNP + tristate "SSV Embedded Systems DIL/Net-PC support" + depends on X86_32 || COMPILE_TEST + help + Enable support for SSV Embedded Systems DIL/Net-PC + + To compile this driver as a module, choose M here: the module will be + called ssv_dnp. + +endif # COMEDI_MISC_DRIVERS + +menuconfig COMEDI_ISA_DRIVERS + bool "Comedi ISA and PC/104 drivers" + help + Enable comedi ISA and PC/104 drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about ISA and PC/104 comedi drivers. + +if COMEDI_ISA_DRIVERS + +config COMEDI_PCL711 + tristate "Advantech PCL-711/711b and ADlink ACL-8112 ISA card support" + select COMEDI_8254 + help + Enable support for Advantech PCL-711 and 711b, ADlink ACL-8112 + + To compile this driver as a module, choose M here: the module will be + called pcl711. + +config COMEDI_PCL724 + tristate "Advantech PCL-722/724/731 and ADlink ACL-7122/7124/PET-48DIO" + select COMEDI_8255 + help + Enable support for ISA and PC/104 based 8255 digital i/o boards. This + driver provides a legacy comedi driver wrapper for the generic 8255 + support driver. + + Supported boards include: + Advantech PCL-724 24 channels + Advantech PCL-722 144 (or 96) channels + Advantech PCL-731 48 channels + ADlink ACL-7122 144 (or 96) channels + ADlink ACL-7124 24 channels + ADlink PET-48DIO 48 channels + WinSystems PCM-IO48 48 channels (PC/104) + Diamond Systems ONYX-MM-DIO 48 channels (PC/104) + + To compile this driver as a module, choose M here: the module will be + called pcl724. + +config COMEDI_PCL726 + tristate "Advantech PCL-726 and compatible ISA card support" + help + Enable support for Advantech PCL-726 and compatible ISA cards. + + To compile this driver as a module, choose M here: the module will be + called pcl726. + +config COMEDI_PCL730 + tristate "Simple Digital I/O board support (8-bit ports)" + help + Enable support for various simple ISA or PC/104 Digital I/O boards. + These boards all use 8-bit I/O ports. + + Advantech PCL-730 iso - 16 in/16 out ttl - 16 in/16 out + ICP ISO-730 iso - 16 in/16 out ttl - 16 in/16 out + ADlink ACL-7130 iso - 16 in/16 out ttl - 16 in/16 out + Advantech PCM-3730 iso - 8 in/8 out ttl - 16 in/16 out + Advantech PCL-725 iso - 8 in/8 out + ICP P8R8-DIO iso - 8 in/8 out + ADlink ACL-7225b iso - 16 in/16 out + ICP P16R16-DIO iso - 16 in/16 out + Advantech PCL-733 iso - 32 in + Advantech PCL-734 iso - 32 out + Diamond Systems OPMM-1616-XT iso - 16 in/16 out + Diamond Systems PEARL-MM-P iso - 16 out + Diamond Systems IR104-PBF iso - 20 in/20 out + + To compile this driver as a module, choose M here: the module will be + called pcl730. + +config COMEDI_PCL812 + tristate "Advantech PCL-812/813 and ADlink ACL-8112/8113/8113/8216" + select COMEDI_ISADMA if ISA_DMA_API + select COMEDI_8254 + help + Enable support for Advantech PCL-812/PG, PCL-813/B, ADLink + ACL-8112DG/HG/PG, ACL-8113, ACL-8216, ICP DAS A-821PGH/PGL/PGL-NDA, + A-822PGH/PGL, A-823PGH/PGL, A-826PG and ICP DAS ISO-813 ISA cards + + To compile this driver as a module, choose M here: the module will be + called pcl812. + +config COMEDI_PCL816 + tristate "Advantech PCL-814 and PCL-816 ISA card support" + select COMEDI_ISADMA if ISA_DMA_API + select COMEDI_8254 + help + Enable support for Advantech PCL-814 and PCL-816 ISA cards + + To compile this driver as a module, choose M here: the module will be + called pcl816. + +config COMEDI_PCL818 + tristate "Advantech PCL-718 and PCL-818 ISA card support" + select COMEDI_ISADMA if ISA_DMA_API + select COMEDI_8254 + help + Enable support for Advantech PCL-818 ISA cards + PCL-818L, PCL-818H, PCL-818HD, PCL-818HG, PCL-818 and PCL-718 + + To compile this driver as a module, choose M here: the module will be + called pcl818. + +config COMEDI_PCM3724 + tristate "Advantech PCM-3724 PC/104 card support" + select COMEDI_8255 + help + Enable support for Advantech PCM-3724 PC/104 cards. + + To compile this driver as a module, choose M here: the module will be + called pcm3724. + +config COMEDI_AMPLC_DIO200_ISA + tristate "Amplicon PC212E/PC214E/PC215E/PC218E/PC272E" + select COMEDI_AMPLC_DIO200 + help + Enable support for Amplicon PC212E, PC214E, PC215E, PC218E and + PC272E ISA DIO boards + + To compile this driver as a module, choose M here: the module will be + called amplc_dio200. + +config COMEDI_AMPLC_PC236_ISA + tristate "Amplicon PC36AT DIO board support" + select COMEDI_AMPLC_PC236 + help + Enable support for Amplicon PC36AT ISA DIO board. + + To compile this driver as a module, choose M here: the module will be + called amplc_pc236. + +config COMEDI_AMPLC_PC263_ISA + tristate "Amplicon PC263 relay board support" + help + Enable support for Amplicon PC263 ISA relay board. This board has + 16 reed relay output channels. + + To compile this driver as a module, choose M here: the module will be + called amplc_pc263. + +config COMEDI_RTI800 + tristate "Analog Devices RTI-800/815 ISA card support" + help + Enable support for Analog Devices RTI-800/815 ISA cards + + To compile this driver as a module, choose M here: the module will be + called rti800. + +config COMEDI_RTI802 + tristate "Analog Devices RTI-802 ISA card support" + help + Enable support for Analog Devices RTI-802 ISA cards + + To compile this driver as a module, choose M here: the module will be + called rti802. + +config COMEDI_DAC02 + tristate "Keithley Metrabyte DAC02 compatible ISA card support" + help + Enable support for Keithley Metrabyte DAC02 compatible ISA cards. + + To compile this driver as a module, choose M here: the module will be + called dac02. + +config COMEDI_DAS16M1 + tristate "MeasurementComputing CIO-DAS16/M1DAS-16 ISA card support" + select COMEDI_8254 + select COMEDI_8255 + help + Enable support for Measurement Computing CIO-DAS16/M1 ISA cards. + + To compile this driver as a module, choose M here: the module will be + called das16m1. + +config COMEDI_DAS08_ISA + tristate "DAS-08 compatible ISA and PC/104 card support" + select COMEDI_DAS08 + help + Enable support for Keithley Metrabyte/ComputerBoards DAS08 + and compatible ISA and PC/104 cards: + Keithley Metrabyte/ComputerBoards DAS08, DAS08-PGM, DAS08-PGH, + DAS08-PGL, DAS08-AOH, DAS08-AOL, DAS08-AOM, DAS08/JR-AO, + DAS08/JR-16-AO, PC104-DAS08, DAS08/JR/16. + + To compile this driver as a module, choose M here: the module will be + called das08_isa. + +config COMEDI_DAS16 + tristate "DAS-16 compatible ISA and PC/104 card support" + select COMEDI_ISADMA if ISA_DMA_API + select COMEDI_8254 + select COMEDI_8255 + help + Enable support for Keithley Metrabyte/ComputerBoards DAS16 + and compatible ISA and PC/104 cards: + Keithley Metrabyte DAS-16, DAS-16G, DAS-16F, DAS-1201, DAS-1202, + DAS-1401, DAS-1402, DAS-1601, DAS-1602 and + ComputerBoards/MeasurementComputing PC104-DAS16/JR/, + PC104-DAS16JR/16, CIO-DAS16JR/16, CIO-DAS16/JR, CIO-DAS1401/12, + CIO-DAS1402/12, CIO-DAS1402/16, CIO-DAS1601/12, CIO-DAS1602/12, + CIO-DAS1602/16, CIO-DAS16/330 + + To compile this driver as a module, choose M here: the module will be + called das16. + +config COMEDI_DAS800 + tristate "DAS800 and compatible ISA card support" + select COMEDI_8254 + help + Enable support for Keithley Metrabyte DAS800 and compatible ISA cards + Keithley Metrabyte DAS-800, DAS-801, DAS-802 + Measurement Computing CIO-DAS800, CIO-DAS801, CIO-DAS802 and + CIO-DAS802/16 + + To compile this driver as a module, choose M here: the module will be + called das800. + +config COMEDI_DAS1800 + tristate "DAS1800 and compatible ISA card support" + select COMEDI_ISADMA if ISA_DMA_API + select COMEDI_8254 + help + Enable support for DAS1800 and compatible ISA cards + Keithley Metrabyte DAS-1701ST, DAS-1701ST-DA, DAS-1701/AO, + DAS-1702ST, DAS-1702ST-DA, DAS-1702HR, DAS-1702HR-DA, DAS-1702/AO, + DAS-1801ST, DAS-1801ST-DA, DAS-1801HC, DAS-1801AO, DAS-1802ST, + DAS-1802ST-DA, DAS-1802HR, DAS-1802HR-DA, DAS-1802HC and + DAS-1802AO + + To compile this driver as a module, choose M here: the module will be + called das1800. + +config COMEDI_DAS6402 + tristate "DAS6402 and compatible ISA card support" + select COMEDI_8254 + help + Enable support for DAS6402 and compatible ISA cards + Computerboards, Keithley Metrabyte DAS6402 and compatibles + + To compile this driver as a module, choose M here: the module will be + called das6402. + +config COMEDI_DT2801 + tristate "Data Translation DT2801 ISA card support" + help + Enable support for Data Translation DT2801 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2801. + +config COMEDI_DT2811 + tristate "Data Translation DT2811 ISA card support" + help + Enable support for Data Translation DT2811 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2811. + +config COMEDI_DT2814 + tristate "Data Translation DT2814 ISA card support" + help + Enable support for Data Translation DT2814 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2814. + +config COMEDI_DT2815 + tristate "Data Translation DT2815 ISA card support" + help + Enable support for Data Translation DT2815 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2815. + +config COMEDI_DT2817 + tristate "Data Translation DT2817 ISA card support" + help + Enable support for Data Translation DT2817 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2817. + +config COMEDI_DT282X + tristate "Data Translation DT2821 series and DT-EZ ISA card support" + select COMEDI_ISADMA if ISA_DMA_API + help + Enable support for Data Translation DT2821 series including DT-EZ + DT2821, DT2821-F-16SE, DT2821-F-8DI, DT2821-G-16SE, DT2821-G-8DI, + DT2823 (dt2823), DT2824-PGH, DT2824-PGL, DT2825, DT2827, DT2828, + DT21-EZ, DT23-EZ, DT24-EZ and DT24-EZ-PGL + + To compile this driver as a module, choose M here: the module will be + called dt282x. + +config COMEDI_DMM32AT + tristate "Diamond Systems MM-32-AT PC/104 board support" + select COMEDI_8255 + help + Enable support for Diamond Systems MM-32-AT PC/104 boards + + To compile this driver as a module, choose M here: the module will be + called dmm32at. + +config COMEDI_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_8254 + select COMEDI_8255 + help + Enable support for I/O Products PC/104 AIO12-8 Analog I/O Board + + To compile this driver as a module, choose M here: the module will be + called aio_aio12_8. + +config COMEDI_AIO_IIRO_16 + tristate "I/O Products PC/104 IIRO16 Board support" + help + Enable support for I/O Products PC/104 IIRO16 Relay And Isolated + Input Board + + To compile this driver as a module, choose M here: the module will be + called aio_iiro_16. + +config COMEDI_II_PCI20KC + tristate "Intelligent Instruments PCI-20001C carrier support" + depends on HAS_IOMEM + help + Enable support for Intelligent Instruments PCI-20001C carrier + PCI-20001, PCI-20006 and PCI-20341 + + To compile this driver as a module, choose M here: the module will be + called ii_pci20kc. + +config COMEDI_C6XDIGIO + tristate "Mechatronic Systems Inc. C6x_DIGIO DSP daughter card support" + help + Enable support for Mechatronic Systems Inc. C6x_DIGIO DSP daughter + card + + To compile this driver as a module, choose M here: the module will be + called c6xdigio. + +config COMEDI_MPC624 + tristate "Micro/sys MPC-624 PC/104 board support" + help + Enable support for Micro/sys MPC-624 PC/104 board + + To compile this driver as a module, choose M here: the module will be + called mpc624. + +config COMEDI_ADQ12B + tristate "MicroAxial ADQ12-B data acquisition and control card support" + help + Enable MicroAxial ADQ12-B daq and control card support. + + To compile this driver as a module, choose M here: the module will be + called adq12b. + +config COMEDI_NI_AT_A2150 + tristate "NI AT-A2150 ISA card support" + select COMEDI_ISADMA if ISA_DMA_API + select COMEDI_8254 + help + Enable support for National Instruments AT-A2150 cards + + To compile this driver as a module, choose M here: the module will be + called ni_at_a2150. + +config COMEDI_NI_AT_AO + tristate "NI AT-AO-6/10 EISA card support" + select COMEDI_8254 + help + Enable support for National Instruments AT-AO-6/10 cards + + To compile this driver as a module, choose M here: the module will be + called ni_at_ao. + +config COMEDI_NI_ATMIO + tristate "NI AT-MIO E series ISA-PNP card support" + select COMEDI_8255 + select COMEDI_NI_TIO + help + Enable support for National Instruments AT-MIO E series cards + National Instruments AT-MIO-16E-1 (ni_atmio), + AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3, + AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10 + + To compile this driver as a module, choose M here: the module will be + called ni_atmio. + +config COMEDI_NI_ATMIO16D + tristate "NI AT-MIO-16/AT-MIO-16D series ISA card support" + select COMEDI_8255 + help + Enable support for National Instruments AT-MIO-16/AT-MIO-16D cards. + + To compile this driver as a module, choose M here: the module will be + called ni_atmio16d. + +config COMEDI_NI_LABPC_ISA + tristate "NI Lab-PC and compatibles ISA support" + select COMEDI_NI_LABPC + help + Enable support for National Instruments Lab-PC and compatibles + Lab-PC-1200, Lab-PC-1200AI, Lab-PC+. + Kernel-level ISA plug-and-play support for the lab-pc-1200 boards has + not yet been added to the driver. + + To compile this driver as a module, choose M here: the module will be + called ni_labpc. + +config COMEDI_PCMAD + tristate "Winsystems PCM-A/D12 and PCM-A/D16 PC/104 board support" + help + Enable support for Winsystems PCM-A/D12 and PCM-A/D16 PC/104 boards. + + To compile this driver as a module, choose M here: the module will be + called pcmad. + +config COMEDI_PCMDA12 + tristate "Winsystems PCM-D/A-12 8-channel AO PC/104 board support" + help + Enable support for Winsystems PCM-D/A-12 8-channel AO PC/104 boards. + Note that the board is not ISA-PNP capable and thus needs the I/O + port comedi_config parameter. + + To compile this driver as a module, choose M here: the module will be + called pcmda12. + +config COMEDI_PCMMIO + tristate "Winsystems PCM-MIO PC/104 board support" + help + Enable support for Winsystems PCM-MIO multifunction PC/104 boards. + + To compile this driver as a module, choose M here: the module will be + called pcmmio. + +config COMEDI_PCMUIO + tristate "Winsystems PCM-UIO48A and PCM-UIO96A PC/104 board support" + help + Enable support for PCM-UIO48A and PCM-UIO96A PC/104 boards. + + To compile this driver as a module, choose M here: the module will be + called pcmuio. + +config COMEDI_MULTIQ3 + tristate "Quanser Consulting MultiQ-3 ISA card support" + help + Enable support for Quanser Consulting MultiQ-3 ISA cards + + To compile this driver as a module, choose M here: the module will be + called multiq3. + +config COMEDI_S526 + tristate "Sensoray s526 support" + help + Enable support for Sensoray s526 + + To compile this driver as a module, choose M here: the module will be + called s526. + +endif # COMEDI_ISA_DRIVERS + +menuconfig COMEDI_PCI_DRIVERS + tristate "Comedi PCI drivers" + depends on PCI + help + Enable support for comedi PCI drivers. + + To compile this support as a module, choose M here: the module will + be called comedi_pci. + +if COMEDI_PCI_DRIVERS + +config COMEDI_8255_PCI + tristate "Generic PCI based 8255 digital i/o board support" + select COMEDI_8255 + help + Enable support for PCI based 8255 digital i/o boards. This driver + provides a PCI wrapper around the generic 8255 driver. + + Supported boards: + ADlink - PCI-7224, PCI-7248, and PCI-7296 + Measurement Computing - PCI-DIO24, PCI-DIO24H, PCI-DIO48H and + PCI-DIO96H + National Instruments - PCI-DIO-96, PCI-DIO-96B, PXI-6508, PCI-6503, + PCI-6503B, PCI-6503X, and PXI-6503 + + To compile this driver as a module, choose M here: the module will + be called 8255_pci. + +config COMEDI_ADDI_WATCHDOG + tristate + help + Provides support for the watchdog subdevice found on many ADDI-DATA + boards. This module will be automatically selected when needed. The + module will be called addi_watchdog. + +config COMEDI_ADDI_APCI_1032 + tristate "ADDI-DATA APCI_1032 support" + help + Enable support for ADDI-DATA APCI_1032 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1032. + +config COMEDI_ADDI_APCI_1500 + tristate "ADDI-DATA APCI_1500 support" + help + Enable support for ADDI-DATA APCI_1500 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1500. + +config COMEDI_ADDI_APCI_1516 + tristate "ADDI-DATA APCI-1016/1516/2016 support" + select COMEDI_ADDI_WATCHDOG + help + Enable support for ADDI-DATA APCI-1016, APCI-1516 and APCI-2016 boards. + These are 16 channel, optically isolated, digital I/O boards. The 1516 + and 2016 boards also have a watchdog for resetting the outputs to "0". + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1516. + +config COMEDI_ADDI_APCI_1564 + tristate "ADDI-DATA APCI_1564 support" + select COMEDI_ADDI_WATCHDOG + help + Enable support for ADDI-DATA APCI_1564 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1564. + +config COMEDI_ADDI_APCI_16XX + tristate "ADDI-DATA APCI_16xx support" + help + Enable support for ADDI-DATA APCI_16xx cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_16xx. + +config COMEDI_ADDI_APCI_2032 + tristate "ADDI-DATA APCI_2032 support" + select COMEDI_ADDI_WATCHDOG + help + Enable support for ADDI-DATA APCI_2032 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_2032. + +config COMEDI_ADDI_APCI_2200 + tristate "ADDI-DATA APCI_2200 support" + select COMEDI_ADDI_WATCHDOG + help + Enable support for ADDI-DATA APCI_2200 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_2200. + +config COMEDI_ADDI_APCI_3120 + tristate "ADDI-DATA APCI_3120/3001 support" + depends on HAS_DMA + help + Enable support for ADDI-DATA APCI_3120/3001 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3120. + +config COMEDI_ADDI_APCI_3501 + tristate "ADDI-DATA APCI_3501 support" + help + Enable support for ADDI-DATA APCI_3501 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3501. + +config COMEDI_ADDI_APCI_3XXX + tristate "ADDI-DATA APCI_3xxx support" + help + Enable support for ADDI-DATA APCI_3xxx cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3xxx. + +config COMEDI_ADL_PCI6208 + tristate "ADLink PCI-6208A support" + help + Enable support for ADLink PCI-6208A cards + + To compile this driver as a module, choose M here: the module will be + called adl_pci6208. + +config COMEDI_ADL_PCI7X3X + tristate "ADLink PCI-723X/743X isolated digital i/o board support" + help + Enable support for ADlink PCI-723X/743X isolated digital i/o boards. + Supported boards include the 32-channel PCI-7230 (16 in/16 out), + PCI-7233 (32 in), and PCI-7234 (32 out) as well as the 64-channel + PCI-7432 (32 in/32 out), PCI-7433 (64 in), and PCI-7434 (64 out). + + To compile this driver as a module, choose M here: the module will be + called adl_pci7x3x. + +config COMEDI_ADL_PCI8164 + tristate "ADLink PCI-8164 4 Axes Motion Control board support" + help + Enable support for ADlink PCI-8164 4 Axes Motion Control board + + To compile this driver as a module, choose M here: the module will be + called adl_pci8164. + +config COMEDI_ADL_PCI9111 + tristate "ADLink PCI-9111HR support" + select COMEDI_8254 + help + Enable support for ADlink PCI9111 cards + + To compile this driver as a module, choose M here: the module will be + called adl_pci9111. + +config COMEDI_ADL_PCI9118 + tristate "ADLink PCI-9118DG, PCI-9118HG, PCI-9118HR support" + depends on HAS_DMA + select COMEDI_8254 + help + Enable support for ADlink PCI-9118DG, PCI-9118HG, PCI-9118HR cards + + To compile this driver as a module, choose M here: the module will be + called adl_pci9118. + +config COMEDI_ADV_PCI1710 + tristate "Advantech PCI-171x and PCI-1731 support" + select COMEDI_8254 + help + Enable support for Advantech PCI-1710, PCI-1710HG, PCI-1711, + PCI-1713 and PCI-1731 + + To compile this driver as a module, choose M here: the module will be + called adv_pci1710. + +config COMEDI_ADV_PCI1720 + tristate "Advantech PCI-1720 support" + help + Enable support for Advantech PCI-1720 Analog Output board. + + To compile this driver as a module, choose M here: the module will be + called adv_pci1720. + +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_PCI1760 + tristate "Advantech PCI-1760 support" + help + Enable support for Advantech PCI-1760 board. + + To compile this driver as a module, choose M here: the module will be + called adv_pci1760. + +config COMEDI_ADV_PCI_DIO + tristate "Advantech PCI DIO card support" + select COMEDI_8254 + select COMEDI_8255 + help + Enable support for Advantech PCI DIO cards + PCI-1730, PCI-1733, PCI-1734, PCI-1735U, PCI-1736UP, PCI-1739U, + PCI-1750, PCI-1751, PCI-1752, PCI-1753/E, PCI-1754, PCI-1756, + PCI-1761 and PCI-1762 + + To compile this driver as a module, choose M here: the module will be + called adv_pci_dio. + +config COMEDI_AMPLC_DIO200_PCI + tristate "Amplicon PCI215/PCI272/PCIe215/PCIe236/PCIe296 DIO support" + select COMEDI_AMPLC_DIO200 + help + Enable support for Amplicon PCI215, PCI272, PCIe215, PCIe236 + and PCIe296 DIO boards. + + To compile this driver as a module, choose M here: the module will be + called amplc_dio200_pci. + +config COMEDI_AMPLC_PC236_PCI + tristate "Amplicon PCI236 DIO board support" + select COMEDI_AMPLC_PC236 + help + Enable support for Amplicon PCI236 DIO board. + + To compile this driver as a module, choose M here: the module will be + called amplc_pci236. + +config COMEDI_AMPLC_PC263_PCI + tristate "Amplicon PCI263 relay board support" + help + Enable support for Amplicon PCI263 relay board. This is a PCI board + with 16 reed relay output channels. + + To compile this driver as a module, choose M here: the module will be + called amplc_pci263. + +config COMEDI_AMPLC_PCI224 + tristate "Amplicon PCI224 and PCI234 support" + select COMEDI_8254 + help + Enable support for Amplicon PCI224 and PCI234 AO boards + + To compile this driver as a module, choose M here: the module will be + called amplc_pci224. + +config COMEDI_AMPLC_PCI230 + tristate "Amplicon PCI230 and PCI260 support" + select COMEDI_8254 + select COMEDI_8255 + help + Enable support for Amplicon PCI230 and PCI260 Multifunction I/O + boards + + To compile this driver as a module, choose M here: the module will be + called amplc_pci230. + +config COMEDI_CONTEC_PCI_DIO + tristate "Contec PIO1616L digital I/O board support" + help + Enable support for the Contec PIO1616L digital I/O board + + To compile this driver as a module, choose M here: the module will be + called contec_pci_dio. + +config COMEDI_DAS08_PCI + tristate "DAS-08 PCI support" + select COMEDI_DAS08 + help + Enable support for PCI DAS-08 cards. + + To compile this driver as a module, choose M here: the module will be + called das08_pci. + +config COMEDI_DT3000 + tristate "Data Translation DT3000 series support" + help + Enable support for Data Translation DT3000 series + DT3001, DT3001-PGL, DT3002, DT3003, DT3003-PGL, DT3004, DT3005 and + DT3004-200 + + To compile this driver as a module, choose M here: the module will be + called dt3000. + +config COMEDI_DYNA_PCI10XX + tristate "Dynalog PCI DAQ series support" + help + Enable support for Dynalog PCI DAQ series + PCI-1050 + + To compile this driver as a module, choose M here: the module will be + called dyna_pci10xx. + +config COMEDI_GSC_HPDI + tristate "General Standards PCI-HPDI32 / PMC-HPDI32 support" + help + Enable support for General Standards Corporation high speed parallel + digital interface rs485 boards PCI-HPDI32 and PMC-HPDI32. + Only receive mode works, transmit not supported. + + To compile this driver as a module, choose M here: the module will be + called gsc_hpdi. + +config COMEDI_MF6X4 + tristate "Humusoft MF634 and MF624 DAQ Card support" + help + This driver supports both Humusoft MF634 and MF624 Data acquisition + cards. The legacy Humusoft MF614 card is not supported. + +config COMEDI_ICP_MULTI + tristate "Inova ICP_MULTI support" + help + Enable support for Inova ICP_MULTI card + + To compile this driver as a module, choose M here: the module will be + called icp_multi. + +config COMEDI_DAQBOARD2000 + tristate "IOtech DAQboard/2000 support" + select COMEDI_8255 + help + Enable support for the IOtech DAQboard/2000 + + To compile this driver as a module, choose M here: the module will be + called daqboard2000. + +config COMEDI_JR3_PCI + tristate "JR3/PCI force sensor board support" + help + Enable support for JR3/PCI force sensor boards + + To compile this driver as a module, choose M here: the module will be + called jr3_pci. + +config COMEDI_KE_COUNTER + tristate "Kolter-Electronic PCI Counter 1 card support" + help + Enable support for Kolter-Electronic PCI Counter 1 cards + + To compile this driver as a module, choose M here: the module will be + called ke_counter. + +config COMEDI_CB_PCIDAS64 + tristate "MeasurementComputing PCI-DAS 64xx, 60xx, and 4020 support" + select COMEDI_8255 + help + Enable support for ComputerBoards/MeasurementComputing PCI-DAS 64xx, + 60xx, and 4020 series with the PLX 9080 PCI controller + + To compile this driver as a module, choose M here: the module will be + called cb_pcidas64. + +config COMEDI_CB_PCIDAS + tristate "MeasurementComputing PCI-DAS support" + select COMEDI_8254 + select COMEDI_8255 + help + Enable support for ComputerBoards/MeasurementComputing PCI-DAS with + AMCC S5933 PCIcontroller: PCI-DAS1602/16, PCI-DAS1602/16jr, + PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr, PCI-DAS1000, PCI-DAS1001 + and PCI_DAS1002. + + To compile this driver as a module, choose M here: the module will be + called cb_pcidas. + +config COMEDI_CB_PCIDDA + tristate "MeasurementComputing PCI-DDA series support" + select COMEDI_8255 + help + Enable support for ComputerBoards/MeasurementComputing PCI-DDA + series: PCI-DDA08/12, PCI-DDA04/12, PCI-DDA02/12, PCI-DDA08/16, + PCI-DDA04/16 and PCI-DDA02/16 + + To compile this driver as a module, choose M here: the module will be + called cb_pcidda. + +config COMEDI_CB_PCIMDAS + tristate "MeasurementComputing PCIM-DAS1602/16, PCIe-DAS1602/16 support" + select COMEDI_8254 + select COMEDI_8255 + help + Enable support for ComputerBoards/MeasurementComputing PCI Migration + series PCIM-DAS1602/16 and PCIe-DAS1602/16. + + To compile this driver as a module, choose M here: the module will be + called cb_pcimdas. + +config COMEDI_CB_PCIMDDA + tristate "MeasurementComputing PCIM-DDA06-16 support" + select COMEDI_8255 + help + Enable support for ComputerBoards/MeasurementComputing PCIM-DDA06-16 + + To compile this driver as a module, choose M here: the module will be + called cb_pcimdda. + +config COMEDI_ME4000 + tristate "Meilhaus ME-4000 support" + select COMEDI_8254 + help + Enable support for Meilhaus PCI data acquisition cards + ME-4650, ME-4670i, ME-4680, ME-4680i and ME-4680is + + To compile this driver as a module, choose M here: the module will be + called me4000. + +config COMEDI_ME_DAQ + tristate "Meilhaus ME-2000i, ME-2600i, ME-3000vm1 support" + help + Enable support for Meilhaus PCI data acquisition cards + ME-2000i, ME-2600i and ME-3000vm1 + + To compile this driver as a module, choose M here: the module will be + called me_daq. + +config COMEDI_NI_6527 + tristate "NI 6527 support" + help + Enable support for the National Instruments 6527 PCI card + + To compile this driver as a module, choose M here: the module will be + called ni_6527. + +config COMEDI_NI_65XX + tristate "NI 65xx static dio PCI card support" + help + Enable support for National Instruments 65xx static dio boards. + Supported devices: National Instruments PCI-6509 (ni_65xx), + PXI-6509, PCI-6510, PCI-6511, PXI-6511, PCI-6512, PXI-6512, PCI-6513, + PXI-6513, PCI-6514, PXI-6514, PCI-6515, PXI-6515, PCI-6516, PCI-6517, + PCI-6518, PCI-6519, PCI-6520, PCI-6521, PXI-6521, PCI-6528, PXI-6528 + + To compile this driver as a module, choose M here: the module will be + called ni_65xx. + +config COMEDI_NI_660X + tristate "NI 660x counter/timer PCI card support" + depends on HAS_DMA + select COMEDI_NI_TIOCMD + help + Enable support for National Instruments PCI-6601 (ni_660x), PCI-6602, + PXI-6602, PXI-6608, PCI-6624, and PXI-6624. + + To compile this driver as a module, choose M here: the module will be + called ni_660x. + +config COMEDI_NI_670X + tristate "NI 670x PCI card support" + help + Enable support for National Instruments PCI-6703 and PCI-6704 + + To compile this driver as a module, choose M here: the module will be + called ni_670x. + +config COMEDI_NI_LABPC_PCI + tristate "NI Lab-PC PCI-1200 support" + select COMEDI_NI_LABPC + help + Enable support for National Instruments Lab-PC PCI-1200. + + To compile this driver as a module, choose M here: the module will be + called ni_labpc_pci. + +config COMEDI_NI_PCIDIO + tristate "NI PCI-DIO32HS, PCI-6533, PCI-6534 support" + depends on HAS_DMA + select COMEDI_MITE + select COMEDI_8255 + help + Enable support for National Instruments PCI-DIO-32HS, PXI-6533, + PCI-6533 and PCI-6534 + + To compile this driver as a module, choose M here: the module will be + called ni_pcidio. + +config COMEDI_NI_PCIMIO + tristate "NI PCI-MIO-E series and M series support" + depends on HAS_DMA + select COMEDI_NI_TIOCMD + select COMEDI_8255 + help + Enable support for National Instruments PCI-MIO-E series and M series + (all boards): PCI-MIO-16XE-10, PXI-6030E, PCI-MIO-16E-1, + PCI-MIO-16E-4, PCI-6014, PCI-6040E, PXI-6040E, PCI-6030E, PCI-6031E, + PCI-6032E, PCI-6033E, PCI-6071E, PCI-6023E, PCI-6024E, PCI-6025E, + PXI-6025E, PCI-6034E, PCI-6035E, PCI-6052E, PCI-6110, PCI-6111, + PCI-6220, PXI-6220, PCI-6221, PXI-6221, PCI-6224, PXI-6224, PCI-6225, + PXI-6225, PCI-6229, PXI-6229, PCI-6250, PXI-6250, PCI-6251, PXI-6251, + PCIe-6251, PXIe-6251, PCI-6254, PXI-6254, PCI-6259, PXI-6259, + PCIe-6259, PXIe-6259, PCI-6280, PXI-6280, PCI-6281, PXI-6281, + PCI-6284, PXI-6284, PCI-6289, PXI-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" + select COMEDI_8254 + help + Enable support for Real Time Devices PCI4520/DM7520 + + To compile this driver as a module, choose M here: the module will be + called rtd520. + +config COMEDI_S626 + tristate "Sensoray 626 support" + help + Enable support for Sensoray 626 + + To compile this driver as a module, choose M here: the module will be + called s626. + +config COMEDI_MITE + depends on HAS_DMA + tristate + +config COMEDI_NI_TIOCMD + tristate + depends on HAS_DMA + select COMEDI_NI_TIO + select COMEDI_MITE + +endif # COMEDI_PCI_DRIVERS + +menuconfig COMEDI_PCMCIA_DRIVERS + tristate "Comedi PCMCIA drivers" + depends on PCMCIA + help + Enable support for comedi PCMCIA drivers. + + To compile this support as a module, choose M here: the module will + be called comedi_pcmcia. + +if COMEDI_PCMCIA_DRIVERS + +config COMEDI_CB_DAS16_CS + tristate "CB DAS16 series PCMCIA support" + select COMEDI_8254 + help + Enable support for the ComputerBoards/MeasurementComputing PCMCIA + cards DAS16/16, PCM-DAS16D/12 and PCM-DAS16s/16 + + To compile this driver as a module, choose M here: the module will be + called cb_das16_cs. + +config COMEDI_DAS08_CS + tristate "CB DAS08 PCMCIA support" + select COMEDI_DAS08 + help + Enable support for the ComputerBoards/MeasurementComputing DAS-08 + PCMCIA card + + To compile this driver as a module, choose M here: the module will be + called das08_cs. + +config COMEDI_NI_DAQ_700_CS + tristate "NI DAQCard-700 PCMCIA support" + help + Enable support for the National Instruments PCMCIA DAQCard-700 DIO + + To compile this driver as a module, choose M here: the module will be + called ni_daq_700. + +config COMEDI_NI_DAQ_DIO24_CS + tristate "NI DAQ-Card DIO-24 PCMCIA support" + select COMEDI_8255 + help + Enable support for the National Instruments PCMCIA DAQ-Card DIO-24 + + To compile this driver as a module, choose M here: the module will be + called ni_daq_dio24. + +config COMEDI_NI_LABPC_CS + tristate "NI DAQCard-1200 PCMCIA support" + select COMEDI_NI_LABPC + help + Enable support for the National Instruments PCMCIA DAQCard-1200 + + To compile this driver as a module, choose M here: the module will be + called ni_labpc_cs. + +config COMEDI_NI_MIO_CS + tristate "NI DAQCard E series PCMCIA support" + select COMEDI_NI_TIO + select COMEDI_8255 + help + Enable support for the National Instruments PCMCIA DAQCard E series + DAQCard-ai-16xe-50, DAQCard-ai-16e-4, DAQCard-6062E, DAQCard-6024E + and DAQCard-6036E + + To compile this driver as a module, choose M here: the module will be + called ni_mio_cs. + +config COMEDI_QUATECH_DAQP_CS + tristate "Quatech DAQP PCMCIA data capture card support" + help + Enable support for the Quatech DAQP PCMCIA data capture cards + DAQP-208 and DAQP-308 + + To compile this driver as a module, choose M here: the module will be + called quatech_daqp_cs. + +endif # COMEDI_PCMCIA_DRIVERS + +menuconfig COMEDI_USB_DRIVERS + tristate "Comedi USB drivers" + depends on USB + help + Enable support for comedi USB drivers. + + To compile this support as a module, choose M here: the module will + be called comedi_usb. + +if COMEDI_USB_DRIVERS + +config COMEDI_DT9812 + tristate "DataTranslation DT9812 USB module support" + help + Enable support for the Data Translation DT9812 USB module + + To compile this driver as a module, choose M here: the module will be + called dt9812. + +config COMEDI_NI_USB6501 + tristate "NI USB-6501 support" + help + Enable support for the National Instruments USB-6501 module. + + The NI USB-6501 is a Full-Speed USB 2.0 (12 Mbit/s) device that + provides 24 digital I/O lines channels and one 32-bit counter. + + To compile this driver as a module, choose M here: the module will be + called ni_usb6501. + +config COMEDI_USBDUX + tristate "ITL USB-DUX-D support" + help + Enable support for the Incite Technology Ltd USB-DUX-D Board + + To compile this driver as a module, choose M here: the module will be + called usbdux. + +config COMEDI_USBDUXFAST + tristate "ITL USB-DUXfast support" + help + Enable support for the Incite Technology Ltd USB-DUXfast Board + + To compile this driver as a module, choose M here: the module will be + called usbduxfast. + +config COMEDI_USBDUXSIGMA + tristate "ITL USB-DUXsigma support" + help + Enable support for the Incite Technology Ltd USB-DUXsigma Board + + To compile this driver as a module, choose M here: the module will be + called usbduxsigma. + +config COMEDI_VMK80XX + tristate "Velleman VM110/VM140 USB Board support" + help + Build the Velleman USB Board Low-Level Driver supporting the + K8055/K8061 aka VM110/VM140 devices + + To compile this driver as a module, choose M here: the module will be + called vmk80xx. + +endif # COMEDI_USB_DRIVERS + +config COMEDI_8254 + tristate + +config COMEDI_8255 + tristate + +config COMEDI_8255_SA + tristate "Standalone 8255 support" + select COMEDI_8255 + help + Enable support for 8255 digital I/O as a standalone driver. + + You should enable compilation this driver if you plan to use a board + that has an 8255 chip at a known I/O base address and there are no + other Comedi drivers for the board. + + Note that Comedi drivers for most multi-function boards incorporating + an 8255 chip use the 'comedi_8255' module. Most PCI-based 8255 + boards use the 8255_pci driver as a wrapper around the 'comedi_8255' + module. + + To compile this driver as a module, choose M here: the module will be + called 8255. + +config COMEDI_KCOMEDILIB + tristate "Comedi kcomedilib" + help + Build the kcomedilib. + + This is a kernel module used to open and manipulate Comedi devices + from within kernel code. It is currently only used by the + comedi_bond driver, and its functionality has been stripped down to + the needs of that driver, so is currently not very useful for + anything else. + + To compile kcomedilib as a module, choose M here: the module will be + called kcomedilib. + +config COMEDI_AMPLC_DIO200 + select COMEDI_8254 + tristate + +config COMEDI_AMPLC_PC236 + tristate + select COMEDI_8255 + +config COMEDI_DAS08 + tristate + select COMEDI_8254 + select COMEDI_8255 + +config COMEDI_ISADMA + tristate + +config COMEDI_NI_LABPC + tristate + select COMEDI_8254 + select COMEDI_8255 + +config COMEDI_NI_LABPC_ISADMA + tristate + default COMEDI_NI_LABPC + depends on COMEDI_NI_LABPC_ISA != n + depends on ISA_DMA_API + select COMEDI_ISADMA + +config COMEDI_NI_TIO + tristate + select COMEDI_NI_ROUTING + +config COMEDI_NI_ROUTING + tristate + +config COMEDI_TESTS + tristate "Comedi unit tests" + help + Enable comedi unit-test modules 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 comedi unit-test modules. + +if COMEDI_TESTS + +config COMEDI_TESTS_EXAMPLE + tristate "Comedi example unit-test module" + help + Enable support for an example unit-test module. This is just a + silly example to be used as a basis for writing other unit-test + modules. + + To compile this as a module, choose M here: the module will be called + comedi_example_test. + +config COMEDI_TESTS_NI_ROUTES + tristate "NI routing unit-test module" + select COMEDI_NI_ROUTING + help + Enable support for a unit-test module to test the signal routing + code used by comedi drivers for various National Instruments cards. + + To compile this as a module, choose M here: the module will be called + ni_routes_test. + +endif # COMEDI_TESTS + +endif # COMEDI diff --git a/drivers/comedi/Makefile b/drivers/comedi/Makefile new file mode 100644 index 000000000..072ed83a5 --- /dev/null +++ b/drivers/comedi/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG + +comedi-y := comedi_fops.o range.o drivers.o \ + comedi_buf.o +comedi-$(CONFIG_PROC_FS) += proc.o + +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += comedi_pci.o +obj-$(CONFIG_COMEDI_PCMCIA_DRIVERS) += comedi_pcmcia.o +obj-$(CONFIG_COMEDI_USB_DRIVERS) += comedi_usb.o + +obj-$(CONFIG_COMEDI) += comedi.o + +obj-$(CONFIG_COMEDI) += kcomedilib/ +obj-$(CONFIG_COMEDI) += drivers/ diff --git a/drivers/comedi/TODO b/drivers/comedi/TODO new file mode 100644 index 000000000..f733c017f --- /dev/null +++ b/drivers/comedi/TODO @@ -0,0 +1,12 @@ +TODO: + - checkpatch.pl cleanups + - Lindent + - remove all wrappers + - audit userspace interface + - Fix coverity 1195261 + - 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/comedi/comedi_buf.c b/drivers/comedi/comedi_buf.c new file mode 100644 index 000000000..393966c09 --- /dev/null +++ b/drivers/comedi/comedi_buf.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi_buf.c + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/comedi/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) { + if (bm->dma_dir != DMA_NONE) { + /* + * DMA buffer was allocated as a single block. + * Address is in page_list[0]. + */ + buf = &bm->page_list[0]; + dma_free_coherent(bm->dma_hw_dev, + PAGE_SIZE * bm->n_pages, + buf->virt_addr, buf->dma_addr); + } else { + for (i = 0; i < bm->n_pages; i++) { + buf = &bm->page_list[i]; + ClearPageReserved(virt_to_page(buf->virt_addr)); + 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) { + if (s->async_dma_dir == DMA_NONE) + 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 struct comedi_buf_map * +comedi_buf_map_alloc(struct comedi_device *dev, enum dma_data_direction dma_dir, + unsigned int n_pages) +{ + struct comedi_buf_map *bm; + struct comedi_buf_page *buf; + unsigned int i; + + bm = kzalloc(sizeof(*bm), GFP_KERNEL); + if (!bm) + return NULL; + + kref_init(&bm->refcount); + bm->dma_dir = 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) + goto err; + + if (bm->dma_dir != DMA_NONE) { + void *virt_addr; + dma_addr_t dma_addr; + + /* + * Currently, the DMA buffer needs to be allocated as a + * single block so that it can be mmap()'ed. + */ + virt_addr = dma_alloc_coherent(bm->dma_hw_dev, + PAGE_SIZE * n_pages, &dma_addr, + GFP_KERNEL); + if (!virt_addr) + goto err; + + for (i = 0; i < n_pages; i++) { + buf = &bm->page_list[i]; + buf->virt_addr = virt_addr + (i << PAGE_SHIFT); + buf->dma_addr = dma_addr + (i << PAGE_SHIFT); + } + + bm->n_pages = i; + } else { + for (i = 0; i < n_pages; i++) { + buf = &bm->page_list[i]; + buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL); + if (!buf->virt_addr) + break; + + SetPageReserved(virt_to_page(buf->virt_addr)); + } + + bm->n_pages = i; + if (i < n_pages) + goto err; + } + + return bm; + +err: + comedi_buf_map_put(bm); + return NULL; +} + +static void __comedi_buf_alloc(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int 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 int 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 = comedi_buf_map_alloc(dev, s->async_dma_dir, n_pages); + if (!bm) + return; + + spin_lock_irqsave(&s->spin_lock, flags); + async->buf_map = bm; + spin_unlock_irqrestore(&s->spin_lock, flags); + + if (bm->dma_dir != DMA_NONE) { + /* + * DMA buffer was allocated as a single block. + * Address is in page_list[0]. + */ + buf = &bm->page_list[0]; + async->prealloc_buf = buf->virt_addr; + } else { + pages = vmalloc(sizeof(struct page *) * n_pages); + if (!pages) + return; + + for (i = 0; i < n_pages; i++) { + buf = &bm->page_list[i]; + pages[i] = virt_to_page(buf->virt_addr); + } + + /* vmap the pages to prealloc_buf */ + 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; +} + +/* helper for "access" vm operation */ +int comedi_buf_map_access(struct comedi_buf_map *bm, unsigned long offset, + void *buf, int len, int write) +{ + unsigned int pgoff = offset_in_page(offset); + unsigned long pg = offset >> PAGE_SHIFT; + int done = 0; + + while (done < len && pg < bm->n_pages) { + int l = min_t(int, len - done, PAGE_SIZE - pgoff); + void *b = bm->page_list[pg].virt_addr + pgoff; + + if (write) + memcpy(b, buf, l); + else + memcpy(buf, b, l); + buf += l; + done += l; + pg++; + pgoff = 0; + } + return done; +} + +/* 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 && (kref_read(&bm->refcount) > 1); +} + +int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned long new_size) +{ + struct comedi_async *async = s->async; + + lockdep_assert_held(&dev->mutex); + + /* 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 int n_pages = new_size >> PAGE_SHIFT; + + __comedi_buf_alloc(dev, s, n_pages); + + if (!async->prealloc_buf) { + /* allocation failed */ + __comedi_buf_free(dev, s); + return -ENOMEM; + } + } + async->prealloc_bufsz = new_size; + + return 0; +} + +void comedi_buf_reset(struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + + async->buf_write_alloc_count = 0; + async->buf_write_count = 0; + async->buf_read_alloc_count = 0; + async->buf_read_count = 0; + + async->buf_write_ptr = 0; + async->buf_read_ptr = 0; + + async->cur_chan = 0; + async->scans_done = 0; + async->scan_progress = 0; + async->munge_chan = 0; + async->munge_count = 0; + async->munge_ptr = 0; + + async->events = 0; +} + +static unsigned int comedi_buf_write_n_unalloc(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; +} + +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_count; +} + +/** + * comedi_buf_write_alloc() - Reserve buffer space for writing + * @s: COMEDI subdevice. + * @nbytes: Maximum space to reserve in bytes. + * + * Reserve up to @nbytes bytes of space to be written in the COMEDI acquisition + * data buffer associated with the subdevice. The amount reserved is limited + * by the space available. + * + * Return: The amount of space reserved in bytes. + */ +unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, + unsigned int nbytes) +{ + struct comedi_async *async = s->async; + unsigned int unalloc = comedi_buf_write_n_unalloc(s); + + if (nbytes > unalloc) + nbytes = unalloc; + + async->buf_write_alloc_count += nbytes; + + /* + * ensure the async buffer 'counts' are read and updated + * before we write data to the write-alloc'ed buffer space + */ + smp_mb(); + + return nbytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_write_alloc); + +/* + * munging is applied to data by core as it passes between user + * and kernel space + */ +static unsigned int comedi_buf_munge(struct comedi_subdevice *s, + unsigned int num_bytes) +{ + struct comedi_async *async = s->async; + unsigned int count = 0; + const unsigned int num_sample_bytes = comedi_bytes_per_sample(s); + + if (!s->munge || (async->cmd.flags & CMDF_RAWDATA)) { + async->munge_count += num_bytes; + return num_bytes; + } + + /* 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; +} + +/** + * comedi_buf_write_free() - Free buffer space after it is written + * @s: COMEDI subdevice. + * @nbytes: Maximum space to free in bytes. + * + * Free up to @nbytes bytes of space previously reserved for writing in the + * COMEDI acquisition data buffer associated with the subdevice. The amount of + * space freed is limited to the amount that was reserved. The freed space is + * assumed to have been filled with sample data by the writer. + * + * If the samples in the freed space need to be "munged", do so here. The + * freed space becomes available for allocation by the reader. + * + * Return: The amount of space freed in bytes. + */ +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); + +/** + * comedi_buf_read_n_available() - Determine amount of readable buffer space + * @s: COMEDI subdevice. + * + * Determine the amount of readable buffer space in the COMEDI acquisition data + * buffer associated with the subdevice. The readable buffer space is that + * which has been freed by the writer and "munged" to the sample data format + * expected by COMEDI if necessary. + * + * Return: The amount of readable buffer space. + */ +unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + unsigned int 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); + +/** + * comedi_buf_read_alloc() - Reserve buffer space for reading + * @s: COMEDI subdevice. + * @nbytes: Maximum space to reserve in bytes. + * + * Reserve up to @nbytes bytes of previously written and "munged" buffer space + * for reading in the COMEDI acquisition data buffer associated with the + * subdevice. The amount reserved is limited to the space available. The + * reader can read from the reserved space and then free it. A reader is also + * allowed to read from the space before reserving it as long as it determines + * the amount of readable data available, but the space needs to be marked as + * reserved before it can be freed. + * + * Return: The amount of space reserved in bytes. + */ +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; +} + +/** + * comedi_buf_read_free() - Free buffer space after it has been read + * @s: COMEDI subdevice. + * @nbytes: Maximum space to free in bytes. + * + * Free up to @nbytes bytes of buffer space previously reserved for reading in + * the COMEDI acquisition data buffer associated with the subdevice. The + * amount of space freed is limited to the amount that was reserved. + * + * The freed space becomes available for allocation by the writer. + * + * Return: The amount of space freed in bytes. + */ +unsigned int comedi_buf_read_free(struct comedi_subdevice *s, + unsigned int nbytes) +{ + struct comedi_async *async = s->async; + unsigned int allocated; + + /* + * ensure data has been read out of buffer before + * the async read count is incremented + */ + smp_mb(); + + allocated = comedi_buf_read_n_allocated(async); + if (nbytes > allocated) + nbytes = allocated; + + async->buf_read_count += nbytes; + async->buf_read_ptr += nbytes; + async->buf_read_ptr %= async->prealloc_bufsz; + return nbytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_read_free); + +static void comedi_buf_memcpy_to(struct comedi_subdevice *s, + const void *data, unsigned int num_bytes) +{ + struct comedi_async *async = s->async; + unsigned int write_ptr = async->buf_write_ptr; + + while (num_bytes) { + unsigned int block_size; + + if (write_ptr + num_bytes > async->prealloc_bufsz) + block_size = async->prealloc_bufsz - write_ptr; + else + block_size = num_bytes; + + memcpy(async->prealloc_buf + write_ptr, data, block_size); + + data += block_size; + num_bytes -= block_size; + + write_ptr = 0; + } +} + +static void comedi_buf_memcpy_from(struct comedi_subdevice *s, + void *dest, unsigned int nbytes) +{ + void *src; + struct comedi_async *async = s->async; + unsigned int read_ptr = async->buf_read_ptr; + + while (nbytes) { + unsigned int block_size; + + src = async->prealloc_buf + read_ptr; + + if (nbytes >= async->prealloc_bufsz - read_ptr) + block_size = async->prealloc_bufsz - read_ptr; + else + block_size = nbytes; + + memcpy(dest, src, block_size); + nbytes -= block_size; + dest += block_size; + read_ptr = 0; + } +} + +/** + * comedi_buf_write_samples() - Write sample data to COMEDI buffer + * @s: COMEDI subdevice. + * @data: Pointer to source samples. + * @nsamples: Number of samples to write. + * + * Write up to @nsamples samples to the COMEDI acquisition data buffer + * associated with the subdevice, mark it as written and update the + * acquisition scan progress. If there is not enough room for the specified + * number of samples, the number of samples written is limited to the number + * that will fit and the %COMEDI_CB_OVERFLOW event flag is set to cause the + * acquisition to terminate with an overrun error. Set the %COMEDI_CB_BLOCK + * event flag if any samples are written to cause waiting tasks to be woken + * when the event flags are processed. + * + * Return: The amount of data written in bytes. + */ +unsigned int comedi_buf_write_samples(struct comedi_subdevice *s, + const void *data, unsigned int nsamples) +{ + unsigned int max_samples; + unsigned int nbytes; + + /* + * Make sure there is enough room in the buffer for all the samples. + * If not, clamp the nsamples to the number that will fit, flag the + * buffer overrun and add the samples that fit. + */ + max_samples = comedi_bytes_to_samples(s, comedi_buf_write_n_unalloc(s)); + if (nsamples > max_samples) { + dev_warn(s->device->class_dev, "buffer overrun\n"); + s->async->events |= COMEDI_CB_OVERFLOW; + nsamples = max_samples; + } + + if (nsamples == 0) + return 0; + + nbytes = comedi_buf_write_alloc(s, + comedi_samples_to_bytes(s, nsamples)); + comedi_buf_memcpy_to(s, data, nbytes); + comedi_buf_write_free(s, nbytes); + comedi_inc_scan_progress(s, nbytes); + s->async->events |= COMEDI_CB_BLOCK; + + return nbytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_write_samples); + +/** + * comedi_buf_read_samples() - Read sample data from COMEDI buffer + * @s: COMEDI subdevice. + * @data: Pointer to destination. + * @nsamples: Maximum number of samples to read. + * + * Read up to @nsamples samples from the COMEDI acquisition data buffer + * associated with the subdevice, mark it as read and update the acquisition + * scan progress. Limit the number of samples read to the number available. + * Set the %COMEDI_CB_BLOCK event flag if any samples are read to cause waiting + * tasks to be woken when the event flags are processed. + * + * Return: The amount of data read in bytes. + */ +unsigned int comedi_buf_read_samples(struct comedi_subdevice *s, + void *data, unsigned int nsamples) +{ + unsigned int max_samples; + unsigned int nbytes; + + /* clamp nsamples to the number of full samples available */ + max_samples = comedi_bytes_to_samples(s, + comedi_buf_read_n_available(s)); + if (nsamples > max_samples) + nsamples = max_samples; + + if (nsamples == 0) + return 0; + + nbytes = comedi_buf_read_alloc(s, + comedi_samples_to_bytes(s, nsamples)); + comedi_buf_memcpy_from(s, data, nbytes); + comedi_buf_read_free(s, nbytes); + comedi_inc_scan_progress(s, nbytes); + s->async->events |= COMEDI_CB_BLOCK; + + return nbytes; +} +EXPORT_SYMBOL_GPL(comedi_buf_read_samples); diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c new file mode 100644 index 000000000..e2114bcf8 --- /dev/null +++ b/drivers/comedi/comedi_fops.c @@ -0,0 +1,3437 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/comedi_fops.c + * comedi kernel module + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org> + * compat ioctls: + * Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk> + * Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/comedi/comedidev.h> +#include <linux/cdev.h> + +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/compat.h> + +#include "comedi_internal.h" + +/* + * comedi_subdevice "runflags" + * COMEDI_SRF_RT: DEPRECATED: command is running real-time + * COMEDI_SRF_ERROR: indicates an COMEDI_CB_ERROR event has occurred + * since the last command was started + * COMEDI_SRF_RUNNING: command is running + * COMEDI_SRF_FREE_SPRIV: free s->private on detach + * + * COMEDI_SRF_BUSY_MASK: runflags that indicate the subdevice is "busy" + */ +#define COMEDI_SRF_RT BIT(1) +#define COMEDI_SRF_ERROR BIT(2) +#define COMEDI_SRF_RUNNING BIT(27) +#define COMEDI_SRF_FREE_SPRIV BIT(31) + +#define COMEDI_SRF_BUSY_MASK (COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING) + +/** + * struct comedi_file - Per-file private data for COMEDI device + * @dev: COMEDI device. + * @read_subdev: Current "read" subdevice. + * @write_subdev: Current "write" subdevice. + * @last_detach_count: Last known detach count. + * @last_attached: Last known attached/detached state. + */ +struct comedi_file { + struct comedi_device *dev; + struct comedi_subdevice *read_subdev; + struct comedi_subdevice *write_subdev; + unsigned int last_detach_count; + unsigned int last_attached:1; +}; + +#define COMEDI_NUM_MINORS 0x100 +#define COMEDI_NUM_SUBDEVICE_MINORS \ + (COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS) + +static unsigned short comedi_num_legacy_minors; +module_param(comedi_num_legacy_minors, ushort, 0444); +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, 0644); +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, 0644); +MODULE_PARM_DESC(comedi_default_buf_maxsize_kb, + "default maximum size of asynchronous buffer in KiB (default " + __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB) ")"); + +static DEFINE_MUTEX(comedi_board_minor_table_lock); +static struct comedi_device +*comedi_board_minor_table[COMEDI_NUM_BOARD_MINORS]; + +static DEFINE_MUTEX(comedi_subdevice_minor_table_lock); +/* Note: indexed by minor - COMEDI_NUM_BOARD_MINORS. */ +static struct comedi_subdevice +*comedi_subdevice_minor_table[COMEDI_NUM_SUBDEVICE_MINORS]; + +static struct class *comedi_class; +static struct cdev comedi_cdev; + +static void comedi_device_init(struct comedi_device *dev) +{ + kref_init(&dev->refcount); + spin_lock_init(&dev->spinlock); + mutex_init(&dev->mutex); + init_rwsem(&dev->attach_lock); + dev->minor = -1; +} + +static void comedi_dev_kref_release(struct kref *kref) +{ + struct comedi_device *dev = + container_of(kref, struct comedi_device, refcount); + + mutex_destroy(&dev->mutex); + put_device(dev->class_dev); + kfree(dev); +} + +/** + * comedi_dev_put() - Release a use of a COMEDI device + * @dev: COMEDI device. + * + * Must be called when a user of a COMEDI device is finished with it. + * When the last user of the COMEDI device calls this function, the + * COMEDI device is destroyed. + * + * Return: 1 if the COMEDI device is destroyed by this call or @dev is + * NULL, otherwise return 0. Callers must not assume the COMEDI + * device is still valid if this function returns 0. + */ +int comedi_dev_put(struct comedi_device *dev) +{ + if (dev) + return kref_put(&dev->refcount, comedi_dev_kref_release); + return 1; +} +EXPORT_SYMBOL_GPL(comedi_dev_put); + +static struct comedi_device *comedi_dev_get(struct comedi_device *dev) +{ + if (dev) + kref_get(&dev->refcount); + return dev; +} + +static void comedi_device_cleanup(struct comedi_device *dev) +{ + struct module *driver_module = NULL; + + if (!dev) + return; + mutex_lock(&dev->mutex); + if (dev->attached) + driver_module = dev->driver->module; + comedi_device_detach(dev); + if (driver_module && dev->use_count) + module_put(driver_module); + mutex_unlock(&dev->mutex); +} + +static bool comedi_clear_board_dev(struct comedi_device *dev) +{ + unsigned int i = dev->minor; + bool cleared = false; + + lockdep_assert_held(&dev->mutex); + 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 int 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 int minor) +{ + struct comedi_subdevice *s; + unsigned int i = minor - COMEDI_NUM_BOARD_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 int minor) +{ + struct comedi_device *dev; + + 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 int minor) +{ + struct comedi_device *dev; + struct comedi_subdevice *s; + unsigned int i = minor - COMEDI_NUM_BOARD_MINORS; + + mutex_lock(&comedi_subdevice_minor_table_lock); + s = comedi_subdevice_minor_table[i]; + dev = comedi_dev_get(s ? s->device : NULL); + mutex_unlock(&comedi_subdevice_minor_table_lock); + return dev; +} + +/** + * comedi_dev_get_from_minor() - Get COMEDI device by minor device number + * @minor: Minor device number. + * + * Finds the COMEDI device associated with the minor device number, if any, + * and increments its reference count. The COMEDI device is prevented from + * being freed until a matching call is made to comedi_dev_put(). + * + * Return: A pointer to the COMEDI device if it exists, with its usage + * reference incremented. Return NULL if no COMEDI device exists with the + * specified minor device number. + */ +struct comedi_device *comedi_dev_get_from_minor(unsigned int minor) +{ + if (minor < COMEDI_NUM_BOARD_MINORS) + return comedi_dev_get_from_board_minor(minor); + + return comedi_dev_get_from_subdevice_minor(minor); +} +EXPORT_SYMBOL_GPL(comedi_dev_get_from_minor); + +static struct comedi_subdevice * +comedi_read_subdevice(const struct comedi_device *dev, unsigned int minor) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (minor >= COMEDI_NUM_BOARD_MINORS) { + s = comedi_subdevice_from_minor(dev, minor); + if (!s || (s->subdev_flags & SDF_CMD_READ)) + return s; + } + return dev->read_subdev; +} + +static struct comedi_subdevice * +comedi_write_subdevice(const struct comedi_device *dev, unsigned int minor) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (minor >= COMEDI_NUM_BOARD_MINORS) { + s = comedi_subdevice_from_minor(dev, minor); + if (!s || (s->subdev_flags & SDF_CMD_WRITE)) + return s; + } + return dev->write_subdev; +} + +static void comedi_file_reset(struct file *file) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_subdevice *s, *read_s, *write_s; + unsigned int minor = iminor(file_inode(file)); + + read_s = dev->read_subdev; + write_s = dev->write_subdev; + if (minor >= COMEDI_NUM_BOARD_MINORS) { + s = comedi_subdevice_from_minor(dev, minor); + if (!s || s->subdev_flags & SDF_CMD_READ) + read_s = s; + if (!s || s->subdev_flags & SDF_CMD_WRITE) + write_s = s; + } + cfp->last_attached = dev->attached; + cfp->last_detach_count = dev->detach_count; + WRITE_ONCE(cfp->read_subdev, read_s); + WRITE_ONCE(cfp->write_subdev, write_s); +} + +static void comedi_file_check(struct file *file) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + + if (cfp->last_attached != dev->attached || + cfp->last_detach_count != dev->detach_count) + comedi_file_reset(file); +} + +static struct comedi_subdevice *comedi_file_read_subdevice(struct file *file) +{ + struct comedi_file *cfp = file->private_data; + + comedi_file_check(file); + return READ_ONCE(cfp->read_subdev); +} + +static struct comedi_subdevice *comedi_file_write_subdevice(struct file *file) +{ + struct comedi_file *cfp = file->private_data; + + comedi_file_check(file); + return READ_ONCE(cfp->write_subdev); +} + +static int resize_async_buffer(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int new_size) +{ + struct comedi_async *async = s->async; + int retval; + + lockdep_assert_held(&dev->mutex); + + if (new_size > async->max_bufsize) + return -EPERM; + + if (s->busy) { + dev_dbg(dev->class_dev, + "subdevice is busy, cannot resize buffer\n"); + return -EBUSY; + } + if (comedi_buf_is_mmapped(s)) { + dev_dbg(dev->class_dev, + "subdevice is mmapped, cannot resize buffer\n"); + return -EBUSY; + } + + /* make sure buffer is an integral number of pages (we round up) */ + new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK; + + retval = comedi_buf_alloc(dev, s, new_size); + if (retval < 0) + return retval; + + if (s->buf_change) { + retval = s->buf_change(dev, s); + if (retval < 0) + return retval; + } + + dev_dbg(dev->class_dev, "subd %d buffer resized to %i bytes\n", + s->index, async->prealloc_bufsz); + return 0; +} + +/* sysfs attribute files */ + +static ssize_t max_read_buffer_kb_show(struct device *csdev, + struct device_attribute *attr, char *buf) +{ + unsigned int minor = MINOR(csdev->devt); + struct comedi_device *dev; + struct comedi_subdevice *s; + unsigned int size = 0; + + dev = comedi_dev_get_from_minor(minor); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->mutex); + s = comedi_read_subdevice(dev, minor); + if (s && (s->subdev_flags & SDF_CMD_READ) && s->async) + size = s->async->max_bufsize / 1024; + mutex_unlock(&dev->mutex); + + comedi_dev_put(dev); + return sysfs_emit(buf, "%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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%u\n", size); +} + +static ssize_t write_buffer_kb_store(struct device *csdev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int minor = MINOR(csdev->devt); + struct comedi_device *dev; + struct comedi_subdevice *s; + unsigned int size; + int err; + + err = kstrtouint(buf, 10, &size); + if (err) + return err; + if (size > (UINT_MAX / 1024)) + return -EINVAL; + size *= 1024; + + dev = comedi_dev_get_from_minor(minor); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->mutex); + s = comedi_write_subdevice(dev, minor); + if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async) + err = resize_async_buffer(dev, s, size); + else + err = -EINVAL; + mutex_unlock(&dev->mutex); + + comedi_dev_put(dev); + return err ? err : count; +} +static DEVICE_ATTR_RW(write_buffer_kb); + +static struct attribute *comedi_dev_attrs[] = { + &dev_attr_max_read_buffer_kb.attr, + &dev_attr_read_buffer_kb.attr, + &dev_attr_max_write_buffer_kb.attr, + &dev_attr_write_buffer_kb.attr, + NULL, +}; +ATTRIBUTE_GROUPS(comedi_dev); + +static void __comedi_clear_subdevice_runflags(struct comedi_subdevice *s, + unsigned int bits) +{ + s->runflags &= ~bits; +} + +static void __comedi_set_subdevice_runflags(struct comedi_subdevice *s, + unsigned int bits) +{ + s->runflags |= bits; +} + +static void comedi_update_subdevice_runflags(struct comedi_subdevice *s, + unsigned int mask, + unsigned int bits) +{ + unsigned long flags; + + spin_lock_irqsave(&s->spin_lock, flags); + __comedi_clear_subdevice_runflags(s, mask); + __comedi_set_subdevice_runflags(s, bits & mask); + spin_unlock_irqrestore(&s->spin_lock, flags); +} + +static unsigned int __comedi_get_subdevice_runflags(struct comedi_subdevice *s) +{ + return s->runflags; +} + +static unsigned int comedi_get_subdevice_runflags(struct comedi_subdevice *s) +{ + unsigned long flags; + unsigned int runflags; + + spin_lock_irqsave(&s->spin_lock, flags); + runflags = __comedi_get_subdevice_runflags(s); + spin_unlock_irqrestore(&s->spin_lock, flags); + return runflags; +} + +static bool comedi_is_runflags_running(unsigned int runflags) +{ + return runflags & COMEDI_SRF_RUNNING; +} + +static bool comedi_is_runflags_in_error(unsigned int runflags) +{ + return runflags & COMEDI_SRF_ERROR; +} + +/** + * comedi_is_subdevice_running() - Check if async command running on subdevice + * @s: COMEDI subdevice. + * + * Return: %true if an asynchronous COMEDI command is active on the + * subdevice, else %false. + */ +bool comedi_is_subdevice_running(struct comedi_subdevice *s) +{ + unsigned int runflags = comedi_get_subdevice_runflags(s); + + return comedi_is_runflags_running(runflags); +} +EXPORT_SYMBOL_GPL(comedi_is_subdevice_running); + +static bool __comedi_is_subdevice_running(struct comedi_subdevice *s) +{ + unsigned int runflags = __comedi_get_subdevice_runflags(s); + + return comedi_is_runflags_running(runflags); +} + +bool comedi_can_auto_free_spriv(struct comedi_subdevice *s) +{ + unsigned int runflags = __comedi_get_subdevice_runflags(s); + + return runflags & COMEDI_SRF_FREE_SPRIV; +} + +/** + * comedi_set_spriv_auto_free() - Mark subdevice private data as freeable + * @s: COMEDI subdevice. + * + * Mark the subdevice as having a pointer to private data that can be + * automatically freed when the COMEDI device is detached from the low-level + * driver. + */ +void comedi_set_spriv_auto_free(struct comedi_subdevice *s) +{ + __comedi_set_subdevice_runflags(s, COMEDI_SRF_FREE_SPRIV); +} +EXPORT_SYMBOL_GPL(comedi_set_spriv_auto_free); + +/** + * comedi_alloc_spriv - Allocate memory for the subdevice private data + * @s: COMEDI subdevice. + * @size: Size of the memory to allocate. + * + * Allocate memory for the subdevice private data and point @s->private + * to it. The memory will be freed automatically when the COMEDI device + * is detached from the low-level driver. + * + * Return: A pointer to the allocated memory @s->private on success. + * Return NULL on failure. + */ +void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size) +{ + s->private = kzalloc(size, GFP_KERNEL); + if (s->private) + comedi_set_spriv_auto_free(s); + 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; + + lockdep_assert_held(&dev->mutex); + comedi_update_subdevice_runflags(s, COMEDI_SRF_RUNNING, 0); + if (async) { + comedi_buf_reset(s); + async->inttrig = NULL; + kfree(async->cmd.chanlist); + async->cmd.chanlist = NULL; + s->busy = NULL; + wake_up_interruptible_all(&async->wait_head); + } else { + dev_err(dev->class_dev, + "BUG: (?) %s called with async=NULL\n", __func__); + s->busy = NULL; + } +} + +static int do_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int ret = 0; + + lockdep_assert_held(&dev->mutex); + 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; + + lockdep_assert_held(&dev->mutex); + 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; + + lockdep_assert_held(&dev->mutex); + if (!dev->attached) + return 0; + + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + if (s->busy) + return 1; + if (s->async && comedi_buf_is_mmapped(s)) + return 1; + } + + return 0; +} + +/* + * COMEDI_DEVCONFIG ioctl + * attaches (and configures) or detaches a legacy device + * + * arg: + * pointer to comedi_devconfig structure (NULL if detaching) + * + * reads: + * comedi_devconfig structure (if attaching) + * + * writes: + * nothing + */ +static int do_devconfig_ioctl(struct comedi_device *dev, + struct comedi_devconfig __user *arg) +{ + struct comedi_devconfig it; + + lockdep_assert_held(&dev->mutex); + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!arg) { + if (is_device_busy(dev)) + return -EBUSY; + if (dev->attached) { + struct module *driver_module = dev->driver->module; + + comedi_device_detach(dev); + module_put(driver_module); + } + return 0; + } + + if (copy_from_user(&it, arg, sizeof(it))) + return -EFAULT; + + it.board_name[COMEDI_NAMELEN - 1] = 0; + + if (it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + dev_warn(dev->class_dev, + "comedi_config --init_data is deprecated\n"); + return -EINVAL; + } + + if (dev->minor >= comedi_num_legacy_minors) + /* don't re-use dynamically allocated comedi devices */ + return -EBUSY; + + /* This increments the driver module count on success. */ + return comedi_device_attach(dev, &it); +} + +/* + * COMEDI_BUFCONFIG ioctl + * buffer configuration + * + * arg: + * pointer to comedi_bufconfig structure + * + * reads: + * comedi_bufconfig structure + * + * writes: + * modified comedi_bufconfig structure + */ +static int do_bufconfig_ioctl(struct comedi_device *dev, + struct comedi_bufconfig __user *arg) +{ + struct comedi_bufconfig bc; + struct comedi_async *async; + struct comedi_subdevice *s; + int retval = 0; + + lockdep_assert_held(&dev->mutex); + if (copy_from_user(&bc, arg, sizeof(bc))) + return -EFAULT; + + if (bc.subdevice >= dev->n_subdevices) + return -EINVAL; + + s = &dev->subdevices[bc.subdevice]; + async = s->async; + + if (!async) { + dev_dbg(dev->class_dev, + "subdevice does not have async capability\n"); + bc.size = 0; + bc.maximum_size = 0; + goto copyback; + } + + if (bc.maximum_size) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + async->max_bufsize = bc.maximum_size; + } + + if (bc.size) { + retval = resize_async_buffer(dev, s, bc.size); + if (retval < 0) + return retval; + } + + bc.size = async->prealloc_bufsz; + bc.maximum_size = async->max_bufsize; + +copyback: + if (copy_to_user(arg, &bc, sizeof(bc))) + return -EFAULT; + + return 0; +} + +/* + * COMEDI_DEVINFO ioctl + * device info + * + * arg: + * pointer to comedi_devinfo structure + * + * reads: + * nothing + * + * writes: + * comedi_devinfo structure + */ +static int do_devinfo_ioctl(struct comedi_device *dev, + struct comedi_devinfo __user *arg, + struct file *file) +{ + struct comedi_subdevice *s; + struct comedi_devinfo devinfo; + + lockdep_assert_held(&dev->mutex); + memset(&devinfo, 0, sizeof(devinfo)); + + /* fill devinfo structure */ + devinfo.version_code = COMEDI_VERSION_CODE; + devinfo.n_subdevs = dev->n_subdevices; + strscpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN); + strscpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN); + + s = comedi_file_read_subdevice(file); + if (s) + devinfo.read_subdevice = s->index; + else + devinfo.read_subdevice = -1; + + s = comedi_file_write_subdevice(file); + if (s) + devinfo.write_subdevice = s->index; + else + devinfo.write_subdevice = -1; + + if (copy_to_user(arg, &devinfo, sizeof(devinfo))) + return -EFAULT; + + return 0; +} + +/* + * COMEDI_SUBDINFO ioctl + * subdevices info + * + * arg: + * pointer to array of comedi_subdinfo structures + * + * reads: + * nothing + * + * writes: + * array of comedi_subdinfo structures + */ +static int do_subdinfo_ioctl(struct comedi_device *dev, + struct comedi_subdinfo __user *arg, void *file) +{ + int ret, i; + struct comedi_subdinfo *tmp, *us; + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + tmp = kcalloc(dev->n_subdevices, sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + /* fill subdinfo structs */ + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + us = tmp + i; + + us->type = s->type; + us->n_chan = s->n_chan; + us->subd_flags = s->subdev_flags; + if (comedi_is_subdevice_running(s)) + us->subd_flags |= SDF_RUNNING; +#define TIMER_nanosec 5 /* backwards compatibility */ + us->timer_type = TIMER_nanosec; + us->len_chanlist = s->len_chanlist; + us->maxdata = s->maxdata; + if (s->range_table) { + us->range_type = + (i << 24) | (0 << 16) | (s->range_table->length); + } else { + us->range_type = 0; /* XXX */ + } + + if (s->busy) + us->subd_flags |= SDF_BUSY; + if (s->busy == file) + us->subd_flags |= SDF_BUSY_OWNER; + if (s->lock) + us->subd_flags |= SDF_LOCKED; + if (s->lock == file) + us->subd_flags |= SDF_LOCK_OWNER; + if (!s->maxdata && s->maxdata_list) + us->subd_flags |= SDF_MAXDATA; + if (s->range_table_list) + us->subd_flags |= SDF_RANGETYPE; + if (s->do_cmd) + us->subd_flags |= SDF_CMD; + + if (s->insn_bits != &insn_inval) + us->insn_bits_support = COMEDI_SUPPORTED; + else + us->insn_bits_support = COMEDI_UNSUPPORTED; + } + + ret = copy_to_user(arg, tmp, dev->n_subdevices * sizeof(*tmp)); + + kfree(tmp); + + return ret ? -EFAULT : 0; +} + +/* + * COMEDI_CHANINFO ioctl + * subdevice channel info + * + * arg: + * pointer to comedi_chaninfo structure + * + * reads: + * comedi_chaninfo structure + * + * writes: + * array of maxdata values to chaninfo->maxdata_list if requested + * array of range table lengths to chaninfo->range_table_list if requested + */ +static int do_chaninfo_ioctl(struct comedi_device *dev, + struct comedi_chaninfo *it) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + + 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; + } + } + + return 0; +} + +/* + * COMEDI_BUFINFO ioctl + * buffer information + * + * arg: + * pointer to comedi_bufinfo structure + * + * reads: + * comedi_bufinfo structure + * + * writes: + * modified comedi_bufinfo structure + */ +static int do_bufinfo_ioctl(struct comedi_device *dev, + struct comedi_bufinfo __user *arg, void *file) +{ + struct comedi_bufinfo bi; + struct comedi_subdevice *s; + struct comedi_async *async; + unsigned int runflags; + int retval = 0; + bool become_nonbusy = false; + + lockdep_assert_held(&dev->mutex); + if (copy_from_user(&bi, arg, sizeof(bi))) + return -EFAULT; + + if (bi.subdevice >= dev->n_subdevices) + return -EINVAL; + + s = &dev->subdevices[bi.subdevice]; + + async = s->async; + + if (!async || s->busy != file) + return -EINVAL; + + runflags = comedi_get_subdevice_runflags(s); + if (!(async->cmd.flags & CMDF_WRITE)) { + /* command was set up in "read" direction */ + if (bi.bytes_read) { + comedi_buf_read_alloc(s, bi.bytes_read); + bi.bytes_read = comedi_buf_read_free(s, bi.bytes_read); + } + /* + * If nothing left to read, and command has stopped, and + * {"read" position not updated or command stopped normally}, + * then become non-busy. + */ + if (comedi_buf_read_n_available(s) == 0 && + !comedi_is_runflags_running(runflags) && + (bi.bytes_read == 0 || + !comedi_is_runflags_in_error(runflags))) { + become_nonbusy = true; + if (comedi_is_runflags_in_error(runflags)) + retval = -EPIPE; + } + bi.bytes_written = 0; + } else { + /* command was set up in "write" direction */ + if (!comedi_is_runflags_running(runflags)) { + bi.bytes_written = 0; + become_nonbusy = true; + if (comedi_is_runflags_in_error(runflags)) + retval = -EPIPE; + } else if (bi.bytes_written) { + comedi_buf_write_alloc(s, bi.bytes_written); + bi.bytes_written = + comedi_buf_write_free(s, bi.bytes_written); + } + bi.bytes_read = 0; + } + + 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; + + if (become_nonbusy) + do_become_nonbusy(dev, s); + + if (retval) + return retval; + + 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: + case INSN_CONFIG_TIMER_1: + if (insn->n == 5) + return 0; + break; + case INSN_CONFIG_DIGITAL_TRIG: + if (insn->n == 6) + return 0; + break; + case INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS: + if (insn->n >= 4) + return 0; + break; + /* + * by default we allow the insn since we don't have checks for + * all possible cases yet + */ + default: + pr_warn("No check for data length of config insn id %i is implemented\n", + data[0]); + pr_warn("Add a check to %s in %s\n", __func__, __FILE__); + pr_warn("Assuming n=%i is correct\n", insn->n); + return 0; + } + return -EINVAL; +} + +static int check_insn_device_config_length(struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + + switch (data[0]) { + case INSN_DEVICE_CONFIG_TEST_ROUTE: + case INSN_DEVICE_CONFIG_CONNECT_ROUTE: + case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE: + if (insn->n == 3) + return 0; + break; + case INSN_DEVICE_CONFIG_GET_ROUTES: + /* + * Big enough for config_id and the length of the userland + * memory buffer. Additional length should be in factors of 2 + * to communicate any returned route pairs (source,destination). + */ + if (insn->n >= 2) + return 0; + break; + } + return -EINVAL; +} + +/** + * get_valid_routes() - Calls low-level driver get_valid_routes function to + * either return a count of valid routes to user, or copy + * of list of all valid device routes to buffer in + * userspace. + * @dev: comedi device pointer + * @data: data from user insn call. The length of the data must be >= 2. + * data[0] must contain the INSN_DEVICE_CONFIG config_id. + * data[1](input) contains the number of _pairs_ for which memory is + * allotted from the user. If the user specifies '0', then only + * the number of pairs available is returned. + * data[1](output) returns either the number of pairs available (if none + * where requested) or the number of _pairs_ that are copied back + * to the user. + * data[2::2] returns each (source, destination) pair. + * + * Return: -EINVAL if low-level driver does not allocate and return routes as + * expected. Returns 0 otherwise. + */ +static int get_valid_routes(struct comedi_device *dev, unsigned int *data) +{ + lockdep_assert_held(&dev->mutex); + data[1] = dev->get_valid_routes(dev, data[1], data + 2); + return 0; +} + +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; + + lockdep_assert_held(&dev->mutex); + if (insn->insn & INSN_MASK_SPECIAL) { + /* a non-subdevice instruction */ + + switch (insn->insn) { + case INSN_GTOD: + { + struct timespec64 tv; + + if (insn->n != 2) { + ret = -EINVAL; + break; + } + + ktime_get_real_ts64(&tv); + /* unsigned data safe until 2106 */ + data[0] = (unsigned int)tv.tv_sec; + data[1] = tv.tv_nsec / NSEC_PER_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; + case INSN_DEVICE_CONFIG: + ret = check_insn_device_config_length(insn, data); + if (ret) + break; + + if (data[0] == INSN_DEVICE_CONFIG_GET_ROUTES) { + /* + * data[1] should be the number of _pairs_ that + * the memory can hold. + */ + data[1] = (insn->n - 2) / 2; + ret = get_valid_routes(dev, data); + break; + } + + /* other global device config instructions. */ + ret = dev->insn_device_config(dev, insn, data); + break; + default: + dev_dbg(dev->class_dev, "invalid insn\n"); + ret = -EINVAL; + break; + } + } else { + /* a subdevice instruction */ + unsigned int maxdata; + + if (insn->subdev >= dev->n_subdevices) { + dev_dbg(dev->class_dev, "subdevice %d out of range\n", + insn->subdev); + ret = -EINVAL; + goto out; + } + s = &dev->subdevices[insn->subdev]; + + if (s->type == COMEDI_SUBD_UNUSED) { + dev_dbg(dev->class_dev, "%d not usable subdevice\n", + insn->subdev); + ret = -EIO; + goto out; + } + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != file) { + dev_dbg(dev->class_dev, "device locked\n"); + ret = -EACCES; + goto out; + } + + ret = comedi_check_chanlist(s, 1, &insn->chanspec); + if (ret < 0) { + ret = -EINVAL; + dev_dbg(dev->class_dev, "bad chanspec\n"); + goto out; + } + + if (s->busy) { + ret = -EBUSY; + goto out; + } + /* This looks arbitrary. It is. */ + s->busy = parse_insn; + switch (insn->insn) { + case INSN_READ: + ret = s->insn_read(dev, s, insn, data); + if (ret == -ETIMEDOUT) { + dev_dbg(dev->class_dev, + "subdevice %d read instruction timed out\n", + s->index); + } + break; + case INSN_WRITE: + maxdata = s->maxdata_list + ? s->maxdata_list[CR_CHAN(insn->chanspec)] + : s->maxdata; + for (i = 0; i < insn->n; ++i) { + if (data[i] > maxdata) { + ret = -EINVAL; + dev_dbg(dev->class_dev, + "bad data value(s)\n"); + break; + } + } + if (ret == 0) { + ret = s->insn_write(dev, s, insn, data); + if (ret == -ETIMEDOUT) { + dev_dbg(dev->class_dev, + "subdevice %d write instruction timed out\n", + s->index); + } + } + break; + case INSN_BITS: + if (insn->n != 2) { + ret = -EINVAL; + } else { + /* + * Most drivers ignore the base channel in + * insn->chanspec. Fix this here if + * the subdevice has <= 32 channels. + */ + unsigned int orig_mask = data[0]; + unsigned int shift = 0; + + if (s->n_chan <= 32) { + shift = CR_CHAN(insn->chanspec); + if (shift > 0) { + insn->chanspec = 0; + data[0] <<= shift; + data[1] <<= shift; + } + } + ret = s->insn_bits(dev, s, insn, data); + data[0] = orig_mask; + if (shift > 0) + data[1] >>= shift; + } + break; + case INSN_CONFIG: + ret = check_insn_config_length(insn, data); + if (ret) + break; + ret = s->insn_config(dev, s, insn, data); + break; + default: + ret = -EINVAL; + break; + } + + s->busy = NULL; + } + +out: + return ret; +} + +/* + * COMEDI_INSNLIST ioctl + * synchronous instruction list + * + * arg: + * pointer to comedi_insnlist structure + * + * reads: + * comedi_insnlist structure + * array of comedi_insn structures from insnlist->insns pointer + * data (for writes) from insns[].data pointers + * + * writes: + * data (for reads) to insns[].data pointers + */ +/* arbitrary limits */ +#define MIN_SAMPLES 16 +#define MAX_SAMPLES 65536 +static int do_insnlist_ioctl(struct comedi_device *dev, + struct comedi_insn *insns, + unsigned int n_insns, + void *file) +{ + unsigned int *data = NULL; + unsigned int max_n_data_required = MIN_SAMPLES; + int i = 0; + int ret = 0; + + lockdep_assert_held(&dev->mutex); + + /* Determine maximum memory needed for all instructions. */ + for (i = 0; i < n_insns; ++i) { + if (insns[i].n > MAX_SAMPLES) { + dev_dbg(dev->class_dev, + "number of samples too large\n"); + ret = -EINVAL; + goto error; + } + max_n_data_required = max(max_n_data_required, insns[i].n); + } + + /* Allocate scratch space for all instruction data. */ + data = kmalloc_array(max_n_data_required, sizeof(unsigned int), + GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto error; + } + + for (i = 0; i < n_insns; ++i) { + 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(data); + + if (ret < 0) + return ret; + return i; +} + +/* + * COMEDI_INSN ioctl + * synchronous instruction + * + * arg: + * pointer to comedi_insn structure + * + * reads: + * comedi_insn structure + * data (for writes) from insn->data pointer + * + * writes: + * data (for reads) to insn->data pointer + */ +static int do_insn_ioctl(struct comedi_device *dev, + struct comedi_insn *insn, void *file) +{ + unsigned int *data = NULL; + unsigned int n_data = MIN_SAMPLES; + int ret = 0; + + lockdep_assert_held(&dev->mutex); + + n_data = max(n_data, insn->n); + + /* This is where the behavior of insn and insnlist deviate. */ + if (insn->n > MAX_SAMPLES) { + insn->n = MAX_SAMPLES; + n_data = MAX_SAMPLES; + } + + data = kmalloc_array(n_data, sizeof(unsigned int), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto error; + } + + 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 *cmd) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (cmd->subdev >= dev->n_subdevices) { + dev_dbg(dev->class_dev, "%d no such subdevice\n", cmd->subdev); + return -ENODEV; + } + + s = &dev->subdevices[cmd->subdev]; + + if (s->type == COMEDI_SUBD_UNUSED) { + dev_dbg(dev->class_dev, "%d not valid subdevice\n", + cmd->subdev); + return -EIO; + } + + if (!s->do_cmd || !s->do_cmdtest || !s->async) { + dev_dbg(dev->class_dev, + "subdevice %d does not support commands\n", + cmd->subdev); + return -EIO; + } + + /* make sure channel/gain list isn't too long */ + if (cmd->chanlist_len > s->len_chanlist) { + dev_dbg(dev->class_dev, "channel/gain list too long %d > %d\n", + cmd->chanlist_len, s->len_chanlist); + return -EINVAL; + } + + /* + * Set the CMDF_WRITE flag to the correct state if the subdevice + * supports only "read" commands or only "write" commands. + */ + switch (s->subdev_flags & (SDF_CMD_READ | SDF_CMD_WRITE)) { + case SDF_CMD_READ: + cmd->flags &= ~CMDF_WRITE; + break; + case SDF_CMD_WRITE: + cmd->flags |= CMDF_WRITE; + break; + default: + break; + } + + return 0; +} + +static int __comedi_get_user_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int __user *user_chanlist, + struct comedi_cmd *cmd) +{ + unsigned int *chanlist; + int ret; + + lockdep_assert_held(&dev->mutex); + cmd->chanlist = NULL; + chanlist = memdup_user(user_chanlist, + cmd->chanlist_len * sizeof(unsigned int)); + if (IS_ERR(chanlist)) + return PTR_ERR(chanlist); + + /* make sure each element in channel/gain list is valid */ + ret = comedi_check_chanlist(s, cmd->chanlist_len, chanlist); + if (ret < 0) { + kfree(chanlist); + return ret; + } + + cmd->chanlist = chanlist; + + return 0; +} + +/* + * COMEDI_CMD ioctl + * asynchronous acquisition command set-up + * + * arg: + * pointer to comedi_cmd structure + * + * reads: + * comedi_cmd structure + * channel/range list from cmd->chanlist pointer + * + * writes: + * possibly modified comedi_cmd structure (when -EAGAIN returned) + */ +static int do_cmd_ioctl(struct comedi_device *dev, + struct comedi_cmd *cmd, bool *copy, void *file) +{ + struct comedi_subdevice *s; + struct comedi_async *async; + unsigned int __user *user_chanlist; + int ret; + + lockdep_assert_held(&dev->mutex); + + /* do some simple cmd validation */ + ret = __comedi_get_user_cmd(dev, cmd); + if (ret) + return ret; + + /* save user's chanlist pointer so it can be restored later */ + user_chanlist = (unsigned int __user *)cmd->chanlist; + + s = &dev->subdevices[cmd->subdev]; + async = s->async; + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != file) { + dev_dbg(dev->class_dev, "subdevice locked\n"); + return -EACCES; + } + + /* are we busy? */ + if (s->busy) { + dev_dbg(dev->class_dev, "subdevice busy\n"); + return -EBUSY; + } + + /* make sure channel/gain list isn't too short */ + if (cmd->chanlist_len < 1) { + dev_dbg(dev->class_dev, "channel/gain list too short %u < 1\n", + cmd->chanlist_len); + return -EINVAL; + } + + async->cmd = *cmd; + async->cmd.data = NULL; + + /* load channel/gain list */ + ret = __comedi_get_user_chanlist(dev, s, user_chanlist, &async->cmd); + if (ret) + goto cleanup; + + ret = s->do_cmdtest(dev, s, &async->cmd); + + if (async->cmd.flags & CMDF_BOGUS || ret) { + dev_dbg(dev->class_dev, "test returned %d\n", ret); + *cmd = async->cmd; + /* restore chanlist pointer before copying back */ + cmd->chanlist = (unsigned int __force *)user_chanlist; + cmd->data = NULL; + *copy = true; + ret = -EAGAIN; + goto cleanup; + } + + if (!async->prealloc_bufsz) { + ret = -ENOMEM; + dev_dbg(dev->class_dev, "no buffer (?)\n"); + goto cleanup; + } + + comedi_buf_reset(s); + + async->cb_mask = COMEDI_CB_BLOCK | COMEDI_CB_CANCEL_MASK; + if (async->cmd.flags & CMDF_WAKE_EOS) + async->cb_mask |= COMEDI_CB_EOS; + + comedi_update_subdevice_runflags(s, COMEDI_SRF_BUSY_MASK, + COMEDI_SRF_RUNNING); + + /* + * Set s->busy _after_ setting COMEDI_SRF_RUNNING flag to avoid + * race with comedi_read() or comedi_write(). + */ + s->busy = file; + ret = s->do_cmd(dev, s); + if (ret == 0) + return 0; + +cleanup: + do_become_nonbusy(dev, s); + + return ret; +} + +/* + * COMEDI_CMDTEST ioctl + * asynchronous acquisition command testing + * + * arg: + * pointer to comedi_cmd structure + * + * reads: + * comedi_cmd structure + * channel/range list from cmd->chanlist pointer + * + * writes: + * possibly modified comedi_cmd structure + */ +static int do_cmdtest_ioctl(struct comedi_device *dev, + struct comedi_cmd *cmd, bool *copy, void *file) +{ + struct comedi_subdevice *s; + unsigned int __user *user_chanlist; + int ret; + + lockdep_assert_held(&dev->mutex); + + /* do some simple cmd validation */ + ret = __comedi_get_user_cmd(dev, cmd); + if (ret) + return ret; + + /* save user's chanlist pointer so it can be restored later */ + user_chanlist = (unsigned int __user *)cmd->chanlist; + + s = &dev->subdevices[cmd->subdev]; + + /* user_chanlist can be NULL for COMEDI_CMDTEST ioctl */ + if (user_chanlist) { + /* load channel/gain list */ + ret = __comedi_get_user_chanlist(dev, s, user_chanlist, cmd); + if (ret) + return ret; + } + + ret = s->do_cmdtest(dev, s, cmd); + + kfree(cmd->chanlist); /* free kernel copy of user chanlist */ + + /* restore chanlist pointer before copying back */ + cmd->chanlist = (unsigned int __force *)user_chanlist; + *copy = true; + + return ret; +} + +/* + * COMEDI_LOCK ioctl + * lock subdevice + * + * arg: + * subdevice number + * + * reads: + * nothing + * + * writes: + * nothing + */ +static int do_lock_ioctl(struct comedi_device *dev, unsigned long arg, + void *file) +{ + int ret = 0; + unsigned long flags; + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (arg >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[arg]; + + spin_lock_irqsave(&s->spin_lock, flags); + if (s->busy || s->lock) + ret = -EBUSY; + else + s->lock = file; + spin_unlock_irqrestore(&s->spin_lock, flags); + + return ret; +} + +/* + * COMEDI_UNLOCK ioctl + * unlock subdevice + * + * arg: + * subdevice number + * + * reads: + * nothing + * + * writes: + * nothing + */ +static int do_unlock_ioctl(struct comedi_device *dev, unsigned long arg, + void *file) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (arg >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[arg]; + + if (s->busy) + return -EBUSY; + + if (s->lock && s->lock != file) + return -EACCES; + + if (s->lock == file) + s->lock = NULL; + + return 0; +} + +/* + * COMEDI_CANCEL ioctl + * cancel asynchronous acquisition + * + * arg: + * subdevice number + * + * reads: + * nothing + * + * writes: + * nothing + */ +static int do_cancel_ioctl(struct comedi_device *dev, unsigned long arg, + void *file) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (arg >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[arg]; + if (!s->async) + return -EINVAL; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + return do_cancel(dev, s); +} + +/* + * COMEDI_POLL ioctl + * instructs driver to synchronize buffers + * + * arg: + * subdevice number + * + * reads: + * nothing + * + * writes: + * nothing + */ +static int do_poll_ioctl(struct comedi_device *dev, unsigned long arg, + void *file) +{ + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->mutex); + if (arg >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[arg]; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + if (s->poll) + return s->poll(dev, s); + + return -EINVAL; +} + +/* + * COMEDI_SETRSUBD ioctl + * sets the current "read" subdevice on a per-file basis + * + * arg: + * subdevice number + * + * reads: + * nothing + * + * writes: + * nothing + */ +static int do_setrsubd_ioctl(struct comedi_device *dev, unsigned long arg, + struct file *file) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_subdevice *s_old, *s_new; + + lockdep_assert_held(&dev->mutex); + if (arg >= dev->n_subdevices) + return -EINVAL; + + s_new = &dev->subdevices[arg]; + s_old = comedi_file_read_subdevice(file); + if (s_old == s_new) + return 0; /* no change */ + + if (!(s_new->subdev_flags & SDF_CMD_READ)) + return -EINVAL; + + /* + * Check the file isn't still busy handling a "read" command on the + * old subdevice (if any). + */ + if (s_old && s_old->busy == file && s_old->async && + !(s_old->async->cmd.flags & CMDF_WRITE)) + return -EBUSY; + + WRITE_ONCE(cfp->read_subdev, s_new); + return 0; +} + +/* + * COMEDI_SETWSUBD ioctl + * sets the current "write" subdevice on a per-file basis + * + * arg: + * subdevice number + * + * reads: + * nothing + * + * writes: + * nothing + */ +static int do_setwsubd_ioctl(struct comedi_device *dev, unsigned long arg, + struct file *file) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_subdevice *s_old, *s_new; + + lockdep_assert_held(&dev->mutex); + if (arg >= dev->n_subdevices) + return -EINVAL; + + s_new = &dev->subdevices[arg]; + s_old = comedi_file_write_subdevice(file); + if (s_old == s_new) + return 0; /* no change */ + + if (!(s_new->subdev_flags & SDF_CMD_WRITE)) + return -EINVAL; + + /* + * Check the file isn't still busy handling a "write" command on the + * old subdevice (if any). + */ + if (s_old && s_old->busy == file && s_old->async && + (s_old->async->cmd.flags & CMDF_WRITE)) + return -EBUSY; + + WRITE_ONCE(cfp->write_subdev, s_new); + return 0; +} + +static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + unsigned int minor = iminor(file_inode(file)); + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + int rc; + + mutex_lock(&dev->mutex); + + /* + * Device config is special, because it must work on + * an unconfigured device. + */ + if (cmd == COMEDI_DEVCONFIG) { + if (minor >= COMEDI_NUM_BOARD_MINORS) { + /* Device config not appropriate on non-board minors. */ + rc = -ENOTTY; + goto done; + } + rc = do_devconfig_ioctl(dev, + (struct comedi_devconfig __user *)arg); + if (rc == 0) { + if (arg == 0 && + dev->minor >= comedi_num_legacy_minors) { + /* + * Successfully unconfigured a dynamically + * allocated device. Try and remove it. + */ + if (comedi_clear_board_dev(dev)) { + mutex_unlock(&dev->mutex); + comedi_free_board_dev(dev); + return rc; + } + } + } + goto done; + } + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + goto done; + } + + switch (cmd) { + case COMEDI_BUFCONFIG: + rc = do_bufconfig_ioctl(dev, + (struct comedi_bufconfig __user *)arg); + break; + case COMEDI_DEVINFO: + rc = do_devinfo_ioctl(dev, (struct comedi_devinfo __user *)arg, + file); + break; + case COMEDI_SUBDINFO: + rc = do_subdinfo_ioctl(dev, + (struct comedi_subdinfo __user *)arg, + file); + break; + case COMEDI_CHANINFO: { + struct comedi_chaninfo it; + + if (copy_from_user(&it, (void __user *)arg, sizeof(it))) + rc = -EFAULT; + else + rc = do_chaninfo_ioctl(dev, &it); + break; + } + case COMEDI_RANGEINFO: { + struct comedi_rangeinfo it; + + if (copy_from_user(&it, (void __user *)arg, sizeof(it))) + rc = -EFAULT; + else + rc = do_rangeinfo_ioctl(dev, &it); + 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: { + struct comedi_cmd cmd; + bool copy = false; + + if (copy_from_user(&cmd, (void __user *)arg, sizeof(cmd))) { + rc = -EFAULT; + break; + } + rc = do_cmd_ioctl(dev, &cmd, ©, file); + if (copy && copy_to_user((void __user *)arg, &cmd, sizeof(cmd))) + rc = -EFAULT; + break; + } + case COMEDI_CMDTEST: { + struct comedi_cmd cmd; + bool copy = false; + + if (copy_from_user(&cmd, (void __user *)arg, sizeof(cmd))) { + rc = -EFAULT; + break; + } + rc = do_cmdtest_ioctl(dev, &cmd, ©, file); + if (copy && copy_to_user((void __user *)arg, &cmd, sizeof(cmd))) + rc = -EFAULT; + break; + } + case COMEDI_INSNLIST: { + struct comedi_insnlist insnlist; + struct comedi_insn *insns = NULL; + + if (copy_from_user(&insnlist, (void __user *)arg, + sizeof(insnlist))) { + rc = -EFAULT; + break; + } + insns = kcalloc(insnlist.n_insns, sizeof(*insns), GFP_KERNEL); + if (!insns) { + rc = -ENOMEM; + break; + } + if (copy_from_user(insns, insnlist.insns, + sizeof(*insns) * insnlist.n_insns)) { + rc = -EFAULT; + kfree(insns); + break; + } + rc = do_insnlist_ioctl(dev, insns, insnlist.n_insns, file); + kfree(insns); + break; + } + case COMEDI_INSN: { + struct comedi_insn insn; + + if (copy_from_user(&insn, (void __user *)arg, sizeof(insn))) + rc = -EFAULT; + else + rc = do_insn_ioctl(dev, &insn, file); + break; + } + case COMEDI_POLL: + rc = do_poll_ioctl(dev, arg, file); + break; + case COMEDI_SETRSUBD: + rc = do_setrsubd_ioctl(dev, arg, file); + break; + case COMEDI_SETWSUBD: + rc = do_setwsubd_ioctl(dev, arg, file); + break; + default: + rc = -ENOTTY; + break; + } + +done: + mutex_unlock(&dev->mutex); + return rc; +} + +static void comedi_vm_open(struct vm_area_struct *area) +{ + struct comedi_buf_map *bm; + + bm = area->vm_private_data; + comedi_buf_map_get(bm); +} + +static void comedi_vm_close(struct vm_area_struct *area) +{ + struct comedi_buf_map *bm; + + bm = area->vm_private_data; + comedi_buf_map_put(bm); +} + +static int comedi_vm_access(struct vm_area_struct *vma, unsigned long addr, + void *buf, int len, int write) +{ + struct comedi_buf_map *bm = vma->vm_private_data; + unsigned long offset = + addr - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT); + + if (len < 0) + return -EINVAL; + if (len > vma->vm_end - addr) + len = vma->vm_end - addr; + return comedi_buf_map_access(bm, offset, buf, len, write); +} + +static const struct vm_operations_struct comedi_vm_ops = { + .open = comedi_vm_open, + .close = comedi_vm_close, + .access = comedi_vm_access, +}; + +static int comedi_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_subdevice *s; + struct comedi_async *async; + struct comedi_buf_map *bm = NULL; + struct comedi_buf_page *buf; + unsigned long start = vma->vm_start; + unsigned long size; + int n_pages; + int i; + int retval = 0; + + /* + * 'trylock' avoids circular dependency with current->mm->mmap_lock + * and down-reading &dev->attach_lock should normally succeed without + * contention unless the device is in the process of being attached + * or detached. + */ + if (!down_read_trylock(&dev->attach_lock)) + return -EAGAIN; + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + retval = -ENODEV; + goto done; + } + + if (vma->vm_flags & VM_WRITE) + s = comedi_file_write_subdevice(file); + else + s = comedi_file_read_subdevice(file); + if (!s) { + retval = -EINVAL; + goto done; + } + + async = s->async; + if (!async) { + retval = -EINVAL; + goto done; + } + + if (vma->vm_pgoff != 0) { + dev_dbg(dev->class_dev, "mmap() offset must be 0.\n"); + retval = -EINVAL; + goto done; + } + + size = vma->vm_end - vma->vm_start; + if (size > async->prealloc_bufsz) { + retval = -EFAULT; + goto done; + } + if (offset_in_page(size)) { + retval = -EFAULT; + goto done; + } + + n_pages = vma_pages(vma); + + /* 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; + } + if (bm->dma_dir != DMA_NONE) { + /* + * DMA buffer was allocated as a single block. + * Address is in page_list[0]. + */ + buf = &bm->page_list[0]; + retval = dma_mmap_coherent(bm->dma_hw_dev, vma, buf->virt_addr, + buf->dma_addr, n_pages * PAGE_SIZE); + } else { + for (i = 0; i < n_pages; ++i) { + unsigned long pfn; + + buf = &bm->page_list[i]; + pfn = page_to_pfn(virt_to_page(buf->virt_addr)); + retval = remap_pfn_range(vma, start, pfn, PAGE_SIZE, + PAGE_SHARED); + if (retval) + break; + + start += PAGE_SIZE; + } + } + + if (retval == 0) { + vma->vm_ops = &comedi_vm_ops; + vma->vm_private_data = bm; + + vma->vm_ops->open(vma); + } + +done: + up_read(&dev->attach_lock); + comedi_buf_map_put(bm); /* put reference to buf map - okay if NULL */ + return retval; +} + +static __poll_t comedi_poll(struct file *file, poll_table *wait) +{ + __poll_t mask = 0; + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_subdevice *s, *s_read; + + down_read(&dev->attach_lock); + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + goto done; + } + + s = comedi_file_read_subdevice(file); + s_read = s; + if (s && s->async) { + poll_wait(file, &s->async->wait_head, wait); + if (s->busy != file || !comedi_is_subdevice_running(s) || + (s->async->cmd.flags & CMDF_WRITE) || + comedi_buf_read_n_available(s) > 0) + mask |= EPOLLIN | EPOLLRDNORM; + } + + s = comedi_file_write_subdevice(file); + if (s && s->async) { + unsigned int bps = comedi_bytes_per_sample(s); + + if (s != s_read) + poll_wait(file, &s->async->wait_head, wait); + if (s->busy != file || !comedi_is_subdevice_running(s) || + !(s->async->cmd.flags & CMDF_WRITE) || + comedi_buf_write_n_available(s) >= bps) + mask |= EPOLLOUT | EPOLLWRNORM; + } + +done: + up_read(&dev->attach_lock); + 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; + unsigned int n, m; + ssize_t count = 0; + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + bool become_nonbusy = false; + bool attach_locked; + unsigned int old_detach_count; + + /* Protect against device detachment during operation. */ + down_read(&dev->attach_lock); + attach_locked = true; + old_detach_count = dev->detach_count; + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + retval = -ENODEV; + goto out; + } + + s = comedi_file_write_subdevice(file); + if (!s || !s->async) { + retval = -EIO; + goto out; + } + + async = s->async; + if (s->busy != file || !(async->cmd.flags & CMDF_WRITE)) { + retval = -EINVAL; + goto out; + } + + add_wait_queue(&async->wait_head, &wait); + while (count == 0 && !retval) { + unsigned int runflags; + unsigned int wp, n1, n2; + + set_current_state(TASK_INTERRUPTIBLE); + + runflags = comedi_get_subdevice_runflags(s); + if (!comedi_is_runflags_running(runflags)) { + if (comedi_is_runflags_in_error(runflags)) + retval = -EPIPE; + if (retval || nbytes) + become_nonbusy = true; + break; + } + if (nbytes == 0) + break; + + /* Allocate all free buffer space. */ + comedi_buf_write_alloc(s, async->prealloc_bufsz); + m = comedi_buf_write_n_allocated(s); + n = min_t(size_t, m, nbytes); + + if (n == 0) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (s->busy != file || + !(async->cmd.flags & CMDF_WRITE)) { + retval = -EINVAL; + break; + } + continue; + } + + set_current_state(TASK_RUNNING); + wp = async->buf_write_ptr; + n1 = min(n, async->prealloc_bufsz - wp); + n2 = n - n1; + m = copy_from_user(async->prealloc_buf + wp, buf, n1); + if (m) + m += n2; + else if (n2) + m = copy_from_user(async->prealloc_buf, buf + n1, n2); + if (m) { + n -= m; + retval = -EFAULT; + } + comedi_buf_write_free(s, n); + + count += n; + nbytes -= n; + + buf += n; + } + remove_wait_queue(&async->wait_head, &wait); + set_current_state(TASK_RUNNING); + if (become_nonbusy && count == 0) { + 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. + * + * Also check the subdevice is still in a suitable state to + * become non-busy in case it changed behind our back. + */ + new_s = comedi_file_write_subdevice(file); + if (dev->attached && old_detach_count == dev->detach_count && + s == new_s && new_s->async == async && s->busy == file && + (async->cmd.flags & CMDF_WRITE) && + !comedi_is_subdevice_running(s)) + do_become_nonbusy(dev, s); + mutex_unlock(&dev->mutex); + } +out: + 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; + unsigned int n, m; + ssize_t count = 0; + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + unsigned int old_detach_count; + bool become_nonbusy = false; + bool attach_locked; + + /* Protect against device detachment during operation. */ + down_read(&dev->attach_lock); + attach_locked = true; + old_detach_count = dev->detach_count; + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + retval = -ENODEV; + goto out; + } + + s = comedi_file_read_subdevice(file); + if (!s || !s->async) { + retval = -EIO; + goto out; + } + + async = s->async; + if (s->busy != file || (async->cmd.flags & CMDF_WRITE)) { + retval = -EINVAL; + goto out; + } + + add_wait_queue(&async->wait_head, &wait); + while (count == 0 && !retval) { + unsigned int rp, n1, n2; + + set_current_state(TASK_INTERRUPTIBLE); + + m = comedi_buf_read_n_available(s); + n = min_t(size_t, m, nbytes); + + if (n == 0) { + unsigned int runflags = + comedi_get_subdevice_runflags(s); + + if (!comedi_is_runflags_running(runflags)) { + if (comedi_is_runflags_in_error(runflags)) + retval = -EPIPE; + if (retval || nbytes) + become_nonbusy = true; + break; + } + if (nbytes == 0) + break; + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (s->busy != file || + (async->cmd.flags & CMDF_WRITE)) { + retval = -EINVAL; + break; + } + continue; + } + + set_current_state(TASK_RUNNING); + rp = async->buf_read_ptr; + n1 = min(n, async->prealloc_bufsz - rp); + n2 = n - n1; + m = copy_to_user(buf, async->prealloc_buf + rp, n1); + if (m) + m += n2; + else if (n2) + m = copy_to_user(buf + n1, async->prealloc_buf, n2); + if (m) { + n -= m; + retval = -EFAULT; + } + + comedi_buf_read_alloc(s, n); + comedi_buf_read_free(s, n); + + count += n; + nbytes -= n; + + buf += n; + } + remove_wait_queue(&async->wait_head, &wait); + set_current_state(TASK_RUNNING); + if (become_nonbusy && count == 0) { + 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. + * + * Also check the subdevice is still in a suitable state to + * become non-busy in case it changed behind our back. + */ + new_s = comedi_file_read_subdevice(file); + if (dev->attached && old_detach_count == dev->detach_count && + s == new_s && new_s->async == async && s->busy == file && + !(async->cmd.flags & CMDF_WRITE) && + !comedi_is_subdevice_running(s) && + comedi_buf_read_n_available(s) == 0) + do_become_nonbusy(dev, s); + mutex_unlock(&dev->mutex); + } +out: + if (attach_locked) + up_read(&dev->attach_lock); + + return count ? count : retval; +} + +static int comedi_open(struct inode *inode, struct file *file) +{ + const unsigned int minor = iminor(inode); + struct comedi_file *cfp; + struct comedi_device *dev = comedi_dev_get_from_minor(minor); + int rc; + + if (!dev) { + pr_debug("invalid minor number\n"); + return -ENODEV; + } + + cfp = kzalloc(sizeof(*cfp), GFP_KERNEL); + if (!cfp) { + comedi_dev_put(dev); + return -ENOMEM; + } + + cfp->dev = dev; + + mutex_lock(&dev->mutex); + if (!dev->attached && !capable(CAP_SYS_ADMIN)) { + dev_dbg(dev->class_dev, "not attached and not CAP_SYS_ADMIN\n"); + rc = -ENODEV; + goto out; + } + if (dev->attached && dev->use_count == 0) { + if (!try_module_get(dev->driver->module)) { + rc = -ENXIO; + goto out; + } + if (dev->open) { + rc = dev->open(dev); + if (rc < 0) { + module_put(dev->driver->module); + goto out; + } + } + } + + dev->use_count++; + file->private_data = cfp; + comedi_file_reset(file); + rc = 0; + +out: + mutex_unlock(&dev->mutex); + if (rc) { + comedi_dev_put(dev); + kfree(cfp); + } + return rc; +} + +static int comedi_fasync(int fd, struct file *file, int on) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + + return fasync_helper(fd, file, on, &dev->async_queue); +} + +static int comedi_close(struct inode *inode, struct file *file) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_subdevice *s = NULL; + int i; + + mutex_lock(&dev->mutex); + + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + + if (s->busy == file) + do_cancel(dev, s); + if (s->lock == file) + s->lock = NULL; + } + } + if (dev->attached && dev->use_count == 1) { + if (dev->close) + dev->close(dev); + module_put(dev->driver->module); + } + + dev->use_count--; + + mutex_unlock(&dev->mutex); + comedi_dev_put(dev); + kfree(cfp); + + return 0; +} + +#ifdef CONFIG_COMPAT + +#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 32-bit COMEDI_CHANINFO ioctl. */ +static int compat_chaninfo(struct file *file, unsigned long arg) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi32_chaninfo_struct chaninfo32; + struct comedi_chaninfo chaninfo; + int err; + + if (copy_from_user(&chaninfo32, compat_ptr(arg), sizeof(chaninfo32))) + return -EFAULT; + + memset(&chaninfo, 0, sizeof(chaninfo)); + chaninfo.subdev = chaninfo32.subdev; + chaninfo.maxdata_list = compat_ptr(chaninfo32.maxdata_list); + chaninfo.flaglist = compat_ptr(chaninfo32.flaglist); + chaninfo.rangelist = compat_ptr(chaninfo32.rangelist); + + mutex_lock(&dev->mutex); + err = do_chaninfo_ioctl(dev, &chaninfo); + mutex_unlock(&dev->mutex); + return err; +} + +/* Handle 32-bit COMEDI_RANGEINFO ioctl. */ +static int compat_rangeinfo(struct file *file, unsigned long arg) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi32_rangeinfo_struct rangeinfo32; + struct comedi_rangeinfo rangeinfo; + int err; + + if (copy_from_user(&rangeinfo32, compat_ptr(arg), sizeof(rangeinfo32))) + return -EFAULT; + memset(&rangeinfo, 0, sizeof(rangeinfo)); + rangeinfo.range_type = rangeinfo32.range_type; + rangeinfo.range_ptr = compat_ptr(rangeinfo32.range_ptr); + + mutex_lock(&dev->mutex); + err = do_rangeinfo_ioctl(dev, &rangeinfo); + mutex_unlock(&dev->mutex); + return err; +} + +/* Copy 32-bit cmd structure to native cmd structure. */ +static int get_compat_cmd(struct comedi_cmd *cmd, + struct comedi32_cmd_struct __user *cmd32) +{ + struct comedi32_cmd_struct v32; + + if (copy_from_user(&v32, cmd32, sizeof(v32))) + return -EFAULT; + + cmd->subdev = v32.subdev; + cmd->flags = v32.flags; + cmd->start_src = v32.start_src; + cmd->start_arg = v32.start_arg; + cmd->scan_begin_src = v32.scan_begin_src; + cmd->scan_begin_arg = v32.scan_begin_arg; + cmd->convert_src = v32.convert_src; + cmd->convert_arg = v32.convert_arg; + cmd->scan_end_src = v32.scan_end_src; + cmd->scan_end_arg = v32.scan_end_arg; + cmd->stop_src = v32.stop_src; + cmd->stop_arg = v32.stop_arg; + cmd->chanlist = (unsigned int __force *)compat_ptr(v32.chanlist); + cmd->chanlist_len = v32.chanlist_len; + cmd->data = compat_ptr(v32.data); + cmd->data_len = v32.data_len; + return 0; +} + +/* Copy native cmd structure to 32-bit cmd structure. */ +static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32, + struct comedi_cmd *cmd) +{ + struct comedi32_cmd_struct v32; + + memset(&v32, 0, sizeof(v32)); + v32.subdev = cmd->subdev; + v32.flags = cmd->flags; + v32.start_src = cmd->start_src; + v32.start_arg = cmd->start_arg; + v32.scan_begin_src = cmd->scan_begin_src; + v32.scan_begin_arg = cmd->scan_begin_arg; + v32.convert_src = cmd->convert_src; + v32.convert_arg = cmd->convert_arg; + v32.scan_end_src = cmd->scan_end_src; + v32.scan_end_arg = cmd->scan_end_arg; + v32.stop_src = cmd->stop_src; + v32.stop_arg = cmd->stop_arg; + /* Assume chanlist pointer is unchanged. */ + v32.chanlist = ptr_to_compat((unsigned int __user *)cmd->chanlist); + v32.chanlist_len = cmd->chanlist_len; + v32.data = ptr_to_compat(cmd->data); + v32.data_len = cmd->data_len; + if (copy_to_user(cmd32, &v32, sizeof(v32))) + return -EFAULT; + return 0; +} + +/* Handle 32-bit COMEDI_CMD ioctl. */ +static int compat_cmd(struct file *file, unsigned long arg) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_cmd cmd; + bool copy = false; + int rc, err; + + rc = get_compat_cmd(&cmd, compat_ptr(arg)); + if (rc) + return rc; + + mutex_lock(&dev->mutex); + rc = do_cmd_ioctl(dev, &cmd, ©, file); + mutex_unlock(&dev->mutex); + if (copy) { + /* Special case: copy cmd back to user. */ + err = put_compat_cmd(compat_ptr(arg), &cmd); + if (err) + rc = err; + } + return rc; +} + +/* Handle 32-bit COMEDI_CMDTEST ioctl. */ +static int compat_cmdtest(struct file *file, unsigned long arg) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_cmd cmd; + bool copy = false; + int rc, err; + + rc = get_compat_cmd(&cmd, compat_ptr(arg)); + if (rc) + return rc; + + mutex_lock(&dev->mutex); + rc = do_cmdtest_ioctl(dev, &cmd, ©, file); + mutex_unlock(&dev->mutex); + if (copy) { + err = put_compat_cmd(compat_ptr(arg), &cmd); + if (err) + rc = err; + } + return rc; +} + +/* Copy 32-bit insn structure to native insn structure. */ +static int get_compat_insn(struct comedi_insn *insn, + struct comedi32_insn_struct __user *insn32) +{ + struct comedi32_insn_struct v32; + + /* Copy insn structure. Ignore the unused members. */ + if (copy_from_user(&v32, insn32, sizeof(v32))) + return -EFAULT; + memset(insn, 0, sizeof(*insn)); + insn->insn = v32.insn; + insn->n = v32.n; + insn->data = compat_ptr(v32.data); + insn->subdev = v32.subdev; + insn->chanspec = v32.chanspec; + return 0; +} + +/* Handle 32-bit COMEDI_INSNLIST ioctl. */ +static int compat_insnlist(struct file *file, unsigned long arg) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi32_insnlist_struct insnlist32; + struct comedi32_insn_struct __user *insn32; + struct comedi_insn *insns; + unsigned int n; + int rc; + + if (copy_from_user(&insnlist32, compat_ptr(arg), sizeof(insnlist32))) + return -EFAULT; + + insns = kcalloc(insnlist32.n_insns, sizeof(*insns), GFP_KERNEL); + if (!insns) + return -ENOMEM; + + /* Copy insn structures. */ + insn32 = compat_ptr(insnlist32.insns); + for (n = 0; n < insnlist32.n_insns; n++) { + rc = get_compat_insn(insns + n, insn32 + n); + if (rc) { + kfree(insns); + return rc; + } + } + + mutex_lock(&dev->mutex); + rc = do_insnlist_ioctl(dev, insns, insnlist32.n_insns, file); + mutex_unlock(&dev->mutex); + kfree(insns); + return rc; +} + +/* Handle 32-bit COMEDI_INSN ioctl. */ +static int compat_insn(struct file *file, unsigned long arg) +{ + struct comedi_file *cfp = file->private_data; + struct comedi_device *dev = cfp->dev; + struct comedi_insn insn; + int rc; + + rc = get_compat_insn(&insn, (void __user *)arg); + if (rc) + return rc; + + mutex_lock(&dev->mutex); + rc = do_insn_ioctl(dev, &insn, file); + mutex_unlock(&dev->mutex); + return rc; +} + +/* + * compat_ioctl file operation. + * + * Returns -ENOIOCTLCMD for unrecognised ioctl codes. + */ +static long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc; + + switch (cmd) { + case COMEDI_DEVCONFIG: + case COMEDI_DEVINFO: + case COMEDI_SUBDINFO: + case COMEDI_BUFCONFIG: + case COMEDI_BUFINFO: + /* Just need to translate the pointer argument. */ + arg = (unsigned long)compat_ptr(arg); + rc = comedi_unlocked_ioctl(file, cmd, arg); + break; + case COMEDI_LOCK: + case COMEDI_UNLOCK: + case COMEDI_CANCEL: + case COMEDI_POLL: + case COMEDI_SETRSUBD: + case COMEDI_SETWSUBD: + /* No translation needed. */ + rc = comedi_unlocked_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; +} +#else +#define comedi_compat_ioctl NULL +#endif + +static const struct file_operations comedi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = comedi_unlocked_ioctl, + .compat_ioctl = comedi_compat_ioctl, + .open = comedi_open, + .release = comedi_close, + .read = comedi_read, + .write = comedi_write, + .mmap = comedi_mmap, + .poll = comedi_poll, + .fasync = comedi_fasync, + .llseek = noop_llseek, +}; + +/** + * comedi_event() - Handle events for asynchronous COMEDI command + * @dev: COMEDI device. + * @s: COMEDI subdevice. + * Context: in_interrupt() (usually), @s->spin_lock spin-lock not held. + * + * If an asynchronous COMEDI command is active on the subdevice, process + * any %COMEDI_CB_... event flags that have been set, usually by an + * interrupt handler. These may change the run state of the asynchronous + * command, wake a task, and/or send a %SIGIO signal. + */ +void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + unsigned int events; + int si_code = 0; + unsigned long flags; + + spin_lock_irqsave(&s->spin_lock, flags); + + events = async->events; + async->events = 0; + if (!__comedi_is_subdevice_running(s)) { + spin_unlock_irqrestore(&s->spin_lock, flags); + return; + } + + if (events & COMEDI_CB_CANCEL_MASK) + __comedi_clear_subdevice_runflags(s, COMEDI_SRF_RUNNING); + + /* + * Remember if an error event has occurred, so an error can be + * returned the next time the user does a read() or write(). + */ + if (events & COMEDI_CB_ERROR_MASK) + __comedi_set_subdevice_runflags(s, COMEDI_SRF_ERROR); + + if (async->cb_mask & events) { + wake_up_interruptible(&async->wait_head); + si_code = async->cmd.flags & CMDF_WRITE ? POLL_OUT : POLL_IN; + } + + spin_unlock_irqrestore(&s->spin_lock, flags); + + if (si_code) + kill_fasync(&dev->async_queue, SIGIO, si_code); +} +EXPORT_SYMBOL_GPL(comedi_event); + +/* Note: the ->mutex is pre-locked on successful return */ +struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device) +{ + struct comedi_device *dev; + struct device *csdev; + unsigned int i; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return ERR_PTR(-ENOMEM); + comedi_device_init(dev); + comedi_set_hw_dev(dev, hardware_device); + mutex_lock(&dev->mutex); + mutex_lock(&comedi_board_minor_table_lock); + for (i = hardware_device ? comedi_num_legacy_minors : 0; + i < COMEDI_NUM_BOARD_MINORS; ++i) { + if (!comedi_board_minor_table[i]) { + comedi_board_minor_table[i] = dev; + break; + } + } + mutex_unlock(&comedi_board_minor_table_lock); + if (i == COMEDI_NUM_BOARD_MINORS) { + mutex_unlock(&dev->mutex); + comedi_device_cleanup(dev); + comedi_dev_put(dev); + dev_err(hardware_device, + "ran out of minor numbers for board device files\n"); + return ERR_PTR(-EBUSY); + } + dev->minor = i; + csdev = device_create(comedi_class, hardware_device, + MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i", i); + if (!IS_ERR(csdev)) + dev->class_dev = get_device(csdev); + + /* Note: dev->mutex needs to be unlocked by the caller. */ + return dev; +} + +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 int i; + + mutex_lock(&comedi_subdevice_minor_table_lock); + for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i) { + if (!comedi_subdevice_minor_table[i]) { + comedi_subdevice_minor_table[i] = s; + break; + } + } + mutex_unlock(&comedi_subdevice_minor_table_lock); + if (i == COMEDI_NUM_SUBDEVICE_MINORS) { + dev_err(dev->class_dev, + "ran out of minor numbers for subdevice files\n"); + return -EBUSY; + } + i += COMEDI_NUM_BOARD_MINORS; + s->minor = i; + csdev = device_create(comedi_class, dev->class_dev, + MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i_subd%i", + dev->minor, s->index); + if (!IS_ERR(csdev)) + s->class_dev = csdev; + + return 0; +} + +void comedi_free_subdevice_minor(struct comedi_subdevice *s) +{ + unsigned int i; + + if (!s) + return; + if (s->minor < COMEDI_NUM_BOARD_MINORS || + s->minor >= COMEDI_NUM_MINORS) + return; + + 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) +{ + struct comedi_device *dev; + unsigned int i; + + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { + dev = comedi_clear_board_minor(i); + comedi_free_board_dev(dev); + } +} + +static int __init comedi_init(void) +{ + int i; + int retval; + + pr_info("version " COMEDI_RELEASE " - http://www.comedi.org\n"); + + if (comedi_num_legacy_minors > COMEDI_NUM_BOARD_MINORS) { + pr_err("invalid value for module parameter \"comedi_num_legacy_minors\". Valid values are 0 through %i.\n", + COMEDI_NUM_BOARD_MINORS); + return -EINVAL; + } + + retval = register_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS, "comedi"); + if (retval) + return retval; + + cdev_init(&comedi_cdev, &comedi_fops); + comedi_cdev.owner = THIS_MODULE; + + retval = kobject_set_name(&comedi_cdev.kobj, "comedi"); + if (retval) + goto out_unregister_chrdev_region; + + retval = cdev_add(&comedi_cdev, MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + if (retval) + goto out_unregister_chrdev_region; + + comedi_class = class_create(THIS_MODULE, "comedi"); + if (IS_ERR(comedi_class)) { + retval = PTR_ERR(comedi_class); + pr_err("failed to create class\n"); + goto out_cdev_del; + } + + comedi_class->dev_groups = comedi_dev_groups; + + /* 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)) { + retval = PTR_ERR(dev); + goto out_cleanup_board_minors; + } + /* comedi_alloc_board_minor() locked the mutex */ + lockdep_assert_held(&dev->mutex); + mutex_unlock(&dev->mutex); + } + + /* XXX requires /proc interface */ + comedi_proc_init(); + + return 0; + +out_cleanup_board_minors: + comedi_cleanup_board_minors(); + class_destroy(comedi_class); +out_cdev_del: + cdev_del(&comedi_cdev); +out_unregister_chrdev_region: + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS); + return retval; +} +module_init(comedi_init); + +static void __exit comedi_cleanup(void) +{ + comedi_cleanup_board_minors(); + 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("https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi core module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/comedi_internal.h b/drivers/comedi/comedi_internal.h new file mode 100644 index 000000000..9b3631a65 --- /dev/null +++ b/drivers/comedi/comedi_internal.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _COMEDI_INTERNAL_H +#define _COMEDI_INTERNAL_H + +#include <linux/compiler.h> +#include <linux/types.h> + +/* + * various internal comedi stuff + */ + +struct comedi_buf_map; +struct comedi_devconfig; +struct comedi_device; +struct comedi_insn; +struct comedi_rangeinfo; +struct comedi_subdevice; +struct device; + +int do_rangeinfo_ioctl(struct comedi_device *dev, + struct comedi_rangeinfo *it); +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); +int comedi_buf_map_access(struct comedi_buf_map *bm, unsigned long offset, + void *buf, int len, int write); +struct comedi_buf_map * +comedi_buf_map_from_subdev_get(struct comedi_subdevice *s); +unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s); +unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s); +void comedi_device_cancel_all(struct comedi_device *dev); +bool comedi_can_auto_free_spriv(struct comedi_subdevice *s); + +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 *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +void comedi_device_detach(struct comedi_device *dev); +int comedi_device_attach(struct comedi_device *dev, + struct comedi_devconfig *it); + +#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/comedi/comedi_pci.c b/drivers/comedi/comedi_pci.c new file mode 100644 index 000000000..cc2581902 --- /dev/null +++ b/drivers/comedi/comedi_pci.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +/** + * comedi_to_pci_dev() - Return PCI device attached to COMEDI device + * @dev: COMEDI device. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct pci_dev. + * + * Return: Attached PCI device if @dev->hw_dev is non-%NULL. + * Return %NULL if @dev->hw_dev is %NULL. + */ +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. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct pci_dev. Enable the PCI device + * and request its regions. Set @dev->ioenabled to %true if successful, + * otherwise undo what was done. + * + * Calls to comedi_pci_enable() and comedi_pci_disable() cannot be nested. + * + * Return: + * 0 on success, + * -%ENODEV if @dev->hw_dev is %NULL, + * -%EBUSY if regions busy, + * or some negative error number if failed to enable PCI device. + * + */ +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. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct pci_dev. If the earlier call + * to comedi_pci_enable() was successful, release the PCI device's regions + * and disable it. Reset @dev->ioenabled back to %false. + */ +void comedi_pci_disable(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + + if (pcidev && dev->ioenabled) { + pci_release_regions(pcidev); + pci_disable_device(pcidev); + } + dev->ioenabled = false; +} +EXPORT_SYMBOL_GPL(comedi_pci_disable); + +/** + * comedi_pci_detach() - A generic "detach" handler for PCI COMEDI drivers + * @dev: COMEDI device. + * + * COMEDI drivers for PCI devices that need no special clean-up of private data + * and have no ioremapped regions other than that pointed to by @dev->mmio may + * use this function as its "detach" handler called by the COMEDI core when a + * COMEDI device is being detached from the low-level driver. It may be also + * called from a more specific "detach" handler that does additional clean-up. + * + * Free the IRQ if @dev->irq is non-zero, iounmap @dev->mmio if it is + * non-%NULL, and call comedi_pci_disable() to release the PCI device's regions + * and disable it. + */ +void comedi_pci_detach(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + + if (!pcidev || !dev->ioenabled) + return; + + if (dev->irq) { + free_irq(dev->irq, dev); + dev->irq = 0; + } + if (dev->mmio) { + iounmap(dev->mmio); + dev->mmio = NULL; + } + comedi_pci_disable(dev); +} +EXPORT_SYMBOL_GPL(comedi_pci_detach); + +/** + * comedi_pci_auto_config() - Configure/probe a PCI COMEDI device + * @pcidev: PCI device. + * @driver: Registered COMEDI driver. + * @context: Driver specific data, passed to comedi_auto_config(). + * + * Typically called from the pci_driver (*probe) function. Auto-configure + * a COMEDI device, using the &struct device embedded in *@pcidev as the + * hardware device. The @context value gets passed through to @driver's + * "auto_attach" handler. The "auto_attach" handler may call + * comedi_to_pci_dev() on the passed in COMEDI device to recover @pcidev. + * + * Return: The result of calling comedi_auto_config() (0 on success, or + * a negative error number on failure). + */ +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 PCI COMEDI device + * @pcidev: PCI device. + * + * Typically called from the pci_driver (*remove) function. Auto-unconfigure + * a COMEDI device attached to this PCI device, using a pointer to the + * &struct device embedded in *@pcidev as the hardware device. The COMEDI + * driver's "detach" handler will be called during unconfiguration of the + * COMEDI device. + * + * Note that the COMEDI device may have already been unconfigured using the + * %COMEDI_DEVCONFIG ioctl, in which case this attempt to unconfigure it + * again should be ignored. + */ +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 PCI COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @pci_driver: PCI driver to be registered. + * + * This function is called from the module_init() of PCI COMEDI driver modules + * to register the COMEDI driver and the PCI driver. Do not call it directly, + * use the module_comedi_pci_driver() helper macro instead. + * + * Return: 0 on success, or a negative error number on failure. + */ +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 PCI COMEDI driver + * @comedi_driver: COMEDI driver to be unregistered. + * @pci_driver: PCI driver to be unregistered. + * + * This function is called from the module_exit() of PCI COMEDI driver modules + * to unregister the PCI driver and the COMEDI driver. Do not call it + * directly, use the module_comedi_pci_driver() helper macro instead. + */ +void comedi_pci_driver_unregister(struct comedi_driver *comedi_driver, + struct pci_driver *pci_driver) +{ + pci_unregister_driver(pci_driver); + comedi_driver_unregister(comedi_driver); +} +EXPORT_SYMBOL_GPL(comedi_pci_driver_unregister); + +static int __init comedi_pci_init(void) +{ + return 0; +} +module_init(comedi_pci_init); + +static void __exit comedi_pci_exit(void) +{ +} +module_exit(comedi_pci_exit); + +MODULE_AUTHOR("https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi PCI interface module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/comedi_pcmcia.c b/drivers/comedi/comedi_pcmcia.c new file mode 100644 index 000000000..c53aad0fc --- /dev/null +++ b/drivers/comedi/comedi_pcmcia.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/comedi/comedi_pcmcia.h> + +/** + * comedi_to_pcmcia_dev() - Return PCMCIA device attached to COMEDI device + * @dev: COMEDI device. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct pcmcia_device. + * + * Return: Attached PCMCIA device if @dev->hw_dev is non-%NULL. + * Return %NULL if @dev->hw_dev is %NULL. + */ +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. + * @conf_check: Optional callback to check each configuration option of the + * PCMCIA device and request I/O regions. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a a + * &struct device embedded in a &struct pcmcia_device. The comedi PCMCIA + * driver needs to set the 'config_flags' member in the &struct pcmcia_device, + * as appropriate for that driver, before calling this function in order to + * allow pcmcia_loop_config() to do its internal autoconfiguration. + * + * If @conf_check is %NULL it is set to a default function. If is + * passed to pcmcia_loop_config() and should return %0 if the configuration + * is valid and I/O regions requested successfully, otherwise it should return + * a negative error value. The default function returns -%EINVAL if the + * 'config_index' member is %0, otherwise it calls pcmcia_request_io() and + * returns the result. + * + * If the above configuration check passes, pcmcia_enable_device() is called + * to set up and activate the PCMCIA device. + * + * If this function returns an error, comedi_pcmcia_disable() should be called + * to release requested resources. + * + * Return: + * 0 on success, + * -%ENODEV id @dev->hw_dev is %NULL, + * a negative error number from pcmcia_loop_config() if it fails, + * or a negative error number from pcmcia_enable_device() if it fails. + */ +int comedi_pcmcia_enable(struct comedi_device *dev, + int (*conf_check)(struct pcmcia_device *p_dev, + void *priv_data)) +{ + 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. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct pcmcia_device. Call + * pcmcia_disable_device() to disable and clean up the PCMCIA device. + */ +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 PCMCIA COMEDI device + * @link: PCMCIA device. + * @driver: Registered COMEDI driver. + * + * Typically called from the pcmcia_driver (*probe) function. Auto-configure + * a COMEDI device, using a pointer to the &struct device embedded in *@link + * as the hardware device. The @driver's "auto_attach" handler may call + * comedi_to_pcmcia_dev() on the passed in COMEDI device to recover @link. + * + * Return: The result of calling comedi_auto_config() (0 on success, or a + * negative error number on failure). + */ +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 PCMCIA COMEDI device + * @link: PCMCIA device. + * + * Typically called from the pcmcia_driver (*remove) function. + * Auto-unconfigure a COMEDI device attached to this PCMCIA device, using a + * pointer to the &struct device embedded in *@link as the hardware device. + * The COMEDI driver's "detach" handler will be called during unconfiguration + * of the COMEDI device. + * + * Note that the COMEDI device may have already been unconfigured using the + * %COMEDI_DEVCONFIG ioctl, in which case this attempt to unconfigure it + * again should be ignored. + */ +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 PCMCIA COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @pcmcia_driver: PCMCIA driver to be registered. + * + * This function is used for the module_init() of PCMCIA COMEDI driver modules + * to register the COMEDI driver and the PCMCIA driver. Do not call it + * directly, use the module_comedi_pcmcia_driver() helper macro instead. + * + * Return: 0 on success, or a negative error number on failure. + */ +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 PCMCIA COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @pcmcia_driver: PCMCIA driver to be registered. + * + * This function is called from the module_exit() of PCMCIA COMEDI driver + * modules to unregister the PCMCIA driver and the COMEDI driver. Do not call + * it directly, use the module_comedi_pcmcia_driver() helper macro instead. + */ +void comedi_pcmcia_driver_unregister(struct comedi_driver *comedi_driver, + struct pcmcia_driver *pcmcia_driver) +{ + pcmcia_unregister_driver(pcmcia_driver); + comedi_driver_unregister(comedi_driver); +} +EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_unregister); + +static int __init comedi_pcmcia_init(void) +{ + return 0; +} +module_init(comedi_pcmcia_init); + +static void __exit comedi_pcmcia_exit(void) +{ +} +module_exit(comedi_pcmcia_exit); + +MODULE_AUTHOR("https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi PCMCIA interface module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/comedi_usb.c b/drivers/comedi/comedi_usb.c new file mode 100644 index 000000000..d11ea148e --- /dev/null +++ b/drivers/comedi/comedi_usb.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_usb.h> + +/** + * comedi_to_usb_interface() - Return USB interface attached to COMEDI device + * @dev: COMEDI device. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct usb_interface. + * + * Return: Attached USB interface if @dev->hw_dev is non-%NULL. + * Return %NULL if @dev->hw_dev is %NULL. + */ +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() - Return USB device attached to COMEDI device + * @dev: COMEDI device. + * + * Assuming @dev->hw_dev is non-%NULL, it is assumed to be pointing to a + * a &struct device embedded in a &struct usb_interface. + * + * Return: USB device to which the USB interface belongs if @dev->hw_dev is + * non-%NULL. Return %NULL if @dev->hw_dev is %NULL. + */ +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 USB COMEDI driver + * @intf: USB interface. + * @driver: Registered COMEDI driver. + * @context: Driver specific data, passed to comedi_auto_config(). + * + * Typically called from the usb_driver (*probe) function. Auto-configure a + * COMEDI device, using a pointer to the &struct device embedded in *@intf as + * the hardware device. The @context value gets passed through to @driver's + * "auto_attach" handler. The "auto_attach" handler may call + * comedi_to_usb_interface() on the passed in COMEDI device to recover @intf. + * + * Return: The result of calling comedi_auto_config() (%0 on success, or + * a negative error number on failure). + */ +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_usb_auto_unconfig() - Unconfigure/disconnect a USB COMEDI device + * @intf: USB interface. + * + * Typically called from the usb_driver (*disconnect) function. + * Auto-unconfigure a COMEDI device attached to this USB interface, using a + * pointer to the &struct device embedded in *@intf as the hardware device. + * The COMEDI driver's "detach" handler will be called during unconfiguration + * of the COMEDI device. + * + * Note that the COMEDI device may have already been unconfigured using the + * %COMEDI_DEVCONFIG ioctl, in which case this attempt to unconfigure it + * again should be ignored. + */ +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 USB COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @usb_driver: USB driver to be registered. + * + * This function is called from the module_init() of USB COMEDI driver modules + * to register the COMEDI driver and the USB driver. Do not call it directly, + * use the module_comedi_usb_driver() helper macro instead. + * + * Return: %0 on success, or a negative error number on failure. + */ +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 USB COMEDI driver + * @comedi_driver: COMEDI driver to be registered. + * @usb_driver: USB driver to be registered. + * + * This function is called from the module_exit() of USB COMEDI driver modules + * to unregister the USB driver and the COMEDI driver. Do not call it + * directly, use the module_comedi_usb_driver() helper macro instead. + */ +void comedi_usb_driver_unregister(struct comedi_driver *comedi_driver, + struct usb_driver *usb_driver) +{ + usb_deregister(usb_driver); + comedi_driver_unregister(comedi_driver); +} +EXPORT_SYMBOL_GPL(comedi_usb_driver_unregister); + +static int __init comedi_usb_init(void) +{ + return 0; +} +module_init(comedi_usb_init); + +static void __exit comedi_usb_exit(void) +{ +} +module_exit(comedi_usb_exit); + +MODULE_AUTHOR("https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi USB interface module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers.c b/drivers/comedi/drivers.c new file mode 100644 index 000000000..d4e2ed709 --- /dev/null +++ b/drivers/comedi/drivers.c @@ -0,0 +1,1183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * module/drivers.c + * functions for manipulating drivers + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/dma-direction.h> +#include <linux/interrupt.h> +#include <linux/firmware.h> +#include <linux/comedi/comedidev.h> +#include "comedi_internal.h" + +struct comedi_driver *comedi_drivers; +/* protects access to comedi_drivers */ +DEFINE_MUTEX(comedi_drivers_list_lock); + +/** + * comedi_set_hw_dev() - Set hardware device associated with COMEDI device + * @dev: COMEDI device. + * @hw_dev: Hardware device. + * + * For automatically configured COMEDI devices (resulting from a call to + * comedi_auto_config() or one of its wrappers from the low-level COMEDI + * driver), comedi_set_hw_dev() is called automatically by the COMEDI core + * to associate the COMEDI device with the hardware device. It can also be + * called directly by "legacy" low-level COMEDI drivers that rely on the + * %COMEDI_DEVCONFIG ioctl to configure the hardware as long as the hardware + * has a &struct device. + * + * If @dev->hw_dev is NULL, it gets a reference to @hw_dev and sets + * @dev->hw_dev, otherwise, it does nothing. Calling it multiple times + * with the same hardware device is not considered an error. If it gets + * a reference to the hardware device, it will be automatically 'put' when + * the device is detached from COMEDI. + * + * Returns 0 if @dev->hw_dev was NULL or the same as @hw_dev, otherwise + * returns -EEXIST. + */ +int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev) +{ + if (hw_dev == dev->hw_dev) + return 0; + if (dev->hw_dev) + return -EEXIST; + dev->hw_dev = get_device(hw_dev); + return 0; +} +EXPORT_SYMBOL_GPL(comedi_set_hw_dev); + +static void comedi_clear_hw_dev(struct comedi_device *dev) +{ + put_device(dev->hw_dev); + dev->hw_dev = NULL; +} + +/** + * comedi_alloc_devpriv() - Allocate memory for the device private data + * @dev: COMEDI device. + * @size: Size of the memory to allocate. + * + * The allocated memory is zero-filled. @dev->private points to it on + * return. The memory will be automatically freed when the COMEDI device is + * "detached". + * + * Returns a pointer to the allocated memory, or NULL on failure. + */ +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); + +/** + * comedi_alloc_subdevices() - Allocate subdevices for COMEDI device + * @dev: COMEDI device. + * @num_subdevices: Number of subdevices to allocate. + * + * Allocates and initializes an array of &struct comedi_subdevice for the + * COMEDI device. If successful, sets @dev->subdevices to point to the + * first one and @dev->n_subdevices to the number. + * + * Returns 0 on success, -EINVAL if @num_subdevices is < 1, or -ENOMEM if + * failed to allocate the memory. + */ +int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices) +{ + struct comedi_subdevice *s; + int i; + + if (num_subdevices < 1) + return -EINVAL; + + s = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + dev->subdevices = s; + dev->n_subdevices = num_subdevices; + + for (i = 0; i < num_subdevices; ++i) { + s = &dev->subdevices[i]; + s->device = dev; + s->index = i; + s->async_dma_dir = DMA_NONE; + spin_lock_init(&s->spin_lock); + s->minor = -1; + } + return 0; +} +EXPORT_SYMBOL_GPL(comedi_alloc_subdevices); + +/** + * comedi_alloc_subdev_readback() - Allocate memory for the subdevice readback + * @s: COMEDI subdevice. + * + * This is called by low-level COMEDI drivers to allocate an array to record + * the last values written to a subdevice's analog output channels (at least + * by the %INSN_WRITE instruction), to allow them to be read back by an + * %INSN_READ instruction. It also provides a default handler for the + * %INSN_READ instruction unless one has already been set. + * + * On success, @s->readback points to the first element of the array, which + * is zero-filled. The low-level driver is responsible for updating its + * contents. @s->insn_read will be set to comedi_readback_insn_read() + * unless it is already non-NULL. + * + * Returns 0 on success, -EINVAL if the subdevice has no channels, or + * -ENOMEM on allocation failure. + */ +int comedi_alloc_subdev_readback(struct comedi_subdevice *s) +{ + if (!s->n_chan) + return -EINVAL; + + s->readback = kcalloc(s->n_chan, sizeof(*s->readback), GFP_KERNEL); + if (!s->readback) + return -ENOMEM; + + if (!s->insn_read) + s->insn_read = comedi_readback_insn_read; + + return 0; +} +EXPORT_SYMBOL_GPL(comedi_alloc_subdev_readback); + +static void comedi_device_detach_cleanup(struct comedi_device *dev) +{ + int i; + struct comedi_subdevice *s; + + lockdep_assert_held(&dev->attach_lock); + lockdep_assert_held(&dev->mutex); + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + if (comedi_can_auto_free_spriv(s)) + kfree(s->private); + comedi_free_subdevice_minor(s); + if (s->async) { + comedi_buf_alloc(dev, s, 0); + kfree(s->async); + } + kfree(s->readback); + } + kfree(dev->subdevices); + dev->subdevices = NULL; + dev->n_subdevices = 0; + } + kfree(dev->private); + kfree(dev->pacer); + dev->private = NULL; + dev->pacer = NULL; + dev->driver = NULL; + dev->board_name = NULL; + dev->board_ptr = NULL; + dev->mmio = NULL; + dev->iobase = 0; + dev->iolen = 0; + dev->ioenabled = false; + dev->irq = 0; + dev->read_subdev = NULL; + dev->write_subdev = NULL; + dev->open = NULL; + dev->close = NULL; + comedi_clear_hw_dev(dev); +} + +void comedi_device_detach(struct comedi_device *dev) +{ + lockdep_assert_held(&dev->mutex); + 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; +} + +static int insn_device_inval(struct comedi_device *dev, + struct comedi_insn *insn, unsigned int *data) +{ + return -EINVAL; +} + +static unsigned int get_zero_valid_routes(struct comedi_device *dev, + unsigned int n_pairs, + unsigned int *pair_data) +{ + return 0; +} + +int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return -EINVAL; +} + +/** + * comedi_readback_insn_read() - A generic (*insn_read) for subdevice readback. + * @dev: COMEDI device. + * @s: COMEDI subdevice. + * @insn: COMEDI instruction. + * @data: Pointer to return the readback data. + * + * Handles the %INSN_READ instruction for subdevices that use the readback + * array allocated by comedi_alloc_subdev_readback(). It may be used + * directly as the subdevice's handler (@s->insn_read) or called via a + * wrapper. + * + * @insn->n is normally 1, which will read a single value. If higher, the + * same element of the readback array will be read multiple times. + * + * Returns @insn->n on success, or -EINVAL if @s->readback is NULL. + */ +int comedi_readback_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + if (!s->readback) + return -EINVAL; + + for (i = 0; i < insn->n; i++) + data[i] = s->readback[chan]; + + return insn->n; +} +EXPORT_SYMBOL_GPL(comedi_readback_insn_read); + +/** + * comedi_timeout() - Busy-wait for a driver condition to occur + * @dev: COMEDI device. + * @s: COMEDI subdevice. + * @insn: COMEDI instruction. + * @cb: Callback to check for the condition. + * @context: Private context from the driver. + * + * Busy-waits for up to a second (%COMEDI_TIMEOUT_MS) for the condition or + * some error (other than -EBUSY) to occur. The parameters @dev, @s, @insn, + * and @context are passed to the callback function, which returns -EBUSY to + * continue waiting or some other value to stop waiting (generally 0 if the + * condition occurred, or some error value). + * + * Returns -ETIMEDOUT if timed out, otherwise the return value from the + * callback function. + */ +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. + * @s: COMEDI subdevice. + * @insn: COMEDI instruction. + * @data: Instruction parameters and return data. + * @mask: io_bits mask for grouped channels, or 0 for single channel. + * + * If @mask is 0, it is replaced with a single-bit mask corresponding to the + * channel number specified by @insn->chanspec. Otherwise, @mask + * corresponds to a group of channels (which should include the specified + * channel) that are always configured together as inputs or outputs. + * + * Partially handles the %INSN_CONFIG_DIO_INPUT, %INSN_CONFIG_DIO_OUTPUTS, + * and %INSN_CONFIG_DIO_QUERY instructions. The first two update + * @s->io_bits to record the directions of the masked channels. The last + * one sets @data[1] to the current direction of the group of channels + * (%COMEDI_INPUT) or %COMEDI_OUTPUT) as recorded in @s->io_bits. + * + * The caller is responsible for updating the DIO direction in the hardware + * registers if this function returns 0. + * + * Returns 0 for a %INSN_CONFIG_DIO_INPUT or %INSN_CONFIG_DIO_OUTPUT + * instruction, @insn->n (> 0) for a %INSN_CONFIG_DIO_QUERY instruction, or + * -EINVAL for some other instruction. + */ +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. + * @data: The channel mask and bits to update. + * + * Updates @s->state which holds the internal state of the outputs for DIO + * or DO subdevices (up to 32 channels). @data[0] contains a bit-mask of + * the channels to be updated. @data[1] contains a bit-mask of those + * channels to be set to '1'. The caller is responsible for updating the + * outputs in hardware according to @s->state. As a minimum, the channels + * in the returned bit-mask need to be updated. + * + * Returns @mask with non-existent channels removed. + */ +unsigned int comedi_dio_update_state(struct comedi_subdevice *s, + unsigned int *data) +{ + unsigned int chanmask = (s->n_chan < 32) ? ((1 << s->n_chan) - 1) + : 0xffffffff; + unsigned int mask = data[0] & chanmask; + unsigned int bits = data[1]; + + if (mask) { + s->state &= ~mask; + s->state |= (bits & mask); + } + + return mask; +} +EXPORT_SYMBOL_GPL(comedi_dio_update_state); + +/** + * comedi_bytes_per_scan_cmd() - Get length of asynchronous command "scan" in + * bytes + * @s: COMEDI subdevice. + * @cmd: COMEDI command. + * + * Determines the overall scan length according to the subdevice type and the + * number of channels in the scan for the specified command. + * + * For digital input, output or input/output subdevices, samples for + * multiple channels are assumed to be packed into one or more unsigned + * short or unsigned int values according to the subdevice's %SDF_LSAMPL + * flag. For other types of subdevice, samples are assumed to occupy a + * whole unsigned short or unsigned int according to the %SDF_LSAMPL flag. + * + * Returns the overall scan length in bytes. + */ +unsigned int comedi_bytes_per_scan_cmd(struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int num_samples; + unsigned int bits_per_sample; + + switch (s->type) { + case COMEDI_SUBD_DI: + case COMEDI_SUBD_DO: + case COMEDI_SUBD_DIO: + bits_per_sample = 8 * comedi_bytes_per_sample(s); + num_samples = DIV_ROUND_UP(cmd->scan_end_arg, bits_per_sample); + break; + default: + num_samples = cmd->scan_end_arg; + break; + } + return comedi_samples_to_bytes(s, num_samples); +} +EXPORT_SYMBOL_GPL(comedi_bytes_per_scan_cmd); + +/** + * comedi_bytes_per_scan() - Get length of asynchronous command "scan" in bytes + * @s: COMEDI subdevice. + * + * Determines the overall scan length according to the subdevice type and the + * number of channels in the scan for the current command. + * + * For digital input, output or input/output subdevices, samples for + * multiple channels are assumed to be packed into one or more unsigned + * short or unsigned int values according to the subdevice's %SDF_LSAMPL + * flag. For other types of subdevice, samples are assumed to occupy a + * whole unsigned short or unsigned int according to the %SDF_LSAMPL flag. + * + * Returns the overall scan length in bytes. + */ +unsigned int comedi_bytes_per_scan(struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + return comedi_bytes_per_scan_cmd(s, cmd); +} +EXPORT_SYMBOL_GPL(comedi_bytes_per_scan); + +static unsigned int __comedi_nscans_left(struct comedi_subdevice *s, + unsigned int nscans) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + if (cmd->stop_src == TRIG_COUNT) { + unsigned int scans_left = 0; + + if (async->scans_done < cmd->stop_arg) + scans_left = cmd->stop_arg - async->scans_done; + + if (nscans > scans_left) + nscans = scans_left; + } + return nscans; +} + +/** + * comedi_nscans_left() - Return the number of scans left in the command + * @s: COMEDI subdevice. + * @nscans: The expected number of scans or 0 for all available scans. + * + * If @nscans is 0, it is set to the number of scans available in the + * async buffer. + * + * If the async command has a stop_src of %TRIG_COUNT, the @nscans will be + * checked against the number of scans remaining to complete the command. + * + * The return value will then be either the expected number of scans or the + * number of scans remaining to complete the command, whichever is fewer. + */ +unsigned int comedi_nscans_left(struct comedi_subdevice *s, + unsigned int nscans) +{ + if (nscans == 0) { + unsigned int nbytes = comedi_buf_read_n_available(s); + + nscans = nbytes / comedi_bytes_per_scan(s); + } + return __comedi_nscans_left(s, nscans); +} +EXPORT_SYMBOL_GPL(comedi_nscans_left); + +/** + * comedi_nsamples_left() - Return the number of samples left in the command + * @s: COMEDI subdevice. + * @nsamples: The expected number of samples. + * + * Returns the number of samples remaining to complete the command, or the + * specified expected number of samples (@nsamples), whichever is fewer. + */ +unsigned int comedi_nsamples_left(struct comedi_subdevice *s, + unsigned int nsamples) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long long scans_left; + unsigned long long samples_left; + + if (cmd->stop_src != TRIG_COUNT) + return nsamples; + + scans_left = __comedi_nscans_left(s, cmd->stop_arg); + if (!scans_left) + return 0; + + samples_left = scans_left * cmd->scan_end_arg - + comedi_bytes_to_samples(s, async->scan_progress); + + if (samples_left < nsamples) + return samples_left; + return nsamples; +} +EXPORT_SYMBOL_GPL(comedi_nsamples_left); + +/** + * comedi_inc_scan_progress() - Update scan progress in asynchronous command + * @s: COMEDI subdevice. + * @num_bytes: Amount of data in bytes to increment scan progress. + * + * Increments the scan progress by the number of bytes specified by @num_bytes. + * If the scan progress reaches or exceeds the scan length in bytes, reduce + * it modulo the scan length in bytes and set the "end of scan" asynchronous + * event flag (%COMEDI_CB_EOS) to be processed later. + */ +void comedi_inc_scan_progress(struct comedi_subdevice *s, + unsigned int num_bytes) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int scan_length = comedi_bytes_per_scan(s); + + /* track the 'cur_chan' for non-SDF_PACKED subdevices */ + if (!(s->subdev_flags & SDF_PACKED)) { + async->cur_chan += comedi_bytes_to_samples(s, num_bytes); + async->cur_chan %= cmd->chanlist_len; + } + + async->scan_progress += num_bytes; + if (async->scan_progress >= scan_length) { + unsigned int nscans = async->scan_progress / scan_length; + + if (async->scans_done < (UINT_MAX - nscans)) + async->scans_done += nscans; + else + async->scans_done = UINT_MAX; + + async->scan_progress %= scan_length; + async->events |= COMEDI_CB_EOS; + } +} +EXPORT_SYMBOL_GPL(comedi_inc_scan_progress); + +/** + * comedi_handle_events() - Handle events and possibly stop acquisition + * @dev: COMEDI device. + * @s: COMEDI subdevice. + * + * Handles outstanding asynchronous acquisition event flags associated + * with the subdevice. Call the subdevice's @s->cancel() handler if the + * "end of acquisition", "error" or "overflow" event flags are set in order + * to stop the acquisition at the driver level. + * + * Calls comedi_event() to further process the event flags, which may mark + * the asynchronous command as no longer running, possibly terminated with + * an error, and may wake up tasks. + * + * Return a bit-mask of the handled events. + */ +unsigned int comedi_handle_events(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int events = s->async->events; + + if (events == 0) + return events; + + if ((events & COMEDI_CB_CANCEL_MASK) && s->cancel) + s->cancel(dev, s); + + comedi_event(dev, s); + + return events; +} +EXPORT_SYMBOL_GPL(comedi_handle_events); + +static int insn_rw_emulate_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct comedi_insn _insn; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int base_chan = (chan < 32) ? 0 : chan; + unsigned int _data[2]; + int ret; + + memset(_data, 0, sizeof(_data)); + memset(&_insn, 0, sizeof(_insn)); + _insn.insn = INSN_BITS; + _insn.chanspec = base_chan; + _insn.n = 2; + _insn.subdev = insn->subdev; + + if (insn->insn == INSN_WRITE) { + if (!(s->subdev_flags & SDF_WRITABLE)) + return -EINVAL; + _data[0] = 1 << (chan - base_chan); /* mask */ + _data[1] = data[0] ? (1 << (chan - base_chan)) : 0; /* bits */ + } + + ret = s->insn_bits(dev, s, &_insn, _data); + if (ret < 0) + return ret; + + if (insn->insn == INSN_READ) + data[0] = (_data[1] >> (chan - base_chan)) & 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; + + lockdep_assert_held(&dev->mutex); + 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; + } + if (!s->cancel) + dev_warn(dev->class_dev, + "async subdevices should have a cancel() function\n"); + + async = kzalloc(sizeof(*async), GFP_KERNEL); + if (!async) + return -ENOMEM; + + init_waitqueue_head(&async->wait_head); + s->async = async; + + async->max_bufsize = comedi_default_buf_maxsize_kb * 1024; + buf_size = comedi_default_buf_size_kb * 1024; + if (buf_size > async->max_bufsize) + buf_size = async->max_bufsize; + + if (comedi_buf_alloc(dev, s, buf_size) < 0) { + dev_warn(dev->class_dev, "Buffer allocation failed\n"); + return -ENOMEM; + } + if (s->buf_change) { + ret = s->buf_change(dev, s); + if (ret < 0) + return ret; + } + + comedi_alloc_subdevice_minor(s); + + return 0; +} + +static int __comedi_device_postconfig(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + int ret; + int i; + + lockdep_assert_held(&dev->mutex); + if (!dev->insn_device_config) + dev->insn_device_config = insn_device_inval; + + if (!dev->get_valid_routes) + dev->get_valid_routes = get_zero_valid_routes; + + 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; + + lockdep_assert_held(&dev->mutex); + 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. + * @device: Hardware device. + * @name: The name of the firmware image. + * @cb: Callback to the upload the firmware image. + * @context: Private context from the driver. + * + * Sends a firmware request for the hardware device and waits for it. Calls + * the callback function to upload the firmware to the device, them releases + * the firmware. + * + * Returns 0 on success, -EINVAL if @cb is NULL, or a negative error number + * from the firmware request or the callback function. + */ +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 min(ret, 0); +} +EXPORT_SYMBOL_GPL(comedi_load_firmware); + +/** + * __comedi_request_region() - Request an I/O region for a legacy driver + * @dev: COMEDI device. + * @start: Base address of the I/O region. + * @len: Length of the I/O region. + * + * Requests the specified I/O port region which must start at a non-zero + * address. + * + * Returns 0 on success, -EINVAL if @start is 0, or -EIO if the request + * fails. + */ +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 region for a legacy driver + * @dev: COMEDI device. + * @start: Base address of the I/O region. + * @len: Length of the I/O region. + * + * Requests the specified I/O port region which must start at a non-zero + * address. + * + * On success, @dev->iobase is set to the base address of the region and + * @dev->iolen is set to its length. + * + * Returns 0 on success, -EINVAL if @start is 0, or -EIO if the request + * fails. + */ +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. + * + * This is a simple, generic 'detach' handler for legacy COMEDI devices that + * just use a single I/O port region and possibly an IRQ and that don't need + * any special clean-up for their private device or subdevice storage. It + * can also be called by a driver-specific 'detach' handler. + * + * If @dev->irq is non-zero, the IRQ will be freed. If @dev->iobase and + * @dev->iolen are both non-zero, the I/O port region will be released. + */ +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; + + lockdep_assert_held(&dev->mutex); + if (dev->attached) + return -EBUSY; + + mutex_lock(&comedi_drivers_list_lock); + for (driv = comedi_drivers; driv; driv = driv->next) { + if (!try_module_get(driv->module)) + continue; + if (driv->num_names) { + dev->board_ptr = comedi_recognize(driv, it->board_name); + if (dev->board_ptr) + break; + } else if (strcmp(driv->driver_name, it->board_name) == 0) { + break; + } + module_put(driv->module); + } + if (!driv) { + /* recognize has failed if we get here */ + /* report valid board names before returning error */ + for (driv = comedi_drivers; driv; driv = driv->next) { + if (!try_module_get(driv->module)) + continue; + comedi_report_boards(driv); + module_put(driv->module); + } + ret = -EIO; + goto out; + } + if (!driv->attach) { + /* driver does not support manual configuration */ + dev_warn(dev->class_dev, + "driver '%s' does not support attach using comedi_config\n", + driv->driver_name); + module_put(driv->module); + ret = -EIO; + goto out; + } + dev->driver = driv; + dev->board_name = dev->board_ptr ? *(const char **)dev->board_ptr + : dev->driver->driver_name; + ret = driv->attach(dev, it); + if (ret >= 0) + ret = comedi_device_postconfig(dev); + if (ret < 0) { + comedi_device_detach(dev); + module_put(driv->module); + } + /* On success, the driver module count has been incremented. */ +out: + mutex_unlock(&comedi_drivers_list_lock); + return ret; +} + +/** + * comedi_auto_config() - Create a COMEDI device for a hardware device + * @hardware_device: Hardware device. + * @driver: COMEDI low-level driver for the hardware device. + * @context: Driver context for the auto_attach handler. + * + * Allocates a new COMEDI device for the hardware device and calls the + * low-level driver's 'auto_attach' handler to set-up the hardware and + * allocate the COMEDI subdevices. Additional "post-configuration" setting + * up is performed on successful return from the 'auto_attach' handler. + * If the 'auto_attach' handler fails, the low-level driver's 'detach' + * handler will be called as part of the clean-up. + * + * This is usually called from a wrapper function in a bus-specific COMEDI + * module, which in turn is usually called from a bus device 'probe' + * function in the low-level driver. + * + * Returns 0 on success, -EINVAL if the parameters are invalid or the + * post-configuration determines the driver has set the COMEDI device up + * incorrectly, -ENOMEM if failed to allocate memory, -EBUSY if run out of + * COMEDI minor device numbers, or some negative error number returned by + * the driver's 'auto_attach' handler. + */ +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! %s called with NULL hardware_device\n", __func__); + return -EINVAL; + } + if (!driver) { + dev_warn(hardware_device, + "BUG! %s called with NULL comedi driver\n", __func__); + 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. */ + lockdep_assert_held(&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); + + if (ret < 0) { + dev_warn(hardware_device, + "driver '%s' failed to auto-configure device.\n", + driver->driver_name); + mutex_unlock(&dev->mutex); + 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); + mutex_unlock(&dev->mutex); + } + return ret; +} +EXPORT_SYMBOL_GPL(comedi_auto_config); + +/** + * comedi_auto_unconfig() - Unconfigure auto-allocated COMEDI device + * @hardware_device: Hardware device previously passed to + * comedi_auto_config(). + * + * Cleans up and eventually destroys the COMEDI device allocated by + * comedi_auto_config() for the same hardware device. As part of this + * clean-up, the low-level COMEDI driver's 'detach' handler will be called. + * (The COMEDI device itself will persist in an unattached state if it is + * still open, until it is released, and any mmapped buffers will persist + * until they are munmapped.) + * + * This is usually called from a wrapper module in a bus-specific COMEDI + * module, which in turn is usually set as the bus device 'remove' function + * in the low-level COMEDI driver. + */ +void comedi_auto_unconfig(struct device *hardware_device) +{ + if (!hardware_device) + return; + comedi_release_hardware_device(hardware_device); +} +EXPORT_SYMBOL_GPL(comedi_auto_unconfig); + +/** + * comedi_driver_register() - Register a low-level COMEDI driver + * @driver: Low-level COMEDI driver. + * + * The low-level COMEDI driver is added to the list of registered COMEDI + * drivers. This is used by the handler for the "/proc/comedi" file and is + * also used by the handler for the %COMEDI_DEVCONFIG ioctl to configure + * "legacy" COMEDI devices (for those low-level drivers that support it). + * + * Returns 0. + */ +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); + +/** + * comedi_driver_unregister() - Unregister a low-level COMEDI driver + * @driver: Low-level COMEDI driver. + * + * The low-level COMEDI driver is removed from the list of registered COMEDI + * drivers. Detaches any COMEDI devices attached to the driver, which will + * result in the low-level driver's 'detach' handler being called for those + * devices before this function returns. + */ +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/comedi/drivers/8255.c b/drivers/comedi/drivers/8255.c new file mode 100644 index 000000000..ced8ea09d --- /dev/null +++ b/drivers/comedi/drivers/8255.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/8255.c + * Driver for 8255 + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: 8255 + * Description: generic 8255 support + * Devices: [standard] 8255 (8255) + * Author: ds + * Status: works + * Updated: Fri, 7 Jun 2002 12:56:45 -0700 + * + * The classic in digital I/O. The 8255 appears in Comedi as a single + * digital I/O subdevice with 24 channels. The channel 0 corresponds + * to the 8255's port A, bit 0; channel 23 corresponds to port C, bit + * 7. Direction configuration is done in blocks, with channels 0-7, + * 8-15, 16-19, and 20-23 making up the 4 blocks. The only 8255 mode + * supported is mode 0. + * + * You should enable compilation this driver if you plan to use a board + * that has an 8255 chip. For multifunction boards, the main driver will + * configure the 8255 subdevice automatically. + * + * This driver also works independently with ISA and PCI cards that + * directly map the 8255 registers to I/O ports, including cards with + * multiple 8255 chips. To configure the driver for such a card, the + * option list should be a list of the I/O port bases for each of the + * 8255 chips. For example, + * + * comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c + * + * Note that most PCI 8255 boards do NOT work with this driver, and + * need a separate driver as a wrapper. For those that do work, the + * I/O port base address can be found in the output of 'lspci -v'. + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +static int dev_8255_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + int ret; + int i; + + for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) { + iobase = it->options[i]; + if (!iobase) + break; + } + if (i == 0) { + dev_warn(dev->class_dev, "no devices specified\n"); + return -EINVAL; + } + + ret = comedi_alloc_subdevices(dev, i); + if (ret) + return ret; + + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + iobase = it->options[i]; + + /* + * __comedi_request_region() does not set dev->iobase. + * + * For 8255 devices that are manually attached using + * comedi_config, the 'iobase' is the actual I/O port + * base address of the chip. + */ + ret = __comedi_request_region(dev, iobase, I8255_SIZE); + if (ret) { + s->type = COMEDI_SUBD_UNUSED; + } else { + ret = subdev_8255_init(dev, s, NULL, iobase); + if (ret) { + /* + * Release the I/O port region here, as the + * "detach" handler cannot find it. + */ + release_region(iobase, I8255_SIZE); + s->type = COMEDI_SUBD_UNUSED; + return ret; + } + } + } + + return 0; +} + +static void dev_8255_detach(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + int i; + + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + if (s->type != COMEDI_SUBD_UNUSED) { + unsigned long regbase = subdev_8255_regbase(s); + + release_region(regbase, I8255_SIZE); + } + } +} + +static struct comedi_driver dev_8255_driver = { + .driver_name = "8255", + .module = THIS_MODULE, + .attach = dev_8255_attach, + .detach = dev_8255_detach, +}; +module_comedi_driver(dev_8255_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for standalone 8255 devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/8255_pci.c b/drivers/comedi/drivers/8255_pci.c new file mode 100644 index 000000000..0fec048e3 --- /dev/null +++ b/drivers/comedi/drivers/8255_pci.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: 8255_pci + * Description: Generic PCI based 8255 Digital I/O boards + * Devices: [ADLink] PCI-7224 (adl_pci-7224), PCI-7248 (adl_pci-7248), + * PCI-7296 (adl_pci-7296), + * [Measurement Computing] PCI-DIO24 (cb_pci-dio24), + * PCI-DIO24H (cb_pci-dio24h), PCI-DIO48H (cb_pci-dio48h), + * PCI-DIO96H (cb_pci-dio96h), + * [National Instruments] PCI-DIO-96 (ni_pci-dio-96), + * PCI-DIO-96B (ni_pci-dio-96b), PXI-6508 (ni_pxi-6508), + * PCI-6503 (ni_pci-6503), PCI-6503B (ni_pci-6503b), + * PCI-6503X (ni_pci-6503x), PXI-6503 (ni_pxi-6503) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Wed, 12 Sep 2012 11:52:01 -0700 + * Status: untested + * + * These boards have one or more 8255 digital I/O chips, each of which + * is supported as a separate 24-channel DIO subdevice. + * + * Boards with 24 DIO channels (1 DIO subdevice): + * + * PCI-7224, PCI-DIO24, PCI-DIO24H, PCI-6503, PCI-6503B, PCI-6503X, + * PXI-6503 + * + * Boards with 48 DIO channels (2 DIO subdevices): + * + * PCI-7248, PCI-DIO48H + * + * Boards with 96 DIO channels (4 DIO subdevices): + * + * PCI-7296, PCI-DIO96H, PCI-DIO-96, PCI-DIO-96B, PXI-6508 + * + * Some of these boards also have an 8254 programmable timer/counter + * chip. This chip is not currently supported by this driver. + * + * Interrupt support for these boards is also not currently supported. + * + * Configuration Options: not applicable, uses PCI auto config. + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> + +enum pci_8255_boardid { + BOARD_ADLINK_PCI7224, + BOARD_ADLINK_PCI7248, + BOARD_ADLINK_PCI7296, + BOARD_CB_PCIDIO24, + BOARD_CB_PCIDIO24H, + BOARD_CB_PCIDIO48H_OLD, + BOARD_CB_PCIDIO48H_NEW, + BOARD_CB_PCIDIO96H, + BOARD_NI_PCIDIO96, + BOARD_NI_PCIDIO96B, + BOARD_NI_PXI6508, + BOARD_NI_PCI6503, + BOARD_NI_PCI6503B, + BOARD_NI_PCI6503X, + BOARD_NI_PXI_6503, +}; + +struct pci_8255_boardinfo { + const char *name; + int dio_badr; + int n_8255; + unsigned int has_mite:1; +}; + +static const struct pci_8255_boardinfo pci_8255_boards[] = { + [BOARD_ADLINK_PCI7224] = { + .name = "adl_pci-7224", + .dio_badr = 2, + .n_8255 = 1, + }, + [BOARD_ADLINK_PCI7248] = { + .name = "adl_pci-7248", + .dio_badr = 2, + .n_8255 = 2, + }, + [BOARD_ADLINK_PCI7296] = { + .name = "adl_pci-7296", + .dio_badr = 2, + .n_8255 = 4, + }, + [BOARD_CB_PCIDIO24] = { + .name = "cb_pci-dio24", + .dio_badr = 2, + .n_8255 = 1, + }, + [BOARD_CB_PCIDIO24H] = { + .name = "cb_pci-dio24h", + .dio_badr = 2, + .n_8255 = 1, + }, + [BOARD_CB_PCIDIO48H_OLD] = { + .name = "cb_pci-dio48h", + .dio_badr = 1, + .n_8255 = 2, + }, + [BOARD_CB_PCIDIO48H_NEW] = { + .name = "cb_pci-dio48h", + .dio_badr = 2, + .n_8255 = 2, + }, + [BOARD_CB_PCIDIO96H] = { + .name = "cb_pci-dio96h", + .dio_badr = 2, + .n_8255 = 4, + }, + [BOARD_NI_PCIDIO96] = { + .name = "ni_pci-dio-96", + .dio_badr = 1, + .n_8255 = 4, + .has_mite = 1, + }, + [BOARD_NI_PCIDIO96B] = { + .name = "ni_pci-dio-96b", + .dio_badr = 1, + .n_8255 = 4, + .has_mite = 1, + }, + [BOARD_NI_PXI6508] = { + .name = "ni_pxi-6508", + .dio_badr = 1, + .n_8255 = 4, + .has_mite = 1, + }, + [BOARD_NI_PCI6503] = { + .name = "ni_pci-6503", + .dio_badr = 1, + .n_8255 = 1, + .has_mite = 1, + }, + [BOARD_NI_PCI6503B] = { + .name = "ni_pci-6503b", + .dio_badr = 1, + .n_8255 = 1, + .has_mite = 1, + }, + [BOARD_NI_PCI6503X] = { + .name = "ni_pci-6503x", + .dio_badr = 1, + .n_8255 = 1, + .has_mite = 1, + }, + [BOARD_NI_PXI_6503] = { + .name = "ni_pxi-6503", + .dio_badr = 1, + .n_8255 = 1, + .has_mite = 1, + }, +}; + +/* ripped from mite.h and mite_setup2() to avoid mite dependency */ +#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */ +#define WENAB BIT(7) /* window enable */ + +static int pci_8255_mite_init(struct pci_dev *pcidev) +{ + void __iomem *mite_base; + u32 main_phys_addr; + + /* ioremap the MITE registers (BAR 0) temporarily */ + mite_base = pci_ioremap_bar(pcidev, 0); + if (!mite_base) + return -ENOMEM; + + /* set data window to main registers (BAR 1) */ + main_phys_addr = pci_resource_start(pcidev, 1); + writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR); + + /* finished with MITE registers */ + iounmap(mite_base); + return 0; +} + +static int pci_8255_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct pci_8255_boardinfo *board = NULL; + struct comedi_subdevice *s; + int ret; + int i; + + if (context < ARRAY_SIZE(pci_8255_boards)) + board = &pci_8255_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + if (board->has_mite) { + ret = pci_8255_mite_init(pcidev); + if (ret) + return ret; + } + + if ((pci_resource_flags(pcidev, board->dio_badr) & IORESOURCE_MEM)) { + dev->mmio = pci_ioremap_bar(pcidev, board->dio_badr); + if (!dev->mmio) + return -ENOMEM; + } else { + dev->iobase = pci_resource_start(pcidev, board->dio_badr); + } + + /* + * One, two, or four subdevices are setup by this driver depending + * on the number of channels provided by the board. Each subdevice + * has 24 channels supported by the 8255 module. + */ + ret = comedi_alloc_subdevices(dev, board->n_8255); + if (ret) + return ret; + + for (i = 0; i < board->n_8255; i++) { + s = &dev->subdevices[i]; + if (dev->mmio) + ret = subdev_8255_mm_init(dev, s, NULL, i * I8255_SIZE); + else + ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE); + if (ret) + return ret; + } + + return 0; +} + +static struct comedi_driver pci_8255_driver = { + .driver_name = "8255_pci", + .module = THIS_MODULE, + .auto_attach = pci_8255_auto_attach, + .detach = comedi_pci_detach, +}; + +static int pci_8255_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &pci_8255_driver, id->driver_data); +} + +static const struct pci_device_id pci_8255_pci_table[] = { + { PCI_VDEVICE(ADLINK, 0x7224), BOARD_ADLINK_PCI7224 }, + { PCI_VDEVICE(ADLINK, 0x7248), BOARD_ADLINK_PCI7248 }, + { PCI_VDEVICE(ADLINK, 0x7296), BOARD_ADLINK_PCI7296 }, + { PCI_VDEVICE(CB, 0x0028), BOARD_CB_PCIDIO24 }, + { PCI_VDEVICE(CB, 0x0014), BOARD_CB_PCIDIO24H }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_CB, 0x000b, 0x0000, 0x0000), + .driver_data = BOARD_CB_PCIDIO48H_OLD }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_CB, 0x000b, PCI_VENDOR_ID_CB, 0x000b), + .driver_data = BOARD_CB_PCIDIO48H_NEW }, + { PCI_VDEVICE(CB, 0x0017), BOARD_CB_PCIDIO96H }, + { PCI_VDEVICE(NI, 0x0160), BOARD_NI_PCIDIO96 }, + { PCI_VDEVICE(NI, 0x1630), BOARD_NI_PCIDIO96B }, + { PCI_VDEVICE(NI, 0x13c0), BOARD_NI_PXI6508 }, + { PCI_VDEVICE(NI, 0x0400), BOARD_NI_PCI6503 }, + { PCI_VDEVICE(NI, 0x1250), BOARD_NI_PCI6503B }, + { PCI_VDEVICE(NI, 0x17d0), BOARD_NI_PCI6503X }, + { PCI_VDEVICE(NI, 0x1800), BOARD_NI_PXI_6503 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, pci_8255_pci_table); + +static struct pci_driver pci_8255_pci_driver = { + .name = "8255_pci", + .id_table = pci_8255_pci_table, + .probe = pci_8255_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(pci_8255_driver, pci_8255_pci_driver); + +MODULE_DESCRIPTION("COMEDI - Generic PCI based 8255 Digital I/O boards"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/Makefile b/drivers/comedi/drivers/Makefile new file mode 100644 index 000000000..b24ac00ca --- /dev/null +++ b/drivers/comedi/drivers/Makefile @@ -0,0 +1,175 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for individual comedi drivers +# +ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG + +# Comedi "helper" modules +obj-$(CONFIG_COMEDI_8254) += comedi_8254.o +obj-$(CONFIG_COMEDI_ISADMA) += comedi_isadma.o + +# Comedi misc drivers +obj-$(CONFIG_COMEDI_BOND) += comedi_bond.o +obj-$(CONFIG_COMEDI_TEST) += comedi_test.o +obj-$(CONFIG_COMEDI_PARPORT) += comedi_parport.o + +# Comedi ISA drivers +obj-$(CONFIG_COMEDI_AMPLC_DIO200_ISA) += amplc_dio200.o +obj-$(CONFIG_COMEDI_AMPLC_PC236_ISA) += amplc_pc236.o +obj-$(CONFIG_COMEDI_AMPLC_PC263_ISA) += amplc_pc263.o +obj-$(CONFIG_COMEDI_PCL711) += pcl711.o +obj-$(CONFIG_COMEDI_PCL724) += pcl724.o +obj-$(CONFIG_COMEDI_PCL726) += pcl726.o +obj-$(CONFIG_COMEDI_PCL730) += pcl730.o +obj-$(CONFIG_COMEDI_PCL812) += pcl812.o +obj-$(CONFIG_COMEDI_PCL816) += pcl816.o +obj-$(CONFIG_COMEDI_PCL818) += pcl818.o +obj-$(CONFIG_COMEDI_PCM3724) += pcm3724.o +obj-$(CONFIG_COMEDI_RTI800) += rti800.o +obj-$(CONFIG_COMEDI_RTI802) += rti802.o +obj-$(CONFIG_COMEDI_DAC02) += dac02.o +obj-$(CONFIG_COMEDI_DAS16M1) += das16m1.o +obj-$(CONFIG_COMEDI_DAS08_ISA) += das08_isa.o +obj-$(CONFIG_COMEDI_DAS16) += das16.o +obj-$(CONFIG_COMEDI_DAS800) += das800.o +obj-$(CONFIG_COMEDI_DAS1800) += das1800.o +obj-$(CONFIG_COMEDI_DAS6402) += das6402.o +obj-$(CONFIG_COMEDI_DT2801) += dt2801.o +obj-$(CONFIG_COMEDI_DT2811) += dt2811.o +obj-$(CONFIG_COMEDI_DT2814) += dt2814.o +obj-$(CONFIG_COMEDI_DT2815) += dt2815.o +obj-$(CONFIG_COMEDI_DT2817) += dt2817.o +obj-$(CONFIG_COMEDI_DT282X) += dt282x.o +obj-$(CONFIG_COMEDI_DMM32AT) += dmm32at.o +obj-$(CONFIG_COMEDI_FL512) += fl512.o +obj-$(CONFIG_COMEDI_AIO_AIO12_8) += aio_aio12_8.o +obj-$(CONFIG_COMEDI_AIO_IIRO_16) += aio_iiro_16.o +obj-$(CONFIG_COMEDI_II_PCI20KC) += ii_pci20kc.o +obj-$(CONFIG_COMEDI_C6XDIGIO) += c6xdigio.o +obj-$(CONFIG_COMEDI_MPC624) += mpc624.o +obj-$(CONFIG_COMEDI_ADQ12B) += adq12b.o +obj-$(CONFIG_COMEDI_NI_AT_A2150) += ni_at_a2150.o +obj-$(CONFIG_COMEDI_NI_AT_AO) += ni_at_ao.o +obj-$(CONFIG_COMEDI_NI_ATMIO) += ni_atmio.o +obj-$(CONFIG_COMEDI_NI_ATMIO16D) += ni_atmio16d.o +obj-$(CONFIG_COMEDI_NI_LABPC_ISA) += ni_labpc.o +obj-$(CONFIG_COMEDI_PCMAD) += pcmad.o +obj-$(CONFIG_COMEDI_PCMDA12) += pcmda12.o +obj-$(CONFIG_COMEDI_PCMMIO) += pcmmio.o +obj-$(CONFIG_COMEDI_PCMUIO) += pcmuio.o +obj-$(CONFIG_COMEDI_MULTIQ3) += multiq3.o +obj-$(CONFIG_COMEDI_S526) += s526.o + +# Comedi PCI drivers +obj-$(CONFIG_COMEDI_8255_PCI) += 8255_pci.o +obj-$(CONFIG_COMEDI_ADDI_WATCHDOG) += addi_watchdog.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1032) += addi_apci_1032.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1500) += addi_apci_1500.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1516) += addi_apci_1516.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1564) += addi_apci_1564.o +obj-$(CONFIG_COMEDI_ADDI_APCI_16XX) += addi_apci_16xx.o +obj-$(CONFIG_COMEDI_ADDI_APCI_2032) += addi_apci_2032.o +obj-$(CONFIG_COMEDI_ADDI_APCI_2200) += addi_apci_2200.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3120) += addi_apci_3120.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3501) += addi_apci_3501.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3XXX) += addi_apci_3xxx.o +obj-$(CONFIG_COMEDI_ADL_PCI6208) += adl_pci6208.o +obj-$(CONFIG_COMEDI_ADL_PCI7X3X) += adl_pci7x3x.o +obj-$(CONFIG_COMEDI_ADL_PCI8164) += adl_pci8164.o +obj-$(CONFIG_COMEDI_ADL_PCI9111) += adl_pci9111.o +obj-$(CONFIG_COMEDI_ADL_PCI9118) += adl_pci9118.o +obj-$(CONFIG_COMEDI_ADV_PCI1710) += adv_pci1710.o +obj-$(CONFIG_COMEDI_ADV_PCI1720) += adv_pci1720.o +obj-$(CONFIG_COMEDI_ADV_PCI1723) += adv_pci1723.o +obj-$(CONFIG_COMEDI_ADV_PCI1724) += adv_pci1724.o +obj-$(CONFIG_COMEDI_ADV_PCI1760) += adv_pci1760.o +obj-$(CONFIG_COMEDI_ADV_PCI_DIO) += adv_pci_dio.o +obj-$(CONFIG_COMEDI_AMPLC_DIO200_PCI) += amplc_dio200_pci.o +obj-$(CONFIG_COMEDI_AMPLC_PC236_PCI) += amplc_pci236.o +obj-$(CONFIG_COMEDI_AMPLC_PC263_PCI) += amplc_pci263.o +obj-$(CONFIG_COMEDI_AMPLC_PCI224) += amplc_pci224.o +obj-$(CONFIG_COMEDI_AMPLC_PCI230) += amplc_pci230.o +obj-$(CONFIG_COMEDI_CONTEC_PCI_DIO) += contec_pci_dio.o +obj-$(CONFIG_COMEDI_DAS08_PCI) += das08_pci.o +obj-$(CONFIG_COMEDI_DT3000) += dt3000.o +obj-$(CONFIG_COMEDI_DYNA_PCI10XX) += dyna_pci10xx.o +obj-$(CONFIG_COMEDI_GSC_HPDI) += gsc_hpdi.o +obj-$(CONFIG_COMEDI_ICP_MULTI) += icp_multi.o +obj-$(CONFIG_COMEDI_DAQBOARD2000) += daqboard2000.o +obj-$(CONFIG_COMEDI_JR3_PCI) += jr3_pci.o +obj-$(CONFIG_COMEDI_KE_COUNTER) += ke_counter.o +obj-$(CONFIG_COMEDI_CB_PCIDAS64) += cb_pcidas64.o +obj-$(CONFIG_COMEDI_CB_PCIDAS) += cb_pcidas.o +obj-$(CONFIG_COMEDI_CB_PCIDDA) += cb_pcidda.o +obj-$(CONFIG_COMEDI_CB_PCIMDAS) += cb_pcimdas.o +obj-$(CONFIG_COMEDI_CB_PCIMDDA) += cb_pcimdda.o +obj-$(CONFIG_COMEDI_ME4000) += me4000.o +obj-$(CONFIG_COMEDI_ME_DAQ) += me_daq.o +obj-$(CONFIG_COMEDI_NI_6527) += ni_6527.o +obj-$(CONFIG_COMEDI_NI_65XX) += ni_65xx.o +obj-$(CONFIG_COMEDI_NI_660X) += ni_660x.o +obj-$(CONFIG_COMEDI_NI_670X) += ni_670x.o +obj-$(CONFIG_COMEDI_NI_LABPC_PCI) += ni_labpc_pci.o +obj-$(CONFIG_COMEDI_NI_PCIDIO) += ni_pcidio.o +obj-$(CONFIG_COMEDI_NI_PCIMIO) += ni_pcimio.o +obj-$(CONFIG_COMEDI_RTD520) += rtd520.o +obj-$(CONFIG_COMEDI_S626) += s626.o +obj-$(CONFIG_COMEDI_SSV_DNP) += ssv_dnp.o +obj-$(CONFIG_COMEDI_MF6X4) += mf6x4.o + +# Comedi PCMCIA drivers +obj-$(CONFIG_COMEDI_CB_DAS16_CS) += cb_das16_cs.o +obj-$(CONFIG_COMEDI_DAS08_CS) += das08_cs.o +obj-$(CONFIG_COMEDI_NI_DAQ_700_CS) += ni_daq_700.o +obj-$(CONFIG_COMEDI_NI_DAQ_DIO24_CS) += ni_daq_dio24.o +obj-$(CONFIG_COMEDI_NI_LABPC_CS) += ni_labpc_cs.o +obj-$(CONFIG_COMEDI_NI_MIO_CS) += ni_mio_cs.o +obj-$(CONFIG_COMEDI_QUATECH_DAQP_CS) += quatech_daqp_cs.o + +# Comedi USB drivers +obj-$(CONFIG_COMEDI_DT9812) += dt9812.o +obj-$(CONFIG_COMEDI_NI_USB6501) += ni_usb6501.o +obj-$(CONFIG_COMEDI_USBDUX) += usbdux.o +obj-$(CONFIG_COMEDI_USBDUXFAST) += usbduxfast.o +obj-$(CONFIG_COMEDI_USBDUXSIGMA) += usbduxsigma.o +obj-$(CONFIG_COMEDI_VMK80XX) += vmk80xx.o + +# Comedi NI drivers +obj-$(CONFIG_COMEDI_MITE) += mite.o +obj-$(CONFIG_COMEDI_NI_TIO) += ni_tio.o +obj-$(CONFIG_COMEDI_NI_TIOCMD) += ni_tiocmd.o +obj-$(CONFIG_COMEDI_NI_ROUTING) += ni_routing.o +ni_routing-objs += ni_routes.o \ + ni_routing/ni_route_values.o \ + ni_routing/ni_route_values/ni_660x.o \ + ni_routing/ni_route_values/ni_eseries.o \ + ni_routing/ni_route_values/ni_mseries.o \ + ni_routing/ni_device_routes.o \ + ni_routing/ni_device_routes/pxi-6030e.o \ + ni_routing/ni_device_routes/pci-6070e.o \ + ni_routing/ni_device_routes/pci-6220.o \ + ni_routing/ni_device_routes/pci-6221.o \ + ni_routing/ni_device_routes/pxi-6224.o \ + ni_routing/ni_device_routes/pxi-6225.o \ + ni_routing/ni_device_routes/pci-6229.o \ + ni_routing/ni_device_routes/pci-6251.o \ + ni_routing/ni_device_routes/pxi-6251.o \ + ni_routing/ni_device_routes/pxie-6251.o \ + ni_routing/ni_device_routes/pci-6254.o \ + ni_routing/ni_device_routes/pci-6259.o \ + ni_routing/ni_device_routes/pci-6534.o \ + ni_routing/ni_device_routes/pxie-6535.o \ + ni_routing/ni_device_routes/pci-6602.o \ + ni_routing/ni_device_routes/pci-6713.o \ + ni_routing/ni_device_routes/pci-6723.o \ + ni_routing/ni_device_routes/pci-6733.o \ + ni_routing/ni_device_routes/pxi-6733.o \ + ni_routing/ni_device_routes/pxie-6738.o +obj-$(CONFIG_COMEDI_NI_LABPC) += ni_labpc_common.o +obj-$(CONFIG_COMEDI_NI_LABPC_ISADMA) += ni_labpc_isadma.o + +obj-$(CONFIG_COMEDI_8255) += comedi_8255.o +obj-$(CONFIG_COMEDI_8255_SA) += 8255.o +obj-$(CONFIG_COMEDI_AMPLC_DIO200) += amplc_dio200_common.o +obj-$(CONFIG_COMEDI_AMPLC_PC236) += amplc_pc236_common.o +obj-$(CONFIG_COMEDI_DAS08) += das08.o +obj-$(CONFIG_COMEDI_TESTS) += tests/ diff --git a/drivers/comedi/drivers/addi_apci_1032.c b/drivers/comedi/drivers/addi_apci_1032.c new file mode 100644 index 000000000..8eec6d940 --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_1032.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* + * Driver: addi_apci_1032 + * Description: ADDI-DATA APCI-1032 Digital Input Board + * Author: ADDI-DATA GmbH <info@addi-data.com>, + * H Hartley Sweeten <hsweeten@visionengravers.com> + * Status: untested + * Devices: [ADDI-DATA] APCI-1032 (addi_apci_1032) + * + * Configuration options: + * None; devices are configured automatically. + * + * This driver models the APCI-1032 as a 32-channel, digital input subdevice + * plus an additional digital input subdevice to handle change-of-state (COS) + * interrupts (if an interrupt handler can be set up successfully). + * + * The COS subdevice supports comedi asynchronous read commands. + * + * Change-Of-State (COS) interrupt configuration: + * + * Channels 0 to 15 are interruptible. These channels can be configured + * to generate interrupts based on AND/OR logic for the desired channels. + * + * OR logic: + * - reacts to rising or falling edges + * - interrupt is generated when any enabled channel meets the desired + * interrupt condition + * + * AND logic: + * - reacts to changes in level of the selected inputs + * - interrupt is generated when all enabled channels meet the desired + * interrupt condition + * - after an interrupt, a change in level must occur on the selected + * inputs to release the IRQ logic + * + * The COS subdevice must be configured before setting up a comedi + * asynchronous command: + * + * data[0] : INSN_CONFIG_DIGITAL_TRIG + * data[1] : trigger number (= 0) + * data[2] : configuration operation: + * - COMEDI_DIGITAL_TRIG_DISABLE = no interrupts + * - COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts + * - COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts + * data[3] : left-shift for data[4] and data[5] + * data[4] : rising-edge/high level channels + * data[5] : falling-edge/low level channels + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "amcc_s5933.h" + +/* + * I/O Register Map + */ +#define APCI1032_DI_REG 0x00 +#define APCI1032_MODE1_REG 0x04 +#define APCI1032_MODE2_REG 0x08 +#define APCI1032_STATUS_REG 0x0c +#define APCI1032_CTRL_REG 0x10 +#define APCI1032_CTRL_INT_MODE(x) (((x) & 0x1) << 1) +#define APCI1032_CTRL_INT_OR APCI1032_CTRL_INT_MODE(0) +#define APCI1032_CTRL_INT_AND APCI1032_CTRL_INT_MODE(1) +#define APCI1032_CTRL_INT_ENA BIT(2) + +struct apci1032_private { + unsigned long amcc_iobase; /* base of AMCC I/O registers */ + unsigned int mode1; /* rising-edge/high level channels */ + unsigned int mode2; /* falling-edge/low level channels */ + unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */ +}; + +static int apci1032_reset(struct comedi_device *dev) +{ + /* disable the interrupts */ + outl(0x0, dev->iobase + APCI1032_CTRL_REG); + /* Reset the interrupt status register */ + inl(dev->iobase + APCI1032_STATUS_REG); + /* Disable the and/or interrupt */ + outl(0x0, dev->iobase + APCI1032_MODE1_REG); + outl(0x0, dev->iobase + APCI1032_MODE2_REG); + + return 0; +} + +static int apci1032_cos_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1032_private *devpriv = dev->private; + unsigned int shift, oldmask, himask, lomask; + + switch (data[0]) { + case INSN_CONFIG_DIGITAL_TRIG: + if (data[1] != 0) + return -EINVAL; + shift = data[3]; + if (shift < 32) { + oldmask = (1U << shift) - 1; + himask = data[4] << shift; + lomask = data[5] << shift; + } else { + oldmask = 0xffffffffu; + himask = 0; + lomask = 0; + } + 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 |= himask; + devpriv->mode2 |= lomask; + 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 |= himask; + devpriv->mode2 |= lomask; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int apci1032_cos_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = s->state; + + return 0; +} + +static int apci1032_cos_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +/* + * Change-Of-State (COS) 'do_cmd' operation + * + * Enable the COS interrupt as configured by apci1032_cos_insn_config(). + */ +static int apci1032_cos_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci1032_private *devpriv = dev->private; + + if (!devpriv->ctrl) { + dev_warn(dev->class_dev, + "Interrupts disabled due to mode configuration!\n"); + return -EINVAL; + } + + outl(devpriv->mode1, dev->iobase + APCI1032_MODE1_REG); + outl(devpriv->mode2, dev->iobase + APCI1032_MODE2_REG); + outl(devpriv->ctrl, dev->iobase + APCI1032_CTRL_REG); + + return 0; +} + +static int apci1032_cos_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + return apci1032_reset(dev); +} + +static irqreturn_t apci1032_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct apci1032_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int ctrl; + unsigned short val; + + /* 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; + val = s->state; + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + + /* enable the interrupt */ + outl(ctrl, dev->iobase + APCI1032_CTRL_REG); + + return IRQ_HANDLED; +} + +static int apci1032_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inl(dev->iobase + APCI1032_DI_REG); + + return insn->n; +} + +static int apci1032_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct apci1032_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + devpriv->amcc_iobase = pci_resource_start(pcidev, 0); + dev->iobase = pci_resource_start(pcidev, 1); + apci1032_reset(dev); + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, apci1032_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Allocate and Initialise DI Subdevice Structures */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci1032_di_insn_bits; + + /* Change-Of-State (COS) interrupt subdevice */ + s = &dev->subdevices[1]; + if (dev->irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = apci1032_cos_insn_config; + s->insn_bits = apci1032_cos_insn_bits; + s->len_chanlist = 1; + s->do_cmdtest = apci1032_cos_cmdtest; + s->do_cmd = apci1032_cos_cmd; + s->cancel = apci1032_cos_cancel; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static void apci1032_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci1032_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci1032_driver = { + .driver_name = "addi_apci_1032", + .module = THIS_MODULE, + .auto_attach = apci1032_auto_attach, + .detach = apci1032_detach, +}; + +static int apci1032_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci1032_driver, id->driver_data); +} + +static const struct pci_device_id apci1032_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1003) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci1032_pci_table); + +static struct pci_driver apci1032_pci_driver = { + .name = "addi_apci_1032", + .id_table = apci1032_pci_table, + .probe = apci1032_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci1032_driver, apci1032_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("ADDI-DATA APCI-1032, 32 channel DI boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_1500.c b/drivers/comedi/drivers/addi_apci_1500.c new file mode 100644 index 000000000..c94c78588 --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_1500.c @@ -0,0 +1,887 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * addi_apci_1500.c + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "amcc_s5933.h" +#include "z8536.h" + +/* + * PCI Bar 0 Register map (devpriv->amcc) + * see amcc_s5933.h for register and bit defines + */ + +/* + * PCI Bar 1 Register map (dev->iobase) + * see z8536.h for Z8536 internal registers and bit defines + */ +#define APCI1500_Z8536_PORTC_REG 0x00 +#define APCI1500_Z8536_PORTB_REG 0x01 +#define APCI1500_Z8536_PORTA_REG 0x02 +#define APCI1500_Z8536_CTRL_REG 0x03 + +/* + * PCI Bar 2 Register map (devpriv->addon) + */ +#define APCI1500_CLK_SEL_REG 0x00 +#define APCI1500_DI_REG 0x00 +#define APCI1500_DO_REG 0x02 + +struct apci1500_private { + unsigned long amcc; + unsigned long addon; + + unsigned int clk_src; + + /* Digital trigger configuration [0]=AND [1]=OR */ + unsigned int pm[2]; /* Pattern Mask */ + unsigned int pt[2]; /* Pattern Transition */ + unsigned int pp[2]; /* Pattern Polarity */ +}; + +static unsigned int z8536_read(struct comedi_device *dev, unsigned int reg) +{ + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(&dev->spinlock, flags); + outb(reg, dev->iobase + APCI1500_Z8536_CTRL_REG); + val = inb(dev->iobase + APCI1500_Z8536_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return val; +} + +static void z8536_write(struct comedi_device *dev, + unsigned int val, unsigned int reg) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + outb(reg, dev->iobase + APCI1500_Z8536_CTRL_REG); + outb(val, dev->iobase + APCI1500_Z8536_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static void z8536_reset(struct comedi_device *dev) +{ + unsigned long flags; + + /* + * Even if the state of the Z8536 is not known, the following + * sequence will reset it and put it in State 0. + */ + spin_lock_irqsave(&dev->spinlock, flags); + inb(dev->iobase + APCI1500_Z8536_CTRL_REG); + outb(0, dev->iobase + APCI1500_Z8536_CTRL_REG); + inb(dev->iobase + APCI1500_Z8536_CTRL_REG); + outb(0, dev->iobase + APCI1500_Z8536_CTRL_REG); + outb(1, dev->iobase + APCI1500_Z8536_CTRL_REG); + outb(0, dev->iobase + APCI1500_Z8536_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* Disable all Ports and Counter/Timers */ + z8536_write(dev, 0x00, Z8536_CFG_CTRL_REG); + + /* + * Port A is connected to Ditial Input channels 0-7. + * Configure the port to allow interrupt detection. + */ + z8536_write(dev, Z8536_PAB_MODE_PTS_BIT | + Z8536_PAB_MODE_SB | + Z8536_PAB_MODE_PMS_DISABLE, + Z8536_PA_MODE_REG); + z8536_write(dev, 0xff, Z8536_PB_DPP_REG); + z8536_write(dev, 0xff, Z8536_PA_DD_REG); + + /* + * Port B is connected to Ditial Input channels 8-13. + * Configure the port to allow interrupt detection. + * + * NOTE: Bits 7 and 6 of Port B are connected to internal + * diagnostic signals and bit 7 is inverted. + */ + z8536_write(dev, Z8536_PAB_MODE_PTS_BIT | + Z8536_PAB_MODE_SB | + Z8536_PAB_MODE_PMS_DISABLE, + Z8536_PB_MODE_REG); + z8536_write(dev, 0x7f, Z8536_PB_DPP_REG); + z8536_write(dev, 0xff, Z8536_PB_DD_REG); + + /* + * Not sure what Port C is connected to... + */ + z8536_write(dev, 0x09, Z8536_PC_DPP_REG); + z8536_write(dev, 0x0e, Z8536_PC_DD_REG); + + /* + * Clear and disable all interrupt sources. + * + * Just in case, the reset of the Z8536 should have already + * done this. + */ + z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_PA_CMDSTAT_REG); + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PA_CMDSTAT_REG); + + z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_PB_CMDSTAT_REG); + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PB_CMDSTAT_REG); + + z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_CT_CMDSTAT_REG(0)); + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_CT_CMDSTAT_REG(0)); + + z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_CT_CMDSTAT_REG(1)); + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_CT_CMDSTAT_REG(1)); + + z8536_write(dev, Z8536_CMD_CLR_IP_IUS, Z8536_CT_CMDSTAT_REG(2)); + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_CT_CMDSTAT_REG(2)); + + /* Disable all interrupts */ + z8536_write(dev, 0x00, Z8536_INT_CTRL_REG); +} + +static void apci1500_port_enable(struct comedi_device *dev, bool enable) +{ + unsigned int cfg; + + cfg = z8536_read(dev, Z8536_CFG_CTRL_REG); + if (enable) + cfg |= (Z8536_CFG_CTRL_PAE | Z8536_CFG_CTRL_PBE); + else + cfg &= ~(Z8536_CFG_CTRL_PAE | Z8536_CFG_CTRL_PBE); + z8536_write(dev, cfg, Z8536_CFG_CTRL_REG); +} + +static void apci1500_timer_enable(struct comedi_device *dev, + unsigned int chan, bool enable) +{ + unsigned int bit; + unsigned int cfg; + + if (chan == 0) + bit = Z8536_CFG_CTRL_CT1E; + else if (chan == 1) + bit = Z8536_CFG_CTRL_CT2E; + else + bit = Z8536_CFG_CTRL_PCE_CT3E; + + cfg = z8536_read(dev, Z8536_CFG_CTRL_REG); + if (enable) { + cfg |= bit; + } else { + cfg &= ~bit; + z8536_write(dev, 0x00, Z8536_CT_CMDSTAT_REG(chan)); + } + z8536_write(dev, cfg, Z8536_CFG_CTRL_REG); +} + +static bool apci1500_ack_irq(struct comedi_device *dev, + unsigned int reg) +{ + unsigned int val; + + val = z8536_read(dev, reg); + if ((val & Z8536_STAT_IE_IP) == Z8536_STAT_IE_IP) { + val &= 0x0f; /* preserve any write bits */ + val |= Z8536_CMD_CLR_IP_IUS; + z8536_write(dev, val, reg); + + return true; + } + return false; +} + +static irqreturn_t apci1500_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct apci1500_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned short status = 0; + unsigned int val; + + val = inl(devpriv->amcc + AMCC_OP_REG_INTCSR); + if (!(val & INTCSR_INTR_ASSERTED)) + return IRQ_NONE; + + if (apci1500_ack_irq(dev, Z8536_PA_CMDSTAT_REG)) + status |= 0x01; /* port a event (inputs 0-7) */ + + if (apci1500_ack_irq(dev, Z8536_PB_CMDSTAT_REG)) { + /* Tests if this is an external error */ + val = inb(dev->iobase + APCI1500_Z8536_PORTB_REG); + val &= 0xc0; + if (val) { + if (val & 0x80) /* voltage error */ + status |= 0x40; + if (val & 0x40) /* short circuit error */ + status |= 0x80; + } else { + status |= 0x02; /* port b event (inputs 8-13) */ + } + } + + /* + * NOTE: The 'status' returned by the sample matches the + * interrupt mask information from the APCI-1500 Users Manual. + * + * Mask Meaning + * ---------- ------------------------------------------ + * 0b00000001 Event 1 has occurred + * 0b00000010 Event 2 has occurred + * 0b00000100 Counter/timer 1 has run down (not implemented) + * 0b00001000 Counter/timer 2 has run down (not implemented) + * 0b00010000 Counter 3 has run down (not implemented) + * 0b00100000 Watchdog has run down (not implemented) + * 0b01000000 Voltage error + * 0b10000000 Short-circuit error + */ + comedi_buf_write_samples(s, &status, 1); + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int apci1500_di_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* Disables the main interrupt on the board */ + z8536_write(dev, 0x00, Z8536_INT_CTRL_REG); + + /* Disable Ports A & B */ + apci1500_port_enable(dev, false); + + /* Ack any pending interrupts */ + apci1500_ack_irq(dev, Z8536_PA_CMDSTAT_REG); + apci1500_ack_irq(dev, Z8536_PB_CMDSTAT_REG); + + /* Disable pattern interrupts */ + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PA_CMDSTAT_REG); + z8536_write(dev, Z8536_CMD_CLR_IE, Z8536_PB_CMDSTAT_REG); + + /* Enable Ports A & B */ + apci1500_port_enable(dev, true); + + return 0; +} + +static int apci1500_di_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct apci1500_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int pa_mode = Z8536_PAB_MODE_PMS_DISABLE; + unsigned int pb_mode = Z8536_PAB_MODE_PMS_DISABLE; + unsigned int pa_trig = trig_num & 0x01; + unsigned int pb_trig = (trig_num >> 1) & 0x01; + bool valid_trig = false; + unsigned int val; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + /* Disable Ports A & B */ + apci1500_port_enable(dev, false); + + /* Set Port A for selected trigger pattern */ + z8536_write(dev, devpriv->pm[pa_trig] & 0xff, Z8536_PA_PM_REG); + z8536_write(dev, devpriv->pt[pa_trig] & 0xff, Z8536_PA_PT_REG); + z8536_write(dev, devpriv->pp[pa_trig] & 0xff, Z8536_PA_PP_REG); + + /* Set Port B for selected trigger pattern */ + z8536_write(dev, (devpriv->pm[pb_trig] >> 8) & 0xff, Z8536_PB_PM_REG); + z8536_write(dev, (devpriv->pt[pb_trig] >> 8) & 0xff, Z8536_PB_PT_REG); + z8536_write(dev, (devpriv->pp[pb_trig] >> 8) & 0xff, Z8536_PB_PP_REG); + + /* Set Port A trigger mode (if enabled) and enable interrupt */ + if (devpriv->pm[pa_trig] & 0xff) { + pa_mode = pa_trig ? Z8536_PAB_MODE_PMS_AND + : Z8536_PAB_MODE_PMS_OR; + + val = z8536_read(dev, Z8536_PA_MODE_REG); + val &= ~Z8536_PAB_MODE_PMS_MASK; + val |= (pa_mode | Z8536_PAB_MODE_IMO); + z8536_write(dev, val, Z8536_PA_MODE_REG); + + z8536_write(dev, Z8536_CMD_SET_IE, Z8536_PA_CMDSTAT_REG); + + valid_trig = true; + + dev_dbg(dev->class_dev, + "Port A configured for %s mode pattern detection\n", + pa_trig ? "AND" : "OR"); + } + + /* Set Port B trigger mode (if enabled) and enable interrupt */ + if (devpriv->pm[pb_trig] & 0xff00) { + pb_mode = pb_trig ? Z8536_PAB_MODE_PMS_AND + : Z8536_PAB_MODE_PMS_OR; + + val = z8536_read(dev, Z8536_PB_MODE_REG); + val &= ~Z8536_PAB_MODE_PMS_MASK; + val |= (pb_mode | Z8536_PAB_MODE_IMO); + z8536_write(dev, val, Z8536_PB_MODE_REG); + + z8536_write(dev, Z8536_CMD_SET_IE, Z8536_PB_CMDSTAT_REG); + + valid_trig = true; + + dev_dbg(dev->class_dev, + "Port B configured for %s mode pattern detection\n", + pb_trig ? "AND" : "OR"); + } + + /* Enable Ports A & B */ + apci1500_port_enable(dev, true); + + if (!valid_trig) { + dev_dbg(dev->class_dev, + "digital trigger %d is not configured\n", trig_num); + return -EINVAL; + } + + /* Authorizes the main interrupt on the board */ + z8536_write(dev, Z8536_INT_CTRL_MIE | Z8536_INT_CTRL_DLC, + Z8536_INT_CTRL_REG); + + return 0; +} + +static int apci1500_di_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + s->async->inttrig = apci1500_di_inttrig_start; + + return 0; +} + +static int apci1500_di_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + /* + * Internal start source triggers: + * + * 0 AND mode for Port A (digital inputs 0-7) + * AND mode for Port B (digital inputs 8-13 and internal signals) + * + * 1 OR mode for Port A (digital inputs 0-7) + * AND mode for Port B (digital inputs 8-13 and internal signals) + * + * 2 AND mode for Port A (digital inputs 0-7) + * OR mode for Port B (digital inputs 8-13 and internal signals) + * + * 3 OR mode for Port A (digital inputs 0-7) + * OR mode for Port B (digital inputs 8-13 and internal signals) + */ + err |= comedi_check_trigger_arg_max(&cmd->start_arg, 3); + + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +/* + * The pattern-recognition logic must be configured before the digital + * input async command is started. + * + * Digital input channels 0 to 13 can generate interrupts. Channels 14 + * and 15 are connected to internal board status/diagnostic signals. + * + * Channel 14 - Voltage error (the external supply is < 5V) + * Channel 15 - Short-circuit/overtemperature error + * + * data[0] : INSN_CONFIG_DIGITAL_TRIG + * data[1] : trigger number + * 0 = AND mode + * 1 = OR mode + * data[2] : configuration operation: + * COMEDI_DIGITAL_TRIG_DISABLE = no interrupts + * COMEDI_DIGITAL_TRIG_ENABLE_EDGES = edge interrupts + * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = level interrupts + * data[3] : left-shift for data[4] and data[5] + * data[4] : rising-edge/high level channels + * data[5] : falling-edge/low level channels + */ +static int apci1500_di_cfg_trig(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1500_private *devpriv = dev->private; + unsigned int trig = data[1]; + unsigned int shift = data[3]; + unsigned int hi_mask; + unsigned int lo_mask; + unsigned int chan_mask; + unsigned int old_mask; + unsigned int pm; + unsigned int pt; + unsigned int pp; + unsigned int invalid_chan; + + if (trig > 1) { + dev_dbg(dev->class_dev, + "invalid digital trigger number (0=AND, 1=OR)\n"); + return -EINVAL; + } + + if (shift <= 16) { + hi_mask = data[4] << shift; + lo_mask = data[5] << shift; + old_mask = (1U << shift) - 1; + invalid_chan = (data[4] | data[5]) >> (16 - shift); + } else { + hi_mask = 0; + lo_mask = 0; + old_mask = 0xffff; + invalid_chan = data[4] | data[5]; + } + chan_mask = hi_mask | lo_mask; + + if (invalid_chan) { + dev_dbg(dev->class_dev, "invalid digital trigger channel\n"); + return -EINVAL; + } + + pm = devpriv->pm[trig] & old_mask; + pt = devpriv->pt[trig] & old_mask; + pp = devpriv->pp[trig] & old_mask; + + switch (data[2]) { + case COMEDI_DIGITAL_TRIG_DISABLE: + /* clear trigger configuration */ + pm = 0; + pt = 0; + pp = 0; + break; + case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: + pm |= chan_mask; /* enable channels */ + pt |= chan_mask; /* enable edge detection */ + pp |= hi_mask; /* rising-edge channels */ + pp &= ~lo_mask; /* falling-edge channels */ + break; + case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS: + pm |= chan_mask; /* enable channels */ + pt &= ~chan_mask; /* enable level detection */ + pp |= hi_mask; /* high level channels */ + pp &= ~lo_mask; /* low level channels */ + break; + default: + return -EINVAL; + } + + /* + * The AND mode trigger can only have one channel (max) enabled + * for edge detection. + */ + if (trig == 0) { + int ret = 0; + unsigned int src; + + src = pt & 0xff; + if (src) + ret |= comedi_check_trigger_is_unique(src); + + src = (pt >> 8) & 0xff; + if (src) + ret |= comedi_check_trigger_is_unique(src); + + if (ret) { + dev_dbg(dev->class_dev, + "invalid AND trigger configuration\n"); + return ret; + } + } + + /* save the trigger configuration */ + devpriv->pm[trig] = pm; + devpriv->pt[trig] = pt; + devpriv->pp[trig] = pp; + + return insn->n; +} + +static int apci1500_di_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_DIGITAL_TRIG: + return apci1500_di_cfg_trig(dev, s, insn, data); + default: + return -EINVAL; + } +} + +static int apci1500_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1500_private *devpriv = dev->private; + + data[1] = inw(devpriv->addon + APCI1500_DI_REG); + + return insn->n; +} + +static int apci1500_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1500_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + outw(s->state, devpriv->addon + APCI1500_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int apci1500_timer_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1500_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + + switch (data[0]) { + case INSN_CONFIG_ARM: + val = data[1] & s->maxdata; + z8536_write(dev, val & 0xff, Z8536_CT_RELOAD_LSB_REG(chan)); + z8536_write(dev, (val >> 8) & 0xff, + Z8536_CT_RELOAD_MSB_REG(chan)); + + apci1500_timer_enable(dev, chan, true); + z8536_write(dev, Z8536_CT_CMDSTAT_GCB, + Z8536_CT_CMDSTAT_REG(chan)); + break; + case INSN_CONFIG_DISARM: + apci1500_timer_enable(dev, chan, false); + break; + + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = 0; + val = z8536_read(dev, Z8536_CT_CMDSTAT_REG(chan)); + if (val & Z8536_CT_STAT_CIP) + data[1] |= COMEDI_COUNTER_COUNTING; + if (val & Z8536_CT_CMDSTAT_GCB) + data[1] |= COMEDI_COUNTER_ARMED; + if (val & Z8536_STAT_IP) { + data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; + apci1500_ack_irq(dev, Z8536_CT_CMDSTAT_REG(chan)); + } + data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | + COMEDI_COUNTER_TERMINAL_COUNT; + break; + + case INSN_CONFIG_SET_COUNTER_MODE: + /* Simulate the 8254 timer modes */ + switch (data[1]) { + case I8254_MODE0: + /* Interrupt on Terminal Count */ + val = Z8536_CT_MODE_ECE | + Z8536_CT_MODE_DCS_ONESHOT; + break; + case I8254_MODE1: + /* Hardware Retriggerable One-Shot */ + val = Z8536_CT_MODE_ETE | + Z8536_CT_MODE_DCS_ONESHOT; + break; + case I8254_MODE2: + /* Rate Generator */ + val = Z8536_CT_MODE_CSC | + Z8536_CT_MODE_DCS_PULSE; + break; + case I8254_MODE3: + /* Square Wave Mode */ + val = Z8536_CT_MODE_CSC | + Z8536_CT_MODE_DCS_SQRWAVE; + break; + case I8254_MODE4: + /* Software Triggered Strobe */ + val = Z8536_CT_MODE_REB | + Z8536_CT_MODE_DCS_PULSE; + break; + case I8254_MODE5: + /* Hardware Triggered Strobe (watchdog) */ + val = Z8536_CT_MODE_EOE | + Z8536_CT_MODE_ETE | + Z8536_CT_MODE_REB | + Z8536_CT_MODE_DCS_PULSE; + break; + default: + return -EINVAL; + } + apci1500_timer_enable(dev, chan, false); + z8536_write(dev, val, Z8536_CT_MODE_REG(chan)); + break; + + case INSN_CONFIG_SET_CLOCK_SRC: + if (data[1] > 2) + return -EINVAL; + devpriv->clk_src = data[1]; + if (devpriv->clk_src == 2) + devpriv->clk_src = 3; + outw(devpriv->clk_src, devpriv->addon + APCI1500_CLK_SEL_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + switch (devpriv->clk_src) { + case 0: + data[1] = 0; /* 111.86 kHz / 2 */ + data[2] = 17879; /* 17879 ns (approx) */ + break; + case 1: + data[1] = 1; /* 3.49 kHz / 2 */ + data[2] = 573066; /* 573066 ns (approx) */ + break; + case 3: + data[1] = 2; /* 1.747 kHz / 2 */ + data[2] = 1164822; /* 1164822 ns (approx) */ + break; + default: + return -EINVAL; + } + break; + + case INSN_CONFIG_SET_GATE_SRC: + if (chan == 0) + return -EINVAL; + + val = z8536_read(dev, Z8536_CT_MODE_REG(chan)); + val &= Z8536_CT_MODE_EGE; + if (data[1] == 1) + val |= Z8536_CT_MODE_EGE; + else if (data[1] > 1) + return -EINVAL; + z8536_write(dev, val, Z8536_CT_MODE_REG(chan)); + break; + case INSN_CONFIG_GET_GATE_SRC: + if (chan == 0) + return -EINVAL; + break; + + default: + return -EINVAL; + } + return insn->n; +} + +static int apci1500_timer_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int cmd; + + cmd = z8536_read(dev, Z8536_CT_CMDSTAT_REG(chan)); + cmd &= Z8536_CT_CMDSTAT_GCB; /* preserve gate */ + cmd |= Z8536_CT_CMD_TCB; /* set trigger */ + + /* software trigger a timer, it only makes sense to do one write */ + if (insn->n) + z8536_write(dev, cmd, Z8536_CT_CMDSTAT_REG(chan)); + + return insn->n; +} + +static int apci1500_timer_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int cmd; + unsigned int val; + int i; + + cmd = z8536_read(dev, Z8536_CT_CMDSTAT_REG(chan)); + cmd &= Z8536_CT_CMDSTAT_GCB; /* preserve gate */ + cmd |= Z8536_CT_CMD_RCC; /* set RCC */ + + for (i = 0; i < insn->n; i++) { + z8536_write(dev, cmd, Z8536_CT_CMDSTAT_REG(chan)); + + val = z8536_read(dev, Z8536_CT_VAL_MSB_REG(chan)) << 8; + val |= z8536_read(dev, Z8536_CT_VAL_LSB_REG(chan)); + + data[i] = val; + } + + return insn->n; +} + +static int apci1500_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct apci1500_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->iobase = pci_resource_start(pcidev, 1); + devpriv->amcc = pci_resource_start(pcidev, 0); + devpriv->addon = pci_resource_start(pcidev, 2); + + z8536_reset(dev); + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, apci1500_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci1500_di_insn_bits; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 1; + s->insn_config = apci1500_di_insn_config; + s->do_cmdtest = apci1500_di_cmdtest; + s->do_cmd = apci1500_di_cmd; + s->cancel = apci1500_di_cancel; + } + + /* Digital Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci1500_do_insn_bits; + + /* reset all the digital outputs */ + outw(0x0, devpriv->addon + APCI1500_DO_REG); + + /* Counter/Timer(Watchdog) subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 3; + s->maxdata = 0xffff; + s->range_table = &range_unknown; + s->insn_config = apci1500_timer_insn_config; + s->insn_write = apci1500_timer_insn_write; + s->insn_read = apci1500_timer_insn_read; + + /* Enable the PCI interrupt */ + if (dev->irq) { + outl(0x2000 | INTCSR_INBOX_FULL_INT, + devpriv->amcc + AMCC_OP_REG_INTCSR); + inl(devpriv->amcc + AMCC_OP_REG_IMB1); + inl(devpriv->amcc + AMCC_OP_REG_INTCSR); + outl(INTCSR_INBOX_INTR_STATUS | 0x2000 | INTCSR_INBOX_FULL_INT, + devpriv->amcc + AMCC_OP_REG_INTCSR); + } + + return 0; +} + +static void apci1500_detach(struct comedi_device *dev) +{ + struct apci1500_private *devpriv = dev->private; + + if (devpriv->amcc) + outl(0x0, devpriv->amcc + AMCC_OP_REG_INTCSR); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci1500_driver = { + .driver_name = "addi_apci_1500", + .module = THIS_MODULE, + .auto_attach = apci1500_auto_attach, + .detach = apci1500_detach, +}; + +static int apci1500_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci1500_driver, id->driver_data); +} + +static const struct pci_device_id apci1500_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMCC, 0x80fc) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci1500_pci_table); + +static struct pci_driver apci1500_pci_driver = { + .name = "addi_apci_1500", + .id_table = apci1500_pci_table, + .probe = apci1500_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci1500_driver, apci1500_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("ADDI-DATA APCI-1500, 16 channel DI / 16 channel DO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_1516.c b/drivers/comedi/drivers/addi_apci_1516.c new file mode 100644 index 000000000..3c48b72da --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_1516.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +#include "addi_watchdog.h" + +/* + * PCI bar 1 I/O Register map - Digital input/output + */ +#define APCI1516_DI_REG 0x00 +#define APCI1516_DO_REG 0x04 + +/* + * PCI bar 2 I/O Register map - Watchdog (APCI-1516 and APCI-2016) + */ +#define APCI1516_WDOG_REG 0x00 + +enum apci1516_boardid { + BOARD_APCI1016, + BOARD_APCI1516, + BOARD_APCI2016, +}; + +struct apci1516_boardinfo { + const char *name; + int di_nchan; + int do_nchan; + int has_wdog; +}; + +static const struct apci1516_boardinfo apci1516_boardtypes[] = { + [BOARD_APCI1016] = { + .name = "apci1016", + .di_nchan = 16, + }, + [BOARD_APCI1516] = { + .name = "apci1516", + .di_nchan = 8, + .do_nchan = 8, + .has_wdog = 1, + }, + [BOARD_APCI2016] = { + .name = "apci2016", + .do_nchan = 16, + .has_wdog = 1, + }, +}; + +struct apci1516_private { + unsigned long wdog_iobase; +}; + +static int apci1516_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inw(dev->iobase + APCI1516_DI_REG); + + return insn->n; +} + +static int apci1516_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + s->state = inw(dev->iobase + APCI1516_DO_REG); + + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + APCI1516_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int apci1516_reset(struct comedi_device *dev) +{ + const struct apci1516_boardinfo *board = dev->board_ptr; + struct apci1516_private *devpriv = dev->private; + + if (!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 *board = NULL; + struct apci1516_private *devpriv; + struct comedi_subdevice *s; + int ret; + + if (context < ARRAY_SIZE(apci1516_boardtypes)) + board = &apci1516_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, 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 (board->di_nchan) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 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 (board->do_nchan) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 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 (board->has_wdog) { + ret = addi_watchdog_init(s, devpriv->wdog_iobase); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + apci1516_reset(dev); + return 0; +} + +static void apci1516_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci1516_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci1516_driver = { + .driver_name = "addi_apci_1516", + .module = THIS_MODULE, + .auto_attach = apci1516_auto_attach, + .detach = apci1516_detach, +}; + +static int apci1516_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci1516_driver, id->driver_data); +} + +static const struct pci_device_id apci1516_pci_table[] = { + { PCI_VDEVICE(ADDIDATA, 0x1000), BOARD_APCI1016 }, + { PCI_VDEVICE(ADDIDATA, 0x1001), BOARD_APCI1516 }, + { PCI_VDEVICE(ADDIDATA, 0x1002), BOARD_APCI2016 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci1516_pci_table); + +static struct pci_driver apci1516_pci_driver = { + .name = "addi_apci_1516", + .id_table = apci1516_pci_table, + .probe = apci1516_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci1516_driver, apci1516_pci_driver); + +MODULE_DESCRIPTION("ADDI-DATA APCI-1016/1516/2016, 16 channel DIO boards"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_1564.c b/drivers/comedi/drivers/addi_apci_1564.c new file mode 100644 index 000000000..0cd40948b --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_1564.c @@ -0,0 +1,820 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * addi_apci_1564.c + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + */ + +/* + * Driver: addi_apci_1564 + * Description: ADDI-DATA APCI-1564 Digital I/O board + * Devices: [ADDI-DATA] APCI-1564 (addi_apci_1564) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Thu, 02 Jun 2016 13:12:46 -0700 + * Status: untested + * + * Configuration Options: not applicable, uses comedi PCI auto config + * + * This board has the following features: + * - 32 optically isolated digital inputs (24V), 16 of which can + * generate change-of-state (COS) interrupts (channels 4 to 19) + * - 32 optically isolated digital outputs (10V to 36V) + * - 1 8-bit watchdog for resetting the outputs + * - 1 12-bit timer + * - 3 32-bit counters + * - 2 diagnostic inputs + * + * The COS, timer, and counter subdevices all use the dev->read_subdev to + * return the interrupt status. The sample data is updated and returned when + * any of these subdevices generate an interrupt. The sample data format is: + * + * Bit Description + * ----- ------------------------------------------ + * 31 COS interrupt + * 30 timer interrupt + * 29 counter 2 interrupt + * 28 counter 1 interrupt + * 27 counter 0 interrupt + * 26:20 not used + * 19:4 COS digital input state (channels 19 to 4) + * 3:0 not used + * + * The COS interrupts must be configured using an INSN_CONFIG_DIGITAL_TRIG + * instruction before they can be enabled by an async command. The COS + * interrupts will stay active until canceled. + * + * The timer subdevice does not use an async command. All control is handled + * by the (*insn_config). + * + * FIXME: The format of the ADDI_TCW_TIMEBASE_REG is not descibed in the + * datasheet I have. The INSN_CONFIG_SET_CLOCK_SRC currently just writes + * the raw data[1] to this register along with the raw data[2] value to the + * ADDI_TCW_RELOAD_REG. If anyone tests this and can determine the actual + * timebase/reload operation please let me know. + * + * The counter subdevice also does not use an async command. All control is + * handled by the (*insn_config). + * + * FIXME: The operation of the counters is not really described in the + * datasheet I have. The (*insn_config) needs more work. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "addi_tcw.h" +#include "addi_watchdog.h" + +/* + * PCI BAR 0 + * + * PLD Revision 1.0 I/O Mapping + * 0x00 93C76 EEPROM + * 0x04 - 0x18 Timer 12-Bit + * + * PLD Revision 2.x I/O Mapping + * 0x00 93C76 EEPROM + * 0x04 - 0x14 Digital Input + * 0x18 - 0x25 Digital Output + * 0x28 - 0x44 Watchdog 8-Bit + * 0x48 - 0x64 Timer 12-Bit + */ +#define APCI1564_EEPROM_REG 0x00 +#define APCI1564_EEPROM_VCC_STATUS BIT(8) +#define APCI1564_EEPROM_TO_REV(x) (((x) >> 4) & 0xf) +#define APCI1564_EEPROM_DI BIT(3) +#define APCI1564_EEPROM_DO BIT(2) +#define APCI1564_EEPROM_CS BIT(1) +#define APCI1564_EEPROM_CLK BIT(0) +#define APCI1564_REV1_TIMER_IOBASE 0x04 +#define APCI1564_REV2_MAIN_IOBASE 0x04 +#define APCI1564_REV2_TIMER_IOBASE 0x48 + +/* + * PCI BAR 1 + * + * PLD Revision 1.0 I/O Mapping + * 0x00 - 0x10 Digital Input + * 0x14 - 0x20 Digital Output + * 0x24 - 0x3c Watchdog 8-Bit + * + * PLD Revision 2.x I/O Mapping + * 0x00 Counter_0 + * 0x20 Counter_1 + * 0x30 Counter_3 + */ +#define APCI1564_REV1_MAIN_IOBASE 0x00 + +/* + * dev->iobase Register Map + * PLD Revision 1.0 - PCI BAR 1 + 0x00 + * PLD Revision 2.x - PCI BAR 0 + 0x04 + */ +#define APCI1564_DI_REG 0x00 +#define APCI1564_DI_INT_MODE1_REG 0x04 +#define APCI1564_DI_INT_MODE2_REG 0x08 +#define APCI1564_DI_INT_MODE_MASK 0x000ffff0 /* chans [19:4] */ +#define APCI1564_DI_INT_STATUS_REG 0x0c +#define APCI1564_DI_IRQ_REG 0x10 +#define APCI1564_DI_IRQ_ENA BIT(2) +#define APCI1564_DI_IRQ_MODE BIT(1) /* 1=AND, 0=OR */ +#define APCI1564_DO_REG 0x14 +#define APCI1564_DO_INT_CTRL_REG 0x18 +#define APCI1564_DO_INT_CTRL_CC_INT_ENA BIT(1) +#define APCI1564_DO_INT_CTRL_VCC_INT_ENA BIT(0) +#define APCI1564_DO_INT_STATUS_REG 0x1c +#define APCI1564_DO_INT_STATUS_CC BIT(1) +#define APCI1564_DO_INT_STATUS_VCC BIT(0) +#define APCI1564_DO_IRQ_REG 0x20 +#define APCI1564_DO_IRQ_INTR BIT(0) +#define APCI1564_WDOG_IOBASE 0x24 + +/* + * devpriv->timer Register Map (see addi_tcw.h for register/bit defines) + * PLD Revision 1.0 - PCI BAR 0 + 0x04 + * PLD Revision 2.x - PCI BAR 0 + 0x48 + */ + +/* + * devpriv->counters Register Map (see addi_tcw.h for register/bit defines) + * PLD Revision 2.x - PCI BAR 1 + 0x00 + */ +#define APCI1564_COUNTER(x) ((x) * 0x20) + +/* + * The dev->read_subdev is used to return the interrupt events along with + * the state of the interrupt capable inputs. + */ +#define APCI1564_EVENT_COS BIT(31) +#define APCI1564_EVENT_TIMER BIT(30) +#define APCI1564_EVENT_COUNTER(x) BIT(27 + (x)) /* counter 0-2 */ +#define APCI1564_EVENT_MASK 0xfff0000f /* all but [19:4] */ + +struct apci1564_private { + unsigned long eeprom; /* base address of EEPROM register */ + unsigned long timer; /* base address of 12-bit timer */ + unsigned long counters; /* base address of 32-bit counters */ + unsigned int mode1; /* rising-edge/high level channels */ + unsigned int mode2; /* falling-edge/low level channels */ + unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */ +}; + +static int apci1564_reset(struct comedi_device *dev) +{ + struct apci1564_private *devpriv = dev->private; + + /* Disable the input interrupts and reset status register */ + outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG); + inl(dev->iobase + APCI1564_DI_INT_STATUS_REG); + outl(0x0, dev->iobase + APCI1564_DI_INT_MODE1_REG); + outl(0x0, dev->iobase + APCI1564_DI_INT_MODE2_REG); + + /* Reset the output channels and disable interrupts */ + outl(0x0, dev->iobase + APCI1564_DO_REG); + outl(0x0, dev->iobase + APCI1564_DO_INT_CTRL_REG); + + /* Reset the watchdog registers */ + addi_watchdog_reset(dev->iobase + APCI1564_WDOG_IOBASE); + + /* Reset the timer registers */ + outl(0x0, devpriv->timer + ADDI_TCW_CTRL_REG); + outl(0x0, devpriv->timer + ADDI_TCW_RELOAD_REG); + + if (devpriv->counters) { + unsigned long iobase = devpriv->counters + ADDI_TCW_CTRL_REG; + + /* Reset the counter registers */ + outl(0x0, iobase + APCI1564_COUNTER(0)); + outl(0x0, iobase + APCI1564_COUNTER(1)); + outl(0x0, iobase + APCI1564_COUNTER(2)); + } + + return 0; +} + +static irqreturn_t apci1564_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct apci1564_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + unsigned int ctrl; + unsigned int chan; + + s->state &= ~APCI1564_EVENT_MASK; + + status = inl(dev->iobase + APCI1564_DI_IRQ_REG); + if (status & APCI1564_DI_IRQ_ENA) { + /* get the COS interrupt state and set the event flag */ + s->state = inl(dev->iobase + APCI1564_DI_INT_STATUS_REG); + s->state &= APCI1564_DI_INT_MODE_MASK; + s->state |= APCI1564_EVENT_COS; + + /* clear the interrupt */ + outl(status & ~APCI1564_DI_IRQ_ENA, + dev->iobase + APCI1564_DI_IRQ_REG); + outl(status, dev->iobase + APCI1564_DI_IRQ_REG); + } + + status = inl(devpriv->timer + ADDI_TCW_IRQ_REG); + if (status & ADDI_TCW_IRQ) { + s->state |= APCI1564_EVENT_TIMER; + + /* clear the interrupt */ + ctrl = inl(devpriv->timer + ADDI_TCW_CTRL_REG); + outl(0x0, devpriv->timer + ADDI_TCW_CTRL_REG); + outl(ctrl, devpriv->timer + ADDI_TCW_CTRL_REG); + } + + if (devpriv->counters) { + for (chan = 0; chan < 3; chan++) { + unsigned long iobase; + + iobase = devpriv->counters + APCI1564_COUNTER(chan); + + status = inl(iobase + ADDI_TCW_IRQ_REG); + if (status & ADDI_TCW_IRQ) { + s->state |= APCI1564_EVENT_COUNTER(chan); + + /* clear the interrupt */ + ctrl = inl(iobase + ADDI_TCW_CTRL_REG); + outl(0x0, iobase + ADDI_TCW_CTRL_REG); + outl(ctrl, iobase + ADDI_TCW_CTRL_REG); + } + } + } + + if (s->state & APCI1564_EVENT_MASK) { + comedi_buf_write_samples(s, &s->state, 1); + comedi_handle_events(dev, s); + } + + return IRQ_HANDLED; +} + +static int apci1564_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inl(dev->iobase + APCI1564_DI_REG); + + return insn->n; +} + +static int apci1564_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + s->state = inl(dev->iobase + APCI1564_DO_REG); + + if (comedi_dio_update_state(s, data)) + outl(s->state, dev->iobase + APCI1564_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int apci1564_diag_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inl(dev->iobase + APCI1564_DO_INT_STATUS_REG) & 3; + + return insn->n; +} + +/* + * Change-Of-State (COS) interrupt configuration + * + * Channels 4 to 19 are interruptible. These channels can be configured + * to generate interrupts based on AND/OR logic for the desired channels. + * + * OR logic + * - reacts to rising or falling edges + * - interrupt is generated when any enabled channel + * meet the desired interrupt condition + * + * AND logic + * - reacts to changes in level of the selected inputs + * - interrupt is generated when all enabled channels + * meet the desired interrupt condition + * - after an interrupt, a change in level must occur on + * the selected inputs to release the IRQ logic + * + * The COS interrupt must be configured before it can be enabled. + * + * data[0] : INSN_CONFIG_DIGITAL_TRIG + * data[1] : trigger number (= 0) + * data[2] : configuration operation: + * COMEDI_DIGITAL_TRIG_DISABLE = no interrupts + * COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts + * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts + * data[3] : left-shift for data[4] and data[5] + * data[4] : rising-edge/high level channels + * data[5] : falling-edge/low level channels + */ +static int apci1564_cos_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + unsigned int shift, oldmask, himask, lomask; + + switch (data[0]) { + case INSN_CONFIG_DIGITAL_TRIG: + if (data[1] != 0) + return -EINVAL; + shift = data[3]; + if (shift < 32) { + oldmask = (1U << shift) - 1; + himask = data[4] << shift; + lomask = data[5] << shift; + } else { + oldmask = 0xffffffffu; + himask = 0; + lomask = 0; + } + switch (data[2]) { + case COMEDI_DIGITAL_TRIG_DISABLE: + devpriv->ctrl = 0; + devpriv->mode1 = 0; + devpriv->mode2 = 0; + outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG); + inl(dev->iobase + APCI1564_DI_INT_STATUS_REG); + outl(0x0, dev->iobase + APCI1564_DI_INT_MODE1_REG); + outl(0x0, dev->iobase + APCI1564_DI_INT_MODE2_REG); + break; + case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: + if (devpriv->ctrl != APCI1564_DI_IRQ_ENA) { + /* switching to 'OR' mode */ + devpriv->ctrl = APCI1564_DI_IRQ_ENA; + /* wipe old channels */ + devpriv->mode1 = 0; + devpriv->mode2 = 0; + } else { + /* preserve unspecified channels */ + devpriv->mode1 &= oldmask; + devpriv->mode2 &= oldmask; + } + /* configure specified channels */ + devpriv->mode1 |= himask; + devpriv->mode2 |= lomask; + break; + case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS: + if (devpriv->ctrl != (APCI1564_DI_IRQ_ENA | + APCI1564_DI_IRQ_MODE)) { + /* switching to 'AND' mode */ + devpriv->ctrl = APCI1564_DI_IRQ_ENA | + APCI1564_DI_IRQ_MODE; + /* wipe old channels */ + devpriv->mode1 = 0; + devpriv->mode2 = 0; + } else { + /* preserve unspecified channels */ + devpriv->mode1 &= oldmask; + devpriv->mode2 &= oldmask; + } + /* configure specified channels */ + devpriv->mode1 |= himask; + devpriv->mode2 |= lomask; + break; + default: + return -EINVAL; + } + + /* ensure the mode bits are in-range for channels [19:4] */ + devpriv->mode1 &= APCI1564_DI_INT_MODE_MASK; + devpriv->mode2 &= APCI1564_DI_INT_MODE_MASK; + break; + default: + return -EINVAL; + } + return insn->n; +} + +static int apci1564_cos_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = s->state; + + return 0; +} + +static int apci1564_cos_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +/* + * Change-Of-State (COS) 'do_cmd' operation + * + * Enable the COS interrupt as configured by apci1564_cos_insn_config(). + */ +static int apci1564_cos_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci1564_private *devpriv = dev->private; + + if (!devpriv->ctrl && !(devpriv->mode1 || devpriv->mode2)) { + dev_warn(dev->class_dev, + "Interrupts disabled due to mode configuration!\n"); + return -EINVAL; + } + + outl(devpriv->mode1, dev->iobase + APCI1564_DI_INT_MODE1_REG); + outl(devpriv->mode2, dev->iobase + APCI1564_DI_INT_MODE2_REG); + outl(devpriv->ctrl, dev->iobase + APCI1564_DI_IRQ_REG); + + return 0; +} + +static int apci1564_cos_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + outl(0x0, dev->iobase + APCI1564_DI_IRQ_REG); + inl(dev->iobase + APCI1564_DI_INT_STATUS_REG); + outl(0x0, dev->iobase + APCI1564_DI_INT_MODE1_REG); + outl(0x0, dev->iobase + APCI1564_DI_INT_MODE2_REG); + + return 0; +} + +static int apci1564_timer_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + unsigned int val; + + switch (data[0]) { + case INSN_CONFIG_ARM: + if (data[1] > s->maxdata) + return -EINVAL; + outl(data[1], devpriv->timer + ADDI_TCW_RELOAD_REG); + outl(ADDI_TCW_CTRL_IRQ_ENA | ADDI_TCW_CTRL_TIMER_ENA, + devpriv->timer + ADDI_TCW_CTRL_REG); + break; + case INSN_CONFIG_DISARM: + outl(0x0, devpriv->timer + ADDI_TCW_CTRL_REG); + break; + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = 0; + val = inl(devpriv->timer + ADDI_TCW_CTRL_REG); + if (val & ADDI_TCW_CTRL_IRQ_ENA) + data[1] |= COMEDI_COUNTER_ARMED; + if (val & ADDI_TCW_CTRL_TIMER_ENA) + data[1] |= COMEDI_COUNTER_COUNTING; + val = inl(devpriv->timer + ADDI_TCW_STATUS_REG); + if (val & ADDI_TCW_STATUS_OVERFLOW) + data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; + data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | + COMEDI_COUNTER_TERMINAL_COUNT; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + if (data[2] > s->maxdata) + return -EINVAL; + outl(data[1], devpriv->timer + ADDI_TCW_TIMEBASE_REG); + outl(data[2], devpriv->timer + ADDI_TCW_RELOAD_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + data[1] = inl(devpriv->timer + ADDI_TCW_TIMEBASE_REG); + data[2] = inl(devpriv->timer + ADDI_TCW_RELOAD_REG); + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int apci1564_timer_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + + /* just write the last to the reload register */ + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + outl(val, devpriv->timer + ADDI_TCW_RELOAD_REG); + } + + return insn->n; +} + +static int apci1564_timer_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + int i; + + /* return the actual value of the timer */ + for (i = 0; i < insn->n; i++) + data[i] = inl(devpriv->timer + ADDI_TCW_VAL_REG); + + return insn->n; +} + +static int apci1564_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan); + unsigned int val; + + switch (data[0]) { + case INSN_CONFIG_ARM: + val = inl(iobase + ADDI_TCW_CTRL_REG); + val |= ADDI_TCW_CTRL_IRQ_ENA | ADDI_TCW_CTRL_CNTR_ENA; + outl(data[1], iobase + ADDI_TCW_RELOAD_REG); + outl(val, iobase + ADDI_TCW_CTRL_REG); + break; + case INSN_CONFIG_DISARM: + val = inl(iobase + ADDI_TCW_CTRL_REG); + val &= ~(ADDI_TCW_CTRL_IRQ_ENA | ADDI_TCW_CTRL_CNTR_ENA); + outl(val, iobase + ADDI_TCW_CTRL_REG); + break; + case INSN_CONFIG_SET_COUNTER_MODE: + /* + * FIXME: The counter operation is not described in the + * datasheet. For now just write the raw data[1] value to + * the control register. + */ + outl(data[1], iobase + ADDI_TCW_CTRL_REG); + break; + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = 0; + val = inl(iobase + ADDI_TCW_CTRL_REG); + if (val & ADDI_TCW_CTRL_IRQ_ENA) + data[1] |= COMEDI_COUNTER_ARMED; + if (val & ADDI_TCW_CTRL_CNTR_ENA) + data[1] |= COMEDI_COUNTER_COUNTING; + val = inl(iobase + ADDI_TCW_STATUS_REG); + if (val & ADDI_TCW_STATUS_OVERFLOW) + data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; + data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | + COMEDI_COUNTER_TERMINAL_COUNT; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int apci1564_counter_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan); + + /* just write the last to the reload register */ + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + outl(val, iobase + ADDI_TCW_RELOAD_REG); + } + + return insn->n; +} + +static int apci1564_counter_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci1564_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan); + int i; + + /* return the actual value of the counter */ + for (i = 0; i < insn->n; i++) + data[i] = inl(iobase + ADDI_TCW_VAL_REG); + + return insn->n; +} + +static int apci1564_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct apci1564_private *devpriv; + struct comedi_subdevice *s; + unsigned int val; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + /* read the EEPROM register and check the I/O map revision */ + devpriv->eeprom = pci_resource_start(pcidev, 0); + val = inl(devpriv->eeprom + APCI1564_EEPROM_REG); + if (APCI1564_EEPROM_TO_REV(val) == 0) { + /* PLD Revision 1.0 I/O Mapping */ + dev->iobase = pci_resource_start(pcidev, 1) + + APCI1564_REV1_MAIN_IOBASE; + devpriv->timer = devpriv->eeprom + APCI1564_REV1_TIMER_IOBASE; + } else { + /* PLD Revision 2.x I/O Mapping */ + dev->iobase = devpriv->eeprom + APCI1564_REV2_MAIN_IOBASE; + devpriv->timer = devpriv->eeprom + APCI1564_REV2_TIMER_IOBASE; + devpriv->counters = pci_resource_start(pcidev, 1); + } + + apci1564_reset(dev); + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, apci1564_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 7); + if (ret) + return ret; + + /* Allocate and Initialise DI Subdevice Structures */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci1564_di_insn_bits; + + /* Allocate and Initialise DO Subdevice Structures */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci1564_do_insn_bits; + + /* Change-Of-State (COS) interrupt subdevice */ + s = &dev->subdevices[2]; + if (dev->irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_LSAMPL; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->len_chanlist = 1; + s->insn_config = apci1564_cos_insn_config; + s->insn_bits = apci1564_cos_insn_bits; + s->do_cmdtest = apci1564_cos_cmdtest; + s->do_cmd = apci1564_cos_cmd; + s->cancel = apci1564_cos_cancel; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Timer subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 1; + s->maxdata = 0x0fff; + s->range_table = &range_digital; + s->insn_config = apci1564_timer_insn_config; + s->insn_write = apci1564_timer_insn_write; + s->insn_read = apci1564_timer_insn_read; + + /* Counter subdevice */ + s = &dev->subdevices[4]; + if (devpriv->counters) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL; + s->n_chan = 3; + s->maxdata = 0xffffffff; + s->range_table = &range_digital; + s->insn_config = apci1564_counter_insn_config; + s->insn_write = apci1564_counter_insn_write; + s->insn_read = apci1564_counter_insn_read; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Initialize the watchdog subdevice */ + s = &dev->subdevices[5]; + ret = addi_watchdog_init(s, dev->iobase + APCI1564_WDOG_IOBASE); + if (ret) + return ret; + + /* Initialize the diagnostic status subdevice */ + s = &dev->subdevices[6]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 2; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci1564_diag_insn_bits; + + return 0; +} + +static void apci1564_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci1564_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci1564_driver = { + .driver_name = "addi_apci_1564", + .module = THIS_MODULE, + .auto_attach = apci1564_auto_attach, + .detach = apci1564_detach, +}; + +static int apci1564_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci1564_driver, id->driver_data); +} + +static const struct pci_device_id apci1564_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1006) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci1564_pci_table); + +static struct pci_driver apci1564_pci_driver = { + .name = "addi_apci_1564", + .id_table = apci1564_pci_table, + .probe = apci1564_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci1564_driver, apci1564_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("ADDI-DATA APCI-1564, 32 channel DI / 32 channel DO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_16xx.c b/drivers/comedi/drivers/addi_apci_16xx.c new file mode 100644 index 000000000..ec2c321d2 --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_16xx.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +/* + * Register I/O map + */ +#define APCI16XX_IN_REG(x) (((x) * 4) + 0x08) +#define APCI16XX_OUT_REG(x) (((x) * 4) + 0x14) +#define APCI16XX_DIR_REG(x) (((x) * 4) + 0x20) + +enum apci16xx_boardid { + BOARD_APCI1648, + BOARD_APCI1696, +}; + +struct apci16xx_boardinfo { + const char *name; + int n_chan; +}; + +static const struct apci16xx_boardinfo apci16xx_boardtypes[] = { + [BOARD_APCI1648] = { + .name = "apci1648", + .n_chan = 48, /* 2 subdevices */ + }, + [BOARD_APCI1696] = { + .name = "apci1696", + .n_chan = 96, /* 3 subdevices */ + }, +}; + +static int apci16xx_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x000000ff; + else if (chan < 16) + mask = 0x0000ff00; + else if (chan < 24) + mask = 0x00ff0000; + else + mask = 0xff000000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + outl(s->io_bits, dev->iobase + APCI16XX_DIR_REG(s->index)); + + return insn->n; +} + +static int apci16xx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outl(s->state, dev->iobase + APCI16XX_OUT_REG(s->index)); + + data[1] = inl(dev->iobase + APCI16XX_IN_REG(s->index)); + + return insn->n; +} + +static int apci16xx_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct apci16xx_boardinfo *board = NULL; + struct comedi_subdevice *s; + unsigned int n_subdevs; + unsigned int last; + int i; + int ret; + + if (context < ARRAY_SIZE(apci16xx_boardtypes)) + board = &apci16xx_boardtypes[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->iobase = pci_resource_start(pcidev, 0); + + /* + * Work out the number of subdevices needed to support all the + * digital i/o channels on the board. Each subdevice supports + * up to 32 channels. + */ + n_subdevs = board->n_chan / 32; + if ((n_subdevs * 32) < board->n_chan) { + last = board->n_chan - (n_subdevs * 32); + n_subdevs++; + } else { + last = 0; + } + + ret = comedi_alloc_subdevices(dev, n_subdevs); + if (ret) + return ret; + + /* Initialize the TTL digital i/o subdevices */ + for (i = 0; i < n_subdevs; i++) { + s = &dev->subdevices[i]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = ((i * 32) < board->n_chan) ? 32 : last; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = apci16xx_insn_config; + s->insn_bits = apci16xx_dio_insn_bits; + + /* Default all channels to inputs */ + s->io_bits = 0; + outl(s->io_bits, dev->iobase + APCI16XX_DIR_REG(i)); + } + + return 0; +} + +static struct comedi_driver apci16xx_driver = { + .driver_name = "addi_apci_16xx", + .module = THIS_MODULE, + .auto_attach = apci16xx_auto_attach, + .detach = comedi_pci_detach, +}; + +static int apci16xx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci16xx_driver, id->driver_data); +} + +static const struct pci_device_id apci16xx_pci_table[] = { + { PCI_VDEVICE(ADDIDATA, 0x1009), BOARD_APCI1648 }, + { PCI_VDEVICE(ADDIDATA, 0x100a), BOARD_APCI1696 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci16xx_pci_table); + +static struct pci_driver apci16xx_pci_driver = { + .name = "addi_apci_16xx", + .id_table = apci16xx_pci_table, + .probe = apci16xx_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci16xx_driver, apci16xx_pci_driver); + +MODULE_DESCRIPTION("ADDI-DATA APCI-1648/1696, TTL I/O boards"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_2032.c b/drivers/comedi/drivers/addi_apci_2032.c new file mode 100644 index 000000000..e048dfc3e --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_2032.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/comedi/comedi_pci.h> + +#include "addi_watchdog.h" + +/* + * PCI bar 1 I/O Register map + */ +#define APCI2032_DO_REG 0x00 +#define APCI2032_INT_CTRL_REG 0x04 +#define APCI2032_INT_CTRL_VCC_ENA BIT(0) +#define APCI2032_INT_CTRL_CC_ENA BIT(1) +#define APCI2032_INT_STATUS_REG 0x08 +#define APCI2032_INT_STATUS_VCC BIT(0) +#define APCI2032_INT_STATUS_CC BIT(1) +#define APCI2032_STATUS_REG 0x0c +#define APCI2032_STATUS_IRQ BIT(0) +#define APCI2032_WDOG_REG 0x10 + +struct apci2032_int_private { + spinlock_t spinlock; /* protects the following members */ + bool active; /* an async command is running */ + unsigned char enabled_isns; /* mask of enabled interrupt channels */ +}; + +static int apci2032_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + s->state = inl(dev->iobase + APCI2032_DO_REG); + + if (comedi_dio_update_state(s, data)) + outl(s->state, dev->iobase + APCI2032_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int apci2032_int_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3; + return insn->n; +} + +static void apci2032_int_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci2032_int_private *subpriv = s->private; + + subpriv->active = false; + subpriv->enabled_isns = 0; + outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG); +} + +static int apci2032_int_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int apci2032_int_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + struct apci2032_int_private *subpriv = s->private; + unsigned char enabled_isns; + unsigned int n; + unsigned long flags; + + enabled_isns = 0; + for (n = 0; n < cmd->chanlist_len; n++) + enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]); + + spin_lock_irqsave(&subpriv->spinlock, flags); + + subpriv->enabled_isns = enabled_isns; + subpriv->active = true; + outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +static int apci2032_int_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci2032_int_private *subpriv = s->private; + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + if (subpriv->active) + apci2032_int_stop(dev, s); + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +static irqreturn_t apci2032_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + struct apci2032_int_private *subpriv; + unsigned int val; + + if (!dev->attached) + return IRQ_NONE; + + /* Check if VCC OR CC interrupt has occurred */ + val = inl(dev->iobase + APCI2032_STATUS_REG) & APCI2032_STATUS_IRQ; + if (!val) + return IRQ_NONE; + + subpriv = s->private; + spin_lock(&subpriv->spinlock); + + val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3; + /* Disable triggered interrupt sources. */ + outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG); + /* + * Note: We don't reenable the triggered interrupt sources because they + * are level-sensitive, hardware error status interrupt sources and + * they'd keep triggering interrupts repeatedly. + */ + + if (subpriv->active && (val & subpriv->enabled_isns) != 0) { + unsigned short bits = 0; + int i; + + /* Bits in scan data correspond to indices in channel list. */ + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (val & (1 << chan)) + bits |= (1 << i); + } + + comedi_buf_write_samples(s, &bits, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + } + + spin_unlock(&subpriv->spinlock); + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int apci2032_reset(struct comedi_device *dev) +{ + outl(0x0, dev->iobase + APCI2032_DO_REG); + outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG); + + addi_watchdog_reset(dev->iobase + APCI2032_WDOG_REG); + + return 0; +} + +static int apci2032_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 1); + apci2032_reset(dev); + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, apci2032_interrupt, + IRQF_SHARED, dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Initialize the digital output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci2032_do_insn_bits; + + /* Initialize the watchdog subdevice */ + s = &dev->subdevices[1]; + ret = addi_watchdog_init(s, dev->iobase + APCI2032_WDOG_REG); + if (ret) + return ret; + + /* Initialize the interrupt subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 2; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci2032_int_insn_bits; + if (dev->irq) { + struct apci2032_int_private *subpriv; + + dev->read_subdev = s; + subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL); + if (!subpriv) + return -ENOMEM; + spin_lock_init(&subpriv->spinlock); + s->private = subpriv; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_PACKED; + s->len_chanlist = 2; + s->do_cmdtest = apci2032_int_cmdtest; + s->do_cmd = apci2032_int_cmd; + s->cancel = apci2032_int_cancel; + } + + return 0; +} + +static void apci2032_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci2032_reset(dev); + comedi_pci_detach(dev); + if (dev->read_subdev) + kfree(dev->read_subdev->private); +} + +static struct comedi_driver apci2032_driver = { + .driver_name = "addi_apci_2032", + .module = THIS_MODULE, + .auto_attach = apci2032_auto_attach, + .detach = apci2032_detach, +}; + +static int apci2032_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci2032_driver, id->driver_data); +} + +static const struct pci_device_id apci2032_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci2032_pci_table); + +static struct pci_driver apci2032_pci_driver = { + .name = "addi_apci_2032", + .id_table = apci2032_pci_table, + .probe = apci2032_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("ADDI-DATA APCI-2032, 32 channel DO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_2200.c b/drivers/comedi/drivers/addi_apci_2200.c new file mode 100644 index 000000000..00378c9dd --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_2200.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +#include "addi_watchdog.h" + +/* + * I/O Register Map + */ +#define APCI2200_DI_REG 0x00 +#define APCI2200_DO_REG 0x04 +#define APCI2200_WDOG_REG 0x08 + +static int apci2200_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inw(dev->iobase + APCI2200_DI_REG); + + return insn->n; +} + +static int apci2200_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + s->state = inw(dev->iobase + APCI2200_DO_REG); + + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + APCI2200_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int apci2200_reset(struct comedi_device *dev) +{ + outw(0x0, dev->iobase + APCI2200_DO_REG); + + addi_watchdog_reset(dev->iobase + APCI2200_WDOG_REG); + + return 0; +} + +static int apci2200_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->iobase = pci_resource_start(pcidev, 1); + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Initialize the digital input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci2200_di_insn_bits; + + /* Initialize the digital output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci2200_do_insn_bits; + + /* Initialize the watchdog subdevice */ + s = &dev->subdevices[2]; + ret = addi_watchdog_init(s, dev->iobase + APCI2200_WDOG_REG); + if (ret) + return ret; + + apci2200_reset(dev); + return 0; +} + +static void apci2200_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci2200_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci2200_driver = { + .driver_name = "addi_apci_2200", + .module = THIS_MODULE, + .auto_attach = apci2200_auto_attach, + .detach = apci2200_detach, +}; + +static int apci2200_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci2200_driver, id->driver_data); +} + +static const struct pci_device_id apci2200_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1005) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci2200_pci_table); + +static struct pci_driver apci2200_pci_driver = { + .name = "addi_apci_2200", + .id_table = apci2200_pci_table, + .probe = apci2200_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci2200_driver, apci2200_pci_driver); + +MODULE_DESCRIPTION("ADDI-DATA APCI-2200 Relay board, optically isolated"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_3120.c b/drivers/comedi/drivers/addi_apci_3120.c new file mode 100644 index 000000000..28a242e69 --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_3120.c @@ -0,0 +1,1117 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * addi_apci_3120.c + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "amcc_s5933.h" + +/* + * PCI BAR 0 register map (devpriv->amcc) + * see amcc_s5933.h for register and bit defines + */ +#define APCI3120_FIFO_ADVANCE_ON_BYTE_2 BIT(29) + +/* + * PCI BAR 1 register map (dev->iobase) + */ +#define APCI3120_AI_FIFO_REG 0x00 +#define APCI3120_CTRL_REG 0x00 +#define APCI3120_CTRL_EXT_TRIG BIT(15) +#define APCI3120_CTRL_GATE(x) BIT(12 + (x)) +#define APCI3120_CTRL_PR(x) (((x) & 0xf) << 8) +#define APCI3120_CTRL_PA(x) (((x) & 0xf) << 0) +#define APCI3120_AI_SOFTTRIG_REG 0x02 +#define APCI3120_STATUS_REG 0x02 +#define APCI3120_STATUS_EOC_INT BIT(15) +#define APCI3120_STATUS_AMCC_INT BIT(14) +#define APCI3120_STATUS_EOS_INT BIT(13) +#define APCI3120_STATUS_TIMER2_INT BIT(12) +#define APCI3120_STATUS_INT_MASK (0xf << 12) +#define APCI3120_STATUS_TO_DI_BITS(x) (((x) >> 8) & 0xf) +#define APCI3120_STATUS_TO_VERSION(x) (((x) >> 4) & 0xf) +#define APCI3120_STATUS_FIFO_FULL BIT(2) +#define APCI3120_STATUS_FIFO_EMPTY BIT(1) +#define APCI3120_STATUS_DA_READY BIT(0) +#define APCI3120_TIMER_REG 0x04 +#define APCI3120_CHANLIST_REG 0x06 +#define APCI3120_CHANLIST_INDEX(x) (((x) & 0xf) << 8) +#define APCI3120_CHANLIST_UNIPOLAR BIT(7) +#define APCI3120_CHANLIST_GAIN(x) (((x) & 0x3) << 4) +#define APCI3120_CHANLIST_MUX(x) (((x) & 0xf) << 0) +#define APCI3120_AO_REG(x) (0x08 + (((x) / 4) * 2)) +#define APCI3120_AO_MUX(x) (((x) & 0x3) << 14) +#define APCI3120_AO_DATA(x) ((x) << 0) +#define APCI3120_TIMER_MODE_REG 0x0c +#define APCI3120_TIMER_MODE(_t, _m) ((_m) << ((_t) * 2)) +#define APCI3120_TIMER_MODE0 0 /* I8254_MODE0 */ +#define APCI3120_TIMER_MODE2 1 /* I8254_MODE2 */ +#define APCI3120_TIMER_MODE4 2 /* I8254_MODE4 */ +#define APCI3120_TIMER_MODE5 3 /* I8254_MODE5 */ +#define APCI3120_TIMER_MODE_MASK(_t) (3 << ((_t) * 2)) +#define APCI3120_CTR0_REG 0x0d +#define APCI3120_CTR0_DO_BITS(x) ((x) << 4) +#define APCI3120_CTR0_TIMER_SEL(x) ((x) << 0) +#define APCI3120_MODE_REG 0x0e +#define APCI3120_MODE_TIMER2_CLK(x) (((x) & 0x3) << 6) +#define APCI3120_MODE_TIMER2_CLK_OSC APCI3120_MODE_TIMER2_CLK(0) +#define APCI3120_MODE_TIMER2_CLK_OUT1 APCI3120_MODE_TIMER2_CLK(1) +#define APCI3120_MODE_TIMER2_CLK_EOC APCI3120_MODE_TIMER2_CLK(2) +#define APCI3120_MODE_TIMER2_CLK_EOS APCI3120_MODE_TIMER2_CLK(3) +#define APCI3120_MODE_TIMER2_CLK_MASK APCI3120_MODE_TIMER2_CLK(3) +#define APCI3120_MODE_TIMER2_AS(x) (((x) & 0x3) << 4) +#define APCI3120_MODE_TIMER2_AS_TIMER APCI3120_MODE_TIMER2_AS(0) +#define APCI3120_MODE_TIMER2_AS_COUNTER APCI3120_MODE_TIMER2_AS(1) +#define APCI3120_MODE_TIMER2_AS_WDOG APCI3120_MODE_TIMER2_AS(2) +#define APCI3120_MODE_TIMER2_AS_MASK APCI3120_MODE_TIMER2_AS(3) +#define APCI3120_MODE_SCAN_ENA BIT(3) +#define APCI3120_MODE_TIMER2_IRQ_ENA BIT(2) +#define APCI3120_MODE_EOS_IRQ_ENA BIT(1) +#define APCI3120_MODE_EOC_IRQ_ENA BIT(0) + +/* + * PCI BAR 2 register map (devpriv->addon) + */ +#define APCI3120_ADDON_ADDR_REG 0x00 +#define APCI3120_ADDON_DATA_REG 0x02 +#define APCI3120_ADDON_CTRL_REG 0x04 +#define APCI3120_ADDON_CTRL_AMWEN_ENA BIT(1) +#define APCI3120_ADDON_CTRL_A2P_FIFO_ENA BIT(0) + +/* + * Board revisions + */ +#define APCI3120_REVA 0xa +#define APCI3120_REVB 0xb +#define APCI3120_REVA_OSC_BASE 70 /* 70ns = 14.29MHz */ +#define APCI3120_REVB_OSC_BASE 50 /* 50ns = 20MHz */ + +static const struct comedi_lrange apci3120_ai_range = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +enum apci3120_boardid { + BOARD_APCI3120, + BOARD_APCI3001, +}; + +struct apci3120_board { + const char *name; + unsigned int ai_is_16bit:1; + unsigned int has_ao:1; +}; + +static const struct apci3120_board apci3120_boardtypes[] = { + [BOARD_APCI3120] = { + .name = "apci3120", + .ai_is_16bit = 1, + .has_ao = 1, + }, + [BOARD_APCI3001] = { + .name = "apci3001", + }, +}; + +struct apci3120_dmabuf { + unsigned short *virt; + dma_addr_t hw; + unsigned int size; + unsigned int use_size; +}; + +struct apci3120_private { + unsigned long amcc; + unsigned long addon; + unsigned int osc_base; + unsigned int use_dma:1; + unsigned int use_double_buffer:1; + unsigned int cur_dmabuf:1; + struct apci3120_dmabuf dmabuf[2]; + unsigned char do_bits; + unsigned char timer_mode; + unsigned char mode; + unsigned short ctrl; +}; + +static void apci3120_addon_write(struct comedi_device *dev, + unsigned int val, unsigned int reg) +{ + struct apci3120_private *devpriv = dev->private; + + /* 16-bit interface for AMCC add-on registers */ + + outw(reg, devpriv->addon + APCI3120_ADDON_ADDR_REG); + outw(val & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG); + + outw(reg + 2, devpriv->addon + APCI3120_ADDON_ADDR_REG); + outw((val >> 16) & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG); +} + +static void apci3120_init_dma(struct comedi_device *dev, + struct apci3120_dmabuf *dmabuf) +{ + struct apci3120_private *devpriv = dev->private; + + /* AMCC - enable transfer count and reset A2P FIFO */ + outl(AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO, + devpriv->amcc + AMCC_OP_REG_AGCSTS); + + /* Add-On - enable transfer count and reset A2P FIFO */ + apci3120_addon_write(dev, AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO, + AMCC_OP_REG_AGCSTS); + + /* AMCC - enable transfers and reset A2P flags */ + outl(RESET_A2P_FLAGS | EN_A2P_TRANSFERS, + devpriv->amcc + AMCC_OP_REG_MCSR); + + /* Add-On - DMA start address */ + apci3120_addon_write(dev, dmabuf->hw, AMCC_OP_REG_AMWAR); + + /* Add-On - Number of acquisitions */ + apci3120_addon_write(dev, dmabuf->use_size, AMCC_OP_REG_AMWTC); + + /* AMCC - enable write complete (DMA) and set FIFO advance */ + outl(APCI3120_FIFO_ADVANCE_ON_BYTE_2 | AINT_WRITE_COMPL, + devpriv->amcc + AMCC_OP_REG_INTCSR); + + /* Add-On - enable DMA */ + outw(APCI3120_ADDON_CTRL_AMWEN_ENA | APCI3120_ADDON_CTRL_A2P_FIFO_ENA, + devpriv->addon + APCI3120_ADDON_CTRL_REG); +} + +static void apci3120_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci3120_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + struct apci3120_dmabuf *dmabuf0 = &devpriv->dmabuf[0]; + struct apci3120_dmabuf *dmabuf1 = &devpriv->dmabuf[1]; + unsigned int dmalen0 = dmabuf0->size; + unsigned int dmalen1 = dmabuf1->size; + unsigned int scan_bytes; + + scan_bytes = comedi_samples_to_bytes(s, cmd->scan_end_arg); + + if (cmd->stop_src == TRIG_COUNT) { + /* + * Must we fill full first buffer? And must we fill + * full second buffer when first is once filled? + */ + if (dmalen0 > (cmd->stop_arg * scan_bytes)) + dmalen0 = cmd->stop_arg * scan_bytes; + else if (dmalen1 > (cmd->stop_arg * scan_bytes - dmalen0)) + dmalen1 = cmd->stop_arg * scan_bytes - dmalen0; + } + + if (cmd->flags & CMDF_WAKE_EOS) { + /* don't we want wake up every scan? */ + if (dmalen0 > scan_bytes) { + dmalen0 = scan_bytes; + if (cmd->scan_end_arg & 1) + dmalen0 += 2; + } + if (dmalen1 > scan_bytes) { + dmalen1 = scan_bytes; + if (cmd->scan_end_arg & 1) + dmalen1 -= 2; + if (dmalen1 < 4) + dmalen1 = 4; + } + } else { + /* isn't output buff smaller that our DMA buff? */ + if (dmalen0 > s->async->prealloc_bufsz) + dmalen0 = s->async->prealloc_bufsz; + if (dmalen1 > s->async->prealloc_bufsz) + dmalen1 = s->async->prealloc_bufsz; + } + dmabuf0->use_size = dmalen0; + dmabuf1->use_size = dmalen1; + + apci3120_init_dma(dev, dmabuf0); +} + +/* + * There are three timers on the board. They all use the same base + * clock with a fixed prescaler for each timer. The base clock used + * depends on the board version and type. + * + * APCI-3120 Rev A boards OSC = 14.29MHz base clock (~70ns) + * APCI-3120 Rev B boards OSC = 20MHz base clock (50ns) + * APCI-3001 boards OSC = 20MHz base clock (50ns) + * + * The prescalers for each timer are: + * Timer 0 CLK = OSC/10 + * Timer 1 CLK = OSC/1000 + * Timer 2 CLK = OSC/1000 + */ +static unsigned int apci3120_ns_to_timer(struct comedi_device *dev, + unsigned int timer, + unsigned int ns, + unsigned int flags) +{ + struct apci3120_private *devpriv = dev->private; + unsigned int prescale = (timer == 0) ? 10 : 1000; + unsigned int timer_base = devpriv->osc_base * prescale; + unsigned int divisor; + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_UP: + divisor = DIV_ROUND_UP(ns, timer_base); + break; + case CMDF_ROUND_DOWN: + divisor = ns / timer_base; + break; + case CMDF_ROUND_NEAREST: + default: + divisor = DIV_ROUND_CLOSEST(ns, timer_base); + break; + } + + if (timer == 2) { + /* timer 2 is 24-bits */ + if (divisor > 0x00ffffff) + divisor = 0x00ffffff; + } else { + /* timers 0 and 1 are 16-bits */ + if (divisor > 0xffff) + divisor = 0xffff; + } + /* the timers require a minimum divisor of 2 */ + if (divisor < 2) + divisor = 2; + + return divisor; +} + +static void apci3120_clr_timer2_interrupt(struct comedi_device *dev) +{ + /* a dummy read of APCI3120_CTR0_REG clears the timer 2 interrupt */ + inb(dev->iobase + APCI3120_CTR0_REG); +} + +static void apci3120_timer_write(struct comedi_device *dev, + unsigned int timer, unsigned int val) +{ + struct apci3120_private *devpriv = dev->private; + + /* write 16-bit value to timer (lower 16-bits of timer 2) */ + outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | + APCI3120_CTR0_TIMER_SEL(timer), + dev->iobase + APCI3120_CTR0_REG); + outw(val & 0xffff, dev->iobase + APCI3120_TIMER_REG); + + if (timer == 2) { + /* write upper 16-bits to timer 2 */ + outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | + APCI3120_CTR0_TIMER_SEL(timer + 1), + dev->iobase + APCI3120_CTR0_REG); + outw((val >> 16) & 0xffff, dev->iobase + APCI3120_TIMER_REG); + } +} + +static unsigned int apci3120_timer_read(struct comedi_device *dev, + unsigned int timer) +{ + struct apci3120_private *devpriv = dev->private; + unsigned int val; + + /* read 16-bit value from timer (lower 16-bits of timer 2) */ + outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | + APCI3120_CTR0_TIMER_SEL(timer), + dev->iobase + APCI3120_CTR0_REG); + val = inw(dev->iobase + APCI3120_TIMER_REG); + + if (timer == 2) { + /* read upper 16-bits from timer 2 */ + outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | + APCI3120_CTR0_TIMER_SEL(timer + 1), + dev->iobase + APCI3120_CTR0_REG); + val |= (inw(dev->iobase + APCI3120_TIMER_REG) << 16); + } + + return val; +} + +static void apci3120_timer_set_mode(struct comedi_device *dev, + unsigned int timer, unsigned int mode) +{ + struct apci3120_private *devpriv = dev->private; + + devpriv->timer_mode &= ~APCI3120_TIMER_MODE_MASK(timer); + devpriv->timer_mode |= APCI3120_TIMER_MODE(timer, mode); + outb(devpriv->timer_mode, dev->iobase + APCI3120_TIMER_MODE_REG); +} + +static void apci3120_timer_enable(struct comedi_device *dev, + unsigned int timer, bool enable) +{ + struct apci3120_private *devpriv = dev->private; + + if (enable) + devpriv->ctrl |= APCI3120_CTRL_GATE(timer); + else + devpriv->ctrl &= ~APCI3120_CTRL_GATE(timer); + outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); +} + +static void apci3120_exttrig_enable(struct comedi_device *dev, bool enable) +{ + struct apci3120_private *devpriv = dev->private; + + if (enable) + devpriv->ctrl |= APCI3120_CTRL_EXT_TRIG; + else + devpriv->ctrl &= ~APCI3120_CTRL_EXT_TRIG; + outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); +} + +static void apci3120_set_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + int n_chan, unsigned int *chanlist) +{ + struct apci3120_private *devpriv = dev->private; + int i; + + /* set chanlist for scan */ + for (i = 0; i < n_chan; i++) { + unsigned int chan = CR_CHAN(chanlist[i]); + unsigned int range = CR_RANGE(chanlist[i]); + unsigned int val; + + val = APCI3120_CHANLIST_MUX(chan) | + APCI3120_CHANLIST_GAIN(range) | + APCI3120_CHANLIST_INDEX(i); + + if (comedi_range_is_unipolar(s, range)) + val |= APCI3120_CHANLIST_UNIPOLAR; + + outw(val, dev->iobase + APCI3120_CHANLIST_REG); + } + + /* a dummy read of APCI3120_TIMER_MODE_REG resets the ai FIFO */ + inw(dev->iobase + APCI3120_TIMER_MODE_REG); + + /* set scan length (PR) and scan start (PA) */ + devpriv->ctrl = APCI3120_CTRL_PR(n_chan - 1) | APCI3120_CTRL_PA(0); + outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); + + /* enable chanlist scanning if necessary */ + if (n_chan > 1) + devpriv->mode |= APCI3120_MODE_SCAN_ENA; +} + +static void apci3120_interrupt_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci3120_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + struct apci3120_dmabuf *dmabuf; + unsigned int nbytes; + unsigned int nsamples; + + dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf]; + + nbytes = dmabuf->use_size - inl(devpriv->amcc + AMCC_OP_REG_MWTC); + + if (nbytes < dmabuf->use_size) + dev_err(dev->class_dev, "Interrupted DMA transfer!\n"); + if (nbytes & 1) { + dev_err(dev->class_dev, "Odd count of bytes in DMA ring!\n"); + async->events |= COMEDI_CB_ERROR; + return; + } + + nsamples = comedi_bytes_to_samples(s, nbytes); + if (nsamples) { + comedi_buf_write_samples(s, dmabuf->virt, nsamples); + + if (!(cmd->flags & CMDF_WAKE_EOS)) + async->events |= COMEDI_CB_EOS; + } + + if ((async->events & COMEDI_CB_CANCEL_MASK) || + (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)) + return; + + if (devpriv->use_double_buffer) { + /* switch DMA buffers for next interrupt */ + devpriv->cur_dmabuf = !devpriv->cur_dmabuf; + dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf]; + apci3120_init_dma(dev, dmabuf); + } else { + /* restart DMA if not using double buffering */ + apci3120_init_dma(dev, dmabuf); + } +} + +static irqreturn_t apci3120_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct apci3120_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status; + unsigned int int_amcc; + + status = inw(dev->iobase + APCI3120_STATUS_REG); + int_amcc = inl(devpriv->amcc + AMCC_OP_REG_INTCSR); + + if (!(status & APCI3120_STATUS_INT_MASK) && + !(int_amcc & ANY_S593X_INT)) { + dev_err(dev->class_dev, "IRQ from unknown source\n"); + return IRQ_NONE; + } + + outl(int_amcc | AINT_INT_MASK, devpriv->amcc + AMCC_OP_REG_INTCSR); + + if (devpriv->ctrl & APCI3120_CTRL_EXT_TRIG) + apci3120_exttrig_enable(dev, false); + + if (int_amcc & MASTER_ABORT_INT) + dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n"); + if (int_amcc & TARGET_ABORT_INT) + dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n"); + + if ((status & APCI3120_STATUS_EOS_INT) && + (devpriv->mode & APCI3120_MODE_EOS_IRQ_ENA)) { + unsigned short val; + int i; + + for (i = 0; i < cmd->chanlist_len; i++) { + val = inw(dev->iobase + APCI3120_AI_FIFO_REG); + comedi_buf_write_samples(s, &val, 1); + } + + devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA; + outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); + } + + if (status & APCI3120_STATUS_TIMER2_INT) { + /* + * for safety... + * timer2 interrupts are not enabled in the driver + */ + apci3120_clr_timer2_interrupt(dev); + } + + if (status & APCI3120_STATUS_AMCC_INT) { + /* AMCC- Clear write complete interrupt (DMA) */ + outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR); + + /* do some data transfer */ + apci3120_interrupt_dma(dev, s); + } + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int apci3120_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci3120_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int divisor; + + /* set default mode bits */ + devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC | + APCI3120_MODE_TIMER2_AS_TIMER; + + /* AMCC- Clear write complete interrupt (DMA) */ + outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR); + + devpriv->cur_dmabuf = 0; + + /* load chanlist for command scan */ + apci3120_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist); + + if (cmd->start_src == TRIG_EXT) + apci3120_exttrig_enable(dev, true); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* + * Timer 1 is used in MODE2 (rate generator) to set the + * start time for each scan. + */ + divisor = apci3120_ns_to_timer(dev, 1, cmd->scan_begin_arg, + cmd->flags); + apci3120_timer_set_mode(dev, 1, APCI3120_TIMER_MODE2); + apci3120_timer_write(dev, 1, divisor); + } + + /* + * Timer 0 is used in MODE2 (rate generator) to set the conversion + * time for each acquisition. + */ + divisor = apci3120_ns_to_timer(dev, 0, cmd->convert_arg, cmd->flags); + apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE2); + apci3120_timer_write(dev, 0, divisor); + + if (devpriv->use_dma) + apci3120_setup_dma(dev, s); + else + devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA; + + /* set mode to enable acquisition */ + outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); + + if (cmd->scan_begin_src == TRIG_TIMER) + apci3120_timer_enable(dev, 1, true); + apci3120_timer_enable(dev, 0, true); + + return 0; +} + +static int apci3120_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int arg; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { /* Test Delay timing */ + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + 100000); + } + + /* minimum conversion time per sample is 10us */ + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* scan begin must be larger than the scan time */ + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int apci3120_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci3120_private *devpriv = dev->private; + + /* Add-On - disable DMA */ + outw(0, devpriv->addon + 4); + + /* Add-On - disable bus master */ + apci3120_addon_write(dev, 0, AMCC_OP_REG_AGCSTS); + + /* AMCC - disable bus master */ + outl(0, devpriv->amcc + AMCC_OP_REG_MCSR); + + /* disable all counters, ext trigger, and reset scan */ + devpriv->ctrl = 0; + outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); + + /* DISABLE_ALL_INTERRUPT */ + devpriv->mode = 0; + outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); + + inw(dev->iobase + APCI3120_STATUS_REG); + devpriv->cur_dmabuf = 0; + + return 0; +} + +static int apci3120_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + APCI3120_STATUS_REG); + if ((status & APCI3120_STATUS_EOC_INT) == 0) + return 0; + return -EBUSY; +} + +static int apci3120_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci3120_private *devpriv = dev->private; + unsigned int divisor; + int ret; + int i; + + /* set mode for A/D conversions by software trigger with timer 0 */ + devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC | + APCI3120_MODE_TIMER2_AS_TIMER; + outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); + + /* load chanlist for single channel scan */ + apci3120_set_chanlist(dev, s, 1, &insn->chanspec); + + /* + * Timer 0 is used in MODE4 (software triggered strobe) to set the + * conversion time for each acquisition. Each conversion is triggered + * when the divisor is written to the timer, The conversion is done + * when the EOC bit in the status register is '0'. + */ + apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE4); + apci3120_timer_enable(dev, 0, true); + + /* fixed conversion time of 10 us */ + divisor = apci3120_ns_to_timer(dev, 0, 10000, CMDF_ROUND_NEAREST); + + for (i = 0; i < insn->n; i++) { + /* trigger conversion */ + apci3120_timer_write(dev, 0, divisor); + + ret = comedi_timeout(dev, s, insn, apci3120_ai_eoc, 0); + if (ret) + return ret; + + data[i] = inw(dev->iobase + APCI3120_AI_FIFO_REG); + } + + return insn->n; +} + +static int apci3120_ao_ready(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + APCI3120_STATUS_REG); + if (status & APCI3120_STATUS_DA_READY) + return 0; + return -EBUSY; +} + +static int apci3120_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + int ret; + + ret = comedi_timeout(dev, s, insn, apci3120_ao_ready, 0); + if (ret) + return ret; + + outw(APCI3120_AO_MUX(chan) | APCI3120_AO_DATA(val), + dev->iobase + APCI3120_AO_REG(chan)); + + s->readback[chan] = val; + } + + return insn->n; +} + +static int apci3120_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int status; + + status = inw(dev->iobase + APCI3120_STATUS_REG); + data[1] = APCI3120_STATUS_TO_DI_BITS(status); + + return insn->n; +} + +static int apci3120_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci3120_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) { + devpriv->do_bits = s->state; + outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits), + dev->iobase + APCI3120_CTR0_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static int apci3120_timer_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct apci3120_private *devpriv = dev->private; + unsigned int divisor; + unsigned int status; + unsigned int mode; + unsigned int timer_mode; + + switch (data[0]) { + case INSN_CONFIG_ARM: + apci3120_clr_timer2_interrupt(dev); + divisor = apci3120_ns_to_timer(dev, 2, data[1], + CMDF_ROUND_DOWN); + apci3120_timer_write(dev, 2, divisor); + apci3120_timer_enable(dev, 2, true); + break; + + case INSN_CONFIG_DISARM: + apci3120_timer_enable(dev, 2, false); + apci3120_clr_timer2_interrupt(dev); + break; + + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = 0; + data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | + COMEDI_COUNTER_TERMINAL_COUNT; + + if (devpriv->ctrl & APCI3120_CTRL_GATE(2)) { + data[1] |= COMEDI_COUNTER_ARMED; + data[1] |= COMEDI_COUNTER_COUNTING; + } + status = inw(dev->iobase + APCI3120_STATUS_REG); + if (status & APCI3120_STATUS_TIMER2_INT) { + data[1] &= ~COMEDI_COUNTER_COUNTING; + data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; + } + break; + + case INSN_CONFIG_SET_COUNTER_MODE: + switch (data[1]) { + case I8254_MODE0: + mode = APCI3120_MODE_TIMER2_AS_COUNTER; + timer_mode = APCI3120_TIMER_MODE0; + break; + case I8254_MODE2: + mode = APCI3120_MODE_TIMER2_AS_TIMER; + timer_mode = APCI3120_TIMER_MODE2; + break; + case I8254_MODE4: + mode = APCI3120_MODE_TIMER2_AS_TIMER; + timer_mode = APCI3120_TIMER_MODE4; + break; + case I8254_MODE5: + mode = APCI3120_MODE_TIMER2_AS_WDOG; + timer_mode = APCI3120_TIMER_MODE5; + break; + default: + return -EINVAL; + } + apci3120_timer_enable(dev, 2, false); + apci3120_clr_timer2_interrupt(dev); + apci3120_timer_set_mode(dev, 2, timer_mode); + devpriv->mode &= ~APCI3120_MODE_TIMER2_AS_MASK; + devpriv->mode |= mode; + outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); + break; + + default: + return -EINVAL; + } + + return insn->n; +} + +static int apci3120_timer_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) + data[i] = apci3120_timer_read(dev, 2); + + return insn->n; +} + +static void apci3120_dma_alloc(struct comedi_device *dev) +{ + struct apci3120_private *devpriv = dev->private; + struct apci3120_dmabuf *dmabuf; + int order; + int i; + + for (i = 0; i < 2; i++) { + dmabuf = &devpriv->dmabuf[i]; + for (order = 2; order >= 0; order--) { + dmabuf->virt = dma_alloc_coherent(dev->hw_dev, + PAGE_SIZE << order, + &dmabuf->hw, + GFP_KERNEL); + if (dmabuf->virt) + break; + } + if (!dmabuf->virt) + break; + dmabuf->size = PAGE_SIZE << order; + + if (i == 0) + devpriv->use_dma = 1; + if (i == 1) + devpriv->use_double_buffer = 1; + } +} + +static void apci3120_dma_free(struct comedi_device *dev) +{ + struct apci3120_private *devpriv = dev->private; + struct apci3120_dmabuf *dmabuf; + int i; + + if (!devpriv) + return; + + for (i = 0; i < 2; i++) { + dmabuf = &devpriv->dmabuf[i]; + if (dmabuf->virt) { + dma_free_coherent(dev->hw_dev, dmabuf->size, + dmabuf->virt, dmabuf->hw); + } + } +} + +static void apci3120_reset(struct comedi_device *dev) +{ + /* disable all interrupt sources */ + outb(0, dev->iobase + APCI3120_MODE_REG); + + /* disable all counters, ext trigger, and reset scan */ + outw(0, dev->iobase + APCI3120_CTRL_REG); + + /* clear interrupt status */ + inw(dev->iobase + APCI3120_STATUS_REG); +} + +static int apci3120_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct apci3120_board *board = NULL; + struct apci3120_private *devpriv; + struct comedi_subdevice *s; + unsigned int status; + int ret; + + if (context < ARRAY_SIZE(apci3120_boardtypes)) + board = &apci3120_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; + pci_set_master(pcidev); + + dev->iobase = pci_resource_start(pcidev, 1); + devpriv->amcc = pci_resource_start(pcidev, 0); + devpriv->addon = pci_resource_start(pcidev, 2); + + apci3120_reset(dev); + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, apci3120_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) { + dev->irq = pcidev->irq; + + apci3120_dma_alloc(dev); + } + } + + status = inw(dev->iobase + APCI3120_STATUS_REG); + if (APCI3120_STATUS_TO_VERSION(status) == APCI3120_REVB || + context == BOARD_APCI3001) + devpriv->osc_base = APCI3120_REVB_OSC_BASE; + else + devpriv->osc_base = APCI3120_REVA_OSC_BASE; + + ret = comedi_alloc_subdevices(dev, 5); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + s->n_chan = 16; + s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff; + s->range_table = &apci3120_ai_range; + s->insn_read = apci3120_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = apci3120_ai_cmdtest; + s->do_cmd = apci3120_ai_cmd; + s->cancel = apci3120_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 0x3fff; + s->range_table = &range_bipolar10; + s->insn_write = apci3120_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci3120_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci3120_do_insn_bits; + + /* Timer subdevice */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_READABLE; + s->n_chan = 1; + s->maxdata = 0x00ffffff; + s->insn_config = apci3120_timer_insn_config; + s->insn_read = apci3120_timer_insn_read; + + return 0; +} + +static void apci3120_detach(struct comedi_device *dev) +{ + comedi_pci_detach(dev); + apci3120_dma_free(dev); +} + +static struct comedi_driver apci3120_driver = { + .driver_name = "addi_apci_3120", + .module = THIS_MODULE, + .auto_attach = apci3120_auto_attach, + .detach = apci3120_detach, +}; + +static int apci3120_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci3120_driver, id->driver_data); +} + +static const struct pci_device_id apci3120_pci_table[] = { + { PCI_VDEVICE(AMCC, 0x818d), BOARD_APCI3120 }, + { PCI_VDEVICE(AMCC, 0x828d), BOARD_APCI3001 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci3120_pci_table); + +static struct pci_driver apci3120_pci_driver = { + .name = "addi_apci_3120", + .id_table = apci3120_pci_table, + .probe = apci3120_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci3120_driver, apci3120_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("ADDI-DATA APCI-3120, Analog input board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_3501.c b/drivers/comedi/drivers/addi_apci_3501.c new file mode 100644 index 000000000..ecb5552f1 --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_3501.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* + * Driver: addi_apci_3501 + * Description: ADDI-DATA APCI-3501 Analog output board + * Devices: [ADDI-DATA] APCI-3501 (addi_apci_3501) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Mon, 20 Jun 2016 10:57:01 -0700 + * Status: untested + * + * Configuration Options: not applicable, uses comedi PCI auto config + * + * This board has the following features: + * - 4 or 8 analog output channels + * - 2 optically isolated digital inputs + * - 2 optically isolated digital outputs + * - 1 12-bit watchdog/timer + * + * There are 2 versions of the APCI-3501: + * - APCI-3501-4 4 analog output channels + * - APCI-3501-8 8 analog output channels + * + * These boards use the same PCI Vendor/Device IDs. The number of output + * channels used by this driver is determined by reading the EEPROM on + * the board. + * + * The watchdog/timer subdevice is not currently supported. + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +#include "amcc_s5933.h" + +/* + * PCI bar 1 register I/O map + */ +#define APCI3501_AO_CTRL_STATUS_REG 0x00 +#define APCI3501_AO_CTRL_BIPOLAR BIT(0) +#define APCI3501_AO_STATUS_READY BIT(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 BIT(31) +#define APCI3501_AO_TRIG_SCS_REG 0x08 +#define APCI3501_TIMER_BASE 0x20 +#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 { + unsigned long amcc; + unsigned char timer_mode; +}; + +static const struct comedi_lrange apci3501_ao_range = { + 2, { + BIP_RANGE(10), + UNI_RANGE(10) + } +}; + +static int apci3501_wait_for_dac(struct comedi_device *dev) +{ + unsigned int status; + + do { + status = inl(dev->iobase + APCI3501_AO_CTRL_STATUS_REG); + } while (!(status & APCI3501_AO_STATUS_READY)); + + return 0; +} + +static int apci3501_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int cfg = APCI3501_AO_DATA_CHAN(chan); + int ret; + int i; + + /* + * All analog output channels have the same output range. + * 14-bit bipolar: 0-10V + * 13-bit unipolar: +/-10V + * Changing the range of one channel changes all of them! + */ + if (range) { + outl(0, dev->iobase + APCI3501_AO_CTRL_STATUS_REG); + } else { + cfg |= APCI3501_AO_DATA_BIPOLAR; + outl(APCI3501_AO_CTRL_BIPOLAR, + dev->iobase + APCI3501_AO_CTRL_STATUS_REG); + } + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + if (range == 1) { + if (data[i] > 0x1fff) { + dev_err(dev->class_dev, + "Unipolar resolution is only 13-bits\n"); + return -EINVAL; + } + } + + ret = apci3501_wait_for_dac(dev); + if (ret) + return ret; + + outl(cfg | APCI3501_AO_DATA_VAL(val), + dev->iobase + APCI3501_AO_DATA_REG); + + s->readback[chan] = val; + } + + return insn->n; +} + +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 char nfuncs; + int i; + + nfuncs = apci3501_eeprom_readw(devpriv->amcc, 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(devpriv->amcc, 12 + offset) & 0x3f; + addr = apci3501_eeprom_readw(devpriv->amcc, 14 + offset); + + if (func == EEPROM_ANALOGOUTPUT) { + val = apci3501_eeprom_readw(devpriv->amcc, 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); + unsigned int val; + unsigned int i; + + if (insn->n) { + /* No point reading the same EEPROM location more than once. */ + val = apci3501_eeprom_readw(devpriv->amcc, 2 * addr); + for (i = 0; i < insn->n; i++) + data[i] = val; + } + + return insn->n; +} + +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; + + devpriv->amcc = pci_resource_start(pcidev, 0); + dev->iobase = pci_resource_start(pcidev, 1); + + ao_n_chan = apci3501_eeprom_get_ao_n_chan(dev); + + ret = comedi_alloc_subdevices(dev, 5); + if (ret) + return ret; + + /* Initialize the analog output subdevice */ + s = &dev->subdevices[0]; + if (ao_n_chan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = ao_n_chan; + s->maxdata = 0x3fff; + s->range_table = &apci3501_ao_range; + s->insn_write = apci3501_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Initialize the digital input subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 2; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci3501_di_insn_bits; + + /* Initialize the digital output subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci3501_do_insn_bits; + + /* Timer/Watchdog subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_UNUSED; + + /* Initialize the eeprom subdevice */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->n_chan = 256; + s->maxdata = 0xffff; + s->insn_read = apci3501_eeprom_insn_read; + + apci3501_reset(dev); + return 0; +} + +static void apci3501_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci3501_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci3501_driver = { + .driver_name = "addi_apci_3501", + .module = THIS_MODULE, + .auto_attach = apci3501_auto_attach, + .detach = apci3501_detach, +}; + +static int apci3501_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci3501_driver, id->driver_data); +} + +static const struct pci_device_id apci3501_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3001) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci3501_pci_table); + +static struct pci_driver apci3501_pci_driver = { + .name = "addi_apci_3501", + .id_table = apci3501_pci_table, + .probe = apci3501_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci3501_driver, apci3501_pci_driver); + +MODULE_DESCRIPTION("ADDI-DATA APCI-3501 Analog output board"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_apci_3xxx.c b/drivers/comedi/drivers/addi_apci_3xxx.c new file mode 100644 index 000000000..bc72273e6 --- /dev/null +++ b/drivers/comedi/drivers/addi_apci_3xxx.c @@ -0,0 +1,960 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#define CONV_UNIT_NS BIT(0) +#define CONV_UNIT_US BIT(1) +#define CONV_UNIT_MS BIT(2) + +static const struct comedi_lrange apci3xxx_ai_range = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +static const struct comedi_lrange apci3xxx_ao_range = { + 2, { + BIP_RANGE(10), + UNI_RANGE(10) + } +}; + +enum apci3xxx_boardid { + BOARD_APCI3000_16, + BOARD_APCI3000_8, + BOARD_APCI3000_4, + BOARD_APCI3006_16, + BOARD_APCI3006_8, + BOARD_APCI3006_4, + BOARD_APCI3010_16, + BOARD_APCI3010_8, + BOARD_APCI3010_4, + BOARD_APCI3016_16, + BOARD_APCI3016_8, + BOARD_APCI3016_4, + BOARD_APCI3100_16_4, + BOARD_APCI3100_8_4, + BOARD_APCI3106_16_4, + BOARD_APCI3106_8_4, + BOARD_APCI3110_16_4, + BOARD_APCI3110_8_4, + BOARD_APCI3116_16_4, + BOARD_APCI3116_8_4, + BOARD_APCI3003, + BOARD_APCI3002_16, + BOARD_APCI3002_8, + BOARD_APCI3002_4, + BOARD_APCI3500, +}; + +struct apci3xxx_boardinfo { + const char *name; + int ai_subdev_flags; + int ai_n_chan; + unsigned int ai_maxdata; + unsigned char ai_conv_units; + unsigned int ai_min_acq_ns; + unsigned int has_ao:1; + unsigned int has_dig_in:1; + unsigned int has_dig_out:1; + unsigned int has_ttl_io:1; +}; + +static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = { + [BOARD_APCI3000_16] = { + .name = "apci3000-16", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ttl_io = 1, + }, + [BOARD_APCI3000_8] = { + .name = "apci3000-8", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ttl_io = 1, + }, + [BOARD_APCI3000_4] = { + .name = "apci3000-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 4, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ttl_io = 1, + }, + [BOARD_APCI3006_16] = { + .name = "apci3006-16", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ttl_io = 1, + }, + [BOARD_APCI3006_8] = { + .name = "apci3006-8", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ttl_io = 1, + }, + [BOARD_APCI3006_4] = { + .name = "apci3006-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 4, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ttl_io = 1, + }, + [BOARD_APCI3010_16] = { + .name = "apci3010-16", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3010_8] = { + .name = "apci3010-8", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3010_4] = { + .name = "apci3010-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 4, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3016_16] = { + .name = "apci3016-16", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3016_8] = { + .name = "apci3016-8", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3016_4] = { + .name = "apci3016-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 4, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3100_16_4] = { + .name = "apci3100-16-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ao = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3100_8_4] = { + .name = "apci3100-8-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ao = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3106_16_4] = { + .name = "apci3106-16-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ao = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3106_8_4] = { + .name = "apci3106-8-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 10000, + .has_ao = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3110_16_4] = { + .name = "apci3110-16-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_ao = 1, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3110_8_4] = { + .name = "apci3110-8-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0x0fff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_ao = 1, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3116_16_4] = { + .name = "apci3116-16-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_ao = 1, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3116_8_4] = { + .name = "apci3116-8-4", + .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_ao = 1, + .has_dig_in = 1, + .has_dig_out = 1, + .has_ttl_io = 1, + }, + [BOARD_APCI3003] = { + .name = "apci3003", + .ai_subdev_flags = SDF_DIFF, + .ai_n_chan = 4, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US | + CONV_UNIT_NS, + .ai_min_acq_ns = 2500, + .has_dig_in = 1, + .has_dig_out = 1, + }, + [BOARD_APCI3002_16] = { + .name = "apci3002-16", + .ai_subdev_flags = SDF_DIFF, + .ai_n_chan = 16, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + }, + [BOARD_APCI3002_8] = { + .name = "apci3002-8", + .ai_subdev_flags = SDF_DIFF, + .ai_n_chan = 8, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + }, + [BOARD_APCI3002_4] = { + .name = "apci3002-4", + .ai_subdev_flags = SDF_DIFF, + .ai_n_chan = 4, + .ai_maxdata = 0xffff, + .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, + .ai_min_acq_ns = 5000, + .has_dig_in = 1, + .has_dig_out = 1, + }, + [BOARD_APCI3500] = { + .name = "apci3500", + .has_ao = 1, + .has_ttl_io = 1, + }, +}; + +struct apci3xxx_private { + unsigned int ai_timer; + unsigned char ai_time_base; +}; + +static irqreturn_t apci3xxx_irq_handler(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + unsigned int val; + + /* Test if interrupt occur */ + status = readl(dev->mmio + 16); + if ((status & 0x2) == 0x2) { + /* Reset the interrupt */ + writel(status, dev->mmio + 16); + + val = readl(dev->mmio + 28); + comedi_buf_write_samples(s, &val, 1); + + s->async->events |= COMEDI_CB_EOA; + comedi_handle_events(dev, s); + + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int apci3xxx_ai_started(struct comedi_device *dev) +{ + if ((readl(dev->mmio + 8) & 0x80000) == 0x80000) + return 1; + + return 0; +} + +static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec) +{ + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + unsigned int delay_mode; + unsigned int val; + + if (apci3xxx_ai_started(dev)) + return -EBUSY; + + /* Clear the FIFO */ + writel(0x10000, dev->mmio + 12); + + /* Get and save the delay mode */ + delay_mode = readl(dev->mmio + 4); + delay_mode &= 0xfffffef0; + + /* Channel configuration selection */ + writel(delay_mode, dev->mmio + 4); + + /* Make the configuration */ + val = (range & 3) | ((range >> 2) << 6) | + ((aref == AREF_DIFF) << 7); + writel(val, dev->mmio + 0); + + /* Channel selection */ + writel(delay_mode | 0x100, dev->mmio + 4); + writel(chan, dev->mmio + 0); + + /* Restore delay mode */ + writel(delay_mode, dev->mmio + 4); + + /* Set the number of sequence to 1 */ + writel(1, dev->mmio + 48); + + return 0; +} + +static int apci3xxx_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readl(dev->mmio + 20); + if (status & 0x1) + return 0; + return -EBUSY; +} + +static int apci3xxx_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + int i; + + ret = apci3xxx_ai_setup(dev, insn->chanspec); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + /* Start the conversion */ + writel(0x80000, dev->mmio + 8); + + /* Wait the EOS */ + ret = comedi_timeout(dev, s, insn, apci3xxx_ai_eoc, 0); + if (ret) + return ret; + + /* Read the analog value */ + data[i] = readl(dev->mmio + 28); + } + + return insn->n; +} + +static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev, + unsigned int *ns, unsigned int flags) +{ + const struct apci3xxx_boardinfo *board = dev->board_ptr; + struct apci3xxx_private *devpriv = dev->private; + unsigned int base; + unsigned int timer; + int time_base; + + /* time_base: 0 = ns, 1 = us, 2 = ms */ + for (time_base = 0; time_base < 3; time_base++) { + /* skip unsupported time bases */ + if (!(board->ai_conv_units & (1 << time_base))) + continue; + + switch (time_base) { + case 0: + base = 1; + break; + case 1: + base = 1000; + break; + case 2: + base = 1000000; + break; + } + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + timer = DIV_ROUND_CLOSEST(*ns, base); + break; + case CMDF_ROUND_DOWN: + timer = *ns / base; + break; + case CMDF_ROUND_UP: + timer = DIV_ROUND_UP(*ns, base); + break; + } + + if (timer < 0x10000) { + devpriv->ai_time_base = time_base; + devpriv->ai_timer = timer; + *ns = timer * time_base; + return 0; + } + } + return -EINVAL; +} + +static int apci3xxx_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct apci3xxx_boardinfo *board = dev->board_ptr; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_min_acq_ns); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->convert_arg; + err |= apci3xxx_ai_ns_to_timer(dev, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (err) + return 4; + + return 0; +} + +static int apci3xxx_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct apci3xxx_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int ret; + + ret = apci3xxx_ai_setup(dev, cmd->chanlist[0]); + if (ret) + return ret; + + /* Set the convert timing unit */ + writel(devpriv->ai_time_base, dev->mmio + 36); + + /* Set the convert timing */ + writel(devpriv->ai_timer, dev->mmio + 32); + + /* Start the conversion */ + writel(0x180000, dev->mmio + 8); + + return 0; +} + +static int apci3xxx_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + return 0; +} + +static int apci3xxx_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readl(dev->mmio + 96); + if (status & 0x100) + return 0; + return -EBUSY; +} + +static int apci3xxx_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int ret; + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + /* Set the range selection */ + writel(range, dev->mmio + 96); + + /* Write the analog value to the selected channel */ + writel((val << 8) | chan, dev->mmio + 100); + + /* Wait the end of transfer */ + ret = comedi_timeout(dev, s, insn, apci3xxx_ao_eoc, 0); + if (ret) + return ret; + + s->readback[chan] = val; + } + + return insn->n; +} + +static int apci3xxx_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inl(dev->iobase + 32) & 0xf; + + return insn->n; +} + +static int apci3xxx_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + s->state = inl(dev->iobase + 48) & 0xf; + + if (comedi_dio_update_state(s, data)) + outl(s->state, dev->iobase + 48); + + data[1] = s->state; + + return insn->n; +} + +static int apci3xxx_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask = 0; + int ret; + + /* + * Port 0 (channels 0-7) are always inputs + * Port 1 (channels 8-15) are always outputs + * Port 2 (channels 16-23) are programmable i/o + */ + if (data[0] != INSN_CONFIG_DIO_QUERY) { + /* ignore all other instructions for ports 0 and 1 */ + if (chan < 16) + return -EINVAL; + + /* changing any channel in port 2 changes the entire port */ + mask = 0xff0000; + } + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + /* update port 2 configuration */ + outl((s->io_bits >> 24) & 0xff, dev->iobase + 224); + + return insn->n; +} + +static int apci3xxx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0xff) + outl(s->state & 0xff, dev->iobase + 80); + if (mask & 0xff0000) + outl((s->state >> 16) & 0xff, dev->iobase + 112); + } + + val = inl(dev->iobase + 80); + val |= (inl(dev->iobase + 64) << 8); + if (s->io_bits & 0xff0000) + val |= (inl(dev->iobase + 112) << 16); + else + val |= (inl(dev->iobase + 96) << 16); + + data[1] = val; + + return insn->n; +} + +static int apci3xxx_reset(struct comedi_device *dev) +{ + unsigned int val; + int i; + + /* Disable the interrupt */ + disable_irq(dev->irq); + + /* Clear the start command */ + writel(0, dev->mmio + 8); + + /* Reset the interrupt flags */ + val = readl(dev->mmio + 16); + writel(val, dev->mmio + 16); + + /* clear the EOS */ + readl(dev->mmio + 20); + + /* Clear the FIFO */ + for (i = 0; i < 16; i++) + val = readl(dev->mmio + 28); + + /* Enable the interrupt */ + enable_irq(dev->irq); + + return 0; +} + +static int apci3xxx_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct apci3xxx_boardinfo *board = NULL; + struct apci3xxx_private *devpriv; + struct comedi_subdevice *s; + int n_subdevices; + int subdev; + int ret; + + if (context < ARRAY_SIZE(apci3xxx_boardtypes)) + board = &apci3xxx_boardtypes[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->iobase = pci_resource_start(pcidev, 2); + dev->mmio = pci_ioremap_bar(pcidev, 3); + if (!dev->mmio) + return -ENOMEM; + + if (pcidev->irq > 0) { + ret = request_irq(pcidev->irq, apci3xxx_irq_handler, + IRQF_SHARED, dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao + + board->has_dig_in + board->has_dig_out + + board->has_ttl_io; + ret = comedi_alloc_subdevices(dev, n_subdevices); + if (ret) + return ret; + + subdev = 0; + + /* Analog Input subdevice */ + if (board->ai_n_chan) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | board->ai_subdev_flags; + s->n_chan = board->ai_n_chan; + s->maxdata = board->ai_maxdata; + s->range_table = &apci3xxx_ai_range; + s->insn_read = apci3xxx_ai_insn_read; + if (dev->irq) { + /* + * FIXME: The hardware supports multiple scan modes + * but the original addi-data driver only supported + * reading a single channel with interrupts. Need a + * proper datasheet to fix this. + * + * The following scan modes are supported by the + * hardware: + * 1) Single software scan + * 2) Single hardware triggered scan + * 3) Continuous software scan + * 4) Continuous software scan with timer delay + * 5) Continuous hardware triggered scan + * 6) Continuous hardware triggered scan with timer + * delay + * + * For now, limit the chanlist to a single channel. + */ + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 1; + s->do_cmdtest = apci3xxx_ai_cmdtest; + s->do_cmd = apci3xxx_ai_cmd; + s->cancel = apci3xxx_ai_cancel; + } + + subdev++; + } + + /* Analog Output subdevice */ + if (board->has_ao) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->range_table = &apci3xxx_ao_range; + s->insn_write = apci3xxx_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + subdev++; + } + + /* Digital Input subdevice */ + if (board->has_dig_in) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci3xxx_di_insn_bits; + + subdev++; + } + + /* Digital Output subdevice */ + if (board->has_dig_out) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = apci3xxx_do_insn_bits; + + subdev++; + } + + /* TTL Digital I/O subdevice */ + if (board->has_ttl_io) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->io_bits = 0xff; /* channels 0-7 are always outputs */ + s->range_table = &range_digital; + s->insn_config = apci3xxx_dio_insn_config; + s->insn_bits = apci3xxx_dio_insn_bits; + + subdev++; + } + + apci3xxx_reset(dev); + return 0; +} + +static void apci3xxx_detach(struct comedi_device *dev) +{ + if (dev->iobase) + apci3xxx_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver apci3xxx_driver = { + .driver_name = "addi_apci_3xxx", + .module = THIS_MODULE, + .auto_attach = apci3xxx_auto_attach, + .detach = apci3xxx_detach, +}; + +static int apci3xxx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data); +} + +static const struct pci_device_id apci3xxx_pci_table[] = { + { PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 }, + { PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 }, + { PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 }, + { PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 }, + { PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 }, + { PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 }, + { PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 }, + { PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 }, + { PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 }, + { PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 }, + { PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 }, + { PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 }, + { PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 }, + { PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 }, + { PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 }, + { PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 }, + { PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 }, + { PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table); + +static struct pci_driver apci3xxx_pci_driver = { + .name = "addi_apci_3xxx", + .id_table = apci3xxx_pci_table, + .probe = apci3xxx_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_tcw.h b/drivers/comedi/drivers/addi_tcw.h new file mode 100644 index 000000000..2b44d3a04 --- /dev/null +++ b/drivers/comedi/drivers/addi_tcw.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ADDI_TCW_H +#define _ADDI_TCW_H + +/* + * Following are the generic definitions for the ADDI-DATA timer/counter/ + * watchdog (TCW) registers and bits. Some of the registers are not used + * depending on the use of the TCW. + */ + +#define ADDI_TCW_VAL_REG 0x00 + +#define ADDI_TCW_SYNC_REG 0x00 +#define ADDI_TCW_SYNC_CTR_TRIG BIT(8) +#define ADDI_TCW_SYNC_CTR_DIS BIT(7) +#define ADDI_TCW_SYNC_CTR_ENA BIT(6) +#define ADDI_TCW_SYNC_TIMER_TRIG BIT(5) +#define ADDI_TCW_SYNC_TIMER_DIS BIT(4) +#define ADDI_TCW_SYNC_TIMER_ENA BIT(3) +#define ADDI_TCW_SYNC_WDOG_TRIG BIT(2) +#define ADDI_TCW_SYNC_WDOG_DIS BIT(1) +#define ADDI_TCW_SYNC_WDOG_ENA BIT(0) + +#define ADDI_TCW_RELOAD_REG 0x04 + +#define ADDI_TCW_TIMEBASE_REG 0x08 + +#define ADDI_TCW_CTRL_REG 0x0c +#define ADDI_TCW_CTRL_EXT_CLK_STATUS BIT(21) +#define ADDI_TCW_CTRL_CASCADE BIT(20) +#define ADDI_TCW_CTRL_CNTR_ENA BIT(19) +#define ADDI_TCW_CTRL_CNT_UP BIT(18) +#define ADDI_TCW_CTRL_EXT_CLK(x) (((x) & 3) << 16) +#define ADDI_TCW_CTRL_EXT_CLK_MASK ADDI_TCW_CTRL_EXT_CLK(3) +#define ADDI_TCW_CTRL_MODE(x) (((x) & 7) << 13) +#define ADDI_TCW_CTRL_MODE_MASK ADDI_TCW_CTRL_MODE(7) +#define ADDI_TCW_CTRL_OUT(x) (((x) & 3) << 11) +#define ADDI_TCW_CTRL_OUT_MASK ADDI_TCW_CTRL_OUT(3) +#define ADDI_TCW_CTRL_GATE BIT(10) +#define ADDI_TCW_CTRL_TRIG BIT(9) +#define ADDI_TCW_CTRL_EXT_GATE(x) (((x) & 3) << 7) +#define ADDI_TCW_CTRL_EXT_GATE_MASK ADDI_TCW_CTRL_EXT_GATE(3) +#define ADDI_TCW_CTRL_EXT_TRIG(x) (((x) & 3) << 5) +#define ADDI_TCW_CTRL_EXT_TRIG_MASK ADDI_TCW_CTRL_EXT_TRIG(3) +#define ADDI_TCW_CTRL_TIMER_ENA BIT(4) +#define ADDI_TCW_CTRL_RESET_ENA BIT(3) +#define ADDI_TCW_CTRL_WARN_ENA BIT(2) +#define ADDI_TCW_CTRL_IRQ_ENA BIT(1) +#define ADDI_TCW_CTRL_ENA BIT(0) + +#define ADDI_TCW_STATUS_REG 0x10 +#define ADDI_TCW_STATUS_SOFT_CLR BIT(3) +#define ADDI_TCW_STATUS_HARDWARE_TRIG BIT(2) +#define ADDI_TCW_STATUS_SOFT_TRIG BIT(1) +#define ADDI_TCW_STATUS_OVERFLOW BIT(0) + +#define ADDI_TCW_IRQ_REG 0x14 +#define ADDI_TCW_IRQ BIT(0) + +#define ADDI_TCW_WARN_TIMEVAL_REG 0x18 + +#define ADDI_TCW_WARN_TIMEBASE_REG 0x1c + +#endif diff --git a/drivers/comedi/drivers/addi_watchdog.c b/drivers/comedi/drivers/addi_watchdog.c new file mode 100644 index 000000000..ed87ab432 --- /dev/null +++ b/drivers/comedi/drivers/addi_watchdog.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include "addi_tcw.h" +#include "addi_watchdog.h" + +struct addi_watchdog_private { + unsigned long iobase; + unsigned int wdog_ctrl; +}; + +/* + * The watchdog subdevice is configured with two INSN_CONFIG instructions: + * + * Enable the watchdog and set the reload timeout: + * data[0] = INSN_CONFIG_ARM + * data[1] = timeout reload value + * + * Disable the watchdog: + * data[0] = INSN_CONFIG_DISARM + */ +static int addi_watchdog_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_watchdog_private *spriv = s->private; + unsigned int reload; + + switch (data[0]) { + case INSN_CONFIG_ARM: + spriv->wdog_ctrl = ADDI_TCW_CTRL_ENA; + reload = data[1] & s->maxdata; + outl(reload, spriv->iobase + ADDI_TCW_RELOAD_REG); + + /* Time base is 20ms, let the user know the timeout */ + dev_info(dev->class_dev, "watchdog enabled, timeout:%dms\n", + 20 * reload + 20); + break; + case INSN_CONFIG_DISARM: + spriv->wdog_ctrl = 0; + break; + default: + return -EINVAL; + } + + outl(spriv->wdog_ctrl, spriv->iobase + ADDI_TCW_CTRL_REG); + + return insn->n; +} + +static int addi_watchdog_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_watchdog_private *spriv = s->private; + int i; + + for (i = 0; i < insn->n; i++) + data[i] = inl(spriv->iobase + ADDI_TCW_STATUS_REG); + + return insn->n; +} + +static int addi_watchdog_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct addi_watchdog_private *spriv = s->private; + int i; + + if (spriv->wdog_ctrl == 0) { + dev_warn(dev->class_dev, "watchdog is disabled\n"); + return -EINVAL; + } + + /* "ping" the watchdog */ + for (i = 0; i < insn->n; i++) { + outl(spriv->wdog_ctrl | ADDI_TCW_CTRL_TRIG, + spriv->iobase + ADDI_TCW_CTRL_REG); + } + + return insn->n; +} + +void addi_watchdog_reset(unsigned long iobase) +{ + outl(0x0, iobase + ADDI_TCW_CTRL_REG); + outl(0x0, iobase + ADDI_TCW_RELOAD_REG); +} +EXPORT_SYMBOL_GPL(addi_watchdog_reset); + +int addi_watchdog_init(struct comedi_subdevice *s, unsigned long iobase) +{ + struct addi_watchdog_private *spriv; + + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return -ENOMEM; + + spriv->iobase = iobase; + + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 1; + s->maxdata = 0xff; + s->insn_config = addi_watchdog_insn_config; + s->insn_read = addi_watchdog_insn_read; + s->insn_write = addi_watchdog_insn_write; + + return 0; +} +EXPORT_SYMBOL_GPL(addi_watchdog_init); + +static int __init addi_watchdog_module_init(void) +{ + return 0; +} +module_init(addi_watchdog_module_init); + +static void __exit addi_watchdog_module_exit(void) +{ +} +module_exit(addi_watchdog_module_exit); + +MODULE_DESCRIPTION("ADDI-DATA Watchdog subdevice"); +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/addi_watchdog.h b/drivers/comedi/drivers/addi_watchdog.h new file mode 100644 index 000000000..7523084a0 --- /dev/null +++ b/drivers/comedi/drivers/addi_watchdog.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ADDI_WATCHDOG_H +#define _ADDI_WATCHDOG_H + +struct comedi_subdevice; + +void addi_watchdog_reset(unsigned long iobase); +int addi_watchdog_init(struct comedi_subdevice *s, unsigned long iobase); + +#endif diff --git a/drivers/comedi/drivers/adl_pci6208.c b/drivers/comedi/drivers/adl_pci6208.c new file mode 100644 index 000000000..b27354a51 --- /dev/null +++ b/drivers/comedi/drivers/adl_pci6208.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: adl_pci6208 + * Description: ADLink PCI-6208/6216 Series Multi-channel Analog Output Cards + * Devices: [ADLink] PCI-6208 (adl_pci6208), PCI-6216 + * Author: nsyeow <nsyeow@pd.jaring.my> + * Updated: Wed, 11 Feb 2015 11:37:18 +0000 + * Status: untested + * + * Configuration Options: not applicable, uses PCI auto config + * + * All supported devices share the same PCI device ID and are treated as a + * PCI-6216 with 16 analog output channels. On a PCI-6208, the upper 8 + * channels exist in registers, but don't go to DAC chips. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI-6208/6216-GL register map + */ +#define PCI6208_AO_CONTROL(x) (0x00 + (2 * (x))) +#define PCI6208_AO_STATUS 0x00 +#define PCI6208_AO_STATUS_DATA_SEND BIT(0) +#define PCI6208_DIO 0x40 +#define PCI6208_DIO_DO_MASK (0x0f) +#define PCI6208_DIO_DO_SHIFT (0) +#define PCI6208_DIO_DI_MASK (0xf0) +#define PCI6208_DIO_DI_SHIFT (4) + +static int pci6208_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + PCI6208_AO_STATUS); + if ((status & PCI6208_AO_STATUS_DATA_SEND) == 0) + return 0; + return -EBUSY; +} + +static int pci6208_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int ret; + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + /* D/A transfer rate is 2.2us */ + ret = comedi_timeout(dev, s, insn, pci6208_ao_eoc, 0); + if (ret) + return ret; + + /* the hardware expects two's complement values */ + outw(comedi_offset_munge(s, val), + dev->iobase + PCI6208_AO_CONTROL(chan)); + + s->readback[chan] = val; + } + + return insn->n; +} + +static int pci6208_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int val; + + val = inw(dev->iobase + PCI6208_DIO); + val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT; + + data[1] = val; + + return insn->n; +} + +static int pci6208_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + PCI6208_DIO); + + data[1] = s->state; + + return insn->n; +} + +static int pci6208_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + unsigned int val; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; /* Only 8 usable on PCI-6208 */ + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = pci6208_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[1]; + /* digital input subdevice */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci6208_di_insn_bits; + + s = &dev->subdevices[2]; + /* digital output subdevice */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci6208_do_insn_bits; + + /* + * Get the read back signals from the digital outputs + * and save it as the initial state for the subdevice. + */ + val = inw(dev->iobase + PCI6208_DIO); + val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT; + s->state = val; + + return 0; +} + +static struct comedi_driver adl_pci6208_driver = { + .driver_name = "adl_pci6208", + .module = THIS_MODULE, + .auto_attach = pci6208_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adl_pci6208_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci6208_driver, + id->driver_data); +} + +static const struct pci_device_id adl_pci6208_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x6208) }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, + 0x9999, 0x6208) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table); + +static struct pci_driver adl_pci6208_pci_driver = { + .name = "adl_pci6208", + .id_table = adl_pci6208_pci_table, + .probe = adl_pci6208_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for ADLink 6208 series cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adl_pci7x3x.c b/drivers/comedi/drivers/adl_pci7x3x.c new file mode 100644 index 000000000..e9f22de9b --- /dev/null +++ b/drivers/comedi/drivers/adl_pci7x3x.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: adl_pci7x3x + * Description: 32/64-Channel Isolated Digital I/O Boards + * Devices: [ADLink] PCI-7230 (adl_pci7230), PCI-7233 (adl_pci7233), + * PCI-7234 (adl_pci7234), PCI-7432 (adl_pci7432), PCI-7433 (adl_pci7433), + * PCI-7434 (adl_pci7434) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Fri, 20 Nov 2020 14:49:36 +0000 + * Status: works (tested on PCI-7230) + * + * 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 - 4 subdevices: 0 - 16 input, 1 - 16 output, + * 2 - IRQ_IDI0, 3 - IRQ_IDI1 + * PCI-7233 - 1 subdevice: 0 - 32 input + * PCI-7234 - 1 subdevice: 0 - 32 output + * PCI-7432 - 2 subdevices: 0 - 32 input, 1 - 32 output + * PCI-7433 - 2 subdevices: 0 - 32 input, 1 - 32 input + * PCI-7434 - 2 subdevices: 0 - 32 output, 1 - 32 output + * + * The PCI-7230, PCI-7432 and PCI-7433 boards also support external + * interrupt signals on digital input channels 0 and 1. The PCI-7233 + * has dual-interrupt sources for change-of-state (COS) on any 16 + * digital input channels of LSB and for COS on any 16 digital input + * lines of MSB. + * + * Currently, this driver only supports interrupts for PCI-7230. + * + * Configuration Options: not applicable, uses comedi PCI auto config + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +#include "plx9052.h" + +/* + * Register I/O map (32-bit access only) + */ +#define PCI7X3X_DIO_REG 0x0000 /* in the DigIO Port area */ +#define PCI743X_DIO_REG 0x0004 + +#define ADL_PT_CLRIRQ 0x0040 /* in the DigIO Port area */ + +#define LINTI1_EN_ACT_IDI0 (PLX9052_INTCSR_LI1ENAB | PLX9052_INTCSR_LI1STAT) +#define LINTI2_EN_ACT_IDI1 (PLX9052_INTCSR_LI2ENAB | PLX9052_INTCSR_LI2STAT) +#define EN_PCI_LINT2H_LINT1H \ + (PLX9052_INTCSR_PCIENAB | PLX9052_INTCSR_LI2POL | PLX9052_INTCSR_LI1POL) + +enum adl_pci7x3x_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; + int irq_nchan; +}; + +static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = { + [BOARD_PCI7230] = { + .name = "adl_pci7230", + .nsubdevs = 4, /* IDI, IDO, IRQ_IDI0, IRQ_IDI1 */ + .di_nchan = 16, + .do_nchan = 16, + .irq_nchan = 2, + }, + [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, + } +}; + +struct adl_pci7x3x_dev_private_data { + unsigned long lcr_io_base; + unsigned int int_ctrl; +}; + +struct adl_pci7x3x_sd_private_data { + spinlock_t subd_slock; /* spin-lock for cmd_running */ + unsigned long port_offset; + short int cmd_running; +}; + +static void process_irq(struct comedi_device *dev, unsigned int subdev, + unsigned short intcsr) +{ + struct comedi_subdevice *s = &dev->subdevices[subdev]; + struct adl_pci7x3x_sd_private_data *sd_priv = s->private; + unsigned long reg = sd_priv->port_offset; + struct comedi_async *async_p = s->async; + + if (async_p) { + unsigned short val = inw(dev->iobase + reg); + + spin_lock(&sd_priv->subd_slock); + if (sd_priv->cmd_running) + comedi_buf_write_samples(s, &val, 1); + spin_unlock(&sd_priv->subd_slock); + comedi_handle_events(dev, s); + } +} + +static irqreturn_t adl_pci7x3x_interrupt(int irq, void *p_device) +{ + struct comedi_device *dev = p_device; + struct adl_pci7x3x_dev_private_data *dev_private = dev->private; + unsigned long cpu_flags; + unsigned int intcsr; + bool li1stat, li2stat; + + if (!dev->attached) { + /* Ignore interrupt before device fully attached. */ + /* Might not even have allocated subdevices yet! */ + return IRQ_NONE; + } + + /* Check if we are source of interrupt */ + spin_lock_irqsave(&dev->spinlock, cpu_flags); + intcsr = inl(dev_private->lcr_io_base + PLX9052_INTCSR); + li1stat = (intcsr & LINTI1_EN_ACT_IDI0) == LINTI1_EN_ACT_IDI0; + li2stat = (intcsr & LINTI2_EN_ACT_IDI1) == LINTI2_EN_ACT_IDI1; + if (li1stat || li2stat) { + /* clear all current interrupt flags */ + /* Fixme: Reset all 2 Int Flags */ + outb(0x00, dev->iobase + ADL_PT_CLRIRQ); + } + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + + /* SubDev 2, 3 = Isolated DigIn , on "SCSI2" jack!*/ + + if (li1stat) /* 0x0005 LINTi1 is Enabled && IDI0 is 1 */ + process_irq(dev, 2, intcsr); + + if (li2stat) /* 0x0028 LINTi2 is Enabled && IDI1 is 1 */ + process_irq(dev, 3, intcsr); + + return IRQ_RETVAL(li1stat || li2stat); +} + +static int adl_pci7x3x_asy_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int adl_pci7x3x_asy_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct adl_pci7x3x_dev_private_data *dev_private = dev->private; + struct adl_pci7x3x_sd_private_data *sd_priv = s->private; + unsigned long cpu_flags; + unsigned int int_enab; + + if (s->index == 2) { + /* enable LINTi1 == IDI sdi[0] Ch 0 IRQ ActHigh */ + int_enab = PLX9052_INTCSR_LI1ENAB; + } else { + /* enable LINTi2 == IDI sdi[0] Ch 1 IRQ ActHigh */ + int_enab = PLX9052_INTCSR_LI2ENAB; + } + + spin_lock_irqsave(&dev->spinlock, cpu_flags); + dev_private->int_ctrl |= int_enab; + outl(dev_private->int_ctrl, dev_private->lcr_io_base + PLX9052_INTCSR); + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + + spin_lock_irqsave(&sd_priv->subd_slock, cpu_flags); + sd_priv->cmd_running = 1; + spin_unlock_irqrestore(&sd_priv->subd_slock, cpu_flags); + + return 0; +} + +static int adl_pci7x3x_asy_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct adl_pci7x3x_dev_private_data *dev_private = dev->private; + struct adl_pci7x3x_sd_private_data *sd_priv = s->private; + unsigned long cpu_flags; + unsigned int int_enab; + + spin_lock_irqsave(&sd_priv->subd_slock, cpu_flags); + sd_priv->cmd_running = 0; + spin_unlock_irqrestore(&sd_priv->subd_slock, cpu_flags); + /* disable Interrupts */ + if (s->index == 2) + int_enab = PLX9052_INTCSR_LI1ENAB; + else + int_enab = PLX9052_INTCSR_LI2ENAB; + spin_lock_irqsave(&dev->spinlock, cpu_flags); + dev_private->int_ctrl &= ~int_enab; + outl(dev_private->int_ctrl, dev_private->lcr_io_base + PLX9052_INTCSR); + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + + return 0; +} + +/* same as _di_insn_bits because the IRQ-pins are the DI-ports */ +static int adl_pci7x3x_dirq_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct adl_pci7x3x_sd_private_data *sd_priv = s->private; + unsigned long reg = (unsigned long)sd_priv->port_offset; + + data[1] = inl(dev->iobase + reg); + + return insn->n; +} + +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)) { + unsigned int val = s->state; + + if (s->n_chan == 16) { + /* + * It seems the PCI-7230 needs the 16-bit DO state + * to be shifted left by 16 bits before being written + * to the 32-bit register. Set the value in both + * halves of the register to be sure. + */ + val |= val << 16; + } + outl(val, 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_reset(struct comedi_device *dev) +{ + struct adl_pci7x3x_dev_private_data *dev_private = dev->private; + + /* disable Interrupts */ + dev_private->int_ctrl = 0x00; /* Disable PCI + LINTi2 + LINTi1 */ + outl(dev_private->int_ctrl, dev_private->lcr_io_base + PLX9052_INTCSR); + + return 0; +} + +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; + struct adl_pci7x3x_dev_private_data *dev_private; + int subdev; + int nchan; + int ret; + int ic; + + 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; + + dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private)); + if (!dev_private) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + dev_private->lcr_io_base = pci_resource_start(pcidev, 1); + + adl_pci7x3x_reset(dev); + + if (board->irq_nchan) { + /* discard all evtl. old IRQs */ + outb(0x00, dev->iobase + ADL_PT_CLRIRQ); + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, adl_pci7x3x_interrupt, + IRQF_SHARED, dev->board_name, dev); + if (ret == 0) { + dev->irq = pcidev->irq; + /* 0x52 PCI + IDI Ch 1 Ch 0 IRQ Off ActHigh */ + dev_private->int_ctrl = EN_PCI_LINT2H_LINT1H; + outl(dev_private->int_ctrl, + dev_private->lcr_io_base + PLX9052_INTCSR); + } + } + } + + 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++; + } + } + + for (ic = 0; ic < board->irq_nchan; ++ic) { + struct adl_pci7x3x_sd_private_data *sd_priv; + + nchan = 1; + + s = &dev->subdevices[subdev]; + /* Isolated digital inputs 0 or 1 */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = nchan; + s->maxdata = 1; + s->insn_bits = adl_pci7x3x_dirq_insn_bits; + s->range_table = &range_digital; + + sd_priv = comedi_alloc_spriv(s, sizeof(*sd_priv)); + if (!sd_priv) + return -ENOMEM; + + spin_lock_init(&sd_priv->subd_slock); + sd_priv->port_offset = PCI7X3X_DIO_REG; + sd_priv->cmd_running = 0; + + if (dev->irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->len_chanlist = 1; + s->do_cmdtest = adl_pci7x3x_asy_cmdtest; + s->do_cmd = adl_pci7x3x_asy_cmd; + s->cancel = adl_pci7x3x_asy_cancel; + } + + subdev++; + } + + return 0; +} + +static void adl_pci7x3x_detach(struct comedi_device *dev) +{ + if (dev->iobase) + adl_pci7x3x_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver adl_pci7x3x_driver = { + .driver_name = "adl_pci7x3x", + .module = THIS_MODULE, + .auto_attach = adl_pci7x3x_auto_attach, + .detach = adl_pci7x3x_detach, +}; + +static int adl_pci7x3x_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci7x3x_driver, + id->driver_data); +} + +static const struct pci_device_id adl_pci7x3x_pci_table[] = { + { PCI_VDEVICE(ADLINK, 0x7230), BOARD_PCI7230 }, + { PCI_VDEVICE(ADLINK, 0x7233), BOARD_PCI7233 }, + { PCI_VDEVICE(ADLINK, 0x7234), BOARD_PCI7234 }, + { PCI_VDEVICE(ADLINK, 0x7432), BOARD_PCI7432 }, + { PCI_VDEVICE(ADLINK, 0x7433), BOARD_PCI7433 }, + { PCI_VDEVICE(ADLINK, 0x7434), BOARD_PCI7434 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adl_pci7x3x_pci_table); + +static struct pci_driver adl_pci7x3x_pci_driver = { + .name = "adl_pci7x3x", + .id_table = adl_pci7x3x_pci_table, + .probe = adl_pci7x3x_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci7x3x_driver, adl_pci7x3x_pci_driver); + +MODULE_DESCRIPTION("ADLINK PCI-723x/743x Isolated Digital I/O boards"); +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adl_pci8164.c b/drivers/comedi/drivers/adl_pci8164.c new file mode 100644 index 000000000..0c513a67a --- /dev/null +++ b/drivers/comedi/drivers/adl_pci8164.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/adl_pci8164.c + * + * Hardware comedi driver for PCI-8164 Adlink card + * Copyright (C) 2004 Michel Lachine <mike@mikelachaine.ca> + */ + +/* + * 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/comedi/comedi_pci.h> + +#define PCI8164_AXIS(x) ((x) * 0x08) +#define PCI8164_CMD_MSTS_REG 0x00 +#define PCI8164_OTP_SSTS_REG 0x02 +#define PCI8164_BUF0_REG 0x04 +#define PCI8164_BUF1_REG 0x06 + +static int adl_pci8164_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long offset = (unsigned long)s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = inw(dev->iobase + PCI8164_AXIS(chan) + offset); + + return insn->n; +} + +static int adl_pci8164_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long offset = (unsigned long)s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + outw(data[i], dev->iobase + PCI8164_AXIS(chan) + offset); + + return insn->n; +} + +static int adl_pci8164_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* read MSTS register / write CMD register for each axis (channel) */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + s->insn_read = adl_pci8164_insn_read; + s->insn_write = adl_pci8164_insn_write; + s->private = (void *)PCI8164_CMD_MSTS_REG; + + /* read SSTS register / write OTP register for each axis (channel) */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + s->insn_read = adl_pci8164_insn_read; + s->insn_write = adl_pci8164_insn_write; + s->private = (void *)PCI8164_OTP_SSTS_REG; + + /* read/write BUF0 register for each axis (channel) */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + s->insn_read = adl_pci8164_insn_read; + s->insn_write = adl_pci8164_insn_write; + s->private = (void *)PCI8164_BUF0_REG; + + /* read/write BUF1 register for each axis (channel) */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + s->insn_read = adl_pci8164_insn_read; + s->insn_write = adl_pci8164_insn_write; + s->private = (void *)PCI8164_BUF1_REG; + + return 0; +} + +static struct comedi_driver adl_pci8164_driver = { + .driver_name = "adl_pci8164", + .module = THIS_MODULE, + .auto_attach = adl_pci8164_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adl_pci8164_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci8164_driver, + id->driver_data); +} + +static const struct pci_device_id adl_pci8164_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x8164) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adl_pci8164_pci_table); + +static struct pci_driver adl_pci8164_pci_driver = { + .name = "adl_pci8164", + .id_table = adl_pci8164_pci_table, + .probe = adl_pci8164_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci8164_driver, adl_pci8164_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adl_pci9111.c b/drivers/comedi/drivers/adl_pci9111.c new file mode 100644 index 000000000..c50f94272 --- /dev/null +++ b/drivers/comedi/drivers/adl_pci9111.c @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adl_pci9111.c + * Hardware driver for PCI9111 ADLink cards: PCI-9111HR + * Copyright (C) 2002-2005 Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> + */ + +/* + * Driver: adl_pci9111 + * Description: Adlink PCI-9111HR + * Devices: [ADLink] PCI-9111HR (adl_pci9111) + * Author: Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> + * Status: experimental + * + * Configuration options: not applicable, uses PCI auto config + * + * 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. + */ + +/* + * TODO: + * - Really test implemented functionality. + * - Add support for the PCI-9111DG with a probe routine to identify + * the card type (perhaps with the help of the channel number readback + * of the A/D Data register). + * - Add external multiplexer support. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8254.h> + +#include "plx9052.h" + +#define PCI9111_FIFO_HALF_SIZE 512 + +#define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000 + +#define PCI9111_RANGE_SETTING_DELAY 10 +#define PCI9111_AI_INSTANT_READ_UDELAY_US 2 + +/* + * IO address map and bit defines + */ +#define PCI9111_AI_FIFO_REG 0x00 +#define PCI9111_AO_REG 0x00 +#define PCI9111_DIO_REG 0x02 +#define PCI9111_EDIO_REG 0x04 +#define PCI9111_AI_CHANNEL_REG 0x06 +#define PCI9111_AI_RANGE_STAT_REG 0x08 +#define PCI9111_AI_STAT_AD_BUSY BIT(7) +#define PCI9111_AI_STAT_FF_FF BIT(6) +#define PCI9111_AI_STAT_FF_HF BIT(5) +#define PCI9111_AI_STAT_FF_EF BIT(4) +#define PCI9111_AI_RANGE(x) (((x) & 0x7) << 0) +#define PCI9111_AI_RANGE_MASK PCI9111_AI_RANGE(7) +#define PCI9111_AI_TRIG_CTRL_REG 0x0a +#define PCI9111_AI_TRIG_CTRL_TRGEVENT BIT(5) +#define PCI9111_AI_TRIG_CTRL_POTRG BIT(4) +#define PCI9111_AI_TRIG_CTRL_PTRG BIT(3) +#define PCI9111_AI_TRIG_CTRL_ETIS BIT(2) +#define PCI9111_AI_TRIG_CTRL_TPST BIT(1) +#define PCI9111_AI_TRIG_CTRL_ASCAN BIT(0) +#define PCI9111_INT_CTRL_REG 0x0c +#define PCI9111_INT_CTRL_ISC2 BIT(3) +#define PCI9111_INT_CTRL_FFEN BIT(2) +#define PCI9111_INT_CTRL_ISC1 BIT(1) +#define PCI9111_INT_CTRL_ISC0 BIT(0) +#define PCI9111_SOFT_TRIG_REG 0x0e +#define PCI9111_8254_BASE_REG 0x40 +#define PCI9111_INT_CLR_REG 0x48 + +/* PLX 9052 Local Interrupt 1 enabled and active */ +#define PCI9111_LI1_ACTIVE (PLX9052_INTCSR_LI1ENAB | \ + PLX9052_INTCSR_LI1STAT) + +/* PLX 9052 Local Interrupt 2 enabled and active */ +#define PCI9111_LI2_ACTIVE (PLX9052_INTCSR_LI2ENAB | \ + PLX9052_INTCSR_LI2STAT) + +static const struct comedi_lrange pci9111_ai_range = { + 5, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625) + } +}; + +struct pci9111_private_data { + unsigned long lcr_io_base; + + unsigned int scan_delay; + unsigned int chunk_counter; + unsigned int chunk_num_samples; + + unsigned short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE]; +}; + +static void plx9050_interrupt_control(unsigned long io_base, + bool int1_enable, + bool int1_active_high, + bool int2_enable, + bool int2_active_high, + bool interrupt_enable) +{ + int flags = 0; + + if (int1_enable) + flags |= PLX9052_INTCSR_LI1ENAB; + if (int1_active_high) + flags |= PLX9052_INTCSR_LI1POL; + if (int2_enable) + flags |= PLX9052_INTCSR_LI2ENAB; + if (int2_active_high) + flags |= PLX9052_INTCSR_LI2POL; + + if (interrupt_enable) + flags |= PLX9052_INTCSR_PCIENAB; + + outb(flags, io_base + PLX9052_INTCSR); +} + +enum pci9111_ISC0_sources { + irq_on_eoc, + irq_on_fifo_half_full +}; + +enum pci9111_ISC1_sources { + irq_on_timer_tick, + irq_on_external_trigger +}; + +static void pci9111_interrupt_source_set(struct comedi_device *dev, + enum pci9111_ISC0_sources irq_0_source, + enum pci9111_ISC1_sources irq_1_source) +{ + int flags; + + /* Read the current interrupt control bits */ + flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + /* Shift the bits so they are compatible with the write register */ + flags >>= 4; + /* Mask off the ISCx bits */ + flags &= 0xc0; + + /* Now set the new ISCx bits */ + if (irq_0_source == irq_on_fifo_half_full) + flags |= PCI9111_INT_CTRL_ISC0; + + if (irq_1_source == irq_on_external_trigger) + flags |= PCI9111_INT_CTRL_ISC1; + + outb(flags, dev->iobase + PCI9111_INT_CTRL_REG); +} + +static void pci9111_fifo_reset(struct comedi_device *dev) +{ + unsigned long int_ctrl_reg = dev->iobase + PCI9111_INT_CTRL_REG; + + /* To reset the FIFO, set FFEN sequence as 0 -> 1 -> 0 */ + outb(0, int_ctrl_reg); + outb(PCI9111_INT_CTRL_FFEN, int_ctrl_reg); + outb(0, int_ctrl_reg); +} + +static int pci9111_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9111_private_data *dev_private = dev->private; + + /* Disable interrupts */ + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, + true, false); + + /* disable A/D triggers (software trigger mode) and auto scan off */ + outb(0, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + + pci9111_fifo_reset(dev); + + return 0; +} + +static int pci9111_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (chan != i) { + dev_dbg(dev->class_dev, + "entries in chanlist must be consecutive channels,counting upwards from 0\n"); + return -EINVAL; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same gain\n"); + return -EINVAL; + } + + if (aref != aref0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same reference\n"); + return -EINVAL; + } + } + + return 0; +} + +static int pci9111_ai_do_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, + TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->scan_begin_src != TRIG_FOLLOW) { + if (cmd->scan_begin_src != cmd->convert_src) + err |= -EINVAL; + } + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + PCI9111_AI_ACQUISITION_PERIOD_MIN_NS); + } else { /* TRIG_EXT */ + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + PCI9111_AI_ACQUISITION_PERIOD_MIN_NS); + } else { /* TRIG_FOLLOW || TRIG_EXT */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + /* + * There's only one timer on this card, so the scan_begin timer + * must be a multiple of chanlist_len*convert_arg + */ + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->chanlist_len * cmd->convert_arg; + + if (arg < cmd->scan_begin_arg) + arg *= (cmd->scan_begin_arg / arg); + + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= pci9111_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int pci9111_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9111_private_data *dev_private = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int trig = 0; + + /* Set channel scan limit */ + /* PCI9111 allows only scanning from channel 0 to channel n */ + /* TODO: handle the case of an external multiplexer */ + + if (cmd->chanlist_len > 1) + trig |= PCI9111_AI_TRIG_CTRL_ASCAN; + + outb(last_chan, dev->iobase + PCI9111_AI_CHANNEL_REG); + + /* Set gain - all channels use the same range */ + outb(PCI9111_AI_RANGE(range0), dev->iobase + PCI9111_AI_RANGE_STAT_REG); + + /* Set timer pacer */ + dev_private->scan_delay = 0; + if (cmd->convert_src == TRIG_TIMER) { + trig |= PCI9111_AI_TRIG_CTRL_TPST; + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + pci9111_fifo_reset(dev); + pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, + irq_on_timer_tick); + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, + false, true, true); + + if (cmd->scan_begin_src == TRIG_TIMER) { + dev_private->scan_delay = (cmd->scan_begin_arg / + (cmd->convert_arg * cmd->chanlist_len)) - 1; + } + } else { /* TRIG_EXT */ + trig |= PCI9111_AI_TRIG_CTRL_ETIS; + pci9111_fifo_reset(dev); + pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, + irq_on_timer_tick); + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, + false, true, true); + } + outb(trig, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + + dev_private->chunk_counter = 0; + dev_private->chunk_num_samples = cmd->chanlist_len * + (1 + dev_private->scan_delay); + + return 0; +} + +static void pci9111_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *data, + unsigned int num_bytes, + unsigned int start_chan_index) +{ + unsigned short *array = data; + unsigned int maxdata = s->maxdata; + unsigned int invert = (maxdata + 1) >> 1; + unsigned int shift = (maxdata == 0xffff) ? 0 : 4; + unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes); + unsigned int i; + + for (i = 0; i < num_samples; i++) + array[i] = ((array[i] >> shift) & maxdata) ^ invert; +} + +static void pci9111_handle_fifo_half_full(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9111_private_data *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short *buf = devpriv->ai_bounce_buffer; + unsigned int samples; + + samples = comedi_nsamples_left(s, PCI9111_FIFO_HALF_SIZE); + insw(dev->iobase + PCI9111_AI_FIFO_REG, buf, samples); + + if (devpriv->scan_delay < 1) { + comedi_buf_write_samples(s, buf, samples); + } else { + unsigned int pos = 0; + unsigned int to_read; + + while (pos < samples) { + if (devpriv->chunk_counter < cmd->chanlist_len) { + to_read = cmd->chanlist_len - + devpriv->chunk_counter; + + if (to_read > samples - pos) + to_read = samples - pos; + + comedi_buf_write_samples(s, buf + pos, to_read); + } else { + to_read = devpriv->chunk_num_samples - + devpriv->chunk_counter; + + if (to_read > samples - pos) + to_read = samples - pos; + } + + pos += to_read; + devpriv->chunk_counter += to_read; + + if (devpriv->chunk_counter >= + devpriv->chunk_num_samples) + devpriv->chunk_counter = 0; + } + } +} + +static irqreturn_t pci9111_interrupt(int irq, void *p_device) +{ + struct comedi_device *dev = p_device; + struct pci9111_private_data *dev_private = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + unsigned int status; + unsigned long irq_flags; + unsigned char intcsr; + + if (!dev->attached) { + /* Ignore interrupt before device fully attached. */ + /* Might not even have allocated subdevices yet! */ + return IRQ_NONE; + } + + async = s->async; + cmd = &async->cmd; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + + /* Check if we are source of interrupt */ + intcsr = inb(dev_private->lcr_io_base + PLX9052_INTCSR); + if (!(((intcsr & PLX9052_INTCSR_PCIENAB) != 0) && + (((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) || + ((intcsr & PCI9111_LI2_ACTIVE) == PCI9111_LI2_ACTIVE)))) { + /* Not the source of the interrupt. */ + /* (N.B. not using PLX9052_INTCSR_SOFTINT) */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + return IRQ_NONE; + } + + if ((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) { + /* Interrupt comes from fifo_half-full signal */ + + status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); + + /* '0' means FIFO is full, data may have been lost */ + if (!(status & PCI9111_AI_STAT_FF_FF)) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + dev_dbg(dev->class_dev, "fifo overflow\n"); + outb(0, dev->iobase + PCI9111_INT_CLR_REG); + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + + return IRQ_HANDLED; + } + + /* '0' means FIFO is half-full */ + if (!(status & PCI9111_AI_STAT_FF_HF)) + pci9111_handle_fifo_half_full(dev, s); + } + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + + outb(0, dev->iobase + PCI9111_INT_CLR_REG); + + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int pci9111_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); + if (status & PCI9111_AI_STAT_FF_EF) + return 0; + return -EBUSY; +} + +static int pci9111_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int maxdata = s->maxdata; + unsigned int invert = (maxdata + 1) >> 1; + unsigned int shift = (maxdata == 0xffff) ? 0 : 4; + unsigned int status; + int ret; + int i; + + outb(chan, dev->iobase + PCI9111_AI_CHANNEL_REG); + + status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); + if ((status & PCI9111_AI_RANGE_MASK) != range) { + outb(PCI9111_AI_RANGE(range), + dev->iobase + PCI9111_AI_RANGE_STAT_REG); + } + + pci9111_fifo_reset(dev); + + for (i = 0; i < insn->n; i++) { + /* Generate a software trigger */ + outb(0, dev->iobase + PCI9111_SOFT_TRIG_REG); + + ret = comedi_timeout(dev, s, insn, pci9111_ai_eoc, 0); + if (ret) { + pci9111_fifo_reset(dev); + return ret; + } + + data[i] = inw(dev->iobase + PCI9111_AI_FIFO_REG); + data[i] = ((data[i] >> shift) & maxdata) ^ invert; + } + + return i; +} + +static int pci9111_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + PCI9111_AO_REG); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pci9111_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inw(dev->iobase + PCI9111_DIO_REG); + + return insn->n; +} + +static int pci9111_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + PCI9111_DIO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int pci9111_reset(struct comedi_device *dev) +{ + struct pci9111_private_data *dev_private = dev->private; + + /* Set trigger source to software */ + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, + true, false); + + /* disable A/D triggers (software trigger mode) and auto scan off */ + outb(0, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); + + return 0; +} + +static int pci9111_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pci9111_private_data *dev_private; + struct comedi_subdevice *s; + int ret; + + dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private)); + if (!dev_private) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev_private->lcr_io_base = pci_resource_start(pcidev, 1); + dev->iobase = pci_resource_start(pcidev, 2); + + pci9111_reset(dev); + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, pci9111_interrupt, + IRQF_SHARED, dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + dev->pacer = comedi_8254_init(dev->iobase + PCI9111_8254_BASE_REG, + I8254_OSC_BASE_2MHZ, I8254_IO16, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON; + s->n_chan = 16; + s->maxdata = 0xffff; + s->range_table = &pci9111_ai_range; + s->insn_read = pci9111_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = pci9111_ai_do_cmd_test; + s->do_cmd = pci9111_ai_do_cmd; + s->cancel = pci9111_ai_cancel; + s->munge = pci9111_ai_munge; + } + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_COMMON; + s->n_chan = 1; + s->maxdata = 0x0fff; + s->len_chanlist = 1; + s->range_table = &range_bipolar10; + s->insn_write = pci9111_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci9111_di_insn_bits; + + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci9111_do_insn_bits; + + return 0; +} + +static void pci9111_detach(struct comedi_device *dev) +{ + if (dev->iobase) + pci9111_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver adl_pci9111_driver = { + .driver_name = "adl_pci9111", + .module = THIS_MODULE, + .auto_attach = pci9111_auto_attach, + .detach = pci9111_detach, +}; + +static int pci9111_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci9111_driver, + id->driver_data); +} + +static const struct pci_device_id pci9111_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x9111) }, + /* { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HG_DEVICE_ID) }, */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, pci9111_pci_table); + +static struct pci_driver adl_pci9111_pci_driver = { + .name = "adl_pci9111", + .id_table = pci9111_pci_table, + .probe = pci9111_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci9111_driver, adl_pci9111_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adl_pci9118.c b/drivers/comedi/drivers/adl_pci9118.c new file mode 100644 index 000000000..9a816c718 --- /dev/null +++ b/drivers/comedi/drivers/adl_pci9118.c @@ -0,0 +1,1735 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * comedi/drivers/adl_pci9118.c + * + * hardware driver for ADLink cards: + * card: PCI-9118DG, PCI-9118HG, PCI-9118HR + * driver: pci9118dg, pci9118hg, pci9118hr + * + * Author: Michal Dobes <dobes@tesnet.cz> + * + */ + +/* + * Driver: adl_pci9118 + * Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR + * Author: Michal Dobes <dobes@tesnet.cz> + * Devices: [ADLink] PCI-9118DG (pci9118dg), PCI-9118HG (pci9118hg), + * PCI-9118HR (pci9118hr) + * Status: works + * + * This driver supports AI, AO, DI and DO subdevices. + * AI subdevice supports cmd and insn interface, + * other subdevices support only insn interface. + * For AI: + * - If cmd->scan_begin_src=TRIG_EXT then trigger input is TGIN (pin 46). + * - If cmd->convert_src=TRIG_EXT then trigger input is EXTTRG (pin 44). + * - If cmd->start_src/stop_src=TRIG_EXT then trigger input is TGIN (pin 46). + * - It is not necessary to have cmd.scan_end_arg=cmd.chanlist_len but + * cmd.scan_end_arg modulo cmd.chanlist_len must by 0. + * - If return value of cmdtest is 5 then you've bad channel list + * (it isn't possible mixture S.E. and DIFF inputs or bipolar and unipolar + * ranges). + * + * There are some hardware limitations: + * a) You cann't use mixture of unipolar/bipoar ranges or differencial/single + * ended inputs. + * b) DMA transfers must have the length aligned to two samples (32 bit), + * so there is some problems if cmd->chanlist_len is odd. This driver tries + * bypass this with adding one sample to the end of the every scan and discard + * it on output but this can't be used if cmd->scan_begin_src=TRIG_FOLLOW + * and is used flag CMDF_WAKE_EOS, then driver switch to interrupt driven mode + * with interrupt after every sample. + * c) If isn't used DMA then you can use only mode where + * cmd->scan_begin_src=TRIG_FOLLOW. + * + * Configuration options: + * [0] - PCI bus of device (optional) + * [1] - PCI slot of device (optional) + * If bus/slot is not specified, then first available PCI + * card will be used. + * [2] - 0= standard 8 DIFF/16 SE channels configuration + * n = external multiplexer connected, 1 <= n <= 256 + * [3] - ignored + * [4] - sample&hold signal - card can generate signal for external S&H board + * 0 = use SSHO(pin 45) signal is generated in onboard hardware S&H logic + * 0 != use ADCHN7(pin 23) signal is generated from driver, number say how + * long delay is requested in ns and sign polarity of the hold + * (in this case external multiplexor can serve only 128 channels) + * [5] - ignored + */ + +/* + * FIXME + * + * All the supported boards have the same PCI vendor and device IDs, so + * auto-attachment of PCI devices will always find the first board type. + * + * Perhaps the boards have different subdevice IDs that we could use to + * distinguish them? + * + * Need some device attributes so the board type can be corrected after + * attachment if necessary, and possibly to set other options supported by + * manual attachment. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8254.h> + +#include "amcc_s5933.h" + +/* + * PCI BAR2 Register map (dev->iobase) + */ +#define PCI9118_TIMER_BASE 0x00 +#define PCI9118_AI_FIFO_REG 0x10 +#define PCI9118_AO_REG(x) (0x10 + ((x) * 4)) +#define PCI9118_AI_STATUS_REG 0x18 +#define PCI9118_AI_STATUS_NFULL BIT(8) /* 0=FIFO full (fatal) */ +#define PCI9118_AI_STATUS_NHFULL BIT(7) /* 0=FIFO half full */ +#define PCI9118_AI_STATUS_NEPTY BIT(6) /* 0=FIFO empty */ +#define PCI9118_AI_STATUS_ACMP BIT(5) /* 1=about trigger complete */ +#define PCI9118_AI_STATUS_DTH BIT(4) /* 1=ext. digital trigger */ +#define PCI9118_AI_STATUS_BOVER BIT(3) /* 1=burst overrun (fatal) */ +#define PCI9118_AI_STATUS_ADOS BIT(2) /* 1=A/D over speed (warn) */ +#define PCI9118_AI_STATUS_ADOR BIT(1) /* 1=A/D overrun (fatal) */ +#define PCI9118_AI_STATUS_ADRDY BIT(0) /* 1=A/D ready */ +#define PCI9118_AI_CTRL_REG 0x18 +#define PCI9118_AI_CTRL_UNIP BIT(7) /* 1=unipolar */ +#define PCI9118_AI_CTRL_DIFF BIT(6) /* 1=differential inputs */ +#define PCI9118_AI_CTRL_SOFTG BIT(5) /* 1=8254 software gate */ +#define PCI9118_AI_CTRL_EXTG BIT(4) /* 1=8254 TGIN(pin 46) gate */ +#define PCI9118_AI_CTRL_EXTM BIT(3) /* 1=ext. trigger (pin 44) */ +#define PCI9118_AI_CTRL_TMRTR BIT(2) /* 1=8254 is trigger source */ +#define PCI9118_AI_CTRL_INT BIT(1) /* 1=enable interrupt */ +#define PCI9118_AI_CTRL_DMA BIT(0) /* 1=enable DMA */ +#define PCI9118_DIO_REG 0x1c +#define PCI9118_SOFTTRG_REG 0x20 +#define PCI9118_AI_CHANLIST_REG 0x24 +#define PCI9118_AI_CHANLIST_RANGE(x) (((x) & 0x3) << 8) +#define PCI9118_AI_CHANLIST_CHAN(x) ((x) << 0) +#define PCI9118_AI_BURST_NUM_REG 0x28 +#define PCI9118_AI_AUTOSCAN_MODE_REG 0x2c +#define PCI9118_AI_CFG_REG 0x30 +#define PCI9118_AI_CFG_PDTRG BIT(7) /* 1=positive trigger */ +#define PCI9118_AI_CFG_PETRG BIT(6) /* 1=positive ext. trigger */ +#define PCI9118_AI_CFG_BSSH BIT(5) /* 1=with sample & hold */ +#define PCI9118_AI_CFG_BM BIT(4) /* 1=burst mode */ +#define PCI9118_AI_CFG_BS BIT(3) /* 1=burst mode start */ +#define PCI9118_AI_CFG_PM BIT(2) /* 1=post trigger */ +#define PCI9118_AI_CFG_AM BIT(1) /* 1=about trigger */ +#define PCI9118_AI_CFG_START BIT(0) /* 1=trigger start */ +#define PCI9118_FIFO_RESET_REG 0x34 +#define PCI9118_INT_CTRL_REG 0x38 +#define PCI9118_INT_CTRL_TIMER BIT(3) /* timer interrupt */ +#define PCI9118_INT_CTRL_ABOUT BIT(2) /* about trigger complete */ +#define PCI9118_INT_CTRL_HFULL BIT(1) /* A/D FIFO half full */ +#define PCI9118_INT_CTRL_DTRG BIT(0) /* ext. digital trigger */ + +#define START_AI_EXT 0x01 /* start measure on external trigger */ +#define STOP_AI_EXT 0x02 /* stop measure on external trigger */ +#define STOP_AI_INT 0x08 /* stop measure on internal trigger */ + +static const struct comedi_lrange pci9118_ai_range = { + 8, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange pci9118hg_ai_range = { + 8, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; + +enum pci9118_boardid { + BOARD_PCI9118DG, + BOARD_PCI9118HG, + BOARD_PCI9118HR, +}; + +struct pci9118_boardinfo { + const char *name; + unsigned int ai_is_16bit:1; + unsigned int is_hg:1; +}; + +static const struct pci9118_boardinfo pci9118_boards[] = { + [BOARD_PCI9118DG] = { + .name = "pci9118dg", + }, + [BOARD_PCI9118HG] = { + .name = "pci9118hg", + .is_hg = 1, + }, + [BOARD_PCI9118HR] = { + .name = "pci9118hr", + .ai_is_16bit = 1, + }, +}; + +struct pci9118_dmabuf { + unsigned short *virt; /* virtual address of buffer */ + dma_addr_t hw; /* hardware (bus) address of buffer */ + unsigned int size; /* size of dma buffer in bytes */ + unsigned int use_size; /* which size we may now use for transfer */ +}; + +struct pci9118_private { + unsigned long iobase_a; /* base+size for AMCC chip */ + unsigned int master:1; + unsigned int dma_doublebuf:1; + unsigned int ai_neverending:1; + unsigned int usedma:1; + unsigned int usemux:1; + unsigned char ai_ctrl; + unsigned char int_ctrl; + unsigned char ai_cfg; + unsigned int ai_do; /* what do AI? 0=nothing, 1 to 4 mode */ + unsigned int ai_n_realscanlen; /* + * what we must transfer for one + * outgoing scan include front/back adds + */ + unsigned int ai_act_dmapos; /* position in actual real stream */ + unsigned int ai_add_front; /* + * how many channels we must add + * before scan to satisfy S&H? + */ + unsigned int ai_add_back; /* + * how many channels we must add + * before scan to satisfy DMA? + */ + unsigned int ai_flags; + char ai12_startstop; /* + * measure can start/stop + * on external trigger + */ + unsigned int dma_actbuf; /* which buffer is used now */ + struct pci9118_dmabuf dmabuf[2]; + int softsshdelay; /* + * >0 use software S&H, + * numer is requested delay in ns + */ + unsigned char softsshsample; /* + * polarity of S&H signal + * in sample state + */ + unsigned char softsshhold; /* + * polarity of S&H signal + * in hold state + */ + unsigned int ai_ns_min; +}; + +static void pci9118_amcc_setup_dma(struct comedi_device *dev, unsigned int buf) +{ + struct pci9118_private *devpriv = dev->private; + struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[buf]; + + /* set the master write address and transfer count */ + outl(dmabuf->hw, devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(dmabuf->use_size, devpriv->iobase_a + AMCC_OP_REG_MWTC); +} + +static void pci9118_amcc_dma_ena(struct comedi_device *dev, bool enable) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int mcsr; + + mcsr = inl(devpriv->iobase_a + AMCC_OP_REG_MCSR); + if (enable) + mcsr |= RESET_A2P_FLAGS | A2P_HI_PRIORITY | EN_A2P_TRANSFERS; + else + mcsr &= ~EN_A2P_TRANSFERS; + outl(mcsr, devpriv->iobase_a + AMCC_OP_REG_MCSR); +} + +static void pci9118_amcc_int_ena(struct comedi_device *dev, bool enable) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int intcsr; + + /* enable/disable interrupt for AMCC Incoming Mailbox 4 (32-bit) */ + intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR); + if (enable) + intcsr |= 0x1f00; + else + intcsr &= ~0x1f00; + outl(intcsr, devpriv->iobase_a + AMCC_OP_REG_INTCSR); +} + +static void pci9118_ai_reset_fifo(struct comedi_device *dev) +{ + /* writing any value resets the A/D FIFO */ + outl(0, dev->iobase + PCI9118_FIFO_RESET_REG); +} + +static int pci9118_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + /* single channel scans are always ok */ + if (cmd->chanlist_len == 1) + return 0; + + 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 (aref != aref0) { + dev_err(dev->class_dev, + "Differential and single ended inputs can't be mixed!\n"); + return -EINVAL; + } + if (comedi_range_is_bipolar(s, range) != + comedi_range_is_bipolar(s, range0)) { + dev_err(dev->class_dev, + "Bipolar and unipolar ranges can't be mixed!\n"); + return -EINVAL; + } + if (!devpriv->usemux && aref == AREF_DIFF && + (chan >= (s->n_chan / 2))) { + dev_err(dev->class_dev, + "AREF_DIFF is only available for the first 8 channels!\n"); + return -EINVAL; + } + } + + return 0; +} + +static void pci9118_set_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + int n_chan, unsigned int *chanlist, + int frontadd, int backadd) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int chan0 = CR_CHAN(chanlist[0]); + unsigned int range0 = CR_RANGE(chanlist[0]); + unsigned int aref0 = CR_AREF(chanlist[0]); + unsigned int ssh = 0x00; + unsigned int val; + int i; + + /* + * Configure analog input based on the first chanlist entry. + * All entries are either unipolar or bipolar and single-ended + * or differential. + */ + devpriv->ai_ctrl = 0; + if (comedi_range_is_unipolar(s, range0)) + devpriv->ai_ctrl |= PCI9118_AI_CTRL_UNIP; + if (aref0 == AREF_DIFF) + devpriv->ai_ctrl |= PCI9118_AI_CTRL_DIFF; + outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG); + + /* gods know why this sequence! */ + outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + + /* insert channels for S&H */ + if (frontadd) { + val = PCI9118_AI_CHANLIST_CHAN(chan0) | + PCI9118_AI_CHANLIST_RANGE(range0); + ssh = devpriv->softsshsample; + for (i = 0; i < frontadd; i++) { + outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG); + ssh = devpriv->softsshhold; + } + } + + /* store chanlist */ + for (i = 0; i < n_chan; i++) { + unsigned int chan = CR_CHAN(chanlist[i]); + unsigned int range = CR_RANGE(chanlist[i]); + + val = PCI9118_AI_CHANLIST_CHAN(chan) | + PCI9118_AI_CHANLIST_RANGE(range); + outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG); + } + + /* insert channels to fit onto 32bit DMA */ + if (backadd) { + val = PCI9118_AI_CHANLIST_CHAN(chan0) | + PCI9118_AI_CHANLIST_RANGE(range0); + for (i = 0; i < backadd; i++) + outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG); + } + /* close scan queue */ + outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + /* udelay(100); important delay, or first sample will be crippled */ +} + +static void pci9118_ai_mode4_switch(struct comedi_device *dev, + unsigned int next_buf) +{ + struct pci9118_private *devpriv = dev->private; + struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[next_buf]; + + devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG | + PCI9118_AI_CFG_AM; + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); + comedi_8254_load(dev->pacer, 0, dmabuf->hw >> 1, + I8254_MODE0 | I8254_BINARY); + devpriv->ai_cfg |= PCI9118_AI_CFG_START; + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); +} + +static unsigned int pci9118_ai_samples_ready(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int n_raw_samples) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int start_pos = devpriv->ai_add_front; + unsigned int stop_pos = start_pos + cmd->chanlist_len; + unsigned int span_len = stop_pos + devpriv->ai_add_back; + unsigned int dma_pos = devpriv->ai_act_dmapos; + unsigned int whole_spans, n_samples, x; + + if (span_len == cmd->chanlist_len) + return n_raw_samples; /* use all samples */ + + /* + * Not all samples are to be used. Buffer contents consist of a + * possibly non-whole number of spans and a region of each span + * is to be used. + * + * Account for samples in whole number of spans. + */ + whole_spans = n_raw_samples / span_len; + n_samples = whole_spans * cmd->chanlist_len; + n_raw_samples -= whole_spans * span_len; + + /* + * Deal with remaining samples which could overlap up to two spans. + */ + while (n_raw_samples) { + if (dma_pos < start_pos) { + /* Skip samples before start position. */ + x = start_pos - dma_pos; + if (x > n_raw_samples) + x = n_raw_samples; + dma_pos += x; + n_raw_samples -= x; + if (!n_raw_samples) + break; + } + if (dma_pos < stop_pos) { + /* Include samples before stop position. */ + x = stop_pos - dma_pos; + if (x > n_raw_samples) + x = n_raw_samples; + n_samples += x; + dma_pos += x; + n_raw_samples -= x; + } + /* Advance to next span. */ + start_pos += span_len; + stop_pos += span_len; + } + return n_samples; +} + +static void pci9118_ai_dma_xfer(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *dma_buffer, + unsigned int n_raw_samples) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int start_pos = devpriv->ai_add_front; + unsigned int stop_pos = start_pos + cmd->chanlist_len; + unsigned int span_len = stop_pos + devpriv->ai_add_back; + unsigned int dma_pos = devpriv->ai_act_dmapos; + unsigned int x; + + if (span_len == cmd->chanlist_len) { + /* All samples are to be copied. */ + comedi_buf_write_samples(s, dma_buffer, n_raw_samples); + dma_pos += n_raw_samples; + } else { + /* + * Not all samples are to be copied. Buffer contents consist + * of a possibly non-whole number of spans and a region of + * each span is to be copied. + */ + while (n_raw_samples) { + if (dma_pos < start_pos) { + /* Skip samples before start position. */ + x = start_pos - dma_pos; + if (x > n_raw_samples) + x = n_raw_samples; + dma_pos += x; + n_raw_samples -= x; + if (!n_raw_samples) + break; + } + if (dma_pos < stop_pos) { + /* Copy samples before stop position. */ + x = stop_pos - dma_pos; + if (x > n_raw_samples) + x = n_raw_samples; + comedi_buf_write_samples(s, dma_buffer, x); + dma_pos += x; + n_raw_samples -= x; + } + /* Advance to next span. */ + start_pos += span_len; + stop_pos += span_len; + } + } + /* Update position in span for next time. */ + devpriv->ai_act_dmapos = dma_pos % span_len; +} + +static void pci9118_exttrg_enable(struct comedi_device *dev, bool enable) +{ + struct pci9118_private *devpriv = dev->private; + + if (enable) + devpriv->int_ctrl |= PCI9118_INT_CTRL_DTRG; + else + devpriv->int_ctrl &= ~PCI9118_INT_CTRL_DTRG; + outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG); + + if (devpriv->int_ctrl) + pci9118_amcc_int_ena(dev, true); + else + pci9118_amcc_int_ena(dev, false); +} + +static void pci9118_calc_divisors(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *tim1, unsigned int *tim2, + unsigned int flags, int chans, + unsigned int *div1, unsigned int *div2, + unsigned int chnsshfront) +{ + struct comedi_8254 *pacer = dev->pacer; + struct comedi_cmd *cmd = &s->async->cmd; + + *div1 = *tim2 / pacer->osc_base; /* convert timer (burst) */ + *div2 = *tim1 / pacer->osc_base; /* scan timer */ + *div2 = *div2 / *div1; /* major timer is c1*c2 */ + if (*div2 < chans) + *div2 = chans; + + *tim2 = *div1 * pacer->osc_base; /* real convert timer */ + + if (cmd->convert_src == TRIG_NOW && !chnsshfront) { + /* use BSSH signal */ + if (*div2 < (chans + 2)) + *div2 = chans + 2; + } + + *tim1 = *div1 * *div2 * pacer->osc_base; +} + +static void pci9118_start_pacer(struct comedi_device *dev, int mode) +{ + if (mode == 1 || mode == 2 || mode == 4) + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); +} + +static int pci9118_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + + if (devpriv->usedma) + pci9118_amcc_dma_ena(dev, false); + pci9118_exttrg_enable(dev, false); + comedi_8254_pacer_enable(dev->pacer, 1, 2, false); + /* set default config (disable burst and triggers) */ + devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG; + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); + /* reset acquisition control */ + devpriv->ai_ctrl = 0; + outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG); + outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG); + /* reset scan queue */ + outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + pci9118_ai_reset_fifo(dev); + + devpriv->int_ctrl = 0; + outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG); + pci9118_amcc_int_ena(dev, false); + + devpriv->ai_do = 0; + devpriv->usedma = 0; + + devpriv->ai_act_dmapos = 0; + s->async->inttrig = NULL; + devpriv->ai_neverending = 0; + devpriv->dma_actbuf = 0; + + return 0; +} + +static void pci9118_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *data, + unsigned int num_bytes, + unsigned int start_chan_index) +{ + struct pci9118_private *devpriv = dev->private; + unsigned short *array = data; + unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes); + unsigned int i; + __be16 *barray = data; + + for (i = 0; i < num_samples; i++) { + if (devpriv->usedma) + array[i] = be16_to_cpu(barray[i]); + if (s->maxdata == 0xffff) + array[i] ^= 0x8000; + else + array[i] = (array[i] >> 4) & 0x0fff; + } +} + +static void pci9118_ai_get_onesample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short sampl; + + sampl = inl(dev->iobase + PCI9118_AI_FIFO_REG); + + comedi_buf_write_samples(s, &sampl, 1); + + if (!devpriv->ai_neverending) { + if (s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + } +} + +static void pci9118_ai_get_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[devpriv->dma_actbuf]; + unsigned int n_all = comedi_bytes_to_samples(s, dmabuf->use_size); + unsigned int n_valid; + bool more_dma; + + /* determine whether more DMA buffers to do after this one */ + n_valid = pci9118_ai_samples_ready(dev, s, n_all); + more_dma = n_valid < comedi_nsamples_left(s, n_valid + 1); + + /* switch DMA buffers and restart DMA if double buffering */ + if (more_dma && devpriv->dma_doublebuf) { + devpriv->dma_actbuf = 1 - devpriv->dma_actbuf; + pci9118_amcc_setup_dma(dev, devpriv->dma_actbuf); + if (devpriv->ai_do == 4) + pci9118_ai_mode4_switch(dev, devpriv->dma_actbuf); + } + + if (n_all) + pci9118_ai_dma_xfer(dev, s, dmabuf->virt, n_all); + + if (!devpriv->ai_neverending) { + if (s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + } + + if (s->async->events & COMEDI_CB_CANCEL_MASK) + more_dma = false; + + /* restart DMA if not double buffering */ + if (more_dma && !devpriv->dma_doublebuf) { + pci9118_amcc_setup_dma(dev, 0); + if (devpriv->ai_do == 4) + pci9118_ai_mode4_switch(dev, 0); + } +} + +static irqreturn_t pci9118_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct pci9118_private *devpriv = dev->private; + unsigned int intsrc; /* IRQ reasons from card */ + unsigned int intcsr; /* INT register from AMCC chip */ + unsigned int adstat; /* STATUS register */ + + if (!dev->attached) + return IRQ_NONE; + + intsrc = inl(dev->iobase + PCI9118_INT_CTRL_REG) & 0xf; + intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR); + + if (!intsrc && !(intcsr & ANY_S593X_INT)) + return IRQ_NONE; + + outl(intcsr | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR); + + if (intcsr & MASTER_ABORT_INT) { + dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n"); + s->async->events |= COMEDI_CB_ERROR; + goto interrupt_exit; + } + + if (intcsr & TARGET_ABORT_INT) { + dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n"); + s->async->events |= COMEDI_CB_ERROR; + goto interrupt_exit; + } + + adstat = inl(dev->iobase + PCI9118_AI_STATUS_REG); + if ((adstat & PCI9118_AI_STATUS_NFULL) == 0) { + dev_err(dev->class_dev, + "A/D FIFO Full status (Fatal Error!)\n"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; + goto interrupt_exit; + } + if (adstat & PCI9118_AI_STATUS_BOVER) { + dev_err(dev->class_dev, + "A/D Burst Mode Overrun Status (Fatal Error!)\n"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; + goto interrupt_exit; + } + if (adstat & PCI9118_AI_STATUS_ADOS) { + dev_err(dev->class_dev, "A/D Over Speed Status (Warning!)\n"); + s->async->events |= COMEDI_CB_ERROR; + goto interrupt_exit; + } + if (adstat & PCI9118_AI_STATUS_ADOR) { + dev_err(dev->class_dev, "A/D Overrun Status (Fatal Error!)\n"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; + goto interrupt_exit; + } + + if (!devpriv->ai_do) + return IRQ_HANDLED; + + if (devpriv->ai12_startstop) { + if ((adstat & PCI9118_AI_STATUS_DTH) && + (intsrc & PCI9118_INT_CTRL_DTRG)) { + /* start/stop of measure */ + if (devpriv->ai12_startstop & START_AI_EXT) { + /* deactivate EXT trigger */ + devpriv->ai12_startstop &= ~START_AI_EXT; + if (!(devpriv->ai12_startstop & STOP_AI_EXT)) + pci9118_exttrg_enable(dev, false); + + /* start pacer */ + pci9118_start_pacer(dev, devpriv->ai_do); + outl(devpriv->ai_ctrl, + dev->iobase + PCI9118_AI_CTRL_REG); + } else if (devpriv->ai12_startstop & STOP_AI_EXT) { + /* deactivate EXT trigger */ + devpriv->ai12_startstop &= ~STOP_AI_EXT; + pci9118_exttrg_enable(dev, false); + + /* on next interrupt measure will stop */ + devpriv->ai_neverending = 0; + } + } + } + + if (devpriv->usedma) + pci9118_ai_get_dma(dev, s); + else + pci9118_ai_get_onesample(dev, s); + +interrupt_exit: + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static void pci9118_ai_cmd_start(struct comedi_device *dev) +{ + struct pci9118_private *devpriv = dev->private; + + outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG); + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); + if (devpriv->ai_do != 3) { + pci9118_start_pacer(dev, devpriv->ai_do); + devpriv->ai_ctrl |= PCI9118_AI_CTRL_SOFTG; + } + outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG); +} + +static int pci9118_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + s->async->inttrig = NULL; + pci9118_ai_cmd_start(dev); + + return 1; +} + +static int pci9118_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + struct pci9118_dmabuf *dmabuf0 = &devpriv->dmabuf[0]; + struct pci9118_dmabuf *dmabuf1 = &devpriv->dmabuf[1]; + unsigned int dmalen0 = dmabuf0->size; + unsigned int dmalen1 = dmabuf1->size; + unsigned int scan_bytes = devpriv->ai_n_realscanlen * + comedi_bytes_per_sample(s); + + /* isn't output buff smaller that our DMA buff? */ + if (dmalen0 > s->async->prealloc_bufsz) { + /* align to 32bit down */ + dmalen0 = s->async->prealloc_bufsz & ~3L; + } + if (dmalen1 > s->async->prealloc_bufsz) { + /* align to 32bit down */ + dmalen1 = s->async->prealloc_bufsz & ~3L; + } + + /* we want wake up every scan? */ + if (devpriv->ai_flags & CMDF_WAKE_EOS) { + if (dmalen0 < scan_bytes) { + /* uff, too short DMA buffer, disable EOS support! */ + devpriv->ai_flags &= (~CMDF_WAKE_EOS); + dev_info(dev->class_dev, + "WAR: DMA0 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n", + dmalen0, scan_bytes); + } else { + /* short first DMA buffer to one scan */ + dmalen0 = scan_bytes; + if (dmalen0 < 4) { + dev_info(dev->class_dev, + "ERR: DMA0 buf len bug? (%d<4)\n", + dmalen0); + dmalen0 = 4; + } + } + } + if (devpriv->ai_flags & CMDF_WAKE_EOS) { + if (dmalen1 < scan_bytes) { + /* uff, too short DMA buffer, disable EOS support! */ + devpriv->ai_flags &= (~CMDF_WAKE_EOS); + dev_info(dev->class_dev, + "WAR: DMA1 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n", + dmalen1, scan_bytes); + } else { + /* short second DMA buffer to one scan */ + dmalen1 = scan_bytes; + if (dmalen1 < 4) { + dev_info(dev->class_dev, + "ERR: DMA1 buf len bug? (%d<4)\n", + dmalen1); + dmalen1 = 4; + } + } + } + + /* transfer without CMDF_WAKE_EOS */ + if (!(devpriv->ai_flags & CMDF_WAKE_EOS)) { + unsigned int tmp; + + /* if it's possible then align DMA buffers to length of scan */ + tmp = dmalen0; + dmalen0 = (dmalen0 / scan_bytes) * scan_bytes; + dmalen0 &= ~3L; + if (!dmalen0) + dmalen0 = tmp; /* uff. very long scan? */ + tmp = dmalen1; + dmalen1 = (dmalen1 / scan_bytes) * scan_bytes; + dmalen1 &= ~3L; + if (!dmalen1) + dmalen1 = tmp; /* 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) { + unsigned long long scanlen; + + scanlen = (unsigned long long)scan_bytes * + cmd->stop_arg; + + /* fits whole measure into one DMA buffer? */ + if (dmalen0 > scanlen) { + dmalen0 = scanlen; + dmalen0 &= ~3L; + } else { + /* fits whole measure into two DMA buffer? */ + if (dmalen1 > (scanlen - dmalen0)) { + dmalen1 = scanlen - dmalen0; + dmalen1 &= ~3L; + } + } + } + } + + /* these DMA buffer size will be used */ + devpriv->dma_actbuf = 0; + dmabuf0->use_size = dmalen0; + dmabuf1->use_size = dmalen1; + + pci9118_amcc_dma_ena(dev, false); + pci9118_amcc_setup_dma(dev, 0); + /* init DMA transfer */ + outl(0x00000000 | AINT_WRITE_COMPL, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); +/* outl(0x02000000|AINT_WRITE_COMPL, devpriv->iobase_a+AMCC_OP_REG_INTCSR); */ + pci9118_amcc_dma_ena(dev, true); + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | EN_A2P_TRANSFERS, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow bus mastering */ + + return 0; +} + +static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pci9118_private *devpriv = dev->private; + struct comedi_8254 *pacer = dev->pacer; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int addchans = 0; + unsigned int scanlen; + + devpriv->ai12_startstop = 0; + devpriv->ai_flags = cmd->flags; + devpriv->ai_add_front = 0; + devpriv->ai_add_back = 0; + + /* prepare for start/stop conditions */ + if (cmd->start_src == TRIG_EXT) + devpriv->ai12_startstop |= START_AI_EXT; + if (cmd->stop_src == TRIG_EXT) { + devpriv->ai_neverending = 1; + devpriv->ai12_startstop |= STOP_AI_EXT; + } + if (cmd->stop_src == TRIG_NONE) + devpriv->ai_neverending = 1; + if (cmd->stop_src == TRIG_COUNT) + devpriv->ai_neverending = 0; + + /* + * use additional sample at end of every scan + * to satisty DMA 32 bit transfer? + */ + devpriv->ai_add_front = 0; + devpriv->ai_add_back = 0; + if (devpriv->master) { + devpriv->usedma = 1; + if ((cmd->flags & CMDF_WAKE_EOS) && + (cmd->scan_end_arg == 1)) { + if (cmd->convert_src == TRIG_NOW) + devpriv->ai_add_back = 1; + if (cmd->convert_src == TRIG_TIMER) { + devpriv->usedma = 0; + /* + * use INT transfer if scanlist + * have only one channel + */ + } + } + if ((cmd->flags & CMDF_WAKE_EOS) && + (cmd->scan_end_arg & 1) && + (cmd->scan_end_arg > 1)) { + if (cmd->scan_begin_src == TRIG_FOLLOW) { + devpriv->usedma = 0; + /* + * XXX maybe can be corrected to use 16 bit DMA + */ + } else { /* + * well, we must insert one sample + * to end of EOS to meet 32 bit transfer + */ + devpriv->ai_add_back = 1; + } + } + } else { /* interrupt transfer don't need any correction */ + devpriv->usedma = 0; + } + + /* + * we need software S&H signal? + * It adds two samples before every scan as minimum + */ + if (cmd->convert_src == TRIG_NOW && devpriv->softsshdelay) { + devpriv->ai_add_front = 2; + if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) { + /* move it to front */ + devpriv->ai_add_front++; + devpriv->ai_add_back = 0; + } + if (cmd->convert_arg < devpriv->ai_ns_min) + cmd->convert_arg = devpriv->ai_ns_min; + addchans = devpriv->softsshdelay / cmd->convert_arg; + if (devpriv->softsshdelay % cmd->convert_arg) + addchans++; + if (addchans > (devpriv->ai_add_front - 1)) { + /* uff, still short */ + devpriv->ai_add_front = addchans + 1; + if (devpriv->usedma == 1) + if ((devpriv->ai_add_front + + cmd->chanlist_len + + devpriv->ai_add_back) & 1) + devpriv->ai_add_front++; + /* round up to 32 bit */ + } + } + /* well, we now know what must be all added */ + scanlen = devpriv->ai_add_front + cmd->chanlist_len + + devpriv->ai_add_back; + /* + * what we must take from card in real to have cmd->scan_end_arg + * on output? + */ + devpriv->ai_n_realscanlen = scanlen * + (cmd->scan_end_arg / cmd->chanlist_len); + + if (scanlen > s->len_chanlist) { + dev_err(dev->class_dev, + "range/channel list is too long for actual configuration!\n"); + return -EINVAL; + } + + /* + * Configure analog input and load the chanlist. + * The acquisition control bits are enabled later. + */ + pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist, + devpriv->ai_add_front, devpriv->ai_add_back); + + /* Determine acquisition mode and calculate timing */ + devpriv->ai_do = 0; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + /* cascaded timers 1 and 2 are used for convert timing */ + if (cmd->scan_begin_src == TRIG_EXT) + devpriv->ai_do = 4; + else + devpriv->ai_do = 1; + + comedi_8254_cascade_ns_to_timer(pacer, &cmd->convert_arg, + devpriv->ai_flags & + CMDF_ROUND_NEAREST); + comedi_8254_update_divisors(pacer); + + devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR; + + if (!devpriv->usedma) { + devpriv->ai_ctrl |= PCI9118_AI_CTRL_INT; + devpriv->int_ctrl |= PCI9118_INT_CTRL_TIMER; + } + + if (cmd->scan_begin_src == TRIG_EXT) { + struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[0]; + + devpriv->ai_cfg |= PCI9118_AI_CFG_AM; + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); + comedi_8254_load(pacer, 0, dmabuf->hw >> 1, + I8254_MODE0 | I8254_BINARY); + devpriv->ai_cfg |= PCI9118_AI_CFG_START; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src != TRIG_EXT) { + if (!devpriv->usedma) { + dev_err(dev->class_dev, + "cmd->scan_begin_src=TRIG_TIMER works only with bus mastering!\n"); + return -EIO; + } + + /* double timed action */ + devpriv->ai_do = 2; + + pci9118_calc_divisors(dev, s, + &cmd->scan_begin_arg, &cmd->convert_arg, + devpriv->ai_flags, + devpriv->ai_n_realscanlen, + &pacer->divisor1, + &pacer->divisor2, + devpriv->ai_add_front); + + devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR; + devpriv->ai_cfg |= PCI9118_AI_CFG_BM | PCI9118_AI_CFG_BS; + if (cmd->convert_src == TRIG_NOW && !devpriv->softsshdelay) + devpriv->ai_cfg |= PCI9118_AI_CFG_BSSH; + outl(devpriv->ai_n_realscanlen, + dev->iobase + PCI9118_AI_BURST_NUM_REG); + } + + if (cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_EXT) { + /* external trigger conversion */ + devpriv->ai_do = 3; + + devpriv->ai_ctrl |= PCI9118_AI_CTRL_EXTM; + } + + if (devpriv->ai_do == 0) { + dev_err(dev->class_dev, + "Unable to determine acquisition mode! BUG in (*do_cmdtest)?\n"); + return -EINVAL; + } + + if (devpriv->usedma) + devpriv->ai_ctrl |= PCI9118_AI_CTRL_DMA; + + /* set default config (disable burst and triggers) */ + devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG; + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); + udelay(1); + pci9118_ai_reset_fifo(dev); + + /* clear A/D and INT status registers */ + inl(dev->iobase + PCI9118_AI_STATUS_REG); + inl(dev->iobase + PCI9118_INT_CTRL_REG); + + devpriv->ai_act_dmapos = 0; + + if (devpriv->usedma) { + pci9118_ai_setup_dma(dev, s); + + outl(0x02000000 | AINT_WRITE_COMPL, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + } else { + pci9118_amcc_int_ena(dev, true); + } + + /* start async command now or wait for internal trigger */ + if (cmd->start_src == TRIG_NOW) + pci9118_ai_cmd_start(dev); + else if (cmd->start_src == TRIG_INT) + s->async->inttrig = pci9118_ai_inttrig; + + /* enable external trigger for command start/stop */ + if (cmd->start_src == TRIG_EXT || cmd->stop_src == TRIG_EXT) + pci9118_exttrg_enable(dev, true); + + return 0; +} + +static int pci9118_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct pci9118_private *devpriv = dev->private; + int err = 0; + unsigned int flags; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_EXT | TRIG_INT); + + flags = TRIG_FOLLOW; + if (devpriv->master) + flags |= TRIG_TIMER | TRIG_EXT; + err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags); + + flags = TRIG_TIMER | TRIG_EXT; + if (devpriv->master) + flags |= TRIG_NOW; + err |= comedi_check_trigger_src(&cmd->convert_src, flags); + + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, + TRIG_COUNT | TRIG_NONE | TRIG_EXT); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT) + err |= -EINVAL; + + if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) && + (!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW)))) + err |= -EINVAL; + + if ((cmd->scan_begin_src == TRIG_FOLLOW) && + (!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT)))) + err |= -EINVAL; + + if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + case TRIG_EXT: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_INT: + /* start_arg is the internal trigger (any value) */ + break; + } + + if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT)) + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if ((cmd->scan_begin_src == TRIG_TIMER) && + (cmd->convert_src == TRIG_TIMER) && (cmd->scan_end_arg == 1)) { + cmd->scan_begin_src = TRIG_FOLLOW; + cmd->convert_arg = cmd->scan_begin_arg; + cmd->scan_begin_arg = 0; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + devpriv->ai_ns_min); + } + + if (cmd->scan_begin_src == TRIG_EXT) { + if (cmd->scan_begin_arg) { + cmd->scan_begin_arg = 0; + err |= -EINVAL; + err |= comedi_check_trigger_arg_max(&cmd->scan_end_arg, + 65535); + } + } + + if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + devpriv->ai_ns_min); + } + + if (cmd->convert_src == TRIG_EXT) + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + + err |= comedi_check_trigger_arg_min(&cmd->scan_end_arg, + cmd->chanlist_len); + + if ((cmd->scan_end_arg % cmd->chanlist_len)) { + cmd->scan_end_arg = + cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len); + err |= -EINVAL; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) { + arg = cmd->convert_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_NOW) { + if (cmd->convert_arg == 0) { + arg = devpriv->ai_ns_min * + (cmd->scan_end_arg + 2); + } else { + arg = cmd->convert_arg * cmd->chanlist_len; + } + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, arg); + } + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + + if (cmd->chanlist) + err |= pci9118_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int pci9118_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inl(dev->iobase + PCI9118_AI_STATUS_REG); + if (status & PCI9118_AI_STATUS_ADRDY) + return 0; + return -EBUSY; +} + +static void pci9118_ai_start_conv(struct comedi_device *dev) +{ + /* writing any value triggers an A/D conversion */ + outl(0, dev->iobase + PCI9118_SOFTTRG_REG); +} + +static int pci9118_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci9118_private *devpriv = dev->private; + unsigned int val; + int ret; + int i; + + /* + * Configure analog input based on the chanspec. + * Acqusition is software controlled without interrupts. + */ + pci9118_set_chanlist(dev, s, 1, &insn->chanspec, 0, 0); + + /* set default config (disable burst and triggers) */ + devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG; + outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG); + + pci9118_ai_reset_fifo(dev); + + for (i = 0; i < insn->n; i++) { + pci9118_ai_start_conv(dev); + + ret = comedi_timeout(dev, s, insn, pci9118_ai_eoc, 0); + if (ret) + return ret; + + val = inl(dev->iobase + PCI9118_AI_FIFO_REG); + if (s->maxdata == 0xffff) + data[i] = (val & 0xffff) ^ 0x8000; + else + data[i] = (val >> 4) & 0xfff; + } + + return insn->n; +} + +static int pci9118_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outl(val, dev->iobase + PCI9118_AO_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pci9118_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* + * The digital inputs and outputs share the read register. + * bits [7:4] are the digital outputs + * bits [3:0] are the digital inputs + */ + data[1] = inl(dev->iobase + PCI9118_DIO_REG) & 0xf; + + return insn->n; +} + +static int pci9118_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* + * The digital outputs are set with the same register that + * the digital inputs and outputs are read from. But the + * outputs are set with bits [3:0] so we can simply write + * the s->state to set them. + */ + if (comedi_dio_update_state(s, data)) + outl(s->state, dev->iobase + PCI9118_DIO_REG); + + data[1] = s->state; + + return insn->n; +} + +static void pci9118_reset(struct comedi_device *dev) +{ + /* reset analog input subsystem */ + outl(0, dev->iobase + PCI9118_INT_CTRL_REG); + outl(0, dev->iobase + PCI9118_AI_CTRL_REG); + outl(0, dev->iobase + PCI9118_AI_CFG_REG); + pci9118_ai_reset_fifo(dev); + + /* clear any pending interrupts and status */ + inl(dev->iobase + PCI9118_INT_CTRL_REG); + inl(dev->iobase + PCI9118_AI_STATUS_REG); + + /* reset DMA and scan queue */ + outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG); + outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG); + + /* reset analog outputs to 0V */ + outl(2047, dev->iobase + PCI9118_AO_REG(0)); + outl(2047, dev->iobase + PCI9118_AO_REG(1)); +} + +static struct pci_dev *pci9118_find_pci(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + int bus = it->options[0]; + int slot = it->options[1]; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor != PCI_VENDOR_ID_AMCC) + continue; + if (pcidev->device != 0x80d9) + continue; + if (bus || slot) { + /* requested particular bus/slot */ + if (pcidev->bus->number != bus || + PCI_SLOT(pcidev->devfn) != slot) + continue; + } + return pcidev; + } + dev_err(dev->class_dev, + "no supported board found! (req. bus/slot : %d/%d)\n", + bus, slot); + return NULL; +} + +static void pci9118_alloc_dma(struct comedi_device *dev) +{ + struct pci9118_private *devpriv = dev->private; + struct pci9118_dmabuf *dmabuf; + int order; + int i; + + for (i = 0; i < 2; i++) { + dmabuf = &devpriv->dmabuf[i]; + for (order = 2; order >= 0; order--) { + dmabuf->virt = + dma_alloc_coherent(dev->hw_dev, PAGE_SIZE << order, + &dmabuf->hw, GFP_KERNEL); + if (dmabuf->virt) + break; + } + if (!dmabuf->virt) + break; + dmabuf->size = PAGE_SIZE << order; + + if (i == 0) + devpriv->master = 1; + if (i == 1) + devpriv->dma_doublebuf = 1; + } +} + +static void pci9118_free_dma(struct comedi_device *dev) +{ + struct pci9118_private *devpriv = dev->private; + struct pci9118_dmabuf *dmabuf; + int i; + + if (!devpriv) + return; + + for (i = 0; i < 2; i++) { + dmabuf = &devpriv->dmabuf[i]; + if (dmabuf->virt) { + dma_free_coherent(dev->hw_dev, dmabuf->size, + dmabuf->virt, dmabuf->hw); + } + } +} + +static int pci9118_common_attach(struct comedi_device *dev, + int ext_mux, int softsshdelay) +{ + const struct pci9118_boardinfo *board = dev->board_ptr; + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pci9118_private *devpriv; + struct comedi_subdevice *s; + int ret; + int i; + u16 u16w; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + pci_set_master(pcidev); + + devpriv->iobase_a = pci_resource_start(pcidev, 0); + dev->iobase = pci_resource_start(pcidev, 2); + + dev->pacer = comedi_8254_init(dev->iobase + PCI9118_TIMER_BASE, + I8254_OSC_BASE_4MHZ, I8254_IO32, 0); + if (!dev->pacer) + return -ENOMEM; + + pci9118_reset(dev); + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, pci9118_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) { + dev->irq = pcidev->irq; + + pci9118_alloc_dma(dev); + } + } + + if (ext_mux > 0) { + if (ext_mux > 256) + ext_mux = 256; /* max 256 channels! */ + if (softsshdelay > 0) + if (ext_mux > 128) + ext_mux = 128; + devpriv->usemux = 1; + } else { + devpriv->usemux = 0; + } + + if (softsshdelay < 0) { + /* select sample&hold signal polarity */ + devpriv->softsshdelay = -softsshdelay; + devpriv->softsshsample = 0x80; + devpriv->softsshhold = 0x00; + } else { + devpriv->softsshdelay = softsshdelay; + devpriv->softsshsample = 0x00; + devpriv->softsshhold = 0x80; + } + + pci_read_config_word(pcidev, PCI_COMMAND, &u16w); + pci_write_config_word(pcidev, PCI_COMMAND, u16w | 64); + /* Enable parity check for parity error */ + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + s->n_chan = (devpriv->usemux) ? ext_mux : 16; + s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff; + s->range_table = board->is_hg ? &pci9118hg_ai_range + : &pci9118_ai_range; + s->insn_read = pci9118_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 255; + s->do_cmdtest = pci9118_ai_cmdtest; + s->do_cmd = pci9118_ai_cmd; + s->cancel = pci9118_ai_cancel; + s->munge = pci9118_ai_munge; + } + + if (s->maxdata == 0xffff) { + /* + * 16-bit samples are from an ADS7805 A/D converter. + * Minimum sampling rate is 10us. + */ + devpriv->ai_ns_min = 10000; + } else { + /* + * 12-bit samples are from an ADS7800 A/D converter. + * Minimum sampling rate is 3us. + */ + devpriv->ai_ns_min = 3000; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &range_bipolar10; + s->insn_write = pci9118_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* the analog outputs were reset to 0V, make the readback match */ + for (i = 0; i < s->n_chan; i++) + s->readback[i] = 2047; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci9118_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci9118_do_insn_bits; + + /* get the current state of the digital outputs */ + s->state = inl(dev->iobase + PCI9118_DIO_REG) >> 4; + + return 0; +} + +static int pci9118_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci_dev *pcidev; + int ext_mux, softsshdelay; + + ext_mux = it->options[2]; + softsshdelay = it->options[4]; + + pcidev = pci9118_find_pci(dev, it); + if (!pcidev) + return -EIO; + comedi_set_hw_dev(dev, &pcidev->dev); + + return pci9118_common_attach(dev, ext_mux, softsshdelay); +} + +static int pci9118_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct pci9118_boardinfo *board = NULL; + + if (context < ARRAY_SIZE(pci9118_boards)) + board = &pci9118_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + /* + * Need to 'get' the PCI device to match the 'put' in pci9118_detach(). + * (The 'put' also matches the implicit 'get' by pci9118_find_pci().) + */ + pci_dev_get(pcidev); + /* no external mux, no sample-hold delay */ + return pci9118_common_attach(dev, 0, 0); +} + +static void pci9118_detach(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + + if (dev->iobase) + pci9118_reset(dev); + comedi_pci_detach(dev); + pci9118_free_dma(dev); + pci_dev_put(pcidev); +} + +static struct comedi_driver adl_pci9118_driver = { + .driver_name = "adl_pci9118", + .module = THIS_MODULE, + .attach = pci9118_attach, + .auto_attach = pci9118_auto_attach, + .detach = pci9118_detach, + .num_names = ARRAY_SIZE(pci9118_boards), + .board_name = &pci9118_boards[0].name, + .offset = sizeof(struct pci9118_boardinfo), +}; + +static int adl_pci9118_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci9118_driver, + id->driver_data); +} + +/* FIXME: All the supported board types have the same device ID! */ +static const struct pci_device_id adl_pci9118_pci_table[] = { + { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118DG }, +/* { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HG }, */ +/* { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HR }, */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adl_pci9118_pci_table); + +static struct pci_driver adl_pci9118_pci_driver = { + .name = "adl_pci9118", + .id_table = adl_pci9118_pci_table, + .probe = adl_pci9118_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci9118_driver, adl_pci9118_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adq12b.c b/drivers/comedi/drivers/adq12b.c new file mode 100644 index 000000000..19d765182 --- /dev/null +++ b/drivers/comedi/drivers/adq12b.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adq12b.c + * Driver for MicroAxial ADQ12-B data acquisition and control card + * written by jeremy theler <thelerg@ib.cnea.gov.ar> + * instituto balseiro + * commission nacional de energia atomica + * universidad nacional de cuyo + * argentina + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * 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 + * + * Configuration options: + * [0] - I/O base address (set with hardware jumpers) + * address jumper JADR + * 0x300 1 (factory default) + * 0x320 2 + * 0x340 3 + * 0x360 4 + * 0x380 5 + * 0x3A0 6 + * [1] - Analog Input unipolar/bipolar selection + * selection option JUB + * bipolar 0 2-3 (factory default) + * unipolar 1 1-2 + * [2] - Analog Input single-ended/differential selection + * selection option JCHA JCHB + * single-ended 0 1-2 1-2 (factory default) + * differential 1 2-3 2-3 + * + * 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 + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedidev.h> + +/* address scheme (page 2.17 of the manual) */ +#define ADQ12B_CTREG 0x00 +#define ADQ12B_CTREG_MSKP BIT(7) /* enable pacer interrupt */ +#define ADQ12B_CTREG_GTP BIT(6) /* enable pacer */ +#define ADQ12B_CTREG_RANGE(x) ((x) << 4) +#define ADQ12B_CTREG_CHAN(x) ((x) << 0) +#define ADQ12B_STINR 0x00 +#define ADQ12B_STINR_OUT2 BIT(7) /* timer 2 output state */ +#define ADQ12B_STINR_OUTP BIT(6) /* pacer output state */ +#define ADQ12B_STINR_EOC BIT(5) /* A/D end-of-conversion */ +#define ADQ12B_STINR_IN_MASK (0x1f << 0) +#define ADQ12B_OUTBR 0x04 +#define ADQ12B_ADLOW 0x08 +#define ADQ12B_ADHIG 0x09 +#define ADQ12B_TIMER_BASE 0x0c + +/* available ranges through the PGA gains */ +static const struct comedi_lrange range_adq12b_ai_bipolar = { + 4, { + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5) + } +}; + +static const struct comedi_lrange range_adq12b_ai_unipolar = { + 4, { + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5) + } +}; + +struct adq12b_private { + unsigned int last_ctreg; +}; + +static int adq12b_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + ADQ12B_STINR); + if (status & ADQ12B_STINR_EOC) + return 0; + return -EBUSY; +} + +static int adq12b_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct adq12b_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int ret; + int i; + + /* change channel and range only if it is different from the previous */ + val = ADQ12B_CTREG_RANGE(range) | ADQ12B_CTREG_CHAN(chan); + if (val != devpriv->last_ctreg) { + outb(val, dev->iobase + ADQ12B_CTREG); + devpriv->last_ctreg = val; + usleep_range(50, 100); /* wait for the mux to settle */ + } + + val = inb(dev->iobase + ADQ12B_ADLOW); /* trigger A/D */ + + for (i = 0; i < insn->n; i++) { + ret = comedi_timeout(dev, s, insn, adq12b_ai_eoc, 0); + if (ret) + return ret; + + val = inb(dev->iobase + ADQ12B_ADHIG) << 8; + val |= inb(dev->iobase + ADQ12B_ADLOW); /* retriggers A/D */ + + data[i] = val; + } + + return insn->n; +} + +static int adq12b_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + /* only bits 0-4 have information about digital inputs */ + data[1] = (inb(dev->iobase + ADQ12B_STINR) & ADQ12B_STINR_IN_MASK); + + return insn->n; +} + +static int adq12b_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + unsigned int chan; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + for (chan = 0; chan < 8; chan++) { + if ((mask >> chan) & 0x01) { + val = (s->state >> chan) & 0x01; + outb((val << 3) | chan, + dev->iobase + ADQ12B_OUTBR); + } + } + } + + data[1] = s->state; + + return insn->n; +} + +static int adq12b_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct adq12b_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->last_ctreg = -1; /* force ctreg update */ + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + if (it->options[2]) { + s->subdev_flags = SDF_READABLE | SDF_DIFF; + s->n_chan = 8; + } else { + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 16; + } + s->maxdata = 0xfff; + s->range_table = it->options[1] ? &range_adq12b_ai_unipolar + : &range_adq12b_ai_bipolar; + s->insn_read = adq12b_ai_insn_read; + + /* Digital Input subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 5; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adq12b_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adq12b_do_insn_bits; + + return 0; +} + +static struct comedi_driver adq12b_driver = { + .driver_name = "adq12b", + .module = THIS_MODULE, + .attach = adq12b_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(adq12b_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adv_pci1710.c b/drivers/comedi/drivers/adv_pci1710.c new file mode 100644 index 000000000..4f2639968 --- /dev/null +++ b/drivers/comedi/drivers/adv_pci1710.c @@ -0,0 +1,962 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * adv_pci1710.c + * Comedi driver for Advantech PCI-1710 series boards + * Author: Michal Dobes <dobes@tesnet.cz> + * + * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn> + * for testing and information. + */ + +/* + * Driver: adv_pci1710 + * Description: Comedi driver for Advantech PCI-1710 series boards + * Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG, PCI-1711, + * PCI-1713, PCI-1731 + * Author: Michal Dobes <dobes@tesnet.cz> + * Updated: Fri, 29 Oct 2015 17:19:35 -0700 + * Status: works + * + * Configuration options: not applicable, uses PCI auto config + * + * 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. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8254.h> + +#include "amcc_s5933.h" + +/* + * PCI BAR2 Register map (dev->iobase) + */ +#define PCI171X_AD_DATA_REG 0x00 /* R: A/D data */ +#define PCI171X_SOFTTRG_REG 0x00 /* W: soft trigger for A/D */ +#define PCI171X_RANGE_REG 0x02 /* W: A/D gain/range register */ +#define PCI171X_RANGE_DIFF BIT(5) +#define PCI171X_RANGE_UNI BIT(4) +#define PCI171X_RANGE_GAIN(x) (((x) & 0x7) << 0) +#define PCI171X_MUX_REG 0x04 /* W: A/D multiplexor control */ +#define PCI171X_MUX_CHANH(x) (((x) & 0xff) << 8) +#define PCI171X_MUX_CHANL(x) (((x) & 0xff) << 0) +#define PCI171X_MUX_CHAN(x) (PCI171X_MUX_CHANH(x) | PCI171X_MUX_CHANL(x)) +#define PCI171X_STATUS_REG 0x06 /* R: status register */ +#define PCI171X_STATUS_IRQ BIT(11) /* 1=IRQ occurred */ +#define PCI171X_STATUS_FF BIT(10) /* 1=FIFO is full, fatal error */ +#define PCI171X_STATUS_FH BIT(9) /* 1=FIFO is half full */ +#define PCI171X_STATUS_FE BIT(8) /* 1=FIFO is empty */ +#define PCI171X_CTRL_REG 0x06 /* W: control register */ +#define PCI171X_CTRL_CNT0 BIT(6) /* 1=ext. clk, 0=int. 100kHz clk */ +#define PCI171X_CTRL_ONEFH BIT(5) /* 1=on FIFO half full, 0=on sample */ +#define PCI171X_CTRL_IRQEN BIT(4) /* 1=enable IRQ */ +#define PCI171X_CTRL_GATE BIT(3) /* 1=enable ext. trigger GATE (8254?) */ +#define PCI171X_CTRL_EXT BIT(2) /* 1=enable ext. trigger source */ +#define PCI171X_CTRL_PACER BIT(1) /* 1=enable int. 8254 trigger source */ +#define PCI171X_CTRL_SW BIT(0) /* 1=enable software trigger source */ +#define PCI171X_CLRINT_REG 0x08 /* W: clear interrupts request */ +#define PCI171X_CLRFIFO_REG 0x09 /* W: clear FIFO */ +#define PCI171X_DA_REG(x) (0x0a + ((x) * 2)) /* W: D/A register */ +#define PCI171X_DAREF_REG 0x0e /* W: D/A reference control */ +#define PCI171X_DAREF(c, r) (((r) & 0x3) << ((c) * 2)) +#define PCI171X_DAREF_MASK(c) PCI171X_DAREF((c), 0x3) +#define PCI171X_DI_REG 0x10 /* R: digital inputs */ +#define PCI171X_DO_REG 0x10 /* W: digital outputs */ +#define PCI171X_TIMER_BASE 0x18 /* R/W: 8254 timer */ + +static const struct comedi_lrange pci1710_ai_range = { + 9, { + BIP_RANGE(5), /* gain 1 (0x00) */ + BIP_RANGE(2.5), /* gain 2 (0x01) */ + BIP_RANGE(1.25), /* gain 4 (0x02) */ + BIP_RANGE(0.625), /* gain 8 (0x03) */ + BIP_RANGE(10), /* gain 0.5 (0x04) */ + UNI_RANGE(10), /* gain 1 (0x00 | UNI) */ + UNI_RANGE(5), /* gain 2 (0x01 | UNI) */ + UNI_RANGE(2.5), /* gain 4 (0x02 | UNI) */ + UNI_RANGE(1.25) /* gain 8 (0x03 | UNI) */ + } +}; + +static const struct comedi_lrange pci1710hg_ai_range = { + 12, { + BIP_RANGE(5), /* gain 1 (0x00) */ + BIP_RANGE(0.5), /* gain 10 (0x01) */ + BIP_RANGE(0.05), /* gain 100 (0x02) */ + BIP_RANGE(0.005), /* gain 1000 (0x03) */ + BIP_RANGE(10), /* gain 0.5 (0x04) */ + BIP_RANGE(1), /* gain 5 (0x05) */ + BIP_RANGE(0.1), /* gain 50 (0x06) */ + BIP_RANGE(0.01), /* gain 500 (0x07) */ + UNI_RANGE(10), /* gain 1 (0x00 | UNI) */ + UNI_RANGE(1), /* gain 10 (0x01 | UNI) */ + UNI_RANGE(0.1), /* gain 100 (0x02 | UNI) */ + UNI_RANGE(0.01) /* gain 1000 (0x03 | UNI) */ + } +}; + +static const struct comedi_lrange pci1711_ai_range = { + 5, { + BIP_RANGE(10), /* gain 1 (0x00) */ + BIP_RANGE(5), /* gain 2 (0x01) */ + BIP_RANGE(2.5), /* gain 4 (0x02) */ + BIP_RANGE(1.25), /* gain 8 (0x03) */ + BIP_RANGE(0.625) /* gain 16 (0x04) */ + } +}; + +static const struct comedi_lrange pci171x_ao_range = { + 3, { + UNI_RANGE(5), /* internal -5V ref */ + UNI_RANGE(10), /* internal -10V ref */ + RANGE_ext(0, 1) /* external -Vref (+/-10V max) */ + } +}; + +enum pci1710_boardid { + BOARD_PCI1710, + BOARD_PCI1710HG, + BOARD_PCI1711, + BOARD_PCI1713, + BOARD_PCI1731, +}; + +struct boardtype { + const char *name; + const struct comedi_lrange *ai_range; + unsigned int is_pci1711:1; + unsigned int is_pci1713:1; + unsigned int has_ao:1; +}; + +static const struct boardtype boardtypes[] = { + [BOARD_PCI1710] = { + .name = "pci1710", + .ai_range = &pci1710_ai_range, + .has_ao = 1, + }, + [BOARD_PCI1710HG] = { + .name = "pci1710hg", + .ai_range = &pci1710hg_ai_range, + .has_ao = 1, + }, + [BOARD_PCI1711] = { + .name = "pci1711", + .ai_range = &pci1711_ai_range, + .is_pci1711 = 1, + .has_ao = 1, + }, + [BOARD_PCI1713] = { + .name = "pci1713", + .ai_range = &pci1710_ai_range, + .is_pci1713 = 1, + }, + [BOARD_PCI1731] = { + .name = "pci1731", + .ai_range = &pci1711_ai_range, + .is_pci1711 = 1, + }, +}; + +struct pci1710_private { + unsigned int max_samples; + unsigned int ctrl; /* control register value */ + unsigned int ctrl_ext; /* used to switch from TRIG_EXT to TRIG_xxx */ + unsigned int mux_scan; /* used to set the channel interval to scan */ + unsigned char ai_et; + 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 char unipolar_gain; /* adjust for unipolar gain codes */ +}; + +static int pci1710_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 pci1710_ai_setup_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, + unsigned int n_chan, + unsigned int seglen) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int first_chan = CR_CHAN(chanlist[0]); + unsigned int last_chan = CR_CHAN(chanlist[seglen - 1]); + unsigned int i; + + for (i = 0; i < seglen; i++) { /* store range list to card */ + unsigned int chan = CR_CHAN(chanlist[i]); + unsigned int range = CR_RANGE(chanlist[i]); + unsigned int aref = CR_AREF(chanlist[i]); + unsigned int rangeval = 0; + + if (aref == AREF_DIFF) + rangeval |= PCI171X_RANGE_DIFF; + if (comedi_range_is_unipolar(s, range)) { + rangeval |= PCI171X_RANGE_UNI; + range -= devpriv->unipolar_gain; + } + rangeval |= PCI171X_RANGE_GAIN(range); + + /* select channel and set range */ + outw(PCI171X_MUX_CHAN(chan), dev->iobase + PCI171X_MUX_REG); + outw(rangeval, dev->iobase + PCI171X_RANGE_REG); + + devpriv->act_chanlist[i] = chan; + } + for ( ; i < n_chan; i++) /* store remainder of channel list */ + devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]); + + /* select channel interval to scan */ + devpriv->mux_scan = PCI171X_MUX_CHANL(first_chan) | + PCI171X_MUX_CHANH(last_chan); + outw(devpriv->mux_scan, dev->iobase + PCI171X_MUX_REG); +} + +static int pci1710_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_REG); + if ((status & PCI171X_STATUS_FE) == 0) + return 0; + return -EBUSY; +} + +static int pci1710_ai_read_sample(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int cur_chan, + unsigned short *val) +{ + const struct boardtype *board = dev->board_ptr; + struct pci1710_private *devpriv = dev->private; + unsigned short sample; + unsigned int chan; + + sample = inw(dev->iobase + PCI171X_AD_DATA_REG); + if (!board->is_pci1713) { + /* + * The upper 4 bits of the 16-bit sample are the channel number + * that the sample was acquired from. Verify that this channel + * number matches the expected channel number. + */ + chan = sample >> 12; + if (chan != devpriv->act_chanlist[cur_chan]) { + dev_err(dev->class_dev, + "A/D data dropout: received from channel %d, expected %d\n", + chan, devpriv->act_chanlist[cur_chan]); + return -ENODATA; + } + } + *val = sample & s->maxdata; + return 0; +} + +static int pci1710_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + int ret = 0; + int i; + + /* enable software trigger */ + devpriv->ctrl |= PCI171X_CTRL_SW; + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + + outb(0, dev->iobase + PCI171X_CLRFIFO_REG); + outb(0, dev->iobase + PCI171X_CLRINT_REG); + + pci1710_ai_setup_chanlist(dev, s, &insn->chanspec, 1, 1); + + for (i = 0; i < insn->n; i++) { + unsigned short val; + + /* start conversion */ + outw(0, dev->iobase + PCI171X_SOFTTRG_REG); + + ret = comedi_timeout(dev, s, insn, pci1710_ai_eoc, 0); + if (ret) + break; + + ret = pci1710_ai_read_sample(dev, s, 0, &val); + if (ret) + break; + + data[i] = val; + } + + /* disable software trigger */ + devpriv->ctrl &= ~PCI171X_CTRL_SW; + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + + outb(0, dev->iobase + PCI171X_CLRFIFO_REG); + outb(0, dev->iobase + PCI171X_CLRINT_REG); + + return ret ? ret : insn->n; +} + +static int pci1710_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + + /* disable A/D triggers and interrupt sources */ + devpriv->ctrl &= PCI171X_CTRL_CNT0; /* preserve counter 0 clk src */ + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + + /* disable pacer */ + comedi_8254_pacer_enable(dev->pacer, 1, 2, false); + + /* clear A/D FIFO and any pending interrutps */ + outb(0, dev->iobase + PCI171X_CLRFIFO_REG); + outb(0, dev->iobase + PCI171X_CLRINT_REG); + + return 0; +} + +static void pci1710_handle_every_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int status; + unsigned short val; + int ret; + + status = inw(dev->iobase + PCI171X_STATUS_REG); + if (status & PCI171X_STATUS_FE) { + dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n", status); + s->async->events |= COMEDI_CB_ERROR; + return; + } + if (status & PCI171X_STATUS_FF) { + dev_dbg(dev->class_dev, + "A/D FIFO Full status (Fatal Error!) (%4x)\n", status); + s->async->events |= COMEDI_CB_ERROR; + return; + } + + outb(0, dev->iobase + PCI171X_CLRINT_REG); + + for (; !(inw(dev->iobase + PCI171X_STATUS_REG) & PCI171X_STATUS_FE);) { + ret = pci1710_ai_read_sample(dev, s, s->async->cur_chan, &val); + if (ret) { + s->async->events |= COMEDI_CB_ERROR; + break; + } + + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + s->async->events |= COMEDI_CB_EOA; + break; + } + } + + outb(0, dev->iobase + PCI171X_CLRINT_REG); +} + +static void pci1710_handle_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status; + int i; + + status = inw(dev->iobase + PCI171X_STATUS_REG); + if (!(status & PCI171X_STATUS_FH)) { + dev_dbg(dev->class_dev, "A/D FIFO not half full!\n"); + async->events |= COMEDI_CB_ERROR; + return; + } + if (status & PCI171X_STATUS_FF) { + dev_dbg(dev->class_dev, + "A/D FIFO Full status (Fatal Error!)\n"); + async->events |= COMEDI_CB_ERROR; + return; + } + + for (i = 0; i < devpriv->max_samples; i++) { + unsigned short val; + int ret; + + ret = pci1710_ai_read_sample(dev, s, s->async->cur_chan, &val); + if (ret) { + s->async->events |= COMEDI_CB_ERROR; + break; + } + + if (!comedi_buf_write_samples(s, &val, 1)) + break; + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + + outb(0, dev->iobase + PCI171X_CLRINT_REG); +} + +static irqreturn_t pci1710_irq_handler(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_REG) & PCI171X_STATUS_IRQ)) + return IRQ_NONE; /* no, exit */ + + if (devpriv->ai_et) { /* Switch from initial TRIG_EXT to TRIG_xxx. */ + devpriv->ai_et = 0; + devpriv->ctrl &= PCI171X_CTRL_CNT0; + devpriv->ctrl |= PCI171X_CTRL_SW; /* set software trigger */ + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + devpriv->ctrl = devpriv->ctrl_ext; + outb(0, dev->iobase + PCI171X_CLRFIFO_REG); + outb(0, dev->iobase + PCI171X_CLRINT_REG); + /* no sample on this interrupt; reset the channel interval */ + outw(devpriv->mux_scan, dev->iobase + PCI171X_MUX_REG); + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + return IRQ_HANDLED; + } + + if (cmd->flags & CMDF_WAKE_EOS) + pci1710_handle_every_sample(dev, s); + else + pci1710_handle_fifo(dev, s); + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int pci1710_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + pci1710_ai_setup_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len, + devpriv->saved_seglen); + + outb(0, dev->iobase + PCI171X_CLRFIFO_REG); + outb(0, dev->iobase + PCI171X_CLRINT_REG); + + devpriv->ctrl &= PCI171X_CTRL_CNT0; + if ((cmd->flags & CMDF_WAKE_EOS) == 0) + devpriv->ctrl |= PCI171X_CTRL_ONEFH; + + if (cmd->convert_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + + devpriv->ctrl |= PCI171X_CTRL_PACER | PCI171X_CTRL_IRQEN; + if (cmd->start_src == TRIG_EXT) { + devpriv->ctrl_ext = devpriv->ctrl; + devpriv->ctrl &= ~(PCI171X_CTRL_PACER | + PCI171X_CTRL_ONEFH | + PCI171X_CTRL_GATE); + devpriv->ctrl |= PCI171X_CTRL_EXT; + devpriv->ai_et = 1; + } else { /* TRIG_NOW */ + devpriv->ai_et = 0; + } + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + + if (cmd->start_src == TRIG_NOW) + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + } else { /* TRIG_EXT */ + devpriv->ctrl |= PCI171X_CTRL_EXT | PCI171X_CTRL_IRQEN; + outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG); + } + + return 0; +} + +static int pci1710_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* step 2a: make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* step 2b: and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); + else /* TRIG_FOLLOW */ + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list */ + + err |= pci1710_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int pci1710_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + devpriv->da_ranges &= ~PCI171X_DAREF_MASK(chan); + devpriv->da_ranges |= PCI171X_DAREF(chan, range); + outw(devpriv->da_ranges, dev->iobase + PCI171X_DAREF_REG); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + PCI171X_DA_REG(chan)); + } + + s->readback[chan] = val; + + return insn->n; +} + +static int pci1710_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inw(dev->iobase + PCI171X_DI_REG); + + return insn->n; +} + +static int pci1710_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + PCI171X_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int pci1710_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + switch (data[1]) { + case 0: /* internal */ + devpriv->ctrl_ext &= ~PCI171X_CTRL_CNT0; + break; + case 1: /* external */ + devpriv->ctrl_ext |= PCI171X_CTRL_CNT0; + break; + default: + return -EINVAL; + } + outw(devpriv->ctrl_ext, dev->iobase + PCI171X_CTRL_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + if (devpriv->ctrl_ext & PCI171X_CTRL_CNT0) { + data[1] = 1; + data[2] = 0; + } else { + data[1] = 0; + data[2] = I8254_OSC_BASE_1MHZ; + } + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static void pci1710_reset(struct comedi_device *dev) +{ + const struct boardtype *board = dev->board_ptr; + + /* + * Disable A/D triggers and interrupt sources, set counter 0 + * to use internal 1 MHz clock. + */ + outw(0, dev->iobase + PCI171X_CTRL_REG); + + /* clear A/D FIFO and any pending interrutps */ + outb(0, dev->iobase + PCI171X_CLRFIFO_REG); + outb(0, dev->iobase + PCI171X_CLRINT_REG); + + if (board->has_ao) { + /* set DACs to 0..5V and outputs to 0V */ + outb(0, dev->iobase + PCI171X_DAREF_REG); + outw(0, dev->iobase + PCI171X_DA_REG(0)); + outw(0, dev->iobase + PCI171X_DA_REG(1)); + } + + /* set digital outputs to 0 */ + outw(0, dev->iobase + PCI171X_DO_REG); +} + +static int pci1710_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct boardtype *board = NULL; + struct pci1710_private *devpriv; + struct comedi_subdevice *s; + int ret, subdev, n_subdevices; + int i; + + if (context < ARRAY_SIZE(boardtypes)) + board = &boardtypes[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + + dev->pacer = comedi_8254_init(dev->iobase + PCI171X_TIMER_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO16, 0); + if (!dev->pacer) + return -ENOMEM; + + n_subdevices = 1; /* all boards have analog inputs */ + if (board->has_ao) + n_subdevices++; + if (!board->is_pci1713) { + /* + * All other boards have digital inputs and outputs as + * well as a user counter. + */ + n_subdevices += 3; + } + + ret = comedi_alloc_subdevices(dev, n_subdevices); + if (ret) + return ret; + + pci1710_reset(dev); + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, pci1710_irq_handler, + IRQF_SHARED, dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + subdev = 0; + + /* Analog Input subdevice */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + if (!board->is_pci1711) + s->subdev_flags |= SDF_DIFF; + s->n_chan = board->is_pci1713 ? 32 : 16; + s->maxdata = 0x0fff; + s->range_table = board->ai_range; + s->insn_read = pci1710_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 = pci1710_ai_cmdtest; + s->do_cmd = pci1710_ai_cmd; + s->cancel = pci1710_ai_cancel; + } + + /* find the value needed to adjust for unipolar gain codes */ + for (i = 0; i < s->range_table->length; i++) { + if (comedi_range_is_unipolar(s, i)) { + devpriv->unipolar_gain = i; + break; + } + } + + if (board->has_ao) { + /* Analog Output subdevice */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &pci171x_ao_range; + s->insn_write = pci1710_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } + + if (!board->is_pci1713) { + /* 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 = pci1710_di_insn_bits; + + /* 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 = pci1710_do_insn_bits; + + /* Counter subdevice (8254) */ + s = &dev->subdevices[subdev++]; + comedi_8254_subdevice_init(s, dev->pacer); + + dev->pacer->insn_config = pci1710_counter_insn_config; + + /* counters 1 and 2 are used internally for the pacer */ + comedi_8254_set_busy(dev->pacer, 1, true); + comedi_8254_set_busy(dev->pacer, 2, true); + } + + /* max_samples is half the FIFO size (2 bytes/sample) */ + devpriv->max_samples = (board->is_pci1711) ? 512 : 2048; + + return 0; +} + +static struct comedi_driver adv_pci1710_driver = { + .driver_name = "adv_pci1710", + .module = THIS_MODULE, + .auto_attach = pci1710_auto_attach, + .detach = comedi_pci_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, 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi: Advantech PCI-1710 Series Multifunction DAS Cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adv_pci1720.c b/drivers/comedi/drivers/adv_pci1720.c new file mode 100644 index 000000000..2619591ba --- /dev/null +++ b/drivers/comedi/drivers/adv_pci1720.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * COMEDI driver for Advantech PCI-1720U + * Copyright (c) 2015 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Separated from the adv_pci1710 driver written by: + * Michal Dobes <dobes@tesnet.cz> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: adv_pci1720 + * Description: 4-channel Isolated D/A Output board + * Devices: [Advantech] PCI-7120U (adv_pci1720) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Fri, 29 Oct 2015 17:19:35 -0700 + * Status: untested + * + * Configuration options: not applicable, uses PCI auto config + * + * The PCI-1720 has 4 isolated 12-bit analog output channels with multiple + * output ranges. It also has a BoardID switch to allow differentiating + * multiple boards in the system. + * + * The analog outputs can operate in two modes, immediate and synchronized. + * This driver currently does not support the synchronized output mode. + * + * Jumpers JP1 to JP4 are used to set the current sink ranges for each + * analog output channel. In order to use the current sink ranges, the + * unipolar 5V range must be used. The voltage output and sink output for + * each channel is available on the connector as separate pins. + * + * Jumper JP5 controls the "hot" reset state of the analog outputs. + * Depending on its setting, the analog outputs will either keep the + * last settings and output values or reset to the default state after + * a "hot" reset. The default state for all channels is uniploar 5V range + * and all the output values are 0V. To allow this feature to work, the + * analog outputs are not "reset" when the driver attaches. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI BAR2 Register map (dev->iobase) + */ +#define PCI1720_AO_LSB_REG(x) (0x00 + ((x) * 2)) +#define PCI1720_AO_MSB_REG(x) (0x01 + ((x) * 2)) +#define PCI1720_AO_RANGE_REG 0x08 +#define PCI1720_AO_RANGE(c, r) (((r) & 0x3) << ((c) * 2)) +#define PCI1720_AO_RANGE_MASK(c) PCI1720_AO_RANGE((c), 0x3) +#define PCI1720_SYNC_REG 0x09 +#define PCI1720_SYNC_CTRL_REG 0x0f +#define PCI1720_SYNC_CTRL_SC0 BIT(0) +#define PCI1720_BOARDID_REG 0x14 + +static const struct comedi_lrange pci1720_ao_range = { + 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static int pci1720_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int i; + + /* set the channel range and polarity */ + val = inb(dev->iobase + PCI1720_AO_RANGE_REG); + val &= ~PCI1720_AO_RANGE_MASK(chan); + val |= PCI1720_AO_RANGE(chan, range); + outb(val, dev->iobase + PCI1720_AO_RANGE_REG); + + val = s->readback[chan]; + for (i = 0; i < insn->n; i++) { + val = data[i]; + + outb(val & 0xff, dev->iobase + PCI1720_AO_LSB_REG(chan)); + outb((val >> 8) & 0xff, dev->iobase + PCI1720_AO_MSB_REG(chan)); + + /* conversion time is 2us (500 kHz throughput) */ + usleep_range(2, 100); + } + + s->readback[chan] = val; + + return insn->n; +} + +static int pci1720_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + PCI1720_BOARDID_REG); + + return insn->n; +} + +static int pci1720_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + 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, 2); + if (ret) + return ret; + + /* Analog Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->range_table = &pci1720_ao_range; + s->insn_write = pci1720_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice (BoardID SW1) */ + s = &dev->subdevices[1]; + 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 = pci1720_di_insn_bits; + + /* disable synchronized output, channels update when written */ + outb(0, dev->iobase + PCI1720_SYNC_CTRL_REG); + + return 0; +} + +static struct comedi_driver adv_pci1720_driver = { + .driver_name = "adv_pci1720", + .module = THIS_MODULE, + .auto_attach = pci1720_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adv_pci1720_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adv_pci1720_driver, + id->driver_data); +} + +static const struct pci_device_id adv_pci1720_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1720) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adv_pci1720_pci_table); + +static struct pci_driver adv_pci1720_pci_driver = { + .name = "adv_pci1720", + .id_table = adv_pci1720_pci_table, + .probe = adv_pci1720_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adv_pci1720_driver, adv_pci1720_pci_driver); + +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1720 Analog Output board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adv_pci1723.c b/drivers/comedi/drivers/adv_pci1723.c new file mode 100644 index 000000000..e2aedb152 --- /dev/null +++ b/drivers/comedi/drivers/adv_pci1723.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adv_pci1723.c + * Comedi driver for the Advantech PCI-1723 card. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: adv_pci1723 + * Description: Advantech PCI-1723 + * Author: yonggang <rsmgnu@gmail.com>, Ian Abbott <abbotti@mev.co.uk> + * Devices: [Advantech] PCI-1723 (adv_pci1723) + * Updated: Mon, 14 Apr 2008 15:12:56 +0100 + * Status: works + * + * Configuration Options: not applicable, uses comedi PCI auto config + * + * Subdevice 0 is 8-channel AO, 16-bit, range +/- 10 V. + * + * Subdevice 1 is 16-channel DIO. The channels are configurable as + * input or output in 2 groups (0 to 7, 8 to 15). Configuring any + * channel implicitly configures all channels in the same group. + * + * TODO: + * 1. Add the two milliamp ranges to the AO subdevice (0 to 20 mA, + * 4 to 20 mA). + * 2. Read the initial ranges and values of the AO subdevice at + * start-up instead of reinitializing them. + * 3. Implement calibration. + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI Bar 2 I/O Register map (dev->iobase) + */ +#define PCI1723_AO_REG(x) (0x00 + ((x) * 2)) +#define PCI1723_BOARD_ID_REG 0x10 +#define PCI1723_BOARD_ID_MASK (0xf << 0) +#define PCI1723_SYNC_CTRL_REG 0x12 +#define PCI1723_SYNC_CTRL(x) (((x) & 0x1) << 0) +#define PCI1723_SYNC_CTRL_ASYNC PCI1723_SYNC_CTRL(0) +#define PCI1723_SYNC_CTRL_SYNC PCI1723_SYNC_CTRL(1) +#define PCI1723_CTRL_REG 0x14 +#define PCI1723_CTRL_BUSY BIT(15) +#define PCI1723_CTRL_INIT BIT(14) +#define PCI1723_CTRL_SELF BIT(8) +#define PCI1723_CTRL_IDX(x) (((x) & 0x3) << 6) +#define PCI1723_CTRL_RANGE(x) (((x) & 0x3) << 4) +#define PCI1723_CTRL_SEL(x) (((x) & 0x1) << 3) +#define PCI1723_CTRL_GAIN PCI1723_CTRL_SEL(0) +#define PCI1723_CTRL_OFFSET PCI1723_CTRL_SEL(1) +#define PCI1723_CTRL_CHAN(x) (((x) & 0x7) << 0) +#define PCI1723_CALIB_CTRL_REG 0x16 +#define PCI1723_CALIB_CTRL_CS BIT(2) +#define PCI1723_CALIB_CTRL_DAT BIT(1) +#define PCI1723_CALIB_CTRL_CLK BIT(0) +#define PCI1723_CALIB_STROBE_REG 0x18 +#define PCI1723_DIO_CTRL_REG 0x1a +#define PCI1723_DIO_CTRL_HDIO BIT(1) +#define PCI1723_DIO_CTRL_LDIO BIT(0) +#define PCI1723_DIO_DATA_REG 0x1c +#define PCI1723_CALIB_DATA_REG 0x1e +#define PCI1723_SYNC_STROBE_REG 0x20 +#define PCI1723_RESET_AO_STROBE_REG 0x22 +#define PCI1723_RESET_CALIB_STROBE_REG 0x24 +#define PCI1723_RANGE_STROBE_REG 0x26 +#define PCI1723_VREF_REG 0x28 +#define PCI1723_VREF(x) (((x) & 0x3) << 0) +#define PCI1723_VREF_NEG10V PCI1723_VREF(0) +#define PCI1723_VREF_0V PCI1723_VREF(1) +#define PCI1723_VREF_POS10V PCI1723_VREF(3) + +static int pci1723_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + outw(val, dev->iobase + PCI1723_AO_REG(chan)); + s->readback[chan] = val; + } + + return insn->n; +} + +static int pci1723_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask = (chan < 8) ? 0x00ff : 0xff00; + unsigned short mode = 0x0000; /* assume output */ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + if (!(s->io_bits & 0x00ff)) + mode |= PCI1723_DIO_CTRL_LDIO; /* low byte input */ + if (!(s->io_bits & 0xff00)) + mode |= PCI1723_DIO_CTRL_HDIO; /* high byte input */ + outw(mode, dev->iobase + PCI1723_DIO_CTRL_REG); + + return insn->n; +} + +static int pci1723_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + PCI1723_DIO_DATA_REG); + + data[1] = inw(dev->iobase + PCI1723_DIO_DATA_REG); + + return insn->n; +} + +static int pci1723_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + unsigned int val; + int ret; + int i; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = pci1723_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* synchronously reset all analog outputs to 0V, +/-10V range */ + outw(PCI1723_SYNC_CTRL_SYNC, dev->iobase + PCI1723_SYNC_CTRL_REG); + for (i = 0; i < s->n_chan; i++) { + outw(PCI1723_CTRL_RANGE(0) | PCI1723_CTRL_CHAN(i), + PCI1723_CTRL_REG); + outw(0, dev->iobase + PCI1723_RANGE_STROBE_REG); + + outw(0x8000, dev->iobase + PCI1723_AO_REG(i)); + s->readback[i] = 0x8000; + } + outw(0, dev->iobase + PCI1723_SYNC_STROBE_REG); + + /* disable syncronous control */ + outw(PCI1723_SYNC_CTRL_ASYNC, dev->iobase + PCI1723_SYNC_CTRL_REG); + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = pci1723_dio_insn_config; + s->insn_bits = pci1723_dio_insn_bits; + + /* get initial DIO direction and state */ + val = inw(dev->iobase + PCI1723_DIO_CTRL_REG); + if (!(val & PCI1723_DIO_CTRL_LDIO)) + s->io_bits |= 0x00ff; /* low byte output */ + if (!(val & PCI1723_DIO_CTRL_HDIO)) + s->io_bits |= 0xff00; /* high byte output */ + s->state = inw(dev->iobase + PCI1723_DIO_DATA_REG); + + return 0; +} + +static struct comedi_driver adv_pci1723_driver = { + .driver_name = "adv_pci1723", + .module = THIS_MODULE, + .auto_attach = pci1723_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adv_pci1723_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adv_pci1723_driver, + id->driver_data); +} + +static const struct pci_device_id adv_pci1723_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1723) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adv_pci1723_pci_table); + +static struct pci_driver adv_pci1723_pci_driver = { + .name = "adv_pci1723", + .id_table = adv_pci1723_pci_table, + .probe = adv_pci1723_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adv_pci1723_driver, adv_pci1723_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Advantech PCI-1723 Comedi driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adv_pci1724.c b/drivers/comedi/drivers/adv_pci1724.c new file mode 100644 index 000000000..bb43b7dee --- /dev/null +++ b/drivers/comedi/drivers/adv_pci1724.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adv_pci1724.c + * Comedi driver for the Advantech PCI-1724U card. + * + * Author: Frank Mori Hess <fmh6jj@gmail.com> + * Copyright (C) 2013 GnuBIO Inc + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: adv_pci1724 + * Description: Advantech PCI-1724U + * Devices: [Advantech] PCI-1724U (adv_pci1724) + * Author: Frank Mori Hess <fmh6jj@gmail.com> + * Updated: 2013-02-09 + * Status: works + * + * Configuration Options: not applicable, uses comedi PCI auto config + * + * Subdevice 0 is the analog output. + * Subdevice 1 is the offset calibration for the analog output. + * Subdevice 2 is the gain calibration for the analog output. + * + * The calibration offset and gains have quite a large effect on the + * analog output, so it is possible to adjust the analog output to + * have an output range significantly different from the board's + * nominal output ranges. For a calibrated +/-10V range, the analog + * output's offset will be set somewhere near mid-range (0x2000) and + * its gain will be near maximum (0x3fff). + * + * There is really no difference between the board's documented 0-20mA + * versus 4-20mA output ranges. To pick one or the other is simply a + * matter of adjusting the offset and gain calibration until the board + * outputs in the desired range. + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI bar 2 Register I/O map (dev->iobase) + */ +#define PCI1724_DAC_CTRL_REG 0x00 +#define PCI1724_DAC_CTRL_GX(x) BIT(20 + ((x) / 8)) +#define PCI1724_DAC_CTRL_CX(x) (((x) % 8) << 16) +#define PCI1724_DAC_CTRL_MODE(x) (((x) & 0x3) << 14) +#define PCI1724_DAC_CTRL_MODE_GAIN PCI1724_DAC_CTRL_MODE(1) +#define PCI1724_DAC_CTRL_MODE_OFFSET PCI1724_DAC_CTRL_MODE(2) +#define PCI1724_DAC_CTRL_MODE_NORMAL PCI1724_DAC_CTRL_MODE(3) +#define PCI1724_DAC_CTRL_MODE_MASK PCI1724_DAC_CTRL_MODE(3) +#define PCI1724_DAC_CTRL_DATA(x) (((x) & 0x3fff) << 0) +#define PCI1724_SYNC_CTRL_REG 0x04 +#define PCI1724_SYNC_CTRL_DACSTAT BIT(1) +#define PCI1724_SYNC_CTRL_SYN BIT(0) +#define PCI1724_EEPROM_CTRL_REG 0x08 +#define PCI1724_SYNC_TRIG_REG 0x0c /* any value works */ +#define PCI1724_BOARD_ID_REG 0x10 +#define PCI1724_BOARD_ID_MASK (0xf << 0) + +static const struct comedi_lrange adv_pci1724_ao_ranges = { + 4, { + BIP_RANGE(10), + RANGE_mA(0, 20), + RANGE_mA(4, 20), + RANGE_unitless(0, 1) + } +}; + +static int adv_pci1724_dac_idle(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inl(dev->iobase + PCI1724_SYNC_CTRL_REG); + if ((status & PCI1724_SYNC_CTRL_DACSTAT) == 0) + return 0; + return -EBUSY; +} + +static int adv_pci1724_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long mode = (unsigned long)s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int ctrl; + int ret; + int i; + + ctrl = PCI1724_DAC_CTRL_GX(chan) | PCI1724_DAC_CTRL_CX(chan) | mode; + + /* turn off synchronous mode */ + outl(0, dev->iobase + PCI1724_SYNC_CTRL_REG); + + for (i = 0; i < insn->n; ++i) { + unsigned int val = data[i]; + + ret = comedi_timeout(dev, s, insn, adv_pci1724_dac_idle, 0); + if (ret) + return ret; + + outl(ctrl | PCI1724_DAC_CTRL_DATA(val), + dev->iobase + PCI1724_DAC_CTRL_REG); + + s->readback[chan] = val; + } + + return insn->n; +} + +static int adv_pci1724_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + unsigned int board_id; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->iobase = pci_resource_start(pcidev, 2); + board_id = inl(dev->iobase + PCI1724_BOARD_ID_REG); + dev_info(dev->class_dev, "board id: %d\n", + board_id & PCI1724_BOARD_ID_MASK); + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Analog Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND; + s->n_chan = 32; + s->maxdata = 0x3fff; + s->range_table = &adv_pci1724_ao_ranges; + s->insn_write = adv_pci1724_insn_write; + s->private = (void *)PCI1724_DAC_CTRL_MODE_NORMAL; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Offset Calibration subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 32; + s->maxdata = 0x3fff; + s->insn_write = adv_pci1724_insn_write; + s->private = (void *)PCI1724_DAC_CTRL_MODE_OFFSET; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Gain Calibration subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 32; + s->maxdata = 0x3fff; + s->insn_write = adv_pci1724_insn_write; + s->private = (void *)PCI1724_DAC_CTRL_MODE_GAIN; + + return comedi_alloc_subdev_readback(s); +} + +static struct comedi_driver adv_pci1724_driver = { + .driver_name = "adv_pci1724", + .module = THIS_MODULE, + .auto_attach = adv_pci1724_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adv_pci1724_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adv_pci1724_driver, + id->driver_data); +} + +static const struct pci_device_id adv_pci1724_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table); + +static struct pci_driver adv_pci1724_pci_driver = { + .name = "adv_pci1724", + .id_table = adv_pci1724_pci_table, + .probe = adv_pci1724_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver); + +MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>"); +MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adv_pci1760.c b/drivers/comedi/drivers/adv_pci1760.c new file mode 100644 index 000000000..27f3890f4 --- /dev/null +++ b/drivers/comedi/drivers/adv_pci1760.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * COMEDI driver for the Advantech PCI-1760 + * Copyright (C) 2015 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Based on the pci1760 support in the adv_pci_dio driver written by: + * Michal Dobes <dobes@tesnet.cz> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: adv_pci1760 + * Description: Advantech PCI-1760 Relay & Isolated Digital Input Card + * Devices: [Advantech] PCI-1760 (adv_pci1760) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Fri, 13 Nov 2015 12:34:00 -0700 + * Status: untested + * + * Configuration Options: not applicable, uses PCI auto config + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI-1760 Register Map + * + * Outgoing Mailbox Bytes + * OMB3: Not used (must be 0) + * OMB2: The command code to the PCI-1760 + * OMB1: The hi byte of the parameter for the command in OMB2 + * OMB0: The lo byte of the parameter for the command in OMB2 + * + * Incoming Mailbox Bytes + * IMB3: The Isolated Digital Input status (updated every 100us) + * IMB2: The current command (matches OMB2 when command is successful) + * IMB1: The hi byte of the feedback data for the command in OMB2 + * IMB0: The lo byte of the feedback data for the command in OMB2 + * + * Interrupt Control/Status + * INTCSR3: Not used (must be 0) + * INTCSR2: The interrupt status (read only) + * INTCSR1: Interrupt enable/disable + * INTCSR0: Not used (must be 0) + */ +#define PCI1760_OMB_REG(x) (0x0c + (x)) +#define PCI1760_IMB_REG(x) (0x1c + (x)) +#define PCI1760_INTCSR_REG(x) (0x38 + (x)) +#define PCI1760_INTCSR1_IRQ_ENA BIT(5) +#define PCI1760_INTCSR2_OMB_IRQ BIT(0) +#define PCI1760_INTCSR2_IMB_IRQ BIT(1) +#define PCI1760_INTCSR2_IRQ_STATUS BIT(6) +#define PCI1760_INTCSR2_IRQ_ASSERTED BIT(7) + +/* PCI-1760 command codes */ +#define PCI1760_CMD_CLR_IMB2 0x00 /* Clears IMB2 */ +#define PCI1760_CMD_SET_DO 0x01 /* Set output state */ +#define PCI1760_CMD_GET_DO 0x02 /* Read output status */ +#define PCI1760_CMD_GET_STATUS 0x07 /* Read current status */ +#define PCI1760_CMD_GET_FW_VER 0x0e /* Read firmware version */ +#define PCI1760_CMD_GET_HW_VER 0x0f /* Read hardware version */ +#define PCI1760_CMD_SET_PWM_HI(x) (0x10 + (x) * 2) /* Set "hi" period */ +#define PCI1760_CMD_SET_PWM_LO(x) (0x11 + (x) * 2) /* Set "lo" period */ +#define PCI1760_CMD_SET_PWM_CNT(x) (0x14 + (x)) /* Set burst count */ +#define PCI1760_CMD_ENA_PWM 0x1f /* Enable PWM outputs */ +#define PCI1760_CMD_ENA_FILT 0x20 /* Enable input filter */ +#define PCI1760_CMD_ENA_PAT_MATCH 0x21 /* Enable input pattern match */ +#define PCI1760_CMD_SET_PAT_MATCH 0x22 /* Set input pattern match */ +#define PCI1760_CMD_ENA_RISE_EDGE 0x23 /* Enable input rising edge */ +#define PCI1760_CMD_ENA_FALL_EDGE 0x24 /* Enable input falling edge */ +#define PCI1760_CMD_ENA_CNT 0x28 /* Enable counter */ +#define PCI1760_CMD_RST_CNT 0x29 /* Reset counter */ +#define PCI1760_CMD_ENA_CNT_OFLOW 0x2a /* Enable counter overflow */ +#define PCI1760_CMD_ENA_CNT_MATCH 0x2b /* Enable counter match */ +#define PCI1760_CMD_SET_CNT_EDGE 0x2c /* Set counter edge */ +#define PCI1760_CMD_GET_CNT 0x2f /* Reads counter value */ +#define PCI1760_CMD_SET_HI_SAMP(x) (0x30 + (x)) /* Set "hi" sample time */ +#define PCI1760_CMD_SET_LO_SAMP(x) (0x38 + (x)) /* Set "lo" sample time */ +#define PCI1760_CMD_SET_CNT(x) (0x40 + (x)) /* Set counter reset val */ +#define PCI1760_CMD_SET_CNT_MATCH(x) (0x48 + (x)) /* Set counter match val */ +#define PCI1760_CMD_GET_INT_FLAGS 0x60 /* Read interrupt flags */ +#define PCI1760_CMD_GET_INT_FLAGS_MATCH BIT(0) +#define PCI1760_CMD_GET_INT_FLAGS_COS BIT(1) +#define PCI1760_CMD_GET_INT_FLAGS_OFLOW BIT(2) +#define PCI1760_CMD_GET_OS 0x61 /* Read edge change flags */ +#define PCI1760_CMD_GET_CNT_STATUS 0x62 /* Read counter oflow/match */ + +#define PCI1760_CMD_TIMEOUT 250 /* 250 usec timeout */ +#define PCI1760_CMD_RETRIES 3 /* limit number of retries */ + +#define PCI1760_PWM_TIMEBASE 100000 /* 1 unit = 100 usec */ + +static int pci1760_send_cmd(struct comedi_device *dev, + unsigned char cmd, unsigned short val) +{ + unsigned long timeout; + + /* send the command and parameter */ + outb(val & 0xff, dev->iobase + PCI1760_OMB_REG(0)); + outb((val >> 8) & 0xff, dev->iobase + PCI1760_OMB_REG(1)); + outb(cmd, dev->iobase + PCI1760_OMB_REG(2)); + outb(0, dev->iobase + PCI1760_OMB_REG(3)); + + /* datasheet says to allow up to 250 usec for the command to complete */ + timeout = jiffies + usecs_to_jiffies(PCI1760_CMD_TIMEOUT); + do { + if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) { + /* command success; return the feedback data */ + return inb(dev->iobase + PCI1760_IMB_REG(0)) | + (inb(dev->iobase + PCI1760_IMB_REG(1)) << 8); + } + cpu_relax(); + } while (time_before(jiffies, timeout)); + + return -EBUSY; +} + +static int pci1760_cmd(struct comedi_device *dev, + unsigned char cmd, unsigned short val) +{ + int repeats; + int ret; + + /* send PCI1760_CMD_CLR_IMB2 between identical commands */ + if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) { + ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0); + if (ret < 0) { + /* timeout? try it once more */ + ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0); + if (ret < 0) + return -ETIMEDOUT; + } + } + + /* datasheet says to keep retrying the command */ + for (repeats = 0; repeats < PCI1760_CMD_RETRIES; repeats++) { + ret = pci1760_send_cmd(dev, cmd, val); + if (ret >= 0) + return ret; + } + + /* command failed! */ + return -ETIMEDOUT; +} + +static int pci1760_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + PCI1760_IMB_REG(3)); + + return insn->n; +} + +static int pci1760_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + if (comedi_dio_update_state(s, data)) { + ret = pci1760_cmd(dev, PCI1760_CMD_SET_DO, s->state); + if (ret < 0) + return ret; + } + + data[1] = s->state; + + return insn->n; +} + +static int pci1760_pwm_ns_to_div(unsigned int flags, unsigned int ns) +{ + unsigned int divisor; + + switch (flags) { + case CMDF_ROUND_NEAREST: + divisor = DIV_ROUND_CLOSEST(ns, PCI1760_PWM_TIMEBASE); + break; + case CMDF_ROUND_UP: + divisor = DIV_ROUND_UP(ns, PCI1760_PWM_TIMEBASE); + break; + case CMDF_ROUND_DOWN: + divisor = ns / PCI1760_PWM_TIMEBASE; + break; + default: + return -EINVAL; + } + + if (divisor < 1) + divisor = 1; + if (divisor > 0xffff) + divisor = 0xffff; + + return divisor; +} + +static int pci1760_pwm_enable(struct comedi_device *dev, + unsigned int chan, bool enable) +{ + int ret; + + ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, PCI1760_CMD_ENA_PWM); + if (ret < 0) + return ret; + + if (enable) + ret |= BIT(chan); + else + ret &= ~BIT(chan); + + return pci1760_cmd(dev, PCI1760_CMD_ENA_PWM, ret); +} + +static int pci1760_pwm_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int hi_div; + int lo_div; + int ret; + + switch (data[0]) { + case INSN_CONFIG_ARM: + ret = pci1760_pwm_enable(dev, chan, false); + if (ret < 0) + return ret; + + if (data[1] > 0xffff) + return -EINVAL; + ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_CNT(chan), data[1]); + if (ret < 0) + return ret; + + ret = pci1760_pwm_enable(dev, chan, true); + if (ret < 0) + return ret; + break; + case INSN_CONFIG_DISARM: + ret = pci1760_pwm_enable(dev, chan, false); + if (ret < 0) + return ret; + break; + case INSN_CONFIG_PWM_OUTPUT: + ret = pci1760_pwm_enable(dev, chan, false); + if (ret < 0) + return ret; + + hi_div = pci1760_pwm_ns_to_div(data[1], data[2]); + lo_div = pci1760_pwm_ns_to_div(data[3], data[4]); + if (hi_div < 0 || lo_div < 0) + return -EINVAL; + if ((hi_div * PCI1760_PWM_TIMEBASE) != data[2] || + (lo_div * PCI1760_PWM_TIMEBASE) != data[4]) { + data[2] = hi_div * PCI1760_PWM_TIMEBASE; + data[4] = lo_div * PCI1760_PWM_TIMEBASE; + return -EAGAIN; + } + ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_HI(chan), hi_div); + if (ret < 0) + return ret; + ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_LO(chan), lo_div); + if (ret < 0) + return ret; + break; + case INSN_CONFIG_GET_PWM_OUTPUT: + hi_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, + PCI1760_CMD_SET_PWM_HI(chan)); + lo_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, + PCI1760_CMD_SET_PWM_LO(chan)); + if (hi_div < 0 || lo_div < 0) + return -ETIMEDOUT; + + data[1] = hi_div * PCI1760_PWM_TIMEBASE; + data[2] = lo_div * PCI1760_PWM_TIMEBASE; + break; + case INSN_CONFIG_GET_PWM_STATUS: + ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, + PCI1760_CMD_ENA_PWM); + if (ret < 0) + return ret; + + data[1] = (ret & BIT(chan)) ? 1 : 0; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static void pci1760_reset(struct comedi_device *dev) +{ + int i; + + /* disable interrupts (intcsr2 is read-only) */ + outb(0, dev->iobase + PCI1760_INTCSR_REG(0)); + outb(0, dev->iobase + PCI1760_INTCSR_REG(1)); + outb(0, dev->iobase + PCI1760_INTCSR_REG(3)); + + /* disable counters */ + pci1760_cmd(dev, PCI1760_CMD_ENA_CNT, 0); + + /* disable overflow interrupts */ + pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_OFLOW, 0); + + /* disable match */ + pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_MATCH, 0); + + /* set match and counter reset values */ + for (i = 0; i < 8; i++) { + pci1760_cmd(dev, PCI1760_CMD_SET_CNT_MATCH(i), 0x8000); + pci1760_cmd(dev, PCI1760_CMD_SET_CNT(i), 0x0000); + } + + /* reset counters to reset values */ + pci1760_cmd(dev, PCI1760_CMD_RST_CNT, 0xff); + + /* set counter count edges */ + pci1760_cmd(dev, PCI1760_CMD_SET_CNT_EDGE, 0); + + /* disable input filters */ + pci1760_cmd(dev, PCI1760_CMD_ENA_FILT, 0); + + /* disable pattern matching */ + pci1760_cmd(dev, PCI1760_CMD_ENA_PAT_MATCH, 0); + + /* set pattern match value */ + pci1760_cmd(dev, PCI1760_CMD_SET_PAT_MATCH, 0); +} + +static int pci1760_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + 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); + + pci1760_reset(dev); + + 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 = pci1760_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci1760_do_insn_bits; + + /* get the current state of the outputs */ + ret = pci1760_cmd(dev, PCI1760_CMD_GET_DO, 0); + if (ret < 0) + return ret; + s->state = ret; + + /* PWM subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_PWM_COUNTER; + s->n_chan = 2; + s->insn_config = pci1760_pwm_insn_config; + + /* Counter subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_UNUSED; + + return 0; +} + +static struct comedi_driver pci1760_driver = { + .driver_name = "adv_pci1760", + .module = THIS_MODULE, + .auto_attach = pci1760_auto_attach, + .detach = comedi_pci_detach, +}; + +static int pci1760_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &pci1760_driver, id->driver_data); +} + +static const struct pci_device_id pci1760_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1760) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, pci1760_pci_table); + +static struct pci_driver pci1760_pci_driver = { + .name = "adv_pci1760", + .id_table = pci1760_pci_table, + .probe = pci1760_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(pci1760_driver, pci1760_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1760"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/adv_pci_dio.c b/drivers/comedi/drivers/adv_pci_dio.c new file mode 100644 index 000000000..efa3e46b5 --- /dev/null +++ b/drivers/comedi/drivers/adv_pci_dio.c @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 Digital I/O Cards + * 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-1761, PCI-1762 + * Author: Michal Dobes <dobes@tesnet.cz> + * Updated: Fri, 25 Aug 2017 07:23:06 +0300 + * Status: untested + * + * Configuration Options: not applicable, uses PCI auto config + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +/* + * Register offset definitions + */ + +/* PCI-1730, PCI-1733, PCI-1736 interrupt control registers */ +#define PCI173X_INT_EN_REG 0x0008 /* R/W: enable/disable */ +#define PCI173X_INT_RF_REG 0x000c /* R/W: falling/rising edge */ +#define PCI173X_INT_FLAG_REG 0x0010 /* R: status */ +#define PCI173X_INT_CLR_REG 0x0010 /* W: clear */ + +#define PCI173X_INT_IDI0 0x01 /* IDI0 edge occurred */ +#define PCI173X_INT_IDI1 0x02 /* IDI1 edge occurred */ +#define PCI173X_INT_DI0 0x04 /* DI0 edge occurred */ +#define PCI173X_INT_DI1 0x08 /* DI1 edge occurred */ + +/* PCI-1739U, PCI-1750, PCI1751 interrupt control registers */ +#define PCI1750_INT_REG 0x20 /* R/W: status/control */ + +/* PCI-1753, PCI-1753E interrupt control registers */ +#define PCI1753_INT_REG(x) (0x10 + (x)) /* R/W: control group 0 to 3 */ +#define PCI1753E_INT_REG(x) (0x30 + (x)) /* R/W: control group 0 to 3 */ + +/* PCI-1754, PCI-1756 interrupt control registers */ +#define PCI1754_INT_REG(x) (0x08 + (x) * 2) /* R/W: control group 0 to 3 */ + +/* PCI-1752, PCI-1756 special registers */ +#define PCI1752_CFC_REG 0x12 /* R/W: channel freeze function */ + +/* PCI-1761 interrupt control registers */ +#define PCI1761_INT_EN_REG 0x03 /* R/W: enable/disable interrupts */ +#define PCI1761_INT_RF_REG 0x04 /* R/W: falling/rising edge */ +#define PCI1761_INT_CLR_REG 0x05 /* R/W: clear interrupts */ + +/* PCI-1762 interrupt control registers */ +#define PCI1762_INT_REG 0x06 /* R/W: status/control */ + +/* maximum number of subdevice descriptions in the boardinfo */ +#define PCI_DIO_MAX_DI_SUBDEVS 2 /* 2 x 8/16/32 input channels max */ +#define PCI_DIO_MAX_DO_SUBDEVS 2 /* 2 x 8/16/32 output channels max */ +#define PCI_DIO_MAX_DIO_SUBDEVG 2 /* 2 x any number of 8255 devices max */ +#define PCI_DIO_MAX_IRQ_SUBDEVS 4 /* 4 x 1 input IRQ channels max */ + +enum pci_dio_boardid { + 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_PCI1761, + TYPE_PCI1762 +}; + +struct diosubd_data { + int chans; /* num of chans or 8255 devices */ + unsigned long addr; /* PCI address offset */ +}; + +struct dio_irq_subd_data { + unsigned short int_en; /* interrupt enable/status bit */ + unsigned long addr; /* PCI address offset */ +}; + +struct dio_boardtype { + const char *name; /* board name */ + int nsubdevs; + struct diosubd_data sdi[PCI_DIO_MAX_DI_SUBDEVS]; + struct diosubd_data sdo[PCI_DIO_MAX_DO_SUBDEVS]; + struct diosubd_data sdio[PCI_DIO_MAX_DIO_SUBDEVG]; + struct dio_irq_subd_data sdirq[PCI_DIO_MAX_IRQ_SUBDEVS]; + unsigned long id_reg; + unsigned long timer_regbase; + unsigned int is_16bit:1; +}; + +static const struct dio_boardtype boardtypes[] = { + [TYPE_PCI1730] = { + .name = "pci1730", + /* DI, IDI, DO, IDO, ID, IRQ_DI0, IRQ_DI1, IRQ_IDI0, IRQ_IDI1 */ + .nsubdevs = 9, + .sdi[0] = { 16, 0x02, }, /* DI 0-15 */ + .sdi[1] = { 16, 0x00, }, /* ISO DI 0-15 */ + .sdo[0] = { 16, 0x02, }, /* DO 0-15 */ + .sdo[1] = { 16, 0x00, }, /* ISO DO 0-15 */ + .id_reg = 0x04, + .sdirq[0] = { PCI173X_INT_DI0, 0x02, }, /* DI 0 */ + .sdirq[1] = { PCI173X_INT_DI1, 0x02, }, /* DI 1 */ + .sdirq[2] = { PCI173X_INT_IDI0, 0x00, }, /* ISO DI 0 */ + .sdirq[3] = { PCI173X_INT_IDI1, 0x00, }, /* ISO DI 1 */ + }, + [TYPE_PCI1733] = { + .name = "pci1733", + .nsubdevs = 2, + .sdi[1] = { 32, 0x00, }, /* ISO DI 0-31 */ + .id_reg = 0x04, + }, + [TYPE_PCI1734] = { + .name = "pci1734", + .nsubdevs = 2, + .sdo[1] = { 32, 0x00, }, /* ISO DO 0-31 */ + .id_reg = 0x04, + }, + [TYPE_PCI1735] = { + .name = "pci1735", + .nsubdevs = 4, + .sdi[0] = { 32, 0x00, }, /* DI 0-31 */ + .sdo[0] = { 32, 0x00, }, /* DO 0-31 */ + .id_reg = 0x08, + .timer_regbase = 0x04, + }, + [TYPE_PCI1736] = { + .name = "pci1736", + .nsubdevs = 3, + .sdi[1] = { 16, 0x00, }, /* ISO DI 0-15 */ + .sdo[1] = { 16, 0x00, }, /* ISO DO 0-15 */ + .id_reg = 0x04, + }, + [TYPE_PCI1739] = { + .name = "pci1739", + .nsubdevs = 3, + .sdio[0] = { 2, 0x00, }, /* 8255 DIO */ + .id_reg = 0x08, + }, + [TYPE_PCI1750] = { + .name = "pci1750", + .nsubdevs = 2, + .sdi[1] = { 16, 0x00, }, /* ISO DI 0-15 */ + .sdo[1] = { 16, 0x00, }, /* ISO DO 0-15 */ + }, + [TYPE_PCI1751] = { + .name = "pci1751", + .nsubdevs = 3, + .sdio[0] = { 2, 0x00, }, /* 8255 DIO */ + .timer_regbase = 0x18, + }, + [TYPE_PCI1752] = { + .name = "pci1752", + .nsubdevs = 3, + .sdo[0] = { 32, 0x00, }, /* DO 0-31 */ + .sdo[1] = { 32, 0x04, }, /* DO 32-63 */ + .id_reg = 0x10, + .is_16bit = 1, + }, + [TYPE_PCI1753] = { + .name = "pci1753", + .nsubdevs = 4, + .sdio[0] = { 4, 0x00, }, /* 8255 DIO */ + }, + [TYPE_PCI1753E] = { + .name = "pci1753e", + .nsubdevs = 8, + .sdio[0] = { 4, 0x00, }, /* 8255 DIO */ + .sdio[1] = { 4, 0x20, }, /* 8255 DIO */ + }, + [TYPE_PCI1754] = { + .name = "pci1754", + .nsubdevs = 3, + .sdi[0] = { 32, 0x00, }, /* DI 0-31 */ + .sdi[1] = { 32, 0x04, }, /* DI 32-63 */ + .id_reg = 0x10, + .is_16bit = 1, + }, + [TYPE_PCI1756] = { + .name = "pci1756", + .nsubdevs = 3, + .sdi[1] = { 32, 0x00, }, /* DI 0-31 */ + .sdo[1] = { 32, 0x04, }, /* DO 0-31 */ + .id_reg = 0x10, + .is_16bit = 1, + }, + [TYPE_PCI1761] = { + .name = "pci1761", + .nsubdevs = 3, + .sdi[1] = { 8, 0x01 }, /* ISO DI 0-7 */ + .sdo[1] = { 8, 0x00 }, /* RELAY DO 0-7 */ + .id_reg = 0x02, + }, + [TYPE_PCI1762] = { + .name = "pci1762", + .nsubdevs = 3, + .sdi[1] = { 16, 0x02, }, /* ISO DI 0-15 */ + .sdo[1] = { 16, 0x00, }, /* ISO DO 0-15 */ + .id_reg = 0x04, + .is_16bit = 1, + }, +}; + +struct pci_dio_dev_private_data { + int boardtype; + int irq_subd; + unsigned short int_ctrl; + unsigned short int_rf; +}; + +struct pci_dio_sd_private_data { + spinlock_t subd_slock; /* spin-lock for cmd_running */ + unsigned long port_offset; + short int cmd_running; +}; + +static void process_irq(struct comedi_device *dev, unsigned int subdev, + unsigned char irqflags) +{ + struct comedi_subdevice *s = &dev->subdevices[subdev]; + struct pci_dio_sd_private_data *sd_priv = s->private; + unsigned long reg = sd_priv->port_offset; + struct comedi_async *async_p = s->async; + + if (async_p) { + unsigned short val = inw(dev->iobase + reg); + + spin_lock(&sd_priv->subd_slock); + if (sd_priv->cmd_running) + comedi_buf_write_samples(s, &val, 1); + spin_unlock(&sd_priv->subd_slock); + comedi_handle_events(dev, s); + } +} + +static irqreturn_t pci_dio_interrupt(int irq, void *p_device) +{ + struct comedi_device *dev = p_device; + struct pci_dio_dev_private_data *dev_private = dev->private; + const struct dio_boardtype *board = dev->board_ptr; + unsigned long cpu_flags; + unsigned char irqflags; + int i; + + if (!dev->attached) { + /* Ignore interrupt before device fully attached. */ + /* Might not even have allocated subdevices yet! */ + return IRQ_NONE; + } + + /* Check if we are source of interrupt */ + spin_lock_irqsave(&dev->spinlock, cpu_flags); + irqflags = inb(dev->iobase + PCI173X_INT_FLAG_REG); + if (!(irqflags & 0x0F)) { + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + return IRQ_NONE; + } + + /* clear all current interrupt flags */ + outb(irqflags, dev->iobase + PCI173X_INT_CLR_REG); + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + + /* check irq subdevice triggers */ + for (i = 0; i < PCI_DIO_MAX_IRQ_SUBDEVS; i++) { + if (irqflags & board->sdirq[i].int_en) + process_irq(dev, dev_private->irq_subd + i, irqflags); + } + + return IRQ_HANDLED; +} + +static int pci_dio_asy_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + /* + * For scan_begin_arg, the trigger number must be 0 and the only + * allowed flags are CR_EDGE and CR_INVERT. CR_EDGE is ignored, + * CR_INVERT sets the trigger to falling edge. + */ + if (cmd->scan_begin_arg & ~(CR_EDGE | CR_INVERT)) { + cmd->scan_begin_arg &= (CR_EDGE | CR_INVERT); + err |= -EINVAL; + } + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int pci_dio_asy_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci_dio_dev_private_data *dev_private = dev->private; + struct pci_dio_sd_private_data *sd_priv = s->private; + const struct dio_boardtype *board = dev->board_ptr; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long cpu_flags; + unsigned short int_en; + + int_en = board->sdirq[s->index - dev_private->irq_subd].int_en; + + spin_lock_irqsave(&dev->spinlock, cpu_flags); + if (cmd->scan_begin_arg & CR_INVERT) + dev_private->int_rf |= int_en; /* falling edge */ + else + dev_private->int_rf &= ~int_en; /* rising edge */ + outb(dev_private->int_rf, dev->iobase + PCI173X_INT_RF_REG); + dev_private->int_ctrl |= int_en; /* enable interrupt source */ + outb(dev_private->int_ctrl, dev->iobase + PCI173X_INT_EN_REG); + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + + spin_lock_irqsave(&sd_priv->subd_slock, cpu_flags); + sd_priv->cmd_running = 1; + spin_unlock_irqrestore(&sd_priv->subd_slock, cpu_flags); + + return 0; +} + +static int pci_dio_asy_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci_dio_dev_private_data *dev_private = dev->private; + struct pci_dio_sd_private_data *sd_priv = s->private; + const struct dio_boardtype *board = dev->board_ptr; + unsigned long cpu_flags; + unsigned short int_en; + + spin_lock_irqsave(&sd_priv->subd_slock, cpu_flags); + sd_priv->cmd_running = 0; + spin_unlock_irqrestore(&sd_priv->subd_slock, cpu_flags); + + int_en = board->sdirq[s->index - dev_private->irq_subd].int_en; + + spin_lock_irqsave(&dev->spinlock, cpu_flags); + dev_private->int_ctrl &= ~int_en; + outb(dev_private->int_ctrl, dev->iobase + PCI173X_INT_EN_REG); + spin_unlock_irqrestore(&dev->spinlock, cpu_flags); + + return 0; +} + +/* same as _insn_bits_di_ because the IRQ-pins are the DI-ports */ +static int pci_dio_insn_bits_dirq_b(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci_dio_sd_private_data *sd_priv = s->private; + unsigned long reg = (unsigned long)sd_priv->port_offset; + unsigned long iobase = dev->iobase + reg; + + data[1] = inb(iobase); + + return insn->n; +} + +static int pci_dio_insn_bits_di_b(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long reg = (unsigned long)s->private; + unsigned long iobase = dev->iobase + reg; + + data[1] = inb(iobase); + if (s->n_chan > 8) + data[1] |= (inb(iobase + 1) << 8); + if (s->n_chan > 16) + data[1] |= (inb(iobase + 2) << 16); + if (s->n_chan > 24) + data[1] |= (inb(iobase + 3) << 24); + + 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) +{ + unsigned long reg = (unsigned long)s->private; + unsigned long iobase = dev->iobase + reg; + + data[1] = inw(iobase); + if (s->n_chan > 16) + data[1] |= (inw(iobase + 2) << 16); + + 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) +{ + unsigned long reg = (unsigned long)s->private; + unsigned long iobase = dev->iobase + reg; + + if (comedi_dio_update_state(s, data)) { + outb(s->state & 0xff, iobase); + if (s->n_chan > 8) + outb((s->state >> 8) & 0xff, iobase + 1); + if (s->n_chan > 16) + outb((s->state >> 16) & 0xff, iobase + 2); + if (s->n_chan > 24) + outb((s->state >> 24) & 0xff, iobase + 3); + } + + 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) +{ + unsigned long reg = (unsigned long)s->private; + unsigned long iobase = dev->iobase + reg; + + if (comedi_dio_update_state(s, data)) { + outw(s->state & 0xffff, iobase); + if (s->n_chan > 16) + outw((s->state >> 16) & 0xffff, iobase + 2); + } + + data[1] = s->state; + + return insn->n; +} + +static int pci_dio_reset(struct comedi_device *dev, unsigned long cardtype) +{ + struct pci_dio_dev_private_data *dev_private = dev->private; + /* disable channel freeze function on the PCI-1752/1756 boards */ + if (cardtype == TYPE_PCI1752 || cardtype == TYPE_PCI1756) + outw(0, dev->iobase + PCI1752_CFC_REG); + + /* disable and clear interrupts */ + switch (cardtype) { + case TYPE_PCI1730: + case TYPE_PCI1733: + case TYPE_PCI1736: + dev_private->int_ctrl = 0x00; + outb(dev_private->int_ctrl, dev->iobase + PCI173X_INT_EN_REG); + /* Reset all 4 Int Flags */ + outb(0x0f, dev->iobase + PCI173X_INT_CLR_REG); + /* Rising Edge => IRQ . On all 4 Pins */ + dev_private->int_rf = 0x00; + outb(dev_private->int_rf, dev->iobase + PCI173X_INT_RF_REG); + break; + case TYPE_PCI1739: + case TYPE_PCI1750: + case TYPE_PCI1751: + outb(0x88, dev->iobase + PCI1750_INT_REG); + break; + case TYPE_PCI1753: + case TYPE_PCI1753E: + outb(0x88, dev->iobase + PCI1753_INT_REG(0)); + outb(0x80, dev->iobase + PCI1753_INT_REG(1)); + outb(0x80, dev->iobase + PCI1753_INT_REG(2)); + outb(0x80, dev->iobase + PCI1753_INT_REG(3)); + if (cardtype == TYPE_PCI1753E) { + outb(0x88, dev->iobase + PCI1753E_INT_REG(0)); + outb(0x80, dev->iobase + PCI1753E_INT_REG(1)); + outb(0x80, dev->iobase + PCI1753E_INT_REG(2)); + outb(0x80, dev->iobase + PCI1753E_INT_REG(3)); + } + break; + case TYPE_PCI1754: + case TYPE_PCI1756: + outw(0x08, dev->iobase + PCI1754_INT_REG(0)); + outw(0x08, dev->iobase + PCI1754_INT_REG(1)); + if (cardtype == TYPE_PCI1754) { + outw(0x08, dev->iobase + PCI1754_INT_REG(2)); + outw(0x08, dev->iobase + PCI1754_INT_REG(3)); + } + break; + case TYPE_PCI1761: + /* disable interrupts */ + outb(0, dev->iobase + PCI1761_INT_EN_REG); + /* clear interrupts */ + outb(0xff, dev->iobase + PCI1761_INT_CLR_REG); + /* set rising edge trigger */ + outb(0, dev->iobase + PCI1761_INT_RF_REG); + break; + case TYPE_PCI1762: + outw(0x0101, dev->iobase + PCI1762_INT_REG); + break; + default: + break; + } + + return 0; +} + +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 *board = NULL; + struct comedi_subdevice *s; + struct pci_dio_dev_private_data *dev_private; + int ret, subdev, i, j; + + if (context < ARRAY_SIZE(boardtypes)) + board = &boardtypes[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; + if (context == TYPE_PCI1736) + dev->iobase = pci_resource_start(pcidev, 0); + else + dev->iobase = pci_resource_start(pcidev, 2); + + dev_private->boardtype = context; + pci_dio_reset(dev, context); + + /* request IRQ if device has irq subdevices */ + if (board->sdirq[0].int_en && pcidev->irq) { + ret = request_irq(pcidev->irq, pci_dio_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, board->nsubdevs); + if (ret) + return ret; + + subdev = 0; + for (i = 0; i < PCI_DIO_MAX_DI_SUBDEVS; i++) { + const struct diosubd_data *d = &board->sdi[i]; + + if (d->chans) { + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = d->chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = board->is_16bit + ? pci_dio_insn_bits_di_w + : pci_dio_insn_bits_di_b; + s->private = (void *)d->addr; + } + } + + for (i = 0; i < PCI_DIO_MAX_DO_SUBDEVS; i++) { + const struct diosubd_data *d = &board->sdo[i]; + + if (d->chans) { + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = d->chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = board->is_16bit + ? pci_dio_insn_bits_do_w + : pci_dio_insn_bits_do_b; + s->private = (void *)d->addr; + + /* reset all outputs to 0 */ + if (board->is_16bit) { + outw(0, dev->iobase + d->addr); + if (s->n_chan > 16) + outw(0, dev->iobase + d->addr + 2); + } else { + outb(0, dev->iobase + d->addr); + if (s->n_chan > 8) + outb(0, dev->iobase + d->addr + 1); + if (s->n_chan > 16) + outb(0, dev->iobase + d->addr + 2); + if (s->n_chan > 24) + outb(0, dev->iobase + d->addr + 3); + } + } + } + + for (i = 0; i < PCI_DIO_MAX_DIO_SUBDEVG; i++) { + const struct diosubd_data *d = &board->sdio[i]; + + for (j = 0; j < d->chans; j++) { + s = &dev->subdevices[subdev++]; + ret = subdev_8255_init(dev, s, NULL, + d->addr + j * I8255_SIZE); + if (ret) + return ret; + } + } + + if (board->id_reg) { + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = board->is_16bit ? pci_dio_insn_bits_di_w + : pci_dio_insn_bits_di_b; + s->private = (void *)board->id_reg; + } + + if (board->timer_regbase) { + s = &dev->subdevices[subdev++]; + + dev->pacer = comedi_8254_init(dev->iobase + + board->timer_regbase, + 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + comedi_8254_subdevice_init(s, dev->pacer); + } + + dev_private->irq_subd = subdev; /* first interrupt subdevice index */ + for (i = 0; i < PCI_DIO_MAX_IRQ_SUBDEVS; ++i) { + struct pci_dio_sd_private_data *sd_priv = NULL; + const struct dio_irq_subd_data *d = &board->sdirq[i]; + + if (d->int_en) { + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci_dio_insn_bits_dirq_b; + sd_priv = comedi_alloc_spriv(s, sizeof(*sd_priv)); + if (!sd_priv) + return -ENOMEM; + + spin_lock_init(&sd_priv->subd_slock); + sd_priv->port_offset = d->addr; + sd_priv->cmd_running = 0; + + if (dev->irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->len_chanlist = 1; + s->do_cmdtest = pci_dio_asy_cmdtest; + s->do_cmd = pci_dio_asy_cmd; + s->cancel = pci_dio_asy_cancel; + } + } + } + + return 0; +} + +static void pci_dio_detach(struct comedi_device *dev) +{ + struct pci_dio_dev_private_data *dev_private = dev->private; + int boardtype = dev_private->boardtype; + + if (dev->iobase) + pci_dio_reset(dev, boardtype); + comedi_pci_detach(dev); +} + +static struct comedi_driver adv_pci_dio_driver = { + .driver_name = "adv_pci_dio", + .module = THIS_MODULE, + .auto_attach = pci_dio_auto_attach, + .detach = pci_dio_detach, +}; + +static 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, 2, "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, 2) + 53; + + outb(0x05, reg); + if ((inb(reg) & 0x07) == 0x02) { + outb(0x02, reg); + if ((inb(reg) & 0x07) == 0x05) + cardtype = TYPE_PCI1753E; + } + pci_release_region(pcidev, 2); + } + pci_disable_device(pcidev); + return cardtype; +} + +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, 0x1761), TYPE_PCI1761 }, + { 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Advantech Digital I/O Cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/aio_aio12_8.c b/drivers/comedi/drivers/aio_aio12_8.c new file mode 100644 index 000000000..30b8a3220 --- /dev/null +++ b/drivers/comedi/drivers/aio_aio12_8.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +/* + * 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-4 (aio_ao12_4) + * Status: experimental + * + * Configuration Options: + * [0] - I/O port base address + * + * Notes: + * Only synchronous operations are supported. + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +/* + * Register map + */ +#define AIO12_8_STATUS_REG 0x00 +#define AIO12_8_STATUS_ADC_EOC BIT(7) +#define AIO12_8_STATUS_PORT_C_COS BIT(6) +#define AIO12_8_STATUS_IRQ_ENA BIT(2) +#define AIO12_8_INTERRUPT_REG 0x01 +#define AIO12_8_INTERRUPT_ADC BIT(7) +#define AIO12_8_INTERRUPT_COS BIT(6) +#define AIO12_8_INTERRUPT_COUNTER1 BIT(5) +#define AIO12_8_INTERRUPT_PORT_C3 BIT(4) +#define AIO12_8_INTERRUPT_PORT_C0 BIT(3) +#define AIO12_8_INTERRUPT_ENA BIT(2) +#define AIO12_8_ADC_REG 0x02 +#define AIO12_8_ADC_MODE(x) (((x) & 0x3) << 6) +#define AIO12_8_ADC_MODE_NORMAL AIO12_8_ADC_MODE(0) +#define AIO12_8_ADC_MODE_INT_CLK AIO12_8_ADC_MODE(1) +#define AIO12_8_ADC_MODE_STANDBY AIO12_8_ADC_MODE(2) +#define AIO12_8_ADC_MODE_POWERDOWN AIO12_8_ADC_MODE(3) +#define AIO12_8_ADC_ACQ(x) (((x) & 0x1) << 5) +#define AIO12_8_ADC_ACQ_3USEC AIO12_8_ADC_ACQ(0) +#define AIO12_8_ADC_ACQ_PROGRAM AIO12_8_ADC_ACQ(1) +#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 BIT(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 BIT(1) +#define AIO12_8_TRIGGER_DACTRIG BIT(0) +#define AIO12_8_COS_REG 0x17 +#define AIO12_8_DAC_ENABLE_REG 0x18 +#define AIO12_8_DAC_ENABLE_REF_ENA BIT(0) + +static const struct comedi_lrange aio_aio12_8_range = { + 4, { + UNI_RANGE(5), + BIP_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(10) + } +}; + +struct aio12_8_boardtype { + const char *name; + unsigned int has_ai:1; + unsigned int has_ao:1; +}; + +static const struct aio12_8_boardtype board_types[] = { + { + .name = "aio_aio12_8", + .has_ai = 1, + .has_ao = 1, + }, { + .name = "aio_ai12_8", + .has_ai = 1, + }, { + .name = "aio_ao12_4", + .has_ao = 1, + }, +}; + +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 int val; + unsigned char control; + int ret; + int i; + + /* + * 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 (i = 0; i < insn->n; i++) { + /* 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; + + val = inw(dev->iobase + AIO12_8_ADC_REG) & s->maxdata; + + /* munge bipolar 2's complement data to offset binary */ + if (comedi_range_is_bipolar(s, range)) + val = comedi_offset_munge(s, val); + + data[i] = val; + } + + return insn->n; +} + +static int aio_aio12_8_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + /* enable DACs */ + outb(AIO12_8_DAC_ENABLE_REF_ENA, dev->iobase + AIO12_8_DAC_ENABLE_REG); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + AIO12_8_DAC_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int aio_aio12_8_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case INSN_CONFIG_GET_CLOCK_SRC: + /* + * Channels 0 and 2 have external clock sources. + * Channel 1 has a fixed 1 MHz clock source. + */ + data[0] = 0; + data[1] = (chan == 1) ? I8254_OSC_BASE_1MHZ : 0; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int aio_aio12_8_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct aio12_8_boardtype *board = dev->board_ptr; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 32); + if (ret) + return ret; + + dev->pacer = comedi_8254_init(dev->iobase + AIO12_8_8254_BASE_REG, + 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + if (board->has_ai) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 8; + s->maxdata = 0x0fff; + s->range_table = &aio_aio12_8_range; + s->insn_read = aio_aio12_8_ai_read; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->range_table = &aio_aio12_8_range; + s->insn_write = aio_aio12_8_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital I/O subdevice (8255) */ + s = &dev->subdevices[2]; + ret = subdev_8255_init(dev, s, NULL, AIO12_8_8255_BASE_REG); + if (ret) + return ret; + + /* Counter subdevice (8254) */ + s = &dev->subdevices[3]; + comedi_8254_subdevice_init(s, dev->pacer); + + dev->pacer->insn_config = aio_aio12_8_counter_insn_config; + + 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Access I/O AIO12-8 Analog I/O Board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/aio_iiro_16.c b/drivers/comedi/drivers/aio_iiro_16.c new file mode 100644 index 000000000..b00fab0b8 --- /dev/null +++ b/drivers/comedi/drivers/aio_iiro_16.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aio_iiro_16.c + * Comedi driver for Access I/O Products 104-IIRO-16 board + * Copyright (C) 2006 C&C Technologies, Inc. + */ + +/* + * Driver: aio_iiro_16 + * Description: Access I/O Products PC/104 Isolated Input/Relay Output Board + * Author: Zachary Ware <zach.ware@cctechnol.com> + * Devices: [Access I/O] 104-IIRO-16 (aio_iiro_16) + * Status: experimental + * + * Configuration Options: + * [0] - I/O port base address + * [1] - IRQ (optional) + * + * The board supports interrupts on change of state of the digital inputs. + * The sample data returned by the async command indicates which inputs + * changed state and the current state of the inputs: + * + * Bit 23 - IRQ Enable (1) / Disable (0) + * Bit 17 - Input 8-15 Changed State (1 = Changed, 0 = No Change) + * Bit 16 - Input 0-7 Changed State (1 = Changed, 0 = No Change) + * Bit 15 - Digital input 15 + * ... + * Bit 0 - Digital input 0 + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> + +#define AIO_IIRO_16_RELAY_0_7 0x00 +#define AIO_IIRO_16_INPUT_0_7 0x01 +#define AIO_IIRO_16_IRQ 0x02 +#define AIO_IIRO_16_RELAY_8_15 0x04 +#define AIO_IIRO_16_INPUT_8_15 0x05 +#define AIO_IIRO_16_STATUS 0x07 +#define AIO_IIRO_16_STATUS_IRQE BIT(7) +#define AIO_IIRO_16_STATUS_INPUT_8_15 BIT(1) +#define AIO_IIRO_16_STATUS_INPUT_0_7 BIT(0) + +static unsigned int aio_iiro_16_read_inputs(struct comedi_device *dev) +{ + unsigned int val; + + val = inb(dev->iobase + AIO_IIRO_16_INPUT_0_7); + val |= inb(dev->iobase + AIO_IIRO_16_INPUT_8_15) << 8; + + return val; +} + +static irqreturn_t aio_iiro_16_cos(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + unsigned int val; + + status = inb(dev->iobase + AIO_IIRO_16_STATUS); + if (!(status & AIO_IIRO_16_STATUS_IRQE)) + return IRQ_NONE; + + val = aio_iiro_16_read_inputs(dev); + val |= (status << 16); + + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static void aio_iiro_enable_irq(struct comedi_device *dev, bool enable) +{ + if (enable) + inb(dev->iobase + AIO_IIRO_16_IRQ); + else + outb(0, dev->iobase + AIO_IIRO_16_IRQ); +} + +static int aio_iiro_16_cos_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + aio_iiro_enable_irq(dev, false); + + return 0; +} + +static int aio_iiro_16_cos_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + aio_iiro_enable_irq(dev, true); + + return 0; +} + +static int aio_iiro_16_cos_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int aio_iiro_16_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) { + outb(s->state & 0xff, dev->iobase + AIO_IIRO_16_RELAY_0_7); + outb((s->state >> 8) & 0xff, + dev->iobase + AIO_IIRO_16_RELAY_8_15); + } + + data[1] = s->state; + + return insn->n; +} + +static int aio_iiro_16_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = aio_iiro_16_read_inputs(dev); + + return insn->n; +} + +static int aio_iiro_16_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x8); + if (ret) + return ret; + + aio_iiro_enable_irq(dev, false); + + /* + * Digital input change of state interrupts are optionally supported + * using IRQ 2-7, 10-12, 14, or 15. + */ + if ((1 << it->options[1]) & 0xdcfc) { + ret = request_irq(it->options[1], aio_iiro_16_cos, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Digital Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = aio_iiro_16_do_insn_bits; + + /* get the initial state of the relays */ + s->state = inb(dev->iobase + AIO_IIRO_16_RELAY_0_7) | + (inb(dev->iobase + AIO_IIRO_16_RELAY_8_15) << 8); + + /* Digital Input subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = aio_iiro_16_di_insn_bits; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL; + s->len_chanlist = 1; + s->do_cmdtest = aio_iiro_16_cos_cmdtest; + s->do_cmd = aio_iiro_16_cos_cmd; + s->cancel = aio_iiro_16_cos_cancel; + } + + return 0; +} + +static struct comedi_driver aio_iiro_16_driver = { + .driver_name = "aio_iiro_16", + .module = THIS_MODULE, + .attach = aio_iiro_16_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(aio_iiro_16_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Access I/O Products 104-IIRO-16 board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amcc_s5933.h b/drivers/comedi/drivers/amcc_s5933.h new file mode 100644 index 000000000..f738b91b2 --- /dev/null +++ b/drivers/comedi/drivers/amcc_s5933.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Stuff for AMCC S5933 PCI Controller + * + * Author: Michal Dobes <dobes@tesnet.cz> + * + * Inspirated from general-purpose AMCC S5933 PCI Matchmaker driver + * made by Andrea Cisternino <acister@pcape1.pi.infn.it> + * and as result of espionage from MITE code made by David A. Schleef. + * Thanks to AMCC for their on-line documentation and bus master DMA + * example. + */ + +#ifndef _AMCC_S5933_H_ +#define _AMCC_S5933_H_ + +/****************************************************************************/ +/* AMCC Operation Register Offsets - PCI */ +/****************************************************************************/ + +#define AMCC_OP_REG_OMB1 0x00 +#define AMCC_OP_REG_OMB2 0x04 +#define AMCC_OP_REG_OMB3 0x08 +#define AMCC_OP_REG_OMB4 0x0c +#define AMCC_OP_REG_IMB1 0x10 +#define AMCC_OP_REG_IMB2 0x14 +#define AMCC_OP_REG_IMB3 0x18 +#define AMCC_OP_REG_IMB4 0x1c +#define AMCC_OP_REG_FIFO 0x20 +#define AMCC_OP_REG_MWAR 0x24 +#define AMCC_OP_REG_MWTC 0x28 +#define AMCC_OP_REG_MRAR 0x2c +#define AMCC_OP_REG_MRTC 0x30 +#define AMCC_OP_REG_MBEF 0x34 +#define AMCC_OP_REG_INTCSR 0x38 +#define AMCC_OP_REG_INTCSR_SRC (AMCC_OP_REG_INTCSR + 2) /* INT source */ +#define AMCC_OP_REG_INTCSR_FEC (AMCC_OP_REG_INTCSR + 3) /* FIFO ctrl */ +#define AMCC_OP_REG_MCSR 0x3c +#define AMCC_OP_REG_MCSR_NVDATA (AMCC_OP_REG_MCSR + 2) /* Data in byte 2 */ +#define AMCC_OP_REG_MCSR_NVCMD (AMCC_OP_REG_MCSR + 3) /* Command in byte 3 */ + +#define AMCC_FIFO_DEPTH_DWORD 8 +#define AMCC_FIFO_DEPTH_BYTES (8 * sizeof(u32)) + +/****************************************************************************/ +/* AMCC - PCI Interrupt Control/Status Register */ +/****************************************************************************/ +#define INTCSR_OUTBOX_BYTE(x) ((x) & 0x3) +#define INTCSR_OUTBOX_SELECT(x) (((x) & 0x3) << 2) +#define INTCSR_OUTBOX_EMPTY_INT 0x10 /* enable outbox empty interrupt */ +#define INTCSR_INBOX_BYTE(x) (((x) & 0x3) << 8) +#define INTCSR_INBOX_SELECT(x) (((x) & 0x3) << 10) +#define INTCSR_INBOX_FULL_INT 0x1000 /* enable inbox full interrupt */ +/* read, or write clear inbox full interrupt */ +#define INTCSR_INBOX_INTR_STATUS 0x20000 +/* read only, interrupt asserted */ +#define INTCSR_INTR_ASSERTED 0x800000 + +/****************************************************************************/ +/* AMCC - PCI non-volatile ram command register (byte 3 of AMCC_OP_REG_MCSR) */ +/****************************************************************************/ +#define MCSR_NV_LOAD_LOW_ADDR 0x0 +#define MCSR_NV_LOAD_HIGH_ADDR 0x20 +#define MCSR_NV_WRITE 0x40 +#define MCSR_NV_READ 0x60 +#define MCSR_NV_MASK 0x60 +#define MCSR_NV_ENABLE 0x80 +#define MCSR_NV_BUSY MCSR_NV_ENABLE + +/****************************************************************************/ +/* AMCC Operation Registers Size - PCI */ +/****************************************************************************/ + +#define AMCC_OP_REG_SIZE 64 /* in bytes */ + +/****************************************************************************/ +/* AMCC Operation Register Offsets - Add-on */ +/****************************************************************************/ + +#define AMCC_OP_REG_AIMB1 0x00 +#define AMCC_OP_REG_AIMB2 0x04 +#define AMCC_OP_REG_AIMB3 0x08 +#define AMCC_OP_REG_AIMB4 0x0c +#define AMCC_OP_REG_AOMB1 0x10 +#define AMCC_OP_REG_AOMB2 0x14 +#define AMCC_OP_REG_AOMB3 0x18 +#define AMCC_OP_REG_AOMB4 0x1c +#define AMCC_OP_REG_AFIFO 0x20 +#define AMCC_OP_REG_AMWAR 0x24 +#define AMCC_OP_REG_APTA 0x28 +#define AMCC_OP_REG_APTD 0x2c +#define AMCC_OP_REG_AMRAR 0x30 +#define AMCC_OP_REG_AMBEF 0x34 +#define AMCC_OP_REG_AINT 0x38 +#define AMCC_OP_REG_AGCSTS 0x3c +#define AMCC_OP_REG_AMWTC 0x58 +#define AMCC_OP_REG_AMRTC 0x5c + +/****************************************************************************/ +/* AMCC - Add-on General Control/Status Register */ +/****************************************************************************/ + +#define AGCSTS_CONTROL_MASK 0xfffff000 +#define AGCSTS_NV_ACC_MASK 0xe0000000 +#define AGCSTS_RESET_MASK 0x0e000000 +#define AGCSTS_NV_DA_MASK 0x00ff0000 +#define AGCSTS_BIST_MASK 0x0000f000 +#define AGCSTS_STATUS_MASK 0x000000ff +#define AGCSTS_TCZERO_MASK 0x000000c0 +#define AGCSTS_FIFO_ST_MASK 0x0000003f + +#define AGCSTS_TC_ENABLE 0x10000000 + +#define AGCSTS_RESET_MBFLAGS 0x08000000 +#define AGCSTS_RESET_P2A_FIFO 0x04000000 +#define AGCSTS_RESET_A2P_FIFO 0x02000000 +#define AGCSTS_RESET_FIFOS (AGCSTS_RESET_A2P_FIFO | AGCSTS_RESET_P2A_FIFO) + +#define AGCSTS_A2P_TCOUNT 0x00000080 +#define AGCSTS_P2A_TCOUNT 0x00000040 + +#define AGCSTS_FS_P2A_EMPTY 0x00000020 +#define AGCSTS_FS_P2A_HALF 0x00000010 +#define AGCSTS_FS_P2A_FULL 0x00000008 + +#define AGCSTS_FS_A2P_EMPTY 0x00000004 +#define AGCSTS_FS_A2P_HALF 0x00000002 +#define AGCSTS_FS_A2P_FULL 0x00000001 + +/****************************************************************************/ +/* AMCC - Add-on Interrupt Control/Status Register */ +/****************************************************************************/ + +#define AINT_INT_MASK 0x00ff0000 +#define AINT_SEL_MASK 0x0000ffff +#define AINT_IS_ENSEL_MASK 0x00001f1f + +#define AINT_INT_ASSERTED 0x00800000 +#define AINT_BM_ERROR 0x00200000 +#define AINT_BIST_INT 0x00100000 + +#define AINT_RT_COMPLETE 0x00080000 +#define AINT_WT_COMPLETE 0x00040000 + +#define AINT_OUT_MB_INT 0x00020000 +#define AINT_IN_MB_INT 0x00010000 + +#define AINT_READ_COMPL 0x00008000 +#define AINT_WRITE_COMPL 0x00004000 + +#define AINT_OMB_ENABLE 0x00001000 +#define AINT_OMB_SELECT 0x00000c00 +#define AINT_OMB_BYTE 0x00000300 + +#define AINT_IMB_ENABLE 0x00000010 +#define AINT_IMB_SELECT 0x0000000c +#define AINT_IMB_BYTE 0x00000003 + +/* these are bits from various different registers, needs cleanup XXX */ +/* Enable Bus Mastering */ +#define EN_A2P_TRANSFERS 0x00000400 +/* FIFO Flag Reset */ +#define RESET_A2P_FLAGS 0x04000000L +/* FIFO Relative Priority */ +#define A2P_HI_PRIORITY 0x00000100L +/* Identify Interrupt Sources */ +#define ANY_S593X_INT 0x00800000L +#define READ_TC_INT 0x00080000L +#define WRITE_TC_INT 0x00040000L +#define IN_MB_INT 0x00020000L +#define MASTER_ABORT_INT 0x00100000L +#define TARGET_ABORT_INT 0x00200000L +#define BUS_MASTER_INT 0x00200000L + +#endif diff --git a/drivers/comedi/drivers/amplc_dio200.c b/drivers/comedi/drivers/amplc_dio200.c new file mode 100644 index 000000000..4544bcdd8 --- /dev/null +++ b/drivers/comedi/drivers/amplc_dio200.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/amplc_dio200.c + * + * Driver for Amplicon PC212E, PC214E, PC215E, PC218E, PC272E. + * + * Copyright (C) 2005-2013 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +#include "amplc_dio200.h" + +/* + * Board descriptions. + */ +static const struct dio200_board dio200_isa_boards[] = { + { + .name = "pc212e", + .n_subdevs = 6, + .sdtype = { + sd_8255, sd_8254, sd_8254, sd_8254, sd_8254, sd_intr + }, + .sdinfo = { 0x00, 0x08, 0x0c, 0x10, 0x14, 0x3f }, + .has_int_sce = true, + .has_clk_gat_sce = true, + }, { + .name = "pc214e", + .n_subdevs = 4, + .sdtype = { + sd_8255, sd_8255, sd_8254, sd_intr + }, + .sdinfo = { 0x00, 0x08, 0x10, 0x01 }, + }, { + .name = "pc215e", + .n_subdevs = 5, + .sdtype = { + sd_8255, sd_8255, sd_8254, sd_8254, sd_intr + }, + .sdinfo = { 0x00, 0x08, 0x10, 0x14, 0x3f }, + .has_int_sce = true, + .has_clk_gat_sce = true, + }, { + .name = "pc218e", + .n_subdevs = 7, + .sdtype = { + sd_8254, sd_8254, sd_8255, sd_8254, sd_8254, sd_intr + }, + .sdinfo = { 0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x3f }, + .has_int_sce = true, + .has_clk_gat_sce = true, + }, { + .name = "pc272e", + .n_subdevs = 4, + .sdtype = { + sd_8255, sd_8255, sd_8255, sd_intr + }, + .sdinfo = { 0x00, 0x08, 0x10, 0x3f }, + .has_int_sce = true, + }, +}; + +static int dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x20); + if (ret) + return ret; + + return amplc_dio200_common_attach(dev, it->options[1], 0); +} + +static struct comedi_driver amplc_dio200_driver = { + .driver_name = "amplc_dio200", + .module = THIS_MODULE, + .attach = dio200_attach, + .detach = comedi_legacy_detach, + .board_name = &dio200_isa_boards[0].name, + .offset = sizeof(struct dio200_board), + .num_names = ARRAY_SIZE(dio200_isa_boards), +}; +module_comedi_driver(amplc_dio200_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series ISA DIO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_dio200.h b/drivers/comedi/drivers/amplc_dio200.h new file mode 100644 index 000000000..745baaf94 --- /dev/null +++ b/drivers/comedi/drivers/amplc_dio200.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> + */ + +#ifndef AMPLC_DIO200_H_INCLUDED +#define AMPLC_DIO200_H_INCLUDED + +#include <linux/types.h> + +struct comedi_device; + +/* + * Subdevice types. + */ +enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254, sd_timer }; + +#define DIO200_MAX_SUBDEVS 8 +#define DIO200_MAX_ISNS 6 + +struct dio200_board { + const char *name; + unsigned char mainbar; + unsigned short n_subdevs; /* number of subdevices */ + unsigned char sdtype[DIO200_MAX_SUBDEVS]; /* enum dio200_sdtype */ + unsigned char sdinfo[DIO200_MAX_SUBDEVS]; /* depends on sdtype */ + unsigned int has_int_sce:1; /* has interrupt enable/status reg */ + unsigned int has_clk_gat_sce:1; /* has clock/gate selection registers */ + unsigned int is_pcie:1; /* has enhanced features */ +}; + +int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq, + unsigned long req_irq_flags); + +/* Used by initialization of PCIe boards. */ +void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val); + +#endif diff --git a/drivers/comedi/drivers/amplc_dio200_common.c b/drivers/comedi/drivers/amplc_dio200_common.c new file mode 100644 index 000000000..ff651f2eb --- /dev/null +++ b/drivers/comedi/drivers/amplc_dio200_common.c @@ -0,0 +1,857 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/amplc_dio200_common.c + * + * Common support code for "amplc_dio200" and "amplc_dio200_pci". + * + * Copyright (C) 2005-2013 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> /* only for register defines */ +#include <linux/comedi/comedi_8254.h> + +#include "amplc_dio200.h" + +/* 200 series registers */ +#define DIO200_IO_SIZE 0x20 +#define DIO200_PCIE_IO_SIZE 0x4000 +#define DIO200_CLK_SCE(x) (0x18 + (x)) /* Group X/Y/Z clock sel reg */ +#define DIO200_GAT_SCE(x) (0x1b + (x)) /* Group X/Y/Z gate sel reg */ +#define DIO200_INT_SCE 0x1e /* Interrupt enable/status register */ +/* Extra registers for new PCIe boards */ +#define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */ +#define DIO200_VERSION 0x24 /* Hardware version register */ +#define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */ +#define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */ + +/* + * Functions for constructing value for DIO_200_?CLK_SCE and + * DIO_200_?GAT_SCE registers: + * + * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2. + * 'chan' is the channel: 0, 1 or 2. + * 'source' is the signal source: 0 to 7, or 0 to 31 for "enhanced" boards. + */ +static unsigned char clk_gat_sce(unsigned int which, unsigned int chan, + unsigned int source) +{ + return (which << 5) | (chan << 3) | + ((source & 030) << 3) | (source & 007); +} + +/* + * Periods of the internal clock sources in nanoseconds. + */ +static const unsigned int clock_period[32] = { + [1] = 100, /* 10 MHz */ + [2] = 1000, /* 1 MHz */ + [3] = 10000, /* 100 kHz */ + [4] = 100000, /* 10 kHz */ + [5] = 1000000, /* 1 kHz */ + [11] = 50, /* 20 MHz (enhanced boards) */ + /* clock sources 12 and later reserved for enhanced boards */ +}; + +/* + * Timestamp timer configuration register (for new PCIe boards). + */ +#define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */ +#define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */ +#define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */ + +/* + * Periods of the timestamp timer clock sources in nanoseconds. + */ +static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = { + 1, /* 1 nanosecond (but with 20 ns granularity). */ + 1000, /* 1 microsecond. */ + 1000000, /* 1 millisecond. */ +}; + +struct dio200_subdev_8255 { + unsigned int ofs; /* DIO base offset */ +}; + +struct dio200_subdev_intr { + spinlock_t spinlock; /* protects the 'active' flag */ + unsigned int ofs; + unsigned int valid_isns; + unsigned int enabled_isns; + unsigned int active:1; +}; + +static unsigned char dio200_read8(struct comedi_device *dev, + unsigned int offset) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + return readb(dev->mmio + offset); + return inb(dev->iobase + offset); +} + +static void dio200_write8(struct comedi_device *dev, + unsigned int offset, unsigned char val) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + writeb(val, dev->mmio + offset); + else + outb(val, dev->iobase + offset); +} + +static unsigned int dio200_read32(struct comedi_device *dev, + unsigned int offset) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + return readl(dev->mmio + offset); + return inl(dev->iobase + offset); +} + +static void dio200_write32(struct comedi_device *dev, + unsigned int offset, unsigned int val) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + writel(val, dev->mmio + offset); + else + outl(val, dev->iobase + offset); +} + +static unsigned int dio200_subdev_8254_offset(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_8254 *i8254 = s->private; + unsigned int offset; + + /* get the offset that was passed to comedi_8254_*_init() */ + if (dev->mmio) + offset = i8254->mmio - dev->mmio; + else + offset = i8254->iobase - dev->iobase; + + /* remove the shift that was added for PCIe boards */ + if (board->is_pcie) + offset >>= 3; + + /* this offset now works for the dio200_{read,write} helpers */ + return offset; +} + +static int dio200_subdev_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + + if (board->has_int_sce) { + /* Just read the interrupt status register. */ + data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns; + } else { + /* No interrupt status register. */ + data[0] = 0; + } + + return insn->n; +} + +static void dio200_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + + subpriv->active = false; + subpriv->enabled_isns = 0; + if (board->has_int_sce) + dio200_write8(dev, subpriv->ofs, 0); +} + +static void dio200_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int n; + unsigned int isn_bits; + + /* Determine interrupt sources to enable. */ + isn_bits = 0; + if (cmd->chanlist) { + for (n = 0; n < cmd->chanlist_len; n++) + isn_bits |= (1U << CR_CHAN(cmd->chanlist[n])); + } + isn_bits &= subpriv->valid_isns; + /* Enable interrupt sources. */ + subpriv->enabled_isns = isn_bits; + if (board->has_int_sce) + dio200_write8(dev, subpriv->ofs, isn_bits); +} + +static int dio200_inttrig_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct dio200_subdev_intr *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&subpriv->spinlock, flags); + s->async->inttrig = NULL; + if (subpriv->active) + dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 1; +} + +static void dio200_read_scan_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int triggered) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short val; + unsigned int n, ch; + + val = 0; + for (n = 0; n < cmd->chanlist_len; n++) { + ch = CR_CHAN(cmd->chanlist[n]); + if (triggered & (1U << ch)) + val |= (1U << n); + } + + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; +} + +static int dio200_handle_read_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + unsigned int triggered; + unsigned int intstat; + unsigned int cur_enabled; + unsigned long flags; + + triggered = 0; + + spin_lock_irqsave(&subpriv->spinlock, flags); + if (board->has_int_sce) { + /* + * Collect interrupt sources that have triggered and disable + * them temporarily. Loop around until no extra interrupt + * sources have triggered, at which point, the valid part of + * the interrupt status register will read zero, clearing the + * cause of the interrupt. + * + * Mask off interrupt sources already seen to avoid infinite + * loop in case of misconfiguration. + */ + cur_enabled = subpriv->enabled_isns; + while ((intstat = (dio200_read8(dev, subpriv->ofs) & + subpriv->valid_isns & ~triggered)) != 0) { + triggered |= intstat; + cur_enabled &= ~triggered; + dio200_write8(dev, subpriv->ofs, cur_enabled); + } + } else { + /* + * No interrupt status register. Assume the single interrupt + * source has triggered. + */ + triggered = subpriv->enabled_isns; + } + + if (triggered) { + /* + * Some interrupt sources have triggered and have been + * temporarily disabled to clear the cause of the interrupt. + * + * Reenable them NOW to minimize the time they are disabled. + */ + cur_enabled = subpriv->enabled_isns; + if (board->has_int_sce) + dio200_write8(dev, subpriv->ofs, cur_enabled); + + if (subpriv->active) { + /* + * The command is still active. + * + * Ignore interrupt sources that the command isn't + * interested in (just in case there's a race + * condition). + */ + if (triggered & subpriv->enabled_isns) { + /* Collect scan data. */ + dio200_read_scan_intr(dev, s, triggered); + } + } + } + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + comedi_handle_events(dev, s); + + return (triggered != 0); +} + +static int dio200_subdev_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + if (subpriv->active) + dio200_stop_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +static int dio200_subdev_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} + +static int dio200_subdev_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + struct dio200_subdev_intr *subpriv = s->private; + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + + subpriv->active = true; + + if (cmd->start_src == TRIG_INT) + s->async->inttrig = dio200_inttrig_start_intr; + else /* TRIG_NOW */ + dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +static int dio200_subdev_intr_init(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int offset, + unsigned int valid_isns) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv; + + subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); + if (!subpriv) + return -ENOMEM; + + subpriv->ofs = offset; + subpriv->valid_isns = valid_isns; + spin_lock_init(&subpriv->spinlock); + + if (board->has_int_sce) + /* Disable interrupt sources. */ + dio200_write8(dev, subpriv->ofs, 0); + + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_PACKED; + if (board->has_int_sce) { + s->n_chan = DIO200_MAX_ISNS; + s->len_chanlist = DIO200_MAX_ISNS; + } else { + /* No interrupt source register. Support single channel. */ + s->n_chan = 1; + s->len_chanlist = 1; + } + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dio200_subdev_intr_insn_bits; + s->do_cmdtest = dio200_subdev_intr_cmdtest; + s->do_cmd = dio200_subdev_intr_cmd; + s->cancel = dio200_subdev_intr_cancel; + + return 0; +} + +static irqreturn_t dio200_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + int handled; + + if (!dev->attached) + return IRQ_NONE; + + handled = dio200_handle_read_intr(dev, s); + + return IRQ_RETVAL(handled); +} + +static void dio200_subdev_8254_set_gate_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, + unsigned int src) +{ + unsigned int offset = dio200_subdev_8254_offset(dev, s); + + dio200_write8(dev, DIO200_GAT_SCE(offset >> 3), + clk_gat_sce((offset >> 2) & 1, chan, src)); +} + +static void dio200_subdev_8254_set_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, + unsigned int src) +{ + unsigned int offset = dio200_subdev_8254_offset(dev, s); + + dio200_write8(dev, DIO200_CLK_SCE(offset >> 3), + clk_gat_sce((offset >> 2) & 1, chan, src)); +} + +static int dio200_subdev_8254_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_8254 *i8254 = s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int max_src = board->is_pcie ? 31 : 7; + unsigned int src; + + if (!board->has_clk_gat_sce) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_SET_GATE_SRC: + src = data[2]; + if (src > max_src) + return -EINVAL; + + dio200_subdev_8254_set_gate_src(dev, s, chan, src); + i8254->gate_src[chan] = src; + break; + case INSN_CONFIG_GET_GATE_SRC: + data[2] = i8254->gate_src[chan]; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + src = data[1]; + if (src > max_src) + return -EINVAL; + + dio200_subdev_8254_set_clock_src(dev, s, chan, src); + i8254->clock_src[chan] = src; + break; + case INSN_CONFIG_GET_CLOCK_SRC: + data[1] = i8254->clock_src[chan]; + data[2] = clock_period[i8254->clock_src[chan]]; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int dio200_subdev_8254_init(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int offset) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_8254 *i8254; + unsigned int regshift; + int chan; + + /* + * PCIe boards need the offset shifted in order to get the + * correct base address of the timer. + */ + if (board->is_pcie) { + offset <<= 3; + regshift = 3; + } else { + regshift = 0; + } + + if (dev->mmio) { + i8254 = comedi_8254_mm_init(dev->mmio + offset, + 0, I8254_IO8, regshift); + } else { + i8254 = comedi_8254_init(dev->iobase + offset, + 0, I8254_IO8, regshift); + } + if (!i8254) + return -ENOMEM; + + comedi_8254_subdevice_init(s, i8254); + + i8254->insn_config = dio200_subdev_8254_config; + + /* + * There could be multiple timers so this driver does not + * use dev->pacer to save the i8254 pointer. Instead, + * comedi_8254_subdevice_init() saved the i8254 pointer in + * s->private. Mark the subdevice as having private data + * to be automatically freed when the device is detached. + */ + comedi_set_spriv_auto_free(s); + + /* Initialize channels. */ + if (board->has_clk_gat_sce) { + for (chan = 0; chan < 3; chan++) { + /* Gate source 0 is VCC (logic 1). */ + dio200_subdev_8254_set_gate_src(dev, s, chan, 0); + /* Clock source 0 is the dedicated clock input. */ + dio200_subdev_8254_set_clock_src(dev, s, chan, 0); + } + } + + return 0; +} + +static void dio200_subdev_8255_set_dir(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_8255 *subpriv = s->private; + int config; + + config = I8255_CTRL_CW; + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= I8255_CTRL_A_IO; + if (!(s->io_bits & 0x00ff00)) + config |= I8255_CTRL_B_IO; + if (!(s->io_bits & 0x0f0000)) + config |= I8255_CTRL_C_LO_IO; + if (!(s->io_bits & 0xf00000)) + config |= I8255_CTRL_C_HI_IO; + dio200_write8(dev, subpriv->ofs + I8255_CTRL_REG, config); +} + +static int dio200_subdev_8255_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dio200_subdev_8255 *subpriv = s->private; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0xff) { + dio200_write8(dev, subpriv->ofs + I8255_DATA_A_REG, + s->state & 0xff); + } + if (mask & 0xff00) { + dio200_write8(dev, subpriv->ofs + I8255_DATA_B_REG, + (s->state >> 8) & 0xff); + } + if (mask & 0xff0000) { + dio200_write8(dev, subpriv->ofs + I8255_DATA_C_REG, + (s->state >> 16) & 0xff); + } + } + + val = dio200_read8(dev, subpriv->ofs + I8255_DATA_A_REG); + val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_B_REG) << 8; + val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_C_REG) << 16; + + data[1] = val; + + return insn->n; +} + +static int dio200_subdev_8255_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x0000ff; + else if (chan < 16) + mask = 0x00ff00; + else if (chan < 20) + mask = 0x0f0000; + else + mask = 0xf00000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + dio200_subdev_8255_set_dir(dev, s); + + return insn->n; +} + +static int dio200_subdev_8255_init(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int offset) +{ + struct dio200_subdev_8255 *subpriv; + + subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); + if (!subpriv) + return -ENOMEM; + + subpriv->ofs = offset; + + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dio200_subdev_8255_bits; + s->insn_config = dio200_subdev_8255_config; + dio200_subdev_8255_set_dir(dev, s); + return 0; +} + +static int dio200_subdev_timer_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int n; + + for (n = 0; n < insn->n; n++) + data[n] = dio200_read32(dev, DIO200_TS_COUNT); + return n; +} + +static void dio200_subdev_timer_reset(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int clock; + + clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK; + dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET); + dio200_write32(dev, DIO200_TS_CONFIG, clock); +} + +static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *src, + unsigned int *period) +{ + unsigned int clk; + + clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK; + *src = clk; + *period = (clk < ARRAY_SIZE(ts_clock_period)) ? + ts_clock_period[clk] : 0; +} + +static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int src) +{ + if (src > TS_CONFIG_MAX_CLK_SRC) + return -EINVAL; + dio200_write32(dev, DIO200_TS_CONFIG, src); + return 0; +} + +static int dio200_subdev_timer_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret = 0; + + switch (data[0]) { + case INSN_CONFIG_RESET: + dio200_subdev_timer_reset(dev, s); + break; + case INSN_CONFIG_SET_CLOCK_SRC: + ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_GET_CLOCK_SRC: + dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]); + break; + default: + ret = -EINVAL; + break; + } + return ret < 0 ? ret : insn->n; +} + +void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val) +{ + dio200_write8(dev, DIO200_ENHANCE, val); +} +EXPORT_SYMBOL_GPL(amplc_dio200_set_enhance); + +int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq, + unsigned long req_irq_flags) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_subdevice *s; + unsigned int n; + int ret; + + ret = comedi_alloc_subdevices(dev, board->n_subdevs); + if (ret) + return ret; + + for (n = 0; n < dev->n_subdevices; n++) { + s = &dev->subdevices[n]; + switch (board->sdtype[n]) { + case sd_8254: + /* counter subdevice (8254) */ + ret = dio200_subdev_8254_init(dev, s, + board->sdinfo[n]); + if (ret < 0) + return ret; + break; + case sd_8255: + /* digital i/o subdevice (8255) */ + ret = dio200_subdev_8255_init(dev, s, + board->sdinfo[n]); + if (ret < 0) + return ret; + break; + case sd_intr: + /* 'INTERRUPT' subdevice */ + if (irq && !dev->read_subdev) { + ret = dio200_subdev_intr_init(dev, s, + DIO200_INT_SCE, + board->sdinfo[n]); + if (ret < 0) + return ret; + dev->read_subdev = s; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + break; + case sd_timer: + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = 1; + s->maxdata = 0xffffffff; + s->insn_read = dio200_subdev_timer_read; + s->insn_config = dio200_subdev_timer_config; + break; + default: + s->type = COMEDI_SUBD_UNUSED; + break; + } + } + + if (irq && dev->read_subdev) { + if (request_irq(irq, dio200_interrupt, req_irq_flags, + dev->board_name, dev) >= 0) { + dev->irq = irq; + } else { + dev_warn(dev->class_dev, + "warning! irq %u unavailable!\n", irq); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(amplc_dio200_common_attach); + +static int __init amplc_dio200_common_init(void) +{ + return 0; +} +module_init(amplc_dio200_common_init); + +static void __exit amplc_dio200_common_exit(void) +{ +} +module_exit(amplc_dio200_common_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_dio200_pci.c b/drivers/comedi/drivers/amplc_dio200_pci.c new file mode 100644 index 000000000..527994d82 --- /dev/null +++ b/drivers/comedi/drivers/amplc_dio200_pci.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* comedi/drivers/amplc_dio200_pci.c + * + * Driver for Amplicon PCI215, PCI272, PCIe215, PCIe236, PCIe296. + * + * Copyright (C) 2005-2013 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: amplc_dio200_pci + * Description: Amplicon 200 Series PCI Digital I/O + * Author: Ian Abbott <abbotti@mev.co.uk> + * Devices: [Amplicon] PCI215 (amplc_dio200_pci), PCIe215, PCIe236, + * PCI272, PCIe296 + * Updated: Mon, 18 Mar 2013 15:03:50 +0000 + * Status: works + * + * Configuration options: + * none + * + * Manual configuration of PCI(e) cards is not supported; they are configured + * automatically. + * + * SUBDEVICES + * + * PCI215 PCIe215 PCIe236 + * ------------- ------------- ------------- + * Subdevices 5 8 8 + * 0 PPI-X PPI-X PPI-X + * 1 PPI-Y UNUSED UNUSED + * 2 CTR-Z1 PPI-Y UNUSED + * 3 CTR-Z2 UNUSED UNUSED + * 4 INTERRUPT CTR-Z1 CTR-Z1 + * 5 CTR-Z2 CTR-Z2 + * 6 TIMER TIMER + * 7 INTERRUPT INTERRUPT + * + * + * PCI272 PCIe296 + * ------------- ------------- + * Subdevices 4 8 + * 0 PPI-X PPI-X1 + * 1 PPI-Y PPI-X2 + * 2 PPI-Z PPI-Y1 + * 3 INTERRUPT PPI-Y2 + * 4 CTR-Z1 + * 5 CTR-Z2 + * 6 TIMER + * 7 INTERRUPT + * + * Each PPI is a 8255 chip providing 24 DIO channels. The DIO channels + * are configurable as inputs or outputs in four groups: + * + * Port A - channels 0 to 7 + * Port B - channels 8 to 15 + * Port CL - channels 16 to 19 + * Port CH - channels 20 to 23 + * + * Only mode 0 of the 8255 chips is supported. + * + * Each CTR is a 8254 chip providing 3 16-bit counter channels. Each + * channel is configured individually with INSN_CONFIG instructions. The + * specific type of configuration instruction is specified in data[0]. + * Some configuration instructions expect an additional parameter in + * data[1]; others return a value in data[1]. The following configuration + * instructions are supported: + * + * INSN_CONFIG_SET_COUNTER_MODE. Sets the counter channel's mode and + * BCD/binary setting specified in data[1]. + * + * INSN_CONFIG_8254_READ_STATUS. Reads the status register value for the + * counter channel into data[1]. + * + * INSN_CONFIG_SET_CLOCK_SRC. Sets the counter channel's clock source as + * specified in data[1] (this is a hardware-specific value). Not + * supported on PC214E. For the other boards, valid clock sources are + * 0 to 7 as follows: + * + * 0. CLK n, the counter channel's dedicated CLK input from the SK1 + * connector. (N.B. for other values, the counter channel's CLKn + * pin on the SK1 connector is an output!) + * 1. Internal 10 MHz clock. + * 2. Internal 1 MHz clock. + * 3. Internal 100 kHz clock. + * 4. Internal 10 kHz clock. + * 5. Internal 1 kHz clock. + * 6. OUT n-1, the output of counter channel n-1 (see note 1 below). + * 7. Ext Clock, the counter chip's dedicated Ext Clock input from + * the SK1 connector. This pin is shared by all three counter + * channels on the chip. + * + * For the PCIe boards, clock sources in the range 0 to 31 are allowed + * and the following additional clock sources are defined: + * + * 8. HIGH logic level. + * 9. LOW logic level. + * 10. "Pattern present" signal. + * 11. Internal 20 MHz clock. + * + * INSN_CONFIG_GET_CLOCK_SRC. Returns the counter channel's current + * clock source in data[1]. For internal clock sources, data[2] is set + * to the period in ns. + * + * INSN_CONFIG_SET_GATE_SRC. Sets the counter channel's gate source as + * specified in data[2] (this is a hardware-specific value). Not + * supported on PC214E. For the other boards, valid gate sources are 0 + * to 7 as follows: + * + * 0. VCC (internal +5V d.c.), i.e. gate permanently enabled. + * 1. GND (internal 0V d.c.), i.e. gate permanently disabled. + * 2. GAT n, the counter channel's dedicated GAT input from the SK1 + * connector. (N.B. for other values, the counter channel's GATn + * pin on the SK1 connector is an output!) + * 3. /OUT n-2, the inverted output of counter channel n-2 (see note + * 2 below). + * 4. Reserved. + * 5. Reserved. + * 6. Reserved. + * 7. Reserved. + * + * For the PCIe boards, gate sources in the range 0 to 31 are allowed; + * the following additional clock sources and clock sources 6 and 7 are + * (re)defined: + * + * 6. /GAT n, negated version of the counter channel's dedicated + * GAT input (negated version of gate source 2). + * 7. OUT n-2, the non-inverted output of counter channel n-2 + * (negated version of gate source 3). + * 8. "Pattern present" signal, HIGH while pattern present. + * 9. "Pattern occurred" latched signal, latches HIGH when pattern + * occurs. + * 10. "Pattern gone away" latched signal, latches LOW when pattern + * goes away after it occurred. + * 11. Negated "pattern present" signal, LOW while pattern present + * (negated version of gate source 8). + * 12. Negated "pattern occurred" latched signal, latches LOW when + * pattern occurs (negated version of gate source 9). + * 13. Negated "pattern gone away" latched signal, latches LOW when + * pattern goes away after it occurred (negated version of gate + * source 10). + * + * INSN_CONFIG_GET_GATE_SRC. Returns the counter channel's current gate + * source in data[2]. + * + * Clock and gate interconnection notes: + * + * 1. Clock source OUT n-1 is the output of the preceding channel on the + * same counter subdevice if n > 0, or the output of channel 2 on the + * preceding counter subdevice (see note 3) if n = 0. + * + * 2. Gate source /OUT n-2 is the inverted output of channel 0 on the + * same counter subdevice if n = 2, or the inverted output of channel n+1 + * on the preceding counter subdevice (see note 3) if n < 2. + * + * 3. The counter subdevices are connected in a ring, so the highest + * counter subdevice precedes the lowest. + * + * The 'TIMER' subdevice is a free-running 32-bit timer subdevice. + * + * The 'INTERRUPT' subdevice pretends to be a digital input subdevice. The + * digital inputs come from the interrupt status register. The number of + * channels matches the number of interrupt sources. The PC214E does not + * have an interrupt status register; see notes on 'INTERRUPT SOURCES' + * below. + * + * INTERRUPT SOURCES + * + * PCI215 PCIe215 PCIe236 + * ------------- ------------- ------------- + * Sources 6 6 6 + * 0 PPI-X-C0 PPI-X-C0 PPI-X-C0 + * 1 PPI-X-C3 PPI-X-C3 PPI-X-C3 + * 2 PPI-Y-C0 PPI-Y-C0 unused + * 3 PPI-Y-C3 PPI-Y-C3 unused + * 4 CTR-Z1-OUT1 CTR-Z1-OUT1 CTR-Z1-OUT1 + * 5 CTR-Z2-OUT1 CTR-Z2-OUT1 CTR-Z2-OUT1 + * + * PCI272 PCIe296 + * ------------- ------------- + * Sources 6 6 + * 0 PPI-X-C0 PPI-X1-C0 + * 1 PPI-X-C3 PPI-X1-C3 + * 2 PPI-Y-C0 PPI-Y1-C0 + * 3 PPI-Y-C3 PPI-Y1-C3 + * 4 PPI-Z-C0 CTR-Z1-OUT1 + * 5 PPI-Z-C3 CTR-Z2-OUT1 + * + * When an interrupt source is enabled in the interrupt source enable + * register, a rising edge on the source signal latches the corresponding + * bit to 1 in the interrupt status register. + * + * When the interrupt status register value as a whole (actually, just the + * 6 least significant bits) goes from zero to non-zero, the board will + * generate an interrupt. The interrupt will remain asserted until the + * interrupt status register is cleared to zero. To clear a bit to zero in + * the interrupt status register, the corresponding interrupt source must + * be disabled in the interrupt source enable register (there is no + * separate interrupt clear register). + * + * COMMANDS + * + * The driver supports a read streaming acquisition command on the + * 'INTERRUPT' subdevice. The channel list selects the interrupt sources + * to be enabled. All channels will be sampled together (convert_src == + * TRIG_NOW). The scan begins a short time after the hardware interrupt + * occurs, subject to interrupt latencies (scan_begin_src == TRIG_EXT, + * scan_begin_arg == 0). The value read from the interrupt status register + * is packed into a short value, one bit per requested channel, in the + * order they appear in the channel list. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "amplc_dio200.h" + +/* + * Board descriptions. + */ + +enum dio200_pci_model { + pci215_model, + pci272_model, + pcie215_model, + pcie236_model, + pcie296_model +}; + +static const struct dio200_board dio200_pci_boards[] = { + [pci215_model] = { + .name = "pci215", + .mainbar = 2, + .n_subdevs = 5, + .sdtype = { + sd_8255, sd_8255, sd_8254, sd_8254, sd_intr + }, + .sdinfo = { 0x00, 0x08, 0x10, 0x14, 0x3f }, + .has_int_sce = true, + .has_clk_gat_sce = true, + }, + [pci272_model] = { + .name = "pci272", + .mainbar = 2, + .n_subdevs = 4, + .sdtype = { + sd_8255, sd_8255, sd_8255, sd_intr + }, + .sdinfo = { 0x00, 0x08, 0x10, 0x3f }, + .has_int_sce = true, + }, + [pcie215_model] = { + .name = "pcie215", + .mainbar = 1, + .n_subdevs = 8, + .sdtype = { + sd_8255, sd_none, sd_8255, sd_none, + sd_8254, sd_8254, sd_timer, sd_intr + }, + .sdinfo = { + 0x00, 0x00, 0x08, 0x00, 0x10, 0x14, 0x00, 0x3f + }, + .has_int_sce = true, + .has_clk_gat_sce = true, + .is_pcie = true, + }, + [pcie236_model] = { + .name = "pcie236", + .mainbar = 1, + .n_subdevs = 8, + .sdtype = { + sd_8255, sd_none, sd_none, sd_none, + sd_8254, sd_8254, sd_timer, sd_intr + }, + .sdinfo = { + 0x00, 0x00, 0x00, 0x00, 0x10, 0x14, 0x00, 0x3f + }, + .has_int_sce = true, + .has_clk_gat_sce = true, + .is_pcie = true, + }, + [pcie296_model] = { + .name = "pcie296", + .mainbar = 1, + .n_subdevs = 8, + .sdtype = { + sd_8255, sd_8255, sd_8255, sd_8255, + sd_8254, sd_8254, sd_timer, sd_intr + }, + .sdinfo = { + 0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x00, 0x3f + }, + .has_int_sce = true, + .has_clk_gat_sce = true, + .is_pcie = true, + }, +}; + +/* + * This function does some special set-up for the PCIe boards + * PCIe215, PCIe236, PCIe296. + */ +static int dio200_pcie_board_setup(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + void __iomem *brbase; + + /* + * The board uses Altera Cyclone IV with PCI-Express hard IP. + * The FPGA configuration has the PCI-Express Avalon-MM Bridge + * Control registers in PCI BAR 0, offset 0, and the length of + * these registers is 0x4000. + * + * We need to write 0x80 to the "Avalon-MM to PCI-Express Interrupt + * Enable" register at offset 0x50 to allow generation of PCIe + * interrupts when RXmlrq_i is asserted in the SOPC Builder system. + */ + if (pci_resource_len(pcidev, 0) < 0x4000) { + dev_err(dev->class_dev, "error! bad PCI region!\n"); + return -EINVAL; + } + brbase = pci_ioremap_bar(pcidev, 0); + if (!brbase) { + dev_err(dev->class_dev, "error! failed to map registers!\n"); + return -ENOMEM; + } + writel(0x80, brbase + 0x50); + iounmap(brbase); + /* Enable "enhanced" features of board. */ + amplc_dio200_set_enhance(dev, 1); + return 0; +} + +static int dio200_pci_auto_attach(struct comedi_device *dev, + unsigned long context_model) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + const struct dio200_board *board = NULL; + unsigned int bar; + int ret; + + if (context_model < ARRAY_SIZE(dio200_pci_boards)) + board = &dio200_pci_boards[context_model]; + if (!board) + return -EINVAL; + dev->board_ptr = board; + dev->board_name = board->name; + + dev_info(dev->class_dev, "%s: attach pci %s (%s)\n", + dev->driver->driver_name, pci_name(pci_dev), dev->board_name); + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + bar = board->mainbar; + if (pci_resource_flags(pci_dev, bar) & IORESOURCE_MEM) { + dev->mmio = pci_ioremap_bar(pci_dev, bar); + if (!dev->mmio) { + dev_err(dev->class_dev, + "error! cannot remap registers\n"); + return -ENOMEM; + } + } else { + dev->iobase = pci_resource_start(pci_dev, bar); + } + + if (board->is_pcie) { + ret = dio200_pcie_board_setup(dev); + if (ret < 0) + return ret; + } + + return amplc_dio200_common_attach(dev, pci_dev->irq, IRQF_SHARED); +} + +static struct comedi_driver dio200_pci_comedi_driver = { + .driver_name = "amplc_dio200_pci", + .module = THIS_MODULE, + .auto_attach = dio200_pci_auto_attach, + .detach = comedi_pci_detach, +}; + +static const struct pci_device_id dio200_pci_table[] = { + { PCI_VDEVICE(AMPLICON, 0x000b), pci215_model }, + { PCI_VDEVICE(AMPLICON, 0x000a), pci272_model }, + { PCI_VDEVICE(AMPLICON, 0x0011), pcie236_model }, + { PCI_VDEVICE(AMPLICON, 0x0012), pcie215_model }, + { PCI_VDEVICE(AMPLICON, 0x0014), pcie296_model }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, dio200_pci_table); + +static int dio200_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &dio200_pci_comedi_driver, + id->driver_data); +} + +static struct pci_driver dio200_pci_pci_driver = { + .name = "amplc_dio200_pci", + .id_table = dio200_pci_table, + .probe = dio200_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(dio200_pci_comedi_driver, dio200_pci_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series PCI(e) DIO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pc236.c b/drivers/comedi/drivers/amplc_pc236.c new file mode 100644 index 000000000..b21e0c906 --- /dev/null +++ b/drivers/comedi/drivers/amplc_pc236.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/amplc_pc236.c + * Driver for Amplicon PC36AT DIO boards. + * + * Copyright (C) 2002 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ +/* + * Driver: amplc_pc236 + * Description: Amplicon PC36AT + * Author: Ian Abbott <abbotti@mev.co.uk> + * Devices: [Amplicon] PC36AT (pc36at) + * Updated: Fri, 25 Jul 2014 15:32:40 +0000 + * Status: works + * + * Configuration options - PC36AT: + * [0] - I/O port base address + * [1] - IRQ (optional) + * + * The PC36AT board has a single 8255 appearing as subdevice 0. + * + * Subdevice 1 pretends to be a digital input device, but it always returns + * 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT, + * a rising edge on port C bit 3 acts as an external trigger, which can be + * used to wake up tasks. This is like the comedi_parport device, but the + * only way to physically disable the interrupt on the PC36AT is to remove + * the IRQ jumper. If no interrupt is connected, then subdevice 1 is + * unused. + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> + +#include "amplc_pc236.h" + +static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pc236_private *devpriv; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x4); + if (ret) + return ret; + + return amplc_pc236_common_attach(dev, dev->iobase, it->options[1], 0); +} + +static const struct pc236_board pc236_boards[] = { + { + .name = "pc36at", + }, +}; + +static struct comedi_driver amplc_pc236_driver = { + .driver_name = "amplc_pc236", + .module = THIS_MODULE, + .attach = pc236_attach, + .detach = comedi_legacy_detach, + .board_name = &pc236_boards[0].name, + .offset = sizeof(struct pc236_board), + .num_names = ARRAY_SIZE(pc236_boards), +}; + +module_comedi_driver(amplc_pc236_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PC36AT DIO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pc236.h b/drivers/comedi/drivers/amplc_pc236.h new file mode 100644 index 000000000..7e72729f7 --- /dev/null +++ b/drivers/comedi/drivers/amplc_pc236.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/amplc_pc236.h + * Header for "amplc_pc236", "amplc_pci236" and "amplc_pc236_common". + * + * Copyright (C) 2002-2014 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +#ifndef AMPLC_PC236_H_INCLUDED +#define AMPLC_PC236_H_INCLUDED + +#include <linux/types.h> + +struct comedi_device; + +struct pc236_board { + const char *name; + void (*intr_update_cb)(struct comedi_device *dev, bool enable); + bool (*intr_chk_clr_cb)(struct comedi_device *dev); +}; + +struct pc236_private { + unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */ + bool enable_irq; +}; + +int amplc_pc236_common_attach(struct comedi_device *dev, unsigned long iobase, + unsigned int irq, unsigned long req_irq_flags); + +#endif diff --git a/drivers/comedi/drivers/amplc_pc236_common.c b/drivers/comedi/drivers/amplc_pc236_common.c new file mode 100644 index 000000000..9f4f89b1e --- /dev/null +++ b/drivers/comedi/drivers/amplc_pc236_common.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/amplc_pc236_common.c + * Common support code for "amplc_pc236" and "amplc_pci236". + * + * Copyright (C) 2002-2014 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +#include "amplc_pc236.h" + +static void pc236_intr_update(struct comedi_device *dev, bool enable) +{ + const struct pc236_board *board = dev->board_ptr; + struct pc236_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->enable_irq = enable; + if (board->intr_update_cb) + board->intr_update_cb(dev, enable); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +/* + * This function is called when an interrupt occurs to check whether + * the interrupt has been marked as enabled and was generated by the + * board. If so, the function prepares the hardware for the next + * interrupt. + * Returns false if the interrupt should be ignored. + */ +static bool pc236_intr_check(struct comedi_device *dev) +{ + const struct pc236_board *board = dev->board_ptr; + struct pc236_private *devpriv = dev->private; + bool retval = false; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + if (devpriv->enable_irq) { + if (board->intr_chk_clr_cb) + retval = board->intr_chk_clr_cb(dev); + else + retval = true; + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + return retval; +} + +static int pc236_intr_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + return insn->n; +} + +static int pc236_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check it arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + pc236_intr_update(dev, true); + + return 0; +} + +static int pc236_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pc236_intr_update(dev, false); + + return 0; +} + +static irqreturn_t pc236_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + bool handled; + + handled = pc236_intr_check(dev); + if (dev->attached && handled) { + unsigned short val = 0; + + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + } + return IRQ_RETVAL(handled); +} + +int amplc_pc236_common_attach(struct comedi_device *dev, unsigned long iobase, + unsigned int irq, unsigned long req_irq_flags) +{ + struct comedi_subdevice *s; + int ret; + + dev->iobase = iobase; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* digital i/o subdevice (8255) */ + ret = subdev_8255_init(dev, s, NULL, 0x00); + if (ret) + return ret; + + s = &dev->subdevices[1]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_UNUSED; + pc236_intr_update(dev, false); + if (irq) { + if (request_irq(irq, pc236_interrupt, req_irq_flags, + dev->board_name, dev) >= 0) { + dev->irq = irq; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pc236_intr_insn; + s->len_chanlist = 1; + s->do_cmdtest = pc236_intr_cmdtest; + s->do_cmd = pc236_intr_cmd; + s->cancel = pc236_intr_cancel; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(amplc_pc236_common_attach); + +static int __init amplc_pc236_common_init(void) +{ + return 0; +} +module_init(amplc_pc236_common_init); + +static void __exit amplc_pc236_common_exit(void) +{ +} +module_exit(amplc_pc236_common_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi helper for amplc_pc236 and amplc_pci236"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pc263.c b/drivers/comedi/drivers/amplc_pc263.c new file mode 100644 index 000000000..d7f088a8a --- /dev/null +++ b/drivers/comedi/drivers/amplc_pc263.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Amplicon PC263 relay board. + * + * Copyright (C) 2002 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* PC263 registers */ +#define PC263_DO_0_7_REG 0x00 +#define PC263_DO_8_15_REG 0x01 + +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 + PC263_DO_0_7_REG); + outb((s->state >> 8) & 0xff, dev->iobase + PC263_DO_8_15_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x2); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + /* Digital Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pc263_do_insn_bits; + + /* read initial relay state */ + s->state = inb(dev->iobase + PC263_DO_0_7_REG) | + (inb(dev->iobase + PC263_DO_8_15_REG) << 8); + + return 0; +} + +static struct comedi_driver amplc_pc263_driver = { + .driver_name = "amplc_pc263", + .module = THIS_MODULE, + .attach = pc263_attach, + .detach = comedi_legacy_detach, + .board_name = &pc263_boards[0].name, + .offset = sizeof(struct pc263_board), + .num_names = ARRAY_SIZE(pc263_boards), +}; + +module_comedi_driver(amplc_pc263_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PC263 relay board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pci224.c b/drivers/comedi/drivers/amplc_pci224.c new file mode 100644 index 000000000..5a04e55da --- /dev/null +++ b/drivers/comedi/drivers/amplc_pci224.c @@ -0,0 +1,1141 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/amplc_pci224.c + * Driver for Amplicon PCI224 and PCI234 AO boards. + * + * Copyright (C) 2005 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: amplc_pci224 + * Description: Amplicon PCI224, PCI234 + * Author: Ian Abbott <abbotti@mev.co.uk> + * Devices: [Amplicon] PCI224 (amplc_pci224), PCI234 + * Updated: Thu, 31 Jul 2014 11:08:03 +0000 + * Status: works, but see caveats + * + * Supports: + * + * - ao_insn read/write + * - ao_do_cmd mode with the following sources: + * + * - start_src TRIG_INT TRIG_EXT + * - scan_begin_src TRIG_TIMER TRIG_EXT + * - convert_src TRIG_NOW + * - scan_end_src TRIG_COUNT + * - stop_src TRIG_COUNT TRIG_EXT TRIG_NONE + * + * The channel list must contain at least one channel with no repeated + * channels. The scan end count must equal the number of channels in + * the channel list. + * + * There is only one external trigger source so only one of start_src, + * scan_begin_src or stop_src may use TRIG_EXT. + * + * Configuration options: + * none + * + * Manual configuration of PCI cards is not supported; they are configured + * automatically. + * + * Output range selection - PCI224: + * + * Output ranges on PCI224 are partly software-selectable and partly + * hardware-selectable according to jumper LK1. All channels are set + * to the same range: + * + * - LK1 position 1-2 (factory default) corresponds to the following + * comedi ranges: + * + * 0: [-10V,+10V]; 1: [-5V,+5V]; 2: [-2.5V,+2.5V], 3: [-1.25V,+1.25V], + * 4: [0,+10V], 5: [0,+5V], 6: [0,+2.5V], 7: [0,+1.25V] + * + * - LK1 position 2-3 corresponds to the following Comedi ranges, using + * an external voltage reference: + * + * 0: [-Vext,+Vext], + * 1: [0,+Vext] + * + * Output range selection - PCI234: + * + * Output ranges on PCI234 are hardware-selectable according to jumper + * LK1 which affects all channels, and jumpers LK2, LK3, LK4 and LK5 + * which affect channels 0, 1, 2 and 3 individually. LK1 chooses between + * an internal 5V reference and an external voltage reference (Vext). + * LK2/3/4/5 choose (per channel) to double the reference or not according + * to the following table: + * + * LK1 position LK2/3/4/5 pos Comedi range + * ------------- ------------- -------------- + * 2-3 (factory) 1-2 (factory) 0: [-10V,+10V] + * 2-3 (factory) 2-3 1: [-5V,+5V] + * 1-2 1-2 (factory) 2: [-2*Vext,+2*Vext] + * 1-2 2-3 3: [-Vext,+Vext] + * + * Caveats: + * + * 1) All channels on the PCI224 share the same range. Any change to the + * range as a result of insn_write or a streaming command will affect + * the output voltages of all channels, including those not specified + * by the instruction or command. + * + * 2) For the analog output command, the first scan may be triggered + * falsely at the start of acquisition. This occurs when the DAC scan + * trigger source is switched from 'none' to 'timer' (scan_begin_src = + * TRIG_TIMER) or 'external' (scan_begin_src == TRIG_EXT) at the start + * of acquisition and the trigger source is at logic level 1 at the + * time of the switch. This is very likely for TRIG_TIMER. For + * TRIG_EXT, it depends on the state of the external line and whether + * the CR_INVERT flag has been set. The remaining scans are triggered + * correctly. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8254.h> + +/* + * PCI224/234 i/o space 1 (PCIBAR2) registers. + */ +#define PCI224_Z2_BASE 0x14 /* 82C54 counter/timer */ +#define PCI224_ZCLK_SCE 0x1A /* Group Z Clock Configuration Register */ +#define PCI224_ZGAT_SCE 0x1D /* Group Z Gate Configuration Register */ +#define PCI224_INT_SCE 0x1E /* ISR Interrupt source mask register */ + /* /Interrupt status */ + +/* + * PCI224/234 i/o space 2 (PCIBAR3) 16-bit registers. + */ +#define PCI224_DACDATA 0x00 /* (w-o) DAC FIFO data. */ +#define PCI224_SOFTTRIG 0x00 /* (r-o) DAC software scan trigger. */ +#define PCI224_DACCON 0x02 /* (r/w) DAC status/configuration. */ +#define PCI224_FIFOSIZ 0x04 /* (w-o) FIFO size for wraparound mode. */ +#define PCI224_DACCEN 0x06 /* (w-o) DAC channel enable register. */ + +/* + * DACCON values. + */ +/* (r/w) Scan trigger. */ +#define PCI224_DACCON_TRIG(x) (((x) & 0x7) << 0) +#define PCI224_DACCON_TRIG_MASK PCI224_DACCON_TRIG(7) +#define PCI224_DACCON_TRIG_NONE PCI224_DACCON_TRIG(0) /* none */ +#define PCI224_DACCON_TRIG_SW PCI224_DACCON_TRIG(1) /* soft trig */ +#define PCI224_DACCON_TRIG_EXTP PCI224_DACCON_TRIG(2) /* ext + edge */ +#define PCI224_DACCON_TRIG_EXTN PCI224_DACCON_TRIG(3) /* ext - edge */ +#define PCI224_DACCON_TRIG_Z2CT0 PCI224_DACCON_TRIG(4) /* Z2 CT0 out */ +#define PCI224_DACCON_TRIG_Z2CT1 PCI224_DACCON_TRIG(5) /* Z2 CT1 out */ +#define PCI224_DACCON_TRIG_Z2CT2 PCI224_DACCON_TRIG(6) /* Z2 CT2 out */ +/* (r/w) Polarity (PCI224 only, PCI234 always bipolar!). */ +#define PCI224_DACCON_POLAR(x) (((x) & 0x1) << 3) +#define PCI224_DACCON_POLAR_MASK PCI224_DACCON_POLAR(1) +#define PCI224_DACCON_POLAR_UNI PCI224_DACCON_POLAR(0) /* [0,+V] */ +#define PCI224_DACCON_POLAR_BI PCI224_DACCON_POLAR(1) /* [-V,+V] */ +/* (r/w) Internal Vref (PCI224 only, when LK1 in position 1-2). */ +#define PCI224_DACCON_VREF(x) (((x) & 0x3) << 4) +#define PCI224_DACCON_VREF_MASK PCI224_DACCON_VREF(3) +#define PCI224_DACCON_VREF_1_25 PCI224_DACCON_VREF(0) /* 1.25V */ +#define PCI224_DACCON_VREF_2_5 PCI224_DACCON_VREF(1) /* 2.5V */ +#define PCI224_DACCON_VREF_5 PCI224_DACCON_VREF(2) /* 5V */ +#define PCI224_DACCON_VREF_10 PCI224_DACCON_VREF(3) /* 10V */ +/* (r/w) Wraparound mode enable (to play back stored waveform). */ +#define PCI224_DACCON_FIFOWRAP BIT(7) +/* (r/w) FIFO enable. It MUST be set! */ +#define PCI224_DACCON_FIFOENAB BIT(8) +/* (r/w) FIFO interrupt trigger level (most values are not very useful). */ +#define PCI224_DACCON_FIFOINTR(x) (((x) & 0x7) << 9) +#define PCI224_DACCON_FIFOINTR_MASK PCI224_DACCON_FIFOINTR(7) +#define PCI224_DACCON_FIFOINTR_EMPTY PCI224_DACCON_FIFOINTR(0) /* empty */ +#define PCI224_DACCON_FIFOINTR_NEMPTY PCI224_DACCON_FIFOINTR(1) /* !empty */ +#define PCI224_DACCON_FIFOINTR_NHALF PCI224_DACCON_FIFOINTR(2) /* !half */ +#define PCI224_DACCON_FIFOINTR_HALF PCI224_DACCON_FIFOINTR(3) /* half */ +#define PCI224_DACCON_FIFOINTR_NFULL PCI224_DACCON_FIFOINTR(4) /* !full */ +#define PCI224_DACCON_FIFOINTR_FULL PCI224_DACCON_FIFOINTR(5) /* full */ +/* (r-o) FIFO fill level. */ +#define PCI224_DACCON_FIFOFL(x) (((x) & 0x7) << 12) +#define PCI224_DACCON_FIFOFL_MASK PCI224_DACCON_FIFOFL(7) +#define PCI224_DACCON_FIFOFL_EMPTY PCI224_DACCON_FIFOFL(1) /* 0 */ +#define PCI224_DACCON_FIFOFL_ONETOHALF PCI224_DACCON_FIFOFL(0) /* 1-2048 */ +#define PCI224_DACCON_FIFOFL_HALFTOFULL PCI224_DACCON_FIFOFL(4) /* 2049-4095 */ +#define PCI224_DACCON_FIFOFL_FULL PCI224_DACCON_FIFOFL(6) /* 4096 */ +/* (r-o) DAC busy flag. */ +#define PCI224_DACCON_BUSY BIT(15) +/* (w-o) FIFO reset. */ +#define PCI224_DACCON_FIFORESET BIT(12) +/* (w-o) Global reset (not sure what it does). */ +#define PCI224_DACCON_GLOBALRESET BIT(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 */ + +static unsigned int pci224_clk_config(unsigned int chan, unsigned int src) +{ + return ((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 */ + +static unsigned int pci224_gat_config(unsigned int chan, unsigned int src) +{ + return ((chan & 3) << 3) | (src & 7); +} + +/* + * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI224 and PCI234: + * + * Channel's Channel's + * clock input gate input + * Channel CLK_OUTNM1 GAT_NOUTNM2 + * ------- ---------- ----------- + * Z2-CT0 Z2-CT2-OUT /Z2-CT1-OUT + * Z2-CT1 Z2-CT0-OUT /Z2-CT2-OUT + * Z2-CT2 Z2-CT1-OUT /Z2-CT0-OUT + */ + +/* + * Interrupt enable/status bits + */ +#define PCI224_INTR_EXT 0x01 /* rising edge on external input */ +#define PCI224_INTR_DAC 0x04 /* DAC (FIFO) interrupt */ +#define PCI224_INTR_Z2CT1 0x20 /* rising edge on Z2-CT1 output */ + +#define PCI224_INTR_EDGE_BITS (PCI224_INTR_EXT | PCI224_INTR_Z2CT1) +#define PCI224_INTR_LEVEL_BITS PCI224_INTR_DACFIFO + +/* + * Handy macros. + */ + +/* Combine old and new bits. */ +#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask))) + +/* Current CPU. XXX should this be hard_smp_processor_id()? */ +#define THISCPU smp_processor_id() + +/* State bits for use with atomic bit operations. */ +#define AO_CMD_STARTED 0 + +/* + * Range tables. + */ + +/* + * The ranges for PCI224. + * + * These are partly hardware-selectable by jumper LK1 and partly + * software-selectable. + * + * All channels share the same hardware range. + */ +static const struct comedi_lrange range_pci224 = { + 10, { + /* jumper LK1 in position 1-2 (factory default) */ + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + /* jumper LK1 in position 2-3 */ + RANGE_ext(-1, 1), /* bipolar [-Vext,+Vext] */ + RANGE_ext(0, 1), /* unipolar [0,+Vext] */ + } +}; + +static const unsigned short hwrange_pci224[10] = { + /* jumper LK1 in position 1-2 (factory default) */ + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_10, + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_5, + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_2_5, + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_1_25, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_10, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_5, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_2_5, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_1_25, + /* jumper LK1 in position 2-3 */ + PCI224_DACCON_POLAR_BI, + PCI224_DACCON_POLAR_UNI, +}; + +/* Used to check all channels set to the same range on PCI224. */ +static const unsigned char range_check_pci224[10] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +}; + +/* + * The ranges for PCI234. + * + * These are all hardware-selectable by jumper LK1 affecting all channels, + * and jumpers LK2, LK3, LK4 and LK5 affecting channels 0, 1, 2 and 3 + * individually. + */ +static const struct comedi_lrange range_pci234 = { + 4, { + /* LK1: 1-2 (fact def), LK2/3/4/5: 2-3 (fac def) */ + BIP_RANGE(10), + /* LK1: 1-2 (fact def), LK2/3/4/5: 1-2 */ + BIP_RANGE(5), + /* LK1: 2-3, LK2/3/4/5: 2-3 (fac def) */ + RANGE_ext(-2, 2), /* bipolar [-2*Vext,+2*Vext] */ + /* LK1: 2-3, LK2/3/4/5: 1-2 */ + RANGE_ext(-1, 1), /* bipolar [-Vext,+Vext] */ + } +}; + +/* N.B. PCI234 ignores the polarity bit, but software uses it. */ +static const unsigned short hwrange_pci234[4] = { + PCI224_DACCON_POLAR_BI, + PCI224_DACCON_POLAR_BI, + PCI224_DACCON_POLAR_BI, + PCI224_DACCON_POLAR_BI, +}; + +/* Used to check all channels use same LK1 setting on PCI234. */ +static const unsigned char range_check_pci234[4] = { + 0, 0, 1, 1, +}; + +/* + * Board descriptions. + */ + +enum pci224_model { pci224_model, pci234_model }; + +struct pci224_board { + const char *name; + unsigned int ao_chans; + unsigned int ao_bits; + const struct comedi_lrange *ao_range; + const unsigned short *ao_hwrange; + const unsigned char *ao_range_check; +}; + +static const struct pci224_board pci224_boards[] = { + [pci224_model] = { + .name = "pci224", + .ao_chans = 16, + .ao_bits = 12, + .ao_range = &range_pci224, + .ao_hwrange = &hwrange_pci224[0], + .ao_range_check = &range_check_pci224[0], + }, + [pci234_model] = { + .name = "pci234", + .ao_chans = 4, + .ao_bits = 16, + .ao_range = &range_pci234, + .ao_hwrange = &hwrange_pci234[0], + .ao_range_check = &range_check_pci234[0], + }, +}; + +struct pci224_private { + unsigned long iobase1; + unsigned long state; + spinlock_t ao_spinlock; /* spinlock for AO command handling */ + unsigned short *ao_scan_vals; + unsigned char *ao_scan_order; + int intr_cpuid; + short intr_running; + unsigned short daccon; + unsigned short ao_enab; /* max 16 channels so 'short' will do */ + unsigned char intsce; +}; + +/* + * Called from the 'insn_write' function to perform a single write. + */ +static void +pci224_ao_set_data(struct comedi_device *dev, int chan, int range, + unsigned int data) +{ + const struct pci224_board *board = dev->board_ptr; + struct pci224_private *devpriv = dev->private; + unsigned short mangled; + + /* Enable the channel. */ + outw(1 << chan, dev->iobase + PCI224_DACCEN); + /* Set range and reset FIFO. */ + devpriv->daccon = COMBINE(devpriv->daccon, board->ao_hwrange[range], + PCI224_DACCON_POLAR_MASK | + PCI224_DACCON_VREF_MASK); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); + /* + * Mangle the data. The hardware expects: + * - bipolar: 16-bit 2's complement + * - unipolar: 16-bit unsigned + */ + mangled = (unsigned short)data << (16 - board->ao_bits); + if ((devpriv->daccon & PCI224_DACCON_POLAR_MASK) == + PCI224_DACCON_POLAR_BI) { + mangled ^= 0x8000; + } + /* Write mangled data to the FIFO. */ + outw(mangled, dev->iobase + PCI224_DACDATA); + /* Trigger the conversion. */ + inw(dev->iobase + PCI224_SOFTTRIG); +} + +static int pci224_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + pci224_ao_set_data(dev, chan, range, val); + } + s->readback[chan] = val; + + return insn->n; +} + +/* + * Kills a command running on the AO subdevice. + */ +static void pci224_ao_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci224_private *devpriv = dev->private; + unsigned long flags; + + if (!test_and_clear_bit(AO_CMD_STARTED, &devpriv->state)) + return; + + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + /* Kill the interrupts. */ + devpriv->intsce = 0; + outb(0, devpriv->iobase1 + PCI224_INT_SCE); + /* + * Interrupt routine may or may not be running. We may or may not + * have been called from the interrupt routine (directly or + * indirectly via a comedi_events() callback routine). It's highly + * unlikely that we've been called from some other interrupt routine + * but who knows what strange things coders get up to! + * + * If the interrupt routine is currently running, wait for it to + * finish, unless we appear to have been called via the interrupt + * routine. + */ + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + } + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + /* Reconfigure DAC for insn_write usage. */ + outw(0, dev->iobase + PCI224_DACCEN); /* Disable channels. */ + devpriv->daccon = + COMBINE(devpriv->daccon, + PCI224_DACCON_TRIG_SW | PCI224_DACCON_FIFOINTR_EMPTY, + PCI224_DACCON_TRIG_MASK | PCI224_DACCON_FIFOINTR_MASK); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); +} + +/* + * Handles start of acquisition for the AO subdevice. + */ +static void pci224_ao_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci224_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + set_bit(AO_CMD_STARTED, &devpriv->state); + + /* Enable interrupts. */ + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + if (cmd->stop_src == TRIG_EXT) + devpriv->intsce = PCI224_INTR_EXT | PCI224_INTR_DAC; + else + devpriv->intsce = PCI224_INTR_DAC; + + outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE); + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); +} + +/* + * Handles interrupts from the DAC FIFO. + */ +static void pci224_ao_handle_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci224_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int num_scans = comedi_nscans_left(s, 0); + unsigned int room; + unsigned short dacstat; + unsigned int i, n; + + /* Determine how much room is in the FIFO (in samples). */ + dacstat = inw(dev->iobase + PCI224_DACCON); + switch (dacstat & PCI224_DACCON_FIFOFL_MASK) { + case PCI224_DACCON_FIFOFL_EMPTY: + room = PCI224_FIFO_ROOM_EMPTY; + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + /* FIFO empty at end of counted acquisition. */ + s->async->events |= COMEDI_CB_EOA; + comedi_handle_events(dev, s); + return; + } + break; + case PCI224_DACCON_FIFOFL_ONETOHALF: + room = PCI224_FIFO_ROOM_ONETOHALF; + break; + case PCI224_DACCON_FIFOFL_HALFTOFULL: + room = PCI224_FIFO_ROOM_HALFTOFULL; + break; + default: + room = PCI224_FIFO_ROOM_FULL; + break; + } + if (room >= PCI224_FIFO_ROOM_ONETOHALF) { + /* FIFO is less than half-full. */ + if (num_scans == 0) { + /* Nothing left to put in the FIFO. */ + dev_err(dev->class_dev, "AO buffer underrun\n"); + s->async->events |= COMEDI_CB_OVERFLOW; + } + } + /* Determine how many new scans can be put in the FIFO. */ + room /= cmd->chanlist_len; + + /* Determine how many scans to process. */ + if (num_scans > room) + num_scans = room; + + /* Process scans. */ + for (n = 0; n < num_scans; n++) { + comedi_buf_read_samples(s, &devpriv->ao_scan_vals[0], + cmd->chanlist_len); + for (i = 0; i < cmd->chanlist_len; i++) { + outw(devpriv->ao_scan_vals[devpriv->ao_scan_order[i]], + dev->iobase + PCI224_DACDATA); + } + } + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + /* + * Change FIFO interrupt trigger level to wait + * until FIFO is empty. + */ + devpriv->daccon = COMBINE(devpriv->daccon, + PCI224_DACCON_FIFOINTR_EMPTY, + PCI224_DACCON_FIFOINTR_MASK); + outw(devpriv->daccon, dev->iobase + PCI224_DACCON); + } + if ((devpriv->daccon & PCI224_DACCON_TRIG_MASK) == + PCI224_DACCON_TRIG_NONE) { + unsigned short trig; + + /* + * This is the initial DAC FIFO interrupt at the + * start of the acquisition. The DAC's scan trigger + * has been set to 'none' up until now. + * + * Now that data has been written to the FIFO, the + * DAC's scan trigger source can be set to the + * correct value. + * + * BUG: The first scan will be triggered immediately + * if the scan trigger source is at logic level 1. + */ + if (cmd->scan_begin_src == TRIG_TIMER) { + trig = PCI224_DACCON_TRIG_Z2CT0; + } else { + /* cmd->scan_begin_src == TRIG_EXT */ + if (cmd->scan_begin_arg & CR_INVERT) + trig = PCI224_DACCON_TRIG_EXTN; + else + trig = PCI224_DACCON_TRIG_EXTP; + } + devpriv->daccon = + COMBINE(devpriv->daccon, trig, PCI224_DACCON_TRIG_MASK); + outw(devpriv->daccon, dev->iobase + PCI224_DACCON); + } + + comedi_handle_events(dev, s); +} + +static int pci224_ao_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + s->async->inttrig = NULL; + pci224_ao_start(dev, s); + + return 1; +} + +static int pci224_ao_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct pci224_board *board = dev->board_ptr; + unsigned int range_check_0; + unsigned int chan_mask = 0; + int i; + + range_check_0 = board->ao_range_check[CR_RANGE(cmd->chanlist[0])]; + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (chan_mask & (1 << chan)) { + dev_dbg(dev->class_dev, + "%s: entries in chanlist must contain no duplicate channels\n", + __func__); + return -EINVAL; + } + chan_mask |= 1 << chan; + + if (board->ao_range_check[CR_RANGE(cmd->chanlist[i])] != + range_check_0) { + dev_dbg(dev->class_dev, + "%s: entries in chanlist have incompatible ranges\n", + __func__); + return -EINVAL; + } + } + + return 0; +} + +#define MAX_SCAN_PERIOD 0xFFFFFFFFU +#define MIN_SCAN_PERIOD 2500 +#define CONVERT_PERIOD 625 + +/* + * 'do_cmdtest' function for AO subdevice. + */ +static int +pci224_ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_EXT | TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, + TRIG_COUNT | TRIG_EXT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + /* + * There's only one external trigger signal (which makes these + * tests easier). Only one thing can use it. + */ + arg = 0; + if (cmd->start_src & TRIG_EXT) + arg++; + if (cmd->scan_begin_src & TRIG_EXT) + arg++; + if (cmd->stop_src & TRIG_EXT) + arg++; + if (arg > 1) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_INT: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if (cmd->start_arg & ~CR_FLAGS_MASK) { + cmd->start_arg = + COMBINE(cmd->start_arg, 0, ~CR_FLAGS_MASK); + err |= -EINVAL; + } + /* The only flag allowed is CR_EDGE, which is ignored. */ + if (cmd->start_arg & CR_FLAGS_MASK & ~CR_EDGE) { + cmd->start_arg = COMBINE(cmd->start_arg, 0, + CR_FLAGS_MASK & ~CR_EDGE); + err |= -EINVAL; + } + break; + } + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + MAX_SCAN_PERIOD); + + arg = cmd->chanlist_len * CONVERT_PERIOD; + if (arg < MIN_SCAN_PERIOD) + arg = MIN_SCAN_PERIOD; + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg); + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) { + cmd->scan_begin_arg = + COMBINE(cmd->scan_begin_arg, 0, ~CR_FLAGS_MASK); + err |= -EINVAL; + } + /* Only allow flags CR_EDGE and CR_INVERT. Ignore CR_EDGE. */ + if (cmd->scan_begin_arg & CR_FLAGS_MASK & + ~(CR_EDGE | CR_INVERT)) { + cmd->scan_begin_arg = + COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT)); + err |= -EINVAL; + } + break; + } + + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if (cmd->stop_arg & ~CR_FLAGS_MASK) { + cmd->stop_arg = + COMBINE(cmd->stop_arg, 0, ~CR_FLAGS_MASK); + err |= -EINVAL; + } + /* The only flag allowed is CR_EDGE, which is ignored. */ + if (cmd->stop_arg & CR_FLAGS_MASK & ~CR_EDGE) { + cmd->stop_arg = + COMBINE(cmd->stop_arg, 0, CR_FLAGS_MASK & ~CR_EDGE); + } + break; + case TRIG_NONE: + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + } + + if (err) + return 3; + + /* Step 4: fix up any arguments. */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + /* Use two timers. */ + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= pci224_ao_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static void pci224_ao_start_pacer(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci224_private *devpriv = dev->private; + + /* + * The output of timer Z2-0 will be used as the scan trigger + * source. + */ + /* Make sure Z2-0 is gated on. */ + outb(pci224_gat_config(0, GAT_VCC), devpriv->iobase1 + PCI224_ZGAT_SCE); + /* Cascading with Z2-2. */ + /* Make sure Z2-2 is gated on. */ + outb(pci224_gat_config(2, GAT_VCC), devpriv->iobase1 + PCI224_ZGAT_SCE); + /* Z2-2 needs 10 MHz clock. */ + outb(pci224_clk_config(2, CLK_10MHZ), + devpriv->iobase1 + PCI224_ZCLK_SCE); + /* Z2-0 is clocked from Z2-2's output. */ + outb(pci224_clk_config(0, CLK_OUTNM1), + devpriv->iobase1 + PCI224_ZCLK_SCE); + + comedi_8254_pacer_enable(dev->pacer, 2, 0, false); +} + +static int pci224_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct pci224_board *board = dev->board_ptr; + struct pci224_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int range; + unsigned int i, j; + unsigned int ch; + unsigned int rank; + unsigned long flags; + + /* Cannot handle null/empty chanlist. */ + if (!cmd->chanlist || cmd->chanlist_len == 0) + return -EINVAL; + + /* Determine which channels are enabled and their load order. */ + devpriv->ao_enab = 0; + + for (i = 0; i < cmd->chanlist_len; i++) { + ch = CR_CHAN(cmd->chanlist[i]); + devpriv->ao_enab |= 1U << ch; + rank = 0; + for (j = 0; j < cmd->chanlist_len; j++) { + if (CR_CHAN(cmd->chanlist[j]) < ch) + rank++; + } + devpriv->ao_scan_order[rank] = i; + } + + /* Set enabled channels. */ + outw(devpriv->ao_enab, dev->iobase + PCI224_DACCEN); + + /* Determine range and polarity. All channels the same. */ + range = CR_RANGE(cmd->chanlist[0]); + + /* + * Set DAC range and polarity. + * Set DAC scan trigger source to 'none'. + * Set DAC FIFO interrupt trigger level to 'not half full'. + * Reset DAC FIFO. + * + * N.B. DAC FIFO interrupts are currently disabled. + */ + devpriv->daccon = + COMBINE(devpriv->daccon, + board->ao_hwrange[range] | PCI224_DACCON_TRIG_NONE | + PCI224_DACCON_FIFOINTR_NHALF, + PCI224_DACCON_POLAR_MASK | PCI224_DACCON_VREF_MASK | + PCI224_DACCON_TRIG_MASK | PCI224_DACCON_FIFOINTR_MASK); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); + + if (cmd->scan_begin_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + pci224_ao_start_pacer(dev, s); + } + + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + if (cmd->start_src == TRIG_INT) { + s->async->inttrig = pci224_ao_inttrig_start; + } else { /* TRIG_EXT */ + /* Enable external interrupt trigger to start acquisition. */ + devpriv->intsce |= PCI224_INTR_EXT; + outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE); + } + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + + return 0; +} + +/* + * 'cancel' function for AO subdevice. + */ +static int pci224_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pci224_ao_stop(dev, s); + return 0; +} + +/* + * 'munge' data for AO command. + */ +static void +pci224_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, unsigned int chan_index) +{ + const struct pci224_board *board = dev->board_ptr; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short *array = data; + unsigned int length = num_bytes / sizeof(*array); + unsigned int offset; + unsigned int shift; + unsigned int i; + + /* The hardware expects 16-bit numbers. */ + shift = 16 - board->ao_bits; + /* Channels will be all bipolar or all unipolar. */ + if ((board->ao_hwrange[CR_RANGE(cmd->chanlist[0])] & + PCI224_DACCON_POLAR_MASK) == PCI224_DACCON_POLAR_UNI) { + /* Unipolar */ + offset = 0; + } else { + /* Bipolar */ + offset = 32768; + } + /* Munge the data. */ + for (i = 0; i < length; i++) + array[i] = (array[i] << shift) - offset; +} + +/* + * Interrupt handler. + */ +static irqreturn_t pci224_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pci224_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_cmd *cmd; + unsigned char intstat, valid_intstat; + unsigned char curenab; + int retval = 0; + unsigned long flags; + + intstat = inb(devpriv->iobase1 + PCI224_INT_SCE) & 0x3F; + if (intstat) { + retval = 1; + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + valid_intstat = devpriv->intsce & intstat; + /* Temporarily disable interrupt sources. */ + curenab = devpriv->intsce & ~intstat; + outb(curenab, devpriv->iobase1 + PCI224_INT_SCE); + devpriv->intr_running = 1; + devpriv->intr_cpuid = THISCPU; + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + if (valid_intstat) { + cmd = &s->async->cmd; + if (valid_intstat & PCI224_INTR_EXT) { + devpriv->intsce &= ~PCI224_INTR_EXT; + if (cmd->start_src == TRIG_EXT) + pci224_ao_start(dev, s); + else if (cmd->stop_src == TRIG_EXT) + pci224_ao_stop(dev, s); + } + if (valid_intstat & PCI224_INTR_DAC) + pci224_ao_handle_fifo(dev, s); + } + /* Reenable interrupt sources. */ + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + if (curenab != devpriv->intsce) { + outb(devpriv->intsce, + devpriv->iobase1 + PCI224_INT_SCE); + } + devpriv->intr_running = 0; + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + } + return IRQ_RETVAL(retval); +} + +static int +pci224_auto_attach(struct comedi_device *dev, unsigned long context_model) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + const struct pci224_board *board = NULL; + struct pci224_private *devpriv; + struct comedi_subdevice *s; + unsigned int irq; + int ret; + + if (context_model < ARRAY_SIZE(pci224_boards)) + board = &pci224_boards[context_model]; + if (!board || !board->name) { + dev_err(dev->class_dev, + "amplc_pci224: BUG! cannot determine board type!\n"); + return -EINVAL; + } + dev->board_ptr = board; + dev->board_name = board->name; + + dev_info(dev->class_dev, "amplc_pci224: attach pci %s - %s\n", + pci_name(pci_dev), dev->board_name); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + spin_lock_init(&devpriv->ao_spinlock); + + devpriv->iobase1 = pci_resource_start(pci_dev, 2); + dev->iobase = pci_resource_start(pci_dev, 3); + irq = pci_dev->irq; + + /* Allocate buffer to hold values for AO channel scan. */ + devpriv->ao_scan_vals = kmalloc_array(board->ao_chans, + sizeof(devpriv->ao_scan_vals[0]), + GFP_KERNEL); + if (!devpriv->ao_scan_vals) + return -ENOMEM; + + /* Allocate buffer to hold AO channel scan order. */ + devpriv->ao_scan_order = + kmalloc_array(board->ao_chans, + sizeof(devpriv->ao_scan_order[0]), + GFP_KERNEL); + if (!devpriv->ao_scan_order) + return -ENOMEM; + + /* Disable interrupt sources. */ + devpriv->intsce = 0; + outb(0, devpriv->iobase1 + PCI224_INT_SCE); + + /* Initialize the DAC hardware. */ + outw(PCI224_DACCON_GLOBALRESET, dev->iobase + PCI224_DACCON); + outw(0, dev->iobase + PCI224_DACCEN); + outw(0, dev->iobase + PCI224_FIFOSIZ); + devpriv->daccon = PCI224_DACCON_TRIG_SW | PCI224_DACCON_POLAR_BI | + PCI224_DACCON_FIFOENAB | PCI224_DACCON_FIFOINTR_EMPTY; + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); + + dev->pacer = comedi_8254_init(devpriv->iobase1 + PCI224_Z2_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* Analog output subdevice. */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = board->ao_chans; + s->maxdata = (1 << board->ao_bits) - 1; + s->range_table = board->ao_range; + s->insn_write = pci224_ao_insn_write; + s->len_chanlist = s->n_chan; + dev->write_subdev = s; + s->do_cmd = pci224_ao_cmd; + s->do_cmdtest = pci224_ao_cmdtest; + s->cancel = pci224_ao_cancel; + s->munge = pci224_ao_munge; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + if (irq) { + ret = request_irq(irq, pci224_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret < 0) { + dev_err(dev->class_dev, + "error! unable to allocate irq %u\n", irq); + return ret; + } + dev->irq = irq; + } + + return 0; +} + +static void pci224_detach(struct comedi_device *dev) +{ + struct pci224_private *devpriv = dev->private; + + comedi_pci_detach(dev); + if (devpriv) { + kfree(devpriv->ao_scan_vals); + kfree(devpriv->ao_scan_order); + } +} + +static struct comedi_driver amplc_pci224_driver = { + .driver_name = "amplc_pci224", + .module = THIS_MODULE, + .detach = pci224_detach, + .auto_attach = pci224_auto_attach, + .board_name = &pci224_boards[0].name, + .offset = sizeof(struct pci224_board), + .num_names = ARRAY_SIZE(pci224_boards), +}; + +static int amplc_pci224_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &lc_pci224_driver, + id->driver_data); +} + +static const struct pci_device_id amplc_pci224_pci_table[] = { + { PCI_VDEVICE(AMPLICON, 0x0007), pci224_model }, + { PCI_VDEVICE(AMPLICON, 0x0008), pci234_model }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, amplc_pci224_pci_table); + +static struct pci_driver amplc_pci224_pci_driver = { + .name = "amplc_pci224", + .id_table = amplc_pci224_pci_table, + .probe = amplc_pci224_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(amplc_pci224_driver, amplc_pci224_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PCI224 and PCI234 AO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pci230.c b/drivers/comedi/drivers/amplc_pci230.c new file mode 100644 index 000000000..92ba8b8c0 --- /dev/null +++ b/drivers/comedi/drivers/amplc_pci230.c @@ -0,0 +1,2573 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: amplc_pci230 + * Description: Amplicon PCI230, PCI260 Multifunction I/O boards + * Author: Allan Willcox <allanwillcox@ozemail.com.au>, + * Steve D Sharples <steve.sharples@nottingham.ac.uk>, + * Ian Abbott <abbotti@mev.co.uk> + * Updated: Mon, 01 Sep 2014 10:09:16 +0000 + * Devices: [Amplicon] PCI230 (amplc_pci230), PCI230+, PCI260, PCI260+ + * Status: works + * + * Configuration options: + * none + * + * Manual configuration of PCI cards is not supported; they are configured + * automatically. + * + * The PCI230+ and PCI260+ have the same PCI device IDs as the PCI230 and + * PCI260, but can be distinguished by the size of the PCI regions. A + * card will be configured as a "+" model if detected as such. + * + * Subdevices: + * + * PCI230(+) PCI260(+) + * --------- --------- + * Subdevices 3 1 + * 0 AI AI + * 1 AO + * 2 DIO + * + * AI Subdevice: + * + * The AI subdevice has 16 single-ended channels or 8 differential + * channels. + * + * The PCI230 and PCI260 cards have 12-bit resolution. The PCI230+ and + * PCI260+ cards have 16-bit resolution. + * + * For differential mode, use inputs 2N and 2N+1 for channel N (e.g. use + * inputs 14 and 15 for channel 7). If the card is physically a PCI230 + * or PCI260 then it actually uses a "pseudo-differential" mode where the + * inputs are sampled a few microseconds apart. The PCI230+ and PCI260+ + * use true differential sampling. Another difference is that if the + * card is physically a PCI230 or PCI260, the inverting input is 2N, + * whereas for a PCI230+ or PCI260+ the inverting input is 2N+1. So if a + * PCI230 is physically replaced by a PCI230+ (or a PCI260 with a + * PCI260+) and differential mode is used, the differential inputs need + * to be physically swapped on the connector. + * + * The following input ranges are supported: + * + * 0 => [-10, +10] V + * 1 => [-5, +5] V + * 2 => [-2.5, +2.5] V + * 3 => [-1.25, +1.25] V + * 4 => [0, 10] V + * 5 => [0, 5] V + * 6 => [0, 2.5] V + * + * AI Commands: + * + * +=========+==============+===========+============+==========+ + * |start_src|scan_begin_src|convert_src|scan_end_src| stop_src | + * +=========+==============+===========+============+==========+ + * |TRIG_NOW | TRIG_FOLLOW |TRIG_TIMER | TRIG_COUNT |TRIG_NONE | + * |TRIG_INT | |TRIG_EXT(3)| |TRIG_COUNT| + * | | |TRIG_INT | | | + * | |--------------|-----------| | | + * | | TRIG_TIMER(1)|TRIG_TIMER | | | + * | | TRIG_EXT(2) | | | | + * | | TRIG_INT | | | | + * +---------+--------------+-----------+------------+----------+ + * + * Note 1: If AI command and AO command are used simultaneously, only + * one may have scan_begin_src == TRIG_TIMER. + * + * Note 2: For PCI230 and PCI230+, scan_begin_src == TRIG_EXT uses + * DIO channel 16 (pin 49) which will need to be configured as + * a digital input. For PCI260+, the EXTTRIG/EXTCONVCLK input + * (pin 17) is used instead. For PCI230, scan_begin_src == + * TRIG_EXT is not supported. The trigger is a rising edge + * on the input. + * + * Note 3: For convert_src == TRIG_EXT, the EXTTRIG/EXTCONVCLK input + * (pin 25 on PCI230(+), pin 17 on PCI260(+)) is used. The + * convert_arg value is interpreted as follows: + * + * convert_arg == (CR_EDGE | 0) => rising edge + * convert_arg == (CR_EDGE | CR_INVERT | 0) => falling edge + * convert_arg == 0 => falling edge (backwards compatibility) + * convert_arg == 1 => rising edge (backwards compatibility) + * + * All entries in the channel list must use the same analogue reference. + * If the analogue reference is not AREF_DIFF (not differential) each + * pair of channel numbers (0 and 1, 2 and 3, etc.) must use the same + * input range. The input ranges used in the sequence must be all + * bipolar (ranges 0 to 3) or all unipolar (ranges 4 to 6). The channel + * sequence must consist of 1 or more identical subsequences. Within the + * subsequence, channels must be in ascending order with no repeated + * channels. For example, the following sequences are valid: 0 1 2 3 + * (single valid subsequence), 0 2 3 5 0 2 3 5 (repeated valid + * subsequence), 1 1 1 1 (repeated valid subsequence). The following + * sequences are invalid: 0 3 2 1 (invalid subsequence), 0 2 3 5 0 2 3 + * (incompletely repeated subsequence). Some versions of the PCI230+ and + * PCI260+ have a bug that requires a subsequence longer than one entry + * long to include channel 0. + * + * AO Subdevice: + * + * The AO subdevice has 2 channels with 12-bit resolution. + * The following output ranges are supported: + * 0 => [0, 10] V + * 1 => [-10, +10] V + * + * AO Commands: + * + * +=========+==============+===========+============+==========+ + * |start_src|scan_begin_src|convert_src|scan_end_src| stop_src | + * +=========+==============+===========+============+==========+ + * |TRIG_INT | TRIG_TIMER(1)| TRIG_NOW | TRIG_COUNT |TRIG_NONE | + * | | TRIG_EXT(2) | | |TRIG_COUNT| + * | | TRIG_INT | | | | + * +---------+--------------+-----------+------------+----------+ + * + * Note 1: If AI command and AO command are used simultaneously, only + * one may have scan_begin_src == TRIG_TIMER. + * + * Note 2: scan_begin_src == TRIG_EXT is only supported if the card is + * configured as a PCI230+ and is only supported on later + * versions of the card. As a card configured as a PCI230+ is + * not guaranteed to support external triggering, please consider + * this support to be a bonus. It uses the EXTTRIG/ EXTCONVCLK + * input (PCI230+ pin 25). Triggering will be on the rising edge + * unless the CR_INVERT flag is set in scan_begin_arg. + * + * The channels in the channel sequence must be in ascending order with + * no repeats. All entries in the channel sequence must use the same + * output range. + * + * DIO Subdevice: + * + * The DIO subdevice is a 8255 chip providing 24 DIO channels. The DIO + * channels are configurable as inputs or outputs in four groups: + * + * Port A - channels 0 to 7 + * Port B - channels 8 to 15 + * Port CL - channels 16 to 19 + * Port CH - channels 20 to 23 + * + * Only mode 0 of the 8255 chip is supported. + * + * Bit 0 of port C (DIO channel 16) is also used as an external scan + * trigger input for AI commands on PCI230 and PCI230+, so would need to + * be configured as an input to use it for that purpose. + */ + +/* + * Extra triggered scan functionality, interrupt bug-fix added by Steve + * Sharples. Support for PCI230+/260+, more triggered scan functionality, + * and workarounds for (or detection of) various hardware problems added + * by Ian Abbott. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +/* + * PCI230 PCI configuration register information + */ +#define PCI_DEVICE_ID_PCI230 0x0000 +#define PCI_DEVICE_ID_PCI260 0x0006 + +/* + * PCI230 i/o space 1 registers. + */ +#define PCI230_PPI_X_BASE 0x00 /* User PPI (82C55) base */ +#define PCI230_PPI_X_A 0x00 /* User PPI (82C55) port A */ +#define PCI230_PPI_X_B 0x01 /* User PPI (82C55) port B */ +#define PCI230_PPI_X_C 0x02 /* User PPI (82C55) port C */ +#define PCI230_PPI_X_CMD 0x03 /* User PPI (82C55) control word */ +#define PCI230_Z2_CT_BASE 0x14 /* 82C54 counter/timer base */ +#define PCI230_ZCLK_SCE 0x1A /* Group Z Clock Configuration */ +#define PCI230_ZGAT_SCE 0x1D /* Group Z Gate Configuration */ +#define PCI230_INT_SCE 0x1E /* Interrupt source mask (w) */ +#define PCI230_INT_STAT 0x1E /* Interrupt status (r) */ + +/* + * PCI230 i/o space 2 registers. + */ +#define PCI230_DACCON 0x00 /* DAC control */ +#define PCI230_DACOUT1 0x02 /* DAC channel 0 (w) */ +#define PCI230_DACOUT2 0x04 /* DAC channel 1 (w) (not FIFO mode) */ +#define PCI230_ADCDATA 0x08 /* ADC data (r) */ +#define PCI230_ADCSWTRIG 0x08 /* ADC software trigger (w) */ +#define PCI230_ADCCON 0x0A /* ADC control */ +#define PCI230_ADCEN 0x0C /* ADC channel enable bits */ +#define PCI230_ADCG 0x0E /* ADC gain control bits */ +/* PCI230+ i/o space 2 additional registers. */ +#define PCI230P_ADCTRIG 0x10 /* ADC start acquisition trigger */ +#define PCI230P_ADCTH 0x12 /* ADC analog trigger threshold */ +#define PCI230P_ADCFFTH 0x14 /* ADC FIFO interrupt threshold */ +#define PCI230P_ADCFFLEV 0x16 /* ADC FIFO level (r) */ +#define PCI230P_ADCPTSC 0x18 /* ADC pre-trigger sample count (r) */ +#define PCI230P_ADCHYST 0x1A /* ADC analog trigger hysteresys */ +#define PCI230P_EXTFUNC 0x1C /* Extended functions */ +#define PCI230P_HWVER 0x1E /* Hardware version (r) */ +/* PCI230+ hardware version 2 onwards. */ +#define PCI230P2_DACDATA 0x02 /* DAC data (FIFO mode) (w) */ +#define PCI230P2_DACSWTRIG 0x02 /* DAC soft trigger (FIFO mode) (r) */ +#define PCI230P2_DACEN 0x06 /* DAC channel enable (FIFO mode) */ + +/* + * DACCON read-write values. + */ +#define PCI230_DAC_OR(x) (((x) & 0x1) << 0) +#define PCI230_DAC_OR_UNI PCI230_DAC_OR(0) /* Output unipolar */ +#define PCI230_DAC_OR_BIP PCI230_DAC_OR(1) /* Output bipolar */ +#define PCI230_DAC_OR_MASK PCI230_DAC_OR(1) +/* + * 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 BIT(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(x) (((x) & 0x7) << 2) +#define PCI230P2_DAC_TRIG_NONE PCI230P2_DAC_TRIG(0) /* none */ +#define PCI230P2_DAC_TRIG_SW PCI230P2_DAC_TRIG(1) /* soft trig */ +#define PCI230P2_DAC_TRIG_EXTP PCI230P2_DAC_TRIG(2) /* ext + edge */ +#define PCI230P2_DAC_TRIG_EXTN PCI230P2_DAC_TRIG(3) /* ext - edge */ +#define PCI230P2_DAC_TRIG_Z2CT0 PCI230P2_DAC_TRIG(4) /* Z2 CT0 out */ +#define PCI230P2_DAC_TRIG_Z2CT1 PCI230P2_DAC_TRIG(5) /* Z2 CT1 out */ +#define PCI230P2_DAC_TRIG_Z2CT2 PCI230P2_DAC_TRIG(6) /* Z2 CT2 out */ +#define PCI230P2_DAC_TRIG_MASK PCI230P2_DAC_TRIG(7) +#define PCI230P2_DAC_FIFO_WRAP BIT(7) /* FIFO wraparound mode */ +#define PCI230P2_DAC_INT_FIFO(x) (((x) & 7) << 9) +#define PCI230P2_DAC_INT_FIFO_EMPTY PCI230P2_DAC_INT_FIFO(0) /* empty */ +#define PCI230P2_DAC_INT_FIFO_NEMPTY PCI230P2_DAC_INT_FIFO(1) /* !empty */ +#define PCI230P2_DAC_INT_FIFO_NHALF PCI230P2_DAC_INT_FIFO(2) /* !half */ +#define PCI230P2_DAC_INT_FIFO_HALF PCI230P2_DAC_INT_FIFO(3) /* half */ +#define PCI230P2_DAC_INT_FIFO_NFULL PCI230P2_DAC_INT_FIFO(4) /* !full */ +#define PCI230P2_DAC_INT_FIFO_FULL PCI230P2_DAC_INT_FIFO(5) /* full */ +#define PCI230P2_DAC_INT_FIFO_MASK PCI230P2_DAC_INT_FIFO(7) + +/* + * DACCON read-only values. + */ +#define PCI230_DAC_BUSY BIT(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 BIT(5) /* Underrun error */ +#define PCI230P2_DAC_FIFO_EMPTY BIT(13) /* FIFO empty */ +#define PCI230P2_DAC_FIFO_FULL BIT(14) /* FIFO full */ +#define PCI230P2_DAC_FIFO_HALF BIT(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 BIT(5) /* Clear underrun */ +#define PCI230P2_DAC_FIFO_RESET BIT(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(x) (((x) & 0x7) << 0) +#define PCI230_ADC_TRIG_NONE PCI230_ADC_TRIG(0) /* none */ +#define PCI230_ADC_TRIG_SW PCI230_ADC_TRIG(1) /* soft trig */ +#define PCI230_ADC_TRIG_EXTP PCI230_ADC_TRIG(2) /* ext + edge */ +#define PCI230_ADC_TRIG_EXTN PCI230_ADC_TRIG(3) /* ext - edge */ +#define PCI230_ADC_TRIG_Z2CT0 PCI230_ADC_TRIG(4) /* Z2 CT0 out*/ +#define PCI230_ADC_TRIG_Z2CT1 PCI230_ADC_TRIG(5) /* Z2 CT1 out */ +#define PCI230_ADC_TRIG_Z2CT2 PCI230_ADC_TRIG(6) /* Z2 CT2 out */ +#define PCI230_ADC_TRIG_MASK PCI230_ADC_TRIG(7) +#define PCI230_ADC_IR(x) (((x) & 0x1) << 3) +#define PCI230_ADC_IR_UNI PCI230_ADC_IR(0) /* Input unipolar */ +#define PCI230_ADC_IR_BIP PCI230_ADC_IR(1) /* Input bipolar */ +#define PCI230_ADC_IR_MASK PCI230_ADC_IR(1) +#define PCI230_ADC_IM(x) (((x) & 0x1) << 4) +#define PCI230_ADC_IM_SE PCI230_ADC_IM(0) /* single ended */ +#define PCI230_ADC_IM_DIF PCI230_ADC_IM(1) /* differential */ +#define PCI230_ADC_IM_MASK PCI230_ADC_IM(1) +#define PCI230_ADC_FIFO_EN BIT(8) /* FIFO enable */ +#define PCI230_ADC_INT_FIFO(x) (((x) & 0x7) << 9) +#define PCI230_ADC_INT_FIFO_EMPTY PCI230_ADC_INT_FIFO(0) /* empty */ +#define PCI230_ADC_INT_FIFO_NEMPTY PCI230_ADC_INT_FIFO(1) /* !empty */ +#define PCI230_ADC_INT_FIFO_NHALF PCI230_ADC_INT_FIFO(2) /* !half */ +#define PCI230_ADC_INT_FIFO_HALF PCI230_ADC_INT_FIFO(3) /* half */ +#define PCI230_ADC_INT_FIFO_NFULL PCI230_ADC_INT_FIFO(4) /* !full */ +#define PCI230_ADC_INT_FIFO_FULL PCI230_ADC_INT_FIFO(5) /* full */ +#define PCI230P_ADC_INT_FIFO_THRESH PCI230_ADC_INT_FIFO(7) /* threshold */ +#define PCI230_ADC_INT_FIFO_MASK PCI230_ADC_INT_FIFO(7) + +/* + * ADCCON write-only, transient values. + */ +#define PCI230_ADC_FIFO_RESET BIT(12) /* FIFO reset */ +#define PCI230_ADC_GLOB_RESET BIT(13) /* Global reset */ + +/* + * ADCCON read-only values. + */ +#define PCI230_ADC_BUSY BIT(15) /* ADC busy */ +#define PCI230_ADC_FIFO_EMPTY BIT(12) /* FIFO empty */ +#define PCI230_ADC_FIFO_FULL BIT(13) /* FIFO full */ +#define PCI230_ADC_FIFO_HALF BIT(14) /* FIFO half full */ +#define PCI230_ADC_FIFO_FULL_LATCHED BIT(5) /* FIFO overrun occurred */ + +/* + * PCI230 ADC FIFO levels. + */ +#define PCI230_ADC_FIFOLEVEL_HALFFULL 2049 /* Value for FIFO half full */ +#define PCI230_ADC_FIFOLEVEL_FULL 4096 /* FIFO size */ + +/* + * PCI230+ EXTFUNC values. + */ +/* Route EXTTRIG pin to external gate inputs. */ +#define PCI230P_EXTFUNC_GAT_EXTTRIG BIT(0) +/* PCI230+ hardware version 2 values. */ +/* Allow DAC FIFO to be enabled. */ +#define PCI230P2_EXTFUNC_DACFIFO BIT(1) + +/* + * Counter/timer clock input configuration sources. + */ +#define CLK_CLK 0 /* reserved (channel-specific clock) */ +#define CLK_10MHZ 1 /* internal 10 MHz clock */ +#define CLK_1MHZ 2 /* internal 1 MHz clock */ +#define CLK_100KHZ 3 /* internal 100 kHz clock */ +#define CLK_10KHZ 4 /* internal 10 kHz clock */ +#define CLK_1KHZ 5 /* internal 1 kHz clock */ +#define CLK_OUTNM1 6 /* output of channel-1 modulo total */ +#define CLK_EXT 7 /* external clock */ + +static unsigned int pci230_clk_config(unsigned int chan, unsigned int src) +{ + return ((chan & 3) << 3) | (src & 7); +} + +/* + * Counter/timer gate input configuration sources. + */ +#define GAT_VCC 0 /* VCC (i.e. enabled) */ +#define GAT_GND 1 /* GND (i.e. disabled) */ +#define GAT_EXT 2 /* external gate input (PPCn on PCI230) */ +#define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */ + +static unsigned int pci230_gat_config(unsigned int chan, unsigned int src) +{ + return ((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 BIT(0) +#define PCI230_INT_PPI_C3 BIT(1) +#define PCI230_INT_ADC BIT(2) +#define PCI230_INT_ZCLK_CT1 BIT(5) +/* For PCI230+ hardware version 2 when DAC FIFO enabled. */ +#define PCI230P2_INT_DAC BIT(4) + +/* + * (Potentially) shared resources and their owners + */ +enum { + RES_Z2CT0 = BIT(0), /* Z2-CT0 */ + RES_Z2CT1 = BIT(1), /* Z2-CT1 */ + RES_Z2CT2 = BIT(2) /* Z2-CT2 */ +}; + +enum { + OWNER_AICMD, /* Owned by AI command */ + OWNER_AOCMD, /* Owned by AO command */ + NUM_OWNERS /* Number of owners */ +}; + +/* + * Handy macros. + */ + +/* Combine old and new bits. */ +#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask))) + +/* Current CPU. XXX should this be hard_smp_processor_id()? */ +#define THISCPU smp_processor_id() + +/* + * Board descriptions for the two boards supported. + */ + +struct pci230_board { + const char *name; + unsigned short id; + unsigned char ai_bits; + unsigned char ao_bits; + unsigned char min_hwver; /* Minimum hardware version supported. */ + unsigned int have_dio:1; +}; + +static const struct pci230_board pci230_boards[] = { + { + .name = "pci230+", + .id = PCI_DEVICE_ID_PCI230, + .ai_bits = 16, + .ao_bits = 12, + .have_dio = true, + .min_hwver = 1, + }, + { + .name = "pci260+", + .id = PCI_DEVICE_ID_PCI260, + .ai_bits = 16, + .min_hwver = 1, + }, + { + .name = "pci230", + .id = PCI_DEVICE_ID_PCI230, + .ai_bits = 12, + .ao_bits = 12, + .have_dio = true, + }, + { + .name = "pci260", + .id = PCI_DEVICE_ID_PCI260, + .ai_bits = 12, + }, +}; + +struct pci230_private { + spinlock_t isr_spinlock; /* Interrupt spin lock */ + spinlock_t res_spinlock; /* Shared resources spin lock */ + spinlock_t ai_stop_spinlock; /* Spin lock for stopping AI command */ + spinlock_t ao_stop_spinlock; /* Spin lock for stopping AO command */ + unsigned long daqio; /* PCI230's DAQ I/O space */ + int intr_cpuid; /* ID of CPU running ISR */ + unsigned short hwver; /* Hardware version (for '+' models) */ + unsigned short adccon; /* ADCCON register value */ + unsigned short daccon; /* DACCON register value */ + unsigned short adcfifothresh; /* ADC FIFO threshold (PCI230+/260+) */ + unsigned short adcg; /* ADCG register value */ + unsigned char ier; /* Interrupt enable bits */ + unsigned char res_owned[NUM_OWNERS]; /* Owned resources */ + unsigned int intr_running:1; /* Flag set in interrupt routine */ + unsigned int ai_bipolar:1; /* Flag AI range is bipolar */ + unsigned int ao_bipolar:1; /* Flag AO range is bipolar */ + unsigned int ai_cmd_started:1; /* Flag AI command started */ + unsigned int ao_cmd_started:1; /* Flag AO command started */ +}; + +/* PCI230 clock source periods in ns */ +static const unsigned int pci230_timebase[8] = { + [CLK_10MHZ] = I8254_OSC_BASE_10MHZ, + [CLK_1MHZ] = I8254_OSC_BASE_1MHZ, + [CLK_100KHZ] = I8254_OSC_BASE_100KHZ, + [CLK_10KHZ] = I8254_OSC_BASE_10KHZ, + [CLK_1KHZ] = I8254_OSC_BASE_1KHZ, +}; + +/* PCI230 analogue input range table */ +static const struct comedi_lrange pci230_ai_range = { + 7, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5) + } +}; + +/* PCI230 analogue gain bits for each input range. */ +static const unsigned char pci230_ai_gain[7] = { 0, 1, 2, 3, 1, 2, 3 }; + +/* PCI230 analogue output range table */ +static const struct comedi_lrange pci230_ao_range = { + 2, { + UNI_RANGE(10), + BIP_RANGE(10) + } +}; + +static unsigned short pci230_ai_read(struct comedi_device *dev) +{ + const struct pci230_board *board = dev->board_ptr; + struct pci230_private *devpriv = dev->private; + unsigned short data; + + /* Read sample. */ + data = inw(devpriv->daqio + PCI230_ADCDATA); + /* + * PCI230 is 12 bit - stored in upper bits of 16 bit register + * (lower four bits reserved for expansion). PCI230+ is 16 bit AI. + * + * If a bipolar range was specified, mangle it + * (twos complement->straight binary). + */ + if (devpriv->ai_bipolar) + data ^= 0x8000; + data >>= (16 - board->ai_bits); + return data; +} + +static unsigned short pci230_ao_mangle_datum(struct comedi_device *dev, + unsigned short datum) +{ + const struct pci230_board *board = dev->board_ptr; + struct pci230_private *devpriv = dev->private; + + /* + * PCI230 is 12 bit - stored in upper bits of 16 bit register (lower + * four bits reserved for expansion). PCI230+ is also 12 bit AO. + */ + datum <<= (16 - board->ao_bits); + /* + * If a bipolar range was specified, mangle it + * (straight binary->twos complement). + */ + if (devpriv->ao_bipolar) + datum ^= 0x8000; + return datum; +} + +static void pci230_ao_write_nofifo(struct comedi_device *dev, + unsigned short datum, unsigned int chan) +{ + struct pci230_private *devpriv = dev->private; + + /* Write mangled datum to appropriate DACOUT register. */ + outw(pci230_ao_mangle_datum(dev, datum), + devpriv->daqio + ((chan == 0) ? PCI230_DACOUT1 : PCI230_DACOUT2)); +} + +static void pci230_ao_write_fifo(struct comedi_device *dev, + unsigned short datum, unsigned int chan) +{ + struct pci230_private *devpriv = dev->private; + + /* Write mangled datum to appropriate DACDATA register. */ + outw(pci230_ao_mangle_datum(dev, datum), + devpriv->daqio + PCI230P2_DACDATA); +} + +static bool pci230_claim_shared(struct comedi_device *dev, + unsigned char res_mask, unsigned int owner) +{ + struct pci230_private *devpriv = dev->private; + unsigned int o; + unsigned long irqflags; + + spin_lock_irqsave(&devpriv->res_spinlock, irqflags); + for (o = 0; o < NUM_OWNERS; o++) { + if (o == owner) + continue; + if (devpriv->res_owned[o] & res_mask) { + spin_unlock_irqrestore(&devpriv->res_spinlock, + irqflags); + return false; + } + } + devpriv->res_owned[owner] |= res_mask; + spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); + return true; +} + +static void pci230_release_shared(struct comedi_device *dev, + unsigned char res_mask, unsigned int owner) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + + spin_lock_irqsave(&devpriv->res_spinlock, irqflags); + devpriv->res_owned[owner] &= ~res_mask; + spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); +} + +static void pci230_release_all_resources(struct comedi_device *dev, + unsigned int owner) +{ + pci230_release_shared(dev, (unsigned char)~0, owner); +} + +static unsigned int pci230_divide_ns(u64 ns, unsigned int timebase, + unsigned int flags) +{ + u64 div; + unsigned int rem; + + div = ns; + rem = do_div(div, timebase); + switch (flags & CMDF_ROUND_MASK) { + default: + case CMDF_ROUND_NEAREST: + div += DIV_ROUND_CLOSEST(rem, timebase); + break; + case CMDF_ROUND_DOWN: + break; + case CMDF_ROUND_UP: + div += DIV_ROUND_UP(rem, 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(u64 ns, unsigned int *count, + unsigned int flags) +{ + unsigned int clk_src, cnt; + + for (clk_src = CLK_10MHZ;; clk_src++) { + cnt = pci230_divide_ns(ns, pci230_timebase[clk_src], flags); + if (cnt <= 65536 || clk_src == CLK_1KHZ) + break; + } + *count = cnt; + return clk_src; +} + +static void pci230_ns_to_single_timer(unsigned int *ns, unsigned int flags) +{ + unsigned int count; + unsigned int clk_src; + + clk_src = pci230_choose_clk_count(*ns, &count, flags); + *ns = count * pci230_timebase[clk_src]; +} + +static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct, + unsigned int mode, u64 ns, + unsigned int flags) +{ + unsigned int clk_src; + unsigned int count; + + /* Set mode. */ + comedi_8254_set_mode(dev->pacer, ct, mode); + /* Determine clock source and count. */ + clk_src = pci230_choose_clk_count(ns, &count, flags); + /* Program clock source. */ + outb(pci230_clk_config(ct, clk_src), dev->iobase + PCI230_ZCLK_SCE); + /* Set initial count. */ + if (count >= 65536) + count = 0; + + comedi_8254_write(dev->pacer, ct, count); +} + +static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct) +{ + /* Counter ct, 8254 mode 1, initial count not written. */ + comedi_8254_set_mode(dev->pacer, ct, I8254_MODE1); +} + +static int pci230_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + struct pci230_private *devpriv = dev->private; + unsigned int status; + + status = inw(devpriv->daqio + PCI230_ADCCON); + if ((status & PCI230_ADC_FIFO_EMPTY) == 0) + return 0; + return -EBUSY; +} + +static int pci230_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct pci230_private *devpriv = dev->private; + unsigned int n; + unsigned int chan, range, aref; + unsigned int gainshift; + unsigned short adccon, adcen; + int ret; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + aref = CR_AREF(insn->chanspec); + if (aref == AREF_DIFF) { + /* Differential. */ + if (chan >= s->n_chan / 2) { + dev_dbg(dev->class_dev, + "%s: differential channel number out of range 0 to %u\n", + __func__, (s->n_chan / 2) - 1); + return -EINVAL; + } + } + + /* + * Use Z2-CT2 as a conversion trigger instead of the built-in + * software trigger, as otherwise triggering of differential channels + * doesn't work properly for some versions of PCI230/260. Also set + * FIFO mode because the ADC busy bit only works for software triggers. + */ + adccon = PCI230_ADC_TRIG_Z2CT2 | PCI230_ADC_FIFO_EN; + /* Set Z2-CT2 output low to avoid any false triggers. */ + comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0); + devpriv->ai_bipolar = comedi_range_is_bipolar(s, range); + if (aref == AREF_DIFF) { + /* Differential. */ + gainshift = chan * 2; + if (devpriv->hwver == 0) { + /* + * Original PCI230/260 expects both inputs of the + * differential channel to be enabled. + */ + adcen = 3 << gainshift; + } else { + /* + * PCI230+/260+ expects only one input of the + * differential channel to be enabled. + */ + adcen = 1 << gainshift; + } + adccon |= PCI230_ADC_IM_DIF; + } else { + /* Single ended. */ + adcen = 1 << chan; + gainshift = chan & ~1; + adccon |= PCI230_ADC_IM_SE; + } + devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) | + (pci230_ai_gain[range] << gainshift); + if (devpriv->ai_bipolar) + adccon |= PCI230_ADC_IR_BIP; + else + adccon |= PCI230_ADC_IR_UNI; + + /* + * Enable only this channel in the scan list - otherwise by default + * we'll get one sample from each channel. + */ + outw(adcen, devpriv->daqio + PCI230_ADCEN); + + /* Set gain for channel. */ + outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG); + + /* Specify uni/bip, se/diff, conversion source, and reset FIFO. */ + devpriv->adccon = adccon; + outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON); + + /* Convert n samples */ + for (n = 0; n < insn->n; n++) { + /* + * Trigger conversion by toggling Z2-CT2 output + * (finish with output high). + */ + comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0); + comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1); + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, pci230_ai_eoc, 0); + if (ret) + return ret; + + /* read data */ + data[n] = pci230_ai_read(dev); + } + + /* return the number of samples read/written */ + return n; +} + +static int pci230_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci230_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + /* + * Set range - see analogue output range table; 0 => unipolar 10V, + * 1 => bipolar +/-10V range scale + */ + devpriv->ao_bipolar = comedi_range_is_bipolar(s, range); + outw(range, devpriv->daqio + PCI230_DACCON); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + pci230_ao_write_nofifo(dev, val, chan); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pci230_ao_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int prev_chan = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + if (chan < prev_chan) { + dev_dbg(dev->class_dev, + "%s: channel numbers must increase\n", + __func__); + return -EINVAL; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "%s: channels must have the same range\n", + __func__); + return -EINVAL; + } + + prev_chan = chan; + } + + return 0; +} + +static int pci230_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct pci230_board *board = dev->board_ptr; + struct pci230_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); + + tmp = TRIG_TIMER | TRIG_INT; + if (board->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 board->min_hwver + * (the static capabilities of the configured card). To fix + * it, a new card model, e.g. "pci230+2" would have to be + * defined with min_hwver set to 2. It doesn't seem worth it + * for this alone. At the moment, please consider + * scan_begin_src==TRIG_EXT support to be a bonus rather than a + * guarantee! + */ + tmp |= TRIG_EXT; + } + err |= comedi_check_trigger_src(&cmd->scan_begin_src, tmp); + + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED_AO 8000 /* 8000 ns => 125 kHz */ +/* + * Comedi limit due to unsigned int cmd. Driver limit = + * 2^16 (16bit * counter) * 1000000ns (1kHz onboard clock) = 65.536s + */ +#define MIN_SPEED_AO 4294967295u /* 4294967295ns = 4.29s */ + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SPEED_AO); + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + MIN_SPEED_AO); + break; + case TRIG_EXT: + /* + * External trigger - for PCI230+ hardware version 2 onwards. + */ + /* Trigger number must be 0. */ + if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + ~CR_FLAGS_MASK); + err |= -EINVAL; + } + /* + * The only flags allowed are CR_EDGE and CR_INVERT. + * The CR_EDGE flag is ignored. + */ + if (cmd->scan_begin_arg & CR_FLAGS_MASK & + ~(CR_EDGE | CR_INVERT)) { + cmd->scan_begin_arg = + COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT)); + err |= -EINVAL; + } + break; + default: + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + break; + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + pci230_ns_to_single_timer(&cmd->scan_begin_arg, cmd->flags); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= pci230_ao_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static void pci230_ao_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + unsigned char intsrc; + bool started; + struct comedi_cmd *cmd; + + spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); + started = devpriv->ao_cmd_started; + devpriv->ao_cmd_started = false; + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags); + if (!started) + return; + cmd = &s->async->cmd; + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Stop scan rate generator. */ + pci230_cancel_ct(dev, 1); + } + /* Determine interrupt source. */ + if (devpriv->hwver < 2) { + /* Not using DAC FIFO. Using CT1 interrupt. */ + intsrc = PCI230_INT_ZCLK_CT1; + } else { + /* Using DAC FIFO interrupt. */ + intsrc = PCI230P2_INT_DAC; + } + /* + * Disable interrupt and wait for interrupt routine to finish running + * unless we are called from the interrupt routine. + */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->ier &= ~intsrc; + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + } + outb(devpriv->ier, dev->iobase + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + if (devpriv->hwver >= 2) { + /* + * Using DAC FIFO. Reset FIFO, clear underrun error, + * disable FIFO. + */ + devpriv->daccon &= PCI230_DAC_OR_MASK; + outw(devpriv->daccon | PCI230P2_DAC_FIFO_RESET | + PCI230P2_DAC_FIFO_UNDERRUN_CLEAR, + devpriv->daqio + PCI230_DACCON); + } + /* Release resources. */ + pci230_release_all_resources(dev, OWNER_AOCMD); +} + +static void pci230_handle_ao_nofifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned short data; + int i; + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + return; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (!comedi_buf_read_samples(s, &data, 1)) { + async->events |= COMEDI_CB_OVERFLOW; + return; + } + pci230_ao_write_nofifo(dev, data, chan); + s->readback[chan] = data; + } + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; +} + +/* + * Loads DAC FIFO (if using it) from buffer. + * Returns false if AO finished due to completion or error, true if still going. + */ +static bool pci230_handle_ao_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int num_scans = comedi_nscans_left(s, 0); + unsigned int room; + unsigned short dacstat; + unsigned int i, n; + unsigned int events = 0; + + /* Get DAC FIFO status. */ + dacstat = inw(devpriv->daqio + PCI230_DACCON); + + if (cmd->stop_src == TRIG_COUNT && num_scans == 0) + events |= COMEDI_CB_EOA; + + if (events == 0) { + /* Check for FIFO underrun. */ + if (dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) { + dev_err(dev->class_dev, "AO FIFO underrun\n"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + /* + * Check for buffer underrun if FIFO less than half full + * (otherwise there will be loads of "DAC FIFO not half full" + * interrupts). + */ + if (num_scans == 0 && + (dacstat & PCI230P2_DAC_FIFO_HALF) == 0) { + dev_err(dev->class_dev, "AO buffer underrun\n"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + } + if (events == 0) { + /* Determine how much room is in the FIFO (in samples). */ + if (dacstat & PCI230P2_DAC_FIFO_FULL) + room = PCI230P2_DAC_FIFOROOM_FULL; + else if (dacstat & PCI230P2_DAC_FIFO_HALF) + room = PCI230P2_DAC_FIFOROOM_HALFTOFULL; + else if (dacstat & PCI230P2_DAC_FIFO_EMPTY) + room = PCI230P2_DAC_FIFOROOM_EMPTY; + else + room = PCI230P2_DAC_FIFOROOM_ONETOHALF; + /* Convert room to number of scans that can be added. */ + room /= cmd->chanlist_len; + /* Determine number of scans to process. */ + if (num_scans > room) + num_scans = room; + /* Process scans. */ + for (n = 0; n < num_scans; n++) { + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned short datum; + + comedi_buf_read_samples(s, &datum, 1); + pci230_ao_write_fifo(dev, datum, chan); + s->readback[chan] = datum; + } + } + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + /* + * All data for the command has been written + * to FIFO. Set FIFO interrupt trigger level + * to 'empty'. + */ + devpriv->daccon &= ~PCI230P2_DAC_INT_FIFO_MASK; + devpriv->daccon |= PCI230P2_DAC_INT_FIFO_EMPTY; + outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON); + } + /* Check if FIFO underrun occurred while writing to FIFO. */ + dacstat = inw(devpriv->daqio + PCI230_DACCON); + if (dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) { + dev_err(dev->class_dev, "AO FIFO underrun\n"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + } + async->events |= events; + return !(async->events & COMEDI_CB_CANCEL_MASK); +} + +static int pci230_ao_inttrig_scan_begin(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + + if (trig_num) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); + if (!devpriv->ao_cmd_started) { + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags); + return 1; + } + /* Perform scan. */ + if (devpriv->hwver < 2) { + /* Not using DAC FIFO. */ + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags); + pci230_handle_ao_nofifo(dev, s); + comedi_handle_events(dev, s); + } else { + /* Using DAC FIFO. */ + /* Read DACSWTRIG register to trigger conversion. */ + inw(devpriv->daqio + PCI230P2_DACSWTRIG); + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags); + } + /* Delay. Should driver be responsible for this? */ + /* XXX TODO: See if DAC busy bit can be used. */ + udelay(8); + return 1; +} + +static void pci230_ao_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long irqflags; + + devpriv->ao_cmd_started = true; + + if (devpriv->hwver >= 2) { + /* Using DAC FIFO. */ + unsigned short scantrig; + bool run; + + /* Preload FIFO data. */ + run = pci230_handle_ao_fifo(dev, s); + comedi_handle_events(dev, s); + if (!run) { + /* Stopped. */ + return; + } + /* Set scan trigger source. */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + scantrig = PCI230P2_DAC_TRIG_Z2CT1; + break; + case TRIG_EXT: + /* Trigger on EXTTRIG/EXTCONVCLK pin. */ + if ((cmd->scan_begin_arg & CR_INVERT) == 0) { + /* +ve edge */ + scantrig = PCI230P2_DAC_TRIG_EXTP; + } else { + /* -ve edge */ + scantrig = PCI230P2_DAC_TRIG_EXTN; + } + break; + case TRIG_INT: + scantrig = PCI230P2_DAC_TRIG_SW; + break; + default: + /* Shouldn't get here. */ + scantrig = PCI230P2_DAC_TRIG_NONE; + break; + } + devpriv->daccon = + (devpriv->daccon & ~PCI230P2_DAC_TRIG_MASK) | scantrig; + outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON); + } + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + if (devpriv->hwver < 2) { + /* Not using DAC FIFO. */ + /* Enable CT1 timer interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->ier |= PCI230_INT_ZCLK_CT1; + outb(devpriv->ier, dev->iobase + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, + irqflags); + } + /* Set CT1 gate high to start counting. */ + outb(pci230_gat_config(1, GAT_VCC), + dev->iobase + PCI230_ZGAT_SCE); + break; + case TRIG_INT: + async->inttrig = pci230_ao_inttrig_scan_begin; + break; + } + if (devpriv->hwver >= 2) { + /* Using DAC FIFO. Enable DAC FIFO interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->ier |= PCI230P2_INT_DAC; + outb(devpriv->ier, dev->iobase + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + } +} + +static int pci230_ao_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_src) + return -EINVAL; + + s->async->inttrig = NULL; + pci230_ao_start(dev, s); + + return 1; +} + +static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + unsigned short daccon; + unsigned int range; + + /* Get the command. */ + struct comedi_cmd *cmd = &s->async->cmd; + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Claim Z2-CT1. */ + if (!pci230_claim_shared(dev, RES_Z2CT1, OWNER_AOCMD)) + return -EBUSY; + } + + /* + * Set range - see analogue output range table; 0 => unipolar 10V, + * 1 => bipolar +/-10V range scale + */ + range = CR_RANGE(cmd->chanlist[0]); + devpriv->ao_bipolar = comedi_range_is_bipolar(s, range); + daccon = devpriv->ao_bipolar ? PCI230_DAC_OR_BIP : PCI230_DAC_OR_UNI; + /* Use DAC FIFO for hardware version 2 onwards. */ + if (devpriv->hwver >= 2) { + unsigned short dacen; + unsigned int i; + + dacen = 0; + for (i = 0; i < cmd->chanlist_len; i++) + dacen |= 1 << CR_CHAN(cmd->chanlist[i]); + + /* Set channel scan list. */ + outw(dacen, devpriv->daqio + PCI230P2_DACEN); + /* + * Enable DAC FIFO. + * Set DAC scan source to 'none'. + * Set DAC FIFO interrupt trigger level to 'not half full'. + * Reset DAC FIFO and clear underrun. + * + * N.B. DAC FIFO interrupts are currently disabled. + */ + daccon |= PCI230P2_DAC_FIFO_EN | PCI230P2_DAC_FIFO_RESET | + PCI230P2_DAC_FIFO_UNDERRUN_CLEAR | + PCI230P2_DAC_TRIG_NONE | PCI230P2_DAC_INT_FIFO_NHALF; + } + + /* Set DACCON. */ + outw(daccon, devpriv->daqio + PCI230_DACCON); + /* Preserve most of DACCON apart from write-only, transient bits. */ + devpriv->daccon = daccon & ~(PCI230P2_DAC_FIFO_RESET | + PCI230P2_DAC_FIFO_UNDERRUN_CLEAR); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* + * Set the counter timer 1 to the specified scan frequency. + * cmd->scan_begin_arg is sampling period in ns. + * Gate it off for now. + */ + outb(pci230_gat_config(1, GAT_GND), + dev->iobase + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, + cmd->scan_begin_arg, + cmd->flags); + } + + /* N.B. cmd->start_src == TRIG_INT */ + s->async->inttrig = pci230_ao_inttrig_start; + + return 0; +} + +static int pci230_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pci230_ao_stop(dev, s); + return 0; +} + +static int pci230_ai_check_scan_period(struct comedi_cmd *cmd) +{ + unsigned int min_scan_period, chanlist_len; + int err = 0; + + chanlist_len = cmd->chanlist_len; + if (cmd->chanlist_len == 0) + chanlist_len = 1; + + min_scan_period = chanlist_len * cmd->convert_arg; + if (min_scan_period < chanlist_len || + min_scan_period < cmd->convert_arg) { + /* Arithmetic overflow. */ + min_scan_period = UINT_MAX; + err++; + } + if (cmd->scan_begin_arg < min_scan_period) { + cmd->scan_begin_arg = min_scan_period; + err++; + } + + return !err; +} + +static int pci230_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct pci230_private *devpriv = dev->private; + unsigned int max_diff_chan = (s->n_chan / 2) - 1; + unsigned int prev_chan = 0; + unsigned int prev_range = 0; + unsigned int prev_aref = 0; + bool prev_bipolar = false; + unsigned int subseq_len = 0; + int i; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chanspec = cmd->chanlist[i]; + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + bool bipolar = comedi_range_is_bipolar(s, range); + + if (aref == AREF_DIFF && chan >= max_diff_chan) { + dev_dbg(dev->class_dev, + "%s: differential channel number out of range 0 to %u\n", + __func__, max_diff_chan); + return -EINVAL; + } + + if (i > 0) { + /* + * Channel numbers must strictly increase or + * subsequence must repeat exactly. + */ + if (chan <= prev_chan && subseq_len == 0) + subseq_len = i; + + if (subseq_len > 0 && + cmd->chanlist[i % subseq_len] != chanspec) { + dev_dbg(dev->class_dev, + "%s: channel numbers must increase or sequence must repeat exactly\n", + __func__); + return -EINVAL; + } + + if (aref != prev_aref) { + dev_dbg(dev->class_dev, + "%s: channel sequence analogue references must be all the same (single-ended or differential)\n", + __func__); + return -EINVAL; + } + + if (bipolar != prev_bipolar) { + dev_dbg(dev->class_dev, + "%s: channel sequence ranges must be all bipolar or all unipolar\n", + __func__); + return -EINVAL; + } + + if (aref != AREF_DIFF && range != prev_range && + ((chan ^ prev_chan) & ~1) == 0) { + dev_dbg(dev->class_dev, + "%s: single-ended channel pairs must have the same range\n", + __func__); + return -EINVAL; + } + } + prev_chan = chan; + prev_range = range; + prev_aref = aref; + prev_bipolar = bipolar; + } + + if (subseq_len == 0) + subseq_len = cmd->chanlist_len; + + if (cmd->chanlist_len % subseq_len) { + dev_dbg(dev->class_dev, + "%s: sequence must repeat exactly\n", __func__); + return -EINVAL; + } + + /* + * Buggy PCI230+ or PCI260+ requires channel 0 to be (first) in the + * sequence if the sequence contains more than one channel. Hardware + * versions 1 and 2 have the bug. There is no hardware version 3. + * + * Actually, there are two firmwares that report themselves as + * hardware version 1 (the boards have different ADC chips with + * slightly different timing requirements, which was supposed to + * be invisible to software). The first one doesn't seem to have + * the bug, but the second one does, and we can't tell them apart! + */ + if (devpriv->hwver > 0 && devpriv->hwver < 4) { + if (subseq_len > 1 && CR_CHAN(cmd->chanlist[0])) { + dev_info(dev->class_dev, + "amplc_pci230: ai_cmdtest: Buggy PCI230+/260+ h/w version %u requires first channel of multi-channel sequence to be 0 (corrected in h/w version 4)\n", + devpriv->hwver); + return -EINVAL; + } + } + + return 0; +} + +static int pci230_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct pci230_board *board = dev->board_ptr; + struct pci230_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + + tmp = TRIG_FOLLOW | TRIG_TIMER | TRIG_INT; + if (board->have_dio || board->min_hwver > 0) { + /* + * Unfortunately, we cannot trigger a scan off an external + * source on the PCI260 board, since it uses the PPIC0 (DIO) + * input, which isn't present on the PCI260. For PCI260+ + * we can use the EXTTRIG/EXTCONVCLK input on pin 17 instead. + */ + tmp |= TRIG_EXT; + } + err |= comedi_check_trigger_src(&cmd->scan_begin_src, tmp); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_INT | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + /* + * If scan_begin_src is not TRIG_FOLLOW, then a monostable will be + * set up to generate a fixed number of timed conversion pulses. + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->convert_src != TRIG_TIMER) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED_AI_SE 3200 /* PCI230 SE: 3200 ns => 312.5 kHz */ +#define MAX_SPEED_AI_DIFF 8000 /* PCI230 DIFF: 8000 ns => 125 kHz */ +#define MAX_SPEED_AI_PLUS 4000 /* PCI230+: 4000 ns => 250 kHz */ +/* + * Comedi limit due to unsigned int cmd. Driver limit = + * 2^16 (16bit * counter) * 1000000ns (1kHz onboard clock) = 65.536s + */ +#define MIN_SPEED_AI 4294967295u /* 4294967295ns = 4.29s */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int max_speed_ai; + + if (devpriv->hwver == 0) { + /* + * PCI230 or PCI260. Max speed depends whether + * single-ended or pseudo-differential. + */ + if (cmd->chanlist && cmd->chanlist_len > 0) { + /* Peek analogue reference of first channel. */ + if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF) + max_speed_ai = MAX_SPEED_AI_DIFF; + else + max_speed_ai = MAX_SPEED_AI_SE; + + } else { + /* No channel list. Assume single-ended. */ + max_speed_ai = MAX_SPEED_AI_SE; + } + } else { + /* PCI230+ or PCI260+. */ + max_speed_ai = MAX_SPEED_AI_PLUS; + } + + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + max_speed_ai); + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, + MIN_SPEED_AI); + } else if (cmd->convert_src == TRIG_EXT) { + /* + * external trigger + * + * convert_arg == (CR_EDGE | 0) + * => trigger on +ve edge. + * convert_arg == (CR_EDGE | CR_INVERT | 0) + * => trigger on -ve edge. + */ + if (cmd->convert_arg & CR_FLAGS_MASK) { + /* Trigger number must be 0. */ + if (cmd->convert_arg & ~CR_FLAGS_MASK) { + cmd->convert_arg = COMBINE(cmd->convert_arg, 0, + ~CR_FLAGS_MASK); + err |= -EINVAL; + } + /* + * The only flags allowed are CR_INVERT and CR_EDGE. + * CR_EDGE is required. + */ + if ((cmd->convert_arg & CR_FLAGS_MASK & ~CR_INVERT) != + CR_EDGE) { + /* Set CR_EDGE, preserve CR_INVERT. */ + cmd->convert_arg = + COMBINE(cmd->start_arg, CR_EDGE | 0, + CR_FLAGS_MASK & ~CR_INVERT); + err |= -EINVAL; + } + } else { + /* + * Backwards compatibility with previous versions: + * convert_arg == 0 => trigger on -ve edge. + * convert_arg == 1 => trigger on +ve edge. + */ + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, + 1); + } + } else { + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (cmd->scan_begin_src == TRIG_EXT) { + /* + * external "trigger" to begin each scan: + * scan_begin_arg==0 => use PPC0 input -> gate of CT0 -> gate + * of CT2 (sample convert trigger is CT2) + */ + if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + ~CR_FLAGS_MASK); + err |= -EINVAL; + } + /* The only flag allowed is CR_EDGE, which is ignored. */ + if (cmd->scan_begin_arg & CR_FLAGS_MASK & ~CR_EDGE) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & ~CR_EDGE); + err |= -EINVAL; + } + } else if (cmd->scan_begin_src == TRIG_TIMER) { + /* N.B. cmd->convert_arg is also TRIG_TIMER */ + if (!pci230_ai_check_scan_period(cmd)) + err |= -EINVAL; + + } else { + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + pci230_ns_to_single_timer(&cmd->convert_arg, cmd->flags); + if (tmp != cmd->convert_arg) + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* N.B. cmd->convert_arg is also TRIG_TIMER */ + tmp = cmd->scan_begin_arg; + pci230_ns_to_single_timer(&cmd->scan_begin_arg, cmd->flags); + if (!pci230_ai_check_scan_period(cmd)) { + /* Was below minimum required. Round up. */ + pci230_ns_to_single_timer(&cmd->scan_begin_arg, + CMDF_ROUND_UP); + pci230_ai_check_scan_period(cmd); + } + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= pci230_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static void pci230_ai_update_fifo_trigger_level(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int wake; + unsigned short triglev; + unsigned short adccon; + + if (cmd->flags & CMDF_WAKE_EOS) + wake = cmd->scan_end_arg - s->async->cur_chan; + else + wake = comedi_nsamples_left(s, PCI230_ADC_FIFOLEVEL_HALFFULL); + + if (wake >= PCI230_ADC_FIFOLEVEL_HALFFULL) { + triglev = PCI230_ADC_INT_FIFO_HALF; + } else if (wake > 1 && devpriv->hwver > 0) { + /* PCI230+/260+ programmable FIFO interrupt level. */ + if (devpriv->adcfifothresh != wake) { + devpriv->adcfifothresh = wake; + outw(wake, devpriv->daqio + PCI230P_ADCFFTH); + } + triglev = PCI230P_ADC_INT_FIFO_THRESH; + } else { + triglev = PCI230_ADC_INT_FIFO_NEMPTY; + } + adccon = (devpriv->adccon & ~PCI230_ADC_INT_FIFO_MASK) | triglev; + if (adccon != devpriv->adccon) { + devpriv->adccon = adccon; + outw(adccon, devpriv->daqio + PCI230_ADCCON); + } +} + +static int pci230_ai_inttrig_convert(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + unsigned int delayus; + + if (trig_num) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + if (!devpriv->ai_cmd_started) { + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + return 1; + } + /* + * Trigger conversion by toggling Z2-CT2 output. + * Finish with output high. + */ + comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0); + comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1); + /* + * Delay. Should driver be responsible for this? An + * alternative would be to wait until conversion is complete, + * but we can't tell when it's complete because the ADC busy + * bit has a different meaning when FIFO enabled (and when + * FIFO not enabled, it only works for software triggers). + */ + if ((devpriv->adccon & PCI230_ADC_IM_MASK) == PCI230_ADC_IM_DIF && + devpriv->hwver == 0) { + /* PCI230/260 in differential mode */ + delayus = 8; + } else { + /* single-ended or PCI230+/260+ */ + delayus = 4; + } + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + udelay(delayus); + return 1; +} + +static int pci230_ai_inttrig_scan_begin(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + unsigned char zgat; + + if (trig_num) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + if (devpriv->ai_cmd_started) { + /* Trigger scan by waggling CT0 gate source. */ + zgat = pci230_gat_config(0, GAT_GND); + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + zgat = pci230_gat_config(0, GAT_VCC); + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + } + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + + return 1; +} + +static void pci230_ai_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + struct comedi_cmd *cmd; + bool started; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + started = devpriv->ai_cmd_started; + devpriv->ai_cmd_started = false; + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + if (!started) + return; + cmd = &s->async->cmd; + if (cmd->convert_src == TRIG_TIMER) { + /* Stop conversion rate generator. */ + pci230_cancel_ct(dev, 2); + } + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Stop scan period monostable. */ + pci230_cancel_ct(dev, 0); + } + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + /* + * Disable ADC interrupt and wait for interrupt routine to finish + * running unless we are called from the interrupt routine. + */ + devpriv->ier &= ~PCI230_INT_ADC; + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + } + outb(devpriv->ier, dev->iobase + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + /* + * Reset FIFO, disable FIFO and set start conversion source to none. + * Keep se/diff and bip/uni settings. + */ + devpriv->adccon = + (devpriv->adccon & (PCI230_ADC_IR_MASK | PCI230_ADC_IM_MASK)) | + PCI230_ADC_TRIG_NONE; + outw(devpriv->adccon | PCI230_ADC_FIFO_RESET, + devpriv->daqio + PCI230_ADCCON); + /* Release resources. */ + pci230_release_all_resources(dev, OWNER_AICMD); +} + +static void pci230_ai_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + unsigned long irqflags; + unsigned short conv; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + devpriv->ai_cmd_started = true; + + /* Enable ADC FIFO trigger level interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->ier |= PCI230_INT_ADC; + outb(devpriv->ier, dev->iobase + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + /* + * Update conversion trigger source which is currently set + * to CT2 output, which is currently stuck high. + */ + switch (cmd->convert_src) { + default: + conv = PCI230_ADC_TRIG_NONE; + break; + case TRIG_TIMER: + /* Using CT2 output. */ + conv = PCI230_ADC_TRIG_Z2CT2; + break; + case TRIG_EXT: + if (cmd->convert_arg & CR_EDGE) { + if ((cmd->convert_arg & CR_INVERT) == 0) { + /* Trigger on +ve edge. */ + conv = PCI230_ADC_TRIG_EXTP; + } else { + /* Trigger on -ve edge. */ + conv = PCI230_ADC_TRIG_EXTN; + } + } else { + /* Backwards compatibility. */ + if (cmd->convert_arg) { + /* Trigger on +ve edge. */ + conv = PCI230_ADC_TRIG_EXTP; + } else { + /* Trigger on -ve edge. */ + conv = PCI230_ADC_TRIG_EXTN; + } + } + break; + case TRIG_INT: + /* + * Use CT2 output for software trigger due to problems + * in differential mode on PCI230/260. + */ + conv = PCI230_ADC_TRIG_Z2CT2; + break; + } + devpriv->adccon = (devpriv->adccon & ~PCI230_ADC_TRIG_MASK) | conv; + outw(devpriv->adccon, devpriv->daqio + PCI230_ADCCON); + if (cmd->convert_src == TRIG_INT) + async->inttrig = pci230_ai_inttrig_convert; + + /* + * Update FIFO interrupt trigger level, which is currently + * set to "full". + */ + pci230_ai_update_fifo_trigger_level(dev, s); + if (cmd->convert_src == TRIG_TIMER) { + /* Update timer gates. */ + unsigned char zgat; + + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* + * Conversion timer CT2 needs to be gated by + * inverted output of monostable CT2. + */ + zgat = pci230_gat_config(2, GAT_NOUTNM2); + } else { + /* + * Conversion timer CT2 needs to be gated on + * continuously. + */ + zgat = pci230_gat_config(2, GAT_VCC); + } + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Set monostable CT0 trigger source. */ + switch (cmd->scan_begin_src) { + default: + zgat = pci230_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 = pci230_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 = pci230_gat_config(0, GAT_NOUTNM2); + break; + case TRIG_INT: + /* + * Monostable CT0 is triggered by inttrig + * function waggling the CT0 gate source. + */ + zgat = pci230_gat_config(0, GAT_VCC); + break; + } + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + /* + * Scan period timer CT1 needs to be + * gated on to start counting. + */ + zgat = pci230_gat_config(1, GAT_VCC); + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + break; + case TRIG_INT: + async->inttrig = pci230_ai_inttrig_scan_begin; + break; + } + } + } else if (cmd->convert_src != TRIG_INT) { + /* No longer need Z2-CT2. */ + pci230_release_shared(dev, RES_Z2CT2, OWNER_AICMD); + } +} + +static int pci230_ai_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + s->async->inttrig = NULL; + pci230_ai_start(dev, s); + + return 1; +} + +static void pci230_handle_ai(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status_fifo; + unsigned int i; + unsigned int nsamples; + unsigned int fifoamount; + unsigned short val; + + /* Determine number of samples to read. */ + nsamples = comedi_nsamples_left(s, PCI230_ADC_FIFOLEVEL_HALFFULL); + if (nsamples == 0) + return; + + fifoamount = 0; + for (i = 0; i < nsamples; i++) { + if (fifoamount == 0) { + /* Read FIFO state. */ + status_fifo = inw(devpriv->daqio + PCI230_ADCCON); + if (status_fifo & PCI230_ADC_FIFO_FULL_LATCHED) { + /* + * Report error otherwise FIFO overruns will go + * unnoticed by the caller. + */ + dev_err(dev->class_dev, "AI FIFO overrun\n"); + async->events |= COMEDI_CB_ERROR; + break; + } else if (status_fifo & PCI230_ADC_FIFO_EMPTY) { + /* FIFO empty. */ + break; + } else if (status_fifo & PCI230_ADC_FIFO_HALF) { + /* FIFO half full. */ + fifoamount = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else if (devpriv->hwver > 0) { + /* Read PCI230+/260+ ADC FIFO level. */ + fifoamount = inw(devpriv->daqio + + PCI230P_ADCFFLEV); + if (fifoamount == 0) + break; /* Shouldn't happen. */ + } else { + /* FIFO not empty. */ + fifoamount = 1; + } + } + + val = pci230_ai_read(dev); + if (!comedi_buf_write_samples(s, &val, 1)) + break; + + fifoamount--; + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + + /* update FIFO interrupt trigger level if still running */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) + pci230_ai_update_fifo_trigger_level(dev, s); +} + +static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pci230_private *devpriv = dev->private; + unsigned int i, chan, range, diff; + unsigned int res_mask; + unsigned short adccon, adcen; + unsigned char zgat; + + /* Get the command. */ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + /* + * Determine which shared resources are needed. + */ + res_mask = 0; + /* + * Need Z2-CT2 to supply a conversion trigger source at a high + * logic level, even if not doing timed conversions. + */ + res_mask |= RES_Z2CT2; + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Using Z2-CT0 monostable to gate Z2-CT2 conversion timer */ + res_mask |= RES_Z2CT0; + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Using Z2-CT1 for scan frequency */ + res_mask |= RES_Z2CT1; + } + } + /* Claim resources. */ + if (!pci230_claim_shared(dev, res_mask, OWNER_AICMD)) + return -EBUSY; + + /* + * Steps: + * - Set channel scan list. + * - Set channel gains. + * - Enable and reset FIFO, specify uni/bip, se/diff, and set + * start conversion source to point to something at a high logic + * level (we use the output of counter/timer 2 for this purpose. + * - PAUSE to allow things to settle down. + * - Reset the FIFO again because it needs resetting twice and there + * may have been a false conversion trigger on some versions of + * PCI230/260 due to the start conversion source being set to a + * high logic level. + * - Enable ADC FIFO level interrupt. + * - Set actual conversion trigger source and FIFO interrupt trigger + * level. + * - If convert_src is TRIG_TIMER, set up the timers. + */ + + adccon = PCI230_ADC_FIFO_EN; + adcen = 0; + + if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF) { + /* Differential - all channels must be differential. */ + diff = 1; + adccon |= PCI230_ADC_IM_DIF; + } else { + /* Single ended - all channels must be single-ended. */ + diff = 0; + adccon |= PCI230_ADC_IM_SE; + } + + range = CR_RANGE(cmd->chanlist[0]); + devpriv->ai_bipolar = comedi_range_is_bipolar(s, range); + if (devpriv->ai_bipolar) + adccon |= PCI230_ADC_IR_BIP; + else + adccon |= PCI230_ADC_IR_UNI; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int gainshift; + + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + if (diff) { + gainshift = 2 * chan; + if (devpriv->hwver == 0) { + /* + * Original PCI230/260 expects both inputs of + * the differential channel to be enabled. + */ + adcen |= 3 << gainshift; + } else { + /* + * PCI230+/260+ expects only one input of the + * differential channel to be enabled. + */ + adcen |= 1 << gainshift; + } + } else { + gainshift = chan & ~1; + adcen |= 1 << chan; + } + devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) | + (pci230_ai_gain[range] << gainshift); + } + + /* Set channel scan list. */ + outw(adcen, devpriv->daqio + PCI230_ADCEN); + + /* Set channel gains. */ + outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG); + + /* + * Set counter/timer 2 output high for use as the initial start + * conversion source. + */ + comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1); + + /* + * Temporarily use CT2 output as conversion trigger source and + * temporarily set FIFO interrupt trigger level to 'full'. + */ + adccon |= PCI230_ADC_INT_FIFO_FULL | PCI230_ADC_TRIG_Z2CT2; + + /* + * Enable and reset FIFO, specify FIFO trigger level full, specify + * uni/bip, se/diff, and temporarily set the start conversion source + * to CT2 output. Note that CT2 output is currently high, and this + * will produce a false conversion trigger on some versions of the + * PCI230/260, but that will be dealt with later. + */ + devpriv->adccon = adccon; + outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON); + + /* + * Delay - + * Failure to include this will result in the first few channels'-worth + * of data being corrupt, normally manifesting itself by large negative + * voltages. It seems the board needs time to settle between the first + * FIFO reset (above) and the second FIFO reset (below). Setting the + * channel gains and scan list _before_ the first FIFO reset also + * helps, though only slightly. + */ + usleep_range(25, 100); + + /* Reset FIFO again. */ + outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON); + + if (cmd->convert_src == TRIG_TIMER) { + /* + * Set up CT2 as conversion timer, but gate it off for now. + * Note, counter/timer output 2 can be monitored on the + * connector: PCI230 pin 21, PCI260 pin 18. + */ + zgat = pci230_gat_config(2, GAT_GND); + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + /* Set counter/timer 2 to the specified conversion period. */ + pci230_ct_setup_ns_mode(dev, 2, I8254_MODE3, cmd->convert_arg, + cmd->flags); + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* + * Set up monostable on CT0 output for scan timing. A + * rising edge on the trigger (gate) input of CT0 will + * trigger the monostable, causing its output to go low + * for the configured period. The period depends on + * the conversion period and the number of conversions + * in the scan. + * + * Set the trigger high before setting up the + * monostable to stop it triggering. The trigger + * source will be changed later. + */ + zgat = pci230_gat_config(0, GAT_VCC); + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 0, I8254_MODE1, + ((u64)cmd->convert_arg * + cmd->scan_end_arg), + CMDF_ROUND_UP); + if (cmd->scan_begin_src == TRIG_TIMER) { + /* + * Monostable on CT0 will be triggered by + * output of CT1 at configured scan frequency. + * + * Set up CT1 but gate it off for now. + */ + zgat = pci230_gat_config(1, GAT_GND); + outb(zgat, dev->iobase + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, + cmd->scan_begin_arg, + cmd->flags); + } + } + } + + if (cmd->start_src == TRIG_INT) + s->async->inttrig = pci230_ai_inttrig_start; + else /* TRIG_NOW */ + pci230_ai_start(dev, s); + + return 0; +} + +static int pci230_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pci230_ai_stop(dev, s); + return 0; +} + +/* Interrupt handler */ +static irqreturn_t pci230_interrupt(int irq, void *d) +{ + unsigned char status_int, valid_status_int, temp_ier; + struct comedi_device *dev = d; + struct pci230_private *devpriv = dev->private; + struct comedi_subdevice *s_ao = dev->write_subdev; + struct comedi_subdevice *s_ai = dev->read_subdev; + unsigned long irqflags; + + /* Read interrupt status/enable register. */ + status_int = inb(dev->iobase + PCI230_INT_STAT); + + if (status_int == PCI230_INT_DISABLE) + return IRQ_NONE; + + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + valid_status_int = devpriv->ier & status_int; + /* + * Disable triggered interrupts. + * (Only those interrupts that need re-enabling, are, later in the + * handler). + */ + temp_ier = devpriv->ier & ~status_int; + outb(temp_ier, dev->iobase + PCI230_INT_SCE); + devpriv->intr_running = true; + devpriv->intr_cpuid = THISCPU; + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + /* + * Check the source of interrupt and handle it. + * The PCI230 can cope with concurrent ADC, DAC, PPI C0 and C3 + * interrupts. However, at present (Comedi-0.7.60) does not allow + * concurrent execution of commands, instructions or a mixture of the + * two. + */ + + if (valid_status_int & PCI230_INT_ZCLK_CT1) + pci230_handle_ao_nofifo(dev, s_ao); + + if (valid_status_int & PCI230P2_INT_DAC) + pci230_handle_ao_fifo(dev, s_ao); + + if (valid_status_int & PCI230_INT_ADC) + pci230_handle_ai(dev, s_ai); + + /* Reenable interrupts. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + if (devpriv->ier != temp_ier) + outb(devpriv->ier, dev->iobase + PCI230_INT_SCE); + devpriv->intr_running = false; + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + if (s_ao) + comedi_handle_events(dev, s_ao); + comedi_handle_events(dev, s_ai); + + return IRQ_HANDLED; +} + +/* Check if PCI device matches a specific board. */ +static bool pci230_match_pci_board(const struct pci230_board *board, + struct pci_dev *pci_dev) +{ + /* assume pci_dev->device != PCI_DEVICE_ID_INVALID */ + if (board->id != pci_dev->device) + return false; + if (board->min_hwver == 0) + return true; + /* Looking for a '+' model. First check length of registers. */ + if (pci_resource_len(pci_dev, 3) < 32) + return false; /* Not a '+' model. */ + /* + * TODO: temporarily enable PCI device and read the hardware version + * register. For now, assume it's okay. + */ + return true; +} + +/* Look for board matching PCI device. */ +static const struct pci230_board *pci230_find_pci_board(struct pci_dev *pci_dev) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pci230_boards); i++) + if (pci230_match_pci_board(&pci230_boards[i], pci_dev)) + return &pci230_boards[i]; + return NULL; +} + +static int pci230_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + const struct pci230_board *board; + struct pci230_private *devpriv; + struct comedi_subdevice *s; + int rc; + + dev_info(dev->class_dev, "amplc_pci230: attach pci %s\n", + pci_name(pci_dev)); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + spin_lock_init(&devpriv->isr_spinlock); + spin_lock_init(&devpriv->res_spinlock); + spin_lock_init(&devpriv->ai_stop_spinlock); + spin_lock_init(&devpriv->ao_stop_spinlock); + + board = pci230_find_pci_board(pci_dev); + if (!board) { + dev_err(dev->class_dev, + "amplc_pci230: BUG! cannot determine board type!\n"); + return -EINVAL; + } + dev->board_ptr = board; + dev->board_name = board->name; + + rc = comedi_pci_enable(dev); + if (rc) + return rc; + + /* + * Read base addresses of the PCI230's two I/O regions from PCI + * configuration register. + */ + dev->iobase = pci_resource_start(pci_dev, 2); + devpriv->daqio = pci_resource_start(pci_dev, 3); + dev_dbg(dev->class_dev, + "%s I/O region 1 0x%04lx I/O region 2 0x%04lx\n", + dev->board_name, dev->iobase, devpriv->daqio); + /* Read bits of DACCON register - only the output range. */ + devpriv->daccon = inw(devpriv->daqio + PCI230_DACCON) & + PCI230_DAC_OR_MASK; + /* + * Read hardware version register and set extended function register + * if they exist. + */ + if (pci_resource_len(pci_dev, 3) >= 32) { + unsigned short extfunc = 0; + + devpriv->hwver = inw(devpriv->daqio + PCI230P_HWVER); + if (devpriv->hwver < board->min_hwver) { + dev_err(dev->class_dev, + "%s - bad hardware version - got %u, need %u\n", + dev->board_name, devpriv->hwver, + board->min_hwver); + return -EIO; + } + if (devpriv->hwver > 0) { + if (!board->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 (board->ao_bits && devpriv->hwver >= 2) { + /* Enable DAC FIFO functionality. */ + extfunc |= PCI230P2_EXTFUNC_DACFIFO; + } + } + outw(extfunc, devpriv->daqio + PCI230P_EXTFUNC); + if (extfunc & PCI230P2_EXTFUNC_DACFIFO) { + /* + * Temporarily enable DAC FIFO, reset it and disable + * FIFO wraparound. + */ + outw(devpriv->daccon | PCI230P2_DAC_FIFO_EN | + PCI230P2_DAC_FIFO_RESET, + devpriv->daqio + PCI230_DACCON); + /* Clear DAC FIFO channel enable register. */ + outw(0, devpriv->daqio + PCI230P2_DACEN); + /* Disable DAC FIFO. */ + outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON); + } + } + /* Disable board's interrupts. */ + outb(0, dev->iobase + PCI230_INT_SCE); + /* Set ADC to a reasonable state. */ + devpriv->adcg = 0; + devpriv->adccon = PCI230_ADC_TRIG_NONE | PCI230_ADC_IM_SE | + PCI230_ADC_IR_BIP; + outw(BIT(0), devpriv->daqio + PCI230_ADCEN); + outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG); + outw(devpriv->adccon | PCI230_ADC_FIFO_RESET, + devpriv->daqio + PCI230_ADCCON); + + if (pci_dev->irq) { + rc = request_irq(pci_dev->irq, pci230_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (rc == 0) + dev->irq = pci_dev->irq; + } + + dev->pacer = comedi_8254_init(dev->iobase + PCI230_Z2_CT_BASE, + 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + rc = comedi_alloc_subdevices(dev, 3); + if (rc) + return rc; + + s = &dev->subdevices[0]; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND; + s->n_chan = 16; + s->maxdata = (1 << board->ai_bits) - 1; + s->range_table = &pci230_ai_range; + s->insn_read = pci230_ai_insn_read; + s->len_chanlist = 256; /* but there are restrictions. */ + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmd = pci230_ai_cmd; + s->do_cmdtest = pci230_ai_cmdtest; + s->cancel = pci230_ai_cancel; + } + + s = &dev->subdevices[1]; + /* analog output subdevice */ + if (board->ao_bits) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = 2; + s->maxdata = (1 << board->ao_bits) - 1; + s->range_table = &pci230_ao_range; + s->insn_write = pci230_ao_insn_write; + s->len_chanlist = 2; + if (dev->irq) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->do_cmd = pci230_ao_cmd; + s->do_cmdtest = pci230_ao_cmdtest; + s->cancel = pci230_ao_cancel; + } + + rc = comedi_alloc_subdev_readback(s); + if (rc) + return rc; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + /* digital i/o subdevice */ + if (board->have_dio) { + rc = subdev_8255_init(dev, s, NULL, PCI230_PPI_X_BASE); + if (rc) + return rc; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static struct comedi_driver amplc_pci230_driver = { + .driver_name = "amplc_pci230", + .module = THIS_MODULE, + .auto_attach = pci230_auto_attach, + .detach = comedi_pci_detach, +}; + +static int amplc_pci230_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PCI230(+) and PCI260(+)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pci236.c b/drivers/comedi/drivers/amplc_pci236.c new file mode 100644 index 000000000..482eb261c --- /dev/null +++ b/drivers/comedi/drivers/amplc_pci236.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/amplc_pci236.c + * Driver for Amplicon PCI236 DIO boards. + * + * Copyright (C) 2002-2014 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ +/* + * Driver: amplc_pci236 + * Description: Amplicon PCI236 + * Author: Ian Abbott <abbotti@mev.co.uk> + * Devices: [Amplicon] PCI236 (amplc_pci236) + * Updated: Fri, 25 Jul 2014 15:32:40 +0000 + * Status: works + * + * Configuration options: + * none + * + * Manual configuration of PCI board (PCI236) is not supported; it is + * configured automatically. + * + * The PCI236 board has a single 8255 appearing as subdevice 0. + * + * Subdevice 1 pretends to be a digital input device, but it always + * returns 0 when read. However, if you run a command with + * scan_begin_src=TRIG_EXT, a rising edge on port C bit 3 acts as an + * external trigger, which can be used to wake up tasks. This is like + * the comedi_parport device. If no interrupt is connected, then + * subdevice 1 is unused. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "amplc_pc236.h" +#include "plx9052.h" + +/* Disable, and clear, interrupts */ +#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1POL | \ + PLX9052_INTCSR_LI2POL | \ + PLX9052_INTCSR_LI1SEL | \ + PLX9052_INTCSR_LI1CLRINT) + +/* Enable, and clear, interrupts */ +#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB | \ + PLX9052_INTCSR_LI1POL | \ + PLX9052_INTCSR_LI2POL | \ + PLX9052_INTCSR_PCIENAB | \ + PLX9052_INTCSR_LI1SEL | \ + PLX9052_INTCSR_LI1CLRINT) + +static void pci236_intr_update_cb(struct comedi_device *dev, bool enable) +{ + struct pc236_private *devpriv = dev->private; + + /* this will also clear the "local interrupt 1" latch */ + outl(enable ? PCI236_INTR_ENABLE : PCI236_INTR_DISABLE, + devpriv->lcr_iobase + PLX9052_INTCSR); +} + +static bool pci236_intr_chk_clr_cb(struct comedi_device *dev) +{ + struct pc236_private *devpriv = dev->private; + + /* check if interrupt occurred */ + if (!(inl(devpriv->lcr_iobase + PLX9052_INTCSR) & + PLX9052_INTCSR_LI1STAT)) + return false; + /* clear the interrupt */ + pci236_intr_update_cb(dev, devpriv->enable_irq); + return true; +} + +static const struct pc236_board pc236_pci_board = { + .name = "pci236", + .intr_update_cb = pci236_intr_update_cb, + .intr_chk_clr_cb = pci236_intr_chk_clr_cb, +}; + +static int pci236_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pci_dev = comedi_to_pci_dev(dev); + struct pc236_private *devpriv; + unsigned long iobase; + int ret; + + dev_info(dev->class_dev, "amplc_pci236: attach pci %s\n", + pci_name(pci_dev)); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->board_ptr = &pc236_pci_board; + dev->board_name = pc236_pci_board.name; + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); + iobase = pci_resource_start(pci_dev, 2); + return amplc_pc236_common_attach(dev, iobase, pci_dev->irq, + IRQF_SHARED); +} + +static struct comedi_driver amplc_pci236_driver = { + .driver_name = "amplc_pci236", + .module = THIS_MODULE, + .auto_attach = pci236_auto_attach, + .detach = comedi_pci_detach, +}; + +static const struct pci_device_id pci236_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci236_pci_table); + +static int amplc_pci236_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &lc_pci236_driver, + id->driver_data); +} + +static struct pci_driver amplc_pci236_pci_driver = { + .name = "amplc_pci236", + .id_table = pci236_pci_table, + .probe = &lc_pci236_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; + +module_comedi_pci_driver(amplc_pci236_driver, amplc_pci236_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PCI236 DIO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/amplc_pci263.c b/drivers/comedi/drivers/amplc_pci263.c new file mode 100644 index 000000000..1609665c4 --- /dev/null +++ b/drivers/comedi/drivers/amplc_pci263.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Amplicon PCI263 relay board. + * + * Copyright (C) 2002 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * 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/comedi/comedi_pci.h> + +/* PCI263 registers */ +#define PCI263_DO_0_7_REG 0x00 +#define PCI263_DO_8_15_REG 0x01 + +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 + PCI263_DO_0_7_REG); + outb((s->state >> 8) & 0xff, dev->iobase + PCI263_DO_8_15_REG); + } + + 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; + + /* Digital Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci263_do_insn_bits; + + /* read initial relay state */ + s->state = inb(dev->iobase + PCI263_DO_0_7_REG) | + (inb(dev->iobase + PCI263_DO_8_15_REG) << 8); + + return 0; +} + +static struct comedi_driver amplc_pci263_driver = { + .driver_name = "amplc_pci263", + .module = THIS_MODULE, + .auto_attach = pci263_auto_attach, + .detach = comedi_pci_detach, +}; + +static const struct pci_device_id pci263_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x000c) }, + {0} +}; +MODULE_DEVICE_TABLE(pci, pci263_pci_table); + +static int amplc_pci263_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &lc_pci263_driver, + id->driver_data); +} + +static struct pci_driver amplc_pci263_pci_driver = { + .name = "amplc_pci263", + .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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PCI263 relay board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/c6xdigio.c b/drivers/comedi/drivers/c6xdigio.c new file mode 100644 index 000000000..14b90d1c6 --- /dev/null +++ b/drivers/comedi/drivers/c6xdigio.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* + * Register I/O map + */ +#define C6XDIGIO_DATA_REG 0x00 +#define C6XDIGIO_DATA_CHAN(x) (((x) + 1) << 4) +#define C6XDIGIO_DATA_PWM BIT(5) +#define C6XDIGIO_DATA_ENCODER BIT(6) +#define C6XDIGIO_STATUS_REG 0x01 +#define C6XDIGIO_CTRL_REG 0x02 + +#define C6XDIGIO_TIME_OUT 20 + +static int c6xdigio_chk_status(struct comedi_device *dev, unsigned long context) +{ + unsigned int status; + int timeout = 0; + + do { + status = inb(dev->iobase + C6XDIGIO_STATUS_REG); + if ((status & 0x80) != context) + return 0; + timeout++; + } while (timeout < C6XDIGIO_TIME_OUT); + + return -EBUSY; +} + +static int c6xdigio_write_data(struct comedi_device *dev, + unsigned int val, unsigned int status) +{ + outb_p(val, dev->iobase + C6XDIGIO_DATA_REG); + return c6xdigio_chk_status(dev, status); +} + +static int c6xdigio_get_encoder_bits(struct comedi_device *dev, + unsigned int *bits, + unsigned int cmd, + unsigned int status) +{ + unsigned int val; + + val = inb(dev->iobase + C6XDIGIO_STATUS_REG); + val >>= 3; + val &= 0x07; + + *bits = val; + + return c6xdigio_write_data(dev, cmd, status); +} + +static void c6xdigio_pwm_write(struct comedi_device *dev, + unsigned int chan, unsigned int val) +{ + unsigned int cmd = C6XDIGIO_DATA_PWM | C6XDIGIO_DATA_CHAN(chan); + unsigned int bits; + + if (val > 498) + val = 498; + if (val < 2) + val = 2; + + bits = (val >> 0) & 0x03; + c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00); + bits = (val >> 2) & 0x03; + c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80); + bits = (val >> 4) & 0x03; + c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00); + bits = (val >> 6) & 0x03; + c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80); + bits = (val >> 8) & 0x03; + c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00); + + c6xdigio_write_data(dev, 0x00, 0x80); +} + +static int c6xdigio_encoder_read(struct comedi_device *dev, + unsigned int chan) +{ + unsigned int cmd = C6XDIGIO_DATA_ENCODER | C6XDIGIO_DATA_CHAN(chan); + unsigned int val = 0; + unsigned int bits; + + c6xdigio_write_data(dev, cmd, 0x00); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); + val |= (bits << 0); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); + val |= (bits << 3); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); + val |= (bits << 6); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); + val |= (bits << 9); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); + val |= (bits << 12); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); + val |= (bits << 15); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80); + val |= (bits << 18); + + c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00); + val |= (bits << 21); + + c6xdigio_write_data(dev, 0x00, 0x80); + + return val; +} + +static int c6xdigio_pwm_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = (s->state >> (16 * chan)) & 0xffff; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + c6xdigio_pwm_write(dev, chan, val); + } + + /* + * There are only 2 PWM channels and they have a maxdata of 500. + * Instead of allocating private data to save the values in for + * readback this driver just packs the values for the two channels + * in the s->state. + */ + s->state &= (0xffff << (16 * chan)); + s->state |= (val << (16 * chan)); + + return insn->n; +} + +static int c6xdigio_pwm_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + val = (s->state >> (16 * chan)) & 0xffff; + + for (i = 0; i < insn->n; i++) + data[i] = val; + + return insn->n; +} + +static int c6xdigio_encoder_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + for (i = 0; i < insn->n; i++) { + val = c6xdigio_encoder_read(dev, chan); + + /* munge two's complement value to offset binary */ + data[i] = comedi_offset_munge(s, val); + } + + return insn->n; +} + +static void c6xdigio_init(struct comedi_device *dev) +{ + /* Initialize the PWM */ + c6xdigio_write_data(dev, 0x70, 0x00); + c6xdigio_write_data(dev, 0x74, 0x80); + c6xdigio_write_data(dev, 0x70, 0x00); + c6xdigio_write_data(dev, 0x00, 0x80); + + /* Reset the encoders */ + c6xdigio_write_data(dev, 0x68, 0x00); + c6xdigio_write_data(dev, 0x6c, 0x80); + c6xdigio_write_data(dev, 0x68, 0x00); + c6xdigio_write_data(dev, 0x00, 0x80); +} + +static const struct pnp_device_id c6xdigio_pnp_tbl[] = { + /* Standard LPT Printer Port */ + {.id = "PNP0400", .driver_data = 0}, + /* ECP Printer Port */ + {.id = "PNP0401", .driver_data = 0}, + {} +}; + +static struct pnp_driver c6xdigio_pnp_driver = { + .name = "c6xdigio", + .id_table = c6xdigio_pnp_tbl, +}; + +static int c6xdigio_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x03); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Make sure that PnP ports get activated */ + pnp_register_driver(&c6xdigio_pnp_driver); + + s = &dev->subdevices[0]; + /* pwm output subdevice */ + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 500; + s->range_table = &range_unknown; + s->insn_write = c6xdigio_pwm_insn_write; + s->insn_read = c6xdigio_pwm_insn_read; + + s = &dev->subdevices[1]; + /* encoder (counter) subdevice */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = 2; + s->maxdata = 0xffffff; + s->range_table = &range_unknown; + s->insn_read = c6xdigio_encoder_insn_read; + + /* I will call this init anyway but more than likely the DSP board */ + /* will not be connected when device driver is loaded. */ + c6xdigio_init(dev); + + return 0; +} + +static void c6xdigio_detach(struct comedi_device *dev) +{ + comedi_legacy_detach(dev); + pnp_unregister_driver(&c6xdigio_pnp_driver); +} + +static struct comedi_driver c6xdigio_driver = { + .driver_name = "c6xdigio", + .module = THIS_MODULE, + .attach = c6xdigio_attach, + .detach = c6xdigio_detach, +}; +module_comedi_driver(c6xdigio_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/cb_das16_cs.c b/drivers/comedi/drivers/cb_das16_cs.c new file mode 100644 index 000000000..8e0d2fa5f --- /dev/null +++ b/drivers/comedi/drivers/cb_das16_cs.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cb_das16_cs.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> + * + * 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 <linux/comedi/comedi_pcmcia.h> +#include <linux/comedi/comedi_8254.h> + +/* + * Register I/O map + */ +#define DAS16CS_AI_DATA_REG 0x00 +#define DAS16CS_AI_MUX_REG 0x02 +#define DAS16CS_AI_MUX_HI_CHAN(x) (((x) & 0xf) << 4) +#define DAS16CS_AI_MUX_LO_CHAN(x) (((x) & 0xf) << 0) +#define DAS16CS_AI_MUX_SINGLE_CHAN(x) (DAS16CS_AI_MUX_HI_CHAN(x) | \ + DAS16CS_AI_MUX_LO_CHAN(x)) +#define DAS16CS_MISC1_REG 0x04 +#define DAS16CS_MISC1_INTE BIT(15) /* 1=enable; 0=disable */ +#define DAS16CS_MISC1_INT_SRC(x) (((x) & 0x7) << 12) /* interrupt src */ +#define DAS16CS_MISC1_INT_SRC_NONE DAS16CS_MISC1_INT_SRC(0) +#define DAS16CS_MISC1_INT_SRC_PACER DAS16CS_MISC1_INT_SRC(1) +#define DAS16CS_MISC1_INT_SRC_EXT DAS16CS_MISC1_INT_SRC(2) +#define DAS16CS_MISC1_INT_SRC_FNE DAS16CS_MISC1_INT_SRC(3) +#define DAS16CS_MISC1_INT_SRC_FHF DAS16CS_MISC1_INT_SRC(4) +#define DAS16CS_MISC1_INT_SRC_EOS DAS16CS_MISC1_INT_SRC(5) +#define DAS16CS_MISC1_INT_SRC_MASK DAS16CS_MISC1_INT_SRC(7) +#define DAS16CS_MISC1_OVR BIT(10) /* ro - 1=FIFO overflow */ +#define DAS16CS_MISC1_AI_CONV(x) (((x) & 0x3) << 8) /* AI convert src */ +#define DAS16CS_MISC1_AI_CONV_SW DAS16CS_MISC1_AI_CONV(0) +#define DAS16CS_MISC1_AI_CONV_EXT_NEG DAS16CS_MISC1_AI_CONV(1) +#define DAS16CS_MISC1_AI_CONV_EXT_POS DAS16CS_MISC1_AI_CONV(2) +#define DAS16CS_MISC1_AI_CONV_PACER DAS16CS_MISC1_AI_CONV(3) +#define DAS16CS_MISC1_AI_CONV_MASK DAS16CS_MISC1_AI_CONV(3) +#define DAS16CS_MISC1_EOC BIT(7) /* ro - 0=busy; 1=ready */ +#define DAS16CS_MISC1_SEDIFF BIT(5) /* 0=diff; 1=se */ +#define DAS16CS_MISC1_INTB BIT(4) /* ro - 0=latched; 1=cleared */ +#define DAS16CS_MISC1_MA_MASK (0xf << 0) /* ro - current ai mux */ +#define DAS16CS_MISC1_DAC1CS BIT(3) /* wo - DAC1 chip select */ +#define DAS16CS_MISC1_DACCLK BIT(2) /* wo - Serial DAC clock */ +#define DAS16CS_MISC1_DACSD BIT(1) /* wo - Serial DAC data */ +#define DAS16CS_MISC1_DAC0CS BIT(0) /* wo - DAC0 chip select */ +#define DAS16CS_MISC1_DAC_MASK (0x0f << 0) +#define DAS16CS_MISC2_REG 0x06 +#define DAS16CS_MISC2_BME BIT(14) /* 1=burst enable; 0=disable */ +#define DAS16CS_MISC2_AI_GAIN(x) (((x) & 0xf) << 8) /* AI gain */ +#define DAS16CS_MISC2_AI_GAIN_1 DAS16CS_MISC2_AI_GAIN(4) /* +/-10V */ +#define DAS16CS_MISC2_AI_GAIN_2 DAS16CS_MISC2_AI_GAIN(0) /* +/-5V */ +#define DAS16CS_MISC2_AI_GAIN_4 DAS16CS_MISC2_AI_GAIN(1) /* +/-2.5V */ +#define DAS16CS_MISC2_AI_GAIN_8 DAS16CS_MISC2_AI_GAIN(2) /* +-1.25V */ +#define DAS16CS_MISC2_AI_GAIN_MASK DAS16CS_MISC2_AI_GAIN(0xf) +#define DAS16CS_MISC2_UDIR BIT(7) /* 1=dio7:4 output; 0=input */ +#define DAS16CS_MISC2_LDIR BIT(6) /* 1=dio3:0 output; 0=input */ +#define DAS16CS_MISC2_TRGPOL BIT(5) /* 1=active lo; 0=hi */ +#define DAS16CS_MISC2_TRGSEL BIT(4) /* 1=edge; 0=level */ +#define DAS16CS_MISC2_FFNE BIT(3) /* ro - 1=FIFO not empty */ +#define DAS16CS_MISC2_TRGCLR BIT(3) /* wo - 1=clr (monstable) */ +#define DAS16CS_MISC2_CLK2 BIT(2) /* 1=10 MHz; 0=1 MHz */ +#define DAS16CS_MISC2_CTR1 BIT(1) /* 1=int. 100 kHz; 0=ext. clk */ +#define DAS16CS_MISC2_TRG0 BIT(0) /* 1=enable; 0=disable */ +#define DAS16CS_TIMER_BASE 0x08 +#define DAS16CS_DIO_REG 0x10 + +struct das16cs_board { + const char *name; + int device_id; + unsigned int has_ao:1; + unsigned int has_4dio:1; +}; + +static const struct das16cs_board das16cs_boards[] = { + { + .name = "PC-CARD DAS16/16-AO", + .device_id = 0x0039, + .has_ao = 1, + .has_4dio = 1, + }, { + .name = "PCM-DAS16s/16", + .device_id = 0x4009, + }, { + .name = "PC-CARD DAS16/16", + .device_id = 0x0000, /* unknown */ + }, +}; + +struct das16cs_private { + unsigned short misc1; + unsigned short misc2; +}; + +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_REG); + if (status & DAS16CS_MISC1_EOC) + return 0; + return -EBUSY; +} + +static int das16cs_ai_insn_read(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(DAS16CS_AI_MUX_SINGLE_CHAN(chan), + dev->iobase + DAS16CS_AI_MUX_REG); + + /* disable interrupts, software convert */ + devpriv->misc1 &= ~(DAS16CS_MISC1_INTE | DAS16CS_MISC1_INT_SRC_MASK | + DAS16CS_MISC1_AI_CONV_MASK); + if (aref == AREF_DIFF) + devpriv->misc1 &= ~DAS16CS_MISC1_SEDIFF; + else + devpriv->misc1 |= DAS16CS_MISC1_SEDIFF; + outw(devpriv->misc1, dev->iobase + DAS16CS_MISC1_REG); + + devpriv->misc2 &= ~(DAS16CS_MISC2_BME | DAS16CS_MISC2_AI_GAIN_MASK); + switch (range) { + case 0: + devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_1; + break; + case 1: + devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_2; + break; + case 2: + devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_4; + break; + case 3: + devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_8; + break; + } + outw(devpriv->misc2, dev->iobase + DAS16CS_MISC2_REG); + + for (i = 0; i < insn->n; i++) { + outw(0, dev->iobase + DAS16CS_AI_DATA_REG); + + ret = comedi_timeout(dev, s, insn, das16cs_ai_eoc, 0); + if (ret) + return ret; + + data[i] = inw(dev->iobase + DAS16CS_AI_DATA_REG); + } + + return i; +} + +static int das16cs_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct das16cs_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + unsigned short misc1; + int bit; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + outw(devpriv->misc1, dev->iobase + DAS16CS_MISC1_REG); + udelay(1); + + /* raise the DACxCS line for the non-selected channel */ + misc1 = devpriv->misc1 & ~DAS16CS_MISC1_DAC_MASK; + if (chan) + misc1 |= DAS16CS_MISC1_DAC0CS; + else + misc1 |= DAS16CS_MISC1_DAC1CS; + + outw(misc1, dev->iobase + DAS16CS_MISC1_REG); + udelay(1); + + for (bit = 15; bit >= 0; bit--) { + if ((val >> bit) & 0x1) + misc1 |= DAS16CS_MISC1_DACSD; + else + misc1 &= ~DAS16CS_MISC1_DACSD; + outw(misc1, dev->iobase + DAS16CS_MISC1_REG); + udelay(1); + outw(misc1 | DAS16CS_MISC1_DACCLK, + dev->iobase + DAS16CS_MISC1_REG); + udelay(1); + } + /* + * Make both DAC0CS and DAC1CS high to load + * the new data and update analog the output + */ + outw(misc1 | DAS16CS_MISC1_DAC0CS | DAS16CS_MISC1_DAC1CS, + dev->iobase + DAS16CS_MISC1_REG); + } + s->readback[chan] = val; + + return insn->n; +} + +static int das16cs_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + DAS16CS_DIO_REG); + + data[1] = inw(dev->iobase + DAS16CS_DIO_REG); + + 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; + + if (s->io_bits & 0xf0) + devpriv->misc2 |= DAS16CS_MISC2_UDIR; + else + devpriv->misc2 &= ~DAS16CS_MISC2_UDIR; + if (s->io_bits & 0x0f) + devpriv->misc2 |= DAS16CS_MISC2_LDIR; + else + devpriv->misc2 &= ~DAS16CS_MISC2_LDIR; + outw(devpriv->misc2, dev->iobase + DAS16CS_MISC2_REG); + + return insn->n; +} + +static int das16cs_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct das16cs_private *devpriv = dev->private; + + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + switch (data[1]) { + case 0: /* internal 100 kHz */ + devpriv->misc2 |= DAS16CS_MISC2_CTR1; + break; + case 1: /* external */ + devpriv->misc2 &= ~DAS16CS_MISC2_CTR1; + break; + default: + return -EINVAL; + } + outw(devpriv->misc2, dev->iobase + DAS16CS_MISC2_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + if (devpriv->misc2 & DAS16CS_MISC2_CTR1) { + data[1] = 0; + data[2] = I8254_OSC_BASE_100KHZ; + } else { + data[1] = 1; + data[2] = 0; /* unknown */ + } + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static const void *das16cs_find_boardinfo(struct comedi_device *dev, + struct pcmcia_device *link) +{ + const struct das16cs_board *board; + int i; + + for (i = 0; i < ARRAY_SIZE(das16cs_boards); i++) { + board = &das16cs_boards[i]; + if (board->device_id == link->card_id) + return board; + } + + return NULL; +} + +static int das16cs_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + const struct das16cs_board *board; + struct das16cs_private *devpriv; + struct comedi_subdevice *s; + int ret; + + board = das16cs_find_boardinfo(dev, link); + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ; + ret = comedi_pcmcia_enable(dev, NULL); + if (ret) + return ret; + dev->iobase = link->resource[0]->start; + + link->priv = dev; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->pacer = comedi_8254_init(dev->iobase + DAS16CS_TIMER_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO16, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 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 = &das16cs_ai_range; + s->insn_read = das16cs_ai_insn_read; + + /* 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 = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = &das16cs_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = board->has_4dio ? 4 : 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16cs_dio_insn_bits; + s->insn_config = das16cs_dio_insn_config; + + /* Counter subdevice (8254) */ + s = &dev->subdevices[3]; + comedi_8254_subdevice_init(s, dev->pacer); + + dev->pacer->insn_config = das16cs_counter_insn_config; + + /* counters 1 and 2 are used internally for the pacer */ + comedi_8254_set_busy(dev->pacer, 1, true); + comedi_8254_set_busy(dev->pacer, 2, true); + + return 0; +} + +static struct comedi_driver 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/comedi/drivers/cb_pcidas.c b/drivers/comedi/drivers/cb_pcidas.c new file mode 100644 index 000000000..0c7576b96 --- /dev/null +++ b/drivers/comedi/drivers/cb_pcidas.c @@ -0,0 +1,1498 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: cb_pcidas + * Description: MeasurementComputing PCI-DAS series + * with the AMCC S5933 PCI controller + * Devices: [Measurement Computing] PCI-DAS1602/16 (cb_pcidas), + * PCI-DAS1602/16jr, PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr, + * PCI-DAS1000, PCI-DAS1001, PCI_DAS1002 + * Author: Ivan Martinez <imr@oersted.dtu.dk>, + * Frank Mori Hess <fmhess@users.sourceforge.net> + * Updated: 2003-3-11 + * + * Status: + * There are many reports of the driver being used with most of the + * supported cards. Despite no detailed log is maintained, it can + * be said that the driver is quite tested and stable. + * + * The boards may be autocalibrated using the comedi_calibrate + * utility. + * + * Configuration options: not applicable, uses PCI auto config + * + * For commands, the scanned channels must be consecutive + * (i.e. 4-5-6-7, 2-3-4,...), and must all have the same + * range and aref. + * + * AI Triggering: + * For start_src == TRIG_EXT, the A/D EXTERNAL TRIGGER IN (pin 45) is used. + * For 1602 series, the start_arg is interpreted as follows: + * start_arg == 0 => gated trigger (level high) + * start_arg == CR_INVERT => gated trigger (level low) + * start_arg == CR_EDGE => Rising edge + * start_arg == CR_EDGE | CR_INVERT => Falling edge + * For the other boards the trigger will be done on rising edge + */ + +/* + * TODO: + * analog triggering on 1602 series + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +#include "amcc_s5933.h" + +#define AI_BUFFER_SIZE 1024 /* max ai fifo size */ +#define AO_BUFFER_SIZE 1024 /* max ao fifo size */ + +/* + * PCI BAR1 Register map (devpriv->pcibar1) + */ +#define PCIDAS_CTRL_REG 0x00 /* INTERRUPT / ADC FIFO register */ +#define PCIDAS_CTRL_INT(x) (((x) & 0x3) << 0) +#define PCIDAS_CTRL_INT_NONE PCIDAS_CTRL_INT(0) /* no int selected */ +#define PCIDAS_CTRL_INT_EOS PCIDAS_CTRL_INT(1) /* int on end of scan */ +#define PCIDAS_CTRL_INT_FHF PCIDAS_CTRL_INT(2) /* int on fifo half full */ +#define PCIDAS_CTRL_INT_FNE PCIDAS_CTRL_INT(3) /* int on fifo not empty */ +#define PCIDAS_CTRL_INT_MASK PCIDAS_CTRL_INT(3) /* mask of int select bits */ +#define PCIDAS_CTRL_INTE BIT(2) /* int enable */ +#define PCIDAS_CTRL_DAHFIE BIT(3) /* dac half full int enable */ +#define PCIDAS_CTRL_EOAIE BIT(4) /* end of acq. int enable */ +#define PCIDAS_CTRL_DAHFI BIT(5) /* dac half full status / clear */ +#define PCIDAS_CTRL_EOAI BIT(6) /* end of acq. int status / clear */ +#define PCIDAS_CTRL_INT_CLR BIT(7) /* int status / clear */ +#define PCIDAS_CTRL_EOBI BIT(9) /* end of burst int status */ +#define PCIDAS_CTRL_ADHFI BIT(10) /* half-full int status */ +#define PCIDAS_CTRL_ADNEI BIT(11) /* fifo not empty int status (latch) */ +#define PCIDAS_CTRL_ADNE BIT(12) /* fifo not empty status (realtime) */ +#define PCIDAS_CTRL_DAEMIE BIT(12) /* dac empty int enable */ +#define PCIDAS_CTRL_LADFUL BIT(13) /* fifo overflow / clear */ +#define PCIDAS_CTRL_DAEMI BIT(14) /* dac fifo empty int status / clear */ + +#define PCIDAS_CTRL_AI_INT (PCIDAS_CTRL_EOAI | PCIDAS_CTRL_EOBI | \ + PCIDAS_CTRL_ADHFI | PCIDAS_CTRL_ADNEI | \ + PCIDAS_CTRL_LADFUL) +#define PCIDAS_CTRL_AO_INT (PCIDAS_CTRL_DAHFI | PCIDAS_CTRL_DAEMI) + +#define PCIDAS_AI_REG 0x02 /* ADC CHANNEL MUX AND CONTROL reg */ +#define PCIDAS_AI_FIRST(x) ((x) & 0xf) +#define PCIDAS_AI_LAST(x) (((x) & 0xf) << 4) +#define PCIDAS_AI_CHAN(x) (PCIDAS_AI_FIRST(x) | PCIDAS_AI_LAST(x)) +#define PCIDAS_AI_GAIN(x) (((x) & 0x3) << 8) +#define PCIDAS_AI_SE BIT(10) /* Inputs in single-ended mode */ +#define PCIDAS_AI_UNIP BIT(11) /* Analog front-end unipolar mode */ +#define PCIDAS_AI_PACER(x) (((x) & 0x3) << 12) +#define PCIDAS_AI_PACER_SW PCIDAS_AI_PACER(0) /* software pacer */ +#define PCIDAS_AI_PACER_INT PCIDAS_AI_PACER(1) /* int. pacer */ +#define PCIDAS_AI_PACER_EXTN PCIDAS_AI_PACER(2) /* ext. falling edge */ +#define PCIDAS_AI_PACER_EXTP PCIDAS_AI_PACER(3) /* ext. rising edge */ +#define PCIDAS_AI_PACER_MASK PCIDAS_AI_PACER(3) /* pacer source bits */ +#define PCIDAS_AI_EOC BIT(14) /* adc not busy */ + +#define PCIDAS_TRIG_REG 0x04 /* TRIGGER CONTROL/STATUS register */ +#define PCIDAS_TRIG_SEL(x) (((x) & 0x3) << 0) +#define PCIDAS_TRIG_SEL_NONE PCIDAS_TRIG_SEL(0) /* no start trigger */ +#define PCIDAS_TRIG_SEL_SW PCIDAS_TRIG_SEL(1) /* software start trigger */ +#define PCIDAS_TRIG_SEL_EXT PCIDAS_TRIG_SEL(2) /* ext. start trigger */ +#define PCIDAS_TRIG_SEL_ANALOG PCIDAS_TRIG_SEL(3) /* ext. analog trigger */ +#define PCIDAS_TRIG_SEL_MASK PCIDAS_TRIG_SEL(3) /* start trigger mask */ +#define PCIDAS_TRIG_POL BIT(2) /* invert trigger (1602 only) */ +#define PCIDAS_TRIG_MODE BIT(3) /* edge/level triggered (1602 only) */ +#define PCIDAS_TRIG_EN BIT(4) /* enable external start trigger */ +#define PCIDAS_TRIG_BURSTE BIT(5) /* burst mode enable */ +#define PCIDAS_TRIG_CLR BIT(7) /* clear external trigger */ + +#define PCIDAS_CALIB_REG 0x06 /* CALIBRATION register */ +#define PCIDAS_CALIB_8800_SEL BIT(8) /* select 8800 caldac */ +#define PCIDAS_CALIB_TRIM_SEL BIT(9) /* select ad7376 trim pot */ +#define PCIDAS_CALIB_DAC08_SEL BIT(10) /* select dac08 caldac */ +#define PCIDAS_CALIB_SRC(x) (((x) & 0x7) << 11) +#define PCIDAS_CALIB_EN BIT(14) /* calibration source enable */ +#define PCIDAS_CALIB_DATA BIT(15) /* serial data bit going to caldac */ + +#define PCIDAS_AO_REG 0x08 /* dac control and status register */ +#define PCIDAS_AO_EMPTY BIT(0) /* fifo empty, write clear (1602) */ +#define PCIDAS_AO_DACEN BIT(1) /* dac enable */ +#define PCIDAS_AO_START BIT(2) /* start/arm fifo (1602) */ +#define PCIDAS_AO_PACER(x) (((x) & 0x3) << 3) /* (1602) */ +#define PCIDAS_AO_PACER_SW PCIDAS_AO_PACER(0) /* software pacer */ +#define PCIDAS_AO_PACER_INT PCIDAS_AO_PACER(1) /* int. pacer */ +#define PCIDAS_AO_PACER_EXTN PCIDAS_AO_PACER(2) /* ext. falling edge */ +#define PCIDAS_AO_PACER_EXTP PCIDAS_AO_PACER(3) /* ext. rising edge */ +#define PCIDAS_AO_PACER_MASK PCIDAS_AO_PACER(3) /* pacer source bits */ +#define PCIDAS_AO_CHAN_EN(c) BIT(5 + ((c) & 0x1)) +#define PCIDAS_AO_CHAN_MASK (PCIDAS_AO_CHAN_EN(0) | PCIDAS_AO_CHAN_EN(1)) +#define PCIDAS_AO_UPDATE_BOTH BIT(7) /* update both dacs */ +#define PCIDAS_AO_RANGE(c, r) (((r) & 0x3) << (8 + 2 * ((c) & 0x1))) +#define PCIDAS_AO_RANGE_MASK(c) PCIDAS_AO_RANGE((c), 0x3) + +/* + * PCI BAR2 Register map (devpriv->pcibar2) + */ +#define PCIDAS_AI_DATA_REG 0x00 +#define PCIDAS_AI_FIFO_CLR_REG 0x02 + +/* + * PCI BAR3 Register map (dev->iobase) + */ +#define PCIDAS_AI_8254_BASE 0x00 +#define PCIDAS_8255_BASE 0x04 +#define PCIDAS_AO_8254_BASE 0x08 + +/* + * PCI BAR4 Register map (devpriv->pcibar4) + */ +#define PCIDAS_AO_DATA_REG(x) (0x00 + ((x) * 2)) +#define PCIDAS_AO_FIFO_REG 0x00 +#define PCIDAS_AO_FIFO_CLR_REG 0x02 + +/* 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 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_speed; /* fastest conversion period in ns */ + int ao_scan_speed; /* analog output scan speed for 1602 series */ + int fifo_size; /* number of samples fifo can hold */ + unsigned int is_16bit; /* ai/ao is 1=16-bit; 0=12-bit */ + unsigned int use_alt_range:1; /* use alternate ai range table */ + unsigned int has_ao:1; /* has 2 analog output channels */ + unsigned int has_ao_fifo:1; /* analog output has fifo */ + unsigned int has_ad8402:1; /* trimpot type 1=AD8402; 0=AD7376 */ + unsigned int has_dac08:1; + unsigned int is_1602:1; +}; + +static const struct cb_pcidas_board cb_pcidas_boards[] = { + [BOARD_PCIDAS1602_16] = { + .name = "pci-das1602/16", + .ai_speed = 5000, + .ao_scan_speed = 10000, + .fifo_size = 512, + .is_16bit = 1, + .has_ao = 1, + .has_ao_fifo = 1, + .has_ad8402 = 1, + .has_dac08 = 1, + .is_1602 = 1, + }, + [BOARD_PCIDAS1200] = { + .name = "pci-das1200", + .ai_speed = 3200, + .fifo_size = 1024, + .has_ao = 1, + }, + [BOARD_PCIDAS1602_12] = { + .name = "pci-das1602/12", + .ai_speed = 3200, + .ao_scan_speed = 4000, + .fifo_size = 1024, + .has_ao = 1, + .has_ao_fifo = 1, + .is_1602 = 1, + }, + [BOARD_PCIDAS1200_JR] = { + .name = "pci-das1200/jr", + .ai_speed = 3200, + .fifo_size = 1024, + }, + [BOARD_PCIDAS1602_16_JR] = { + .name = "pci-das1602/16/jr", + .ai_speed = 5000, + .fifo_size = 512, + .is_16bit = 1, + .has_ad8402 = 1, + .has_dac08 = 1, + .is_1602 = 1, + }, + [BOARD_PCIDAS1000] = { + .name = "pci-das1000", + .ai_speed = 4000, + .fifo_size = 1024, + }, + [BOARD_PCIDAS1001] = { + .name = "pci-das1001", + .ai_speed = 6800, + .fifo_size = 1024, + .use_alt_range = 1, + .has_ao = 1, + }, + [BOARD_PCIDAS1002] = { + .name = "pci-das1002", + .ai_speed = 6800, + .fifo_size = 1024, + .has_ao = 1, + }, +}; + +struct cb_pcidas_private { + struct comedi_8254 *ao_pacer; + /* base addresses */ + unsigned long amcc; /* pcibar0 */ + unsigned long pcibar1; + unsigned long pcibar2; + unsigned long pcibar4; + /* bits to write to registers */ + unsigned int ctrl; + unsigned int amcc_intcsr; + unsigned int ao_ctrl; + /* fifo buffers */ + unsigned short ai_buffer[AI_BUFFER_SIZE]; + unsigned short ao_buffer[AO_BUFFER_SIZE]; + unsigned int calib_src; +}; + +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->pcibar1 + PCIDAS_AI_REG); + if (status & PCIDAS_AI_EOC) + return 0; + return -EBUSY; +} + +static int cb_pcidas_ai_insn_read(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(PCIDAS_CALIB_EN | PCIDAS_CALIB_SRC(devpriv->calib_src), + devpriv->pcibar1 + PCIDAS_CALIB_REG); + chan = 0; + } else { + outw(0, devpriv->pcibar1 + PCIDAS_CALIB_REG); + } + + /* set mux limits and gain */ + bits = PCIDAS_AI_CHAN(chan) | PCIDAS_AI_GAIN(range); + /* set unipolar/bipolar */ + if (comedi_range_is_unipolar(s, range)) + bits |= PCIDAS_AI_UNIP; + /* set single-ended/differential */ + if (aref != AREF_DIFF) + bits |= PCIDAS_AI_SE; + outw(bits, devpriv->pcibar1 + PCIDAS_AI_REG); + + /* clear fifo */ + outw(0, devpriv->pcibar2 + PCIDAS_AI_FIFO_CLR_REG); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outw(0, devpriv->pcibar2 + PCIDAS_AI_DATA_REG); + + /* 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->pcibar2 + PCIDAS_AI_DATA_REG); + } + + /* return the number of samples read/written */ + return n; +} + +static int cb_pcidas_ai_insn_config(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->calib_src = source; + break; + default: + return -EINVAL; + } + return insn->n; +} + +/* analog output insn for pcidas-1000 and 1200 series */ +static int cb_pcidas_ao_nofifo_insn_write(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 val = s->readback[chan]; + unsigned long flags; + int i; + + /* set channel and range */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->ao_ctrl &= ~(PCIDAS_AO_UPDATE_BOTH | + PCIDAS_AO_RANGE_MASK(chan)); + devpriv->ao_ctrl |= PCIDAS_AO_DACEN | PCIDAS_AO_RANGE(chan, range); + outw(devpriv->ao_ctrl, devpriv->pcibar1 + PCIDAS_AO_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, devpriv->pcibar4 + PCIDAS_AO_DATA_REG(chan)); + } + + s->readback[chan] = val; + + return insn->n; +} + +/* analog output insn for pcidas-1602 series */ +static int cb_pcidas_ao_fifo_insn_write(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 val = s->readback[chan]; + unsigned long flags; + int i; + + /* clear dac fifo */ + outw(0, devpriv->pcibar4 + PCIDAS_AO_FIFO_CLR_REG); + + /* set channel and range */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->ao_ctrl &= ~(PCIDAS_AO_CHAN_MASK | PCIDAS_AO_RANGE_MASK(chan) | + PCIDAS_AO_PACER_MASK); + devpriv->ao_ctrl |= PCIDAS_AO_DACEN | PCIDAS_AO_RANGE(chan, range) | + PCIDAS_AO_CHAN_EN(chan) | PCIDAS_AO_START; + outw(devpriv->ao_ctrl, devpriv->pcibar1 + PCIDAS_AO_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, devpriv->pcibar4 + PCIDAS_AO_FIFO_REG); + } + + s->readback[chan] = val; + + return insn->n; +} + +static int cb_pcidas_eeprom_ready(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 = inb(devpriv->amcc + AMCC_OP_REG_MCSR_NVCMD); + if ((status & MCSR_NV_BUSY) == 0) + return 0; + return -EBUSY; +} + +static int cb_pcidas_eeprom_insn_read(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); + int ret; + int i; + + for (i = 0; i < insn->n; i++) { + /* make sure eeprom is ready */ + ret = comedi_timeout(dev, s, insn, cb_pcidas_eeprom_ready, 0); + if (ret) + return ret; + + /* set address (chan) and read operation */ + outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_LOW_ADDR, + devpriv->amcc + AMCC_OP_REG_MCSR_NVCMD); + outb(chan & 0xff, devpriv->amcc + AMCC_OP_REG_MCSR_NVDATA); + outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_HIGH_ADDR, + devpriv->amcc + AMCC_OP_REG_MCSR_NVCMD); + outb((chan >> 8) & 0xff, + devpriv->amcc + AMCC_OP_REG_MCSR_NVDATA); + outb(MCSR_NV_ENABLE | MCSR_NV_READ, + devpriv->amcc + AMCC_OP_REG_MCSR_NVCMD); + + /* wait for data to be returned */ + ret = comedi_timeout(dev, s, insn, cb_pcidas_eeprom_ready, 0); + if (ret) + return ret; + + data[i] = inb(devpriv->amcc + AMCC_OP_REG_MCSR_NVDATA); + } + + return insn->n; +} + +static void cb_pcidas_calib_write(struct comedi_device *dev, + unsigned int val, unsigned int len, + bool trimpot) +{ + struct cb_pcidas_private *devpriv = dev->private; + unsigned int calib_bits; + unsigned int bit; + + calib_bits = PCIDAS_CALIB_EN | PCIDAS_CALIB_SRC(devpriv->calib_src); + if (trimpot) { + /* select trimpot */ + calib_bits |= PCIDAS_CALIB_TRIM_SEL; + outw(calib_bits, devpriv->pcibar1 + PCIDAS_CALIB_REG); + } + + /* write bitstream to calibration device */ + for (bit = 1 << (len - 1); bit; bit >>= 1) { + if (val & bit) + calib_bits |= PCIDAS_CALIB_DATA; + else + calib_bits &= ~PCIDAS_CALIB_DATA; + udelay(1); + outw(calib_bits, devpriv->pcibar1 + PCIDAS_CALIB_REG); + } + udelay(1); + + calib_bits = PCIDAS_CALIB_EN | PCIDAS_CALIB_SRC(devpriv->calib_src); + + if (!trimpot) { + /* select caldac */ + outw(calib_bits | PCIDAS_CALIB_8800_SEL, + devpriv->pcibar1 + PCIDAS_CALIB_REG); + udelay(1); + } + + /* latch value to trimpot/caldac */ + outw(calib_bits, devpriv->pcibar1 + PCIDAS_CALIB_REG); +} + +static int cb_pcidas_caldac_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + if (s->readback[chan] != val) { + /* write 11-bit channel/value to caldac */ + cb_pcidas_calib_write(dev, (chan << 8) | val, 11, + false); + s->readback[chan] = val; + } + } + + return insn->n; +} + +static void cb_pcidas_dac08_write(struct comedi_device *dev, unsigned int val) +{ + struct cb_pcidas_private *devpriv = dev->private; + + val |= PCIDAS_CALIB_EN | PCIDAS_CALIB_SRC(devpriv->calib_src); + + /* latch the new value into the caldac */ + outw(val, devpriv->pcibar1 + PCIDAS_CALIB_REG); + udelay(1); + outw(val | PCIDAS_CALIB_DAC08_SEL, + devpriv->pcibar1 + PCIDAS_CALIB_REG); + udelay(1); + outw(val, devpriv->pcibar1 + PCIDAS_CALIB_REG); + udelay(1); +} + +static int cb_pcidas_dac08_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + if (s->readback[chan] != val) { + cb_pcidas_dac08_write(dev, val); + s->readback[chan] = val; + } + } + + return insn->n; +} + +static void cb_pcidas_trimpot_write(struct comedi_device *dev, + unsigned int chan, unsigned int val) +{ + const struct cb_pcidas_board *board = dev->board_ptr; + + if (board->has_ad8402) { + /* write 10-bit channel/value to AD8402 trimpot */ + cb_pcidas_calib_write(dev, (chan << 8) | val, 10, true); + } else { + /* write 7-bit value to AD7376 trimpot */ + cb_pcidas_calib_write(dev, val, 7, true); + } +} + +static int cb_pcidas_trimpot_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + if (s->readback[chan] != val) { + cb_pcidas_trimpot_write(dev, chan, val); + s->readback[chan] = val; + } + } + + return insn->n; +} + +static int cb_pcidas_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + if (chan != (chan0 + i) % s->n_chan) { + dev_dbg(dev->class_dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + return -EINVAL; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same gain\n"); + return -EINVAL; + } + } + return 0; +} + +static int cb_pcidas_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct cb_pcidas_board *board = dev->board_ptr; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err |= -EINVAL; + if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW) + err |= -EINVAL; + if (cmd->start_src == TRIG_EXT && + (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT)) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* External trigger, only CR_EDGE and CR_INVERT flags allowed */ + if ((cmd->start_arg + & (CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT))) != 0) { + cmd->start_arg &= ~(CR_FLAGS_MASK & + ~(CR_EDGE | CR_INVERT)); + err |= -EINVAL; + } + if (!board->is_1602 && (cmd->start_arg & CR_INVERT)) { + cmd->start_arg &= (CR_FLAGS_MASK & ~CR_INVERT); + err |= -EINVAL; + } + break; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ai_speed * + cmd->chanlist_len); + } + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= cb_pcidas_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int cb_pcidas_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct cb_pcidas_board *board = dev->board_ptr; + struct cb_pcidas_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int bits; + unsigned long flags; + + /* make sure PCIDAS_CALIB_EN is disabled */ + outw(0, devpriv->pcibar1 + PCIDAS_CALIB_REG); + /* initialize before settings pacer source and count values */ + outw(PCIDAS_TRIG_SEL_NONE, devpriv->pcibar1 + PCIDAS_TRIG_REG); + /* clear fifo */ + outw(0, devpriv->pcibar2 + PCIDAS_AI_FIFO_CLR_REG); + + /* set mux limits, gain and pacer source */ + bits = PCIDAS_AI_FIRST(CR_CHAN(cmd->chanlist[0])) | + PCIDAS_AI_LAST(CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])) | + PCIDAS_AI_GAIN(range0); + /* set unipolar/bipolar */ + if (comedi_range_is_unipolar(s, range0)) + bits |= PCIDAS_AI_UNIP; + /* set singleended/differential */ + if (CR_AREF(cmd->chanlist[0]) != AREF_DIFF) + bits |= PCIDAS_AI_SE; + /* set pacer source */ + if (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT) + bits |= PCIDAS_AI_PACER_EXTP; + else + bits |= PCIDAS_AI_PACER_INT; + outw(bits, devpriv->pcibar1 + PCIDAS_AI_REG); + + /* load counters */ + if (cmd->scan_begin_src == TRIG_TIMER || + cmd->convert_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + } + + /* enable interrupts */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->ctrl |= PCIDAS_CTRL_INTE; + devpriv->ctrl &= ~PCIDAS_CTRL_INT_MASK; + if (cmd->flags & CMDF_WAKE_EOS) { + if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1) { + /* interrupt end of burst */ + devpriv->ctrl |= PCIDAS_CTRL_INT_EOS; + } else { + /* interrupt fifo not empty */ + devpriv->ctrl |= PCIDAS_CTRL_INT_FNE; + } + } else { + /* interrupt fifo half full */ + devpriv->ctrl |= PCIDAS_CTRL_INT_FHF; + } + + /* enable (and clear) interrupts */ + outw(devpriv->ctrl | + PCIDAS_CTRL_EOAI | PCIDAS_CTRL_INT_CLR | PCIDAS_CTRL_LADFUL, + devpriv->pcibar1 + PCIDAS_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* set start trigger and burst mode */ + bits = 0; + if (cmd->start_src == TRIG_NOW) { + bits |= PCIDAS_TRIG_SEL_SW; + } else { /* TRIG_EXT */ + bits |= PCIDAS_TRIG_SEL_EXT | PCIDAS_TRIG_EN | PCIDAS_TRIG_CLR; + if (board->is_1602) { + if (cmd->start_arg & CR_INVERT) + bits |= PCIDAS_TRIG_POL; + if (cmd->start_arg & CR_EDGE) + bits |= PCIDAS_TRIG_MODE; + } + } + if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1) + bits |= PCIDAS_TRIG_BURSTE; + outw(bits, devpriv->pcibar1 + PCIDAS_TRIG_REG); + + 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 *board = dev->board_ptr; + struct cb_pcidas_private *devpriv = dev->private; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ao_scan_speed); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int arg = cmd->scan_begin_arg; + + comedi_8254_cascade_ns_to_timer(devpriv->ao_pacer, + &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= cb_pcidas_ao_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int cb_pcidas_ai_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->ctrl &= ~(PCIDAS_CTRL_INTE | PCIDAS_CTRL_EOAIE); + outw(devpriv->ctrl, devpriv->pcibar1 + PCIDAS_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* disable start trigger source and burst mode */ + outw(PCIDAS_TRIG_SEL_NONE, devpriv->pcibar1 + PCIDAS_TRIG_REG); + outw(PCIDAS_AI_PACER_SW, devpriv->pcibar1 + PCIDAS_AI_REG); + + return 0; +} + +static void cb_pcidas_ao_load_fifo(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int nsamples) +{ + struct cb_pcidas_private *devpriv = dev->private; + unsigned int nbytes; + + nsamples = comedi_nsamples_left(s, nsamples); + nbytes = comedi_buf_read_samples(s, devpriv->ao_buffer, nsamples); + + nsamples = comedi_bytes_to_samples(s, nbytes); + outsw(devpriv->pcibar4 + PCIDAS_AO_FIFO_REG, + devpriv->ao_buffer, nsamples); +} + +static int cb_pcidas_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + const struct cb_pcidas_board *board = dev->board_ptr; + struct cb_pcidas_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long flags; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + cb_pcidas_ao_load_fifo(dev, s, board->fifo_size); + + /* enable dac half-full and empty interrupts */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->ctrl |= PCIDAS_CTRL_DAEMIE | PCIDAS_CTRL_DAHFIE; + + /* enable and clear interrupts */ + outw(devpriv->ctrl | PCIDAS_CTRL_DAEMI | PCIDAS_CTRL_DAHFI, + devpriv->pcibar1 + PCIDAS_CTRL_REG); + + /* start dac */ + devpriv->ao_ctrl |= PCIDAS_AO_START | PCIDAS_AO_DACEN | PCIDAS_AO_EMPTY; + outw(devpriv->ao_ctrl, devpriv->pcibar1 + PCIDAS_AO_REG); + + spin_unlock_irqrestore(&dev->spinlock, flags); + + async->inttrig = NULL; + + return 0; +} + +static int cb_pcidas_ao_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct cb_pcidas_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i; + unsigned long flags; + + /* set channel limits, gain */ + spin_lock_irqsave(&dev->spinlock, flags); + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + /* enable channel */ + devpriv->ao_ctrl |= PCIDAS_AO_CHAN_EN(chan); + /* set range */ + devpriv->ao_ctrl |= PCIDAS_AO_RANGE(chan, range); + } + + /* disable analog out before settings pacer source and count values */ + outw(devpriv->ao_ctrl, devpriv->pcibar1 + PCIDAS_AO_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* clear fifo */ + outw(0, devpriv->pcibar4 + PCIDAS_AO_FIFO_CLR_REG); + + /* load counters */ + if (cmd->scan_begin_src == TRIG_TIMER) { + comedi_8254_update_divisors(devpriv->ao_pacer); + comedi_8254_pacer_enable(devpriv->ao_pacer, 1, 2, true); + } + + /* set pacer source */ + spin_lock_irqsave(&dev->spinlock, flags); + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + devpriv->ao_ctrl |= PCIDAS_AO_PACER_INT; + break; + case TRIG_EXT: + devpriv->ao_ctrl |= PCIDAS_AO_PACER_EXTP; + break; + default: + spin_unlock_irqrestore(&dev->spinlock, flags); + dev_err(dev->class_dev, "error setting dac pacer source\n"); + return -1; + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + async->inttrig = cb_pcidas_ao_inttrig; + + return 0; +} + +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->ctrl &= ~(PCIDAS_CTRL_DAHFIE | PCIDAS_CTRL_DAEMIE); + outw(devpriv->ctrl, devpriv->pcibar1 + PCIDAS_CTRL_REG); + + /* disable output */ + devpriv->ao_ctrl &= ~(PCIDAS_AO_DACEN | PCIDAS_AO_PACER_MASK); + outw(devpriv->ao_ctrl, devpriv->pcibar1 + PCIDAS_AO_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +static unsigned int cb_pcidas_ao_interrupt(struct comedi_device *dev, + unsigned int status) +{ + const struct cb_pcidas_board *board = dev->board_ptr; + struct cb_pcidas_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int irq_clr = 0; + + if (status & PCIDAS_CTRL_DAEMI) { + irq_clr |= PCIDAS_CTRL_DAEMI; + + if (inw(devpriv->pcibar4 + PCIDAS_AO_REG) & PCIDAS_AO_EMPTY) { + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + } else { + dev_err(dev->class_dev, "dac fifo underflow\n"); + async->events |= COMEDI_CB_ERROR; + } + } + } else if (status & PCIDAS_CTRL_DAHFI) { + irq_clr |= PCIDAS_CTRL_DAHFI; + + cb_pcidas_ao_load_fifo(dev, s, board->fifo_size / 2); + } + + comedi_handle_events(dev, s); + + return irq_clr; +} + +static unsigned int cb_pcidas_ai_interrupt(struct comedi_device *dev, + unsigned int status) +{ + const struct cb_pcidas_board *board = dev->board_ptr; + struct cb_pcidas_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 irq_clr = 0; + + if (status & PCIDAS_CTRL_ADHFI) { + unsigned int num_samples; + + irq_clr |= PCIDAS_CTRL_INT_CLR; + + /* FIFO is half-full - read data */ + num_samples = comedi_nsamples_left(s, board->fifo_size / 2); + insw(devpriv->pcibar2 + PCIDAS_AI_DATA_REG, + devpriv->ai_buffer, num_samples); + comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples); + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + } else if (status & (PCIDAS_CTRL_ADNEI | PCIDAS_CTRL_EOBI)) { + unsigned int i; + + irq_clr |= PCIDAS_CTRL_INT_CLR; + + /* FIFO is not empty - read data until empty or timeoout */ + for (i = 0; i < 10000; i++) { + unsigned short val; + + /* break if fifo is empty */ + if ((inw(devpriv->pcibar1 + PCIDAS_CTRL_REG) & + PCIDAS_CTRL_ADNE) == 0) + break; + val = inw(devpriv->pcibar2 + PCIDAS_AI_DATA_REG); + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + } else if (status & PCIDAS_CTRL_EOAI) { + irq_clr |= PCIDAS_CTRL_EOAI; + + dev_err(dev->class_dev, + "bug! encountered end of acquisition interrupt?\n"); + } + + /* check for fifo overflow */ + if (status & PCIDAS_CTRL_LADFUL) { + irq_clr |= PCIDAS_CTRL_LADFUL; + + dev_err(dev->class_dev, "fifo overflow\n"); + async->events |= COMEDI_CB_ERROR; + } + + comedi_handle_events(dev, s); + + return irq_clr; +} + +static irqreturn_t cb_pcidas_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct cb_pcidas_private *devpriv = dev->private; + unsigned int irq_clr = 0; + unsigned int amcc_status; + unsigned int status; + + if (!dev->attached) + return IRQ_NONE; + + amcc_status = inl(devpriv->amcc + AMCC_OP_REG_INTCSR); + + if ((INTCSR_INTR_ASSERTED & amcc_status) == 0) + return IRQ_NONE; + + /* make sure mailbox 4 is empty */ + inl_p(devpriv->amcc + AMCC_OP_REG_IMB4); + /* clear interrupt on amcc s5933 */ + outl(devpriv->amcc_intcsr | INTCSR_INBOX_INTR_STATUS, + devpriv->amcc + AMCC_OP_REG_INTCSR); + + status = inw(devpriv->pcibar1 + PCIDAS_CTRL_REG); + + /* handle analog output interrupts */ + if (status & PCIDAS_CTRL_AO_INT) + irq_clr |= cb_pcidas_ao_interrupt(dev, status); + + /* handle analog input interrupts */ + if (status & PCIDAS_CTRL_AI_INT) + irq_clr |= cb_pcidas_ai_interrupt(dev, status); + + if (irq_clr) { + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->ctrl | irq_clr, + devpriv->pcibar1 + PCIDAS_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + } + + 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 *board = NULL; + struct cb_pcidas_private *devpriv; + struct comedi_subdevice *s; + int i; + int ret; + + if (context < ARRAY_SIZE(cb_pcidas_boards)) + board = &cb_pcidas_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->amcc = pci_resource_start(pcidev, 0); + devpriv->pcibar1 = pci_resource_start(pcidev, 1); + devpriv->pcibar2 = pci_resource_start(pcidev, 2); + dev->iobase = pci_resource_start(pcidev, 3); + if (board->has_ao) + devpriv->pcibar4 = pci_resource_start(pcidev, 4); + + /* disable and clear interrupts on amcc s5933 */ + outl(INTCSR_INBOX_INTR_STATUS, + devpriv->amcc + AMCC_OP_REG_INTCSR); + + ret = request_irq(pcidev->irq, cb_pcidas_interrupt, IRQF_SHARED, + "cb_pcidas", dev); + if (ret) { + dev_dbg(dev->class_dev, "unable to allocate irq %d\n", + pcidev->irq); + return ret; + } + dev->irq = pcidev->irq; + + dev->pacer = comedi_8254_init(dev->iobase + PCIDAS_AI_8254_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + devpriv->ao_pacer = comedi_8254_init(dev->iobase + PCIDAS_AO_8254_BASE, + I8254_OSC_BASE_10MHZ, + I8254_IO8, 0); + if (!devpriv->ao_pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 7); + 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 = board->is_16bit ? 0xffff : 0x0fff; + s->range_table = board->use_alt_range ? &cb_pcidas_alt_ranges + : &cb_pcidas_ranges; + s->insn_read = cb_pcidas_ai_insn_read; + s->insn_config = cb_pcidas_ai_insn_config; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmd = cb_pcidas_ai_cmd; + s->do_cmdtest = cb_pcidas_ai_cmdtest; + s->cancel = cb_pcidas_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = 2; + s->maxdata = board->is_16bit ? 0xffff : 0x0fff; + s->range_table = &cb_pcidas_ao_ranges; + s->insn_write = (board->has_ao_fifo) + ? cb_pcidas_ao_fifo_insn_write + : cb_pcidas_ao_nofifo_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + if (dev->irq && board->has_ao_fifo) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->len_chanlist = s->n_chan; + s->do_cmdtest = cb_pcidas_ao_cmdtest; + s->do_cmd = cb_pcidas_ao_cmd; + s->cancel = cb_pcidas_ao_cancel; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 */ + s = &dev->subdevices[2]; + ret = subdev_8255_init(dev, s, NULL, PCIDAS_8255_BASE); + if (ret) + return ret; + + /* Memory subdevice - 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 = cb_pcidas_eeprom_insn_read; + + /* Calibration subdevice - 8800 caldac */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 8; + s->maxdata = 0xff; + s->insn_write = cb_pcidas_caldac_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) { + unsigned int val = s->maxdata / 2; + + /* write 11-bit channel/value to caldac */ + cb_pcidas_calib_write(dev, (i << 8) | val, 11, false); + s->readback[i] = val; + } + + /* Calibration subdevice - trim potentiometer */ + s = &dev->subdevices[5]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + if (board->has_ad8402) { + /* + * pci-das1602/16 have an AD8402 trimpot: + * chan 0 : adc gain + * chan 1 : adc postgain offset + */ + s->n_chan = 2; + s->maxdata = 0xff; + } else { + /* all other boards have an AD7376 trimpot */ + s->n_chan = 1; + s->maxdata = 0x7f; + } + s->insn_write = cb_pcidas_trimpot_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) { + cb_pcidas_trimpot_write(dev, i, s->maxdata / 2); + s->readback[i] = s->maxdata / 2; + } + + /* Calibration subdevice - pci-das1602/16 pregain offset (dac08) */ + s = &dev->subdevices[6]; + if (board->has_dac08) { + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 1; + s->maxdata = 0xff; + s->insn_write = cb_pcidas_dac08_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) { + cb_pcidas_dac08_write(dev, s->maxdata / 2); + s->readback[i] = s->maxdata / 2; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* make sure mailbox 4 is empty */ + inl(devpriv->amcc + AMCC_OP_REG_IMB4); + /* Set bits to enable incoming mailbox interrupts on amcc s5933. */ + devpriv->amcc_intcsr = INTCSR_INBOX_BYTE(3) | INTCSR_INBOX_SELECT(3) | + INTCSR_INBOX_FULL_INT; + /* clear and enable interrupt on amcc s5933 */ + outl(devpriv->amcc_intcsr | INTCSR_INBOX_INTR_STATUS, + devpriv->amcc + 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->amcc) + outl(INTCSR_INBOX_INTR_STATUS, + devpriv->amcc + AMCC_OP_REG_INTCSR); + kfree(devpriv->ao_pacer); + } + comedi_pci_detach(dev); +} + +static struct comedi_driver cb_pcidas_driver = { + .driver_name = "cb_pcidas", + .module = THIS_MODULE, + .auto_attach = cb_pcidas_auto_attach, + .detach = cb_pcidas_detach, +}; + +static int cb_pcidas_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &cb_pcidas_driver, + id->driver_data); +} + +static const struct pci_device_id cb_pcidas_pci_table[] = { + { PCI_VDEVICE(CB, 0x0001), BOARD_PCIDAS1602_16 }, + { PCI_VDEVICE(CB, 0x000f), BOARD_PCIDAS1200 }, + { PCI_VDEVICE(CB, 0x0010), BOARD_PCIDAS1602_12 }, + { PCI_VDEVICE(CB, 0x0019), BOARD_PCIDAS1200_JR }, + { PCI_VDEVICE(CB, 0x001c), BOARD_PCIDAS1602_16_JR }, + { PCI_VDEVICE(CB, 0x004c), BOARD_PCIDAS1000 }, + { PCI_VDEVICE(CB, 0x001a), BOARD_PCIDAS1001 }, + { PCI_VDEVICE(CB, 0x001b), BOARD_PCIDAS1002 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cb_pcidas_pci_table); + +static struct pci_driver cb_pcidas_pci_driver = { + .name = "cb_pcidas", + .id_table = cb_pcidas_pci_table, + .probe = cb_pcidas_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(cb_pcidas_driver, cb_pcidas_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for MeasurementComputing PCI-DAS series"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/cb_pcidas64.c b/drivers/comedi/drivers/cb_pcidas64.c new file mode 100644 index 000000000..ca6038a25 --- /dev/null +++ b/drivers/comedi/drivers/cb_pcidas64.c @@ -0,0 +1,4118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: cb_pcidas64 + * Description: MeasurementComputing PCI-DAS64xx, 60XX, and 4020 series + * with the PLX 9080 PCI controller + * Author: Frank Mori Hess <fmhess@users.sourceforge.net> + * Status: works + * Updated: Fri, 02 Nov 2012 18:58:55 +0000 + * Devices: [Measurement Computing] PCI-DAS6402/16 (cb_pcidas64), + * PCI-DAS6402/12, PCI-DAS64/M1/16, PCI-DAS64/M2/16, + * PCI-DAS64/M3/16, PCI-DAS6402/16/JR, PCI-DAS64/M1/16/JR, + * PCI-DAS64/M2/16/JR, PCI-DAS64/M3/16/JR, PCI-DAS64/M1/14, + * PCI-DAS64/M2/14, PCI-DAS64/M3/14, PCI-DAS6013, PCI-DAS6014, + * PCI-DAS6023, PCI-DAS6025, PCI-DAS6030, + * PCI-DAS6031, PCI-DAS6032, PCI-DAS6033, PCI-DAS6034, + * PCI-DAS6035, PCI-DAS6036, PCI-DAS6040, PCI-DAS6052, + * PCI-DAS6070, PCI-DAS6071, PCI-DAS4020/12 + * + * Configuration options: + * None. + * + * Manual attachment of PCI cards with the comedi_config utility is not + * supported by this driver; they are attached automatically. + * + * These boards may be autocalibrated with the comedi_calibrate utility. + * + * To select the bnc trigger input on the 4020 (instead of the dio input), + * specify a nonzero channel in the chanspec. If you wish to use an external + * master clock on the 4020, you may do so by setting the scan_begin_src + * to TRIG_OTHER, and using an INSN_CONFIG_TIMER_1 configuration insn + * to configure the divisor to use for the external clock. + * + * Some devices are not identified because the PCI device IDs are not yet + * known. If you have such a board, please let the maintainers know. + */ + +/* + * TODO: + * make it return error if user attempts an ai command that uses the + * external queue, and an ao command simultaneously user counter subdevice + * there are a number of boards this driver will support when they are + * fully released, but does not yet since the pci device id numbers + * are not yet available. + * + * support prescaled 100khz clock for slow pacing (not available on 6000 + * series?) + * + * make ao fifo size adjustable like ai fifo + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> + +#include "plx9080.h" + +#define TIMER_BASE 25 /* 40MHz master clock */ +/* + * 100kHz 'prescaled' clock for slow acquisition, + * maybe I'll support this someday + */ +#define PRESCALED_TIMER_BASE 10000 +#define DMA_BUFFER_SIZE 0x1000 +#define DAC_FIFO_SIZE 0x2000 + +/* maximum value that can be loaded into board's 24-bit counters */ +static const int max_counter_value = 0xffffff; + +/* PCI-DAS64xxx base addresses */ + +/* devpriv->main_iobase registers */ +enum write_only_registers { + INTR_ENABLE_REG = 0x0, /* interrupt enable register */ + HW_CONFIG_REG = 0x2, /* hardware config register */ + DAQ_SYNC_REG = 0xc, + DAQ_ATRIG_LOW_4020_REG = 0xc, + ADC_CONTROL0_REG = 0x10, /* adc control register 0 */ + ADC_CONTROL1_REG = 0x12, /* adc control register 1 */ + CALIBRATION_REG = 0x14, + /* lower 16 bits of adc sample interval counter */ + ADC_SAMPLE_INTERVAL_LOWER_REG = 0x16, + /* upper 8 bits of adc sample interval counter */ + ADC_SAMPLE_INTERVAL_UPPER_REG = 0x18, + /* lower 16 bits of delay interval counter */ + ADC_DELAY_INTERVAL_LOWER_REG = 0x1a, + /* upper 8 bits of delay interval counter */ + ADC_DELAY_INTERVAL_UPPER_REG = 0x1c, + /* lower 16 bits of hardware conversion/scan counter */ + ADC_COUNT_LOWER_REG = 0x1e, + /* upper 8 bits of hardware conversion/scan counter */ + ADC_COUNT_UPPER_REG = 0x20, + ADC_START_REG = 0x22, /* software trigger to start acquisition */ + ADC_CONVERT_REG = 0x24, /* initiates single conversion */ + ADC_QUEUE_CLEAR_REG = 0x26, /* clears adc queue */ + ADC_QUEUE_LOAD_REG = 0x28, /* loads adc queue */ + ADC_BUFFER_CLEAR_REG = 0x2a, + /* high channel for internal queue, use adc_chan_bits() inline above */ + ADC_QUEUE_HIGH_REG = 0x2c, + DAC_CONTROL0_REG = 0x50, /* dac control register 0 */ + DAC_CONTROL1_REG = 0x52, /* dac control register 0 */ + /* lower 16 bits of dac sample interval counter */ + DAC_SAMPLE_INTERVAL_LOWER_REG = 0x54, + /* upper 8 bits of dac sample interval counter */ + DAC_SAMPLE_INTERVAL_UPPER_REG = 0x56, + DAC_SELECT_REG = 0x60, + DAC_START_REG = 0x64, + DAC_BUFFER_CLEAR_REG = 0x66, /* clear dac buffer */ +}; + +static inline unsigned int dac_convert_reg(unsigned int channel) +{ + return 0x70 + (2 * (channel & 0x1)); +} + +static inline unsigned int dac_lsb_4020_reg(unsigned int channel) +{ + return 0x70 + (4 * (channel & 0x1)); +} + +static inline unsigned int dac_msb_4020_reg(unsigned int channel) +{ + return 0x72 + (4 * (channel & 0x1)); +} + +enum read_only_registers { + /* + * hardware status register, + * reading this apparently clears pending interrupts as well + */ + HW_STATUS_REG = 0x0, + PIPE1_READ_REG = 0x4, + ADC_READ_PNTR_REG = 0x8, + LOWER_XFER_REG = 0x10, + ADC_WRITE_PNTR_REG = 0xc, + PREPOST_REG = 0x14, +}; + +enum read_write_registers { + I8255_4020_REG = 0x48, /* 8255 offset, for 4020 only */ + /* external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG */ + ADC_QUEUE_FIFO_REG = 0x100, + ADC_FIFO_REG = 0x200, /* adc data fifo */ + /* dac data fifo, has weird interactions with external channel queue */ + DAC_FIFO_REG = 0x300, +}; + +/* dev->mmio registers */ +enum dio_counter_registers { + DIO_8255_OFFSET = 0x0, + DO_REG = 0x20, + DI_REG = 0x28, + DIO_DIRECTION_60XX_REG = 0x40, + DIO_DATA_60XX_REG = 0x48, +}; + +/* bit definitions for write-only registers */ + +enum intr_enable_contents { + ADC_INTR_SRC_MASK = 0x3, /* adc interrupt source mask */ + ADC_INTR_QFULL_BITS = 0x0, /* interrupt fifo quarter full */ + ADC_INTR_EOC_BITS = 0x1, /* interrupt end of conversion */ + ADC_INTR_EOSCAN_BITS = 0x2, /* interrupt end of scan */ + ADC_INTR_EOSEQ_BITS = 0x3, /* interrupt end of sequence mask */ + EN_ADC_INTR_SRC_BIT = 0x4, /* enable adc interrupt source */ + EN_ADC_DONE_INTR_BIT = 0x8, /* enable adc acquisition done intr */ + DAC_INTR_SRC_MASK = 0x30, + DAC_INTR_QEMPTY_BITS = 0x0, + DAC_INTR_HIGH_CHAN_BITS = 0x10, + EN_DAC_INTR_SRC_BIT = 0x40, /* enable dac interrupt source */ + EN_DAC_DONE_INTR_BIT = 0x80, + EN_ADC_ACTIVE_INTR_BIT = 0x200, /* enable adc active interrupt */ + EN_ADC_STOP_INTR_BIT = 0x400, /* enable adc stop trigger interrupt */ + EN_DAC_ACTIVE_INTR_BIT = 0x800, /* enable dac active interrupt */ + EN_DAC_UNDERRUN_BIT = 0x4000, /* enable dac underrun status bit */ + EN_ADC_OVERRUN_BIT = 0x8000, /* enable adc overrun status bit */ +}; + +enum hw_config_contents { + MASTER_CLOCK_4020_MASK = 0x3, /* master clock source mask for 4020 */ + INTERNAL_CLOCK_4020_BITS = 0x1, /* use 40 MHz internal master clock */ + BNC_CLOCK_4020_BITS = 0x2, /* use BNC input for master clock */ + EXT_CLOCK_4020_BITS = 0x3, /* use dio input for master clock */ + EXT_QUEUE_BIT = 0x200, /* use external channel/gain queue */ + /* use 225 nanosec strobe when loading dac instead of 50 nanosec */ + SLOW_DAC_BIT = 0x400, + /* + * bit with unknown function yet given as default value in pci-das64 + * manual + */ + HW_CONFIG_DUMMY_BITS = 0x2000, + /* bit selects channels 1/0 for analog input/output, otherwise 0/1 */ + DMA_CH_SELECT_BIT = 0x8000, + FIFO_SIZE_REG = 0x4, /* allows adjustment of fifo sizes */ + DAC_FIFO_SIZE_MASK = 0xff00, /* bits that set dac fifo size */ + DAC_FIFO_BITS = 0xf800, /* 8k sample ao fifo */ +}; + +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, +}; + +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 */ + /* level-sensitive gate (for digital) */ + ADC_GATE_LEVEL_BIT = 0x4, + 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 u16 adc_lo_chan_4020_bits(unsigned int channel) +{ + return (channel & 0x3) << 8; +}; + +static inline u16 adc_hi_chan_4020_bits(unsigned int channel) +{ + return (channel & 0x3) << 10; +}; + +static inline u16 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 u16 adc_src_bits(unsigned int source) +{ + return (source & 0xf) << 3; +}; + +static inline u16 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 u16 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 u16 pipe_full_bits(u16 hw_status_bits) +{ + return (hw_status_bits >> 10) & 0x3; +}; + +static inline unsigned int dma_chain_flag_bits(u16 prepost_bits) +{ + return (prepost_bits >> 6) & 0x3; +} + +static inline unsigned int adc_upper_read_ptr_code(u16 prepost_bits) +{ + return (prepost_bits >> 12) & 0x3; +} + +static inline unsigned int adc_upper_write_ptr_code(u16 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 u8 adc_src_4020_bits(unsigned int source) +{ + return (source << 4) & ADC_SRC_4020_MASK; +}; + +static inline u8 attenuate_bit(unsigned int channel) +{ + /* attenuate channel (+-5V input range) */ + return 1 << (channel & 0x3); +}; + +/* analog input ranges for 64xx boards */ +static const struct comedi_lrange ai_ranges_64xx = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const u8 ai_range_code_64xx[8] = { + 0x0, 0x1, 0x2, 0x3, /* bipolar 10, 5, 2,5, 1.25 */ + 0x8, 0x9, 0xa, 0xb /* unipolar 10, 5, 2.5, 1.25 */ +}; + +/* analog input ranges for 64-Mx boards */ +static const struct comedi_lrange ai_ranges_64_mx = { + 7, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const u8 ai_range_code_64_mx[7] = { + 0x0, 0x1, 0x2, 0x3, /* bipolar 5, 2.5, 1.25, 0.625 */ + 0x9, 0xa, 0xb /* unipolar 5, 2.5, 1.25 */ +}; + +/* analog input ranges for 60xx boards */ +static const struct comedi_lrange ai_ranges_60xx = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05) + } +}; + +static const u8 ai_range_code_60xx[4] = { + 0x0, 0x1, 0x4, 0x7 /* bipolar 10, 5, 0.5, 0.05 */ +}; + +/* analog input ranges for 6030, etc boards */ +static const struct comedi_lrange ai_ranges_6030 = { + 14, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.2), + BIP_RANGE(0.1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1) + } +}; + +static const u8 ai_range_code_6030[14] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, /* bip 10, 5, 2, 1, 0.5, 0.2, 0.1 */ + 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf /* uni 10, 5, 2, 1, 0.5, 0.2, 0.1 */ +}; + +/* analog input ranges for 6052, etc boards */ +static const struct comedi_lrange ai_ranges_6052 = { + 15, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1) + } +}; + +static const u8 ai_range_code_6052[15] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, /* bipolar 10 ... 0.05 */ + 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf /* unipolar 10 ... 0.1 */ +}; + +/* analog input ranges for 4020 board */ +static const struct comedi_lrange ai_ranges_4020 = { + 2, { + BIP_RANGE(5), + BIP_RANGE(1) + } +}; + +/* analog output ranges */ +static const struct comedi_lrange ao_ranges_64xx = { + 4, { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(10) + } +}; + +static const int ao_range_code_64xx[] = { + 0x0, + 0x1, + 0x2, + 0x3, +}; + +static const int ao_range_code_60xx[] = { + 0x0, +}; + +static const struct comedi_lrange ao_ranges_6030 = { + 2, { + BIP_RANGE(10), + UNI_RANGE(10) + } +}; + +static const int ao_range_code_6030[] = { + 0x0, + 0x2, +}; + +static const struct comedi_lrange ao_ranges_4020 = { + 2, { + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const int ao_range_code_4020[] = { + 0x1, + 0x0, +}; + +enum register_layout { + LAYOUT_60XX, + LAYOUT_64XX, + LAYOUT_4020, +}; + +struct hw_fifo_info { + unsigned int num_segments; + unsigned int max_segment_length; + unsigned int sample_packing_ratio; + u16 fifo_size_reg_mask; +}; + +enum pcidas64_boardid { + BOARD_PCIDAS6402_16, + BOARD_PCIDAS6402_12, + BOARD_PCIDAS64_M1_16, + BOARD_PCIDAS64_M2_16, + BOARD_PCIDAS64_M3_16, + BOARD_PCIDAS6013, + BOARD_PCIDAS6014, + BOARD_PCIDAS6023, + BOARD_PCIDAS6025, + BOARD_PCIDAS6030, + BOARD_PCIDAS6031, + BOARD_PCIDAS6032, + BOARD_PCIDAS6033, + BOARD_PCIDAS6034, + BOARD_PCIDAS6035, + BOARD_PCIDAS6036, + BOARD_PCIDAS6040, + BOARD_PCIDAS6052, + BOARD_PCIDAS6070, + BOARD_PCIDAS6071, + BOARD_PCIDAS4020_12, + BOARD_PCIDAS6402_16_JR, + BOARD_PCIDAS64_M1_16_JR, + BOARD_PCIDAS64_M2_16_JR, + BOARD_PCIDAS64_M3_16_JR, + BOARD_PCIDAS64_M1_14, + BOARD_PCIDAS64_M2_14, + BOARD_PCIDAS64_M3_14, +}; + +struct pcidas64_board { + const char *name; + int ai_se_chans; /* number of ai inputs in single-ended mode */ + int ai_bits; /* analog input resolution */ + int ai_speed; /* fastest conversion period in ns */ + const struct comedi_lrange *ai_range_table; + const u8 *ai_range_code; + int ao_nchan; /* number of analog out channels */ + int ao_bits; /* analog output resolution */ + int ao_scan_speed; /* analog output scan speed */ + const struct comedi_lrange *ao_range_table; + const int *ao_range_code; + const struct hw_fifo_info *const ai_fifo; + /* different board families have slightly different registers */ + enum register_layout layout; + unsigned has_8255:1; +}; + +static const struct hw_fifo_info ai_fifo_4020 = { + .num_segments = 2, + .max_segment_length = 0x8000, + .sample_packing_ratio = 2, + .fifo_size_reg_mask = 0x7f, +}; + +static const struct hw_fifo_info ai_fifo_64xx = { + .num_segments = 4, + .max_segment_length = 0x800, + .sample_packing_ratio = 1, + .fifo_size_reg_mask = 0x3f, +}; + +static const struct hw_fifo_info ai_fifo_60xx = { + .num_segments = 4, + .max_segment_length = 0x800, + .sample_packing_ratio = 1, + .fifo_size_reg_mask = 0x7f, +}; + +/* + * maximum number of dma transfers we will chain together into a ring + * (and the maximum number of dma buffers we maintain) + */ +#define MAX_AI_DMA_RING_COUNT (0x80000 / DMA_BUFFER_SIZE) +#define MIN_AI_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE) +#define AO_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE) +static inline unsigned int ai_dma_ring_count(const struct pcidas64_board *board) +{ + if (board->layout == LAYOUT_4020) + return MAX_AI_DMA_RING_COUNT; + + return MIN_AI_DMA_RING_COUNT; +} + +static const int bytes_in_sample = 2; + +static const struct pcidas64_board pcidas64_boards[] = { + [BOARD_PCIDAS6402_16] = { + .name = "pci-das6402/16", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_range_code = ai_range_code_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS6402_12] = { + .name = "pci-das6402/12", /* XXX check */ + .ai_se_chans = 64, + .ai_bits = 12, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_range_code = ai_range_code_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M1_16] = { + .name = "pci-das64/m1/16", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 1000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M2_16] = { + .name = "pci-das64/m2/16", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 500, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M3_16] = { + .name = "pci-das64/m3/16", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 333, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS6013] = { + .name = "pci-das6013", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_bits = 16, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ao_range_table = &range_bipolar10, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6014] = { + .name = "pci-das6014", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ao_range_table = &range_bipolar10, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6023] = { + .name = "pci-das6023", + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ao_range_table = &range_bipolar10, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS6025] = { + .name = "pci-das6025", + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ao_range_table = &range_bipolar10, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS6030] = { + .name = "pci-das6030", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ai_range_code = ai_range_code_6030, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6031] = { + .name = "pci-das6031", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ai_range_code = ai_range_code_6030, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6032] = { + .name = "pci-das6032", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 0, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ai_range_code = ai_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6033] = { + .name = "pci-das6033", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 0, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ai_range_code = ai_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6034] = { + .name = "pci-das6034", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_scan_speed = 0, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6035] = { + .name = "pci-das6035", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ao_range_table = &range_bipolar10, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6036] = { + .name = "pci-das6036", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_range_code = ai_range_code_60xx, + .ao_range_table = &range_bipolar10, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6040] = { + .name = "pci-das6040", + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 2000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 1000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ai_range_code = ai_range_code_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6052] = { + .name = "pci-das6052", + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 3333, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 3333, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ai_range_code = ai_range_code_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6070] = { + .name = "pci-das6070", + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 800, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 1000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ai_range_code = ai_range_code_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS6071] = { + .name = "pci-das6071", + .ai_se_chans = 64, + .ai_bits = 12, + .ai_speed = 800, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 1000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ai_range_code = ai_range_code_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + [BOARD_PCIDAS4020_12] = { + .name = "pci-das4020/12", + .ai_se_chans = 4, + .ai_bits = 12, + .ai_speed = 50, + .ao_bits = 12, + .ao_nchan = 2, + .ao_scan_speed = 0, /* no hardware pacing on ao */ + .layout = LAYOUT_4020, + .ai_range_table = &ai_ranges_4020, + .ao_range_table = &ao_ranges_4020, + .ao_range_code = ao_range_code_4020, + .ai_fifo = &ai_fifo_4020, + .has_8255 = 1, + }, +#if 0 + /* The device id for these boards is unknown */ + + [BOARD_PCIDAS6402_16_JR] = { + .name = "pci-das6402/16/jr", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_range_code = ai_range_code_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M1_16_JR] = { + .name = "pci-das64/m1/16/jr", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 1000, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M2_16_JR] = { + .name = "pci-das64/m2/16/jr", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 500, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M3_16_JR] = { + .name = "pci-das64/m3/16/jr", + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 333, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M1_14] = { + .name = "pci-das64/m1/14", + .ai_se_chans = 64, + .ai_bits = 14, + .ai_speed = 1000, + .ao_nchan = 2, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M2_14] = { + .name = "pci-das64/m2/14", + .ai_se_chans = 64, + .ai_bits = 14, + .ai_speed = 500, + .ao_nchan = 2, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + [BOARD_PCIDAS64_M3_14] = { + .name = "pci-das64/m3/14", + .ai_se_chans = 64, + .ai_bits = 14, + .ai_speed = 333, + .ao_nchan = 2, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64_mx, + .ai_range_code = ai_range_code_64_mx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, +#endif +}; + +static inline unsigned short se_diff_bit_6xxx(struct comedi_device *dev, + int use_differential) +{ + const struct pcidas64_board *board = dev->board_ptr; + + if ((board->layout == LAYOUT_64XX && !use_differential) || + (board->layout == LAYOUT_60XX && use_differential)) + return ADC_SE_DIFF_BIT; + + return 0; +} + +struct ext_clock_info { + /* master clock divisor to use for scans with external master clock */ + unsigned int divisor; + /* chanspec for master clock input when used as scan begin src */ + unsigned int chanspec; +}; + +/* this structure is for data unique to this hardware driver. */ +struct pcidas64_private { + /* base addresses (physical) */ + resource_size_t main_phys_iobase; + resource_size_t dio_counter_phys_iobase; + /* base addresses (ioremapped) */ + void __iomem *plx9080_iobase; + void __iomem *main_iobase; + /* local address (used by dma controller) */ + u32 local0_iobase; + u32 local1_iobase; + /* dma buffers for analog input */ + u16 *ai_buffer[MAX_AI_DMA_RING_COUNT]; + /* physical addresses of ai dma buffers */ + dma_addr_t ai_buffer_bus_addr[MAX_AI_DMA_RING_COUNT]; + /* + * array of ai dma descriptors read by plx9080, + * allocated to get proper alignment + */ + struct plx_dma_desc *ai_dma_desc; + /* physical address of ai dma descriptor array */ + dma_addr_t ai_dma_desc_bus_addr; + /* + * index of the ai dma descriptor/buffer + * that is currently being used + */ + unsigned int ai_dma_index; + /* dma buffers for analog output */ + u16 *ao_buffer[AO_DMA_RING_COUNT]; + /* physical addresses of ao dma buffers */ + dma_addr_t ao_buffer_bus_addr[AO_DMA_RING_COUNT]; + struct plx_dma_desc *ao_dma_desc; + dma_addr_t ao_dma_desc_bus_addr; + /* keeps track of buffer where the next ao sample should go */ + unsigned int ao_dma_index; + unsigned int hw_revision; /* stc chip hardware revision number */ + /* last bits sent to INTR_ENABLE_REG register */ + unsigned int intr_enable_bits; + /* last bits sent to ADC_CONTROL1_REG register */ + u16 adc_control1_bits; + /* last bits sent to FIFO_SIZE_REG register */ + u16 fifo_size_bits; + /* last bits sent to HW_CONFIG_REG register */ + u16 hw_config_bits; + u16 dac_control1_bits; + /* last bits written to plx9080 control register */ + u32 plx_control_bits; + /* last bits written to plx interrupt control and status register */ + u32 plx_intcsr_bits; + /* index of calibration source readable through ai ch0 */ + int calibration_source; + /* bits written to i2c calibration/range register */ + u8 i2c_cal_range_bits; + /* configure digital triggers to trigger on falling edge */ + unsigned int ext_trig_falling; + short ai_cmd_running; + unsigned int ai_fifo_segment_length; + struct ext_clock_info ext_clock; + unsigned short ao_bounce_buffer[DAC_FIFO_SIZE]; +}; + +static unsigned int ai_range_bits_6xxx(const struct comedi_device *dev, + unsigned int range_index) +{ + const struct pcidas64_board *board = dev->board_ptr; + + return board->ai_range_code[range_index] << 8; +} + +static unsigned int hw_revision(const struct comedi_device *dev, + u16 hw_status_bits) +{ + const struct pcidas64_board *board = dev->board_ptr; + + if (board->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, + u16 *bits, unsigned int channel, + unsigned int range) +{ + const struct pcidas64_board *board = dev->board_ptr; + unsigned int code = board->ao_range_code[range]; + + if (channel > 1) + dev_err(dev->class_dev, "bug! bad channel?\n"); + if (code & ~0x3) + dev_err(dev->class_dev, "bug! bad range code?\n"); + + *bits &= ~(0x3 << (2 * channel)); + *bits |= code << (2 * channel); +}; + +static inline int ao_cmd_is_supported(const struct pcidas64_board *board) +{ + return board->ao_nchan && board->layout != LAYOUT_4020; +} + +static void abort_dma(struct comedi_device *dev, unsigned int channel) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned long flags; + + /* spinlock for plx dma control/status reg */ + spin_lock_irqsave(&dev->spinlock, flags); + + plx9080_abort_dma(devpriv->plx9080_iobase, channel); + + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static void disable_plx_interrupts(struct comedi_device *dev) +{ + struct pcidas64_private *devpriv = dev->private; + + devpriv->plx_intcsr_bits = 0; + writel(devpriv->plx_intcsr_bits, + devpriv->plx9080_iobase + PLX_REG_INTCSR); +} + +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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + u32 bits; + unsigned long flags; + + bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT | + EN_ADC_ACTIVE_INTR_BIT | EN_ADC_STOP_INTR_BIT; + /* + * Use pio transfer and interrupt on end of conversion + * if CMDF_WAKE_EOS flag is set. + */ + if (cmd->flags & CMDF_WAKE_EOS) { + /* 4020 doesn't support pio transfers except for fifo dregs */ + if (board->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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + u32 bits; + void __iomem *plx_iobase = devpriv->plx9080_iobase; + + devpriv->plx_control_bits = + readl(devpriv->plx9080_iobase + PLX_REG_CNTRL); + +#ifdef __BIG_ENDIAN + bits = PLX_BIGEND_DMA0 | PLX_BIGEND_DMA1; +#else + bits = 0; +#endif + writel(bits, devpriv->plx9080_iobase + PLX_REG_BIGEND); + + 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_DMAMODE_READYIEN; + /* enable bterm, not sure if this is necessary */ + bits |= PLX_DMAMODE_BTERMIEN; + /* enable dma chaining */ + bits |= PLX_DMAMODE_CHAINEN; + /* + * enable interrupt on dma done + * (probably don't need this, since chain never finishes) + */ + bits |= PLX_DMAMODE_DONEIEN; + /* + * don't increment local address during transfers + * (we are transferring from a fixed fifo register) + */ + bits |= PLX_DMAMODE_LACONST; + /* route dma interrupt to pci bus */ + bits |= PLX_DMAMODE_INTRPCI; + /* enable demand mode */ + bits |= PLX_DMAMODE_DEMAND; + /* enable local burst mode */ + bits |= PLX_DMAMODE_BURSTEN; + /* 4020 uses 32 bit dma */ + if (board->layout == LAYOUT_4020) + bits |= PLX_DMAMODE_WIDTH_32; + else /* localspace0 bus is 16 bits wide */ + bits |= PLX_DMAMODE_WIDTH_16; + writel(bits, plx_iobase + PLX_REG_DMAMODE1); + if (ao_cmd_is_supported(board)) + writel(bits, plx_iobase + PLX_REG_DMAMODE0); + + /* enable interrupts on plx 9080 */ + devpriv->plx_intcsr_bits |= + PLX_INTCSR_LSEABORTEN | PLX_INTCSR_LSEPARITYEN | PLX_INTCSR_PIEN | + PLX_INTCSR_PLIEN | PLX_INTCSR_PABORTIEN | PLX_INTCSR_LIOEN | + PLX_INTCSR_DMA0IEN | PLX_INTCSR_DMA1IEN; + writel(devpriv->plx_intcsr_bits, + devpriv->plx9080_iobase + PLX_REG_INTCSR); +} + +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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + static const int increment_size = 0x100; + const struct hw_fifo_info *const fifo = board->ai_fifo; + unsigned int num_increments; + u16 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 = DIV_ROUND_CLOSEST(num_entries, 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 *board = dev->board_ptr; + unsigned int num_fifo_entries; + int retval; + const struct hw_fifo_info *const fifo = board->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; + + return retval * fifo->num_segments * fifo->sample_packing_ratio; +} + +/* query length of fifo */ +static unsigned int ai_fifo_size(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + + return devpriv->ai_fifo_segment_length * + board->ai_fifo->num_segments * + board->ai_fifo->sample_packing_ratio; +} + +static void init_stc_registers(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + u16 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 (board->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, board->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 *board = dev->board_ptr; + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pcidas64_private *devpriv = dev->private; + int i; + + /* allocate pci dma buffers */ + for (i = 0; i < ai_dma_ring_count(board); i++) { + devpriv->ai_buffer[i] = + dma_alloc_coherent(&pcidev->dev, DMA_BUFFER_SIZE, + &devpriv->ai_buffer_bus_addr[i], + GFP_KERNEL); + if (!devpriv->ai_buffer[i]) + return -ENOMEM; + } + for (i = 0; i < AO_DMA_RING_COUNT; i++) { + if (ao_cmd_is_supported(board)) { + devpriv->ao_buffer[i] = + dma_alloc_coherent(&pcidev->dev, + DMA_BUFFER_SIZE, + &devpriv->ao_buffer_bus_addr[i], + GFP_KERNEL); + if (!devpriv->ao_buffer[i]) + return -ENOMEM; + } + } + /* allocate dma descriptors */ + devpriv->ai_dma_desc = + dma_alloc_coherent(&pcidev->dev, sizeof(struct plx_dma_desc) * + ai_dma_ring_count(board), + &devpriv->ai_dma_desc_bus_addr, GFP_KERNEL); + if (!devpriv->ai_dma_desc) + return -ENOMEM; + + if (ao_cmd_is_supported(board)) { + devpriv->ao_dma_desc = + dma_alloc_coherent(&pcidev->dev, + sizeof(struct plx_dma_desc) * + AO_DMA_RING_COUNT, + &devpriv->ao_dma_desc_bus_addr, + GFP_KERNEL); + if (!devpriv->ao_dma_desc) + return -ENOMEM; + } + /* initialize dma descriptors */ + for (i = 0; i < ai_dma_ring_count(board); i++) { + devpriv->ai_dma_desc[i].pci_start_addr = + cpu_to_le32(devpriv->ai_buffer_bus_addr[i]); + if (board->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(board)) * + sizeof(devpriv->ai_dma_desc[0])) | + PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR | + PLX_DMADPR_XFERL2P); + } + if (ao_cmd_is_supported(board)) { + 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_DMADPR_DESCPCI | + PLX_DMADPR_TCINTR); + } + } + return 0; +} + +static void cb_pcidas64_free_dma(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct pcidas64_private *devpriv = dev->private; + int i; + + if (!devpriv) + return; + + /* free pci dma buffers */ + for (i = 0; i < ai_dma_ring_count(board); i++) { + if (devpriv->ai_buffer[i]) + dma_free_coherent(&pcidev->dev, + 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]) + dma_free_coherent(&pcidev->dev, + DMA_BUFFER_SIZE, + devpriv->ao_buffer[i], + devpriv->ao_buffer_bus_addr[i]); + } + /* free dma descriptors */ + if (devpriv->ai_dma_desc) + dma_free_coherent(&pcidev->dev, + sizeof(struct plx_dma_desc) * + ai_dma_ring_count(board), + devpriv->ai_dma_desc, + devpriv->ai_dma_desc_bus_addr); + if (devpriv->ao_dma_desc) + dma_free_coherent(&pcidev->dev, + sizeof(struct plx_dma_desc) * + AO_DMA_RING_COUNT, + devpriv->ao_dma_desc, + devpriv->ao_dma_desc_bus_addr); +} + +static inline void warn_external_queue(struct comedi_device *dev) +{ + dev_err(dev->class_dev, + "AO command and AI external channel queue cannot be used simultaneously\n"); + dev_err(dev->class_dev, + "Use internal AI channel queue (channels must be consecutive and use same range/aref)\n"); +} + +/* + * their i2c requires a huge delay on setting clock or data high for some reason + */ +static const int i2c_high_udelay = 1000; +static const int i2c_low_udelay = 10; + +/* set i2c data line high or low */ +static void i2c_set_sda(struct comedi_device *dev, int state) +{ + struct pcidas64_private *devpriv = dev->private; + static const int data_bit = PLX_CNTRL_EEWB; + void __iomem *plx_control_addr = devpriv->plx9080_iobase + + PLX_REG_CNTRL; + + 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 = PLX_CNTRL_USERO; + void __iomem *plx_control_addr = devpriv->plx9080_iobase + + PLX_REG_CNTRL; + + 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, u8 byte) +{ + u8 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 u8 *data, unsigned int length) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int i; + u8 bitstream; + static const int read_bit = 0x1; + + /* + * XXX need mutex to prevent simultaneous attempts to access + * eeprom and i2c bus + */ + + /* make sure we don't send anything to eeprom */ + devpriv->plx_control_bits &= ~PLX_CNTRL_EECS; + + i2c_stop(dev); + i2c_start(dev); + + /* send address and write bit */ + bitstream = (address << 1) & ~read_bit; + i2c_write_byte(dev, bitstream); + + /* get acknowledge */ + if (i2c_read_ack(dev) != 0) { + dev_err(dev->class_dev, "failed: no acknowledge\n"); + i2c_stop(dev); + return; + } + /* write data bytes */ + for (i = 0; i < length; i++) { + i2c_write_byte(dev, data[i]); + if (i2c_read_ack(dev) != 0) { + dev_err(dev->class_dev, "failed: no acknowledge\n"); + i2c_stop(dev); + return; + } + } + i2c_stop(dev); +} + +static int cb_pcidas64_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + unsigned int status; + + status = readw(devpriv->main_iobase + HW_STATUS_REG); + if (board->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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + unsigned int bits = 0, n; + unsigned int channel, range, aref; + unsigned long flags; + int ret; + + channel = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + aref = CR_AREF(insn->chanspec); + + /* disable card's analog input interrupt sources and pacing */ + /* 4020 generates dac done interrupts even though they are disabled */ + disable_ai_pacing(dev); + + spin_lock_irqsave(&dev->spinlock, flags); + if (insn->chanspec & CR_ALT_FILTER) + devpriv->adc_control1_bits |= ADC_DITHER_BIT; + else + devpriv->adc_control1_bits &= ~ADC_DITHER_BIT; + writew(devpriv->adc_control1_bits, + devpriv->main_iobase + ADC_CONTROL1_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + if (board->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 (board->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 { + u8 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) { + u8 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 (board->layout == LAYOUT_4020) + data[n] = readl(dev->mmio + ADC_FIFO_REG) & 0xffff; + else + data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG); + } + + return n; +} + +static int ai_config_calibration_source(struct comedi_device *dev, + unsigned int *data) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + unsigned int source = data[1]; + int num_calibration_sources; + + if (board->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 *board = dev->board_ptr; + int fifo_size; + const struct hw_fifo_info *const fifo = board->ai_fifo; + unsigned int block_size, requested_block_size; + int retval; + + requested_block_size = data[1]; + + if (requested_block_size) { + fifo_size = requested_block_size * fifo->num_segments / + bytes_in_sample; + + retval = set_ai_fifo_size(dev, fifo_size); + if (retval < 0) + return retval; + } + + block_size = ai_fifo_size(dev) / fifo->num_segments * bytes_in_sample; + + data[1] = block_size; + + return 2; +} + +static int ai_config_master_clock_4020(struct comedi_device *dev, + unsigned int *data) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int divisor = data[4]; + int retval = 0; + + if (divisor < 2) { + divisor = 2; + retval = -EAGAIN; + } + + switch (data[1]) { + case COMEDI_EV_SCAN_BEGIN: + devpriv->ext_clock.divisor = divisor; + devpriv->ext_clock.chanspec = data[2]; + break; + default: + return -EINVAL; + } + + data[4] = divisor; + + return retval ? retval : 5; +} + +/* XXX could add support for 60xx series */ +static int ai_config_master_clock(struct comedi_device *dev, unsigned int *data) +{ + const struct pcidas64_board *board = dev->board_ptr; + + switch (board->layout) { + case LAYOUT_4020: + return ai_config_master_clock_4020(dev, data); + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int id = data[0]; + + switch (id) { + case INSN_CONFIG_ALT_SOURCE: + return ai_config_calibration_source(dev, data); + case INSN_CONFIG_BLOCK_SIZE: + return ai_config_block_size(dev, data); + case INSN_CONFIG_TIMER_1: + return ai_config_master_clock(dev, data); + default: + return -EINVAL; + } + return -EINVAL; +} + +/* + * Gets nearest achievable timing given master clock speed, does not + * take into account possible minimum/maximum divisor values. Used + * by other timing checking functions. + */ +static unsigned int get_divisor(unsigned int ns, unsigned int flags) +{ + unsigned int divisor; + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_UP: + divisor = DIV_ROUND_UP(ns, TIMER_BASE); + break; + case CMDF_ROUND_DOWN: + divisor = ns / TIMER_BASE; + break; + case CMDF_ROUND_NEAREST: + default: + divisor = DIV_ROUND_CLOSEST(ns, TIMER_BASE); + break; + } + 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 *board = dev->board_ptr; + unsigned long long convert_divisor = 0; + unsigned int scan_divisor; + static const int min_convert_divisor = 3; + static const int max_convert_divisor = + max_counter_value + min_convert_divisor; + static const int min_scan_divisor_4020 = 2; + unsigned long long max_scan_divisor, min_scan_divisor; + + if (cmd->convert_src == TRIG_TIMER) { + if (board->layout == LAYOUT_4020) { + cmd->convert_arg = 0; + } else { + convert_divisor = get_divisor(cmd->convert_arg, + cmd->flags); + if (convert_divisor > max_convert_divisor) + convert_divisor = max_convert_divisor; + if (convert_divisor < min_convert_divisor) + convert_divisor = min_convert_divisor; + cmd->convert_arg = convert_divisor * TIMER_BASE; + } + } else if (cmd->convert_src == TRIG_NOW) { + cmd->convert_arg = 0; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags); + if (cmd->convert_src == TRIG_TIMER) { + min_scan_divisor = convert_divisor * cmd->chanlist_len; + max_scan_divisor = + (convert_divisor * cmd->chanlist_len - 1) + + max_counter_value; + } else { + min_scan_divisor = min_scan_divisor_4020; + max_scan_divisor = max_counter_value + min_scan_divisor; + } + if (scan_divisor > max_scan_divisor) + scan_divisor = max_scan_divisor; + if (scan_divisor < min_scan_divisor) + scan_divisor = min_scan_divisor; + cmd->scan_begin_arg = scan_divisor * TIMER_BASE; + } +} + +static int cb_pcidas64_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct pcidas64_board *board = dev->board_ptr; + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (aref != aref0) { + dev_dbg(dev->class_dev, + "all elements in chanlist must use the same analog reference\n"); + return -EINVAL; + } + } + + if (board->layout == LAYOUT_4020) { + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (chan != (chan0 + i)) { + dev_dbg(dev->class_dev, + "chanlist must use consecutive channels\n"); + return -EINVAL; + } + } + if (cmd->chanlist_len == 3) { + dev_dbg(dev->class_dev, + "chanlist cannot be 3 channels long, use 1, 2, or 4 channels\n"); + return -EINVAL; + } + } + + return 0; +} + +static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct pcidas64_board *board = dev->board_ptr; + int err = 0; + unsigned int tmp_arg, tmp_arg2; + unsigned int triggers; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + + triggers = TRIG_TIMER; + if (board->layout == LAYOUT_4020) + triggers |= TRIG_OTHER; + else + triggers |= TRIG_FOLLOW; + err |= comedi_check_trigger_src(&cmd->scan_begin_src, triggers); + + triggers = TRIG_TIMER; + if (board->layout == LAYOUT_4020) + triggers |= TRIG_NOW; + else + triggers |= TRIG_EXT; + err |= comedi_check_trigger_src(&cmd->convert_src, triggers); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, + TRIG_COUNT | TRIG_EXT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* + * start_arg is the CR_CHAN | CR_INVERT of the + * external trigger. + */ + break; + } + + if (cmd->convert_src == TRIG_TIMER) { + if (board->layout == LAYOUT_4020) { + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, + 0); + } else { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + /* + * if scans are timed faster than conversion rate + * allows + */ + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, + cmd->convert_arg * + cmd->chanlist_len); + } + } + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_EXT: + break; + case TRIG_COUNT: + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp_arg = cmd->convert_arg; + tmp_arg2 = cmd->scan_begin_arg; + check_adc_timing(dev, cmd); + if (tmp_arg != cmd->convert_arg) + err++; + if (tmp_arg2 != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= cb_pcidas64_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int use_hw_sample_counter(struct comedi_cmd *cmd) +{ +/* disable for now until I work out a race */ + return 0; + + if (cmd->stop_src == TRIG_COUNT && cmd->stop_arg <= max_counter_value) + return 1; + + return 0; +} + +static void setup_sample_counters(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + + /* load hardware conversion counter */ + if (use_hw_sample_counter(cmd)) { + writew(cmd->stop_arg & 0xffff, + devpriv->main_iobase + ADC_COUNT_LOWER_REG); + writew((cmd->stop_arg >> 16) & 0xff, + devpriv->main_iobase + ADC_COUNT_UPPER_REG); + } else { + writew(1, devpriv->main_iobase + ADC_COUNT_LOWER_REG); + } +} + +static inline unsigned int dma_transfer_size(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + unsigned int num_samples; + + num_samples = devpriv->ai_fifo_segment_length * + board->ai_fifo->sample_packing_ratio; + if (num_samples > DMA_BUFFER_SIZE / sizeof(u16)) + num_samples = DMA_BUFFER_SIZE / sizeof(u16); + + return num_samples; +} + +static u32 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 u32 ai_scan_counter_6xxx(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + u32 count; + + /* figure out how long we need to delay at end of scan */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + count = (cmd->scan_begin_arg - + (cmd->convert_arg * (cmd->chanlist_len - 1))) / + TIMER_BASE; + break; + case TRIG_FOLLOW: + count = cmd->convert_arg / TIMER_BASE; + break; + default: + return 0; + } + return count - 3; +} + +static u32 ai_convert_counter_4020(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int divisor; + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + divisor = cmd->scan_begin_arg / TIMER_BASE; + break; + case TRIG_OTHER: + divisor = devpriv->ext_clock.divisor; + break; + default: /* should never happen */ + dev_err(dev->class_dev, "bug! failed to set ai pacing!\n"); + divisor = 1000; + break; + } + + /* supposed to load counter with desired divisor minus 2 for 4020 */ + return divisor - 2; +} + +static void select_master_clock_4020(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + + /* select internal/external master clock */ + devpriv->hw_config_bits &= ~MASTER_CLOCK_4020_MASK; + if (cmd->scan_begin_src == TRIG_OTHER) { + int chanspec = devpriv->ext_clock.chanspec; + + if (CR_CHAN(chanspec)) + devpriv->hw_config_bits |= BNC_CLOCK_4020_BITS; + else + devpriv->hw_config_bits |= EXT_CLOCK_4020_BITS; + } else { + devpriv->hw_config_bits |= INTERNAL_CLOCK_4020_BITS; + } + writew(devpriv->hw_config_bits, + devpriv->main_iobase + HW_CONFIG_REG); +} + +static void select_master_clock(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + const struct pcidas64_board *board = dev->board_ptr; + + switch (board->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); + writeb(PLX_DMACSR_ENABLE | PLX_DMACSR_START | PLX_DMACSR_CLEARINTR, + devpriv->plx9080_iobase + PLX_REG_DMACSR(channel)); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static void set_ai_pacing(struct comedi_device *dev, struct comedi_cmd *cmd) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + u32 convert_counter = 0, scan_counter = 0; + + check_adc_timing(dev, cmd); + + select_master_clock(dev, cmd); + + if (board->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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + unsigned short bits; + int i; + + if (board->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++) { + unsigned int chanspec = cmd->chanlist[i]; + int use_differential; + + bits = 0; + /* set channel */ + bits |= adc_chan_bits(CR_CHAN(chanspec)); + /* set gain */ + bits |= ai_range_bits_6xxx(dev, + CR_RANGE(chanspec)); + /* set single-ended / differential */ + use_differential = 0; + if (CR_AREF(chanspec) == AREF_DIFF) + use_differential = 1; + bits |= se_diff_bit_6xxx(dev, use_differential); + + 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) { + u8 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_REG_DMASIZ1); + writel(0, devpriv->plx9080_iobase + PLX_REG_DMAPADR1); + writel(0, devpriv->plx9080_iobase + PLX_REG_DMALADR1); + writel(descriptor_bits, + devpriv->plx9080_iobase + PLX_REG_DMADPR1); + } else { + writel(0, devpriv->plx9080_iobase + PLX_REG_DMASIZ0); + writel(0, devpriv->plx9080_iobase + PLX_REG_DMAPADR0); + writel(0, devpriv->plx9080_iobase + PLX_REG_DMALADR0); + writel(descriptor_bits, + devpriv->plx9080_iobase + PLX_REG_DMADPR0); + } +} + +static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u32 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 (board->layout != LAYOUT_4020) { + devpriv->adc_control1_bits &= ~ADC_MODE_MASK; + if (cmd->convert_src == TRIG_EXT) + /* good old mode 13 */ + devpriv->adc_control1_bits |= adc_mode_bits(13); + else + /* mode 8. What else could you need? */ + devpriv->adc_control1_bits |= adc_mode_bits(8); + } else { + devpriv->adc_control1_bits &= ~CHANNEL_MODE_4020_MASK; + if (cmd->chanlist_len == 4) + devpriv->adc_control1_bits |= FOUR_CHANNEL_4020_BITS; + else if (cmd->chanlist_len == 2) + devpriv->adc_control1_bits |= TWO_CHANNEL_4020_BITS; + devpriv->adc_control1_bits &= ~ADC_LO_CHANNEL_4020_MASK; + devpriv->adc_control1_bits |= + adc_lo_chan_4020_bits(CR_CHAN(cmd->chanlist[0])); + devpriv->adc_control1_bits &= ~ADC_HI_CHANNEL_4020_MASK; + devpriv->adc_control1_bits |= + adc_hi_chan_4020_bits(CR_CHAN(cmd->chanlist + [cmd->chanlist_len - 1])); + } + writew(devpriv->adc_control1_bits, + devpriv->main_iobase + ADC_CONTROL1_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* clear adc buffer */ + writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG); + + if ((cmd->flags & CMDF_WAKE_EOS) == 0 || + board->layout == LAYOUT_4020) { + devpriv->ai_dma_index = 0; + + /* set dma transfer size */ + for (i = 0; i < ai_dma_ring_count(board); i++) + devpriv->ai_dma_desc[i].transfer_size = + cpu_to_le32(dma_transfer_size(dev) * + sizeof(u16)); + + /* give location of first dma descriptor */ + load_first_dma_descriptor(dev, 1, + devpriv->ai_dma_desc_bus_addr | + PLX_DMADPR_DESCPCI | + PLX_DMADPR_TCINTR | + PLX_DMADPR_XFERL2P); + + dma_start_sync(dev, 1); + } + + if (board->layout == LAYOUT_4020) { + /* set source for external triggers */ + bits = 0; + if (cmd->start_src == TRIG_EXT && CR_CHAN(cmd->start_arg)) + bits |= EXT_START_TRIG_BNC_BIT; + if (cmd->stop_src == TRIG_EXT && CR_CHAN(cmd->stop_arg)) + bits |= EXT_STOP_TRIG_BNC_BIT; + writew(bits, devpriv->main_iobase + DAQ_ATRIG_LOW_4020_REG); + } + + spin_lock_irqsave(&dev->spinlock, flags); + + /* enable pacing, triggering, etc */ + bits = ADC_ENABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT; + if (cmd->flags & CMDF_WAKE_EOS) + bits |= ADC_DMA_DISABLE_BIT; + /* set start trigger */ + if (cmd->start_src == TRIG_EXT) { + bits |= ADC_START_TRIG_EXT_BITS; + if (cmd->start_arg & CR_INVERT) + bits |= ADC_START_TRIG_FALLING_BIT; + } else if (cmd->start_src == TRIG_NOW) { + bits |= ADC_START_TRIG_SOFT_BITS; + } + if (use_hw_sample_counter(cmd)) + bits |= ADC_SAMPLE_COUNTER_EN_BIT; + writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG); + + devpriv->ai_cmd_running = 1; + + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* start acquisition */ + if (cmd->start_src == TRIG_NOW) + writew(0, devpriv->main_iobase + ADC_START_REG); + + return 0; +} + +/* read num_samples from 16 bit wide ai fifo */ +static void pio_drain_ai_fifo_16(struct comedi_device *dev) +{ + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int i; + u16 prepost_bits; + int read_segment, read_index, write_segment, write_index; + int num_samples; + + do { + /* get least significant 15 bits */ + read_index = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & + 0x7fff; + write_index = readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG) & + 0x7fff; + /* + * Get most significant bits (grey code). + * Different boards use different code so use a scheme + * that doesn't depend on encoding. This read must + * occur after reading least significant 15 bits to avoid race + * with fifo switching to next segment. + */ + prepost_bits = readw(devpriv->main_iobase + PREPOST_REG); + + /* + * if read and write pointers are not on the same fifo segment, + * read to the end of the read segment + */ + read_segment = adc_upper_read_ptr_code(prepost_bits); + write_segment = adc_upper_write_ptr_code(prepost_bits); + + if (read_segment != write_segment) + num_samples = + devpriv->ai_fifo_segment_length - read_index; + else + num_samples = write_index - read_index; + if (num_samples < 0) { + dev_err(dev->class_dev, + "cb_pcidas64: bug! num_samples < 0\n"); + break; + } + + num_samples = comedi_nsamples_left(s, num_samples); + if (num_samples == 0) + break; + + for (i = 0; i < num_samples; i++) { + unsigned short val; + + val = readw(devpriv->main_iobase + ADC_FIFO_REG); + comedi_buf_write_samples(s, &val, 1); + } + + } while (read_segment != write_segment); +} + +/* + * Read from 32 bit wide ai fifo of 4020 - deal with insane grey coding of + * pointers. The pci-4020 hardware only supports dma transfers (it only + * supports the use of pio for draining the last remaining points from the + * fifo when a data acquisition operation has completed). + */ +static void pio_drain_ai_fifo_32(struct comedi_device *dev) +{ + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int nsamples; + unsigned int i; + u32 fifo_data; + int write_code = + readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff; + int read_code = + readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & 0x7fff; + + nsamples = comedi_nsamples_left(s, 100000); + for (i = 0; read_code != write_code && i < nsamples;) { + unsigned short val; + + fifo_data = readl(dev->mmio + ADC_FIFO_REG); + val = fifo_data & 0xffff; + comedi_buf_write_samples(s, &val, 1); + i++; + if (i < nsamples) { + val = (fifo_data >> 16) & 0xffff; + comedi_buf_write_samples(s, &val, 1); + i++; + } + read_code = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & + 0x7fff; + } +} + +/* empty fifo */ +static void pio_drain_ai_fifo(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + + if (board->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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + u32 next_transfer_addr; + int j; + int num_samples = 0; + void __iomem *pci_addr_reg; + + pci_addr_reg = devpriv->plx9080_iobase + PLX_REG_DMAPADR(channel); + + /* 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(board); j++) { + /* transfer data from dma buffer to comedi buffer */ + num_samples = comedi_nsamples_left(s, dma_transfer_size(dev)); + comedi_buf_write_samples(s, + devpriv->ai_buffer[devpriv->ai_dma_index], + num_samples); + devpriv->ai_dma_index = (devpriv->ai_dma_index + 1) % + ai_dma_ring_count(board); + } + /* + * 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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u8 dma1_status; + unsigned long flags; + + /* check for fifo overrun */ + if (status & ADC_OVERRUN_BIT) { + dev_err(dev->class_dev, "fifo overrun\n"); + async->events |= COMEDI_CB_ERROR; + } + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma1_status = readb(devpriv->plx9080_iobase + PLX_REG_DMACSR1); + if (plx_status & PLX_INTCSR_DMA1IA) { /* dma chan 1 interrupt */ + writeb((dma1_status & PLX_DMACSR_ENABLE) | PLX_DMACSR_CLEARINTR, + devpriv->plx9080_iobase + PLX_REG_DMACSR1); + + if (dma1_status & PLX_DMACSR_ENABLE) + drain_dma_buffers(dev, 1); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* drain fifo with pio */ + if ((status & ADC_DONE_BIT) || + ((cmd->flags & CMDF_WAKE_EOS) && + (status & ADC_INTR_PENDING_BIT) && + (board->layout != LAYOUT_4020))) { + spin_lock_irqsave(&dev->spinlock, flags); + if (devpriv->ai_cmd_running) { + spin_unlock_irqrestore(&dev->spinlock, flags); + pio_drain_ai_fifo(dev); + } else { + spin_unlock_irqrestore(&dev->spinlock, flags); + } + } + /* if we are have all the data, then quit */ + if ((cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) || + (cmd->stop_src == TRIG_EXT && (status & ADC_STOP_BIT))) + async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); +} + +static inline unsigned int prev_ao_dma_index(struct comedi_device *dev) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int buffer_index; + + if (devpriv->ao_dma_index == 0) + buffer_index = AO_DMA_RING_COUNT - 1; + else + buffer_index = devpriv->ao_dma_index - 1; + return buffer_index; +} + +static int last_ao_dma_load_completed(struct comedi_device *dev) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int buffer_index; + unsigned int transfer_address; + unsigned short dma_status; + + buffer_index = prev_ao_dma_index(dev); + dma_status = readb(devpriv->plx9080_iobase + PLX_REG_DMACSR0); + if ((dma_status & PLX_DMACSR_DONE) == 0) + return 0; + + transfer_address = + readl(devpriv->plx9080_iobase + PLX_REG_DMAPADR0); + if (transfer_address != devpriv->ao_buffer_bus_addr[buffer_index]) + return 0; + + return 1; +} + +static inline int ao_dma_needs_restart(struct comedi_device *dev, + unsigned short dma_status) +{ + if ((dma_status & PLX_DMACSR_DONE) == 0 || + (dma_status & PLX_DMACSR_ENABLE) == 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_REG_DMADPR0); + dma_desc_bits &= ~PLX_DMADPR_CHAINEND; + load_first_dma_descriptor(dev, 0, dma_desc_bits); + + dma_start_sync(dev, 0); +} + +static unsigned int cb_pcidas64_ao_fill_buffer(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *dest, + unsigned int max_bytes) +{ + unsigned int nsamples = comedi_bytes_to_samples(s, max_bytes); + unsigned int actual_bytes; + + nsamples = comedi_nsamples_left(s, nsamples); + actual_bytes = comedi_buf_read_samples(s, dest, nsamples); + + return comedi_bytes_to_samples(s, actual_bytes); +} + +static unsigned int load_ao_dma_buffer(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + unsigned int buffer_index = devpriv->ao_dma_index; + unsigned int prev_buffer_index = prev_ao_dma_index(dev); + unsigned int nsamples; + unsigned int nbytes; + unsigned int next_bits; + + nsamples = cb_pcidas64_ao_fill_buffer(dev, s, + devpriv->ao_buffer[buffer_index], + DMA_BUFFER_SIZE); + if (nsamples == 0) + return 0; + + nbytes = comedi_samples_to_bytes(s, nsamples); + devpriv->ao_dma_desc[buffer_index].transfer_size = cpu_to_le32(nbytes); + /* set end of chain bit so we catch underruns */ + next_bits = le32_to_cpu(devpriv->ao_dma_desc[buffer_index].next); + next_bits |= PLX_DMADPR_CHAINEND; + 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_DMADPR_CHAINEND; + devpriv->ao_dma_desc[prev_buffer_index].next = cpu_to_le32(next_bits); + + devpriv->ao_dma_index = (buffer_index + 1) % AO_DMA_RING_COUNT; + + return nbytes; +} + +static void load_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int num_bytes; + unsigned int next_transfer_addr; + void __iomem *pci_addr_reg = devpriv->plx9080_iobase + PLX_REG_DMAPADR0; + 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; + u8 dma0_status; + unsigned long flags; + + /* board might not support ao, in which case write_subdev is NULL */ + if (!s) + return; + async = s->async; + cmd = &async->cmd; + + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma0_status = readb(devpriv->plx9080_iobase + PLX_REG_DMACSR0); + if (plx_status & PLX_INTCSR_DMA0IA) { /* dma chan 0 interrupt */ + if ((dma0_status & PLX_DMACSR_ENABLE) && + !(dma0_status & PLX_DMACSR_DONE)) { + writeb(PLX_DMACSR_ENABLE | PLX_DMACSR_CLEARINTR, + devpriv->plx9080_iobase + PLX_REG_DMACSR0); + } else { + writeb(PLX_DMACSR_CLEARINTR, + devpriv->plx9080_iobase + PLX_REG_DMACSR0); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + if (dma0_status & PLX_DMACSR_ENABLE) { + load_ao_dma(dev, cmd); + /* try to recover from dma end-of-chain event */ + if (ao_dma_needs_restart(dev, dma0_status)) + restart_ao_dma(dev); + } + } else { + spin_unlock_irqrestore(&dev->spinlock, flags); + } + + if ((status & DAC_DONE_BIT)) { + if ((cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) || + last_ao_dma_load_completed(dev)) + async->events |= COMEDI_CB_EOA; + else + async->events |= COMEDI_CB_ERROR; + } + comedi_handle_events(dev, s); +} + +static irqreturn_t handle_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pcidas64_private *devpriv = dev->private; + unsigned short status; + u32 plx_status; + u32 plx_bits; + + plx_status = readl(devpriv->plx9080_iobase + PLX_REG_INTCSR); + 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 & PLX_INTCSR_LDBIA) { + /* clear local doorbell interrupt */ + plx_bits = readl(devpriv->plx9080_iobase + PLX_REG_L2PDBELL); + writel(plx_bits, devpriv->plx9080_iobase + PLX_REG_L2PDBELL); + } + + 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 *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val = s->readback[chan]; + unsigned int i; + + /* 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); + + for (i = 0; i < insn->n; i++) { + /* write to channel */ + val = data[i]; + if (board->layout == LAYOUT_4020) { + writew(val & 0xff, + devpriv->main_iobase + dac_lsb_4020_reg(chan)); + writew((val >> 8) & 0xf, + devpriv->main_iobase + dac_msb_4020_reg(chan)); + } else { + writew(val, + devpriv->main_iobase + dac_convert_reg(chan)); + } + } + + /* remember last output value */ + s->readback[chan] = val; + + return insn->n; +} + +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; + u16 bits; + unsigned int first_channel, last_channel; + + first_channel = CR_CHAN(cmd->chanlist[0]); + last_channel = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + if (last_channel < first_channel) + dev_err(dev->class_dev, + "bug! last ao channel < first ao channel\n"); + + bits = (first_channel & 0x7) | (last_channel & 0x7) << 3; + + writew(bits, devpriv->main_iobase + DAC_SELECT_REG); +} + +static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags) +{ + return get_divisor(ns, flags) - 2; +} + +static void set_dac_interval_regs(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + unsigned int divisor; + + if (cmd->scan_begin_src != TRIG_TIMER) + return; + + divisor = get_ao_divisor(cmd->scan_begin_arg, cmd->flags); + if (divisor > max_counter_value) { + dev_err(dev->class_dev, "bug! ao divisor too big\n"); + divisor = max_counter_value; + } + writew(divisor & 0xffff, + devpriv->main_iobase + DAC_SAMPLE_INTERVAL_LOWER_REG); + writew((divisor >> 16) & 0xff, + devpriv->main_iobase + DAC_SAMPLE_INTERVAL_UPPER_REG); +} + +static int prep_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd) +{ + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + unsigned int nsamples; + unsigned int nbytes; + int i; + + /* + * clear queue pointer too, since external queue has + * weird interactions with ao fifo + */ + writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG); + writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG); + + nsamples = cb_pcidas64_ao_fill_buffer(dev, s, + devpriv->ao_bounce_buffer, + DAC_FIFO_SIZE); + if (nsamples == 0) + return -1; + + for (i = 0; i < nsamples; i++) { + writew(devpriv->ao_bounce_buffer[i], + devpriv->main_iobase + DAC_FIFO_REG); + } + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + return 0; + + nbytes = load_ao_dma_buffer(dev, cmd); + if (nbytes == 0) + return -1; + load_ao_dma(dev, cmd); + + dma_start_sync(dev, 0); + + return 0; +} + +static inline int external_ai_queue_in_use(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + + if (!dev->read_subdev->busy) + return 0; + if (board->layout == LAYOUT_4020) + return 0; + else if (use_internal_queue_6xxx(&dev->read_subdev->async->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)) { + warn_external_queue(dev); + return -EBUSY; + } + /* disable analog output system during setup */ + writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG); + + devpriv->ao_dma_index = 0; + + set_dac_select_reg(dev, cmd); + set_dac_interval_regs(dev, cmd); + load_first_dma_descriptor(dev, 0, devpriv->ao_dma_desc_bus_addr | + PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR); + + 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 *board = dev->board_ptr; + int err = 0; + unsigned int tmp_arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER) + err |= -EINVAL; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ao_scan_speed); + if (get_ao_divisor(cmd->scan_begin_arg, cmd->flags) > + max_counter_value) { + cmd->scan_begin_arg = (max_counter_value + 2) * + TIMER_BASE; + err |= -EINVAL; + } + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp_arg = cmd->scan_begin_arg; + cmd->scan_begin_arg = get_divisor(cmd->scan_begin_arg, + cmd->flags) * TIMER_BASE; + if (tmp_arg != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= cb_pcidas64_ao_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcidas64_private *devpriv = dev->private; + + writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG); + abort_dma(dev, 0); + return 0; +} + +static int dio_callback_4020(struct comedi_device *dev, + int dir, int port, int data, unsigned long iobase) +{ + struct pcidas64_private *devpriv = dev->private; + + if (dir) { + writew(data, devpriv->main_iobase + iobase + 2 * port); + return 0; + } + return readw(devpriv->main_iobase + iobase + 2 * port); +} + +static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits; + + bits = readb(dev->mmio + DI_REG); + bits &= 0xf; + data[1] = bits; + data[0] = 0; + + return insn->n; +} + +static int do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + writeb(s->state, dev->mmio + DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int dio_60xx_config_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + writeb(s->io_bits, dev->mmio + DIO_DIRECTION_60XX_REG); + + return insn->n; +} + +static int dio_60xx_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + writeb(s->state, dev->mmio + DIO_DATA_60XX_REG); + + data[1] = readb(dev->mmio + DIO_DATA_60XX_REG); + + return insn->n; +} + +/* + * pci-6025 8800 caldac: + * address 0 == dac channel 0 offset + * address 1 == dac channel 0 gain + * address 2 == dac channel 1 offset + * address 3 == dac channel 1 gain + * address 4 == fine adc offset + * address 5 == coarse adc offset + * address 6 == coarse adc gain + * address 7 == fine adc gain + */ +/* + * pci-6402/16 uses all 8 channels for dac: + * address 0 == dac channel 0 fine gain + * address 1 == dac channel 0 coarse gain + * address 2 == dac channel 0 coarse offset + * address 3 == dac channel 1 coarse offset + * address 4 == dac channel 1 fine gain + * address 5 == dac channel 1 coarse gain + * address 6 == dac channel 0 fine offset + * address 7 == dac channel 1 fine offset + */ + +static int caldac_8800_write(struct comedi_device *dev, unsigned int address, + u8 value) +{ + struct pcidas64_private *devpriv = dev->private; + static const int num_caldac_channels = 8; + static const int bitstream_length = 11; + unsigned int bitstream = ((address & 0x7) << 8) | value; + unsigned int bit, register_bits; + static const int caldac_8800_udelay = 1; + + if (address >= num_caldac_channels) { + dev_err(dev->class_dev, "illegal caldac channel\n"); + return -1; + } + for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) { + register_bits = 0; + if (bitstream & bit) + register_bits |= SERIAL_DATA_IN_BIT; + udelay(caldac_8800_udelay); + writew(register_bits, devpriv->main_iobase + CALIBRATION_REG); + register_bits |= SERIAL_CLOCK_BIT; + udelay(caldac_8800_udelay); + writew(register_bits, devpriv->main_iobase + CALIBRATION_REG); + } + udelay(caldac_8800_udelay); + writew(SELECT_8800_BIT, devpriv->main_iobase + CALIBRATION_REG); + udelay(caldac_8800_udelay); + writew(0, devpriv->main_iobase + CALIBRATION_REG); + udelay(caldac_8800_udelay); + return 0; +} + +/* 4020 caldacs */ +static int caldac_i2c_write(struct comedi_device *dev, + unsigned int caldac_channel, unsigned int value) +{ + u8 serial_bytes[3]; + u8 i2c_addr; + enum pointer_bits { + /* manual has gain and offset bits switched */ + OFFSET_0_2 = 0x1, + GAIN_0_2 = 0x2, + OFFSET_1_3 = 0x4, + GAIN_1_3 = 0x8, + }; + enum data_bits { + NOT_CLEAR_REGISTERS = 0x20, + }; + + switch (caldac_channel) { + case 0: /* chan 0 offset */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = OFFSET_0_2; + break; + case 1: /* chan 1 offset */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = OFFSET_1_3; + break; + case 2: /* chan 2 offset */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = OFFSET_0_2; + break; + case 3: /* chan 3 offset */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = OFFSET_1_3; + break; + case 4: /* chan 0 gain */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = GAIN_0_2; + break; + case 5: /* chan 1 gain */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = GAIN_1_3; + break; + case 6: /* chan 2 gain */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = GAIN_0_2; + break; + case 7: /* chan 3 gain */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = GAIN_1_3; + break; + default: + dev_err(dev->class_dev, "invalid caldac channel\n"); + return -1; + } + serial_bytes[1] = NOT_CLEAR_REGISTERS | ((value >> 8) & 0xf); + serial_bytes[2] = value & 0xff; + i2c_write(dev, i2c_addr, serial_bytes, 3); + return 0; +} + +static void caldac_write(struct comedi_device *dev, unsigned int channel, + unsigned int value) +{ + const struct pcidas64_board *board = dev->board_ptr; + + switch (board->layout) { + case LAYOUT_60XX: + case LAYOUT_64XX: + caldac_8800_write(dev, channel, value); + break; + case LAYOUT_4020: + caldac_i2c_write(dev, channel, value); + break; + default: + break; + } +} + +static int cb_pcidas64_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* + * Programming the calib device is slow. Only write the + * last data value if the value has changed. + */ + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + if (s->readback[chan] != val) { + caldac_write(dev, chan, val); + s->readback[chan] = val; + } + } + + return insn->n; +} + +static void ad8402_write(struct comedi_device *dev, unsigned int channel, + unsigned int value) +{ + struct pcidas64_private *devpriv = dev->private; + static const int bitstream_length = 10; + unsigned int bit, register_bits; + unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff); + static const int ad8402_udelay = 1; + + register_bits = SELECT_8402_64XX_BIT; + udelay(ad8402_udelay); + writew(register_bits, devpriv->main_iobase + CALIBRATION_REG); + + for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) { + if (bitstream & bit) + register_bits |= SERIAL_DATA_IN_BIT; + else + register_bits &= ~SERIAL_DATA_IN_BIT; + udelay(ad8402_udelay); + writew(register_bits, devpriv->main_iobase + CALIBRATION_REG); + udelay(ad8402_udelay); + writew(register_bits | SERIAL_CLOCK_BIT, + devpriv->main_iobase + CALIBRATION_REG); + } + + udelay(ad8402_udelay); + writew(0, devpriv->main_iobase + CALIBRATION_REG); +} + +/* for pci-das6402/16, channel 0 is analog input gain and channel 1 is offset */ +static int cb_pcidas64_ad8402_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* + * Programming the calib device is slow. Only write the + * last data value if the value has changed. + */ + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + if (s->readback[chan] != val) { + ad8402_write(dev, chan, val); + s->readback[chan] = val; + } + } + + return insn->n; +} + +static u16 read_eeprom(struct comedi_device *dev, u8 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_REG_CNTRL; + u16 value; + static const int value_length = 16; + static const int eeprom_udelay = 1; + + udelay(eeprom_udelay); + devpriv->plx_control_bits &= ~PLX_CNTRL_EESK & ~PLX_CNTRL_EECS; + /* make sure we don't send anything to the i2c bus on 4020 */ + devpriv->plx_control_bits |= PLX_CNTRL_USERO; + writel(devpriv->plx_control_bits, plx_control_addr); + /* activate serial eeprom */ + udelay(eeprom_udelay); + devpriv->plx_control_bits |= PLX_CNTRL_EECS; + 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 |= PLX_CNTRL_EEWB; + else + devpriv->plx_control_bits &= ~PLX_CNTRL_EEWB; + writel(devpriv->plx_control_bits, plx_control_addr); + /* clock in bit */ + udelay(eeprom_udelay); + devpriv->plx_control_bits |= PLX_CNTRL_EESK; + writel(devpriv->plx_control_bits, plx_control_addr); + udelay(eeprom_udelay); + devpriv->plx_control_bits &= ~PLX_CNTRL_EESK; + 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 |= PLX_CNTRL_EESK; + writel(devpriv->plx_control_bits, plx_control_addr); + udelay(eeprom_udelay); + devpriv->plx_control_bits &= ~PLX_CNTRL_EESK; + writel(devpriv->plx_control_bits, plx_control_addr); + udelay(eeprom_udelay); + if (readl(plx_control_addr) & PLX_CNTRL_EERB) + value |= bit; + } + + /* deactivate eeprom serial input */ + udelay(eeprom_udelay); + devpriv->plx_control_bits &= ~PLX_CNTRL_EECS; + 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) +{ + unsigned int val; + unsigned int i; + + if (insn->n) { + /* No point reading the same EEPROM location more than once. */ + val = read_eeprom(dev, CR_CHAN(insn->chanspec)); + for (i = 0; i < insn->n; i++) + data[i] = val; + } + + return insn->n; +} + +/* Allocate and initialize the subdevice structures. */ +static int setup_subdevices(struct comedi_device *dev) +{ + const struct pcidas64_board *board = dev->board_ptr; + struct pcidas64_private *devpriv = dev->private; + struct comedi_subdevice *s; + int i; + int ret; + + ret = comedi_alloc_subdevices(dev, 10); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog input subdevice */ + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DITHER | SDF_CMD_READ; + if (board->layout == LAYOUT_60XX) + s->subdev_flags |= SDF_COMMON | SDF_DIFF; + else if (board->layout == LAYOUT_64XX) + s->subdev_flags |= SDF_DIFF; + /* XXX Number of inputs in differential mode is ignored */ + s->n_chan = board->ai_se_chans; + s->len_chanlist = 0x2000; + s->maxdata = (1 << board->ai_bits) - 1; + s->range_table = board->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 (board->layout == LAYOUT_4020) { + u8 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 (board->ao_nchan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | + SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = board->ao_nchan; + s->maxdata = (1 << board->ao_bits) - 1; + s->range_table = board->ao_range_table; + s->insn_write = ao_winsn; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + if (ao_cmd_is_supported(board)) { + dev->write_subdev = s; + s->do_cmdtest = ao_cmdtest; + s->do_cmd = ao_cmd; + s->len_chanlist = board->ao_nchan; + s->cancel = ao_cancel; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* digital input */ + s = &dev->subdevices[2]; + if (board->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 (board->layout == LAYOUT_64XX) { + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = do_wbits; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 */ + s = &dev->subdevices[4]; + if (board->has_8255) { + if (board->layout == LAYOUT_4020) { + ret = subdev_8255_init(dev, s, dio_callback_4020, + I8255_4020_REG); + } else { + ret = subdev_8255_mm_init(dev, s, NULL, + DIO_8255_OFFSET); + } + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8 channel dio for 60xx */ + s = &dev->subdevices[5]; + if (board->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 (board->layout == LAYOUT_4020) + s->maxdata = 0xfff; + else + s->maxdata = 0xff; + s->insn_write = cb_pcidas64_calib_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) { + caldac_write(dev, i, s->maxdata / 2); + s->readback[i] = s->maxdata / 2; + } + + /* 2 channel ad8402 potentiometer */ + s = &dev->subdevices[7]; + if (board->layout == LAYOUT_64XX) { + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 2; + s->maxdata = 0xff; + s->insn_write = cb_pcidas64_ad8402_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) { + ad8402_write(dev, i, s->maxdata / 2); + s->readback[i] = s->maxdata / 2; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* serial EEPROM, if present */ + s = &dev->subdevices[8]; + if (readl(devpriv->plx9080_iobase + PLX_REG_CNTRL) & + PLX_CNTRL_EEPRESENT) { + 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 *board = NULL; + struct pcidas64_private *devpriv; + u32 local_range, local_decode; + int retval; + + if (context < ARRAY_SIZE(pcidas64_boards)) + board = &pcidas64_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + + 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 = board->name; + + devpriv->main_phys_iobase = pci_resource_start(pcidev, 2); + devpriv->dio_counter_phys_iobase = pci_resource_start(pcidev, 3); + + devpriv->plx9080_iobase = pci_ioremap_bar(pcidev, 0); + devpriv->main_iobase = pci_ioremap_bar(pcidev, 2); + dev->mmio = pci_ioremap_bar(pcidev, 3); + + if (!devpriv->plx9080_iobase || !devpriv->main_iobase || !dev->mmio) { + dev_warn(dev->class_dev, "failed to remap io memory\n"); + return -ENOMEM; + } + + /* figure out what local addresses are */ + local_range = readl(devpriv->plx9080_iobase + PLX_REG_LAS0RR) & + PLX_LASRR_MEM_MASK; + local_decode = readl(devpriv->plx9080_iobase + PLX_REG_LAS0BA) & + local_range & PLX_LASBA_MEM_MASK; + devpriv->local0_iobase = ((u32)devpriv->main_phys_iobase & + ~local_range) | local_decode; + local_range = readl(devpriv->plx9080_iobase + PLX_REG_LAS1RR) & + PLX_LASRR_MEM_MASK; + local_decode = readl(devpriv->plx9080_iobase + PLX_REG_LAS1BA) & + local_range & PLX_LASBA_MEM_MASK; + devpriv->local1_iobase = ((u32)devpriv->dio_counter_phys_iobase & + ~local_range) | local_decode; + + retval = alloc_and_init_dma_members(dev); + if (retval < 0) + return retval; + + devpriv->hw_revision = + hw_revision(dev, readw(devpriv->main_iobase + HW_STATUS_REG)); + dev_dbg(dev->class_dev, "stc hardware revision %i\n", + devpriv->hw_revision); + init_plx9080(dev); + init_stc_registers(dev); + + retval = request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED, + "cb_pcidas64", dev); + if (retval) { + dev_dbg(dev->class_dev, "unable to allocate irq %u\n", + pcidev->irq); + return retval; + } + dev->irq = pcidev->irq; + dev_dbg(dev->class_dev, "irq %u\n", dev->irq); + + retval = setup_subdevices(dev); + if (retval < 0) + return retval; + + return 0; +} + +static void detach(struct comedi_device *dev) +{ + struct pcidas64_private *devpriv = dev->private; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->plx9080_iobase) { + disable_plx_interrupts(dev); + iounmap(devpriv->plx9080_iobase); + } + if (devpriv->main_iobase) + iounmap(devpriv->main_iobase); + if (dev->mmio) + iounmap(dev->mmio); + } + comedi_pci_disable(dev); + cb_pcidas64_free_dma(dev); +} + +static struct comedi_driver cb_pcidas64_driver = { + .driver_name = "cb_pcidas64", + .module = THIS_MODULE, + .auto_attach = auto_attach, + .detach = detach, +}; + +static int cb_pcidas64_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &cb_pcidas64_driver, + id->driver_data); +} + +static const struct pci_device_id cb_pcidas64_pci_table[] = { + { PCI_VDEVICE(CB, 0x001d), BOARD_PCIDAS6402_16 }, + { PCI_VDEVICE(CB, 0x001e), BOARD_PCIDAS6402_12 }, + { PCI_VDEVICE(CB, 0x0035), BOARD_PCIDAS64_M1_16 }, + { PCI_VDEVICE(CB, 0x0036), BOARD_PCIDAS64_M2_16 }, + { PCI_VDEVICE(CB, 0x0037), BOARD_PCIDAS64_M3_16 }, + { PCI_VDEVICE(CB, 0x0052), BOARD_PCIDAS4020_12 }, + { PCI_VDEVICE(CB, 0x005d), BOARD_PCIDAS6023 }, + { PCI_VDEVICE(CB, 0x005e), BOARD_PCIDAS6025 }, + { PCI_VDEVICE(CB, 0x005f), BOARD_PCIDAS6030 }, + { PCI_VDEVICE(CB, 0x0060), BOARD_PCIDAS6031 }, + { PCI_VDEVICE(CB, 0x0061), BOARD_PCIDAS6032 }, + { PCI_VDEVICE(CB, 0x0062), BOARD_PCIDAS6033 }, + { PCI_VDEVICE(CB, 0x0063), BOARD_PCIDAS6034 }, + { PCI_VDEVICE(CB, 0x0064), BOARD_PCIDAS6035 }, + { PCI_VDEVICE(CB, 0x0065), BOARD_PCIDAS6040 }, + { PCI_VDEVICE(CB, 0x0066), BOARD_PCIDAS6052 }, + { PCI_VDEVICE(CB, 0x0067), BOARD_PCIDAS6070 }, + { PCI_VDEVICE(CB, 0x0068), BOARD_PCIDAS6071 }, + { PCI_VDEVICE(CB, 0x006f), BOARD_PCIDAS6036 }, + { PCI_VDEVICE(CB, 0x0078), BOARD_PCIDAS6013 }, + { PCI_VDEVICE(CB, 0x0079), BOARD_PCIDAS6014 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cb_pcidas64_pci_table); + +static struct pci_driver cb_pcidas64_pci_driver = { + .name = "cb_pcidas64", + .id_table = cb_pcidas64_pci_table, + .probe = cb_pcidas64_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(cb_pcidas64_driver, cb_pcidas64_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/cb_pcidda.c b/drivers/comedi/drivers/cb_pcidda.c new file mode 100644 index 000000000..c52204a6b --- /dev/null +++ b/drivers/comedi/drivers/cb_pcidda.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: cb_pcidda + * Description: MeasurementComputing PCI-DDA series + * Devices: [Measurement Computing] PCI-DDA08/12 (pci-dda08/12), + * PCI-DDA04/12 (pci-dda04/12), PCI-DDA02/12 (pci-dda02/12), + * PCI-DDA08/16 (pci-dda08/16), PCI-DDA04/16 (pci-dda04/16), + * PCI-DDA02/16 (pci-dda02/16) + * Author: Ivan Martinez <ivanmr@altavista.com> + * Frank Mori Hess <fmhess@users.sourceforge.net> + * Status: works + * + * Configuration options: not applicable, uses PCI auto config + * + * Only simple analog output writing is supported. + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_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 BIT(0) /* Simultaneous update */ +#define CB_DDA_DA_CTRL_EN BIT(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 BIT(8) /* Unipolar range */ + +#define DACALIBRATION1 4 /* D/A CALIBRATION REGISTER 1 */ +/* write bits */ +/* serial data input for eeprom, caldacs, reference dac */ +#define SERIAL_IN_BIT 0x1 +#define CAL_CHANNEL_MASK (0x7 << 1) +#define CAL_CHANNEL_BITS(channel) (((channel) << 1) & CAL_CHANNEL_MASK) +/* read bits */ +#define CAL_COUNTER_MASK 0x1f +/* calibration counter overflow status bit */ +#define CAL_COUNTER_OVERFLOW_BIT 0x20 +/* analog output is less than reference dac voltage */ +#define AO_BELOW_REF_BIT 0x40 +#define SERIAL_OUT_BIT 0x80 /* serial data out, for reading from eeprom */ + +#define DACALIBRATION2 6 /* D/A CALIBRATION REGISTER 2 */ +#define SELECT_EEPROM_BIT 0x1 /* send serial data in to eeprom */ +/* don't send serial data to MAX542 reference dac */ +#define DESELECT_REF_DAC_BIT 0x2 +/* don't send serial data to caldac n */ +#define DESELECT_CALDAC_BIT(n) (0x4 << (n)) +/* manual says to set this bit with no explanation */ +#define DUMMY_BIT 0x40 + +#define CB_DDA_DA_DATA_REG(x) (0x08 + ((x) * 2)) + +/* Offsets for the caldac channels */ +#define CB_DDA_CALDAC_FINE_GAIN 0 +#define CB_DDA_CALDAC_COURSE_GAIN 1 +#define CB_DDA_CALDAC_COURSE_OFFSET 2 +#define CB_DDA_CALDAC_FINE_OFFSET 3 + +static const struct comedi_lrange cb_pcidda_ranges = { + 6, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5) + } +}; + +enum cb_pcidda_boardid { + BOARD_DDA02_12, + BOARD_DDA04_12, + BOARD_DDA08_12, + BOARD_DDA02_16, + BOARD_DDA04_16, + BOARD_DDA08_16, +}; + +struct cb_pcidda_board { + const char *name; + int ao_chans; + int ao_bits; +}; + +static const struct cb_pcidda_board cb_pcidda_boards[] = { + [BOARD_DDA02_12] = { + .name = "pci-dda02/12", + .ao_chans = 2, + .ao_bits = 12, + }, + [BOARD_DDA04_12] = { + .name = "pci-dda04/12", + .ao_chans = 4, + .ao_bits = 12, + }, + [BOARD_DDA08_12] = { + .name = "pci-dda08/12", + .ao_chans = 8, + .ao_bits = 12, + }, + [BOARD_DDA02_16] = { + .name = "pci-dda02/16", + .ao_chans = 2, + .ao_bits = 16, + }, + [BOARD_DDA04_16] = { + .name = "pci-dda04/16", + .ao_chans = 4, + .ao_bits = 16, + }, + [BOARD_DDA08_16] = { + .name = "pci-dda08/16", + .ao_chans = 8, + .ao_bits = 16, + }, +}; + +struct cb_pcidda_private { + unsigned long daqio; + /* bits last written to da calibration register 1 */ + unsigned int dac_cal1_bits; + /* current range settings for output channels */ + unsigned int ao_range[MAX_AO_CHANNELS]; + u16 eeprom_data[EEPROM_SIZE]; /* software copy of board's eeprom */ +}; + +/* lowlevel read from eeprom */ +static unsigned int cb_pcidda_serial_in(struct comedi_device *dev) +{ + struct cb_pcidda_private *devpriv = dev->private; + unsigned int value = 0; + int i; + const int value_width = 16; /* number of bits wide values are */ + + for (i = 1; i <= value_width; i++) { + /* read bits most significant bit first */ + if (inw_p(devpriv->daqio + DACALIBRATION1) & SERIAL_OUT_BIT) + value |= 1 << (value_width - i); + } + + return value; +} + +/* lowlevel write to eeprom/dac */ +static void cb_pcidda_serial_out(struct comedi_device *dev, unsigned int value, + unsigned int num_bits) +{ + struct cb_pcidda_private *devpriv = dev->private; + int i; + + for (i = 1; i <= num_bits; i++) { + /* send bits most significant bit first */ + if (value & (1 << (num_bits - i))) + devpriv->dac_cal1_bits |= SERIAL_IN_BIT; + else + devpriv->dac_cal1_bits &= ~SERIAL_IN_BIT; + outw_p(devpriv->dac_cal1_bits, devpriv->daqio + DACALIBRATION1); + } +} + +/* reads a 16 bit value from board's eeprom */ +static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev, + unsigned int address) +{ + struct cb_pcidda_private *devpriv = dev->private; + unsigned int i; + unsigned int cal2_bits; + unsigned int value; + /* one caldac for every two dac channels */ + const int max_num_caldacs = 4; + /* bits to send to tell eeprom we want to read */ + const int read_instruction = 0x6; + const int instruction_length = 3; + const int address_length = 8; + + /* send serial output stream to eeprom */ + cal2_bits = SELECT_EEPROM_BIT | DESELECT_REF_DAC_BIT | DUMMY_BIT; + /* deactivate caldacs (one caldac for every two channels) */ + for (i = 0; i < max_num_caldacs; i++) + cal2_bits |= DESELECT_CALDAC_BIT(i); + outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2); + + /* tell eeprom we want to read */ + cb_pcidda_serial_out(dev, read_instruction, instruction_length); + /* send address we want to read from */ + cb_pcidda_serial_out(dev, address, address_length); + + value = cb_pcidda_serial_in(dev); + + /* deactivate eeprom */ + cal2_bits &= ~SELECT_EEPROM_BIT; + outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2); + + return value; +} + +/* writes to 8 bit calibration dacs */ +static void cb_pcidda_write_caldac(struct comedi_device *dev, + unsigned int caldac, unsigned int channel, + unsigned int value) +{ + struct cb_pcidda_private *devpriv = dev->private; + unsigned int cal2_bits; + unsigned int i; + /* caldacs use 3 bit channel specification */ + const int num_channel_bits = 3; + const int num_caldac_bits = 8; /* 8 bit calibration dacs */ + /* one caldac for every two dac channels */ + const int max_num_caldacs = 4; + + /* write 3 bit channel */ + cb_pcidda_serial_out(dev, channel, num_channel_bits); + /* write 8 bit caldac value */ + cb_pcidda_serial_out(dev, value, num_caldac_bits); + +/* + * latch stream into appropriate caldac deselect reference dac + */ + cal2_bits = DESELECT_REF_DAC_BIT | DUMMY_BIT; + /* deactivate caldacs (one caldac for every two channels) */ + for (i = 0; i < max_num_caldacs; i++) + cal2_bits |= DESELECT_CALDAC_BIT(i); + /* activate the caldac we want */ + cal2_bits &= ~DESELECT_CALDAC_BIT(caldac); + outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2); + /* deactivate caldac */ + cal2_bits |= DESELECT_CALDAC_BIT(caldac); + outw_p(cal2_bits, devpriv->daqio + DACALIBRATION2); +} + +/* set caldacs to eeprom values for given channel and range */ +static void cb_pcidda_calibrate(struct comedi_device *dev, unsigned int channel, + unsigned int range) +{ + struct cb_pcidda_private *devpriv = dev->private; + unsigned int caldac = channel / 2; /* two caldacs per channel */ + unsigned int chan = 4 * (channel % 2); /* caldac channel base */ + unsigned int index = 2 * range + 12 * channel; + unsigned int offset; + unsigned int gain; + + /* save range so we can tell when we need to readjust calibration */ + devpriv->ao_range[channel] = range; + + /* get values from eeprom data */ + offset = devpriv->eeprom_data[0x7 + index]; + gain = devpriv->eeprom_data[0x8 + index]; + + /* set caldacs */ + cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_COURSE_OFFSET, + (offset >> 8) & 0xff); + cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_FINE_OFFSET, + offset & 0xff); + cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_COURSE_GAIN, + (gain >> 8) & 0xff); + cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_FINE_GAIN, + gain & 0xff); +} + +static int cb_pcidda_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcidda_private *devpriv = dev->private; + unsigned int channel = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int ctrl; + unsigned int i; + + if (range != devpriv->ao_range[channel]) + cb_pcidda_calibrate(dev, channel, range); + + ctrl = CB_DDA_DA_CTRL_EN | CB_DDA_DA_CTRL_DAC(channel); + + switch (range) { + case 0: + case 3: + ctrl |= CB_DDA_DA_CTRL_RANGE10V; + break; + case 1: + case 4: + ctrl |= CB_DDA_DA_CTRL_RANGE5V; + break; + case 2: + case 5: + ctrl |= CB_DDA_DA_CTRL_RANGE2V5; + break; + } + + if (range > 2) + ctrl |= CB_DDA_DA_CTRL_UNIP; + + outw(ctrl, devpriv->daqio + CB_DDA_DA_CTRL_REG); + + for (i = 0; i < insn->n; i++) + outw(data[i], devpriv->daqio + CB_DDA_DA_DATA_REG(channel)); + + return insn->n; +} + +static int cb_pcidda_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct cb_pcidda_board *board = NULL; + struct cb_pcidda_private *devpriv; + struct comedi_subdevice *s; + int i; + int ret; + + if (context < ARRAY_SIZE(cb_pcidda_boards)) + board = &cb_pcidda_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + devpriv->daqio = pci_resource_start(pcidev, 3); + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->ao_chans; + s->maxdata = (1 << board->ao_bits) - 1; + s->range_table = &cb_pcidda_ranges; + s->insn_write = cb_pcidda_ao_insn_write; + + /* two 8255 digital io subdevices */ + for (i = 0; i < 2; i++) { + s = &dev->subdevices[1 + i]; + ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE); + if (ret) + return ret; + } + + /* Read the caldac eeprom data */ + for (i = 0; i < EEPROM_SIZE; i++) + devpriv->eeprom_data[i] = cb_pcidda_read_eeprom(dev, i); + + /* set calibrations dacs */ + for (i = 0; i < board->ao_chans; i++) + cb_pcidda_calibrate(dev, i, devpriv->ao_range[i]); + + return 0; +} + +static struct comedi_driver cb_pcidda_driver = { + .driver_name = "cb_pcidda", + .module = THIS_MODULE, + .auto_attach = cb_pcidda_auto_attach, + .detach = comedi_pci_detach, +}; + +static int cb_pcidda_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &cb_pcidda_driver, + id->driver_data); +} + +static const struct pci_device_id cb_pcidda_pci_table[] = { + { PCI_VDEVICE(CB, 0x0020), BOARD_DDA02_12 }, + { PCI_VDEVICE(CB, 0x0021), BOARD_DDA04_12 }, + { PCI_VDEVICE(CB, 0x0022), BOARD_DDA08_12 }, + { PCI_VDEVICE(CB, 0x0023), BOARD_DDA02_16 }, + { PCI_VDEVICE(CB, 0x0024), BOARD_DDA04_16 }, + { PCI_VDEVICE(CB, 0x0025), BOARD_DDA08_16 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cb_pcidda_pci_table); + +static struct pci_driver cb_pcidda_pci_driver = { + .name = "cb_pcidda", + .id_table = cb_pcidda_pci_table, + .probe = cb_pcidda_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(cb_pcidda_driver, cb_pcidda_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/cb_pcimdas.c b/drivers/comedi/drivers/cb_pcimdas.c new file mode 100644 index 000000000..8bdb00774 --- /dev/null +++ b/drivers/comedi/drivers/cb_pcimdas.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/cb_pcimdas.c + * Comedi driver for Computer Boards PCIM-DAS1602/16 and PCIe-DAS1602/16 + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: cb_pcimdas + * Description: Measurement Computing PCI Migration series boards + * Devices: [ComputerBoards] PCIM-DAS1602/16 (cb_pcimdas), PCIe-DAS1602/16 + * Author: Richard Bytheway + * Updated: Mon, 13 Oct 2014 11:57:39 +0000 + * Status: experimental + * + * Written to support the PCIM-DAS1602/16 and PCIe-DAS1602/16. + * + * Configuration Options: + * none + * + * Manual configuration of PCI(e) cards is not supported; they are configured + * automatically. + * + * Developed from cb_pcidas and skel by Richard Bytheway (mocelet@sucs.org). + * Only supports DIO, AO and simple AI in it's present form. + * No interrupts, multi channel or FIFO AI, + * although the card looks like it could support this. + * + * https://www.mccdaq.com/PDFs/Manuals/pcim-das1602-16.pdf + * https://www.mccdaq.com/PDFs/Manuals/pcie-das1602-16.pdf + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +#include "plx9052.h" + +/* + * PCI Bar 1 Register map + * see plx9052.h for register and bit defines + */ + +/* + * PCI Bar 2 Register map (devpriv->daqio) + */ +#define PCIMDAS_AI_REG 0x00 +#define PCIMDAS_AI_SOFTTRIG_REG 0x00 +#define PCIMDAS_AO_REG(x) (0x02 + ((x) * 2)) + +/* + * PCI Bar 3 Register map (devpriv->BADR3) + */ +#define PCIMDAS_MUX_REG 0x00 +#define PCIMDAS_MUX(_lo, _hi) ((_lo) | ((_hi) << 4)) +#define PCIMDAS_DI_DO_REG 0x01 +#define PCIMDAS_STATUS_REG 0x02 +#define PCIMDAS_STATUS_EOC BIT(7) +#define PCIMDAS_STATUS_UB BIT(6) +#define PCIMDAS_STATUS_MUX BIT(5) +#define PCIMDAS_STATUS_CLK BIT(4) +#define PCIMDAS_STATUS_TO_CURR_MUX(x) ((x) & 0xf) +#define PCIMDAS_CONV_STATUS_REG 0x03 +#define PCIMDAS_CONV_STATUS_EOC BIT(7) +#define PCIMDAS_CONV_STATUS_EOB BIT(6) +#define PCIMDAS_CONV_STATUS_EOA BIT(5) +#define PCIMDAS_CONV_STATUS_FNE BIT(4) +#define PCIMDAS_CONV_STATUS_FHF BIT(3) +#define PCIMDAS_CONV_STATUS_OVERRUN BIT(2) +#define PCIMDAS_IRQ_REG 0x04 +#define PCIMDAS_IRQ_INTE BIT(7) +#define PCIMDAS_IRQ_INT BIT(6) +#define PCIMDAS_IRQ_OVERRUN BIT(4) +#define PCIMDAS_IRQ_EOA BIT(3) +#define PCIMDAS_IRQ_EOA_INT_SEL BIT(2) +#define PCIMDAS_IRQ_INTSEL(x) ((x) << 0) +#define PCIMDAS_IRQ_INTSEL_EOC PCIMDAS_IRQ_INTSEL(0) +#define PCIMDAS_IRQ_INTSEL_FNE PCIMDAS_IRQ_INTSEL(1) +#define PCIMDAS_IRQ_INTSEL_EOB PCIMDAS_IRQ_INTSEL(2) +#define PCIMDAS_IRQ_INTSEL_FHF_EOA PCIMDAS_IRQ_INTSEL(3) +#define PCIMDAS_PACER_REG 0x05 +#define PCIMDAS_PACER_GATE_STATUS BIT(6) +#define PCIMDAS_PACER_GATE_POL BIT(5) +#define PCIMDAS_PACER_GATE_LATCH BIT(4) +#define PCIMDAS_PACER_GATE_EN BIT(3) +#define PCIMDAS_PACER_EXT_PACER_POL BIT(2) +#define PCIMDAS_PACER_SRC(x) ((x) << 0) +#define PCIMDAS_PACER_SRC_POLLED PCIMDAS_PACER_SRC(0) +#define PCIMDAS_PACER_SRC_EXT PCIMDAS_PACER_SRC(2) +#define PCIMDAS_PACER_SRC_INT PCIMDAS_PACER_SRC(3) +#define PCIMDAS_PACER_SRC_MASK (3 << 0) +#define PCIMDAS_BURST_REG 0x06 +#define PCIMDAS_BURST_BME BIT(1) +#define PCIMDAS_BURST_CONV_EN BIT(0) +#define PCIMDAS_GAIN_REG 0x07 +#define PCIMDAS_8254_BASE 0x08 +#define PCIMDAS_USER_CNTR_REG 0x0c +#define PCIMDAS_USER_CNTR_CTR1_CLK_SEL BIT(0) +#define PCIMDAS_RESIDUE_MSB_REG 0x0d +#define PCIMDAS_RESIDUE_LSB_REG 0x0e + +/* + * PCI Bar 4 Register map (dev->iobase) + */ +#define PCIMDAS_8255_BASE 0x00 + +static const struct comedi_lrange cb_pcimdas_ai_bip_range = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; + +static const struct comedi_lrange cb_pcimdas_ai_uni_range = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +/* + * The Analog Output range is not programmable. The DAC ranges are + * jumper-settable on the board. The settings are not software-readable. + */ +static const struct comedi_lrange cb_pcimdas_ao_range = { + 6, { + BIP_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(10), + UNI_RANGE(5), + RANGE_ext(-1, 1), + RANGE_ext(0, 1) + } +}; + +/* + * this structure is for data unique to this hardware driver. If + * several hardware drivers keep similar information in this structure, + * feel free to suggest moving the variable to the struct comedi_device + * struct. + */ +struct cb_pcimdas_private { + /* base addresses */ + unsigned long daqio; + unsigned long BADR3; +}; + +static int cb_pcimdas_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int status; + + status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG); + if ((status & PCIMDAS_STATUS_EOC) == 0) + return 0; + return -EBUSY; +} + +static int cb_pcimdas_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int n; + unsigned int d; + int ret; + + /* only support sw initiated reads from a single channel */ + + /* configure for sw initiated read */ + d = inb(devpriv->BADR3 + PCIMDAS_PACER_REG); + if ((d & PCIMDAS_PACER_SRC_MASK) != PCIMDAS_PACER_SRC_POLLED) { + d &= ~PCIMDAS_PACER_SRC_MASK; + d |= PCIMDAS_PACER_SRC_POLLED; + outb(d, devpriv->BADR3 + PCIMDAS_PACER_REG); + } + + /* set bursting off, conversions on */ + outb(PCIMDAS_BURST_CONV_EN, devpriv->BADR3 + PCIMDAS_BURST_REG); + + /* set range */ + outb(range, devpriv->BADR3 + PCIMDAS_GAIN_REG); + + /* set mux for single channel scan */ + outb(PCIMDAS_MUX(chan, chan), devpriv->BADR3 + PCIMDAS_MUX_REG); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outw(0, devpriv->daqio + PCIMDAS_AI_SOFTTRIG_REG); + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, cb_pcimdas_ai_eoc, 0); + if (ret) + return ret; + + /* read data */ + data[n] = inw(devpriv->daqio + PCIMDAS_AI_REG); + } + + /* return the number of samples read/written */ + return n; +} + +static int cb_pcimdas_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, devpriv->daqio + PCIMDAS_AO_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int cb_pcimdas_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int val; + + val = inb(devpriv->BADR3 + PCIMDAS_DI_DO_REG); + + data[1] = val & 0x0f; + + return insn->n; +} + +static int cb_pcimdas_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + + if (comedi_dio_update_state(s, data)) + outb(s->state, devpriv->BADR3 + PCIMDAS_DI_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int cb_pcimdas_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int ctrl; + + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + switch (data[1]) { + case 0: /* internal 100 kHz clock */ + ctrl = PCIMDAS_USER_CNTR_CTR1_CLK_SEL; + break; + case 1: /* external clk on pin 21 */ + ctrl = 0; + break; + default: + return -EINVAL; + } + outb(ctrl, devpriv->BADR3 + PCIMDAS_USER_CNTR_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ctrl = inb(devpriv->BADR3 + PCIMDAS_USER_CNTR_REG); + if (ctrl & PCIMDAS_USER_CNTR_CTR1_CLK_SEL) { + data[1] = 0; + data[2] = I8254_OSC_BASE_100KHZ; + } else { + data[1] = 1; + data[2] = 0; + } + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static unsigned int cb_pcimdas_pacer_clk(struct comedi_device *dev) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int status; + + /* The Pacer Clock jumper selects a 10 MHz or 1 MHz clock */ + status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG); + if (status & PCIMDAS_STATUS_CLK) + return I8254_OSC_BASE_10MHZ; + return I8254_OSC_BASE_1MHZ; +} + +static bool cb_pcimdas_is_ai_se(struct comedi_device *dev) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int status; + + /* + * The number of Analog Input channels is set with the + * Analog Input Mode Switch on the board. The board can + * have 16 single-ended or 8 differential channels. + */ + status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG); + return status & PCIMDAS_STATUS_MUX; +} + +static bool cb_pcimdas_is_ai_uni(struct comedi_device *dev) +{ + struct cb_pcimdas_private *devpriv = dev->private; + unsigned int status; + + /* + * The Analog Input range polarity is set with the + * Analog Input Polarity Switch on the board. The + * inputs can be set to Unipolar or Bipolar ranges. + */ + status = inb(devpriv->BADR3 + PCIMDAS_STATUS_REG); + return status & PCIMDAS_STATUS_UB; +} + +static int cb_pcimdas_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct cb_pcimdas_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + devpriv->daqio = pci_resource_start(pcidev, 2); + devpriv->BADR3 = pci_resource_start(pcidev, 3); + dev->iobase = pci_resource_start(pcidev, 4); + + dev->pacer = comedi_8254_init(devpriv->BADR3 + PCIMDAS_8254_BASE, + cb_pcimdas_pacer_clk(dev), + I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 6); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + if (cb_pcimdas_is_ai_se(dev)) { + s->subdev_flags |= SDF_GROUND; + s->n_chan = 16; + } else { + s->subdev_flags |= SDF_DIFF; + s->n_chan = 8; + } + s->maxdata = 0xffff; + s->range_table = cb_pcimdas_is_ai_uni(dev) ? &cb_pcimdas_ai_uni_range + : &cb_pcimdas_ai_bip_range; + s->insn_read = cb_pcimdas_ai_insn_read; + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0xfff; + s->range_table = &cb_pcimdas_ao_range; + s->insn_write = cb_pcimdas_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + ret = subdev_8255_init(dev, s, NULL, PCIMDAS_8255_BASE); + if (ret) + return ret; + + /* Digital Input subdevice (main connector) */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = cb_pcimdas_di_insn_bits; + + /* Digital Output subdevice (main connector) */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = cb_pcimdas_do_insn_bits; + + /* Counter subdevice (8254) */ + s = &dev->subdevices[5]; + comedi_8254_subdevice_init(s, dev->pacer); + + dev->pacer->insn_config = cb_pcimdas_counter_insn_config; + + /* counters 1 and 2 are used internally for the pacer */ + comedi_8254_set_busy(dev->pacer, 1, true); + comedi_8254_set_busy(dev->pacer, 2, true); + + return 0; +} + +static struct comedi_driver cb_pcimdas_driver = { + .driver_name = "cb_pcimdas", + .module = THIS_MODULE, + .auto_attach = cb_pcimdas_auto_attach, + .detach = comedi_pci_detach, +}; + +static int cb_pcimdas_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &cb_pcimdas_driver, + id->driver_data); +} + +static const struct pci_device_id cb_pcimdas_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0056) }, /* PCIM-DAS1602/16 */ + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0115) }, /* PCIe-DAS1602/16 */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table); + +static struct pci_driver cb_pcimdas_pci_driver = { + .name = "cb_pcimdas", + .id_table = cb_pcimdas_pci_table, + .probe = cb_pcimdas_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(cb_pcimdas_driver, cb_pcimdas_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for PCIM-DAS1602/16 and PCIe-DAS1602/16"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/cb_pcimdda.c b/drivers/comedi/drivers/cb_pcimdda.c new file mode 100644 index 000000000..bf8093a10 --- /dev/null +++ b/drivers/comedi/drivers/cb_pcimdda.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * 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/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> + +/* device ids of the cards we support -- currently only 1 card supported */ +#define PCI_ID_PCIM_DDA06_16 0x0053 + +/* + * Register map, 8-bit access only + */ +#define PCIMDDA_DA_CHAN(x) (0x00 + (x) * 2) +#define PCIMDDA_8255_BASE_REG 0x0c + +static int cb_pcimdda_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + /* + * Write the LSB then MSB. + * + * If the simultaneous xfer mode is selected by the + * jumper on the card, a read instruction is needed + * in order to initiate the simultaneous transfer. + * Otherwise, the DAC will be updated when the MSB + * is written. + */ + outb(val & 0x00ff, offset); + outb((val >> 8) & 0x00ff, offset + 1); + } + s->readback[chan] = val; + + return insn->n; +} + +static int cb_pcimdda_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* Initiate the simultaneous transfer */ + inw(dev->iobase + PCIMDDA_DA_CHAN(chan)); + + return comedi_readback_insn_read(dev, s, insn, data); +} + +static int cb_pcimdda_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 3); + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 6; + s->maxdata = 0xffff; + s->range_table = &range_bipolar5; + s->insn_write = cb_pcimdda_ao_insn_write; + s->insn_read = cb_pcimdda_ao_insn_read; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[1]; + /* digital i/o subdevice */ + return subdev_8255_init(dev, s, NULL, PCIMDDA_8255_BASE_REG); +} + +static struct comedi_driver cb_pcimdda_driver = { + .driver_name = "cb_pcimdda", + .module = THIS_MODULE, + .auto_attach = cb_pcimdda_auto_attach, + .detach = comedi_pci_detach, +}; + +static int cb_pcimdda_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &cb_pcimdda_driver, + id->driver_data); +} + +static const struct pci_device_id cb_pcimdda_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CB, PCI_ID_PCIM_DDA06_16) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cb_pcimdda_pci_table); + +static struct pci_driver cb_pcimdda_driver_pci_driver = { + .name = "cb_pcimdda", + .id_table = cb_pcimdda_pci_table, + .probe = cb_pcimdda_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(cb_pcimdda_driver, cb_pcimdda_driver_pci_driver); + +MODULE_AUTHOR("Calin A. Culianu <calin@rtlab.org>"); +MODULE_DESCRIPTION("Comedi low-level driver for the Computerboards PCIM-DDA series. Currently only supports PCIM-DDA06-16 (which also happens to be the only board in this series. :) ) "); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/comedi_8254.c b/drivers/comedi/drivers/comedi_8254.c new file mode 100644 index 000000000..b4185c1b2 --- /dev/null +++ b/drivers/comedi/drivers/comedi_8254.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi_8254.c + * Generic 8254 timer/counter support + * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Based on 8253.h and various subdevice implementations in comedi drivers. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Module: comedi_8254 + * Description: Generic 8254 timer/counter support + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Thu Jan 8 16:45:45 MST 2015 + * Status: works + * + * This module is not used directly by end-users. Rather, it is used by other + * drivers to provide support for an 8254 Programmable Interval Timer. These + * counters are typically used to generate the pacer clock used for data + * acquisition. Some drivers also expose the counters for general purpose use. + * + * This module provides the following basic functions: + * + * comedi_8254_init() / comedi_8254_mm_init() + * Initializes this module to access the 8254 registers. The _mm version + * sets up the module for MMIO register access the other for PIO access. + * The pointer returned from these functions is normally stored in the + * comedi_device dev->pacer and will be freed by the comedi core during + * the driver (*detach). If a driver has multiple 8254 devices, they need + * to be stored in the drivers private data and freed when the driver is + * detached. + * + * NOTE: The counters are reset by setting them to I8254_MODE0 as part of + * this initialization. + * + * comedi_8254_set_mode() + * Sets a counters operation mode: + * I8254_MODE0 Interrupt on terminal count + * I8254_MODE1 Hardware retriggerable one-shot + * I8254_MODE2 Rate generator + * I8254_MODE3 Square wave mode + * I8254_MODE4 Software triggered strobe + * I8254_MODE5 Hardware triggered strobe (retriggerable) + * + * In addition I8254_BCD and I8254_BINARY specify the counting mode: + * I8254_BCD BCD counting + * I8254_BINARY Binary counting + * + * comedi_8254_write() + * Writes an initial value to a counter. + * + * The largest possible initial count is 0; this is equivalent to 2^16 + * for binary counting and 10^4 for BCD counting. + * + * NOTE: The counter does not stop when it reaches zero. In Mode 0, 1, 4, + * and 5 the counter "wraps around" to the highest count, either 0xffff + * for binary counting or 9999 for BCD counting, and continues counting. + * Modes 2 and 3 are periodic; the counter reloads itself with the initial + * count and continues counting from there. + * + * comedi_8254_read() + * Reads the current value from a counter. + * + * comedi_8254_status() + * Reads the status of a counter. + * + * comedi_8254_load() + * Sets a counters operation mode and writes the initial value. + * + * Typically the pacer clock is created by cascading two of the 16-bit counters + * to create a 32-bit rate generator (I8254_MODE2). These functions are + * provided to handle the cascaded counters: + * + * comedi_8254_ns_to_timer() + * Calculates the divisor value needed for a single counter to generate + * ns timing. + * + * comedi_8254_cascade_ns_to_timer() + * Calculates the two divisor values needed to the generate the pacer + * clock (in ns). + * + * comedi_8254_update_divisors() + * Transfers the intermediate divisor values to the current divisors. + * + * comedi_8254_pacer_enable() + * Programs the mode of the cascaded counters and writes the current + * divisor values. + * + * To expose the counters as a subdevice for general purpose use the following + * functions a provided: + * + * comedi_8254_subdevice_init() + * Initializes a comedi_subdevice to use the 8254 timer. + * + * comedi_8254_set_busy() + * Internally flags a counter as "busy". This is done to protect the + * counters that are used for the cascaded 32-bit pacer. + * + * The subdevice provides (*insn_read) and (*insn_write) operations to read + * the current value and write an initial value to a counter. A (*insn_config) + * operation is also provided to handle the following comedi instructions: + * + * INSN_CONFIG_SET_COUNTER_MODE calls comedi_8254_set_mode() + * INSN_CONFIG_8254_READ_STATUS calls comedi_8254_status() + * + * The (*insn_config) member of comedi_8254 can be initialized by the external + * driver to handle any additional instructions. + * + * NOTE: Gate control, clock routing, and any interrupt handling for the + * counters is not handled by this module. These features are driver dependent. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> + +static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg) +{ + unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift; + unsigned int val; + + switch (i8254->iosize) { + default: + case I8254_IO8: + if (i8254->mmio) + val = readb(i8254->mmio + reg_offset); + else + val = inb(i8254->iobase + reg_offset); + break; + case I8254_IO16: + if (i8254->mmio) + val = readw(i8254->mmio + reg_offset); + else + val = inw(i8254->iobase + reg_offset); + break; + case I8254_IO32: + if (i8254->mmio) + val = readl(i8254->mmio + reg_offset); + else + val = inl(i8254->iobase + reg_offset); + break; + } + return val & 0xff; +} + +static void __i8254_write(struct comedi_8254 *i8254, + unsigned int val, unsigned int reg) +{ + unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift; + + switch (i8254->iosize) { + default: + case I8254_IO8: + if (i8254->mmio) + writeb(val, i8254->mmio + reg_offset); + else + outb(val, i8254->iobase + reg_offset); + break; + case I8254_IO16: + if (i8254->mmio) + writew(val, i8254->mmio + reg_offset); + else + outw(val, i8254->iobase + reg_offset); + break; + case I8254_IO32: + if (i8254->mmio) + writel(val, i8254->mmio + reg_offset); + else + outl(val, i8254->iobase + reg_offset); + break; + } +} + +/** + * comedi_8254_status - return the status of a counter + * @i8254: comedi_8254 struct for the timer + * @counter: the counter number + */ +unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter) +{ + unsigned int cmd; + + if (counter > 2) + return 0; + + cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter); + __i8254_write(i8254, cmd, I8254_CTRL_REG); + + return __i8254_read(i8254, counter); +} +EXPORT_SYMBOL_GPL(comedi_8254_status); + +/** + * comedi_8254_read - read the current counter value + * @i8254: comedi_8254 struct for the timer + * @counter: the counter number + */ +unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter) +{ + unsigned int val; + + if (counter > 2) + return 0; + + /* latch counter */ + __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH, + I8254_CTRL_REG); + + /* read LSB then MSB */ + val = __i8254_read(i8254, counter); + val |= (__i8254_read(i8254, counter) << 8); + + return val; +} +EXPORT_SYMBOL_GPL(comedi_8254_read); + +/** + * comedi_8254_write - load a 16-bit initial counter value + * @i8254: comedi_8254 struct for the timer + * @counter: the counter number + * @val: the initial value + */ +void comedi_8254_write(struct comedi_8254 *i8254, + unsigned int counter, unsigned int val) +{ + unsigned int byte; + + if (counter > 2) + return; + if (val > 0xffff) + return; + + /* load LSB then MSB */ + byte = val & 0xff; + __i8254_write(i8254, byte, counter); + byte = (val >> 8) & 0xff; + __i8254_write(i8254, byte, counter); +} +EXPORT_SYMBOL_GPL(comedi_8254_write); + +/** + * comedi_8254_set_mode - set the mode of a counter + * @i8254: comedi_8254 struct for the timer + * @counter: the counter number + * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY + */ +int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter, + unsigned int mode) +{ + unsigned int byte; + + if (counter > 2) + return -EINVAL; + if (mode > (I8254_MODE5 | I8254_BCD)) + return -EINVAL; + + byte = I8254_CTRL_SEL_CTR(counter) | /* select counter */ + I8254_CTRL_LSB_MSB | /* load LSB then MSB */ + mode; /* mode and BCD|binary */ + __i8254_write(i8254, byte, I8254_CTRL_REG); + + return 0; +} +EXPORT_SYMBOL_GPL(comedi_8254_set_mode); + +/** + * comedi_8254_load - program the mode and initial count of a counter + * @i8254: comedi_8254 struct for the timer + * @counter: the counter number + * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY + * @val: the initial value + */ +int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter, + unsigned int val, unsigned int mode) +{ + if (counter > 2) + return -EINVAL; + if (val > 0xffff) + return -EINVAL; + if (mode > (I8254_MODE5 | I8254_BCD)) + return -EINVAL; + + comedi_8254_set_mode(i8254, counter, mode); + comedi_8254_write(i8254, counter, val); + + return 0; +} +EXPORT_SYMBOL_GPL(comedi_8254_load); + +/** + * comedi_8254_pacer_enable - set the mode and load the cascaded counters + * @i8254: comedi_8254 struct for the timer + * @counter1: the counter number for the first divisor + * @counter2: the counter number for the second divisor + * @enable: flag to enable (load) the counters + */ +void comedi_8254_pacer_enable(struct comedi_8254 *i8254, + unsigned int counter1, + unsigned int counter2, + bool enable) +{ + unsigned int mode; + + if (counter1 > 2 || counter2 > 2 || counter1 == counter2) + return; + + if (enable) + mode = I8254_MODE2 | I8254_BINARY; + else + mode = I8254_MODE0 | I8254_BINARY; + + comedi_8254_set_mode(i8254, counter1, mode); + comedi_8254_set_mode(i8254, counter2, mode); + + if (enable) { + /* + * Divisors are loaded second counter then first counter to + * avoid possible issues with the first counter expiring + * before the second counter is loaded. + */ + comedi_8254_write(i8254, counter2, i8254->divisor2); + comedi_8254_write(i8254, counter1, i8254->divisor1); + } +} +EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable); + +/** + * comedi_8254_update_divisors - update the divisors for the cascaded counters + * @i8254: comedi_8254 struct for the timer + */ +void comedi_8254_update_divisors(struct comedi_8254 *i8254) +{ + /* masking is done since counter maps zero to 0x10000 */ + i8254->divisor = i8254->next_div & 0xffff; + i8254->divisor1 = i8254->next_div1 & 0xffff; + i8254->divisor2 = i8254->next_div2 & 0xffff; +} +EXPORT_SYMBOL_GPL(comedi_8254_update_divisors); + +/** + * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values + * @i8254: comedi_8254 struct for the timer + * @nanosec: the desired ns time + * @flags: comedi_cmd flags + */ +void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254, + unsigned int *nanosec, + unsigned int flags) +{ + unsigned int d1 = i8254->next_div1 ? i8254->next_div1 : I8254_MAX_COUNT; + unsigned int d2 = i8254->next_div2 ? i8254->next_div2 : I8254_MAX_COUNT; + unsigned int div = d1 * d2; + unsigned int ns_lub = 0xffffffff; + unsigned int ns_glb = 0; + unsigned int d1_lub = 0; + unsigned int d1_glb = 0; + unsigned int d2_lub = 0; + unsigned int d2_glb = 0; + unsigned int start; + unsigned int ns; + unsigned int ns_low; + unsigned int ns_high; + + /* exit early if everything is already correct */ + if (div * i8254->osc_base == *nanosec && + d1 > 1 && d1 <= I8254_MAX_COUNT && + d2 > 1 && d2 <= I8254_MAX_COUNT && + /* check for overflow */ + div > d1 && div > d2 && + div * i8254->osc_base > div && + div * i8254->osc_base > i8254->osc_base) + return; + + div = *nanosec / i8254->osc_base; + d2 = I8254_MAX_COUNT; + start = div / d2; + if (start < 2) + start = 2; + for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; d1++) { + for (d2 = div / d1; + d1 * d2 <= div + d1 + 1 && d2 <= I8254_MAX_COUNT; d2++) { + ns = i8254->osc_base * d1 * d2; + if (ns <= *nanosec && ns > ns_glb) { + ns_glb = ns; + d1_glb = d1; + d2_glb = d2; + } + if (ns >= *nanosec && ns < ns_lub) { + ns_lub = ns; + d1_lub = d1; + d2_lub = d2; + } + } + } + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + ns_high = d1_lub * d2_lub * i8254->osc_base; + ns_low = d1_glb * d2_glb * i8254->osc_base; + if (ns_high - *nanosec < *nanosec - ns_low) { + d1 = d1_lub; + d2 = d2_lub; + } else { + d1 = d1_glb; + d2 = d2_glb; + } + break; + case CMDF_ROUND_UP: + d1 = d1_lub; + d2 = d2_lub; + break; + case CMDF_ROUND_DOWN: + d1 = d1_glb; + d2 = d2_glb; + break; + } + + *nanosec = d1 * d2 * i8254->osc_base; + i8254->next_div1 = d1; + i8254->next_div2 = d2; +} +EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer); + +/** + * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing + * @i8254: comedi_8254 struct for the timer + * @nanosec: the desired ns time + * @flags: comedi_cmd flags + */ +void comedi_8254_ns_to_timer(struct comedi_8254 *i8254, + unsigned int *nanosec, unsigned int flags) +{ + unsigned int divisor; + + switch (flags & CMDF_ROUND_MASK) { + default: + case CMDF_ROUND_NEAREST: + divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base); + break; + case CMDF_ROUND_UP: + divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base); + break; + case CMDF_ROUND_DOWN: + divisor = *nanosec / i8254->osc_base; + break; + } + if (divisor < 2) + divisor = 2; + if (divisor > I8254_MAX_COUNT) + divisor = I8254_MAX_COUNT; + + *nanosec = divisor * i8254->osc_base; + i8254->next_div = divisor; +} +EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer); + +/** + * comedi_8254_set_busy - set/clear the "busy" flag for a given counter + * @i8254: comedi_8254 struct for the timer + * @counter: the counter number + * @busy: set/clear flag + */ +void comedi_8254_set_busy(struct comedi_8254 *i8254, + unsigned int counter, bool busy) +{ + if (counter < 3) + i8254->busy[counter] = busy; +} +EXPORT_SYMBOL_GPL(comedi_8254_set_busy); + +static int comedi_8254_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct comedi_8254 *i8254 = s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + if (i8254->busy[chan]) + return -EBUSY; + + for (i = 0; i < insn->n; i++) + data[i] = comedi_8254_read(i8254, chan); + + return insn->n; +} + +static int comedi_8254_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct comedi_8254 *i8254 = s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + + if (i8254->busy[chan]) + return -EBUSY; + + if (insn->n) + comedi_8254_write(i8254, chan, data[insn->n - 1]); + + return insn->n; +} + +static int comedi_8254_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct comedi_8254 *i8254 = s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int ret; + + if (i8254->busy[chan]) + return -EBUSY; + + switch (data[0]) { + case INSN_CONFIG_RESET: + ret = comedi_8254_set_mode(i8254, chan, + I8254_MODE0 | I8254_BINARY); + if (ret) + return ret; + break; + case INSN_CONFIG_SET_COUNTER_MODE: + ret = comedi_8254_set_mode(i8254, chan, data[1]); + if (ret) + return ret; + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = comedi_8254_status(i8254, chan); + break; + default: + /* + * If available, call the driver provided (*insn_config) + * to handle any driver implemented instructions. + */ + if (i8254->insn_config) + return i8254->insn_config(dev, s, insn, data); + + return -EINVAL; + } + + return insn->n; +} + +/** + * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer + * @s: comedi_subdevice struct + * @i8254: comedi_8254 struct + */ +void comedi_8254_subdevice_init(struct comedi_subdevice *s, + struct comedi_8254 *i8254) +{ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 3; + s->maxdata = 0xffff; + s->range_table = &range_unknown; + s->insn_read = comedi_8254_insn_read; + s->insn_write = comedi_8254_insn_write; + s->insn_config = comedi_8254_insn_config; + + s->private = i8254; +} +EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init); + +static struct comedi_8254 *__i8254_init(unsigned long iobase, + void __iomem *mmio, + unsigned int osc_base, + unsigned int iosize, + unsigned int regshift) +{ + struct comedi_8254 *i8254; + int i; + + /* sanity check that the iosize is valid */ + if (!(iosize == I8254_IO8 || iosize == I8254_IO16 || + iosize == I8254_IO32)) + return NULL; + + i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL); + if (!i8254) + return NULL; + + i8254->iobase = iobase; + i8254->mmio = mmio; + i8254->iosize = iosize; + i8254->regshift = regshift; + + /* default osc_base to the max speed of a generic 8254 timer */ + i8254->osc_base = osc_base ? osc_base : I8254_OSC_BASE_10MHZ; + + /* reset all the counters by setting them to I8254_MODE0 */ + for (i = 0; i < 3; i++) + comedi_8254_set_mode(i8254, i, I8254_MODE0 | I8254_BINARY); + + return i8254; +} + +/** + * comedi_8254_init - allocate and initialize the 8254 device for pio access + * @iobase: port I/O base address + * @osc_base: base time of the counter in ns + * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer() + * @iosize: I/O register size + * @regshift: register gap shift + */ +struct comedi_8254 *comedi_8254_init(unsigned long iobase, + unsigned int osc_base, + unsigned int iosize, + unsigned int regshift) +{ + return __i8254_init(iobase, NULL, osc_base, iosize, regshift); +} +EXPORT_SYMBOL_GPL(comedi_8254_init); + +/** + * comedi_8254_mm_init - allocate and initialize the 8254 device for mmio access + * @mmio: memory mapped I/O base address + * @osc_base: base time of the counter in ns + * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer() + * @iosize: I/O register size + * @regshift: register gap shift + */ +struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio, + unsigned int osc_base, + unsigned int iosize, + unsigned int regshift) +{ + return __i8254_init(0, mmio, osc_base, iosize, regshift); +} +EXPORT_SYMBOL_GPL(comedi_8254_mm_init); + +static int __init comedi_8254_module_init(void) +{ + return 0; +} +module_init(comedi_8254_module_init); + +static void __exit comedi_8254_module_exit(void) +{ +} +module_exit(comedi_8254_module_exit); + +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/comedi_8255.c b/drivers/comedi/drivers/comedi_8255.c new file mode 100644 index 000000000..5562b9cd0 --- /dev/null +++ b/drivers/comedi/drivers/comedi_8255.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi_8255.c + * Generic 8255 digital I/O support + * + * Split from the Comedi "8255" driver module. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998 David A. Schleef <ds@schleef.org> + */ + +/* + * Module: comedi_8255 + * Description: Generic 8255 support + * Author: ds + * Updated: Fri, 22 May 2015 12:14:17 +0000 + * Status: works + * + * This module is not used directly by end-users. Rather, it is used by + * other drivers to provide support for an 8255 "Programmable Peripheral + * Interface" (PPI) chip. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +struct subdev_8255_private { + unsigned long regbase; + int (*io)(struct comedi_device *dev, int dir, int port, int data, + unsigned long regbase); +}; + +static int subdev_8255_io(struct comedi_device *dev, + int dir, int port, int data, unsigned long regbase) +{ + if (dir) { + outb(data, dev->iobase + regbase + port); + return 0; + } + return inb(dev->iobase + regbase + port); +} + +static int subdev_8255_mmio(struct comedi_device *dev, + int dir, int port, int data, unsigned long regbase) +{ + if (dir) { + writeb(data, dev->mmio + regbase + port); + return 0; + } + return readb(dev->mmio + regbase + port); +} + +static int subdev_8255_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct subdev_8255_private *spriv = s->private; + unsigned long regbase = spriv->regbase; + unsigned int mask; + unsigned int v; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0xff) + spriv->io(dev, 1, I8255_DATA_A_REG, + s->state & 0xff, regbase); + if (mask & 0xff00) + spriv->io(dev, 1, I8255_DATA_B_REG, + (s->state >> 8) & 0xff, regbase); + if (mask & 0xff0000) + spriv->io(dev, 1, I8255_DATA_C_REG, + (s->state >> 16) & 0xff, regbase); + } + + v = spriv->io(dev, 0, I8255_DATA_A_REG, 0, regbase); + v |= (spriv->io(dev, 0, I8255_DATA_B_REG, 0, regbase) << 8); + v |= (spriv->io(dev, 0, I8255_DATA_C_REG, 0, regbase) << 16); + + data[1] = v; + + return insn->n; +} + +static void subdev_8255_do_config(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct subdev_8255_private *spriv = s->private; + unsigned long regbase = spriv->regbase; + int config; + + config = I8255_CTRL_CW; + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= I8255_CTRL_A_IO; + if (!(s->io_bits & 0x00ff00)) + config |= I8255_CTRL_B_IO; + if (!(s->io_bits & 0x0f0000)) + config |= I8255_CTRL_C_LO_IO; + if (!(s->io_bits & 0xf00000)) + config |= I8255_CTRL_C_HI_IO; + + spriv->io(dev, 1, I8255_CTRL_REG, config, regbase); +} + +static int subdev_8255_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x0000ff; + else if (chan < 16) + mask = 0x00ff00; + else if (chan < 20) + mask = 0x0f0000; + else + mask = 0xf00000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + subdev_8255_do_config(dev, s); + + return insn->n; +} + +static int __subdev_8255_init(struct comedi_device *dev, + struct comedi_subdevice *s, + int (*io)(struct comedi_device *dev, + int dir, int port, int data, + unsigned long regbase), + unsigned long regbase, + bool is_mmio) +{ + struct subdev_8255_private *spriv; + + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return -ENOMEM; + + if (io) + spriv->io = io; + else if (is_mmio) + spriv->io = subdev_8255_mmio; + else + spriv->io = subdev_8255_io; + spriv->regbase = regbase; + + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = subdev_8255_insn; + s->insn_config = subdev_8255_insn_config; + + subdev_8255_do_config(dev, s); + + return 0; +} + +/** + * subdev_8255_init - initialize DIO subdevice for driving I/O mapped 8255 + * @dev: comedi device owning subdevice + * @s: comedi subdevice to initialize + * @io: (optional) register I/O call-back function + * @regbase: offset of 8255 registers from dev->iobase, or call-back context + * + * Initializes a comedi subdevice as a DIO subdevice driving an 8255 chip. + * + * If the optional I/O call-back function is provided, its prototype is of + * the following form: + * + * int my_8255_callback(struct comedi_device *dev, int dir, int port, + * int data, unsigned long regbase); + * + * where 'dev', and 'regbase' match the values passed to this function, + * 'port' is the 8255 port number 0 to 3 (including the control port), 'dir' + * is the direction (0 for read, 1 for write) and 'data' is the value to be + * written. It should return 0 if writing or the value read if reading. + * + * If the optional I/O call-back function is not provided, an internal + * call-back function is used which uses consecutive I/O port addresses + * starting at dev->iobase + regbase. + * + * Return: -ENOMEM if failed to allocate memory, zero on success. + */ +int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*io)(struct comedi_device *dev, int dir, int port, + int data, unsigned long regbase), + unsigned long regbase) +{ + return __subdev_8255_init(dev, s, io, regbase, false); +} +EXPORT_SYMBOL_GPL(subdev_8255_init); + +/** + * subdev_8255_mm_init - initialize DIO subdevice for driving mmio-mapped 8255 + * @dev: comedi device owning subdevice + * @s: comedi subdevice to initialize + * @io: (optional) register I/O call-back function + * @regbase: offset of 8255 registers from dev->mmio, or call-back context + * + * Initializes a comedi subdevice as a DIO subdevice driving an 8255 chip. + * + * If the optional I/O call-back function is provided, its prototype is of + * the following form: + * + * int my_8255_callback(struct comedi_device *dev, int dir, int port, + * int data, unsigned long regbase); + * + * where 'dev', and 'regbase' match the values passed to this function, + * 'port' is the 8255 port number 0 to 3 (including the control port), 'dir' + * is the direction (0 for read, 1 for write) and 'data' is the value to be + * written. It should return 0 if writing or the value read if reading. + * + * If the optional I/O call-back function is not provided, an internal + * call-back function is used which uses consecutive MMIO virtual addresses + * starting at dev->mmio + regbase. + * + * Return: -ENOMEM if failed to allocate memory, zero on success. + */ +int subdev_8255_mm_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*io)(struct comedi_device *dev, int dir, int port, + int data, unsigned long regbase), + unsigned long regbase) +{ + return __subdev_8255_init(dev, s, io, regbase, true); +} +EXPORT_SYMBOL_GPL(subdev_8255_mm_init); + +/** + * subdev_8255_regbase - get offset of 8255 registers or call-back context + * @s: comedi subdevice + * + * Returns the 'regbase' parameter that was previously passed to + * subdev_8255_init() or subdev_8255_mm_init() to set up the subdevice. + * Only valid if the subdevice was set up successfully. + */ +unsigned long subdev_8255_regbase(struct comedi_subdevice *s) +{ + struct subdev_8255_private *spriv = s->private; + + return spriv->regbase; +} +EXPORT_SYMBOL_GPL(subdev_8255_regbase); + +static int __init comedi_8255_module_init(void) +{ + return 0; +} +module_init(comedi_8255_module_init); + +static void __exit comedi_8255_module_exit(void) +{ +} +module_exit(comedi_8255_module_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi: Generic 8255 digital I/O support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/comedi_bond.c b/drivers/comedi/drivers/comedi_bond.c new file mode 100644 index 000000000..78c39fa84 --- /dev/null +++ b/drivers/comedi/drivers/comedi_bond.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi.h> +#include <linux/comedi/comedilib.h> +#include <linux/comedi/comedidev.h> + +struct bonded_device { + struct comedi_device *dev; + unsigned int minor; + unsigned int subdev; + unsigned int nchans; +}; + +struct comedi_bond_private { + char name[256]; + struct bonded_device **devs; + unsigned int ndevs; + unsigned int 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 = (b_chans < 32) ? ((1 << b_chans) - 1) + : 0xffffffff; + b_write_mask = (write_mask >> n_done) & b_mask; + b_data_bits = (data_bits >> n_done) & b_mask; + /* Read/Write the new digital lines. */ + ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev, + b_write_mask, &b_data_bits, + base_chan); + if (ret < 0) + return ret; + /* Place read bits into data[1]. */ + data[1] &= ~(b_mask << n_done); + data[1] |= (b_data_bits & b_mask) << n_done; + /* + * Set up for following bonded device (if still have + * channels to read/write). + */ + base_chan = 0; + n_done += b_chans; + n_left -= b_chans; + } else { + /* Skip bonded devices before base channel. */ + base_chan -= bdev->nchans; + } + } while (n_left); + + return insn->n; +} + +static int bonding_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct comedi_bond_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int ret; + struct bonded_device *bdev; + struct bonded_device **devs; + + /* + * Locate bonded subdevice and adjust channel. + */ + devs = devpriv->devs; + for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++) + chan -= bdev->nchans; + + /* + * The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * configuration instruction INSN_CONFIG_DIO_OUTPUT, + * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY. + * + * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT, + * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT. This is deliberate ;) + */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + case INSN_CONFIG_DIO_INPUT: + ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]); + break; + case INSN_CONFIG_DIO_QUERY: + ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan, + &data[1]); + break; + default: + ret = -EINVAL; + break; + } + if (ret >= 0) + ret = insn->n; + return ret; +} + +static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_bond_private *devpriv = dev->private; + DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS); + int i; + + memset(&devs_opened, 0, sizeof(devs_opened)); + devpriv->name[0] = 0; + /* + * Loop through all comedi devices specified on the command-line, + * building our device list. + */ + for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) { + char file[sizeof("/dev/comediXXXXXX")]; + int minor = it->options[i]; + struct comedi_device *d; + int sdev = -1, nchans; + struct bonded_device *bdev; + struct bonded_device **devs; + + if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) { + dev_err(dev->class_dev, + "Minor %d is invalid!\n", minor); + return -EINVAL; + } + if (minor == dev->minor) { + dev_err(dev->class_dev, + "Cannot bond this driver to itself!\n"); + return -EINVAL; + } + if (test_and_set_bit(minor, devs_opened)) { + dev_err(dev->class_dev, + "Minor %d specified more than once!\n", minor); + return -EINVAL; + } + + snprintf(file, sizeof(file), "/dev/comedi%d", minor); + file[sizeof(file) - 1] = 0; + + d = comedi_open(file); + + if (!d) { + dev_err(dev->class_dev, + "Minor %u could not be opened\n", minor); + return -ENODEV; + } + + /* Do DIO, as that's all we support now.. */ + while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO, + sdev + 1)) > -1) { + nchans = comedi_get_n_channels(d, sdev); + if (nchans <= 0) { + dev_err(dev->class_dev, + "comedi_get_n_channels() returned %d on minor %u subdev %d!\n", + nchans, minor, sdev); + return -EINVAL; + } + bdev = kmalloc(sizeof(*bdev), GFP_KERNEL); + if (!bdev) + return -ENOMEM; + + bdev->dev = d; + bdev->minor = minor; + bdev->subdev = sdev; + bdev->nchans = nchans; + devpriv->nchans += nchans; + + /* + * Now put bdev pointer at end of devpriv->devs array + * list.. + */ + + /* ergh.. ugly.. we need to realloc :( */ + devs = krealloc(devpriv->devs, + (devpriv->ndevs + 1) * sizeof(*devs), + GFP_KERNEL); + if (!devs) { + dev_err(dev->class_dev, + "Could not allocate memory. Out of memory?\n"); + kfree(bdev); + return -ENOMEM; + } + devpriv->devs = devs; + devpriv->devs[devpriv->ndevs++] = bdev; + { + /* Append dev:subdev to devpriv->name */ + char buf[20]; + + snprintf(buf, sizeof(buf), "%u:%u ", + bdev->minor, bdev->subdev); + strlcat(devpriv->name, buf, + sizeof(devpriv->name)); + } + } + } + + if (!devpriv->nchans) { + dev_err(dev->class_dev, "No channels found!\n"); + return -EINVAL; + } + + return 0; +} + +static int bonding_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_bond_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* + * Setup our bonding from config params.. sets up our private struct.. + */ + ret = do_dev_config(dev, it); + if (ret) + return ret; + + dev->board_name = devpriv->name; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = devpriv->nchans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = bonding_dio_insn_bits; + s->insn_config = bonding_dio_insn_config; + + dev_info(dev->class_dev, + "%s: %s attached, %u channels from %u devices\n", + dev->driver->driver_name, dev->board_name, + devpriv->nchans, devpriv->ndevs); + + return 0; +} + +static void bonding_detach(struct comedi_device *dev) +{ + struct comedi_bond_private *devpriv = dev->private; + + if (devpriv && devpriv->devs) { + DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS); + + memset(&devs_closed, 0, sizeof(devs_closed)); + while (devpriv->ndevs--) { + struct bonded_device *bdev; + + bdev = devpriv->devs[devpriv->ndevs]; + if (!bdev) + continue; + if (!test_and_set_bit(bdev->minor, devs_closed)) + comedi_close(bdev->dev); + kfree(bdev); + } + kfree(devpriv->devs); + devpriv->devs = NULL; + } +} + +static struct comedi_driver bonding_driver = { + .driver_name = "comedi_bond", + .module = THIS_MODULE, + .attach = bonding_attach, + .detach = bonding_detach, +}; +module_comedi_driver(bonding_driver); + +MODULE_AUTHOR("Calin A. Culianu"); +MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one."); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/comedi_isadma.c b/drivers/comedi/drivers/comedi_isadma.c new file mode 100644 index 000000000..020b3d1e1 --- /dev/null +++ b/drivers/comedi/drivers/comedi_isadma.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * COMEDI ISA DMA support functions + * Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com> + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/isa-dma.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_isadma.h> + +/** + * comedi_isadma_program - program and enable an ISA DMA transfer + * @desc: the ISA DMA cookie to program and enable + */ +void comedi_isadma_program(struct comedi_isadma_desc *desc) +{ + unsigned long flags; + + flags = claim_dma_lock(); + clear_dma_ff(desc->chan); + set_dma_mode(desc->chan, desc->mode); + set_dma_addr(desc->chan, desc->hw_addr); + set_dma_count(desc->chan, desc->size); + enable_dma(desc->chan); + release_dma_lock(flags); +} +EXPORT_SYMBOL_GPL(comedi_isadma_program); + +/** + * comedi_isadma_disable - disable the ISA DMA channel + * @dma_chan: the DMA channel to disable + * + * Returns the residue (remaining bytes) left in the DMA transfer. + */ +unsigned int comedi_isadma_disable(unsigned int dma_chan) +{ + unsigned long flags; + unsigned int residue; + + flags = claim_dma_lock(); + disable_dma(dma_chan); + residue = get_dma_residue(dma_chan); + release_dma_lock(flags); + + return residue; +} +EXPORT_SYMBOL_GPL(comedi_isadma_disable); + +/** + * comedi_isadma_disable_on_sample - disable the ISA DMA channel + * @dma_chan: the DMA channel to disable + * @size: the sample size (in bytes) + * + * Returns the residue (remaining bytes) left in the DMA transfer. + */ +unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan, + unsigned int size) +{ + int stalled = 0; + unsigned long flags; + unsigned int residue; + unsigned int new_residue; + + residue = comedi_isadma_disable(dma_chan); + while (residue % size) { + /* residue is a partial sample, enable DMA to allow more data */ + flags = claim_dma_lock(); + enable_dma(dma_chan); + release_dma_lock(flags); + + udelay(2); + new_residue = comedi_isadma_disable(dma_chan); + + /* is DMA stalled? */ + if (new_residue == residue) { + stalled++; + if (stalled > 10) + break; + } else { + residue = new_residue; + stalled = 0; + } + } + return residue; +} +EXPORT_SYMBOL_GPL(comedi_isadma_disable_on_sample); + +/** + * comedi_isadma_poll - poll the current DMA transfer + * @dma: the ISA DMA to poll + * + * Returns the position (in bytes) of the current DMA transfer. + */ +unsigned int comedi_isadma_poll(struct comedi_isadma *dma) +{ + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned long flags; + unsigned int result; + unsigned int result1; + + flags = claim_dma_lock(); + clear_dma_ff(desc->chan); + if (!isa_dma_bridge_buggy) + disable_dma(desc->chan); + result = get_dma_residue(desc->chan); + /* + * Read the counter again and choose higher value in order to + * avoid reading during counter lower byte roll over if the + * isa_dma_bridge_buggy is set. + */ + result1 = get_dma_residue(desc->chan); + if (!isa_dma_bridge_buggy) + enable_dma(desc->chan); + release_dma_lock(flags); + + if (result < result1) + result = result1; + if (result >= desc->size || result == 0) + return 0; + return desc->size - result; +} +EXPORT_SYMBOL_GPL(comedi_isadma_poll); + +/** + * comedi_isadma_set_mode - set the ISA DMA transfer direction + * @desc: the ISA DMA cookie to set + * @dma_dir: the DMA direction + */ +void comedi_isadma_set_mode(struct comedi_isadma_desc *desc, char dma_dir) +{ + desc->mode = (dma_dir == COMEDI_ISADMA_READ) ? DMA_MODE_READ + : DMA_MODE_WRITE; +} +EXPORT_SYMBOL_GPL(comedi_isadma_set_mode); + +/** + * comedi_isadma_alloc - allocate and initialize the ISA DMA + * @dev: comedi_device struct + * @n_desc: the number of cookies to allocate + * @dma_chan1: DMA channel for the first cookie + * @dma_chan2: DMA channel for the second cookie + * @maxsize: the size of the buffer to allocate for each cookie + * @dma_dir: the DMA direction + * + * Returns the allocated and initialized ISA DMA or NULL if anything fails. + */ +struct comedi_isadma *comedi_isadma_alloc(struct comedi_device *dev, + int n_desc, unsigned int dma_chan1, + unsigned int dma_chan2, + unsigned int maxsize, char dma_dir) +{ + struct comedi_isadma *dma = NULL; + struct comedi_isadma_desc *desc; + unsigned int dma_chans[2]; + int i; + + if (n_desc < 1 || n_desc > 2) + goto no_dma; + + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + if (!dma) + goto no_dma; + + desc = kcalloc(n_desc, sizeof(*desc), GFP_KERNEL); + if (!desc) + goto no_dma; + dma->desc = desc; + dma->n_desc = n_desc; + if (dev->hw_dev) { + dma->dev = dev->hw_dev; + } else { + /* Fall back to using the "class" device. */ + if (!dev->class_dev) + goto no_dma; + /* Need 24-bit mask for ISA DMA. */ + if (dma_coerce_mask_and_coherent(dev->class_dev, + DMA_BIT_MASK(24))) { + goto no_dma; + } + dma->dev = dev->class_dev; + } + + dma_chans[0] = dma_chan1; + if (dma_chan2 == 0 || dma_chan2 == dma_chan1) + dma_chans[1] = dma_chan1; + else + dma_chans[1] = dma_chan2; + + if (request_dma(dma_chans[0], dev->board_name)) + goto no_dma; + dma->chan = dma_chans[0]; + if (dma_chans[1] != dma_chans[0]) { + if (request_dma(dma_chans[1], dev->board_name)) + goto no_dma; + } + dma->chan2 = dma_chans[1]; + + for (i = 0; i < n_desc; i++) { + desc = &dma->desc[i]; + desc->chan = dma_chans[i]; + desc->maxsize = maxsize; + desc->virt_addr = dma_alloc_coherent(dma->dev, desc->maxsize, + &desc->hw_addr, + GFP_KERNEL); + if (!desc->virt_addr) + goto no_dma; + comedi_isadma_set_mode(desc, dma_dir); + } + + return dma; + +no_dma: + comedi_isadma_free(dma); + return NULL; +} +EXPORT_SYMBOL_GPL(comedi_isadma_alloc); + +/** + * comedi_isadma_free - free the ISA DMA + * @dma: the ISA DMA to free + */ +void comedi_isadma_free(struct comedi_isadma *dma) +{ + struct comedi_isadma_desc *desc; + int i; + + if (!dma) + return; + + if (dma->desc) { + for (i = 0; i < dma->n_desc; i++) { + desc = &dma->desc[i]; + if (desc->virt_addr) + dma_free_coherent(dma->dev, desc->maxsize, + desc->virt_addr, + desc->hw_addr); + } + kfree(dma->desc); + } + if (dma->chan2 && dma->chan2 != dma->chan) + free_dma(dma->chan2); + if (dma->chan) + free_dma(dma->chan); + kfree(dma); +} +EXPORT_SYMBOL_GPL(comedi_isadma_free); + +static int __init comedi_isadma_init(void) +{ + return 0; +} +module_init(comedi_isadma_init); + +static void __exit comedi_isadma_exit(void) +{ +} +module_exit(comedi_isadma_exit); + +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_DESCRIPTION("Comedi ISA DMA support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/comedi_parport.c b/drivers/comedi/drivers/comedi_parport.c new file mode 100644 index 000000000..098738a68 --- /dev/null +++ b/drivers/comedi/drivers/comedi_parport.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* + * Register map + */ +#define PARPORT_DATA_REG 0x00 +#define PARPORT_STATUS_REG 0x01 +#define PARPORT_CTRL_REG 0x02 +#define PARPORT_CTRL_IRQ_ENA BIT(4) +#define PARPORT_CTRL_BIDIR_ENA BIT(5) + +static int parport_data_reg_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outb(s->state, dev->iobase + PARPORT_DATA_REG); + + data[1] = inb(dev->iobase + PARPORT_DATA_REG); + + return insn->n; +} + +static int parport_data_reg_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ctrl; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0xff); + if (ret) + return ret; + + ctrl = inb(dev->iobase + PARPORT_CTRL_REG); + if (s->io_bits) + ctrl &= ~PARPORT_CTRL_BIDIR_ENA; + else + ctrl |= PARPORT_CTRL_BIDIR_ENA; + outb(ctrl, dev->iobase + PARPORT_CTRL_REG); + + return insn->n; +} + +static int parport_status_reg_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + PARPORT_STATUS_REG) >> 3; + + return insn->n; +} + +static int parport_ctrl_reg_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ctrl; + + if (comedi_dio_update_state(s, data)) { + ctrl = inb(dev->iobase + PARPORT_CTRL_REG); + ctrl &= (PARPORT_CTRL_IRQ_ENA | PARPORT_CTRL_BIDIR_ENA); + ctrl |= s->state; + outb(ctrl, dev->iobase + PARPORT_CTRL_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static int parport_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + return insn->n; +} + +static int parport_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int parport_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int ctrl; + + ctrl = inb(dev->iobase + PARPORT_CTRL_REG); + ctrl |= PARPORT_CTRL_IRQ_ENA; + outb(ctrl, dev->iobase + PARPORT_CTRL_REG); + + return 0; +} + +static int parport_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int ctrl; + + ctrl = inb(dev->iobase + PARPORT_CTRL_REG); + ctrl &= ~PARPORT_CTRL_IRQ_ENA; + outb(ctrl, dev->iobase + PARPORT_CTRL_REG); + + return 0; +} + +static irqreturn_t parport_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int ctrl; + unsigned short val = 0; + + ctrl = inb(dev->iobase + PARPORT_CTRL_REG); + if (!(ctrl & PARPORT_CTRL_IRQ_ENA)) + return IRQ_NONE; + + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int parport_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x03); + if (ret) + return ret; + + if (it->options[1]) { + ret = request_irq(it->options[1], parport_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + ret = comedi_alloc_subdevices(dev, dev->irq ? 4 : 3); + if (ret) + return ret; + + /* Digial I/O subdevice - Parallel port DATA register */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_data_reg_insn_bits; + s->insn_config = parport_data_reg_insn_config; + + /* Digial Input subdevice - Parallel port STATUS register */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 5; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_status_reg_insn_bits; + + /* Digial Output subdevice - Parallel port CONTROL register */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_ctrl_reg_insn_bits; + + if (dev->irq) { + /* Digial Input subdevice - Interrupt support */ + s = &dev->subdevices[3]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_intr_insn_bits; + s->len_chanlist = 1; + s->do_cmdtest = parport_intr_cmdtest; + s->do_cmd = parport_intr_cmd; + s->cancel = parport_intr_cancel; + } + + outb(0, dev->iobase + PARPORT_DATA_REG); + outb(0, dev->iobase + PARPORT_CTRL_REG); + + return 0; +} + +static struct comedi_driver parport_driver = { + .driver_name = "comedi_parport", + .module = THIS_MODULE, + .attach = parport_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(parport_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi: Standard parallel port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/comedi_test.c b/drivers/comedi/drivers/comedi_test.c new file mode 100644 index 000000000..0b5c0af1c --- /dev/null +++ b/drivers/comedi/drivers/comedi_test.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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. + * + * Auto-configuration is the default mode if no parameter is supplied during + * module loading. Manual configuration requires COMEDI userspace tool. + * To disable auto-configuration mode, pass "noauto=1" parameter for module + * loading. Refer modinfo or MODULE_PARM_DESC description below for details. + * + * Auto-configuration options: + * Refer modinfo or MODULE_PARM_DESC description below for details. + * + * Manual 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 <linux/comedi/comedidev.h> +#include <asm/div64.h> +#include <linux/timer.h> +#include <linux/ktime.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/kdev_t.h> + +#define N_CHANS 8 +#define DEV_NAME "comedi_testd" +#define CLASS_NAME "comedi_test" + +static bool config_mode; +static unsigned int set_amplitude; +static unsigned int set_period; +static struct class *ctcls; +static struct device *ctdev; + +module_param_named(noauto, config_mode, bool, 0444); +MODULE_PARM_DESC(noauto, "Disable auto-configuration: (1=disable [defaults to enable])"); + +module_param_named(amplitude, set_amplitude, uint, 0444); +MODULE_PARM_DESC(amplitude, "Set auto mode wave amplitude in microvolts: (defaults to 1 volt)"); + +module_param_named(period, set_period, uint, 0444); +MODULE_PARM_DESC(period, "Set auto mode wave period in microseconds: (defaults to 0.1 sec)"); + +/* Data unique to this driver */ +struct waveform_private { + struct timer_list ai_timer; /* timer for AI commands */ + u64 ai_convert_time; /* time of next AI conversion in usec */ + unsigned int wf_amplitude; /* waveform amplitude in microvolts */ + unsigned int wf_period; /* waveform period in microseconds */ + unsigned int wf_current; /* current time in waveform period */ + unsigned int ai_scan_period; /* AI scan period in usec */ + unsigned int ai_convert_period; /* AI conversion period in usec */ + struct timer_list ao_timer; /* timer for AO commands */ + struct comedi_device *dev; /* parent comedi device */ + u64 ao_last_scan_time; /* time of previous AO scan in usec */ + unsigned int ao_scan_period; /* AO scan period in usec */ + unsigned short ao_loopbacks[N_CHANS]; +}; + +/* 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 int 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->wf_amplitude; + do_div(binary_amplitude, krange->max - krange->min); + + value = current_time; + value *= binary_amplitude * 2; + do_div(value, devpriv->wf_period); + value += offset; + /* get rid of sawtooth's dc offset and clamp value */ + if (value < binary_amplitude) { + value = 0; /* negative saturation */ + } else { + value -= binary_amplitude; + if (value > s->maxdata) + value = s->maxdata; /* positive saturation */ + } + + return value; +} + +static unsigned short fake_squarewave(struct comedi_device *dev, + unsigned int range_index, + unsigned int 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]; + + value = s->maxdata; + value *= devpriv->wf_amplitude; + do_div(value, krange->max - krange->min); + + /* get one of two values for square-wave and clamp */ + if (current_time < devpriv->wf_period / 2) { + if (offset < value) + value = 0; /* negative saturation */ + else + value = offset - value; + } else { + value += offset; + if (value > s->maxdata) + value = s->maxdata; /* positive saturation */ + } + + return value; +} + +static unsigned short fake_flatline(struct comedi_device *dev, + unsigned int range_index, + unsigned int 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 int current_time) +{ + enum { + SAWTOOTH_CHAN, + SQUARE_CHAN, + }; + switch (channel) { + case SAWTOOTH_CHAN: + return fake_sawtooth(dev, range, current_time); + case SQUARE_CHAN: + return fake_squarewave(dev, range, current_time); + default: + break; + } + + return fake_flatline(dev, range, current_time); +} + +/* + * This is the background routine used to generate arbitrary data. + * It should run in the background; therefore it is scheduled by + * a timer mechanism. + */ +static void waveform_ai_timer(struct timer_list *t) +{ + struct waveform_private *devpriv = from_timer(devpriv, t, ai_timer); + struct comedi_device *dev = devpriv->dev; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u64 now; + unsigned int nsamples; + unsigned int time_increment; + + now = ktime_to_us(ktime_get()); + nsamples = comedi_nsamples_left(s, UINT_MAX); + + while (nsamples && devpriv->ai_convert_time < now) { + unsigned int chanspec = cmd->chanlist[async->cur_chan]; + unsigned short sample; + + sample = fake_waveform(dev, CR_CHAN(chanspec), + CR_RANGE(chanspec), devpriv->wf_current); + if (comedi_buf_write_samples(s, &sample, 1) == 0) + goto overrun; + time_increment = devpriv->ai_convert_period; + if (async->scan_progress == 0) { + /* done last conversion in scan, so add dead time */ + time_increment += devpriv->ai_scan_period - + devpriv->ai_convert_period * + cmd->scan_end_arg; + } + devpriv->wf_current += time_increment; + if (devpriv->wf_current >= devpriv->wf_period) + devpriv->wf_current %= devpriv->wf_period; + devpriv->ai_convert_time += time_increment; + nsamples--; + } + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + } else { + if (devpriv->ai_convert_time > now) + time_increment = devpriv->ai_convert_time - now; + else + time_increment = 1; + mod_timer(&devpriv->ai_timer, + jiffies + usecs_to_jiffies(time_increment)); + } + +overrun: + comedi_handle_events(dev, s); +} + +static int waveform_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg, limit; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_NOW | TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err |= -EINVAL; /* scan period would be 0 */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_NOW) { + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + } else { /* cmd->convert_src == TRIG_TIMER */ + if (cmd->scan_begin_src == TRIG_FOLLOW) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + NSEC_PER_USEC); + } + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } else { /* cmd->scan_begin_src == TRIG_TIMER */ + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + NSEC_PER_USEC); + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* cmd->stop_src == TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + /* round convert_arg to nearest microsecond */ + arg = cmd->convert_arg; + arg = min(arg, + rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); + arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); + if (cmd->scan_begin_arg == TRIG_TIMER) { + /* limit convert_arg to keep scan_begin_arg in range */ + limit = UINT_MAX / cmd->scan_end_arg; + limit = rounddown(limit, (unsigned int)NSEC_PER_SEC); + arg = min(arg, limit); + } + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* round scan_begin_arg to nearest microsecond */ + arg = cmd->scan_begin_arg; + arg = min(arg, + rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); + arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); + if (cmd->convert_src == TRIG_TIMER) { + /* but ensure scan_begin_arg is large enough */ + arg = max(arg, cmd->convert_arg * cmd->scan_end_arg); + } + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_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; + unsigned int first_convert_time; + u64 wf_current; + + if (cmd->flags & CMDF_PRIORITY) { + dev_err(dev->class_dev, + "commands at RT priority not supported in this driver\n"); + return -1; + } + + if (cmd->convert_src == TRIG_NOW) + devpriv->ai_convert_period = 0; + else /* cmd->convert_src == TRIG_TIMER */ + devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC; + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + devpriv->ai_scan_period = devpriv->ai_convert_period * + cmd->scan_end_arg; + } else { /* cmd->scan_begin_src == TRIG_TIMER */ + devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; + } + + /* + * Simulate first conversion to occur at convert period after + * conversion timer starts. If scan_begin_src is TRIG_FOLLOW, assume + * the conversion timer starts immediately. If scan_begin_src is + * TRIG_TIMER, assume the conversion timer starts after the scan + * period. + */ + first_convert_time = devpriv->ai_convert_period; + if (cmd->scan_begin_src == TRIG_TIMER) + first_convert_time += devpriv->ai_scan_period; + devpriv->ai_convert_time = ktime_to_us(ktime_get()) + + first_convert_time; + + /* Determine time within waveform period at time of conversion. */ + wf_current = devpriv->ai_convert_time; + devpriv->wf_current = do_div(wf_current, devpriv->wf_period); + + /* + * Schedule timer to expire just after first conversion time. + * Seem to need an extra jiffy here, otherwise timer expires slightly + * early! + */ + devpriv->ai_timer.expires = + jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1; + add_timer(&devpriv->ai_timer); + return 0; +} + +static int waveform_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct waveform_private *devpriv = dev->private; + + if (in_softirq()) { + /* Assume we were called from the timer routine itself. */ + del_timer(&devpriv->ai_timer); + } else { + del_timer_sync(&devpriv->ai_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; +} + +/* + * This is the background routine to handle AO commands, scheduled by + * a timer mechanism. + */ +static void waveform_ao_timer(struct timer_list *t) +{ + struct waveform_private *devpriv = from_timer(devpriv, t, ao_timer); + struct comedi_device *dev = devpriv->dev; + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u64 now; + u64 scans_since; + unsigned int scans_avail = 0; + + /* determine number of scan periods since last time */ + now = ktime_to_us(ktime_get()); + scans_since = now - devpriv->ao_last_scan_time; + do_div(scans_since, devpriv->ao_scan_period); + if (scans_since) { + unsigned int i; + + /* determine scans in buffer, limit to scans to do this time */ + scans_avail = comedi_nscans_left(s, 0); + if (scans_avail > scans_since) + scans_avail = scans_since; + if (scans_avail) { + /* skip all but the last scan to save processing time */ + if (scans_avail > 1) { + unsigned int skip_bytes, nbytes; + + skip_bytes = + comedi_samples_to_bytes(s, cmd->scan_end_arg * + (scans_avail - 1)); + nbytes = comedi_buf_read_alloc(s, skip_bytes); + comedi_buf_read_free(s, nbytes); + comedi_inc_scan_progress(s, nbytes); + if (nbytes < skip_bytes) { + /* unexpected underrun! (cancelled?) */ + async->events |= COMEDI_CB_OVERFLOW; + goto underrun; + } + } + /* output the last scan */ + for (i = 0; i < cmd->scan_end_arg; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned short *pd; + + pd = &devpriv->ao_loopbacks[chan]; + + if (!comedi_buf_read_samples(s, pd, 1)) { + /* unexpected underrun! (cancelled?) */ + async->events |= COMEDI_CB_OVERFLOW; + goto underrun; + } + } + /* advance time of last scan */ + devpriv->ao_last_scan_time += + (u64)scans_avail * devpriv->ao_scan_period; + } + } + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + } else if (scans_avail < scans_since) { + async->events |= COMEDI_CB_OVERFLOW; + } else { + unsigned int time_inc = devpriv->ao_last_scan_time + + devpriv->ao_scan_period - now; + + mod_timer(&devpriv->ao_timer, + jiffies + usecs_to_jiffies(time_inc)); + } + +underrun: + comedi_handle_events(dev, s); +} + +static int waveform_ao_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct waveform_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + async->inttrig = NULL; + + devpriv->ao_last_scan_time = ktime_to_us(ktime_get()); + devpriv->ao_timer.expires = + jiffies + usecs_to_jiffies(devpriv->ao_scan_period); + add_timer(&devpriv->ao_timer); + + return 1; +} + +static int waveform_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + NSEC_PER_USEC); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* cmd->stop_src == TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* round scan_begin_arg to nearest microsecond */ + arg = cmd->scan_begin_arg; + arg = min(arg, rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); + arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + + if (err) + return 4; + + return 0; +} + +static int waveform_ao_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct waveform_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + if (cmd->flags & CMDF_PRIORITY) { + dev_err(dev->class_dev, + "commands at RT priority not supported in this driver\n"); + return -1; + } + + devpriv->ao_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; + s->async->inttrig = waveform_ao_inttrig_start; + return 0; +} + +static int waveform_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct waveform_private *devpriv = dev->private; + + s->async->inttrig = NULL; + if (in_softirq()) { + /* Assume we were called from the timer routine itself. */ + del_timer(&devpriv->ao_timer); + } else { + del_timer_sync(&devpriv->ao_timer); + } + return 0; +} + +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_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { + /* + * input: data[1], data[2] : scan_begin_src, convert_src + * output: data[1], data[2] : scan_begin_min, convert_min + */ + if (data[1] == TRIG_FOLLOW) { + /* exactly TRIG_FOLLOW case */ + data[1] = 0; + data[2] = NSEC_PER_USEC; + } else { + data[1] = NSEC_PER_USEC; + if (data[2] & TRIG_TIMER) + data[2] = NSEC_PER_USEC; + else + data[2] = 0; + } + return 0; + } + + return -EINVAL; +} + +static int waveform_ao_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { + /* we don't care about actual channels */ + data[1] = NSEC_PER_USEC; /* scan_begin_min */ + data[2] = 0; /* convert_min */ + return 0; + } + + return -EINVAL; +} + +static int waveform_common_attach(struct comedi_device *dev, + int amplitude, int period) +{ + struct waveform_private *devpriv; + struct comedi_subdevice *s; + int i; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->wf_amplitude = amplitude; + devpriv->wf_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->insn_config = waveform_ai_insn_config; + + s = &dev->subdevices[1]; + dev->write_subdev = s; + /* analog output subdevice (loopback) */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = N_CHANS; + s->maxdata = 0xffff; + s->range_table = &waveform_ai_ranges; + s->len_chanlist = s->n_chan; + s->insn_write = waveform_ao_insn_write; + s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */ + s->do_cmd = waveform_ao_cmd; + s->do_cmdtest = waveform_ao_cmdtest; + s->cancel = waveform_ao_cancel; + s->insn_config = waveform_ao_insn_config; + + /* Our default loopback value is just a 0V flatline */ + for (i = 0; i < s->n_chan; i++) + devpriv->ao_loopbacks[i] = s->maxdata / 2; + + devpriv->dev = dev; + timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0); + timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0); + + dev_info(dev->class_dev, + "%s: %u microvolt, %u microsecond waveform attached\n", + dev->board_name, + devpriv->wf_amplitude, devpriv->wf_period); + + return 0; +} + +static int waveform_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int amplitude = it->options[0]; + int period = it->options[1]; + + /* set default amplitude and period */ + if (amplitude <= 0) + amplitude = 1000000; /* 1 volt */ + if (period <= 0) + period = 100000; /* 0.1 sec */ + + return waveform_common_attach(dev, amplitude, period); +} + +static int waveform_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + int amplitude = set_amplitude; + int period = set_period; + + /* set default amplitude and period */ + if (!amplitude) + amplitude = 1000000; /* 1 volt */ + if (!period) + period = 100000; /* 0.1 sec */ + + return waveform_common_attach(dev, amplitude, period); +} + +static void waveform_detach(struct comedi_device *dev) +{ + struct waveform_private *devpriv = dev->private; + + if (devpriv) { + del_timer_sync(&devpriv->ai_timer); + del_timer_sync(&devpriv->ao_timer); + } +} + +static struct comedi_driver waveform_driver = { + .driver_name = "comedi_test", + .module = THIS_MODULE, + .attach = waveform_attach, + .auto_attach = waveform_auto_attach, + .detach = waveform_detach, +}; + +/* + * For auto-configuration, a device is created to stand in for a + * real hardware device. + */ +static int __init comedi_test_init(void) +{ + int ret; + + ret = comedi_driver_register(&waveform_driver); + if (ret) { + pr_err("comedi_test: unable to register driver\n"); + return ret; + } + + if (!config_mode) { + ctcls = class_create(THIS_MODULE, CLASS_NAME); + if (IS_ERR(ctcls)) { + pr_warn("comedi_test: unable to create class\n"); + goto clean3; + } + + ctdev = device_create(ctcls, NULL, MKDEV(0, 0), NULL, DEV_NAME); + if (IS_ERR(ctdev)) { + pr_warn("comedi_test: unable to create device\n"); + goto clean2; + } + + ret = comedi_auto_config(ctdev, &waveform_driver, 0); + if (ret) { + pr_warn("comedi_test: unable to auto-configure device\n"); + goto clean; + } + } + + return 0; + +clean: + device_destroy(ctcls, MKDEV(0, 0)); +clean2: + class_destroy(ctcls); + ctdev = NULL; +clean3: + ctcls = NULL; + + return 0; +} +module_init(comedi_test_init); + +static void __exit comedi_test_exit(void) +{ + if (ctdev) + comedi_auto_unconfig(ctdev); + + if (ctcls) { + device_destroy(ctcls, MKDEV(0, 0)); + class_destroy(ctcls); + } + + comedi_driver_unregister(&waveform_driver); +} +module_exit(comedi_test_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/contec_pci_dio.c b/drivers/comedi/drivers/contec_pci_dio.c new file mode 100644 index 000000000..41d42ff14 --- /dev/null +++ b/drivers/comedi/drivers/contec_pci_dio.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/contec_pci_dio.c + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * 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/comedi/comedi_pci.h> + +/* + * Register map + */ +#define PIO1616L_DI_REG 0x00 +#define PIO1616L_DO_REG 0x02 + +static int contec_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + PIO1616L_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int contec_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = inw(dev->iobase + PIO1616L_DI_REG); + + return insn->n; +} + +static int contec_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 0); + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = contec_di_insn_bits; + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = contec_do_insn_bits; + + return 0; +} + +static struct comedi_driver contec_pci_dio_driver = { + .driver_name = "contec_pci_dio", + .module = THIS_MODULE, + .auto_attach = contec_auto_attach, + .detach = comedi_pci_detach, +}; + +static int contec_pci_dio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &contec_pci_dio_driver, + id->driver_data); +} + +static const struct pci_device_id contec_pci_dio_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CONTEC, 0x8172) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, contec_pci_dio_pci_table); + +static struct pci_driver contec_pci_dio_pci_driver = { + .name = "contec_pci_dio", + .id_table = contec_pci_dio_pci_table, + .probe = contec_pci_dio_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(contec_pci_dio_driver, contec_pci_dio_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dac02.c b/drivers/comedi/drivers/dac02.c new file mode 100644 index 000000000..4b011d66d --- /dev/null +++ b/drivers/comedi/drivers/dac02.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* + * The output range is selected by jumpering pins on the I/O connector. + * + * Range Chan # Jumper pins Output + * ------------- ------ ------------- ----------------- + * 0 to 5V 0 21 to 22 24 + * 1 15 to 16 18 + * 0 to 10V 0 20 to 22 24 + * 1 14 to 16 18 + * +/-5V 0 21 to 22 23 + * 1 15 to 16 17 + * +/-10V 0 20 to 22 23 + * 1 14 to 16 17 + * 4 to 20mA 0 21 to 22 25 + * 1 15 to 16 19 + * AC reference 0 In on pin 22 24 (2-quadrant) + * In on pin 22 23 (4-quadrant) + * 1 In on pin 16 18 (2-quadrant) + * In on pin 16 17 (4-quadrant) + */ +static const struct comedi_lrange das02_ao_ranges = { + 6, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10), + RANGE_mA(4, 20), + RANGE_ext(0, 1) + } +}; + +/* + * Register I/O map + */ +#define DAC02_AO_LSB(x) (0x00 + ((x) * 2)) +#define DAC02_AO_MSB(x) (0x01 + ((x) * 2)) + +static int dac02_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + s->readback[chan] = val; + + /* + * Unipolar outputs are true binary encoding. + * Bipolar outputs are complementary offset binary + * (that is, 0 = +full scale, maxdata = -full scale). + */ + if (comedi_range_is_bipolar(s, range)) + val = s->maxdata - val; + + /* + * DACs are double-buffered. + * Write LSB then MSB to latch output. + */ + outb((val << 4) & 0xf0, dev->iobase + DAC02_AO_LSB(chan)); + outb((val >> 4) & 0xff, dev->iobase + DAC02_AO_MSB(chan)); + } + + return insn->n; +} + +static int dac02_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x08); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + /* Analog Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &das02_ao_ranges; + s->insn_write = dac02_ao_insn_write; + + return comedi_alloc_subdev_readback(s); +} + +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/comedi/drivers/daqboard2000.c b/drivers/comedi/drivers/daqboard2000.c new file mode 100644 index 000000000..c0a4e1b06 --- /dev/null +++ b/drivers/comedi/drivers/daqboard2000.c @@ -0,0 +1,786 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * 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 firmware, which is available from + * https://www.comedi.org in the comedi_nonfree_firmware tarball. + * + * Configuration options: not applicable, uses PCI auto config + */ +/* + * This card was obviously never intended to leave the Windows world, + * since it lacked all kind of hardware documentation (except for cable + * pinouts, plug and pray has something to catch up with yet). + * + * With some help from our swedish distributor, we got the Windows sourcecode + * for the card, and here are the findings so far. + * + * 1. A good document that describes the PCI interface chip is 9080db-106.pdf + * available from http://www.plxtech.com/products/io/pci9080 + * + * 2. The initialization done so far is: + * a. program the FPGA (windows code sans a lot of error messages) + * b. + * + * 3. Analog out seems to work OK with DAC's disabled, if DAC's are enabled, + * you have to output values to all enabled DAC's until result appears, I + * guess that it has something to do with pacer clocks, but the source + * gives me no clues. I'll keep it simple so far. + * + * 4. Analog in. + * Each channel in the scanlist seems to be controlled by four + * control words: + * + * Word0: + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! | | | ! | | | ! | | | ! | | | ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Word1: + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! | | | ! | | | ! | | | ! | | | ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | | | | | | + * +------+------+ | | | | +-- Digital input (??) + * | | | | +---- 10 us settling time + * | | | +------ Suspend acquisition (last to scan) + * | | +-------- Simultaneous sample and hold + * | +---------- Signed data format + * +------------------------- Correction offset low + * + * Word2: + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! | | | ! | | | ! | | | ! | | | ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | | | | | | | | | + * +-----+ +--+--+ +++ +++ +--+--+ + * | | | | +----- Expansion channel + * | | | +----------- Expansion gain + * | | +--------------- Channel (low) + * | +--------------------- Correction offset high + * +----------------------------- Correction gain low + * Word3: + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ! | | | ! | | | ! | | | ! | | | ! + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | | | | | | | | + * +------+------+ | | +-+-+ | | +-- Low bank enable + * | | | | | +---- High bank enable + * | | | | +------ Hi/low select + * | | | +---------- Gain (1,?,2,4,8,16,32,64) + * | | +-------------- differential/single ended + * | +---------------- Unipolar + * +------------------------- Correction gain high + * + * 999. The card seems to have an incredible amount of capabilities, but + * trying to reverse engineer them from the Windows source is beyond my + * patience. + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8255.h> + +#include "plx9080.h" + +#define DB2K_FIRMWARE "daqboard2000_firmware.bin" + +static const struct comedi_lrange db2k_ai_range = { + 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 DB2K_REG_ACQ_CONTROL 0x00 /* u16 (w) */ +#define DB2K_REG_ACQ_STATUS 0x00 /* u16 (r) */ +#define DB2K_REG_ACQ_SCAN_LIST_FIFO 0x02 /* u16 */ +#define DB2K_REG_ACQ_PACER_CLOCK_DIV_LOW 0x04 /* u32 */ +#define DB2K_REG_ACQ_SCAN_COUNTER 0x08 /* u16 */ +#define DB2K_REG_ACQ_PACER_CLOCK_DIV_HIGH 0x0a /* u16 */ +#define DB2K_REG_ACQ_TRIGGER_COUNT 0x0c /* u16 */ +#define DB2K_REG_ACQ_RESULTS_FIFO 0x10 /* u16 */ +#define DB2K_REG_ACQ_RESULTS_SHADOW 0x14 /* u16 */ +#define DB2K_REG_ACQ_ADC_RESULT 0x18 /* u16 */ +#define DB2K_REG_DAC_SCAN_COUNTER 0x1c /* u16 */ +#define DB2K_REG_DAC_CONTROL 0x20 /* u16 (w) */ +#define DB2K_REG_DAC_STATUS 0x20 /* u16 (r) */ +#define DB2K_REG_DAC_FIFO 0x24 /* s16 */ +#define DB2K_REG_DAC_PACER_CLOCK_DIV 0x2a /* u16 */ +#define DB2K_REG_REF_DACS 0x2c /* u16 */ +#define DB2K_REG_DIO_CONTROL 0x30 /* u16 */ +#define DB2K_REG_P3_HSIO_DATA 0x32 /* s16 */ +#define DB2K_REG_P3_CONTROL 0x34 /* u16 */ +#define DB2K_REG_CAL_EEPROM_CONTROL 0x36 /* u16 */ +#define DB2K_REG_DAC_SETTING(x) (0x38 + (x) * 2) /* s16 */ +#define DB2K_REG_DIO_P2_EXP_IO_8_BIT 0x40 /* s16 */ +#define DB2K_REG_COUNTER_TIMER_CONTROL 0x80 /* u16 */ +#define DB2K_REG_COUNTER_INPUT(x) (0x88 + (x) * 2) /* s16 */ +#define DB2K_REG_TIMER_DIV(x) (0xa0 + (x) * 2) /* u16 */ +#define DB2K_REG_DMA_CONTROL 0xb0 /* u16 */ +#define DB2K_REG_TRIG_CONTROL 0xb2 /* u16 */ +#define DB2K_REG_CAL_EEPROM 0xb8 /* u16 */ +#define DB2K_REG_ACQ_DIGITAL_MARK 0xba /* u16 */ +#define DB2K_REG_TRIG_DACS 0xbc /* u16 */ +#define DB2K_REG_DIO_P2_EXP_IO_16_BIT(x) (0xc0 + (x) * 2) /* s16 */ + +/* CPLD registers */ +#define DB2K_REG_CPLD_STATUS 0x1000 /* u16 (r) */ +#define DB2K_REG_CPLD_WDATA 0x1000 /* u16 (w) */ + +/* Scan Sequencer programming */ +#define DB2K_ACQ_CONTROL_SEQ_START_SCAN_LIST 0x0011 +#define DB2K_ACQ_CONTROL_SEQ_STOP_SCAN_LIST 0x0010 + +/* Prepare for acquisition */ +#define DB2K_ACQ_CONTROL_RESET_SCAN_LIST_FIFO 0x0004 +#define DB2K_ACQ_CONTROL_RESET_RESULTS_FIFO 0x0002 +#define DB2K_ACQ_CONTROL_RESET_CONFIG_PIPE 0x0001 + +/* Pacer Clock Control */ +#define DB2K_ACQ_CONTROL_ADC_PACER_INTERNAL 0x0030 +#define DB2K_ACQ_CONTROL_ADC_PACER_EXTERNAL 0x0032 +#define DB2K_ACQ_CONTROL_ADC_PACER_ENABLE 0x0031 +#define DB2K_ACQ_CONTROL_ADC_PACER_ENABLE_DAC_PACER 0x0034 +#define DB2K_ACQ_CONTROL_ADC_PACER_DISABLE 0x0030 +#define DB2K_ACQ_CONTROL_ADC_PACER_NORMAL_MODE 0x0060 +#define DB2K_ACQ_CONTROL_ADC_PACER_COMPATIBILITY_MODE 0x0061 +#define DB2K_ACQ_CONTROL_ADC_PACER_INTERNAL_OUT_ENABLE 0x0008 +#define DB2K_ACQ_CONTROL_ADC_PACER_EXTERNAL_RISING 0x0100 + +/* Acquisition status bits */ +#define DB2K_ACQ_STATUS_RESULTS_FIFO_MORE_1_SAMPLE 0x0001 +#define DB2K_ACQ_STATUS_RESULTS_FIFO_HAS_DATA 0x0002 +#define DB2K_ACQ_STATUS_RESULTS_FIFO_OVERRUN 0x0004 +#define DB2K_ACQ_STATUS_LOGIC_SCANNING 0x0008 +#define DB2K_ACQ_STATUS_CONFIG_PIPE_FULL 0x0010 +#define DB2K_ACQ_STATUS_SCAN_LIST_FIFO_EMPTY 0x0020 +#define DB2K_ACQ_STATUS_ADC_NOT_READY 0x0040 +#define DB2K_ACQ_STATUS_ARBITRATION_FAILURE 0x0080 +#define DB2K_ACQ_STATUS_ADC_PACER_OVERRUN 0x0100 +#define DB2K_ACQ_STATUS_DAC_PACER_OVERRUN 0x0200 + +/* DAC status */ +#define DB2K_DAC_STATUS_DAC_FULL 0x0001 +#define DB2K_DAC_STATUS_REF_BUSY 0x0002 +#define DB2K_DAC_STATUS_TRIG_BUSY 0x0004 +#define DB2K_DAC_STATUS_CAL_BUSY 0x0008 +#define DB2K_DAC_STATUS_DAC_BUSY(x) (0x0010 << (x)) + +/* DAC control */ +#define DB2K_DAC_CONTROL_ENABLE_BIT 0x0001 +#define DB2K_DAC_CONTROL_DATA_IS_SIGNED 0x0002 +#define DB2K_DAC_CONTROL_RESET_FIFO 0x0004 +#define DB2K_DAC_CONTROL_DAC_DISABLE(x) (0x0020 + ((x) << 4)) +#define DB2K_DAC_CONTROL_DAC_ENABLE(x) (0x0021 + ((x) << 4)) +#define DB2K_DAC_CONTROL_PATTERN_DISABLE 0x0060 +#define DB2K_DAC_CONTROL_PATTERN_ENABLE 0x0061 + +/* Trigger Control */ +#define DB2K_TRIG_CONTROL_TYPE_ANALOG 0x0000 +#define DB2K_TRIG_CONTROL_TYPE_TTL 0x0010 +#define DB2K_TRIG_CONTROL_EDGE_HI_LO 0x0004 +#define DB2K_TRIG_CONTROL_EDGE_LO_HI 0x0000 +#define DB2K_TRIG_CONTROL_LEVEL_ABOVE 0x0000 +#define DB2K_TRIG_CONTROL_LEVEL_BELOW 0x0004 +#define DB2K_TRIG_CONTROL_SENSE_LEVEL 0x0002 +#define DB2K_TRIG_CONTROL_SENSE_EDGE 0x0000 +#define DB2K_TRIG_CONTROL_ENABLE 0x0001 +#define DB2K_TRIG_CONTROL_DISABLE 0x0000 + +/* Reference Dac Selection */ +#define DB2K_REF_DACS_SET 0x0080 +#define DB2K_REF_DACS_SELECT_POS_REF 0x0100 +#define DB2K_REF_DACS_SELECT_NEG_REF 0x0000 + +/* CPLD status bits */ +#define DB2K_CPLD_STATUS_INIT 0x0002 +#define DB2K_CPLD_STATUS_TXREADY 0x0004 +#define DB2K_CPLD_VERSION_MASK 0xf000 +/* "New CPLD" signature. */ +#define DB2K_CPLD_VERSION_NEW 0x5000 + +enum db2k_boardid { + BOARD_DAQBOARD2000, + BOARD_DAQBOARD2001 +}; + +struct db2k_boardtype { + const char *name; + unsigned int has_2_ao:1;/* false: 4 AO chans; true: 2 AO chans */ +}; + +static const struct db2k_boardtype db2k_boardtypes[] = { + [BOARD_DAQBOARD2000] = { + .name = "daqboard2000", + .has_2_ao = true, + }, + [BOARD_DAQBOARD2001] = { + .name = "daqboard2001", + }, +}; + +struct db2k_private { + void __iomem *plx; +}; + +static void db2k_write_acq_scan_list_entry(struct comedi_device *dev, u16 entry) +{ + writew(entry & 0x00ff, dev->mmio + DB2K_REG_ACQ_SCAN_LIST_FIFO); + writew((entry >> 8) & 0x00ff, + dev->mmio + DB2K_REG_ACQ_SCAN_LIST_FIFO); +} + +static void db2k_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; + } + /* These should be read from EEPROM */ + word2 |= 0x0800; /* offset */ + word3 |= 0xc000; /* gain */ + db2k_write_acq_scan_list_entry(dev, word0); + db2k_write_acq_scan_list_entry(dev, word1); + db2k_write_acq_scan_list_entry(dev, word2); + db2k_write_acq_scan_list_entry(dev, word3); +} + +static int db2k_ai_status(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned long context) +{ + unsigned int status; + + status = readw(dev->mmio + DB2K_REG_ACQ_STATUS); + if (status & context) + return 0; + return -EBUSY; +} + +static int db2k_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int gain, chan; + int ret; + int i; + + writew(DB2K_ACQ_CONTROL_RESET_SCAN_LIST_FIFO | + DB2K_ACQ_CONTROL_RESET_RESULTS_FIFO | + DB2K_ACQ_CONTROL_RESET_CONFIG_PIPE, + dev->mmio + DB2K_REG_ACQ_CONTROL); + + /* + * If pacer clock is not set to some high value (> 10 us), we + * risk multiple samples to be put into the result FIFO. + */ + /* 1 second, should be long enough */ + writel(1000000, dev->mmio + DB2K_REG_ACQ_PACER_CLOCK_DIV_LOW); + writew(0, dev->mmio + DB2K_REG_ACQ_PACER_CLOCK_DIV_HIGH); + + 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++) { + db2k_setup_sampling(dev, chan, gain); + /* Enable reading from the scanlist FIFO */ + writew(DB2K_ACQ_CONTROL_SEQ_START_SCAN_LIST, + dev->mmio + DB2K_REG_ACQ_CONTROL); + + ret = comedi_timeout(dev, s, insn, db2k_ai_status, + DB2K_ACQ_STATUS_CONFIG_PIPE_FULL); + if (ret) + return ret; + + writew(DB2K_ACQ_CONTROL_ADC_PACER_ENABLE, + dev->mmio + DB2K_REG_ACQ_CONTROL); + + ret = comedi_timeout(dev, s, insn, db2k_ai_status, + DB2K_ACQ_STATUS_LOGIC_SCANNING); + if (ret) + return ret; + + ret = + comedi_timeout(dev, s, insn, db2k_ai_status, + DB2K_ACQ_STATUS_RESULTS_FIFO_HAS_DATA); + if (ret) + return ret; + + data[i] = readw(dev->mmio + DB2K_REG_ACQ_RESULTS_FIFO); + writew(DB2K_ACQ_CONTROL_ADC_PACER_DISABLE, + dev->mmio + DB2K_REG_ACQ_CONTROL); + writew(DB2K_ACQ_CONTROL_SEQ_STOP_SCAN_LIST, + dev->mmio + DB2K_REG_ACQ_CONTROL); + } + + return i; +} + +static int db2k_ao_eoc(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned long context) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int status; + + status = readw(dev->mmio + DB2K_REG_DAC_STATUS); + if ((status & DB2K_DAC_STATUS_DAC_BUSY(chan)) == 0) + return 0; + return -EBUSY; +} + +static int db2k_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + int ret; + + writew(val, dev->mmio + DB2K_REG_DAC_SETTING(chan)); + + ret = comedi_timeout(dev, s, insn, db2k_ao_eoc, 0); + if (ret) + return ret; + + s->readback[chan] = val; + } + + return insn->n; +} + +static void db2k_reset_local_bus(struct comedi_device *dev) +{ + struct db2k_private *devpriv = dev->private; + u32 cntrl; + + cntrl = readl(devpriv->plx + PLX_REG_CNTRL); + cntrl |= PLX_CNTRL_RESET; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); + cntrl &= ~PLX_CNTRL_RESET; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); +} + +static void db2k_reload_plx(struct comedi_device *dev) +{ + struct db2k_private *devpriv = dev->private; + u32 cntrl; + + cntrl = readl(devpriv->plx + PLX_REG_CNTRL); + cntrl &= ~PLX_CNTRL_EERELOAD; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); + cntrl |= PLX_CNTRL_EERELOAD; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); + cntrl &= ~PLX_CNTRL_EERELOAD; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); +} + +static void db2k_pulse_prog_pin(struct comedi_device *dev) +{ + struct db2k_private *devpriv = dev->private; + u32 cntrl; + + cntrl = readl(devpriv->plx + PLX_REG_CNTRL); + cntrl |= PLX_CNTRL_USERO; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); + cntrl &= ~PLX_CNTRL_USERO; + writel(cntrl, devpriv->plx + PLX_REG_CNTRL); + mdelay(10); /* Not in the original code, but I like symmetry... */ +} + +static int db2k_wait_cpld_init(struct comedi_device *dev) +{ + int result = -ETIMEDOUT; + int i; + u16 cpld; + + /* timeout after 50 tries -> 5ms */ + for (i = 0; i < 50; i++) { + cpld = readw(dev->mmio + DB2K_REG_CPLD_STATUS); + if (cpld & DB2K_CPLD_STATUS_INIT) { + result = 0; + break; + } + usleep_range(100, 1000); + } + udelay(5); + return result; +} + +static int db2k_wait_cpld_txready(struct comedi_device *dev) +{ + int i; + + for (i = 0; i < 100; i++) { + if (readw(dev->mmio + DB2K_REG_CPLD_STATUS) & + DB2K_CPLD_STATUS_TXREADY) { + return 0; + } + udelay(1); + } + return -ETIMEDOUT; +} + +static int db2k_write_cpld(struct comedi_device *dev, u16 data, bool new_cpld) +{ + int result = 0; + + if (new_cpld) { + result = db2k_wait_cpld_txready(dev); + if (result) + return result; + } else { + usleep_range(10, 20); + } + writew(data, dev->mmio + DB2K_REG_CPLD_WDATA); + if (!(readw(dev->mmio + DB2K_REG_CPLD_STATUS) & DB2K_CPLD_STATUS_INIT)) + result = -EIO; + + return result; +} + +static int db2k_wait_fpga_programmed(struct comedi_device *dev) +{ + struct db2k_private *devpriv = dev->private; + int i; + + /* Time out after 200 tries -> 20ms */ + for (i = 0; i < 200; i++) { + u32 cntrl = readl(devpriv->plx + PLX_REG_CNTRL); + /* General Purpose Input (USERI) set on FPGA "DONE". */ + if (cntrl & PLX_CNTRL_USERI) + return 0; + + usleep_range(100, 1000); + } + return -ETIMEDOUT; +} + +static int db2k_load_firmware(struct comedi_device *dev, const u8 *cpld_array, + size_t len, unsigned long context) +{ + struct db2k_private *devpriv = dev->private; + int result = -EIO; + u32 cntrl; + int retry; + size_t i; + bool new_cpld; + + /* Look for FPGA start sequence in firmware. */ + for (i = 0; i + 1 < len; i++) { + if (cpld_array[i] == 0xff && cpld_array[i + 1] == 0x20) + break; + } + if (i + 1 >= len) { + dev_err(dev->class_dev, "bad firmware - no start sequence\n"); + return -EINVAL; + } + /* Check length is even. */ + if ((len - i) & 1) { + dev_err(dev->class_dev, + "bad firmware - odd length (%zu = %zu - %zu)\n", + len - i, len, i); + return -EINVAL; + } + /* Strip firmware header. */ + cpld_array += i; + len -= i; + + /* Check to make sure the serial eeprom is present on the board */ + cntrl = readl(devpriv->plx + PLX_REG_CNTRL); + if (!(cntrl & PLX_CNTRL_EEPRESENT)) + return -EIO; + + for (retry = 0; retry < 3; retry++) { + db2k_reset_local_bus(dev); + db2k_reload_plx(dev); + db2k_pulse_prog_pin(dev); + result = db2k_wait_cpld_init(dev); + if (result) + continue; + + new_cpld = (readw(dev->mmio + DB2K_REG_CPLD_STATUS) & + DB2K_CPLD_VERSION_MASK) == DB2K_CPLD_VERSION_NEW; + for (; i < len; i += 2) { + u16 data = (cpld_array[i] << 8) + cpld_array[i + 1]; + + result = db2k_write_cpld(dev, data, new_cpld); + if (result) + break; + } + if (result == 0) + result = db2k_wait_fpga_programmed(dev); + if (result == 0) { + db2k_reset_local_bus(dev); + db2k_reload_plx(dev); + break; + } + } + return result; +} + +static void db2k_adc_stop_dma_transfer(struct comedi_device *dev) +{ +} + +static void db2k_adc_disarm(struct comedi_device *dev) +{ + /* Disable hardware triggers */ + udelay(2); + writew(DB2K_TRIG_CONTROL_TYPE_ANALOG | DB2K_TRIG_CONTROL_DISABLE, + dev->mmio + DB2K_REG_TRIG_CONTROL); + udelay(2); + writew(DB2K_TRIG_CONTROL_TYPE_TTL | DB2K_TRIG_CONTROL_DISABLE, + dev->mmio + DB2K_REG_TRIG_CONTROL); + + /* Stop the scan list FIFO from loading the configuration pipe */ + udelay(2); + writew(DB2K_ACQ_CONTROL_SEQ_STOP_SCAN_LIST, + dev->mmio + DB2K_REG_ACQ_CONTROL); + + /* Stop the pacer clock */ + udelay(2); + writew(DB2K_ACQ_CONTROL_ADC_PACER_DISABLE, + dev->mmio + DB2K_REG_ACQ_CONTROL); + + /* Stop the input dma (abort channel 1) */ + db2k_adc_stop_dma_transfer(dev); +} + +static void db2k_activate_reference_dacs(struct comedi_device *dev) +{ + unsigned int val; + int timeout; + + /* Set the + reference dac value in the FPGA */ + writew(DB2K_REF_DACS_SET | DB2K_REF_DACS_SELECT_POS_REF, + dev->mmio + DB2K_REG_REF_DACS); + for (timeout = 0; timeout < 20; timeout++) { + val = readw(dev->mmio + DB2K_REG_DAC_STATUS); + if ((val & DB2K_DAC_STATUS_REF_BUSY) == 0) + break; + udelay(2); + } + + /* Set the - reference dac value in the FPGA */ + writew(DB2K_REF_DACS_SET | DB2K_REF_DACS_SELECT_NEG_REF, + dev->mmio + DB2K_REG_REF_DACS); + for (timeout = 0; timeout < 20; timeout++) { + val = readw(dev->mmio + DB2K_REG_DAC_STATUS); + if ((val & DB2K_DAC_STATUS_REF_BUSY) == 0) + break; + udelay(2); + } +} + +static void db2k_initialize_ctrs(struct comedi_device *dev) +{ +} + +static void db2k_initialize_tmrs(struct comedi_device *dev) +{ +} + +static void db2k_dac_disarm(struct comedi_device *dev) +{ +} + +static void db2k_initialize_adc(struct comedi_device *dev) +{ + db2k_adc_disarm(dev); + db2k_activate_reference_dacs(dev); + db2k_initialize_ctrs(dev); + db2k_initialize_tmrs(dev); +} + +static int db2k_8255_cb(struct comedi_device *dev, int dir, int port, int data, + unsigned long iobase) +{ + if (dir) { + writew(data, dev->mmio + iobase + port * 2); + return 0; + } + return readw(dev->mmio + iobase + port * 2); +} + +static int db2k_auto_attach(struct comedi_device *dev, unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct db2k_boardtype *board; + struct db2k_private *devpriv; + struct comedi_subdevice *s; + int result; + + if (context >= ARRAY_SIZE(db2k_boardtypes)) + return -ENODEV; + board = &db2k_boardtypes[context]; + if (!board->name) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + result = comedi_pci_enable(dev); + if (result) + return result; + + devpriv->plx = pci_ioremap_bar(pcidev, 0); + dev->mmio = pci_ioremap_bar(pcidev, 2); + if (!devpriv->plx || !dev->mmio) + return -ENOMEM; + + result = comedi_alloc_subdevices(dev, 3); + if (result) + return result; + + result = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev, + DB2K_FIRMWARE, db2k_load_firmware, 0); + if (result < 0) + return result; + + db2k_initialize_adc(dev); + db2k_dac_disarm(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 = db2k_ai_insn_read; + s->range_table = &db2k_ai_range; + + s = &dev->subdevices[1]; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->has_2_ao ? 2 : 4; + s->maxdata = 0xffff; + s->insn_write = db2k_ao_insn_write; + s->range_table = &range_bipolar10; + + result = comedi_alloc_subdev_readback(s); + if (result) + return result; + + s = &dev->subdevices[2]; + return subdev_8255_init(dev, s, db2k_8255_cb, + DB2K_REG_DIO_P2_EXP_IO_8_BIT); +} + +static void db2k_detach(struct comedi_device *dev) +{ + struct db2k_private *devpriv = dev->private; + + if (devpriv && devpriv->plx) + iounmap(devpriv->plx); + comedi_pci_detach(dev); +} + +static struct comedi_driver db2k_driver = { + .driver_name = "daqboard2000", + .module = THIS_MODULE, + .auto_attach = db2k_auto_attach, + .detach = db2k_detach, +}; + +static int db2k_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &db2k_driver, id->driver_data); +} + +static const struct pci_device_id db2k_pci_table[] = { + { PCI_DEVICE_SUB(PCI_VENDOR_ID_IOTECH, 0x0409, PCI_VENDOR_ID_IOTECH, + 0x0002), .driver_data = BOARD_DAQBOARD2000, }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_IOTECH, 0x0409, PCI_VENDOR_ID_IOTECH, + 0x0004), .driver_data = BOARD_DAQBOARD2001, }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, db2k_pci_table); + +static struct pci_driver db2k_pci_driver = { + .name = "daqboard2000", + .id_table = db2k_pci_table, + .probe = db2k_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(db2k_driver, db2k_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(DB2K_FIRMWARE); diff --git a/drivers/comedi/drivers/das08.c b/drivers/comedi/drivers/das08.c new file mode 100644 index 000000000..f8ab3af2e --- /dev/null +++ b/drivers/comedi/drivers/das08.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/das08.c + * comedi module for common DAS08 support (used by ISA/PCI/PCMCIA drivers) + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net> + * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org> + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +#include "das08.h" + +/* + * Data format of DAS08_AI_LSB_REG and DAS08_AI_MSB_REG depends on + * 'ai_encoding' member of board structure: + * + * das08_encode12 : DATA[11..4] = MSB[7..0], DATA[3..0] = LSB[7..4]. + * das08_pcm_encode12 : DATA[11..8] = MSB[3..0], DATA[7..9] = LSB[7..0]. + * das08_encode16 : SIGN = MSB[7], MAGNITUDE[14..8] = MSB[6..0], + * MAGNITUDE[7..0] = LSB[7..0]. + * SIGN==0 for negative input, SIGN==1 for positive input. + * Note: when read a second time after conversion + * complete, MSB[7] is an "over-range" bit. + */ +#define DAS08_AI_LSB_REG 0x00 /* (R) AI least significant bits */ +#define DAS08_AI_MSB_REG 0x01 /* (R) AI most significant bits */ +#define DAS08_AI_TRIG_REG 0x01 /* (W) AI software trigger */ +#define DAS08_STATUS_REG 0x02 /* (R) status */ +#define DAS08_STATUS_AI_BUSY BIT(7) /* AI conversion in progress */ +/* + * The IRQ status bit is set to 1 by a rising edge on the external interrupt + * input (which may be jumpered to the pacer output). It is cleared by + * setting the INTE control bit to 0. Not present on "JR" boards. + */ +#define DAS08_STATUS_IRQ BIT(3) /* latched interrupt input */ +/* digital inputs (not "JR" boards) */ +#define DAS08_STATUS_DI(x) (((x) & 0x70) >> 4) +#define DAS08_CONTROL_REG 0x02 /* (W) control */ +/* + * Note: The AI multiplexor channel can also be read from status register using + * the same mask. + */ +#define DAS08_CONTROL_MUX_MASK 0x7 /* multiplexor channel mask */ +#define DAS08_CONTROL_MUX(x) ((x) & DAS08_CONTROL_MUX_MASK) /* mux channel */ +#define DAS08_CONTROL_INTE BIT(3) /* interrupt enable (not "JR" boards) */ +#define DAS08_CONTROL_DO_MASK 0xf0 /* digital outputs mask (not "JR") */ +/* digital outputs (not "JR" boards) */ +#define DAS08_CONTROL_DO(x) (((x) << 4) & DAS08_CONTROL_DO_MASK) +/* + * (R/W) programmable AI gain ("PGx" and "AOx" boards): + * + bits 3..0 (R/W) show/set the gain for the current AI mux channel + * + bits 6..4 (R) show the current AI mux channel + * + bit 7 (R) not unused + */ +#define DAS08_GAIN_REG 0x03 + +#define DAS08JR_DI_REG 0x03 /* (R) digital inputs ("JR" boards) */ +#define DAS08JR_DO_REG 0x03 /* (W) digital outputs ("JR" boards) */ +/* (W) analog output l.s.b. registers for 2 channels ("JR" boards) */ +#define DAS08JR_AO_LSB_REG(x) ((x) ? 0x06 : 0x04) +/* (W) analog output m.s.b. registers for 2 channels ("JR" boards) */ +#define DAS08JR_AO_MSB_REG(x) ((x) ? 0x07 : 0x05) +/* + * (R) update analog outputs ("JR" boards set for simultaneous output) + * (same register as digital inputs) + */ +#define DAS08JR_AO_UPDATE_REG 0x03 + +/* (W) analog output l.s.b. registers for 2 channels ("AOx" boards) */ +#define DAS08AOX_AO_LSB_REG(x) ((x) ? 0x0a : 0x08) +/* (W) analog output m.s.b. registers for 2 channels ("AOx" boards) */ +#define DAS08AOX_AO_MSB_REG(x) ((x) ? 0x0b : 0x09) +/* + * (R) update analog outputs ("AOx" boards set for simultaneous output) + * (any of the analog output registers could be used for this) + */ +#define DAS08AOX_AO_UPDATE_REG 0x08 + +/* gainlist same as _pgx_ below */ + +static const struct comedi_lrange das08_pgl_ai_range = { + 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 das08_pgh_ai_range = { + 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 das08_pgm_ai_range = { + 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) + } +}; + +static const struct comedi_lrange *const das08_ai_lranges[] = { + [das08_pg_none] = &range_unknown, + [das08_bipolar5] = &range_bipolar5, + [das08_pgh] = &das08_pgh_ai_range, + [das08_pgl] = &das08_pgl_ai_range, + [das08_pgm] = &das08_pgm_ai_range, +}; + +static const int das08_pgh_ai_gainlist[] = { + 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7 +}; +static const int das08_pgl_ai_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 }; +static const int das08_pgm_ai_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 }; + +static const int *const das08_ai_gainlists[] = { + [das08_pg_none] = NULL, + [das08_bipolar5] = NULL, + [das08_pgh] = das08_pgh_ai_gainlist, + [das08_pgl] = das08_pgl_ai_gainlist, + [das08_pgm] = das08_pgm_ai_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_REG); + if ((status & DAS08_STATUS_AI_BUSY) == 0) + return 0; + return -EBUSY; +} + +static int das08_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct das08_board_struct *board = dev->board_ptr; + struct das08_private_struct *devpriv = dev->private; + int n; + int chan; + int range; + int lsb, msb; + int ret; + + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* clear crap */ + inb(dev->iobase + DAS08_AI_LSB_REG); + inb(dev->iobase + DAS08_AI_MSB_REG); + + /* set multiplexer */ + /* lock to prevent race with digital output */ + spin_lock(&dev->spinlock); + devpriv->do_mux_bits &= ~DAS08_CONTROL_MUX_MASK; + devpriv->do_mux_bits |= DAS08_CONTROL_MUX(chan); + outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL_REG); + spin_unlock(&dev->spinlock); + + if (devpriv->pg_gainlist) { + /* set gain/range */ + range = CR_RANGE(insn->chanspec); + outb(devpriv->pg_gainlist[range], + dev->iobase + DAS08_GAIN_REG); + } + + for (n = 0; n < insn->n; n++) { + /* clear over-range bits for 16-bit boards */ + if (board->ai_nbits == 16) + if (inb(dev->iobase + DAS08_AI_MSB_REG) & 0x80) + dev_info(dev->class_dev, "over-range\n"); + + /* trigger conversion */ + outb_p(0, dev->iobase + DAS08_AI_TRIG_REG); + + ret = comedi_timeout(dev, s, insn, das08_ai_eoc, 0); + if (ret) + return ret; + + msb = inb(dev->iobase + DAS08_AI_MSB_REG); + lsb = inb(dev->iobase + DAS08_AI_LSB_REG); + if (board->ai_encoding == das08_encode12) { + data[n] = (lsb >> 4) | (msb << 4); + } else if (board->ai_encoding == das08_pcm_encode12) { + data[n] = (msb << 8) + lsb; + } else if (board->ai_encoding == das08_encode16) { + /* + * "JR" 16-bit boards are sign-magnitude. + * + * XXX The manual seems to imply that 0 is full-scale + * negative and 65535 is full-scale positive, but the + * original COMEDI patch to add support for the + * DAS08/JR/16 and DAS08/JR/16-AO boards have it + * encoded as sign-magnitude. Assume the original + * COMEDI code is correct for now. + */ + unsigned int magnitude = lsb | ((msb & 0x7f) << 8); + + /* + * MSB bit 7 is 0 for negative, 1 for positive voltage. + * COMEDI 16-bit bipolar data value for 0V is 0x8000. + */ + if (msb & 0x80) + data[n] = BIT(15) + magnitude; + else + data[n] = BIT(15) - magnitude; + } else { + dev_err(dev->class_dev, "bug! unknown ai encoding\n"); + return -1; + } + } + + return n; +} + +static int das08_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = 0; + data[1] = DAS08_STATUS_DI(inb(dev->iobase + DAS08_STATUS_REG)); + + return insn->n; +} + +static int das08_do_insn_bits(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_CONTROL_DO_MASK; + devpriv->do_mux_bits |= DAS08_CONTROL_DO(s->state); + outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL_REG); + spin_unlock(&dev->spinlock); + } + + data[1] = s->state; + + return insn->n; +} + +static int das08jr_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = 0; + data[1] = inb(dev->iobase + DAS08JR_DI_REG); + + return insn->n; +} + +static int das08jr_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 + DAS08JR_DO_REG); + + 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 *board = dev->board_ptr; + unsigned char lsb; + unsigned char msb; + + lsb = data & 0xff; + msb = (data >> 8) & 0xff; + if (board->is_jr) { + outb(lsb, dev->iobase + DAS08JR_AO_LSB_REG(chan)); + outb(msb, dev->iobase + DAS08JR_AO_MSB_REG(chan)); + /* load DACs */ + inb(dev->iobase + DAS08JR_AO_UPDATE_REG); + } else { + outb(lsb, dev->iobase + DAS08AOX_AO_LSB_REG(chan)); + outb(msb, dev->iobase + DAS08AOX_AO_MSB_REG(chan)); + /* load DACs */ + inb(dev->iobase + DAS08AOX_AO_UPDATE_REG); + } +} + +static int das08_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + das08_ao_set_data(dev, chan, val); + } + s->readback[chan] = val; + + return insn->n; +} + +int das08_common_attach(struct comedi_device *dev, unsigned long iobase) +{ + const struct das08_board_struct *board = dev->board_ptr; + struct das08_private_struct *devpriv = dev->private; + struct comedi_subdevice *s; + int ret; + int i; + + dev->iobase = iobase; + + dev->board_name = board->name; + + ret = comedi_alloc_subdevices(dev, 6); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* ai */ + if (board->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 << board->ai_nbits) - 1; + s->range_table = das08_ai_lranges[board->ai_pg]; + s->insn_read = das08_ai_insn_read; + devpriv->pg_gainlist = das08_ai_gainlists[board->ai_pg]; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[1]; + /* ao */ + if (board->ao_nbits) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << board->ao_nbits) - 1; + s->range_table = &range_bipolar5; + s->insn_write = das08_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* initialize all channels to 0V */ + for (i = 0; i < s->n_chan; i++) { + s->readback[i] = s->maxdata / 2; + das08_ao_set_data(dev, i, s->readback[i]); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + /* di */ + if (board->di_nchan) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = board->di_nchan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = board->is_jr ? das08jr_di_insn_bits : + das08_di_insn_bits; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[3]; + /* do */ + if (board->do_nchan) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->do_nchan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = board->is_jr ? das08jr_do_insn_bits : + das08_do_insn_bits; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[4]; + /* 8255 */ + if (board->i8255_offset != 0) { + ret = subdev_8255_init(dev, s, NULL, board->i8255_offset); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Counter subdevice (8254) */ + s = &dev->subdevices[5]; + if (board->i8254_offset) { + dev->pacer = comedi_8254_init(dev->iobase + board->i8254_offset, + 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + comedi_8254_subdevice_init(s, dev->pacer); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} +EXPORT_SYMBOL_GPL(das08_common_attach); + +static int __init das08_init(void) +{ + return 0; +} +module_init(das08_init); + +static void __exit das08_exit(void) +{ +} +module_exit(das08_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi common DAS08 support module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das08.h b/drivers/comedi/drivers/das08.h new file mode 100644 index 000000000..ef65a7e50 --- /dev/null +++ b/drivers/comedi/drivers/das08.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * das08.h + * + * Header for common DAS08 support (used by ISA/PCI/PCMCIA drivers) + * + * Copyright (C) 2003 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +#ifndef _DAS08_H +#define _DAS08_H + +#include <linux/types.h> + +struct comedi_device; + +/* different ways ai data is encoded in first two registers */ +enum das08_ai_encoding { das08_encode12, das08_encode16, das08_pcm_encode12 }; +/* types of ai range table used by different boards */ +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 { + /* bits for do/mux register on boards without separate do register */ + unsigned int do_mux_bits; + const unsigned int *pg_gainlist; +}; + +int das08_common_attach(struct comedi_device *dev, unsigned long iobase); + +#endif /* _DAS08_H */ diff --git a/drivers/comedi/drivers/das08_cs.c b/drivers/comedi/drivers/das08_cs.c new file mode 100644 index 000000000..6075efcf1 --- /dev/null +++ b/drivers/comedi/drivers/das08_cs.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for DAS008 PCMCIA 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> + * + * 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. + * + * Configuration Options: none, uses PCMCIA auto config + * + * Command support does not exist, but could be added for this board. + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pcmcia.h> + +#include "das08.h" + +static const struct das08_board_struct das08_cs_boards[] = { + { + .name = "pcm-das08", + .ai_nbits = 12, + .ai_pg = das08_bipolar5, + .ai_encoding = das08_pcm_encode12, + .di_nchan = 3, + .do_nchan = 3, + .iosize = 16, + }, +}; + +static int das08_cs_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + struct das08_private_struct *devpriv; + unsigned long iobase; + int ret; + + /* The das08 driver needs the board_ptr */ + dev->board_ptr = &das08_cs_boards[0]; + + link->config_flags |= CONF_AUTO_SET_IO; + ret = comedi_pcmcia_enable(dev, NULL); + if (ret) + return ret; + iobase = link->resource[0]->start; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + return das08_common_attach(dev, iobase); +} + +static struct comedi_driver driver_das08_cs = { + .driver_name = "das08_cs", + .module = THIS_MODULE, + .auto_attach = das08_cs_auto_attach, + .detach = comedi_pcmcia_disable, +}; + +static int das08_pcmcia_attach(struct pcmcia_device *link) +{ + return comedi_pcmcia_auto_config(link, &driver_das08_cs); +} + +static const struct pcmcia_device_id das08_cs_id_table[] = { + PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4001), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, das08_cs_id_table); + +static struct pcmcia_driver das08_cs_driver = { + .name = "pcm-das08", + .owner = THIS_MODULE, + .id_table = das08_cs_id_table, + .probe = das08_pcmcia_attach, + .remove = comedi_pcmcia_auto_unconfig, +}; +module_comedi_pcmcia_driver(driver_das08_cs, das08_cs_driver); + +MODULE_AUTHOR("David A. Schleef <ds@schleef.org>"); +MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>"); +MODULE_DESCRIPTION("Comedi driver for ComputerBoards DAS-08 PCMCIA boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das08_isa.c b/drivers/comedi/drivers/das08_isa.c new file mode 100644 index 000000000..3d43b77cc --- /dev/null +++ b/drivers/comedi/drivers/das08_isa.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: das08_isa + * Description: DAS-08 ISA/PC-104 compatible boards + * Devices: [Keithley Metrabyte] DAS08 (isa-das08), + * [ComputerBoards] DAS08 (isa-das08), DAS08-PGM (das08-pgm), + * DAS08-PGH (das08-pgh), DAS08-PGL (das08-pgl), DAS08-AOH (das08-aoh), + * DAS08-AOL (das08-aol), DAS08-AOM (das08-aom), DAS08/JR-AO (das08/jr-ao), + * DAS08/JR-16-AO (das08jr-16-ao), PC104-DAS08 (pc104-das08), + * DAS08/JR/16 (das08jr/16) + * Author: Warren Jasper, ds, Frank Hess + * Updated: Fri, 31 Aug 2012 19:19:06 +0100 + * Status: works + * + * This is the ISA/PC-104-specific support split off from the das08 driver. + * + * Configuration Options: + * [0] - base io address + */ + +#include <linux/module.h> +#include <linux/comedi/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 *board = dev->board_ptr; + struct das08_private_struct *devpriv; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], board->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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das08_pci.c b/drivers/comedi/drivers/das08_pci.c new file mode 100644 index 000000000..982f3ab0c --- /dev/null +++ b/drivers/comedi/drivers/das08_pci.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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/comedi/comedi_pci.h> + +#include "das08.h" + +static const struct das08_board_struct das08_pci_boards[] = { + { + .name = "pci-das08", + .ai_nbits = 12, + .ai_pg = das08_bipolar5, + .ai_encoding = das08_encode12, + .di_nchan = 3, + .do_nchan = 4, + .i8254_offset = 4, + .iosize = 8, + }, +}; + +static int das08_pci_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pdev = comedi_to_pci_dev(dev); + struct das08_private_struct *devpriv; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* The das08 driver needs the board_ptr */ + dev->board_ptr = &das08_pci_boards[0]; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pdev, 2); + + return das08_common_attach(dev, dev->iobase); +} + +static struct comedi_driver das08_pci_comedi_driver = { + .driver_name = "pci-das08", + .module = THIS_MODULE, + .auto_attach = das08_pci_auto_attach, + .detach = comedi_pci_detach, +}; + +static int das08_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &das08_pci_comedi_driver, + id->driver_data); +} + +static const struct pci_device_id das08_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0029) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, das08_pci_table); + +static struct pci_driver das08_pci_driver = { + .name = "pci-das08", + .id_table = das08_pci_table, + .probe = das08_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(das08_pci_comedi_driver, das08_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das16.c b/drivers/comedi/drivers/das16.c new file mode 100644 index 000000000..728dc0215 --- /dev/null +++ b/drivers/comedi/drivers/das16.c @@ -0,0 +1,1198 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: das16 + * Description: DAS16 compatible boards + * Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess, Roman Fietze + * Devices: [Keithley Metrabyte] DAS-16 (das-16), DAS-16G (das-16g), + * DAS-16F (das-16f), DAS-1201 (das-1201), DAS-1202 (das-1202), + * DAS-1401 (das-1401), DAS-1402 (das-1402), DAS-1601 (das-1601), + * DAS-1602 (das-1602), + * [ComputerBoards] PC104-DAS16/JR (pc104-das16jr), + * PC104-DAS16JR/16 (pc104-das16jr/16), CIO-DAS16 (cio-das16), + * CIO-DAS16F (cio-das16/f), CIO-DAS16/JR (cio-das16/jr), + * CIO-DAS16JR/16 (cio-das16jr/16), CIO-DAS1401/12 (cio-das1401/12), + * CIO-DAS1402/12 (cio-das1402/12), CIO-DAS1402/16 (cio-das1402/16), + * CIO-DAS1601/12 (cio-das1601/12), CIO-DAS1602/12 (cio-das1602/12), + * CIO-DAS1602/16 (cio-das1602/16), CIO-DAS16/330 (cio-das16/330) + * Status: works + * Updated: 2003-10-12 + * + * A rewrite of the das16 and das1600 drivers. + * + * Options: + * [0] - base io address + * [1] - irq (does nothing, irq is not used anymore) + * [2] - dma channel (optional, required for comedi_command support) + * [3] - master clock speed in MHz (optional, 1 or 10, ignored if + * board can probe clock, defaults to 1) + * [4] - analog input range lowest voltage in microvolts (optional, + * only useful if your board does not have software + * programmable gain) + * [5] - analog input range highest voltage in microvolts (optional, + * only useful if board does not have software programmable + * gain) + * [6] - analog output range lowest voltage in microvolts (optional) + * [7] - analog output range highest voltage in microvolts (optional) + * + * Passing a zero for an option is the same as leaving it unspecified. + */ + +/* + * Testing and debugging help provided by Daniel Koch. + * + * Keithley Manuals: + * 2309.PDF (das16) + * 4919.PDF (das1400, 1600) + * 4922.PDF (das-1400) + * 4923.PDF (das1200, 1400, 1600) + * + * Computer boards manuals also available from their website + * www.measurementcomputing.com + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> +#include <linux/comedi/comedi_isadma.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 BIT(7) +#define DAS16_STATUS_UNIPOLAR BIT(6) +#define DAS16_STATUS_MUXBIT BIT(5) +#define DAS16_STATUS_INT BIT(4) +#define DAS16_CTRL_REG 0x09 +#define DAS16_CTRL_INTE BIT(7) +#define DAS16_CTRL_IRQ(x) (((x) & 0x7) << 4) +#define DAS16_CTRL_DMAE BIT(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 BIT(1) +#define DAS16_PACER_TRIG0 BIT(0) +#define DAS16_GAIN_REG 0x0b +#define DAS16_TIMER_BASE_REG 0x0c /* to 0x0f */ + +#define DAS1600_CONV_REG 0x404 +#define DAS1600_CONV_DISABLE BIT(6) +#define DAS1600_BURST_REG 0x405 +#define DAS1600_BURST_VAL BIT(6) +#define DAS1600_ENABLE_REG 0x406 +#define DAS1600_ENABLE_VAL BIT(6) +#define DAS1600_STATUS_REG 0x407 +#define DAS1600_STATUS_BME BIT(6) +#define DAS1600_STATUS_ME BIT(5) +#define DAS1600_STATUS_CD BIT(4) +#define DAS1600_STATUS_WS BIT(1) +#define DAS1600_STATUS_CLK_10MHZ BIT(0) + +static const struct comedi_lrange range_das1x01_bip = { + 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_das1x01_unip = { + 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_das1x02_bip = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_das1x02_unip = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_das16jr = { + 9, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_das16jr_16 = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const int das16jr_gainlist[] = { 8, 0, 1, 2, 3, 4, 5, 6, 7 }; +static const int das16jr_16_gainlist[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; +static const int das1600_gainlist[] = { 0, 1, 2, 3 }; + +enum { + das16_pg_none = 0, + das16_pg_16jr, + das16_pg_16jr_16, + das16_pg_1601, + das16_pg_1602, +}; + +static const int *const das16_gainlists[] = { + NULL, + das16jr_gainlist, + das16jr_16_gainlist, + das1600_gainlist, + das1600_gainlist, +}; + +static const struct comedi_lrange *const das16_ai_uni_lranges[] = { + &range_unknown, + &range_das16jr, + &range_das16jr_16, + &range_das1x01_unip, + &range_das1x02_unip, +}; + +static const struct comedi_lrange *const das16_ai_bip_lranges[] = { + &range_unknown, + &range_das16jr, + &range_das16jr_16, + &range_das1x01_bip, + &range_das1x02_bip, +}; + +struct das16_board { + const char *name; + unsigned int ai_maxdata; + unsigned int ai_speed; /* max conversion speed in nanosec */ + unsigned int ai_pg; + unsigned int has_ao:1; + unsigned int has_8255:1; + + unsigned int i8255_offset; + + unsigned int size; + unsigned int id; +}; + +static const struct das16_board das16_boards[] = { + { + .name = "das-16", + .ai_maxdata = 0x0fff, + .ai_speed = 15000, + .ai_pg = das16_pg_none, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x10, + .size = 0x14, + .id = 0x00, + }, { + .name = "das-16g", + .ai_maxdata = 0x0fff, + .ai_speed = 15000, + .ai_pg = das16_pg_none, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x10, + .size = 0x14, + .id = 0x00, + }, { + .name = "das-16f", + .ai_maxdata = 0x0fff, + .ai_speed = 8500, + .ai_pg = das16_pg_none, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x10, + .size = 0x14, + .id = 0x00, + }, { + .name = "cio-das16", + .ai_maxdata = 0x0fff, + .ai_speed = 20000, + .ai_pg = das16_pg_none, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x10, + .size = 0x14, + .id = 0x80, + }, { + .name = "cio-das16/f", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_none, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x10, + .size = 0x14, + .id = 0x80, + }, { + .name = "cio-das16/jr", + .ai_maxdata = 0x0fff, + .ai_speed = 7692, + .ai_pg = das16_pg_16jr, + .size = 0x10, + .id = 0x00, + }, { + .name = "pc104-das16jr", + .ai_maxdata = 0x0fff, + .ai_speed = 3300, + .ai_pg = das16_pg_16jr, + .size = 0x10, + .id = 0x00, + }, { + .name = "cio-das16jr/16", + .ai_maxdata = 0xffff, + .ai_speed = 10000, + .ai_pg = das16_pg_16jr_16, + .size = 0x10, + .id = 0x00, + }, { + .name = "pc104-das16jr/16", + .ai_maxdata = 0xffff, + .ai_speed = 10000, + .ai_pg = das16_pg_16jr_16, + .size = 0x10, + .id = 0x00, + }, { + .name = "das-1201", + .ai_maxdata = 0x0fff, + .ai_speed = 20000, + .ai_pg = das16_pg_none, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0x20, + }, { + .name = "das-1202", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_none, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0x20, + }, { + .name = "das-1401", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_1601, + .size = 0x408, + .id = 0xc0, + }, { + .name = "das-1402", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .size = 0x408, + .id = 0xc0, + }, { + .name = "das-1601", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_1601, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0xc0, + }, { + .name = "das-1602", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das1401/12", + .ai_maxdata = 0x0fff, + .ai_speed = 6250, + .ai_pg = das16_pg_1601, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das1402/12", + .ai_maxdata = 0x0fff, + .ai_speed = 6250, + .ai_pg = das16_pg_1602, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das1402/16", + .ai_maxdata = 0xffff, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das1601/12", + .ai_maxdata = 0x0fff, + .ai_speed = 6250, + .ai_pg = das16_pg_1601, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das1602/12", + .ai_maxdata = 0x0fff, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das1602/16", + .ai_maxdata = 0xffff, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .has_ao = 1, + .has_8255 = 1, + .i8255_offset = 0x400, + .size = 0x408, + .id = 0xc0, + }, { + .name = "cio-das16/330", + .ai_maxdata = 0x0fff, + .ai_speed = 3030, + .ai_pg = das16_pg_16jr, + .size = 0x14, + .id = 0xf0, + }, +}; + +/* + * Period for timer interrupt in jiffies. It's a function + * to deal with possibility of dynamic HZ patches + */ +static inline int timer_period(void) +{ + return HZ / 20; +} + +struct das16_private_struct { + struct comedi_isadma *dma; + struct comedi_device *dev; + unsigned int clockbase; + unsigned int ctrl_reg; + unsigned int divisor1; + unsigned int divisor2; + struct timer_list timer; + unsigned long extra_iobase; + unsigned int can_burst:1; + unsigned int timer_running:1; +}; + +static void das16_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int unread_samples) +{ + struct das16_private_struct *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize); + unsigned int nsamples; + + /* + * Determine dma size based on the buffer size plus the number of + * unread samples and the number of samples remaining in the command. + */ + nsamples = comedi_nsamples_left(s, max_samples + unread_samples); + if (nsamples > unread_samples) { + nsamples -= unread_samples; + desc->size = comedi_samples_to_bytes(s, nsamples); + comedi_isadma_program(desc); + } +} + +static void das16_interrupt(struct comedi_device *dev) +{ + struct das16_private_struct *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned long spin_flags; + unsigned int residue; + unsigned int nbytes; + unsigned int nsamples; + + spin_lock_irqsave(&dev->spinlock, spin_flags); + if (!(devpriv->ctrl_reg & DAS16_CTRL_DMAE)) { + spin_unlock_irqrestore(&dev->spinlock, spin_flags); + return; + } + + /* + * The pc104-das16jr (at least) has problems if the dma + * transfer is interrupted in the middle of transferring + * a 16 bit sample. + */ + residue = comedi_isadma_disable_on_sample(desc->chan, + comedi_bytes_per_sample(s)); + + /* figure out how many samples to read */ + if (residue > desc->size) { + dev_err(dev->class_dev, "residue > transfer size!\n"); + async->events |= COMEDI_CB_ERROR; + nbytes = 0; + } else { + nbytes = desc->size - residue; + } + nsamples = comedi_bytes_to_samples(s, nbytes); + + /* restart DMA if more samples are needed */ + if (nsamples) { + dma->cur_dma = 1 - dma->cur_dma; + das16_ai_setup_dma(dev, s, nsamples); + } + + spin_unlock_irqrestore(&dev->spinlock, spin_flags); + + comedi_buf_write_samples(s, desc->virt_addr, nsamples); + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); +} + +static void das16_timer_interrupt(struct timer_list *t) +{ + struct das16_private_struct *devpriv = from_timer(devpriv, t, timer); + struct comedi_device *dev = devpriv->dev; + unsigned long flags; + + das16_interrupt(dev); + + spin_lock_irqsave(&dev->spinlock, flags); + if (devpriv->timer_running) + mod_timer(&devpriv->timer, jiffies + timer_period()); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static void das16_ai_set_mux_range(struct comedi_device *dev, + unsigned int first_chan, + unsigned int last_chan, + unsigned int range) +{ + const struct das16_board *board = dev->board_ptr; + + /* set multiplexer */ + outb(first_chan | (last_chan << 4), dev->iobase + DAS16_MUX_REG); + + /* some boards do not have programmable gain */ + if (board->ai_pg == das16_pg_none) + return; + + /* + * Set gain (this is also burst rate register but according to + * computer boards manual, burst rate does nothing, even on + * keithley cards). + */ + outb((das16_gainlists[board->ai_pg])[range], + dev->iobase + DAS16_GAIN_REG); +} + +static int das16_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + if (chan != ((chan0 + i) % s->n_chan)) { + dev_dbg(dev->class_dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + return -EINVAL; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same gain\n"); + return -EINVAL; + } + } + + return 0; +} + +static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct das16_board *board = dev->board_ptr; + struct das16_private_struct *devpriv = dev->private; + int err = 0; + unsigned int trig_mask; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + + trig_mask = TRIG_FOLLOW; + if (devpriv->can_burst) + trig_mask |= TRIG_TIMER | TRIG_EXT; + err |= comedi_check_trigger_src(&cmd->scan_begin_src, trig_mask); + + trig_mask = TRIG_TIMER | TRIG_EXT; + if (devpriv->can_burst) + trig_mask |= TRIG_NOW; + err |= comedi_check_trigger_src(&cmd->convert_src, trig_mask); + + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + /* make sure scan_begin_src and convert_src don't conflict */ + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err |= -EINVAL; + if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + /* check against maximum frequency */ + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ai_speed * + cmd->chanlist_len); + } + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + } + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up arguments */ + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= das16_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static unsigned int das16_set_pacer(struct comedi_device *dev, unsigned int ns, + unsigned int flags) +{ + comedi_8254_cascade_ns_to_timer(dev->pacer, &ns, flags); + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + + return ns; +} + +static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct das16_private_struct *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int first_chan = CR_CHAN(cmd->chanlist[0]); + unsigned int last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + unsigned int range = CR_RANGE(cmd->chanlist[0]); + unsigned int byte; + unsigned long flags; + + if (cmd->flags & CMDF_PRIORITY) { + dev_err(dev->class_dev, + "isa dma transfers cannot be performed with CMDF_PRIORITY, aborting\n"); + return -1; + } + + if (devpriv->can_burst) + outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV_REG); + + /* set mux and range for chanlist scan */ + das16_ai_set_mux_range(dev, first_chan, last_chan, range); + + /* set counter mode and counts */ + cmd->convert_arg = das16_set_pacer(dev, cmd->convert_arg, cmd->flags); + + /* enable counters */ + byte = 0; + if (devpriv->can_burst) { + if (cmd->convert_src == TRIG_NOW) { + outb(DAS1600_BURST_VAL, + dev->iobase + DAS1600_BURST_REG); + /* set burst length */ + byte |= DAS16_PACER_BURST_LEN(cmd->chanlist_len - 1); + } else { + outb(0, dev->iobase + DAS1600_BURST_REG); + } + } + outb(byte, dev->iobase + DAS16_PACER_REG); + + /* set up dma transfer */ + dma->cur_dma = 0; + das16_ai_setup_dma(dev, s, 0); + + /* set up timer */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->timer_running = 1; + devpriv->timer.expires = jiffies + timer_period(); + add_timer(&devpriv->timer); + + /* enable DMA interrupt with external or internal pacing */ + devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | DAS16_CTRL_PACING_MASK); + devpriv->ctrl_reg |= DAS16_CTRL_DMAE; + if (cmd->convert_src == TRIG_EXT) + devpriv->ctrl_reg |= DAS16_CTRL_EXT_PACER; + else + devpriv->ctrl_reg |= DAS16_CTRL_INT_PACER; + outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG); + + if (devpriv->can_burst) + outb(0, dev->iobase + DAS1600_CONV_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct das16_private_struct *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + + /* disable interrupts, dma and pacer clocked conversions */ + devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | DAS16_CTRL_DMAE | + DAS16_CTRL_PACING_MASK); + outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG); + + comedi_isadma_disable(dma->chan); + + /* disable SW timer */ + if (devpriv->timer_running) { + devpriv->timer_running = 0; + del_timer(&devpriv->timer); + } + + if (devpriv->can_burst) + outb(0, dev->iobase + DAS1600_BURST_REG); + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +static void das16_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *array, + unsigned int num_bytes, + unsigned int start_chan_index) +{ + unsigned short *data = array; + unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes); + unsigned int i; + __le16 *buf = array; + + for (i = 0; i < num_samples; i++) { + data[i] = le16_to_cpu(buf[i]); + if (s->maxdata == 0x0fff) + data[i] >>= 4; + data[i] &= s->maxdata; + } +} + +static int das16_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + DAS16_STATUS_REG); + if ((status & DAS16_STATUS_BUSY) == 0) + return 0; + return -EBUSY; +} + +static int das16_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int ret; + int i; + + /* set mux and range for single channel */ + das16_ai_set_mux_range(dev, chan, chan, range); + + for (i = 0; i < insn->n; i++) { + /* trigger conversion */ + outb_p(0, dev->iobase + DAS16_TRIG_REG); + + ret = comedi_timeout(dev, s, insn, das16_ai_eoc, 0); + if (ret) + return ret; + + val = inb(dev->iobase + DAS16_AI_MSB_REG) << 8; + val |= inb(dev->iobase + DAS16_AI_LSB_REG); + if (s->maxdata == 0x0fff) + val >>= 4; + val &= s->maxdata; + + data[i] = val; + } + + return insn->n; +} + +static int das16_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + val <<= 4; + + outb(val & 0xff, dev->iobase + DAS16_AO_LSB_REG(chan)); + outb((val >> 8) & 0xff, dev->iobase + DAS16_AO_MSB_REG(chan)); + } + + return insn->n; +} + +static int das16_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + DAS16_DIO_REG) & 0xf; + + return insn->n; +} + +static int das16_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outb(s->state, dev->iobase + DAS16_DIO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int das16_probe(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct das16_board *board = dev->board_ptr; + int diobits; + + /* diobits indicates boards */ + diobits = inb(dev->iobase + DAS16_DIO_REG) & 0xf0; + if (board->id != diobits) { + dev_err(dev->class_dev, + "requested board's id bits are incorrect (0x%x != 0x%x)\n", + board->id, diobits); + return -EINVAL; + } + + return 0; +} + +static void das16_reset(struct comedi_device *dev) +{ + outb(0, dev->iobase + DAS16_STATUS_REG); + outb(0, dev->iobase + DAS16_CTRL_REG); + outb(0, dev->iobase + DAS16_PACER_REG); +} + +static void das16_alloc_dma(struct comedi_device *dev, unsigned int dma_chan) +{ + struct das16_private_struct *devpriv = dev->private; + + timer_setup(&devpriv->timer, das16_timer_interrupt, 0); + + /* only DMA channels 3 and 1 are valid */ + if (!(dma_chan == 1 || dma_chan == 3)) + return; + + /* DMA uses two buffers */ + devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, + DAS16_DMA_SIZE, COMEDI_ISADMA_READ); +} + +static void das16_free_dma(struct comedi_device *dev) +{ + struct das16_private_struct *devpriv = dev->private; + + if (devpriv) { + del_timer_sync(&devpriv->timer); + comedi_isadma_free(devpriv->dma); + } +} + +static const struct comedi_lrange *das16_ai_range(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_devconfig *it, + unsigned int pg_type, + unsigned int status) +{ + unsigned int min = it->options[4]; + unsigned int max = it->options[5]; + + /* get any user-defined input range */ + if (pg_type == das16_pg_none && (min || max)) { + struct comedi_lrange *lrange; + struct comedi_krange *krange; + + /* allocate single-range range table */ + lrange = comedi_alloc_spriv(s, + struct_size(lrange, range, 1)); + if (!lrange) + return &range_unknown; + + /* initialize ai range */ + lrange->length = 1; + krange = lrange->range; + krange->min = min; + krange->max = max; + krange->flags = UNIT_volt; + + return lrange; + } + + /* use software programmable range */ + if (status & DAS16_STATUS_UNIPOLAR) + return das16_ai_uni_lranges[pg_type]; + return das16_ai_bip_lranges[pg_type]; +} + +static const struct comedi_lrange *das16_ao_range(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_devconfig *it) +{ + unsigned int min = it->options[6]; + unsigned int max = it->options[7]; + + /* get any user-defined output range */ + if (min || max) { + struct comedi_lrange *lrange; + struct comedi_krange *krange; + + /* allocate single-range range table */ + lrange = comedi_alloc_spriv(s, + struct_size(lrange, range, 1)); + if (!lrange) + return &range_unknown; + + /* initialize ao range */ + lrange->length = 1; + krange = lrange->range; + krange->min = min; + krange->max = max; + krange->flags = UNIT_volt; + + return lrange; + } + + return &range_unknown; +} + +static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct das16_board *board = dev->board_ptr; + struct das16_private_struct *devpriv; + struct comedi_subdevice *s; + unsigned int osc_base; + unsigned int status; + int ret; + + /* check that clock setting is valid */ + if (it->options[3]) { + if (it->options[3] != 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; + devpriv->dev = dev; + + if (board->size < 0x400) { + ret = comedi_request_region(dev, it->options[0], board->size); + if (ret) + return ret; + } else { + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + /* Request an additional region for the 8255 */ + ret = __comedi_request_region(dev, dev->iobase + 0x400, + board->size & 0x3ff); + if (ret) + return ret; + devpriv->extra_iobase = dev->iobase + 0x400; + devpriv->can_burst = 1; + } + + /* probe id bits to make sure they are consistent */ + if (das16_probe(dev, it)) + return -EINVAL; + + /* get master clock speed */ + osc_base = I8254_OSC_BASE_1MHZ; + if (devpriv->can_burst) { + status = inb(dev->iobase + DAS1600_STATUS_REG); + if (status & DAS1600_STATUS_CLK_10MHZ) + osc_base = I8254_OSC_BASE_10MHZ; + } else { + if (it->options[3]) + osc_base = I8254_OSC_BASE_1MHZ / it->options[3]; + } + + dev->pacer = comedi_8254_init(dev->iobase + DAS16_TIMER_BASE_REG, + osc_base, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + das16_alloc_dma(dev, it->options[2]); + + ret = comedi_alloc_subdevices(dev, 4 + board->has_8255); + if (ret) + return ret; + + status = inb(dev->iobase + DAS16_STATUS_REG); + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + if (status & DAS16_STATUS_MUXBIT) { + s->subdev_flags |= SDF_GROUND; + s->n_chan = 16; + } else { + s->subdev_flags |= SDF_DIFF; + s->n_chan = 8; + } + s->len_chanlist = s->n_chan; + s->maxdata = board->ai_maxdata; + s->range_table = das16_ai_range(dev, s, it, board->ai_pg, status); + s->insn_read = das16_ai_insn_read; + if (devpriv->dma) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmdtest = das16_cmd_test; + s->do_cmd = das16_cmd_exec; + s->cancel = das16_cancel; + s->munge = das16_ai_munge; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = das16_ao_range(dev, s, it); + s->insn_write = das16_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16_do_insn_bits; + + /* initialize digital output lines */ + outb(s->state, dev->iobase + DAS16_DIO_REG); + + /* 8255 Digital I/O subdevice */ + if (board->has_8255) { + s = &dev->subdevices[4]; + ret = subdev_8255_init(dev, s, NULL, board->i8255_offset); + if (ret) + return ret; + } + + das16_reset(dev); + /* set the interrupt level */ + devpriv->ctrl_reg = DAS16_CTRL_IRQ(dev->irq); + outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG); + + if (devpriv->can_burst) { + outb(DAS1600_ENABLE_VAL, dev->iobase + DAS1600_ENABLE_REG); + outb(0, dev->iobase + DAS1600_CONV_REG); + outb(0, dev->iobase + DAS1600_BURST_REG); + } + + return 0; +} + +static void das16_detach(struct comedi_device *dev) +{ + const struct das16_board *board = dev->board_ptr; + struct das16_private_struct *devpriv = dev->private; + + if (devpriv) { + if (dev->iobase) + das16_reset(dev); + das16_free_dma(dev); + + if (devpriv->extra_iobase) + release_region(devpriv->extra_iobase, + board->size & 0x3ff); + } + + comedi_legacy_detach(dev); +} + +static struct comedi_driver das16_driver = { + .driver_name = "das16", + .module = THIS_MODULE, + .attach = das16_attach, + .detach = das16_detach, + .board_name = &das16_boards[0].name, + .num_names = ARRAY_SIZE(das16_boards), + .offset = sizeof(das16_boards[0]), +}; +module_comedi_driver(das16_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for DAS16 compatible boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das16m1.c b/drivers/comedi/drivers/das16m1.c new file mode 100644 index 000000000..275effb77 --- /dev/null +++ b/drivers/comedi/drivers/das16m1.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for CIO-DAS16/M1 + * 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> + */ + +/* + * 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. + * + * Configuration options: + * [0] - base io address + * [1] - irq (optional, but you probably want it) + * + * irq can be omitted, although the cmd interface will not work without it. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +/* + * Register map (dev->iobase) + */ +#define DAS16M1_AI_REG 0x00 /* 16-bit register */ +#define DAS16M1_AI_TO_CHAN(x) (((x) >> 0) & 0xf) +#define DAS16M1_AI_TO_SAMPLE(x) (((x) >> 4) & 0xfff) +#define DAS16M1_CS_REG 0x02 +#define DAS16M1_CS_EXT_TRIG BIT(0) +#define DAS16M1_CS_OVRUN BIT(5) +#define DAS16M1_CS_IRQDATA BIT(7) +#define DAS16M1_DI_REG 0x03 +#define DAS16M1_DO_REG 0x03 +#define DAS16M1_CLR_INTR_REG 0x04 +#define DAS16M1_INTR_CTRL_REG 0x05 +#define DAS16M1_INTR_CTRL_PACER(x) (((x) & 0x3) << 0) +#define DAS16M1_INTR_CTRL_PACER_EXT DAS16M1_INTR_CTRL_PACER(2) +#define DAS16M1_INTR_CTRL_PACER_INT DAS16M1_INTR_CTRL_PACER(3) +#define DAS16M1_INTR_CTRL_PACER_MASK DAS16M1_INTR_CTRL_PACER(3) +#define DAS16M1_INTR_CTRL_IRQ(x) (((x) & 0x7) << 4) +#define DAS16M1_INTR_CTRL_INTE BIT(7) +#define DAS16M1_Q_ADDR_REG 0x06 +#define DAS16M1_Q_REG 0x07 +#define DAS16M1_Q_CHAN(x) (((x) & 0x7) << 0) +#define DAS16M1_Q_RANGE(x) (((x) & 0xf) << 4) +#define DAS16M1_8254_IOBASE1 0x08 +#define DAS16M1_8254_IOBASE2 0x0c +#define DAS16M1_8255_IOBASE 0x400 +#define DAS16M1_8254_IOBASE3 0x404 + +#define DAS16M1_SIZE2 0x08 + +#define DAS16M1_AI_FIFO_SZ 1024 /* # samples */ + +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 comedi_8254 *counter; + unsigned int intr_ctrl; + unsigned int adc_count; + u16 initial_hw_count; + unsigned short ai_buffer[DAS16M1_AI_FIFO_SZ]; + unsigned long extra_iobase; +}; + +static void das16m1_ai_set_queue(struct comedi_device *dev, + unsigned int *chanspec, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + unsigned int chan = CR_CHAN(chanspec[i]); + unsigned int range = CR_RANGE(chanspec[i]); + + outb(i, dev->iobase + DAS16M1_Q_ADDR_REG); + outb(DAS16M1_Q_CHAN(chan) | DAS16M1_Q_RANGE(range), + dev->iobase + DAS16M1_Q_REG); + } +} + +static void das16m1_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 nsamples = comedi_bytes_to_samples(s, num_bytes); + unsigned int i; + + /* + * The fifo values have the channel number in the lower 4-bits and + * the sample in the upper 12-bits. This just shifts the values + * to remove the channel numbers. + */ + for (i = 0; i < nsamples; i++) + array[i] = DAS16M1_AI_TO_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_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 1000); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= das16m1_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int das16m1_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das16m1_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int byte; + + /* set software count */ + devpriv->adc_count = 0; + + /* + * Initialize lower half of hardware counter, used to determine how + * many samples are in fifo. Value doesn't actually load into counter + * until counter's next clock (the next a/d conversion). + */ + comedi_8254_set_mode(devpriv->counter, 1, I8254_MODE2 | I8254_BINARY); + comedi_8254_write(devpriv->counter, 1, 0); + + /* + * Remember current reading of counter so we know when counter has + * actually been loaded. + */ + devpriv->initial_hw_count = comedi_8254_read(devpriv->counter, 1); + + das16m1_ai_set_queue(dev, cmd->chanlist, cmd->chanlist_len); + + /* enable interrupts and set internal pacer counter mode and counts */ + devpriv->intr_ctrl &= ~DAS16M1_INTR_CTRL_PACER_MASK; + if (cmd->convert_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_INT; + } else { /* TRIG_EXT */ + devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_EXT; + } + + /* 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 |= DAS16M1_CS_EXT_TRIG; + + outb(byte, dev->iobase + DAS16M1_CS_REG); + + /* clear interrupt */ + outb(0, dev->iobase + DAS16M1_CLR_INTR_REG); + + devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_INTE; + outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG); + + return 0; +} + +static int das16m1_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das16m1_private *devpriv = dev->private; + + /* disable interrupts and pacer */ + devpriv->intr_ctrl &= ~(DAS16M1_INTR_CTRL_INTE | + DAS16M1_INTR_CTRL_PACER_MASK); + outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG); + + 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_REG); + if (status & DAS16M1_CS_IRQDATA) + return 0; + return -EBUSY; +} + +static int das16m1_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + int i; + + das16m1_ai_set_queue(dev, &insn->chanspec, 1); + + for (i = 0; i < insn->n; i++) { + unsigned short val; + + /* clear interrupt */ + outb(0, dev->iobase + DAS16M1_CLR_INTR_REG); + /* trigger conversion */ + outb(0, dev->iobase + DAS16M1_AI_REG); + + ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0); + if (ret) + return ret; + + val = inw(dev->iobase + DAS16M1_AI_REG); + data[i] = DAS16M1_AI_TO_SAMPLE(val); + } + + return insn->n; +} + +static int das16m1_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + DAS16M1_DI_REG) & 0xf; + + return insn->n; +} + +static int das16m1_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 + DAS16M1_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static void das16m1_handler(struct comedi_device *dev, unsigned int status) +{ + struct das16m1_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u16 num_samples; + u16 hw_counter; + + /* figure out how many samples are in fifo */ + hw_counter = comedi_8254_read(devpriv->counter, 1); + /* + * Make sure hardware counter reading is not bogus due to initial + * value not having been loaded yet. + */ + if (devpriv->adc_count == 0 && + hw_counter == devpriv->initial_hw_count) { + num_samples = 0; + } else { + /* + * The calculation of num_samples looks odd, but it uses the + * following facts. 16 bit hardware counter is initialized with + * value of zero (which really means 0x1000). The counter + * decrements by one on each conversion (when the counter + * decrements from zero it goes to 0xffff). num_samples is a + * 16 bit variable, so it will roll over in a similar fashion + * to the hardware counter. Work it out, and this is what you + * get. + */ + num_samples = -hw_counter - devpriv->adc_count; + } + /* check if we only need some of the points */ + if (cmd->stop_src == TRIG_COUNT) { + if (num_samples > cmd->stop_arg * cmd->chanlist_len) + num_samples = cmd->stop_arg * cmd->chanlist_len; + } + /* make sure we don't try to get too many points if fifo has overrun */ + if (num_samples > DAS16M1_AI_FIFO_SZ) + num_samples = DAS16M1_AI_FIFO_SZ; + insw(dev->iobase, devpriv->ai_buffer, num_samples); + comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples); + devpriv->adc_count += num_samples; + + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) { + /* end of acquisition */ + async->events |= COMEDI_CB_EOA; + } + } + + /* + * This probably won't catch overruns since the card doesn't generate + * overrun interrupts, but we might as well try. + */ + if (status & DAS16M1_CS_OVRUN) { + async->events |= COMEDI_CB_ERROR; + dev_err(dev->class_dev, "fifo overflow\n"); + } + + comedi_handle_events(dev, s); +} + +static int das16m1_ai_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_REG); + das16m1_handler(dev, status); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return comedi_buf_n_bytes_ready(s); +} + +static irqreturn_t das16m1_interrupt(int irq, void *d) +{ + int status; + struct comedi_device *dev = d; + + if (!dev->attached) { + dev_err(dev->class_dev, "premature interrupt\n"); + return IRQ_HANDLED; + } + /* prevent race with comedi_poll() */ + spin_lock(&dev->spinlock); + + status = inb(dev->iobase + DAS16M1_CS_REG); + + if ((status & (DAS16M1_CS_IRQDATA | DAS16M1_CS_OVRUN)) == 0) { + dev_err(dev->class_dev, "spurious interrupt\n"); + spin_unlock(&dev->spinlock); + return IRQ_NONE; + } + + das16m1_handler(dev, status); + + /* clear interrupt */ + outb(0, dev->iobase + DAS16M1_CLR_INTR_REG); + + 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; + } +} + +static int das16m1_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct das16m1_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; + /* Request an additional region for the 8255 and 3rd 8254 */ + ret = __comedi_request_region(dev, dev->iobase + DAS16M1_8255_IOBASE, + DAS16M1_SIZE2); + if (ret) + return ret; + devpriv->extra_iobase = dev->iobase + DAS16M1_8255_IOBASE; + + /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */ + if ((1 << it->options[1]) & 0xdcfc) { + ret = request_irq(it->options[1], das16m1_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + dev->pacer = comedi_8254_init(dev->iobase + DAS16M1_8254_IOBASE2, + I8254_OSC_BASE_10MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + devpriv->counter = comedi_8254_init(dev->iobase + DAS16M1_8254_IOBASE1, + 0, I8254_IO8, 0); + if (!devpriv->counter) + 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; + s->n_chan = 8; + s->maxdata = 0x0fff; + s->range_table = &range_das16m1; + s->insn_read = das16m1_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 256; + s->do_cmdtest = das16m1_ai_cmdtest; + s->do_cmd = das16m1_ai_cmd; + s->cancel = das16m1_ai_cancel; + s->poll = das16m1_ai_poll; + s->munge = das16m1_ai_munge; + } + + /* Digital Input subdevice */ + s = &dev->subdevices[1]; + 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_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16m1_do_insn_bits; + + /* Digital I/O subdevice (8255) */ + s = &dev->subdevices[3]; + ret = subdev_8255_init(dev, s, NULL, DAS16M1_8255_IOBASE); + if (ret) + return ret; + + /* initialize digital output lines */ + outb(0, dev->iobase + DAS16M1_DO_REG); + + /* set the interrupt level */ + devpriv->intr_ctrl = DAS16M1_INTR_CTRL_IRQ(das16m1_irq_bits(dev->irq)); + outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG); + + return 0; +} + +static void das16m1_detach(struct comedi_device *dev) +{ + struct das16m1_private *devpriv = dev->private; + + if (devpriv) { + if (devpriv->extra_iobase) + release_region(devpriv->extra_iobase, DAS16M1_SIZE2); + kfree(devpriv->counter); + } + comedi_legacy_detach(dev); +} + +static struct comedi_driver das16m1_driver = { + .driver_name = "das16m1", + .module = THIS_MODULE, + .attach = das16m1_attach, + .detach = das16m1_detach, +}; +module_comedi_driver(das16m1_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for CIO-DAS16/M1 ISA cards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das1800.c b/drivers/comedi/drivers/das1800.c new file mode 100644 index 000000000..f09608c0f --- /dev/null +++ b/drivers/comedi/drivers/das1800.c @@ -0,0 +1,1362 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for Keithley DAS-1700/DAS-1800 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> + */ + +/* + * 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 + * + * Configuration options: + * [0] - I/O port base address + * [1] - IRQ (optional, required for analog input cmd support) + * [2] - DMA0 (optional, requires irq) + * [3] - DMA1 (optional, requires irq and dma0) + * + * analog input cmd triggers supported: + * + * start_src TRIG_NOW command starts immediately + * TRIG_EXT command starts on external pin TGIN + * + * scan_begin_src TRIG_FOLLOW paced/external scans start immediately + * TRIG_TIMER burst scans start periodically + * TRIG_EXT burst scans start on external pin XPCLK + * + * scan_end_src TRIG_COUNT scan ends after last channel + * + * convert_src TRIG_TIMER paced/burst conversions are timed + * TRIG_EXT conversions on external pin XPCLK + * (requires scan_begin_src == TRIG_FOLLOW) + * + * stop_src TRIG_COUNT command stops after stop_arg scans + * TRIG_EXT command stops on external pin TGIN + * TRIG_NONE command runs until canceled + * + * If TRIG_EXT is used for both the start_src and stop_src, the first TGIN + * trigger starts the command, and the second trigger will stop it. If only + * one is TRIG_EXT, the first trigger will either stop or start the command. + * The external pin TGIN is normally set for negative edge triggering. It + * can be set to positive edge with the CR_INVERT flag. If TRIG_EXT is used + * for both the start_src and stop_src they must have the same polarity. + * + * Minimum conversion speed is limited to 64 microseconds (convert_arg <= 64000) + * for 'burst' scans. This limitation does not apply for 'paced' scans. The + * maximum conversion speed is limited by the board (convert_arg >= ai_speed). + * Maximum conversion speeds are not always achievable depending on the + * board setup (see user manual). + * + * NOTES: + * Only the DAS-1801ST has been tested by me. + * Unipolar and bipolar ranges cannot be mixed in the channel/gain list. + * + * The waveform analog output on the 'ao' cards is not supported. + * If you need it, send me (Frank Hess) an email. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> +#include <linux/comedi/comedi_isadma.h> + +/* misc. defines */ +#define DAS1800_SIZE 16 /* uses 16 io addresses */ +#define FIFO_SIZE 1024 /* 1024 sample fifo */ +#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 TGPL 0x40 +#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 +#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 +#define INT 0x1 +#define DMATC 0x2 +#define CT0TC 0x8 +#define OVF 0x10 +#define FHF 0x20 +#define FNE 0x40 +#define CVEN 0x80 +#define CVEN_MASK 0x40 +#define CLEAR_INTR_MASK (CVEN_MASK | 0x1f) +#define DAS1800_BURST_LENGTH 0x8 +#define DAS1800_BURST_RATE 0x9 +#define DAS1800_QRAM_ADDRESS 0xa +#define DAS1800_COUNTER 0xc + +#define IOBASE2 0x400 + +static const struct comedi_lrange das1801_ai_range = { + 8, { + BIP_RANGE(5), /* bipolar gain = 1 */ + BIP_RANGE(1), /* bipolar gain = 10 */ + BIP_RANGE(0.1), /* bipolar gain = 50 */ + BIP_RANGE(0.02), /* bipolar gain = 250 */ + UNI_RANGE(5), /* unipolar gain = 1 */ + UNI_RANGE(1), /* unipolar gain = 10 */ + UNI_RANGE(0.1), /* unipolar gain = 50 */ + UNI_RANGE(0.02) /* unipolar gain = 250 */ + } +}; + +static const struct comedi_lrange das1802_ai_range = { + 8, { + BIP_RANGE(10), /* bipolar gain = 1 */ + BIP_RANGE(5), /* bipolar gain = 2 */ + BIP_RANGE(2.5), /* bipolar gain = 4 */ + BIP_RANGE(1.25), /* bipolar gain = 8 */ + UNI_RANGE(10), /* unipolar gain = 1 */ + UNI_RANGE(5), /* unipolar gain = 2 */ + UNI_RANGE(2.5), /* unipolar gain = 4 */ + UNI_RANGE(1.25) /* unipolar gain = 8 */ + } +}; + +/* + * The waveform analog outputs on the 'ao' boards are not currently + * supported. They have a comedi_lrange of: + * { 2, { BIP_RANGE(10), BIP_RANGE(5) } } + */ + +enum das1800_boardid { + BOARD_DAS1701ST, + BOARD_DAS1701ST_DA, + BOARD_DAS1702ST, + BOARD_DAS1702ST_DA, + BOARD_DAS1702HR, + BOARD_DAS1702HR_DA, + BOARD_DAS1701AO, + BOARD_DAS1702AO, + BOARD_DAS1801ST, + BOARD_DAS1801ST_DA, + BOARD_DAS1802ST, + BOARD_DAS1802ST_DA, + BOARD_DAS1802HR, + BOARD_DAS1802HR_DA, + BOARD_DAS1801HC, + BOARD_DAS1802HC, + BOARD_DAS1801AO, + BOARD_DAS1802AO +}; + +/* board probe id values (hi byte of the digital input register) */ +#define DAS1800_ID_ST_DA 0x3 +#define DAS1800_ID_HR_DA 0x4 +#define DAS1800_ID_AO 0x5 +#define DAS1800_ID_HR 0x6 +#define DAS1800_ID_ST 0x7 +#define DAS1800_ID_HC 0x8 + +struct das1800_board { + const char *name; + unsigned char id; + unsigned int ai_speed; + unsigned int is_01_series:1; +}; + +static const struct das1800_board das1800_boards[] = { + [BOARD_DAS1701ST] = { + .name = "das-1701st", + .id = DAS1800_ID_ST, + .ai_speed = 6250, + .is_01_series = 1, + }, + [BOARD_DAS1701ST_DA] = { + .name = "das-1701st-da", + .id = DAS1800_ID_ST_DA, + .ai_speed = 6250, + .is_01_series = 1, + }, + [BOARD_DAS1702ST] = { + .name = "das-1702st", + .id = DAS1800_ID_ST, + .ai_speed = 6250, + }, + [BOARD_DAS1702ST_DA] = { + .name = "das-1702st-da", + .id = DAS1800_ID_ST_DA, + .ai_speed = 6250, + }, + [BOARD_DAS1702HR] = { + .name = "das-1702hr", + .id = DAS1800_ID_HR, + .ai_speed = 20000, + }, + [BOARD_DAS1702HR_DA] = { + .name = "das-1702hr-da", + .id = DAS1800_ID_HR_DA, + .ai_speed = 20000, + }, + [BOARD_DAS1701AO] = { + .name = "das-1701ao", + .id = DAS1800_ID_AO, + .ai_speed = 6250, + .is_01_series = 1, + }, + [BOARD_DAS1702AO] = { + .name = "das-1702ao", + .id = DAS1800_ID_AO, + .ai_speed = 6250, + }, + [BOARD_DAS1801ST] = { + .name = "das-1801st", + .id = DAS1800_ID_ST, + .ai_speed = 3000, + .is_01_series = 1, + }, + [BOARD_DAS1801ST_DA] = { + .name = "das-1801st-da", + .id = DAS1800_ID_ST_DA, + .ai_speed = 3000, + .is_01_series = 1, + }, + [BOARD_DAS1802ST] = { + .name = "das-1802st", + .id = DAS1800_ID_ST, + .ai_speed = 3000, + }, + [BOARD_DAS1802ST_DA] = { + .name = "das-1802st-da", + .id = DAS1800_ID_ST_DA, + .ai_speed = 3000, + }, + [BOARD_DAS1802HR] = { + .name = "das-1802hr", + .id = DAS1800_ID_HR, + .ai_speed = 10000, + }, + [BOARD_DAS1802HR_DA] = { + .name = "das-1802hr-da", + .id = DAS1800_ID_HR_DA, + .ai_speed = 10000, + }, + [BOARD_DAS1801HC] = { + .name = "das-1801hc", + .id = DAS1800_ID_HC, + .ai_speed = 3000, + .is_01_series = 1, + }, + [BOARD_DAS1802HC] = { + .name = "das-1802hc", + .id = DAS1800_ID_HC, + .ai_speed = 3000, + }, + [BOARD_DAS1801AO] = { + .name = "das-1801ao", + .id = DAS1800_ID_AO, + .ai_speed = 3000, + .is_01_series = 1, + }, + [BOARD_DAS1802AO] = { + .name = "das-1802ao", + .id = DAS1800_ID_AO, + .ai_speed = 3000, + }, +}; + +struct das1800_private { + struct comedi_isadma *dma; + int irq_dma_bits; + int dma_bits; + unsigned short *fifo_buf; + unsigned long iobase2; + bool ai_is_unipolar; +}; + +static void das1800_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int start_chan_index) +{ + struct das1800_private *devpriv = dev->private; + unsigned short *array = data; + unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes); + unsigned int i; + + if (devpriv->ai_is_unipolar) + return; + + for (i = 0; i < num_samples; i++) + array[i] = comedi_offset_munge(s, array[i]); +} + +static void das1800_handle_fifo_half_full(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das1800_private *devpriv = dev->private; + unsigned int nsamples = comedi_nsamples_left(s, FIFO_SIZE / 2); + + insw(dev->iobase + DAS1800_FIFO, devpriv->fifo_buf, nsamples); + comedi_buf_write_samples(s, devpriv->fifo_buf, nsamples); +} + +static void das1800_handle_fifo_not_empty(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short dpnt; + + while (inb(dev->iobase + DAS1800_STATUS) & FNE) { + dpnt = inw(dev->iobase + DAS1800_FIFO); + comedi_buf_write_samples(s, &dpnt, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + break; + } +} + +static void das1800_flush_dma_channel(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_isadma_desc *desc) +{ + unsigned int residue = comedi_isadma_disable(desc->chan); + unsigned int nbytes = desc->size - residue; + unsigned int nsamples; + + /* figure out how many points to read */ + nsamples = comedi_bytes_to_samples(s, nbytes); + nsamples = comedi_nsamples_left(s, nsamples); + + comedi_buf_write_samples(s, desc->virt_addr, nsamples); +} + +static void das1800_flush_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das1800_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + das1800_flush_dma_channel(dev, s, desc); + + if (dual_dma) { + /* switch to other channel and flush it */ + dma->cur_dma = 1 - dma->cur_dma; + desc = &dma->desc[dma->cur_dma]; + das1800_flush_dma_channel(dev, s, desc); + } + + /* get any remaining samples in fifo */ + das1800_handle_fifo_not_empty(dev, s); +} + +static void das1800_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int status) +{ + struct das1800_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + das1800_flush_dma_channel(dev, s, desc); + + /* re-enable dma channel */ + comedi_isadma_program(desc); + + if (status & DMATC) { + /* clear DMATC interrupt bit */ + outb(CLEAR_INTR_MASK & ~DMATC, dev->iobase + DAS1800_STATUS); + /* switch dma channels for next time, if appropriate */ + if (dual_dma) + dma->cur_dma = 1 - dma->cur_dma; + } +} + +static int das1800_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das1800_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc; + int i; + + /* disable and stop conversions */ + outb(0x0, dev->iobase + DAS1800_STATUS); + outb(0x0, dev->iobase + DAS1800_CONTROL_B); + outb(0x0, dev->iobase + DAS1800_CONTROL_A); + + if (dma) { + for (i = 0; i < 2; i++) { + desc = &dma->desc[i]; + if (desc->chan) + comedi_isadma_disable(desc->chan); + } + } + + return 0; +} + +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 register (spinlock is already held) */ + outb(ADC, dev->iobase + DAS1800_SELECT); + + /* get samples with dma, fifo, or polled as necessary */ + if (devpriv->irq_dma_bits & DMA_ENABLED) + das1800_handle_dma(dev, s, status); + else if (status & FHF) + das1800_handle_fifo_half_full(dev, s); + else if (status & FNE) + das1800_handle_fifo_not_empty(dev, s); + + /* if the card's fifo has overflowed */ + if (status & OVF) { + /* clear OVF interrupt bit */ + outb(CLEAR_INTR_MASK & ~OVF, dev->iobase + DAS1800_STATUS); + dev_err(dev->class_dev, "FIFO overflow\n"); + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + return; + } + /* stop taking data if appropriate */ + /* stop_src TRIG_EXT */ + if (status & CT0TC) { + /* clear CT0TC interrupt bit */ + outb(CLEAR_INTR_MASK & ~CT0TC, dev->iobase + DAS1800_STATUS); + /* get all remaining samples before quitting */ + if (devpriv->irq_dma_bits & DMA_ENABLED) + das1800_flush_dma(dev, s); + else + das1800_handle_fifo_not_empty(dev, s); + async->events |= COMEDI_CB_EOA; + } else if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + } + + comedi_handle_events(dev, s); +} + +static int das1800_ai_poll(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + + /* + * Protects the indirect addressing selected by DAS1800_SELECT + * in das1800_ai_handler() also prevents race with das1800_interrupt(). + */ + spin_lock_irqsave(&dev->spinlock, flags); + + das1800_ai_handler(dev); + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return comedi_buf_n_bytes_ready(s); +} + +static irqreturn_t das1800_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int status; + + if (!dev->attached) { + dev_err(dev->class_dev, "premature interrupt\n"); + return IRQ_HANDLED; + } + + /* + * Protects the indirect addressing selected by DAS1800_SELECT + * in das1800_ai_handler() also prevents race with das1800_ai_poll(). + */ + 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; +} + +static int das1800_ai_fixup_paced_timing(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + unsigned int arg = cmd->convert_arg; + + /* + * Paced mode: + * scan_begin_src is TRIG_FOLLOW + * convert_src is TRIG_TIMER + * + * The convert_arg sets the pacer sample acquisition time. + * The max acquisition speed is limited to the boards + * 'ai_speed' (this was already verified). The min speed is + * limited by the cascaded 8254 timer. + */ + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + return comedi_check_trigger_arg_is(&cmd->convert_arg, arg); +} + +static int das1800_ai_fixup_burst_timing(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + unsigned int arg = cmd->convert_arg; + int err = 0; + + /* + * Burst mode: + * scan_begin_src is TRIG_TIMER or TRIG_EXT + * convert_src is TRIG_TIMER + * + * The convert_arg sets burst sample acquisition time. + * The max acquisition speed is limited to the boards + * 'ai_speed' (this was already verified). The min speed is + * limiited to 64 microseconds, + */ + err |= comedi_check_trigger_arg_max(&arg, 64000); + + /* round to microseconds then verify */ + switch (cmd->flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + arg = DIV_ROUND_CLOSEST(arg, 1000); + break; + case CMDF_ROUND_DOWN: + arg = arg / 1000; + break; + case CMDF_ROUND_UP: + arg = DIV_ROUND_UP(arg, 1000); + break; + } + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg * 1000); + + /* + * The pacer can be used to set the scan sample rate. The max scan + * speed is limited by the conversion speed and the number of channels + * to convert. The min speed is limited by the cascaded 8254 timer. + */ + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->chanlist_len; + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg); + + arg = cmd->scan_begin_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + return err; +} + +static int das1800_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int range = CR_RANGE(cmd->chanlist[0]); + bool unipolar0 = comedi_range_is_unipolar(s, range); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + range = CR_RANGE(cmd->chanlist[i]); + + if (unipolar0 != comedi_range_is_unipolar(s, range)) { + dev_dbg(dev->class_dev, + "unipolar and bipolar ranges cannot be mixed in the chanlist\n"); + return -EINVAL; + } + } + + return 0; +} + +static int das1800_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct das1800_board *board = dev->board_ptr; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, + TRIG_COUNT | TRIG_EXT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + /* burst scans must use timed conversions */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->convert_src != TRIG_TIMER) + err |= -EINVAL; + + /* the external pin TGIN must use the same polarity */ + if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT) + err |= comedi_check_trigger_arg_is(&cmd->start_arg, + cmd->stop_arg); + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + if (cmd->start_arg == TRIG_NOW) + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + switch (cmd->stop_src) { + case TRIG_COUNT: + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + default: + break; + } + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->scan_begin_src == TRIG_FOLLOW) + err |= das1800_ai_fixup_paced_timing(dev, cmd); + else /* TRIG_TIMER or TRIG_EXT */ + err |= das1800_ai_fixup_burst_timing(dev, cmd); + } + + 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; +} + +static unsigned char das1800_ai_chanspec_bits(struct comedi_subdevice *s, + unsigned int chanspec) +{ + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + unsigned char bits; + + bits = UQEN; + if (aref != AREF_DIFF) + bits |= SD; + if (aref == AREF_COMMON) + bits |= CMEN; + if (comedi_range_is_unipolar(s, range)) + bits |= UB; + + return bits; +} + +static unsigned int das1800_ai_transfer_size(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int maxbytes, + unsigned int ns) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int max_samples = comedi_bytes_to_samples(s, maxbytes); + unsigned int samples; + + samples = max_samples; + + /* for timed modes, make dma buffer fill in 'ns' time */ + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: /* not in burst mode */ + if (cmd->convert_src == TRIG_TIMER) + samples = ns / cmd->convert_arg; + break; + case TRIG_TIMER: + samples = ns / (cmd->scan_begin_arg * cmd->chanlist_len); + break; + } + + /* limit samples to what is remaining in the command */ + samples = comedi_nsamples_left(s, samples); + + if (samples > max_samples) + samples = max_samples; + if (samples < 1) + samples = 1; + + return comedi_samples_to_bytes(s, samples); +} + +static void das1800_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das1800_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc; + unsigned int bytes; + + if ((devpriv->irq_dma_bits & DMA_ENABLED) == 0) + return; + + dma->cur_dma = 0; + desc = &dma->desc[0]; + + /* determine a dma transfer size to fill buffer in 0.3 sec */ + bytes = das1800_ai_transfer_size(dev, s, desc->maxsize, 300000000); + + desc->size = bytes; + comedi_isadma_program(desc); + + /* set up dual dma if appropriate */ + if (devpriv->irq_dma_bits & DMA_DUAL) { + desc = &dma->desc[1]; + desc->size = bytes; + comedi_isadma_program(desc); + } +} + +static void das1800_ai_set_chanlist(struct comedi_device *dev, + unsigned int *chanlist, unsigned int len) +{ + unsigned long flags; + unsigned int i; + + /* protects the indirect addressing selected by DAS1800_SELECT */ + spin_lock_irqsave(&dev->spinlock, flags); + + /* select QRAM register and set start address */ + outb(QRAM, dev->iobase + DAS1800_SELECT); + outb(len - 1, dev->iobase + DAS1800_QRAM_ADDRESS); + + /* make channel / gain list */ + for (i = 0; i < len; i++) { + unsigned int chan = CR_CHAN(chanlist[i]); + unsigned int range = CR_RANGE(chanlist[i]); + unsigned short val; + + val = chan | ((range & 0x3) << 8); + outw(val, dev->iobase + DAS1800_QRAM); + } + + /* finish write to QRAM */ + outb(len - 1, dev->iobase + DAS1800_QRAM_ADDRESS); + + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static int das1800_ai_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; + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + + /* + * Disable dma on CMDF_WAKE_EOS, or CMDF_PRIORITY (because dma in + * handler is unsafe at hard real-time priority). + */ + if (cmd->flags & (CMDF_WAKE_EOS | CMDF_PRIORITY)) + devpriv->irq_dma_bits &= ~DMA_ENABLED; + else + devpriv->irq_dma_bits |= devpriv->dma_bits; + /* interrupt on end of conversion for CMDF_WAKE_EOS */ + if (cmd->flags & CMDF_WAKE_EOS) { + /* interrupt fifo not empty */ + devpriv->irq_dma_bits &= ~FIMD; + } else { + /* interrupt fifo half full */ + devpriv->irq_dma_bits |= FIMD; + } + + das1800_ai_cancel(dev, s); + + devpriv->ai_is_unipolar = comedi_range_is_unipolar(s, range0); + + control_a = FFEN; + if (cmd->stop_src == TRIG_EXT) + control_a |= ATEN; + if (cmd->start_src == TRIG_EXT) + control_a |= TGEN | CGSL; + else /* TRIG_NOW */ + control_a |= CGEN; + if (control_a & (ATEN | TGEN)) { + if ((cmd->start_arg & CR_INVERT) || (cmd->stop_arg & CR_INVERT)) + control_a |= TGPL; + } + + control_c = das1800_ai_chanspec_bits(s, cmd->chanlist[0]); + /* set clock source to internal or external */ + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* not in burst mode */ + if (cmd->convert_src == TRIG_TIMER) { + /* trig on cascaded counters */ + control_c |= IPCLK; + } else { /* TRIG_EXT */ + /* trig on falling edge of external trigger */ + control_c |= XPCLK; + } + } else if (cmd->scan_begin_src == TRIG_TIMER) { + /* burst mode with internal pacer clock */ + control_c |= BMDE | IPCLK; + } else { /* TRIG_EXT */ + /* burst mode with external trigger */ + control_c |= BMDE | XPCLK; + } + + das1800_ai_set_chanlist(dev, cmd->chanlist, cmd->chanlist_len); + + /* setup cascaded counters for conversion/scan frequency */ + if ((cmd->scan_begin_src == TRIG_FOLLOW || + cmd->scan_begin_src == TRIG_TIMER) && + cmd->convert_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + } + + /* setup counter 0 for 'about triggering' */ + if (cmd->stop_src == TRIG_EXT) + comedi_8254_load(dev->pacer, 0, 1, I8254_MODE0 | I8254_BINARY); + + das1800_ai_setup_dma(dev, s); + outb(control_c, dev->iobase + DAS1800_CONTROL_C); + /* set conversion rate and length for burst mode */ + if (control_c & BMDE) { + outb(cmd->convert_arg / 1000 - 1, /* microseconds - 1 */ + dev->iobase + DAS1800_BURST_RATE); + outb(cmd->chanlist_len - 1, dev->iobase + DAS1800_BURST_LENGTH); + } + + /* enable and start conversions */ + outb(devpriv->irq_dma_bits, dev->iobase + DAS1800_CONTROL_B); + outb(control_a, dev->iobase + DAS1800_CONTROL_A); + outb(CVEN, dev->iobase + DAS1800_STATUS); + + return 0; +} + +static int das1800_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + DAS1800_STATUS); + if (status & FNE) + return 0; + return -EBUSY; +} + +static int das1800_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int range = CR_RANGE(insn->chanspec); + bool is_unipolar = comedi_range_is_unipolar(s, range); + int ret = 0; + int n; + unsigned short dpnt; + unsigned long flags; + + outb(das1800_ai_chanspec_bits(s, insn->chanspec), + dev->iobase + DAS1800_CONTROL_C); /* software pacer */ + outb(CVEN, dev->iobase + DAS1800_STATUS); /* enable conversions */ + outb(0x0, dev->iobase + DAS1800_CONTROL_A); /* reset fifo */ + outb(FFEN, dev->iobase + DAS1800_CONTROL_A); + + das1800_ai_set_chanlist(dev, &insn->chanspec, 1); + + /* protects the indirect addressing selected by DAS1800_SELECT */ + spin_lock_irqsave(&dev->spinlock, flags); + + /* select ai fifo register */ + outb(ADC, dev->iobase + DAS1800_SELECT); + + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outb(0, dev->iobase + DAS1800_FIFO); + + ret = comedi_timeout(dev, s, insn, das1800_ai_eoc, 0); + if (ret) + break; + + dpnt = inw(dev->iobase + DAS1800_FIFO); + if (!is_unipolar) + dpnt = comedi_offset_munge(s, dpnt); + data[n] = dpnt; + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + return ret ? ret : insn->n; +} + +static int das1800_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 update_chan = s->n_chan - 1; + unsigned long flags; + int i; + + /* protects the indirect addressing selected by DAS1800_SELECT */ + spin_lock_irqsave(&dev->spinlock, flags); + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + val = comedi_offset_munge(s, val); + + /* load this channel (and update if it's the last channel) */ + outb(DAC(chan), dev->iobase + DAS1800_SELECT); + outw(val, dev->iobase + DAS1800_DAC); + + /* update all channels */ + if (chan != update_chan) { + val = comedi_offset_munge(s, s->readback[update_chan]); + + outb(DAC(update_chan), dev->iobase + DAS1800_SELECT); + outw(val, dev->iobase + DAS1800_DAC); + } + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + return insn->n; +} + +static int das1800_di_insn_bits(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_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 + DAS1800_DIGITAL); + + data[1] = s->state; + + return insn->n; +} + +static void das1800_init_dma(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct das1800_private *devpriv = dev->private; + unsigned int *dma_chan; + + /* + * it->options[2] is DMA channel 0 + * it->options[3] is DMA channel 1 + * + * Encode the DMA channels into 2 digit hexadecimal for switch. + */ + dma_chan = &it->options[2]; + + switch ((dma_chan[0] & 0x7) | (dma_chan[1] << 4)) { + case 0x5: /* dma0 == 5 */ + devpriv->dma_bits = DMA_CH5; + break; + case 0x6: /* dma0 == 6 */ + devpriv->dma_bits = DMA_CH6; + break; + case 0x7: /* dma0 == 7 */ + devpriv->dma_bits = DMA_CH7; + break; + case 0x65: /* dma0 == 5, dma1 == 6 */ + devpriv->dma_bits = DMA_CH5_CH6; + break; + case 0x76: /* dma0 == 6, dma1 == 7 */ + devpriv->dma_bits = DMA_CH6_CH7; + break; + case 0x57: /* dma0 == 7, dma1 == 5 */ + devpriv->dma_bits = DMA_CH7_CH5; + break; + default: + return; + } + + /* DMA can use 1 or 2 buffers, each with a separate channel */ + devpriv->dma = comedi_isadma_alloc(dev, dma_chan[1] ? 2 : 1, + dma_chan[0], dma_chan[1], + DMA_BUF_SIZE, COMEDI_ISADMA_READ); + if (!devpriv->dma) + devpriv->dma_bits = 0; +} + +static void das1800_free_dma(struct comedi_device *dev) +{ + struct das1800_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} + +static int das1800_probe(struct comedi_device *dev) +{ + const struct das1800_board *board = dev->board_ptr; + unsigned char id; + + id = (inb(dev->iobase + DAS1800_DIGITAL) >> 4) & 0xf; + + /* + * The dev->board_ptr will be set by comedi_device_attach() if the + * board name provided by the user matches a board->name in this + * driver. If so, this function sanity checks the id to verify that + * the board is correct. + */ + if (board) { + if (board->id == id) + return 0; + dev_err(dev->class_dev, + "probed id does not match board id (0x%x != 0x%x)\n", + id, board->id); + return -ENODEV; + } + + /* + * If the dev->board_ptr is not set, the user is trying to attach + * an unspecified board to this driver. In this case the id is used + * to 'probe' for the dev->board_ptr. + */ + switch (id) { + case DAS1800_ID_ST_DA: + /* das-1701st-da, das-1702st-da, das-1801st-da, das-1802st-da */ + board = &das1800_boards[BOARD_DAS1801ST_DA]; + break; + case DAS1800_ID_HR_DA: + /* das-1702hr-da, das-1802hr-da */ + board = &das1800_boards[BOARD_DAS1802HR_DA]; + break; + case DAS1800_ID_AO: + /* das-1701ao, das-1702ao, das-1801ao, das-1802ao */ + board = &das1800_boards[BOARD_DAS1801AO]; + break; + case DAS1800_ID_HR: + /* das-1702hr, das-1802hr */ + board = &das1800_boards[BOARD_DAS1802HR]; + break; + case DAS1800_ID_ST: + /* das-1701st, das-1702st, das-1801st, das-1802st */ + board = &das1800_boards[BOARD_DAS1801ST]; + break; + case DAS1800_ID_HC: + /* das-1801hc, das-1802hc */ + board = &das1800_boards[BOARD_DAS1801HC]; + break; + default: + dev_err(dev->class_dev, "invalid probe id 0x%x\n", id); + return -ENODEV; + } + dev->board_ptr = board; + dev->board_name = board->name; + dev_warn(dev->class_dev, + "probed id 0x%0x: %s series (not recommended)\n", + id, board->name); + return 0; +} + +static int das1800_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct das1800_board *board; + struct das1800_private *devpriv; + struct comedi_subdevice *s; + unsigned int irq = it->options[1]; + bool is_16bit; + int ret; + int i; + + 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; + + ret = das1800_probe(dev); + if (ret) + return ret; + board = dev->board_ptr; + + is_16bit = board->id == DAS1800_ID_HR || board->id == DAS1800_ID_HR_DA; + + /* waveform 'ao' boards have additional io ports */ + if (board->id == DAS1800_ID_AO) { + unsigned long iobase2 = dev->iobase + IOBASE2; + + ret = __comedi_request_region(dev, iobase2, DAS1800_SIZE); + if (ret) + return ret; + devpriv->iobase2 = iobase2; + } + + if (irq == 3 || irq == 5 || irq == 7 || irq == 10 || irq == 11 || + irq == 15) { + ret = request_irq(irq, das1800_interrupt, 0, + dev->board_name, dev); + if (ret == 0) { + dev->irq = irq; + + switch (irq) { + case 3: + devpriv->irq_dma_bits |= 0x8; + break; + case 5: + devpriv->irq_dma_bits |= 0x10; + break; + case 7: + devpriv->irq_dma_bits |= 0x18; + break; + case 10: + devpriv->irq_dma_bits |= 0x28; + break; + case 11: + devpriv->irq_dma_bits |= 0x30; + break; + case 15: + devpriv->irq_dma_bits |= 0x38; + break; + } + } + } + + /* an irq and one dma channel is required to use dma */ + if (dev->irq & it->options[2]) + das1800_init_dma(dev, it); + + devpriv->fifo_buf = kmalloc_array(FIFO_SIZE, + sizeof(*devpriv->fifo_buf), + GFP_KERNEL); + if (!devpriv->fifo_buf) + return -ENOMEM; + + dev->pacer = comedi_8254_init(dev->iobase + DAS1800_COUNTER, + I8254_OSC_BASE_5MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* + * Analog Input subdevice + * + * The "hc" type boards have 64 analog input channels and a 64 + * entry QRAM fifo. + * + * All the other board types have 16 on-board channels. Each channel + * can be expanded to 16 channels with the addition of an EXP-1800 + * expansion board for a total of 256 channels. The QRAM fifo on + * these boards has 256 entries. + * + * From the datasheets it's not clear what the comedi channel to + * actual physical channel mapping is when EXP-1800 boards are used. + */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND; + if (board->id != DAS1800_ID_HC) + s->subdev_flags |= SDF_COMMON; + s->n_chan = (board->id == DAS1800_ID_HC) ? 64 : 256; + s->maxdata = is_16bit ? 0xffff : 0x0fff; + s->range_table = board->is_01_series ? &das1801_ai_range + : &das1802_ai_range; + s->insn_read = das1800_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmd = das1800_ai_cmd; + s->do_cmdtest = das1800_ai_cmdtest; + s->poll = das1800_ai_poll; + s->cancel = das1800_ai_cancel; + s->munge = das1800_ai_munge; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->id == DAS1800_ID_ST_DA || board->id == DAS1800_ID_HR_DA) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = (board->id == DAS1800_ID_ST_DA) ? 4 : 2; + s->maxdata = is_16bit ? 0xffff : 0x0fff; + s->range_table = &range_bipolar10; + s->insn_write = das1800_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* initialize all channels to 0V */ + for (i = 0; i < s->n_chan; i++) { + /* spinlock is not necessary during the attach */ + outb(DAC(i), dev->iobase + DAS1800_SELECT); + outw(0, dev->iobase + DAS1800_DAC); + } + } else if (board->id == DAS1800_ID_AO) { + /* + * 'ao' boards have waveform analog outputs that are not + * currently supported. + */ + s->type = COMEDI_SUBD_UNUSED; + } 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 = das1800_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = (board->id == DAS1800_ID_HC) ? 8 : 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das1800_do_insn_bits; + + das1800_ai_cancel(dev, dev->read_subdev); + + /* initialize digital out channels */ + outb(0, dev->iobase + DAS1800_DIGITAL); + + return 0; +}; + +static void das1800_detach(struct comedi_device *dev) +{ + struct das1800_private *devpriv = dev->private; + + das1800_free_dma(dev); + if (devpriv) { + kfree(devpriv->fifo_buf); + if (devpriv->iobase2) + release_region(devpriv->iobase2, DAS1800_SIZE); + } + comedi_legacy_detach(dev); +} + +static struct comedi_driver das1800_driver = { + .driver_name = "das1800", + .module = THIS_MODULE, + .attach = das1800_attach, + .detach = das1800_detach, + .num_names = ARRAY_SIZE(das1800_boards), + .board_name = &das1800_boards[0].name, + .offset = sizeof(struct das1800_board), +}; +module_comedi_driver(das1800_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for DAS1800 compatible ISA boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das6402.c b/drivers/comedi/drivers/das6402.c new file mode 100644 index 000000000..1af394591 --- /dev/null +++ b/drivers/comedi/drivers/das6402.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: das6402 + * Description: Keithley Metrabyte DAS6402 (& compatibles) + * Devices: [Keithley Metrabyte] DAS6402-12 (das6402-12), + * DAS6402-16 (das6402-16) + * Author: H Hartley Sweeten <hsweeten@visionengravers.com> + * Updated: Fri, 14 Mar 2014 10:18:43 -0700 + * Status: unknown + * + * Configuration Options: + * [0] - I/O base address + * [1] - IRQ (optional, needed for async command support) + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> + +/* + * Register I/O map + */ +#define DAS6402_AI_DATA_REG 0x00 +#define DAS6402_AI_MUX_REG 0x02 +#define DAS6402_AI_MUX_LO(x) (((x) & 0x3f) << 0) +#define DAS6402_AI_MUX_HI(x) (((x) & 0x3f) << 8) +#define DAS6402_DI_DO_REG 0x03 +#define DAS6402_AO_DATA_REG(x) (0x04 + ((x) * 2)) +#define DAS6402_AO_LSB_REG(x) (0x04 + ((x) * 2)) +#define DAS6402_AO_MSB_REG(x) (0x05 + ((x) * 2)) +#define DAS6402_STATUS_REG 0x08 +#define DAS6402_STATUS_FFNE BIT(0) +#define DAS6402_STATUS_FHALF BIT(1) +#define DAS6402_STATUS_FFULL BIT(2) +#define DAS6402_STATUS_XINT BIT(3) +#define DAS6402_STATUS_INT BIT(4) +#define DAS6402_STATUS_XTRIG BIT(5) +#define DAS6402_STATUS_INDGT BIT(6) +#define DAS6402_STATUS_10MHZ BIT(7) +#define DAS6402_STATUS_W_CLRINT BIT(0) +#define DAS6402_STATUS_W_CLRXTR BIT(1) +#define DAS6402_STATUS_W_CLRXIN BIT(2) +#define DAS6402_STATUS_W_EXTEND BIT(4) +#define DAS6402_STATUS_W_ARMED BIT(5) +#define DAS6402_STATUS_W_POSTMODE BIT(6) +#define DAS6402_STATUS_W_10MHZ BIT(7) +#define DAS6402_CTRL_REG 0x09 +#define DAS6402_CTRL_TRIG(x) ((x) << 0) +#define DAS6402_CTRL_SOFT_TRIG DAS6402_CTRL_TRIG(0) +#define DAS6402_CTRL_EXT_FALL_TRIG DAS6402_CTRL_TRIG(1) +#define DAS6402_CTRL_EXT_RISE_TRIG DAS6402_CTRL_TRIG(2) +#define DAS6402_CTRL_PACER_TRIG DAS6402_CTRL_TRIG(3) +#define DAS6402_CTRL_BURSTEN BIT(2) +#define DAS6402_CTRL_XINTE BIT(3) +#define DAS6402_CTRL_IRQ(x) ((x) << 4) +#define DAS6402_CTRL_INTE BIT(7) +#define DAS6402_TRIG_REG 0x0a +#define DAS6402_TRIG_TGEN BIT(0) +#define DAS6402_TRIG_TGSEL BIT(1) +#define DAS6402_TRIG_TGPOL BIT(2) +#define DAS6402_TRIG_PRETRIG BIT(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) << 2) +#define DAS6402_MODE_POLLED DAS6402_MODE_RANGE(0) +#define DAS6402_MODE_FIFONEPTY DAS6402_MODE_RANGE(1) +#define DAS6402_MODE_FIFOHFULL DAS6402_MODE_RANGE(2) +#define DAS6402_MODE_EOB DAS6402_MODE_RANGE(3) +#define DAS6402_MODE_ENHANCED BIT(4) +#define DAS6402_MODE_SE BIT(5) +#define DAS6402_MODE_UNI BIT(6) +#define DAS6402_MODE_DMA(x) ((x) << 7) +#define DAS6402_MODE_DMA1 DAS6402_MODE_DMA(0) +#define DAS6402_MODE_DMA3 DAS6402_MODE_DMA(1) +#define DAS6402_TIMER_BASE 0x0c + +static const struct comedi_lrange das6402_ai_ranges = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +/* + * Analog output ranges are programmable on the DAS6402/12. + * For the DAS6402/16 the range bits have no function, the + * DAC ranges are selected by switches on the board. + */ +static const struct comedi_lrange das6402_ao_ranges = { + 4, { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(10) + } +}; + +struct das6402_boardinfo { + const char *name; + unsigned int maxdata; +}; + +static struct das6402_boardinfo das6402_boards[] = { + { + .name = "das6402-12", + .maxdata = 0x0fff, + }, { + .name = "das6402-16", + .maxdata = 0xffff, + }, +}; + +struct das6402_private { + unsigned int irq; + unsigned int ao_range; +}; + +static void das6402_set_mode(struct comedi_device *dev, + unsigned int mode) +{ + outb(DAS6402_MODE_ENHANCED | mode, dev->iobase + DAS6402_MODE_REG); +} + +static void das6402_set_extended(struct comedi_device *dev, + unsigned int val) +{ + outb(DAS6402_STATUS_W_EXTEND, dev->iobase + DAS6402_STATUS_REG); + outb(DAS6402_STATUS_W_EXTEND | val, dev->iobase + DAS6402_STATUS_REG); + outb(val, dev->iobase + DAS6402_STATUS_REG); +} + +static void das6402_clear_all_interrupts(struct comedi_device *dev) +{ + outb(DAS6402_STATUS_W_CLRINT | + DAS6402_STATUS_W_CLRXTR | + DAS6402_STATUS_W_CLRXIN, dev->iobase + DAS6402_STATUS_REG); +} + +static void das6402_ai_clear_eoc(struct comedi_device *dev) +{ + outb(DAS6402_STATUS_W_CLRINT, dev->iobase + DAS6402_STATUS_REG); +} + +static unsigned int das6402_ai_read_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + val = inw(dev->iobase + DAS6402_AI_DATA_REG); + if (s->maxdata == 0x0fff) + val >>= 4; + return val; +} + +static irqreturn_t das6402_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status; + + status = inb(dev->iobase + DAS6402_STATUS_REG); + if ((status & DAS6402_STATUS_INT) == 0) + return IRQ_NONE; + + if (status & DAS6402_STATUS_FFULL) { + async->events |= COMEDI_CB_OVERFLOW; + } else if (status & DAS6402_STATUS_FFNE) { + unsigned short val; + + val = das6402_ai_read_sample(dev, s); + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + } + + das6402_clear_all_interrupts(dev); + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static void das6402_ai_set_mode(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec, + unsigned int mode) +{ + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + + mode |= DAS6402_MODE_RANGE(range); + if (aref == AREF_GROUND) + mode |= DAS6402_MODE_SE; + if (comedi_range_is_unipolar(s, range)) + mode |= DAS6402_MODE_UNI; + + das6402_set_mode(dev, mode); +} + +static int das6402_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct das6402_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan_lo = CR_CHAN(cmd->chanlist[0]); + unsigned int chan_hi = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + + das6402_ai_set_mode(dev, s, cmd->chanlist[0], DAS6402_MODE_FIFONEPTY); + + /* load the mux for chanlist conversion */ + outw(DAS6402_AI_MUX_HI(chan_hi) | DAS6402_AI_MUX_LO(chan_lo), + dev->iobase + DAS6402_AI_MUX_REG); + + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + + /* enable interrupt and pacer trigger */ + outb(DAS6402_CTRL_INTE | + DAS6402_CTRL_IRQ(devpriv->irq) | + DAS6402_CTRL_PACER_TRIG, dev->iobase + DAS6402_CTRL_REG); + + return 0; +} + +static int das6402_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (chan != chan0 + i) { + dev_dbg(dev->class_dev, + "chanlist must be consecutive\n"); + return -EINVAL; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "chanlist must have the same range\n"); + return -EINVAL; + } + + if (aref != aref0) { + dev_dbg(dev->class_dev, + "chanlist must have the same reference\n"); + return -EINVAL; + } + + if (aref0 == AREF_DIFF && chan > (s->n_chan / 2)) { + dev_dbg(dev->class_dev, + "chanlist differential channel too large\n"); + return -EINVAL; + } + } + return 0; +} + +static int das6402_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->convert_arg; + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= das6402_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int das6402_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG); + + return 0; +} + +static void das6402_ai_soft_trig(struct comedi_device *dev) +{ + outw(0, dev->iobase + DAS6402_AI_DATA_REG); +} + +static int das6402_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + DAS6402_STATUS_REG); + if (status & DAS6402_STATUS_FFNE) + return 0; + return -EBUSY; +} + +static int das6402_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int aref = CR_AREF(insn->chanspec); + int ret; + int i; + + if (aref == AREF_DIFF && chan > (s->n_chan / 2)) + return -EINVAL; + + /* enable software conversion trigger */ + outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG); + + das6402_ai_set_mode(dev, s, insn->chanspec, DAS6402_MODE_POLLED); + + /* load the mux for single channel conversion */ + outw(DAS6402_AI_MUX_HI(chan) | DAS6402_AI_MUX_LO(chan), + dev->iobase + DAS6402_AI_MUX_REG); + + for (i = 0; i < insn->n; i++) { + das6402_ai_clear_eoc(dev); + das6402_ai_soft_trig(dev); + + ret = comedi_timeout(dev, s, insn, das6402_ai_eoc, 0); + if (ret) + break; + + data[i] = das6402_ai_read_sample(dev, s); + } + + das6402_ai_clear_eoc(dev); + + return insn->n; +} + +static int das6402_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct das6402_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int i; + + /* set the range for this channel */ + val = devpriv->ao_range; + val &= ~DAS6402_AO_RANGE_MASK(chan); + val |= DAS6402_AO_RANGE(chan, range); + if (val != devpriv->ao_range) { + devpriv->ao_range = val; + outb(val, dev->iobase + DAS6402_TRIG_REG); + } + + /* + * The DAS6402/16 has a jumper to select either individual + * update (UPDATE) or simultaneous updating (XFER) of both + * DAC's. In UPDATE mode, when the MSB is written, that DAC + * is updated. In XFER mode, after both DAC's are loaded, + * a read cycle of any DAC register will update both DAC's + * simultaneously. + * + * If you have XFER mode enabled a (*insn_read) will need + * to be performed in order to update the DAC's with the + * last value written. + */ + for (i = 0; i < insn->n; i++) { + val = data[i]; + + s->readback[chan] = val; + + if (s->maxdata == 0x0fff) { + /* + * DAS6402/12 has the two 8-bit DAC registers, left + * justified (the 4 LSB bits are don't care). Data + * can be written as one word. + */ + val <<= 4; + outw(val, dev->iobase + DAS6402_AO_DATA_REG(chan)); + } else { + /* + * DAS6402/16 uses both 8-bit DAC registers and needs + * to be written LSB then MSB. + */ + outb(val & 0xff, + dev->iobase + DAS6402_AO_LSB_REG(chan)); + outb((val >> 8) & 0xff, + dev->iobase + DAS6402_AO_LSB_REG(chan)); + } + } + + return insn->n; +} + +static int das6402_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* + * If XFER mode is enabled, reading any DAC register + * will update both DAC's simultaneously. + */ + inw(dev->iobase + DAS6402_AO_LSB_REG(chan)); + + return comedi_readback_insn_read(dev, s, insn, data); +} + +static int das6402_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + DAS6402_DI_DO_REG); + + return insn->n; +} + +static int das6402_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outb(s->state, dev->iobase + DAS6402_DI_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static void das6402_reset(struct comedi_device *dev) +{ + struct das6402_private *devpriv = dev->private; + + /* enable "Enhanced" mode */ + outb(DAS6402_MODE_ENHANCED, dev->iobase + DAS6402_MODE_REG); + + /* enable 10MHz pacer clock */ + das6402_set_extended(dev, DAS6402_STATUS_W_10MHZ); + + /* enable software conversion trigger */ + outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG); + + /* default ADC to single-ended unipolar 10V inputs */ + das6402_set_mode(dev, DAS6402_MODE_RANGE(0) | + DAS6402_MODE_POLLED | + DAS6402_MODE_SE | + DAS6402_MODE_UNI); + + /* default mux for single channel conversion (channel 0) */ + outw(DAS6402_AI_MUX_HI(0) | DAS6402_AI_MUX_LO(0), + dev->iobase + DAS6402_AI_MUX_REG); + + /* set both DAC's for unipolar 5V output range */ + devpriv->ao_range = DAS6402_AO_RANGE(0, 2) | DAS6402_AO_RANGE(1, 2); + outb(devpriv->ao_range, dev->iobase + DAS6402_TRIG_REG); + + /* set both DAC's to 0V */ + outw(0, dev->iobase + DAS6402_AO_DATA_REG(0)); + outw(0, dev->iobase + DAS6402_AO_DATA_REG(0)); + inw(dev->iobase + DAS6402_AO_LSB_REG(0)); + + /* set all digital outputs low */ + outb(0, dev->iobase + DAS6402_DI_DO_REG); + + das6402_clear_all_interrupts(dev); +} + +static int das6402_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct das6402_boardinfo *board = dev->board_ptr; + struct das6402_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + das6402_reset(dev); + + /* IRQs 2,3,5,6,7, 10,11,15 are valid for "enhanced" mode */ + if ((1 << it->options[1]) & 0x8cec) { + ret = request_irq(it->options[1], das6402_interrupt, 0, + dev->board_name, dev); + if (ret == 0) { + dev->irq = it->options[1]; + + switch (dev->irq) { + case 10: + devpriv->irq = 4; + break; + case 11: + devpriv->irq = 1; + break; + case 15: + devpriv->irq = 6; + break; + default: + devpriv->irq = dev->irq; + break; + } + } + } + + dev->pacer = comedi_8254_init(dev->iobase + DAS6402_TIMER_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 64; + s->maxdata = board->maxdata; + s->range_table = &das6402_ai_ranges; + s->insn_read = das6402_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = das6402_ai_cmdtest; + s->do_cmd = das6402_ai_cmd; + s->cancel = das6402_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = board->maxdata; + s->range_table = &das6402_ao_ranges; + s->insn_write = das6402_ao_insn_write; + s->insn_read = das6402_ao_insn_read; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das6402_di_insn_bits; + + /* Digital Input subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das6402_do_insn_bits; + + return 0; +} + +static struct comedi_driver das6402_driver = { + .driver_name = "das6402", + .module = THIS_MODULE, + .attach = das6402_attach, + .detach = comedi_legacy_detach, + .board_name = &das6402_boards[0].name, + .num_names = ARRAY_SIZE(das6402_boards), + .offset = sizeof(struct das6402_boardinfo), +}; +module_comedi_driver(das6402_driver) + +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_DESCRIPTION("Comedi driver for DAS6402 compatible boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/das800.c b/drivers/comedi/drivers/das800.c new file mode 100644 index 000000000..4ca33f46e --- /dev/null +++ b/drivers/comedi/drivers/das800.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * Driver: das800 + * Description: Keithley Metrabyte DAS800 (& compatibles) + * Author: Frank Mori Hess <fmhess@users.sourceforge.net> + * Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801), + * DAS-802 (das-802), + * [Measurement Computing] CIO-DAS800 (cio-das800), + * CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802), + * CIO-DAS802/16 (cio-das802/16) + * Status: works, cio-das802/16 untested - email me if you have tested it + * + * Configuration options: + * [0] - I/O port base address + * [1] - IRQ (optional, required for timed or externally triggered conversions) + * + * Notes: + * IRQ can be omitted, although the cmd interface will not work without it. + * + * All entries in the channel/gain list must use the same gain and be + * consecutive channels counting upwards in channel number (these are + * hardware limitations.) + * + * I've never tested the gain setting stuff since I only have a + * DAS-800 board with fixed gain. + * + * The cio-das802/16 does not have a fifo-empty status bit! Therefore + * only fifo-half-full transfers are possible with this card. + * + * cmd triggers supported: + * start_src: TRIG_NOW | TRIG_EXT + * scan_begin_src: TRIG_FOLLOW + * scan_end_src: TRIG_COUNT + * convert_src: TRIG_TIMER | TRIG_EXT + * stop_src: TRIG_NONE | TRIG_COUNT + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> + +#define N_CHAN_AI 8 /* number of analog input channels */ + +/* Registers for the das800 */ + +#define DAS800_LSB 0 +#define FIFO_EMPTY 0x1 +#define FIFO_OVF 0x2 +#define DAS800_MSB 1 +#define DAS800_CONTROL1 2 +#define CONTROL1_INTE 0x8 +#define DAS800_CONV_CONTROL 2 +#define ITE 0x1 +#define CASC 0x2 +#define DTEN 0x4 +#define IEOC 0x8 +#define EACS 0x10 +#define CONV_HCEN 0x80 +#define DAS800_SCAN_LIMITS 2 +#define DAS800_STATUS 2 +#define IRQ 0x8 +#define BUSY 0x80 +#define DAS800_GAIN 3 +#define CIO_FFOV 0x8 /* cio-das802/16 fifo overflow */ +#define CIO_ENHF 0x90 /* cio-das802/16 fifo half full int ena */ +#define CONTROL1 0x80 +#define CONV_CONTROL 0xa0 +#define SCAN_LIMITS 0xc0 +#define ID 0xe0 +#define DAS800_8254 4 +#define DAS800_STATUS2 7 +#define STATUS2_HCEN 0x80 +#define STATUS2_INTE 0X20 +#define DAS800_ID 7 + +#define DAS802_16_HALF_FIFO_SZ 128 + +struct das800_board { + const char *name; + int ai_speed; + const struct comedi_lrange *ai_range; + int resolution; +}; + +static const struct comedi_lrange range_das801_ai = { + 9, { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(10), + BIP_RANGE(0.5), + UNI_RANGE(1), + BIP_RANGE(0.05), + UNI_RANGE(0.1), + BIP_RANGE(0.01), + UNI_RANGE(0.02) + } +}; + +static const struct comedi_lrange range_cio_das801_ai = { + 9, { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(10), + BIP_RANGE(0.5), + UNI_RANGE(1), + BIP_RANGE(0.05), + UNI_RANGE(0.1), + BIP_RANGE(0.005), + UNI_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_das802_ai = { + 9, { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(10), + BIP_RANGE(2.5), + UNI_RANGE(5), + BIP_RANGE(1.25), + UNI_RANGE(2.5), + BIP_RANGE(0.625), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_das80216_ai = { + 8, { + BIP_RANGE(10), + UNI_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(5), + BIP_RANGE(2.5), + UNI_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(1.25) + } +}; + +enum das800_boardinfo { + BOARD_DAS800, + BOARD_CIODAS800, + BOARD_DAS801, + BOARD_CIODAS801, + BOARD_DAS802, + BOARD_CIODAS802, + BOARD_CIODAS80216, +}; + +static const struct das800_board das800_boards[] = { + [BOARD_DAS800] = { + .name = "das-800", + .ai_speed = 25000, + .ai_range = &range_bipolar5, + .resolution = 12, + }, + [BOARD_CIODAS800] = { + .name = "cio-das800", + .ai_speed = 20000, + .ai_range = &range_bipolar5, + .resolution = 12, + }, + [BOARD_DAS801] = { + .name = "das-801", + .ai_speed = 25000, + .ai_range = &range_das801_ai, + .resolution = 12, + }, + [BOARD_CIODAS801] = { + .name = "cio-das801", + .ai_speed = 20000, + .ai_range = &range_cio_das801_ai, + .resolution = 12, + }, + [BOARD_DAS802] = { + .name = "das-802", + .ai_speed = 25000, + .ai_range = &range_das802_ai, + .resolution = 12, + }, + [BOARD_CIODAS802] = { + .name = "cio-das802", + .ai_speed = 20000, + .ai_range = &range_das802_ai, + .resolution = 12, + }, + [BOARD_CIODAS80216] = { + .name = "cio-das802/16", + .ai_speed = 10000, + .ai_range = &range_das80216_ai, + .resolution = 16, + }, +}; + +struct das800_private { + unsigned int do_bits; /* digital output bits */ +}; + +static void das800_ind_write(struct comedi_device *dev, + unsigned int val, unsigned int 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 int das800_ind_read(struct comedi_device *dev, unsigned int 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 *board = dev->board_ptr; + struct das800_private *devpriv = dev->private; + unsigned long irq_flags; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* enable fifo-half full interrupts for cio-das802/16 */ + if (board->resolution == 16) + outb(CIO_ENHF, dev->iobase + DAS800_GAIN); + /* enable hardware triggering */ + das800_ind_write(dev, CONV_HCEN, CONV_CONTROL); + /* enable card's interrupt */ + das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); +} + +static void das800_disable(struct comedi_device *dev) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* disable hardware triggering of conversions */ + das800_ind_write(dev, 0x0, CONV_CONTROL); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); +} + +static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + das800_disable(dev); + return 0; +} + +static int das800_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + if (chan != (chan0 + i) % s->n_chan) { + dev_dbg(dev->class_dev, + "chanlist must be consecutive, counting upwards\n"); + return -EINVAL; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "chanlist must all have the same gain\n"); + return -EINVAL; + } + } + + return 0; +} + +static int das800_ai_do_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct das800_board *board = dev->board_ptr; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= das800_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int das800_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct das800_board *board = dev->board_ptr; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int gain = CR_RANGE(cmd->chanlist[0]); + unsigned int start_chan = CR_CHAN(cmd->chanlist[0]); + unsigned int end_chan = (start_chan + cmd->chanlist_len - 1) % 8; + unsigned int scan_chans = (end_chan << 3) | start_chan; + int conv_bits; + unsigned long irq_flags; + + das800_disable(dev); + + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* set scan limits */ + das800_ind_write(dev, scan_chans, SCAN_LIMITS); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + /* set gain */ + if (board->resolution == 12 && gain > 0) + gain += 0x7; + gain &= 0xf; + outb(gain, dev->iobase + DAS800_GAIN); + + /* enable auto channel scan, send interrupts on end of conversion + * and set clock source to internal or external + */ + conv_bits = 0; + conv_bits |= EACS | IEOC; + if (cmd->start_src == TRIG_EXT) + conv_bits |= DTEN; + if (cmd->convert_src == TRIG_TIMER) { + conv_bits |= CASC | ITE; + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + } + + spin_lock_irqsave(&dev->spinlock, irq_flags); + das800_ind_write(dev, conv_bits, CONV_CONTROL); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + das800_enable(dev); + return 0; +} + +static unsigned int das800_ai_get_sample(struct comedi_device *dev) +{ + unsigned int lsb = inb(dev->iobase + DAS800_LSB); + unsigned int msb = inb(dev->iobase + DAS800_MSB); + + return (msb << 8) | lsb; +} + +static irqreturn_t das800_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct das800_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + unsigned long irq_flags; + unsigned int status; + unsigned short val; + bool fifo_empty; + bool fifo_overflow; + int i; + + status = inb(dev->iobase + DAS800_STATUS); + if (!(status & IRQ)) + return IRQ_NONE; + if (!dev->attached) + return IRQ_HANDLED; + + async = s->async; + cmd = &async->cmd; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + status = das800_ind_read(dev, CONTROL1) & STATUS2_HCEN; + /* + * Don't release spinlock yet since we want to make sure + * no one else disables hardware conversions. + */ + + /* if hardware conversions are not enabled, then quit */ + if (status == 0) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + return IRQ_HANDLED; + } + + for (i = 0; i < DAS802_16_HALF_FIFO_SZ; i++) { + val = das800_ai_get_sample(dev); + if (s->maxdata == 0x0fff) { + fifo_empty = !!(val & FIFO_EMPTY); + fifo_overflow = !!(val & FIFO_OVF); + } else { + /* cio-das802/16 has no fifo empty status bit */ + fifo_empty = false; + fifo_overflow = !!(inb(dev->iobase + DAS800_GAIN) & + CIO_FFOV); + } + if (fifo_empty || fifo_overflow) + break; + + if (s->maxdata == 0x0fff) + val >>= 4; /* 12-bit sample */ + + val &= s->maxdata; + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + + if (fifo_overflow) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + return IRQ_HANDLED; + } + + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + /* + * Re-enable card's interrupt. + * We already have spinlock, so indirect addressing is safe + */ + das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, + CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + } else { + /* otherwise, stop taking data */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + das800_disable(dev); + } + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int das800_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + DAS800_STATUS); + if ((status & BUSY) == 0) + return 0; + return -EBUSY; +} + +static int das800_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct das800_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned long irq_flags; + unsigned int val; + int ret; + int i; + + das800_disable(dev); + + /* set multiplexer */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + das800_ind_write(dev, chan | devpriv->do_bits, CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + /* set gain / range */ + if (s->maxdata == 0x0fff && range) + range += 0x7; + range &= 0xf; + outb(range, dev->iobase + DAS800_GAIN); + + udelay(5); + + for (i = 0; i < insn->n; i++) { + /* trigger conversion */ + outb_p(0, dev->iobase + DAS800_MSB); + + ret = comedi_timeout(dev, s, insn, das800_ai_eoc, 0); + if (ret) + return ret; + + val = das800_ai_get_sample(dev); + if (s->maxdata == 0x0fff) + val >>= 4; /* 12-bit sample */ + data[i] = val & s->maxdata; + } + + return insn->n; +} + +static int das800_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = (inb(dev->iobase + DAS800_STATUS) >> 4) & 0x7; + + return insn->n; +} + +static int das800_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct das800_private *devpriv = dev->private; + unsigned long irq_flags; + + if (comedi_dio_update_state(s, data)) { + devpriv->do_bits = s->state << 4; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, + CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + } + + data[1] = s->state; + + return insn->n; +} + +static const struct das800_board *das800_probe(struct comedi_device *dev) +{ + const struct das800_board *board = dev->board_ptr; + int index = board ? board - das800_boards : -EINVAL; + int id_bits; + unsigned long irq_flags; + + /* + * The dev->board_ptr will be set by comedi_device_attach() if the + * board name provided by the user matches a board->name in this + * driver. If so, this function sanity checks the id_bits to verify + * that the board is correct. + * + * If the dev->board_ptr is not set, the user is trying to attach + * an unspecified board to this driver. In this case the id_bits + * are used to 'probe' for the correct dev->board_ptr. + */ + 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 (index == BOARD_DAS800 || index == BOARD_CIODAS800) + return board; + index = BOARD_DAS800; + break; + case 0x2: + if (index == BOARD_DAS801 || index == BOARD_CIODAS801) + return board; + index = BOARD_DAS801; + break; + case 0x3: + if (index == BOARD_DAS802 || index == BOARD_CIODAS802 || + index == BOARD_CIODAS80216) + return board; + index = BOARD_DAS802; + break; + default: + dev_dbg(dev->class_dev, "Board model: 0x%x (unknown)\n", + id_bits); + return NULL; + } + dev_dbg(dev->class_dev, "Board model (probed): %s series\n", + das800_boards[index].name); + + return &das800_boards[index]; +} + +static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct das800_board *board; + struct das800_private *devpriv; + struct comedi_subdevice *s; + unsigned int irq = it->options[1]; + unsigned long irq_flags; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x8); + if (ret) + return ret; + + board = das800_probe(dev); + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + if (irq > 1 && irq <= 7) { + ret = request_irq(irq, das800_interrupt, 0, "das800", + dev); + if (ret == 0) + dev->irq = irq; + } + + dev->pacer = comedi_8254_init(dev->iobase + DAS800_8254, + I8254_OSC_BASE_1MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 8; + s->maxdata = (1 << board->resolution) - 1; + s->range_table = board->ai_range; + s->insn_read = das800_ai_insn_read; + if (dev->irq) { + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 8; + s->do_cmdtest = das800_ai_do_cmdtest; + s->do_cmd = das800_ai_do_cmd; + s->cancel = das800_cancel; + } + + /* Digital Input subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 3; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das800_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das800_do_insn_bits; + + das800_disable(dev); + + /* initialize digital out channels */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return 0; +}; + +static struct comedi_driver driver_das800 = { + .driver_name = "das800", + .module = THIS_MODULE, + .attach = das800_attach, + .detach = comedi_legacy_detach, + .num_names = ARRAY_SIZE(das800_boards), + .board_name = &das800_boards[0].name, + .offset = sizeof(struct das800_board), +}; +module_comedi_driver(driver_das800); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dmm32at.c b/drivers/comedi/drivers/dmm32at.c new file mode 100644 index 000000000..fe023c722 --- /dev/null +++ b/drivers/comedi/drivers/dmm32at.c @@ -0,0 +1,615 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dmm32at.c + * Diamond Systems Diamond-MM-32-AT Comedi driver + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: dmm32at + * Description: Diamond Systems Diamond-MM-32-AT + * Devices: [Diamond Systems] Diamond-MM-32-AT (dmm32at) + * Author: Perry J. Piplani <perry.j.piplani@nasa.gov> + * Updated: Fri Jun 4 09:13:24 CDT 2004 + * Status: experimental + * + * Configuration Options: + * comedi_config /dev/comedi0 dmm32at baseaddr,irq + * + * This driver is for the Diamond Systems MM-32-AT board + * http://www.diamondsystems.com/products/diamondmm32at + * + * It is being used on several projects inside NASA, without + * problems so far. For analog input commands, TRIG_EXT is not + * yet supported. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +/* Board register addresses */ +#define DMM32AT_AI_START_CONV_REG 0x00 +#define DMM32AT_AI_LSB_REG 0x00 +#define DMM32AT_AUX_DOUT_REG 0x01 +#define DMM32AT_AUX_DOUT2 BIT(2) /* J3.42 - OUT2 (OUT2EN) */ +#define DMM32AT_AUX_DOUT1 BIT(1) /* J3.43 */ +#define DMM32AT_AUX_DOUT0 BIT(0) /* J3.44 - OUT0 (OUT0EN) */ +#define DMM32AT_AI_MSB_REG 0x01 +#define DMM32AT_AI_LO_CHAN_REG 0x02 +#define DMM32AT_AI_HI_CHAN_REG 0x03 +#define DMM32AT_AUX_DI_REG 0x04 +#define DMM32AT_AUX_DI_DACBUSY BIT(7) +#define DMM32AT_AUX_DI_CALBUSY BIT(6) +#define DMM32AT_AUX_DI3 BIT(3) /* J3.45 - ADCLK (CLKSEL) */ +#define DMM32AT_AUX_DI2 BIT(2) /* J3.46 - GATE12 (GT12EN) */ +#define DMM32AT_AUX_DI1 BIT(1) /* J3.47 - GATE0 (GT0EN) */ +#define DMM32AT_AUX_DI0 BIT(0) /* J3.48 - CLK0 (SRC0) */ +#define DMM32AT_AO_LSB_REG 0x04 +#define DMM32AT_AO_MSB_REG 0x05 +#define DMM32AT_AO_MSB_DACH(x) ((x) << 6) +#define DMM32AT_FIFO_DEPTH_REG 0x06 +#define DMM32AT_FIFO_CTRL_REG 0x07 +#define DMM32AT_FIFO_CTRL_FIFOEN BIT(3) +#define DMM32AT_FIFO_CTRL_SCANEN BIT(2) +#define DMM32AT_FIFO_CTRL_FIFORST BIT(1) +#define DMM32AT_FIFO_STATUS_REG 0x07 +#define DMM32AT_FIFO_STATUS_EF BIT(7) +#define DMM32AT_FIFO_STATUS_HF BIT(6) +#define DMM32AT_FIFO_STATUS_FF BIT(5) +#define DMM32AT_FIFO_STATUS_OVF BIT(4) +#define DMM32AT_FIFO_STATUS_FIFOEN BIT(3) +#define DMM32AT_FIFO_STATUS_SCANEN BIT(2) +#define DMM32AT_FIFO_STATUS_PAGE_MASK (3 << 0) +#define DMM32AT_CTRL_REG 0x08 +#define DMM32AT_CTRL_RESETA BIT(5) +#define DMM32AT_CTRL_RESETD BIT(4) +#define DMM32AT_CTRL_INTRST BIT(3) +#define DMM32AT_CTRL_PAGE(x) ((x) << 0) +#define DMM32AT_CTRL_PAGE_8254 DMM32AT_CTRL_PAGE(0) +#define DMM32AT_CTRL_PAGE_8255 DMM32AT_CTRL_PAGE(1) +#define DMM32AT_CTRL_PAGE_CALIB DMM32AT_CTRL_PAGE(3) +#define DMM32AT_AI_STATUS_REG 0x08 +#define DMM32AT_AI_STATUS_STS BIT(7) +#define DMM32AT_AI_STATUS_SD1 BIT(6) +#define DMM32AT_AI_STATUS_SD0 BIT(5) +#define DMM32AT_AI_STATUS_ADCH_MASK (0x1f << 0) +#define DMM32AT_INTCLK_REG 0x09 +#define DMM32AT_INTCLK_ADINT BIT(7) +#define DMM32AT_INTCLK_DINT BIT(6) +#define DMM32AT_INTCLK_TINT BIT(5) +#define DMM32AT_INTCLK_CLKEN BIT(1) /* 1=see below 0=software */ +#define DMM32AT_INTCLK_CLKSEL BIT(0) /* 1=OUT2 0=EXTCLK */ +#define DMM32AT_CTRDIO_CFG_REG 0x0a +#define DMM32AT_CTRDIO_CFG_FREQ12 BIT(7) /* CLK12 1=100KHz 0=10MHz */ +#define DMM32AT_CTRDIO_CFG_FREQ0 BIT(6) /* CLK0 1=10KHz 0=10MHz */ +#define DMM32AT_CTRDIO_CFG_OUT2EN BIT(5) /* J3.42 1=OUT2 is DOUT2 */ +#define DMM32AT_CTRDIO_CFG_OUT0EN BIT(4) /* J3,44 1=OUT0 is DOUT0 */ +#define DMM32AT_CTRDIO_CFG_GT0EN BIT(2) /* J3.47 1=DIN1 is GATE0 */ +#define DMM32AT_CTRDIO_CFG_SRC0 BIT(1) /* CLK0 is 0=FREQ0 1=J3.48 */ +#define DMM32AT_CTRDIO_CFG_GT12EN BIT(0) /* J3.46 1=DIN2 is GATE12 */ +#define DMM32AT_AI_CFG_REG 0x0b +#define DMM32AT_AI_CFG_SCINT(x) ((x) << 4) +#define DMM32AT_AI_CFG_SCINT_20US DMM32AT_AI_CFG_SCINT(0) +#define DMM32AT_AI_CFG_SCINT_15US DMM32AT_AI_CFG_SCINT(1) +#define DMM32AT_AI_CFG_SCINT_10US DMM32AT_AI_CFG_SCINT(2) +#define DMM32AT_AI_CFG_SCINT_5US DMM32AT_AI_CFG_SCINT(3) +#define DMM32AT_AI_CFG_RANGE BIT(3) /* 0=5V 1=10V */ +#define DMM32AT_AI_CFG_ADBU BIT(2) /* 0=bipolar 1=unipolar */ +#define DMM32AT_AI_CFG_GAIN(x) ((x) << 0) +#define DMM32AT_AI_READBACK_REG 0x0b +#define DMM32AT_AI_READBACK_WAIT BIT(7) /* DMM32AT_AI_STATUS_STS */ +#define DMM32AT_AI_READBACK_RANGE BIT(3) +#define DMM32AT_AI_READBACK_ADBU BIT(2) +#define DMM32AT_AI_READBACK_GAIN_MASK (3 << 0) + +#define DMM32AT_CLK1 0x0d +#define DMM32AT_CLK2 0x0e +#define DMM32AT_CLKCT 0x0f + +#define DMM32AT_8255_IOBASE 0x0c /* Page 1 registers */ + +/* Board register values. */ + +/* DMM32AT_AI_CFG_REG 0x0b */ +#define DMM32AT_RANGE_U10 0x0c +#define DMM32AT_RANGE_U5 0x0d +#define DMM32AT_RANGE_B10 0x08 +#define DMM32AT_RANGE_B5 0x00 + +/* DMM32AT_CLKCT 0x0f */ +#define DMM32AT_CLKCT1 0x56 /* mode3 counter 1 - write low byte only */ +#define DMM32AT_CLKCT2 0xb6 /* mode3 counter 2 - write high and low byte */ + +/* board AI ranges in comedi structure */ +static const struct comedi_lrange dmm32at_airanges = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + BIP_RANGE(10), + BIP_RANGE(5) + } +}; + +/* register values for above ranges */ +static const unsigned char dmm32at_rangebits[] = { + DMM32AT_RANGE_U10, + DMM32AT_RANGE_U5, + DMM32AT_RANGE_B10, + DMM32AT_RANGE_B5, +}; + +/* only one of these ranges is valid, as set by a jumper on the + * board. The application should only use the range set by the jumper + */ +static const struct comedi_lrange dmm32at_aoranges = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + BIP_RANGE(10), + BIP_RANGE(5) + } +}; + +static void dmm32at_ai_set_chanspec(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec, int nchan) +{ + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int last_chan = (chan + nchan - 1) % s->n_chan; + + outb(DMM32AT_FIFO_CTRL_FIFORST, dev->iobase + DMM32AT_FIFO_CTRL_REG); + + if (nchan > 1) + outb(DMM32AT_FIFO_CTRL_SCANEN, + dev->iobase + DMM32AT_FIFO_CTRL_REG); + + outb(chan, dev->iobase + DMM32AT_AI_LO_CHAN_REG); + outb(last_chan, dev->iobase + DMM32AT_AI_HI_CHAN_REG); + outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AI_CFG_REG); +} + +static unsigned int dmm32at_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + val = inb(dev->iobase + DMM32AT_AI_LSB_REG); + val |= (inb(dev->iobase + DMM32AT_AI_MSB_REG) << 8); + + /* munge two's complement value to offset binary */ + return comedi_offset_munge(s, val); +} + +static int dmm32at_ai_status(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + context); + if ((status & DMM32AT_AI_STATUS_STS) == 0) + return 0; + return -EBUSY; +} + +static int dmm32at_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + int i; + + dmm32at_ai_set_chanspec(dev, s, insn->chanspec, 1); + + /* wait for circuit to settle */ + ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, + DMM32AT_AI_READBACK_REG); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + outb(0xff, dev->iobase + DMM32AT_AI_START_CONV_REG); + + ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, + DMM32AT_AI_STATUS_REG); + if (ret) + return ret; + + data[i] = dmm32at_ai_get_sample(dev, s); + } + + return insn->n; +} + +static int dmm32at_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + int i; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + if (chan != (chan0 + i) % s->n_chan) { + dev_dbg(dev->class_dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + return -EINVAL; + } + if (range != range0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same gain\n"); + return -EINVAL; + } + } + + return 0; +} + +static int dmm32at_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 1000000); + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000); + + if (cmd->convert_arg >= 17500) + cmd->convert_arg = 20000; + else if (cmd->convert_arg >= 12500) + cmd->convert_arg = 15000; + else if (cmd->convert_arg >= 7500) + cmd->convert_arg = 10000; + else + cmd->convert_arg = 5000; + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg); + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= dmm32at_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) +{ + unsigned char lo1, lo2, hi2; + unsigned short both2; + + /* based on 10mhz clock */ + lo1 = 200; + both2 = nansec / 20000; + hi2 = (both2 & 0xff00) >> 8; + lo2 = both2 & 0x00ff; + + /* set counter clocks to 10MHz, disable all aux dio */ + outb(0, dev->iobase + DMM32AT_CTRDIO_CFG_REG); + + /* get access to the clock regs */ + outb(DMM32AT_CTRL_PAGE_8254, dev->iobase + DMM32AT_CTRL_REG); + + /* write the counter 1 control word and low byte to counter */ + outb(DMM32AT_CLKCT1, dev->iobase + DMM32AT_CLKCT); + outb(lo1, dev->iobase + DMM32AT_CLK1); + + /* write the counter 2 control word and low byte then to counter */ + outb(DMM32AT_CLKCT2, dev->iobase + DMM32AT_CLKCT); + outb(lo2, dev->iobase + DMM32AT_CLK2); + outb(hi2, dev->iobase + DMM32AT_CLK2); + + /* enable the ai conversion interrupt and the clock to start scans */ + outb(DMM32AT_INTCLK_ADINT | + DMM32AT_INTCLK_CLKEN | DMM32AT_INTCLK_CLKSEL, + dev->iobase + DMM32AT_INTCLK_REG); +} + +static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int ret; + + dmm32at_ai_set_chanspec(dev, s, cmd->chanlist[0], cmd->chanlist_len); + + /* reset the interrupt just in case */ + outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG); + + /* + * wait for circuit to settle + * we don't have the 'insn' here but it's not needed + */ + ret = comedi_timeout(dev, s, NULL, dmm32at_ai_status, + DMM32AT_AI_READBACK_REG); + if (ret) + return ret; + + if (cmd->stop_src == TRIG_NONE || cmd->stop_arg > 1) { + /* start the clock and enable the interrupts */ + dmm32at_setaitimer(dev, cmd->scan_begin_arg); + } else { + /* start the interrupts and initiate a single scan */ + outb(DMM32AT_INTCLK_ADINT, dev->iobase + DMM32AT_INTCLK_REG); + outb(0xff, dev->iobase + DMM32AT_AI_START_CONV_REG); + } + + return 0; +} + +static int dmm32at_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* disable further interrupts and clocks */ + outb(0x0, dev->iobase + DMM32AT_INTCLK_REG); + return 0; +} + +static irqreturn_t dmm32at_isr(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned char intstat; + unsigned short val; + int i; + + if (!dev->attached) { + dev_err(dev->class_dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + + intstat = inb(dev->iobase + DMM32AT_INTCLK_REG); + + if (intstat & DMM32AT_INTCLK_ADINT) { + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + + for (i = 0; i < cmd->chanlist_len; i++) { + val = dmm32at_ai_get_sample(dev, s); + comedi_buf_write_samples(s, &val, 1); + } + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); + } + + /* reset the interrupt */ + outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG); + return IRQ_HANDLED; +} + +static int dmm32at_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + DMM32AT_AUX_DI_REG); + if ((status & DMM32AT_AUX_DI_DACBUSY) == 0) + return 0; + return -EBUSY; +} + +static int dmm32at_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + int ret; + + /* write LSB then MSB + chan to load DAC */ + outb(val & 0xff, dev->iobase + DMM32AT_AO_LSB_REG); + outb((val >> 8) | DMM32AT_AO_MSB_DACH(chan), + dev->iobase + DMM32AT_AO_MSB_REG); + + /* wait for circuit to settle */ + ret = comedi_timeout(dev, s, insn, dmm32at_ao_eoc, 0); + if (ret) + return ret; + + /* dummy read to update DAC */ + inb(dev->iobase + DMM32AT_AO_MSB_REG); + + s->readback[chan] = val; + } + + return insn->n; +} + +static int dmm32at_8255_io(struct comedi_device *dev, + int dir, int port, int data, unsigned long regbase) +{ + /* get access to the DIO regs */ + outb(DMM32AT_CTRL_PAGE_8255, dev->iobase + DMM32AT_CTRL_REG); + + if (dir) { + outb(data, dev->iobase + regbase + port); + return 0; + } + return inb(dev->iobase + regbase + port); +} + +/* Make sure the board is there and put it to a known state */ +static int dmm32at_reset(struct comedi_device *dev) +{ + unsigned char aihi, ailo, fifostat, aistat, intstat, airback; + + /* reset the board */ + outb(DMM32AT_CTRL_RESETA, dev->iobase + DMM32AT_CTRL_REG); + + /* allow a millisecond to reset */ + usleep_range(1000, 3000); + + /* zero scan and fifo control */ + outb(0x0, dev->iobase + DMM32AT_FIFO_CTRL_REG); + + /* zero interrupt and clock control */ + outb(0x0, dev->iobase + DMM32AT_INTCLK_REG); + + /* write a test channel range, the high 3 bits should drop */ + outb(0x80, dev->iobase + DMM32AT_AI_LO_CHAN_REG); + outb(0xff, dev->iobase + DMM32AT_AI_HI_CHAN_REG); + + /* set the range at 10v unipolar */ + outb(DMM32AT_RANGE_U10, dev->iobase + DMM32AT_AI_CFG_REG); + + /* should take 10 us to settle, here's a hundred */ + usleep_range(100, 200); + + /* read back the values */ + ailo = inb(dev->iobase + DMM32AT_AI_LO_CHAN_REG); + aihi = inb(dev->iobase + DMM32AT_AI_HI_CHAN_REG); + fifostat = inb(dev->iobase + DMM32AT_FIFO_STATUS_REG); + aistat = inb(dev->iobase + DMM32AT_AI_STATUS_REG); + intstat = inb(dev->iobase + DMM32AT_INTCLK_REG); + airback = inb(dev->iobase + DMM32AT_AI_READBACK_REG); + + /* + * NOTE: The (DMM32AT_AI_STATUS_SD1 | DMM32AT_AI_STATUS_SD0) + * test makes this driver only work if the board is configured + * with all A/D channels set for single-ended operation. + */ + if (ailo != 0x00 || aihi != 0x1f || + fifostat != DMM32AT_FIFO_STATUS_EF || + aistat != (DMM32AT_AI_STATUS_SD1 | DMM32AT_AI_STATUS_SD0) || + intstat != 0x00 || airback != 0x0c) + return -EIO; + + return 0; +} + +static int dmm32at_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + ret = dmm32at_reset(dev); + if (ret) { + dev_err(dev->class_dev, "board detection failed\n"); + return ret; + } + + if (it->options[1]) { + ret = request_irq(it->options[1], dmm32at_isr, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 32; + s->maxdata = 0xffff; + s->range_table = &dmm32at_airanges; + s->insn_read = dmm32at_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmd = dmm32at_ai_cmd; + s->do_cmdtest = dmm32at_ai_cmdtest; + s->cancel = dmm32at_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->range_table = &dmm32at_aoranges; + s->insn_write = dmm32at_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + return subdev_8255_init(dev, s, dmm32at_8255_io, DMM32AT_8255_IOBASE); +} + +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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi: Diamond Systems Diamond-MM-32-AT"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt2801.c b/drivers/comedi/drivers/dt2801.c new file mode 100644 index 000000000..230d25010 --- /dev/null +++ b/drivers/comedi/drivers/dt2801.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/delay.h> + +#define DT2801_TIMEOUT 1000 + +/* Hardware Configuration */ +/* ====================== */ + +#define DT2801_MAX_DMA_SIZE (64 * 1024) + +/* define's */ +/* ====================== */ + +/* Commands */ +#define DT_C_RESET 0x0 +#define DT_C_CLEAR_ERR 0x1 +#define DT_C_READ_ERRREG 0x2 +#define DT_C_SET_CLOCK 0x3 + +#define DT_C_TEST 0xb +#define DT_C_STOP 0xf + +#define DT_C_SET_DIGIN 0x4 +#define DT_C_SET_DIGOUT 0x5 +#define DT_C_READ_DIG 0x6 +#define DT_C_WRITE_DIG 0x7 + +#define DT_C_WRITE_DAIM 0x8 +#define DT_C_SET_DA 0x9 +#define DT_C_WRITE_DA 0xa + +#define DT_C_READ_ADIM 0xc +#define DT_C_SET_AD 0xd +#define DT_C_READ_AD 0xe + +/* + * Command modifiers (only used with read/write), EXTTRIG can be + * used with some other commands. + */ +#define DT_MOD_DMA BIT(4) +#define DT_MOD_CONT BIT(5) +#define DT_MOD_EXTCLK BIT(6) +#define DT_MOD_EXTTRIG BIT(7) + +/* Bits in status register */ +#define DT_S_DATA_OUT_READY BIT(0) +#define DT_S_DATA_IN_FULL BIT(1) +#define DT_S_READY BIT(2) +#define DT_S_COMMAND BIT(3) +#define DT_S_COMPOSITE_ERROR BIT(7) + +/* registers */ +#define DT2801_DATA 0 +#define DT2801_STATUS 1 +#define DT2801_CMD 1 + +#if 0 +/* ignore 'defined but not used' warning */ +static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; +#endif +static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = { + 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.02) + } +}; + +#if 0 +/* ignore 'defined but not used' warning */ +static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; +#endif +static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = { + 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.02) + } +}; + +struct dt2801_board { + const char *name; + int boardcode; + int ad_diff; + int ad_chan; + int adbits; + int adrangetype; + int dabits; +}; + +/* + * Typeid's for the different boards of the DT2801-series + * (taken from the test-software, that comes with the board) + */ +static const struct dt2801_board boardtypes[] = { + { + .name = "dt2801", + .boardcode = 0x09, + .ad_diff = 2, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2801-a", + .boardcode = 0x52, + .ad_diff = 2, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2801/5716a", + .boardcode = 0x82, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 16, + .adrangetype = 1, + .dabits = 12}, + { + .name = "dt2805", + .boardcode = 0x12, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2805/5716a", + .boardcode = 0x92, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 16, + .adrangetype = 1, + .dabits = 12}, + { + .name = "dt2808", + .boardcode = 0x20, + .ad_diff = 0, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 2, + .dabits = 8}, + { + .name = "dt2818", + .boardcode = 0xa2, + .ad_diff = 0, + .ad_chan = 4, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2809", + .boardcode = 0xb0, + .ad_diff = 0, + .ad_chan = 8, + .adbits = 12, + .adrangetype = 1, + .dabits = 12}, +}; + +struct dt2801_private { + const struct comedi_lrange *dac_range_types[2]; +}; + +/* + * These are the low-level routines: + * writecommand: write a command to the board + * writedata: write data byte + * readdata: read data byte + */ + +/* + * Only checks DataOutReady-flag, not the Ready-flag as it is done + * in the examples of the manual. I don't see why this should be + * necessary. + */ +static int dt2801_readdata(struct comedi_device *dev, int *data) +{ + int stat = 0; + int timeout = DT2801_TIMEOUT; + + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & (DT_S_COMPOSITE_ERROR | DT_S_READY)) + return stat; + if (stat & DT_S_DATA_OUT_READY) { + *data = inb_p(dev->iobase + DT2801_DATA); + return 0; + } + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_readdata2(struct comedi_device *dev, int *data) +{ + int lb = 0; + int hb = 0; + int ret; + + ret = dt2801_readdata(dev, &lb); + if (ret) + return ret; + ret = dt2801_readdata(dev, &hb); + if (ret) + return ret; + + *data = (hb << 8) + lb; + return 0; +} + +static int dt2801_writedata(struct comedi_device *dev, unsigned int data) +{ + int stat = 0; + int timeout = DT2801_TIMEOUT; + + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + + if (stat & DT_S_COMPOSITE_ERROR) + return stat; + if (!(stat & DT_S_DATA_IN_FULL)) { + outb_p(data & 0xff, dev->iobase + DT2801_DATA); + return 0; + } + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_writedata2(struct comedi_device *dev, unsigned int data) +{ + int ret; + + ret = dt2801_writedata(dev, data & 0xff); + if (ret < 0) + return ret; + ret = dt2801_writedata(dev, data >> 8); + if (ret < 0) + return ret; + + return 0; +} + +static int dt2801_wait_for_ready(struct comedi_device *dev) +{ + int timeout = DT2801_TIMEOUT; + int stat; + + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + return 0; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + + if (stat & DT_S_COMPOSITE_ERROR) + return stat; + if (stat & DT_S_READY) + return 0; + } while (--timeout > 0); + + return -ETIME; +} + +static void dt2801_writecmd(struct comedi_device *dev, int command) +{ + int stat; + + dt2801_wait_for_ready(dev); + + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_COMPOSITE_ERROR) { + dev_dbg(dev->class_dev, + "composite-error in %s, ignoring\n", __func__); + } + if (!(stat & DT_S_READY)) + dev_dbg(dev->class_dev, "!ready in %s, ignoring\n", __func__); + outb_p(command, dev->iobase + DT2801_CMD); +} + +static int dt2801_reset(struct comedi_device *dev) +{ + int board_code = 0; + unsigned int stat; + int timeout; + + /* pull random data from data port */ + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + + /* dt2801_writecmd(dev,DT_C_STOP); */ + outb_p(DT_C_STOP, dev->iobase + DT2801_CMD); + + /* dt2801_wait_for_ready(dev); */ + usleep_range(100, 200); + 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); */ + + usleep_range(100, 200); + timeout = 10000; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + break; + } while (timeout--); + if (!timeout) + dev_dbg(dev->class_dev, "timeout 2 status=0x%02x\n", stat); + + dt2801_readdata(dev, &board_code); + + return board_code; +} + +static int probe_number_of_ai_chans(struct comedi_device *dev) +{ + int n_chans; + int stat; + int data; + + for (n_chans = 0; n_chans < 16; n_chans++) { + dt2801_writecmd(dev, DT_C_READ_ADIM); + dt2801_writedata(dev, 0); + dt2801_writedata(dev, n_chans); + stat = dt2801_readdata2(dev, &data); + + if (stat) + break; + } + + dt2801_reset(dev); + dt2801_reset(dev); + + return n_chans; +} + +static const struct comedi_lrange *dac_range_table[] = { + &range_bipolar10, + &range_bipolar5, + &range_bipolar2_5, + &range_unipolar10, + &range_unipolar5 +}; + +static const struct comedi_lrange *dac_range_lkup(int opt) +{ + if (opt < 0 || opt >= 5) + return &range_unknown; + return dac_range_table[opt]; +} + +static const struct comedi_lrange *ai_range_lkup(int type, int opt) +{ + switch (type) { + case 0: + return (opt) ? + &range_dt2801_ai_pgl_unipolar : + &range_dt2801_ai_pgl_bipolar; + case 1: + return (opt) ? &range_unipolar10 : &range_bipolar10; + case 2: + return &range_unipolar5; + } + return &range_unknown; +} + +static int dt2801_error(struct comedi_device *dev, int stat) +{ + if (stat < 0) { + if (stat == -ETIME) + dev_dbg(dev->class_dev, "timeout\n"); + else + dev_dbg(dev->class_dev, "error %d\n", stat); + return stat; + } + dev_dbg(dev->class_dev, "error status 0x%02x, resetting...\n", stat); + + dt2801_reset(dev); + dt2801_reset(dev); + + return -EIO; +} + +static int dt2801_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int d; + int stat; + int i; + + for (i = 0; i < insn->n; i++) { + dt2801_writecmd(dev, DT_C_READ_ADIM); + dt2801_writedata(dev, CR_RANGE(insn->chanspec)); + dt2801_writedata(dev, CR_CHAN(insn->chanspec)); + stat = dt2801_readdata2(dev, &d); + + if (stat != 0) + return dt2801_error(dev, stat); + + data[i] = d; + } + + return i; +} + +static int dt2801_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + dt2801_writecmd(dev, DT_C_WRITE_DAIM); + dt2801_writedata(dev, chan); + dt2801_writedata2(dev, data[0]); + + s->readback[chan] = data[0]; + + return 1; +} + +static int dt2801_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int which = (s == &dev->subdevices[3]) ? 1 : 0; + unsigned int val = 0; + + if (comedi_dio_update_state(s, data)) { + dt2801_writecmd(dev, DT_C_WRITE_DIG); + dt2801_writedata(dev, which); + dt2801_writedata(dev, s->state); + } + + dt2801_writecmd(dev, DT_C_READ_DIG); + dt2801_writedata(dev, which); + dt2801_readdata(dev, &val); + + data[1] = val; + + return insn->n; +} + +static int dt2801_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0xff); + if (ret) + return ret; + + dt2801_writecmd(dev, s->io_bits ? DT_C_SET_DIGOUT : DT_C_SET_DIGIN); + dt2801_writedata(dev, (s == &dev->subdevices[3]) ? 1 : 0); + + return insn->n; +} + +/* + * options: + * [0] - i/o base + * [1] - unused + * [2] - a/d 0=differential, 1=single-ended + * [3] - a/d range 0=[-10,10], 1=[0,10] + * [4] - dac0 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] + * [5] - dac1 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] + */ +static int dt2801_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct dt2801_board *board; + struct dt2801_private *devpriv; + struct comedi_subdevice *s; + int board_code, type; + int ret = 0; + int n_ai_chans; + + ret = comedi_request_region(dev, it->options[0], 0x2); + if (ret) + return ret; + + /* do some checking */ + + board_code = dt2801_reset(dev); + + /* heh. if it didn't work, try it again. */ + if (!board_code) + board_code = dt2801_reset(dev); + + for (type = 0; type < ARRAY_SIZE(boardtypes); type++) { + if (boardtypes[type].boardcode == board_code) + goto havetype; + } + dev_dbg(dev->class_dev, + "unrecognized board code=0x%02x, contact author\n", board_code); + type = 0; + +havetype: + dev->board_ptr = boardtypes + type; + board = dev->board_ptr; + + n_ai_chans = probe_number_of_ai_chans(dev); + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + goto out; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->board_name = board->name; + + s = &dev->subdevices[0]; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; +#if 1 + s->n_chan = n_ai_chans; +#else + if (it->options[2]) + s->n_chan = board->ad_chan; + else + s->n_chan = board->ad_chan / 2; +#endif + s->maxdata = (1 << board->adbits) - 1; + s->range_table = ai_range_lkup(board->adrangetype, it->options[3]); + s->insn_read = dt2801_ai_insn_read; + + s = &dev->subdevices[1]; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << board->dabits) - 1; + s->range_table_list = devpriv->dac_range_types; + devpriv->dac_range_types[0] = dac_range_lkup(it->options[4]); + devpriv->dac_range_types[1] = dac_range_lkup(it->options[5]); + s->insn_write = dt2801_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[2]; + /* 1st digital subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2801_dio_insn_bits; + s->insn_config = dt2801_dio_insn_config; + + s = &dev->subdevices[3]; + /* 2nd digital subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2801_dio_insn_bits; + s->insn_config = dt2801_dio_insn_config; + + ret = 0; +out: + return ret; +} + +static struct comedi_driver dt2801_driver = { + .driver_name = "dt2801", + .module = THIS_MODULE, + .attach = dt2801_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(dt2801_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt2811.c b/drivers/comedi/drivers/dt2811.c new file mode 100644 index 000000000..dbb9f38da --- /dev/null +++ b/drivers/comedi/drivers/dt2811.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for Data Translation DT2811 + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) David A. Schleef <ds@schleef.org> + */ + +/* + * 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 (optional, needed for async command support) + * [2] - A/D reference (# of analog inputs) + * 0 = single-ended (16 channels) + * 1 = differential (8 channels) + * 2 = pseudo-differential (16 channels) + * [3] - A/D range (deprecated, see below) + * [4] - D/A 0 range (deprecated, see below) + * [5] - D/A 1 range (deprecated, see below) + * + * Notes: + * - A/D ranges are not programmable but the gain is. The AI subdevice has + * a range_table containing all the possible analog input range/gain + * options for the dt2811-pgh or dt2811-pgl. Use the range that matches + * your board configuration and the desired gain to correctly convert + * between data values and physical units and to set the correct output + * gain. + * - D/A ranges are not programmable. The AO subdevice has a range_table + * containing all the possible analog output ranges. Use the range + * that matches your board configuration to convert between data + * values and physical units. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/comedi/comedidev.h> + +/* + * Register I/O map + */ +#define DT2811_ADCSR_REG 0x00 /* r/w A/D Control/Status */ +#define DT2811_ADCSR_ADDONE BIT(7) /* r 1=A/D conv done */ +#define DT2811_ADCSR_ADERROR BIT(6) /* r 1=A/D error */ +#define DT2811_ADCSR_ADBUSY BIT(5) /* r 1=A/D busy */ +#define DT2811_ADCSR_CLRERROR BIT(4) +#define DT2811_ADCSR_DMAENB BIT(3) /* r/w 1=dma ena */ +#define DT2811_ADCSR_INTENB BIT(2) /* r/w 1=interrupts ena */ +#define DT2811_ADCSR_ADMODE(x) (((x) & 0x3) << 0) + +#define DT2811_ADGCR_REG 0x01 /* r/w A/D Gain/Channel */ +#define DT2811_ADGCR_GAIN(x) (((x) & 0x3) << 6) +#define DT2811_ADGCR_CHAN(x) (((x) & 0xf) << 0) + +#define DT2811_ADDATA_LO_REG 0x02 /* r A/D Data low byte */ +#define DT2811_ADDATA_HI_REG 0x03 /* r A/D Data high byte */ + +#define DT2811_DADATA_LO_REG(x) (0x02 + ((x) * 2)) /* w D/A Data low */ +#define DT2811_DADATA_HI_REG(x) (0x03 + ((x) * 2)) /* w D/A Data high */ + +#define DT2811_DI_REG 0x06 /* r Digital Input Port 0 */ +#define DT2811_DO_REG 0x06 /* w Digital Output Port 1 */ + +#define DT2811_TMRCTR_REG 0x07 /* r/w Timer/Counter */ +#define DT2811_TMRCTR_MANTISSA(x) (((x) & 0x7) << 3) +#define DT2811_TMRCTR_EXPONENT(x) (((x) & 0x7) << 0) + +#define DT2811_OSC_BASE 1666 /* 600 kHz = 1666.6667ns */ + +/* + * Timer frequency control: + * DT2811_TMRCTR_MANTISSA DT2811_TMRCTR_EXPONENT + * val divisor frequency val multiply divisor/divide frequency by + * 0 1 600 kHz 0 1 + * 1 10 60 kHz 1 10 + * 2 2 300 kHz 2 100 + * 3 3 200 kHz 3 1000 + * 4 4 150 kHz 4 10000 + * 5 5 120 kHz 5 100000 + * 6 6 100 kHz 6 1000000 + * 7 12 50 kHz 7 10000000 + */ +static const unsigned int dt2811_clk_dividers[] = { + 1, 10, 2, 3, 4, 5, 6, 12 +}; + +static const unsigned int dt2811_clk_multipliers[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; + +/* + * The Analog Input range is set using jumpers on the board. + * + * Input Range W9 W10 + * -5V to +5V In Out + * -2.5V to +2.5V In In + * 0V to +5V Out In + * + * The gain may be set to 1, 2, 4, or 8 (on the dt2811-pgh) or to + * 1, 10, 100, 500 (on the dt2811-pgl). + */ +static const struct comedi_lrange dt2811_pgh_ai_ranges = { + 12, { + BIP_RANGE(5), /* range 0: gain=1 */ + BIP_RANGE(2.5), /* range 1: gain=2 */ + BIP_RANGE(1.25), /* range 2: gain=4 */ + BIP_RANGE(0.625), /* range 3: gain=8 */ + + BIP_RANGE(2.5), /* range 0+4: gain=1 */ + BIP_RANGE(1.25), /* range 1+4: gain=2 */ + BIP_RANGE(0.625), /* range 2+4: gain=4 */ + BIP_RANGE(0.3125), /* range 3+4: gain=8 */ + + UNI_RANGE(5), /* range 0+8: gain=1 */ + UNI_RANGE(2.5), /* range 1+8: gain=2 */ + UNI_RANGE(1.25), /* range 2+8: gain=4 */ + UNI_RANGE(0.625) /* range 3+8: gain=8 */ + } +}; + +static const struct comedi_lrange dt2811_pgl_ai_ranges = { + 12, { + BIP_RANGE(5), /* range 0: gain=1 */ + BIP_RANGE(0.5), /* range 1: gain=10 */ + BIP_RANGE(0.05), /* range 2: gain=100 */ + BIP_RANGE(0.01), /* range 3: gain=500 */ + + BIP_RANGE(2.5), /* range 0+4: gain=1 */ + BIP_RANGE(0.25), /* range 1+4: gain=10 */ + BIP_RANGE(0.025), /* range 2+4: gain=100 */ + BIP_RANGE(0.005), /* range 3+4: gain=500 */ + + UNI_RANGE(5), /* range 0+8: gain=1 */ + UNI_RANGE(0.5), /* range 1+8: gain=10 */ + UNI_RANGE(0.05), /* range 2+8: gain=100 */ + UNI_RANGE(0.01) /* range 3+8: gain=500 */ + } +}; + +/* + * The Analog Output range is set per-channel using jumpers on the board. + * + * DAC0 Jumpers DAC1 Jumpers + * Output Range W5 W6 W7 W8 W1 W2 W3 W4 + * -5V to +5V In Out In Out In Out In Out + * -2.5V to +2.5V In Out Out In In Out Out In + * 0 to +5V Out In Out In Out In Out In + */ +static const struct comedi_lrange dt2811_ao_ranges = { + 3, { + BIP_RANGE(5), /* default setting from factory */ + BIP_RANGE(2.5), + UNI_RANGE(5) + } +}; + +struct dt2811_board { + const char *name; + unsigned int is_pgh:1; +}; + +static const struct dt2811_board dt2811_boards[] = { + { + .name = "dt2811-pgh", + .is_pgh = 1, + }, { + .name = "dt2811-pgl", + }, +}; + +struct dt2811_private { + unsigned int ai_divisor; +}; + +static unsigned int dt2811_ai_read_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + val = inb(dev->iobase + DT2811_ADDATA_LO_REG) | + (inb(dev->iobase + DT2811_ADDATA_HI_REG) << 8); + + return val & s->maxdata; +} + +static irqreturn_t dt2811_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status; + + if (!dev->attached) + return IRQ_NONE; + + status = inb(dev->iobase + DT2811_ADCSR_REG); + + if (status & DT2811_ADCSR_ADERROR) { + async->events |= COMEDI_CB_OVERFLOW; + + outb(status | DT2811_ADCSR_CLRERROR, + dev->iobase + DT2811_ADCSR_REG); + } + + if (status & DT2811_ADCSR_ADDONE) { + unsigned short val; + + val = dt2811_ai_read_sample(dev, s); + comedi_buf_write_samples(s, &val, 1); + } + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int dt2811_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* + * Mode 0 + * Single conversion + * + * Loading a chanspec will trigger a conversion. + */ + outb(DT2811_ADCSR_ADMODE(0), dev->iobase + DT2811_ADCSR_REG); + + return 0; +} + +static void dt2811_ai_set_chanspec(struct comedi_device *dev, + unsigned int chanspec) +{ + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + + outb(DT2811_ADGCR_CHAN(chan) | DT2811_ADGCR_GAIN(range), + dev->iobase + DT2811_ADGCR_REG); +} + +static int dt2811_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dt2811_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int mode; + + if (cmd->start_src == TRIG_NOW) { + /* + * Mode 1 + * Continuous conversion, internal trigger and clock + * + * This resets the trigger flip-flop, disabling A/D strobes. + * The timer/counter register is loaded with the division + * ratio which will give the required sample rate. + * + * Loading the first chanspec sets the trigger flip-flop, + * enabling the timer/counter. A/D strobes are then generated + * at the rate set by the internal clock/divider. + */ + mode = DT2811_ADCSR_ADMODE(1); + } else { /* TRIG_EXT */ + if (cmd->convert_src == TRIG_TIMER) { + /* + * Mode 2 + * Continuous conversion, external trigger + * + * Similar to Mode 1, with the exception that the + * trigger flip-flop must be set by a negative edge + * on the external trigger input. + */ + mode = DT2811_ADCSR_ADMODE(2); + } else { /* TRIG_EXT */ + /* + * Mode 3 + * Continuous conversion, external trigger, clock + * + * Similar to Mode 2, with the exception that the + * conversion rate is set by the frequency on the + * external clock/divider. + */ + mode = DT2811_ADCSR_ADMODE(3); + } + } + outb(mode | DT2811_ADCSR_INTENB, dev->iobase + DT2811_ADCSR_REG); + + /* load timer */ + outb(devpriv->ai_divisor, dev->iobase + DT2811_TMRCTR_REG); + + /* load chanspec - enables timer */ + dt2811_ai_set_chanspec(dev, cmd->chanlist[0]); + + return 0; +} + +static unsigned int dt2811_ns_to_timer(unsigned int *nanosec, + unsigned int flags) +{ + unsigned long long ns; + unsigned int ns_lo = COMEDI_MIN_SPEED; + unsigned int ns_hi = 0; + unsigned int divisor_hi = 0; + unsigned int divisor_lo = 0; + unsigned int _div; + unsigned int _mult; + + /* + * Work through all the divider/multiplier values to find the two + * closest divisors to generate the requested nanosecond timing. + */ + for (_div = 0; _div <= 7; _div++) { + for (_mult = 0; _mult <= 7; _mult++) { + unsigned int div = dt2811_clk_dividers[_div]; + unsigned int mult = dt2811_clk_multipliers[_mult]; + unsigned long long divider = div * mult; + unsigned int divisor = DT2811_TMRCTR_MANTISSA(_div) | + DT2811_TMRCTR_EXPONENT(_mult); + + /* + * The timer can be configured to run at a slowest + * speed of 0.005hz (600 Khz/120000000), which requires + * 37-bits to represent the nanosecond value. Limit the + * slowest timing to what comedi handles (32-bits). + */ + ns = divider * DT2811_OSC_BASE; + if (ns > COMEDI_MIN_SPEED) + continue; + + /* Check for fastest found timing */ + if (ns <= *nanosec && ns > ns_hi) { + ns_hi = ns; + divisor_hi = divisor; + } + /* Check for slowest found timing */ + if (ns >= *nanosec && ns < ns_lo) { + ns_lo = ns; + divisor_lo = divisor; + } + } + } + + /* + * The slowest found timing will be invalid if the requested timing + * is faster than what can be generated by the timer. Fix it so that + * CMDF_ROUND_UP returns valid timing. + */ + if (ns_lo == COMEDI_MIN_SPEED) { + ns_lo = ns_hi; + divisor_lo = divisor_hi; + } + /* + * The fastest found timing will be invalid if the requested timing + * is less than what can be generated by the timer. Fix it so that + * CMDF_ROUND_NEAREST and CMDF_ROUND_DOWN return valid timing. + */ + if (ns_hi == 0) { + ns_hi = ns_lo; + divisor_hi = divisor_lo; + } + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + if (ns_hi - *nanosec < *nanosec - ns_lo) { + *nanosec = ns_lo; + return divisor_lo; + } + *nanosec = ns_hi; + return divisor_hi; + case CMDF_ROUND_UP: + *nanosec = ns_lo; + return divisor_lo; + case CMDF_ROUND_DOWN: + *nanosec = ns_hi; + return divisor_hi; + } +} + +static int dt2811_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct dt2811_private *devpriv = dev->private; + unsigned int arg; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->convert_src == TRIG_EXT && cmd->start_src != TRIG_EXT) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + if (cmd->convert_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 12500); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + devpriv->ai_divisor = dt2811_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } else { /* TRIG_EXT */ + /* The convert_arg is used to set the divisor. */ + devpriv->ai_divisor = cmd->convert_arg; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + + return 0; +} + +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_REG); + if ((status & DT2811_ADCSR_ADBUSY) == 0) + return 0; + return -EBUSY; +} + +static int dt2811_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + int i; + + /* We will already be in Mode 0 */ + for (i = 0; i < insn->n; i++) { + /* load chanspec and trigger conversion */ + dt2811_ai_set_chanspec(dev, insn->chanspec); + + ret = comedi_timeout(dev, s, insn, dt2811_ai_eoc, 0); + if (ret) + return ret; + + data[i] = dt2811_ai_read_sample(dev, s); + } + + return insn->n; +} + +static int dt2811_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outb(val & 0xff, dev->iobase + DT2811_DADATA_LO_REG(chan)); + outb((val >> 8) & 0xff, + dev->iobase + DT2811_DADATA_HI_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int dt2811_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + DT2811_DI_REG); + + 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_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static void dt2811_reset(struct comedi_device *dev) +{ + /* This is the initialization sequence from the users manual */ + outb(DT2811_ADCSR_ADMODE(0), dev->iobase + DT2811_ADCSR_REG); + usleep_range(100, 1000); + inb(dev->iobase + DT2811_ADDATA_LO_REG); + inb(dev->iobase + DT2811_ADDATA_HI_REG); + outb(DT2811_ADCSR_ADMODE(0) | DT2811_ADCSR_CLRERROR, + dev->iobase + DT2811_ADCSR_REG); +} + +static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct dt2811_board *board = dev->board_ptr; + struct dt2811_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], 0x8); + if (ret) + return ret; + + dt2811_reset(dev); + + /* IRQ's 2,3,5,7 are valid for async command support */ + if (it->options[1] <= 7 && (BIT(it->options[1]) & 0xac)) { + ret = request_irq(it->options[1], dt2811_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 | + ((it->options[2] == 1) ? SDF_DIFF : + (it->options[2] == 2) ? SDF_COMMON : SDF_GROUND); + s->n_chan = (it->options[2] == 1) ? 8 : 16; + s->maxdata = 0x0fff; + s->range_table = board->is_pgh ? &dt2811_pgh_ai_ranges + : &dt2811_pgl_ai_ranges; + s->insn_read = dt2811_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 1; + s->do_cmdtest = dt2811_ai_cmdtest; + s->do_cmd = dt2811_ai_cmd; + s->cancel = dt2811_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &dt2811_ao_ranges; + s->insn_write = dt2811_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2811_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2811_do_insn_bits; + + return 0; +} + +static struct comedi_driver dt2811_driver = { + .driver_name = "dt2811", + .module = THIS_MODULE, + .attach = dt2811_attach, + .detach = comedi_legacy_detach, + .board_name = &dt2811_boards[0].name, + .num_names = ARRAY_SIZE(dt2811_boards), + .offset = sizeof(struct dt2811_board), +}; +module_comedi_driver(dt2811_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Data Translation DT2811 series boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt2814.c b/drivers/comedi/drivers/dt2814.c new file mode 100644 index 000000000..c98a5a4a7 --- /dev/null +++ b/drivers/comedi/drivers/dt2814.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/delay.h> + +#define DT2814_CSR 0 +#define DT2814_DATA 1 + +/* + * flags + */ + +#define DT2814_FINISH 0x80 +#define DT2814_ERR 0x40 +#define DT2814_BUSY 0x20 +#define DT2814_ENB 0x10 +#define DT2814_CHANMASK 0x0f + +#define DT2814_TIMEOUT 10 +#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */ + +static int dt2814_ai_notbusy(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 (context) + *(unsigned int *)context = status; + if (status & DT2814_BUSY) + return -EBUSY; + return 0; +} + +static int dt2814_ai_clear(struct comedi_device *dev) +{ + unsigned int status = 0; + int ret; + + /* Wait until not busy and get status register value. */ + ret = comedi_timeout(dev, NULL, NULL, dt2814_ai_notbusy, + (unsigned long)&status); + if (ret) + return ret; + + if (status & (DT2814_FINISH | DT2814_ERR)) { + /* + * There unread data, or the error flag is set. + * Read the data register twice to clear the condition. + */ + inb(dev->iobase + DT2814_DATA); + inb(dev->iobase + DT2814_DATA); + } + return 0; +} + +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; + + dt2814_ai_clear(dev); /* clear stale data or error */ + for (n = 0; n < insn->n; n++) { + chan = CR_CHAN(insn->chanspec); + + outb(chan, dev->iobase + DT2814_CSR); + + ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0); + if (ret) + return ret; + + hi = inb(dev->iobase + DT2814_DATA); + lo = inb(dev->iobase + DT2814_DATA); + + data[n] = (hi << 4) | (lo >> 4); + } + + return n; +} + +static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags) +{ + int i; + unsigned int f; + + /* XXX ignores flags */ + + f = 10000; /* ns */ + for (i = 0; i < 8; i++) { + if ((2 * (*ns)) < (f * 11)) + break; + f *= 10; + } + + *ns = f; + + return i; +} + +static int dt2814_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000); + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + DT2814_MAX_SPEED); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->scan_begin_arg; + dt2814_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + + if (err) + return 4; + + return 0; +} + +static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int chan; + int trigvar; + + dt2814_ai_clear(dev); /* clear stale data or error */ + trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags); + + chan = CR_CHAN(cmd->chanlist[0]); + + outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR); + + return 0; +} + +static int dt2814_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int status; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + status = inb(dev->iobase + DT2814_CSR); + if (status & DT2814_ENB) { + /* + * Clear the timed trigger enable bit. + * + * Note: turning off timed mode triggers another + * sample. This will be mopped up by the calls to + * dt2814_ai_clear(). + */ + outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; +} + +static irqreturn_t dt2814_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + unsigned int lo, hi; + unsigned short data; + unsigned int status; + + if (!dev->attached) { + dev_err(dev->class_dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + + async = s->async; + + spin_lock(&dev->spinlock); + + status = inb(dev->iobase + DT2814_CSR); + if (!(status & DT2814_ENB)) { + /* Timed acquisition not enabled. Nothing to do. */ + spin_unlock(&dev->spinlock); + return IRQ_HANDLED; + } + + if (!(status & (DT2814_FINISH | DT2814_ERR))) { + /* Spurious interrupt? */ + spin_unlock(&dev->spinlock); + return IRQ_HANDLED; + } + + /* Read data or clear error. */ + hi = inb(dev->iobase + DT2814_DATA); + lo = inb(dev->iobase + DT2814_DATA); + + data = (hi << 4) | (lo >> 4); + + if (status & DT2814_ERR) { + async->events |= COMEDI_CB_ERROR; + } else { + comedi_buf_write_samples(s, &data, 1); + if (async->cmd.stop_src == TRIG_COUNT && + async->scans_done >= async->cmd.stop_arg) { + async->events |= COMEDI_CB_EOA; + } + } + if (async->events & COMEDI_CB_CANCEL_MASK) { + /* + * Disable timed mode. + * + * Note: turning off timed mode triggers another + * sample. This will be mopped up by the calls to + * dt2814_ai_clear(). + */ + outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR); + } + + spin_unlock(&dev->spinlock); + + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x2); + if (ret) + return ret; + + outb(0, dev->iobase + DT2814_CSR); + if (dt2814_ai_clear(dev)) { + dev_err(dev->class_dev, "reset error (fatal)\n"); + return -EIO; + } + + 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; + + 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; + s->cancel = dt2814_ai_cancel; + } + + return 0; +} + +static void dt2814_detach(struct comedi_device *dev) +{ + if (dev->irq) { + /* + * An extra conversion triggered on termination of an + * asynchronous command may still be in progress. Wait for + * it to finish and clear the data or error status. + */ + dt2814_ai_clear(dev); + } + comedi_legacy_detach(dev); +} + +static struct comedi_driver dt2814_driver = { + .driver_name = "dt2814", + .module = THIS_MODULE, + .attach = dt2814_attach, + .detach = dt2814_detach, +}; +module_comedi_driver(dt2814_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt2815.c b/drivers/comedi/drivers/dt2815.c new file mode 100644 index 000000000..03ba2fd18 --- /dev/null +++ b/drivers/comedi/drivers/dt2815.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/delay.h> + +#define DT2815_DATA 0 +#define DT2815_STATUS 1 + +struct dt2815_private { + const struct comedi_lrange *range_type_list[8]; + unsigned int ao_readback[8]; +}; + +static int dt2815_ao_status(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + DT2815_STATUS); + if (status == context) + return 0; + return -EBUSY; +} + +static int dt2815_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2815_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int dt2815_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2815_private *devpriv = dev->private; + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned int lo, hi; + int ret; + + for (i = 0; i < insn->n; i++) { + /* FIXME: lo bit 0 chooses voltage output or current output */ + 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; + + outb(hi, dev->iobase + DT2815_DATA); + + devpriv->ao_readback[chan] = data[i]; + } + return i; +} + +/* + * options[0] Board base address + * options[1] IRQ (not applicable) + * options[2] Voltage unipolar/bipolar configuration + * 0 == unipolar 5V (0V -- +5V) + * 1 == bipolar 5V (-5V -- +5V) + * options[3] Current offset configuration + * 0 == disabled (0mA -- +32mAV) + * 1 == enabled (+4mA -- +20mAV) + * options[4] Firmware program configuration + * 0 == program 1 (see manual table 5-4) + * 1 == program 2 (see manual table 5-4) + * 2 == program 3 (see manual table 5-4) + * 3 == program 4 (see manual table 5-4) + * options[5] Analog output 0 range configuration + * 0 == voltage + * 1 == current + * options[6] Analog output 1 range configuration + * ... + * options[12] Analog output 7 range configuration + * 0 == voltage + * 1 == current + */ + +static int dt2815_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct dt2815_private *devpriv; + struct comedi_subdevice *s; + int i; + const struct comedi_lrange *current_range_type, *voltage_range_type; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x2); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + s = &dev->subdevices[0]; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 0xfff; + s->n_chan = 8; + s->insn_write = dt2815_ao_insn; + s->insn_read = dt2815_ao_insn_read; + s->range_table_list = devpriv->range_type_list; + + current_range_type = (it->options[3]) + ? &range_4_20mA : &range_0_32mA; + voltage_range_type = (it->options[2]) + ? &range_bipolar5 : &range_unipolar5; + for (i = 0; i < 8; i++) { + devpriv->range_type_list[i] = (it->options[5 + i]) + ? current_range_type : voltage_range_type; + } + + /* Init the 2815 */ + outb(0x00, dev->iobase + DT2815_STATUS); + for (i = 0; i < 100; i++) { + /* This is incredibly slow (approx 20 ms) */ + unsigned int status; + + usleep_range(1000, 3000); + 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt2817.c b/drivers/comedi/drivers/dt2817.c new file mode 100644 index 000000000..6738045c7 --- /dev/null +++ b/drivers/comedi/drivers/dt2817.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * 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 <linux/comedi/comedidev.h> + +#define DT2817_CR 0 +#define DT2817_DATA 1 + +static int dt2817_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int oe = 0; + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x000000ff; + else if (chan < 16) + mask = 0x0000ff00; + else if (chan < 24) + mask = 0x00ff0000; + else + mask = 0xff000000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + if (s->io_bits & 0x000000ff) + oe |= 0x1; + if (s->io_bits & 0x0000ff00) + oe |= 0x2; + if (s->io_bits & 0x00ff0000) + oe |= 0x4; + if (s->io_bits & 0xff000000) + oe |= 0x8; + + outb(oe, dev->iobase + DT2817_CR); + + return insn->n; +} + +static int dt2817_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long iobase = dev->iobase + DT2817_DATA; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0x000000ff) + outb(s->state & 0xff, iobase + 0); + if (mask & 0x0000ff00) + outb((s->state >> 8) & 0xff, iobase + 1); + if (mask & 0x00ff0000) + outb((s->state >> 16) & 0xff, iobase + 2); + if (mask & 0xff000000) + outb((s->state >> 24) & 0xff, iobase + 3); + } + + val = inb(iobase + 0); + val |= (inb(iobase + 1) << 8); + val |= (inb(iobase + 2) << 16); + val |= (inb(iobase + 3) << 24); + + data[1] = val; + + return insn->n; +} + +static int dt2817_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + struct comedi_subdevice *s; + + ret = comedi_request_region(dev, it->options[0], 0x5); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + s = &dev->subdevices[0]; + + s->n_chan = 32; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dt2817_dio_insn_bits; + s->insn_config = dt2817_dio_insn_config; + + s->state = 0; + outb(0, dev->iobase + DT2817_CR); + + return 0; +} + +static struct comedi_driver dt2817_driver = { + .driver_name = "dt2817", + .module = THIS_MODULE, + .attach = dt2817_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(dt2817_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt282x.c b/drivers/comedi/drivers/dt282x.c new file mode 100644 index 000000000..4ae80e6c7 --- /dev/null +++ b/drivers/comedi/drivers/dt282x.c @@ -0,0 +1,1170 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dt282x.c + * Comedi driver for Data Translation DT2821 series + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: dt282x + * Description: Data Translation DT2821 series (including DT-EZ) + * Author: ds + * Devices: [Data Translation] DT2821 (dt2821), DT2821-F-16SE (dt2821-f), + * DT2821-F-8DI (dt2821-f), DT2821-G-16SE (dt2821-g), + * DT2821-G-8DI (dt2821-g), DT2823 (dt2823), DT2824-PGH (dt2824-pgh), + * DT2824-PGL (dt2824-pgl), DT2825 (dt2825), DT2827 (dt2827), + * DT2828 (dt2828), DT2928 (dt2829), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez), + * DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl) + * Status: complete + * Updated: Wed, 22 Aug 2001 17:11:34 -0700 + * + * Configuration options: + * [0] - I/O port base address + * [1] - IRQ (optional, required for async command support) + * [2] - DMA 1 (optional, required for async command support) + * [3] - DMA 2 (optional, required for async command support) + * [4] - AI jumpered for 0=single ended, 1=differential + * [5] - AI jumpered for 0=straight binary, 1=2's complement + * [6] - AO 0 data format (deprecated, see below) + * [7] - AO 1 data format (deprecated, see below) + * [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5] + * [9] - AO channel 0 range (deprecated, see below) + * [10]- AO channel 1 range (deprecated, see below) + * + * Notes: + * - AO commands might be broken. + * - If you try to run a command on both the AI and AO subdevices + * simultaneously, bad things will happen. The driver needs to + * be fixed to check for this situation and return an error. + * - AO range is not programmable. The AO subdevice has a range_table + * containing all the possible analog output ranges. Use the range + * that matches your board configuration to convert between data + * values and physical units. The format of the data written to the + * board is handled automatically based on the unipolar/bipolar + * range that is selected. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_isadma.h> + +/* + * Register map + */ +#define DT2821_ADCSR_REG 0x00 +#define DT2821_ADCSR_ADERR BIT(15) +#define DT2821_ADCSR_ADCLK BIT(9) +#define DT2821_ADCSR_MUXBUSY BIT(8) +#define DT2821_ADCSR_ADDONE BIT(7) +#define DT2821_ADCSR_IADDONE BIT(6) +#define DT2821_ADCSR_GS(x) (((x) & 0x3) << 4) +#define DT2821_ADCSR_CHAN(x) (((x) & 0xf) << 0) +#define DT2821_CHANCSR_REG 0x02 +#define DT2821_CHANCSR_LLE BIT(15) +#define DT2821_CHANCSR_TO_PRESLA(x) (((x) >> 8) & 0xf) +#define DT2821_CHANCSR_NUMB(x) ((((x) - 1) & 0xf) << 0) +#define DT2821_ADDAT_REG 0x04 +#define DT2821_DACSR_REG 0x06 +#define DT2821_DACSR_DAERR BIT(15) +#define DT2821_DACSR_YSEL(x) ((x) << 9) +#define DT2821_DACSR_SSEL BIT(8) +#define DT2821_DACSR_DACRDY BIT(7) +#define DT2821_DACSR_IDARDY BIT(6) +#define DT2821_DACSR_DACLK BIT(5) +#define DT2821_DACSR_HBOE BIT(1) +#define DT2821_DACSR_LBOE BIT(0) +#define DT2821_DADAT_REG 0x08 +#define DT2821_DIODAT_REG 0x0a +#define DT2821_SUPCSR_REG 0x0c +#define DT2821_SUPCSR_DMAD BIT(15) +#define DT2821_SUPCSR_ERRINTEN BIT(14) +#define DT2821_SUPCSR_CLRDMADNE BIT(13) +#define DT2821_SUPCSR_DDMA BIT(12) +#define DT2821_SUPCSR_DS(x) (((x) & 0x3) << 10) +#define DT2821_SUPCSR_DS_PIO DT2821_SUPCSR_DS(0) +#define DT2821_SUPCSR_DS_AD_CLK DT2821_SUPCSR_DS(1) +#define DT2821_SUPCSR_DS_DA_CLK DT2821_SUPCSR_DS(2) +#define DT2821_SUPCSR_DS_AD_TRIG DT2821_SUPCSR_DS(3) +#define DT2821_SUPCSR_BUFFB BIT(9) +#define DT2821_SUPCSR_SCDN BIT(8) +#define DT2821_SUPCSR_DACON BIT(7) +#define DT2821_SUPCSR_ADCINIT BIT(6) +#define DT2821_SUPCSR_DACINIT BIT(5) +#define DT2821_SUPCSR_PRLD BIT(4) +#define DT2821_SUPCSR_STRIG BIT(3) +#define DT2821_SUPCSR_XTRIG BIT(2) +#define DT2821_SUPCSR_XCLK BIT(1) +#define DT2821_SUPCSR_BDINIT BIT(0) +#define DT2821_TMRCTR_REG 0x0e +#define DT2821_TMRCTR_PRESCALE(x) (((x) & 0xf) << 8) +#define DT2821_TMRCTR_DIVIDER(x) ((255 - ((x) & 0xff)) << 0) + +/* Pacer Clock */ +#define DT2821_OSC_BASE 250 /* 4 MHz (in nanoseconds) */ +#define DT2821_PRESCALE(x) BIT(x) +#define DT2821_PRESCALE_MAX 15 +#define DT2821_DIVIDER_MAX 255 +#define DT2821_OSC_MAX (DT2821_OSC_BASE * \ + DT2821_PRESCALE(DT2821_PRESCALE_MAX) * \ + DT2821_DIVIDER_MAX) + +static const struct comedi_lrange range_dt282x_ai_lo_bipolar = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_dt282x_ai_lo_unipolar = { + 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_dt282x_ai_5_bipolar = { + 4, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625) + } +}; + +static const struct comedi_lrange range_dt282x_ai_5_unipolar = { + 4, { + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + UNI_RANGE(0.625) + } +}; + +static const struct comedi_lrange range_dt282x_ai_hi_bipolar = { + 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.02) + } +}; + +static const struct comedi_lrange range_dt282x_ai_hi_unipolar = { + 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.02) + } +}; + +/* + * The Analog Output range is set per-channel using jumpers on the board. + * All of these ranges may not be available on some DT2821 series boards. + * The default jumper setting has both channels set for +/-10V output. + */ +static const struct comedi_lrange dt282x_ao_range = { + 5, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + UNI_RANGE(10), + UNI_RANGE(5), + } +}; + +struct dt282x_board { + const char *name; + unsigned int ai_maxdata; + int adchan_se; + int adchan_di; + int ai_speed; + int ispgl; + int dachan; + unsigned int ao_maxdata; +}; + +static const struct dt282x_board boardtypes[] = { + { + .name = "dt2821", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt2821-f", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 6500, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt2821-g", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 4000, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt2823", + .ai_maxdata = 0xffff, + .adchan_di = 4, + .ai_speed = 10000, + .dachan = 2, + .ao_maxdata = 0xffff, + }, { + .name = "dt2824-pgh", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + }, { + .name = "dt2824-pgl", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 1, + }, { + .name = "dt2825", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 1, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt2827", + .ai_maxdata = 0xffff, + .adchan_di = 4, + .ai_speed = 10000, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt2828", + .ai_maxdata = 0x0fff, + .adchan_se = 4, + .ai_speed = 10000, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt2829", + .ai_maxdata = 0xffff, + .adchan_se = 8, + .ai_speed = 33250, + .dachan = 2, + .ao_maxdata = 0xffff, + }, { + .name = "dt21-ez", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .dachan = 2, + .ao_maxdata = 0x0fff, + }, { + .name = "dt23-ez", + .ai_maxdata = 0xffff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + }, { + .name = "dt24-ez", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + }, { + .name = "dt24-ez-pgl", + .ai_maxdata = 0x0fff, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 1, + }, +}; + +struct dt282x_private { + struct comedi_isadma *dma; + unsigned int ad_2scomp:1; + unsigned int divisor; + int dacsr; /* software copies of registers */ + int adcsr; + int supcsr; + int ntrig; + int nread; + int dma_dir; +}; + +static int dt282x_prep_ai_dma(struct comedi_device *dev, int dma_index, int n) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma_index]; + + if (!devpriv->ntrig) + return 0; + + if (n == 0) + n = desc->maxsize; + if (n > devpriv->ntrig * 2) + n = devpriv->ntrig * 2; + devpriv->ntrig -= n / 2; + + desc->size = n; + comedi_isadma_set_mode(desc, devpriv->dma_dir); + + comedi_isadma_program(desc); + + return n; +} + +static int dt282x_prep_ao_dma(struct comedi_device *dev, int dma_index, int n) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma_index]; + + desc->size = n; + comedi_isadma_set_mode(desc, devpriv->dma_dir); + + comedi_isadma_program(desc); + + return n; +} + +static void dt282x_disable_dma(struct comedi_device *dev) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc; + int i; + + for (i = 0; i < 2; i++) { + desc = &dma->desc[i]; + comedi_isadma_disable(desc->chan); + } +} + +static unsigned int dt282x_ns_to_timer(unsigned int *ns, unsigned int flags) +{ + unsigned int prescale, base, divider; + + for (prescale = 0; prescale <= DT2821_PRESCALE_MAX; prescale++) { + if (prescale == 1) /* 0 and 1 are both divide by 1 */ + continue; + base = DT2821_OSC_BASE * DT2821_PRESCALE(prescale); + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + divider = DIV_ROUND_CLOSEST(*ns, base); + break; + case CMDF_ROUND_DOWN: + divider = (*ns) / base; + break; + case CMDF_ROUND_UP: + divider = DIV_ROUND_UP(*ns, base); + break; + } + if (divider <= DT2821_DIVIDER_MAX) + break; + } + if (divider > DT2821_DIVIDER_MAX) { + prescale = DT2821_PRESCALE_MAX; + divider = DT2821_DIVIDER_MAX; + base = DT2821_OSC_BASE * DT2821_PRESCALE(prescale); + } + *ns = divider * base; + return DT2821_TMRCTR_PRESCALE(prescale) | + DT2821_TMRCTR_DIVIDER(divider); +} + +static void dt282x_munge(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *buf, + unsigned int nbytes) +{ + struct dt282x_private *devpriv = dev->private; + unsigned int val; + int i; + + if (nbytes % 2) + dev_err(dev->class_dev, + "bug! odd number of bytes from dma xfer\n"); + + for (i = 0; i < nbytes / 2; i++) { + val = buf[i]; + val &= s->maxdata; + if (devpriv->ad_2scomp) + val = comedi_offset_munge(s, val); + + buf[i] = val; + } +} + +static unsigned int dt282x_ao_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + int cur_dma) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[cur_dma]; + unsigned int nsamples = comedi_bytes_to_samples(s, desc->maxsize); + unsigned int nbytes; + + nbytes = comedi_buf_read_samples(s, desc->virt_addr, nsamples); + if (nbytes) + dt282x_prep_ao_dma(dev, cur_dma, nbytes); + else + dev_err(dev->class_dev, "AO underrun\n"); + + return nbytes; +} + +static void dt282x_ao_dma_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + + outw(devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE, + dev->iobase + DT2821_SUPCSR_REG); + + comedi_isadma_disable(desc->chan); + + if (!dt282x_ao_setup_dma(dev, s, dma->cur_dma)) + s->async->events |= COMEDI_CB_OVERFLOW; + + dma->cur_dma = 1 - dma->cur_dma; +} + +static void dt282x_ai_dma_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int nsamples = comedi_bytes_to_samples(s, desc->size); + int ret; + + outw(devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE, + dev->iobase + DT2821_SUPCSR_REG); + + comedi_isadma_disable(desc->chan); + + dt282x_munge(dev, s, desc->virt_addr, desc->size); + ret = comedi_buf_write_samples(s, desc->virt_addr, nsamples); + if (ret != desc->size) + return; + + devpriv->nread -= nsamples; + if (devpriv->nread < 0) { + dev_info(dev->class_dev, "nread off by one\n"); + devpriv->nread = 0; + } + if (!devpriv->nread) { + s->async->events |= COMEDI_CB_EOA; + return; + } + + /* restart the channel */ + dt282x_prep_ai_dma(dev, dma->cur_dma, 0); + + dma->cur_dma = 1 - dma->cur_dma; +} + +static irqreturn_t dt282x_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct dt282x_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_subdevice *s_ao = dev->write_subdev; + unsigned int supcsr, adcsr, dacsr; + int handled = 0; + + if (!dev->attached) { + dev_err(dev->class_dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + + adcsr = inw(dev->iobase + DT2821_ADCSR_REG); + dacsr = inw(dev->iobase + DT2821_DACSR_REG); + supcsr = inw(dev->iobase + DT2821_SUPCSR_REG); + if (supcsr & DT2821_SUPCSR_DMAD) { + if (devpriv->dma_dir == COMEDI_ISADMA_READ) + dt282x_ai_dma_interrupt(dev, s); + else + dt282x_ao_dma_interrupt(dev, s_ao); + handled = 1; + } + if (adcsr & DT2821_ADCSR_ADERR) { + if (devpriv->nread != 0) { + dev_err(dev->class_dev, "A/D error\n"); + s->async->events |= COMEDI_CB_ERROR; + } + handled = 1; + } + if (dacsr & DT2821_DACSR_DAERR) { + dev_err(dev->class_dev, "D/A error\n"); + s_ao->async->events |= COMEDI_CB_ERROR; + handled = 1; + } + + comedi_handle_events(dev, s); + if (s_ao) + comedi_handle_events(dev, s_ao); + + return IRQ_RETVAL(handled); +} + +static void dt282x_load_changain(struct comedi_device *dev, int n, + unsigned int *chanlist) +{ + struct dt282x_private *devpriv = dev->private; + int i; + + outw(DT2821_CHANCSR_LLE | DT2821_CHANCSR_NUMB(n), + dev->iobase + DT2821_CHANCSR_REG); + for (i = 0; i < n; i++) { + unsigned int chan = CR_CHAN(chanlist[i]); + unsigned int range = CR_RANGE(chanlist[i]); + + outw(devpriv->adcsr | + DT2821_ADCSR_GS(range) | + DT2821_ADCSR_CHAN(chan), + dev->iobase + DT2821_ADCSR_REG); + } + outw(DT2821_CHANCSR_NUMB(n), dev->iobase + DT2821_CHANCSR_REG); +} + +static int dt282x_ai_timeout(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + DT2821_ADCSR_REG); + switch (context) { + case DT2821_ADCSR_MUXBUSY: + if ((status & DT2821_ADCSR_MUXBUSY) == 0) + return 0; + break; + case DT2821_ADCSR_ADDONE: + if (status & DT2821_ADCSR_ADDONE) + return 0; + break; + default: + return -EINVAL; + } + return -EBUSY; +} + +/* + * Performs a single A/D conversion. + * - Put channel/gain into channel-gain list + * - preload multiplexer + * - trigger conversion and wait for it to finish + */ +static int dt282x_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dt282x_private *devpriv = dev->private; + unsigned int val; + int ret; + int i; + + /* XXX should we really be enabling the ad clock here? */ + devpriv->adcsr = DT2821_ADCSR_ADCLK; + outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG); + + dt282x_load_changain(dev, 1, &insn->chanspec); + + outw(devpriv->supcsr | DT2821_SUPCSR_PRLD, + dev->iobase + DT2821_SUPCSR_REG); + ret = comedi_timeout(dev, s, insn, + dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + outw(devpriv->supcsr | DT2821_SUPCSR_STRIG, + dev->iobase + DT2821_SUPCSR_REG); + + ret = comedi_timeout(dev, s, insn, + dt282x_ai_timeout, DT2821_ADCSR_ADDONE); + if (ret) + return ret; + + val = inw(dev->iobase + DT2821_ADDAT_REG); + val &= s->maxdata; + if (devpriv->ad_2scomp) + val = comedi_offset_munge(s, val); + + data[i] = val; + } + + return i; +} + +static int dt282x_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct dt282x_board *board = dev->board_ptr; + struct dt282x_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, DT2821_OSC_MAX); + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, board->ai_speed); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_EXT | TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->convert_arg; + devpriv->divisor = dt282x_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (err) + return 4; + + return 0; +} + +static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_cmd *cmd = &s->async->cmd; + int ret; + + dt282x_disable_dma(dev); + + outw(devpriv->divisor, dev->iobase + DT2821_TMRCTR_REG); + + devpriv->supcsr = DT2821_SUPCSR_ERRINTEN; + if (cmd->scan_begin_src == TRIG_FOLLOW) + devpriv->supcsr = DT2821_SUPCSR_DS_AD_CLK; + else + devpriv->supcsr = DT2821_SUPCSR_DS_AD_TRIG; + outw(devpriv->supcsr | + DT2821_SUPCSR_CLRDMADNE | + DT2821_SUPCSR_BUFFB | + DT2821_SUPCSR_ADCINIT, + dev->iobase + DT2821_SUPCSR_REG); + + devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg; + devpriv->nread = devpriv->ntrig; + + devpriv->dma_dir = COMEDI_ISADMA_READ; + dma->cur_dma = 0; + dt282x_prep_ai_dma(dev, 0, 0); + if (devpriv->ntrig) { + dt282x_prep_ai_dma(dev, 1, 0); + devpriv->supcsr |= DT2821_SUPCSR_DDMA; + outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG); + } + + devpriv->adcsr = 0; + + dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist); + + devpriv->adcsr = DT2821_ADCSR_ADCLK | DT2821_ADCSR_IADDONE; + outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG); + + outw(devpriv->supcsr | DT2821_SUPCSR_PRLD, + dev->iobase + DT2821_SUPCSR_REG); + ret = comedi_timeout(dev, s, NULL, + dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY); + if (ret) + return ret; + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + outw(devpriv->supcsr | DT2821_SUPCSR_STRIG, + dev->iobase + DT2821_SUPCSR_REG); + } else { + devpriv->supcsr |= DT2821_SUPCSR_XTRIG; + outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG); + } + + return 0; +} + +static int dt282x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dt282x_private *devpriv = dev->private; + + dt282x_disable_dma(dev); + + devpriv->adcsr = 0; + outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG); + + devpriv->supcsr = 0; + outw(devpriv->supcsr | DT2821_SUPCSR_ADCINIT, + dev->iobase + DT2821_SUPCSR_REG); + + return 0; +} + +static int dt282x_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dt282x_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int i; + + devpriv->dacsr |= DT2821_DACSR_SSEL | DT2821_DACSR_YSEL(chan); + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + if (comedi_range_is_bipolar(s, range)) + val = comedi_offset_munge(s, val); + + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG); + + outw(val, dev->iobase + DT2821_DADAT_REG); + + outw(devpriv->supcsr | DT2821_SUPCSR_DACON, + dev->iobase + DT2821_SUPCSR_REG); + } + + return insn->n; +} + +static int dt282x_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct dt282x_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 5000); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_EXT | TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + arg = cmd->scan_begin_arg; + devpriv->divisor = dt282x_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + + if (err) + return 4; + + return 0; +} + +static int dt282x_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_src) + return -EINVAL; + + if (!dt282x_ao_setup_dma(dev, s, 0)) + return -EPIPE; + + if (!dt282x_ao_setup_dma(dev, s, 1)) + return -EPIPE; + + outw(devpriv->supcsr | DT2821_SUPCSR_STRIG, + dev->iobase + DT2821_SUPCSR_REG); + s->async->inttrig = NULL; + + return 1; +} + +static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct dt282x_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_cmd *cmd = &s->async->cmd; + + dt282x_disable_dma(dev); + + devpriv->supcsr = DT2821_SUPCSR_ERRINTEN | + DT2821_SUPCSR_DS_DA_CLK | + DT2821_SUPCSR_DDMA; + outw(devpriv->supcsr | + DT2821_SUPCSR_CLRDMADNE | + DT2821_SUPCSR_BUFFB | + DT2821_SUPCSR_DACINIT, + dev->iobase + DT2821_SUPCSR_REG); + + devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len; + devpriv->nread = devpriv->ntrig; + + devpriv->dma_dir = COMEDI_ISADMA_WRITE; + dma->cur_dma = 0; + + outw(devpriv->divisor, dev->iobase + DT2821_TMRCTR_REG); + + /* clear all bits but the DIO direction bits */ + devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE); + + devpriv->dacsr |= (DT2821_DACSR_SSEL | + DT2821_DACSR_DACLK | + DT2821_DACSR_IDARDY); + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG); + + s->async->inttrig = dt282x_ao_inttrig; + + return 0; +} + +static int dt282x_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dt282x_private *devpriv = dev->private; + + dt282x_disable_dma(dev); + + /* clear all bits but the DIO direction bits */ + devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE); + + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG); + + devpriv->supcsr = 0; + outw(devpriv->supcsr | DT2821_SUPCSR_DACINIT, + dev->iobase + DT2821_SUPCSR_REG); + + return 0; +} + +static int dt282x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + DT2821_DIODAT_REG); + + data[1] = inw(dev->iobase + DT2821_DIODAT_REG); + + return insn->n; +} + +static int dt282x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dt282x_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x00ff; + else + mask = 0xff00; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + devpriv->dacsr &= ~(DT2821_DACSR_LBOE | DT2821_DACSR_HBOE); + if (s->io_bits & 0x00ff) + devpriv->dacsr |= DT2821_DACSR_LBOE; + if (s->io_bits & 0xff00) + devpriv->dacsr |= DT2821_DACSR_HBOE; + + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG); + + return insn->n; +} + +static const struct comedi_lrange *const ai_range_table[] = { + &range_dt282x_ai_lo_bipolar, + &range_dt282x_ai_lo_unipolar, + &range_dt282x_ai_5_bipolar, + &range_dt282x_ai_5_unipolar +}; + +static const struct comedi_lrange *const ai_range_pgl_table[] = { + &range_dt282x_ai_hi_bipolar, + &range_dt282x_ai_hi_unipolar +}; + +static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x) +{ + if (ispgl) { + if (x < 0 || x >= 2) + x = 0; + return ai_range_pgl_table[x]; + } + + if (x < 0 || x >= 4) + x = 0; + return ai_range_table[x]; +} + +static void dt282x_alloc_dma(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct dt282x_private *devpriv = dev->private; + unsigned int irq_num = it->options[1]; + unsigned int dma_chan[2]; + + if (it->options[2] < it->options[3]) { + dma_chan[0] = it->options[2]; + dma_chan[1] = it->options[3]; + } else { + dma_chan[0] = it->options[3]; + dma_chan[1] = it->options[2]; + } + + if (!irq_num || dma_chan[0] == dma_chan[1] || + dma_chan[0] < 5 || dma_chan[0] > 7 || + dma_chan[1] < 5 || dma_chan[1] > 7) + return; + + if (request_irq(irq_num, dt282x_interrupt, 0, dev->board_name, dev)) + return; + + /* DMA uses two 4K buffers with separate DMA channels */ + devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan[0], dma_chan[1], + PAGE_SIZE, 0); + if (!devpriv->dma) + free_irq(irq_num, dev); + else + dev->irq = irq_num; +} + +static void dt282x_free_dma(struct comedi_device *dev) +{ + struct dt282x_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} + +static int dt282x_initialize(struct comedi_device *dev) +{ + /* Initialize board */ + outw(DT2821_SUPCSR_BDINIT, dev->iobase + DT2821_SUPCSR_REG); + inw(dev->iobase + DT2821_ADCSR_REG); + + /* + * At power up, some registers are in a well-known state. + * Check them to see if a DT2821 series board is present. + */ + if (((inw(dev->iobase + DT2821_ADCSR_REG) & 0xfff0) != 0x7c00) || + ((inw(dev->iobase + DT2821_CHANCSR_REG) & 0xf0f0) != 0x70f0) || + ((inw(dev->iobase + DT2821_DACSR_REG) & 0x7c93) != 0x7c90) || + ((inw(dev->iobase + DT2821_SUPCSR_REG) & 0xf8ff) != 0x0000) || + ((inw(dev->iobase + DT2821_TMRCTR_REG) & 0xff00) != 0xf000)) { + dev_err(dev->class_dev, "board not found\n"); + return -EIO; + } + return 0; +} + +static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct dt282x_board *board = dev->board_ptr; + struct dt282x_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + ret = dt282x_initialize(dev); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* an IRQ and 2 DMA channels are required for async command support */ + dt282x_alloc_dma(dev, it); + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + if ((it->options[4] && board->adchan_di) || board->adchan_se == 0) { + s->subdev_flags |= SDF_DIFF; + s->n_chan = board->adchan_di; + } else { + s->subdev_flags |= SDF_COMMON; + s->n_chan = board->adchan_se; + } + s->maxdata = board->ai_maxdata; + + s->range_table = opt_ai_range_lkup(board->ispgl, it->options[8]); + devpriv->ad_2scomp = it->options[5] ? 1 : 0; + + s->insn_read = dt282x_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = dt282x_ai_cmdtest; + s->do_cmd = dt282x_ai_cmd; + s->cancel = dt282x_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->dachan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->dachan; + s->maxdata = board->ao_maxdata; + /* ranges are per-channel, set by jumpers on the board */ + s->range_table = &dt282x_ao_range; + s->insn_write = dt282x_ao_insn_write; + if (dev->irq) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->len_chanlist = s->n_chan; + s->do_cmdtest = dt282x_ao_cmdtest; + s->do_cmd = dt282x_ao_cmd; + s->cancel = dt282x_ao_cancel; + } + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt282x_dio_insn_bits; + s->insn_config = dt282x_dio_insn_config; + + return 0; +} + +static void dt282x_detach(struct comedi_device *dev) +{ + dt282x_free_dma(dev); + comedi_legacy_detach(dev); +} + +static struct comedi_driver dt282x_driver = { + .driver_name = "dt282x", + .module = THIS_MODULE, + .attach = dt282x_attach, + .detach = dt282x_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct dt282x_board), +}; +module_comedi_driver(dt282x_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Data Translation DT2821 series"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt3000.c b/drivers/comedi/drivers/dt3000.c new file mode 100644 index 000000000..fc6e9c30e --- /dev/null +++ b/drivers/comedi/drivers/dt3000.c @@ -0,0 +1,739 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dt3000.c + * Data Translation DT3000 series driver + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1999 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: dt3000 + * Description: Data Translation DT3000 series + * Devices: [Data Translation] DT3001 (dt3000), DT3001-PGL, DT3002, DT3003, + * DT3003-PGL, DT3004, DT3005, DT3004-200 + * Author: ds + * Updated: Mon, 14 Apr 2008 15:41:24 +0100 + * Status: works + * + * Configuration Options: not applicable, uses PCI auto config + * + * There is code to support AI commands, but it may not work. + * + * AO commands are not supported. + */ + +/* + * The DT3000 series is Data Translation's attempt to make a PCI + * data acquisition board. The design of this series is very nice, + * since each board has an on-board DSP (Texas Instruments TMS320C52). + * However, a few details are a little annoying. The boards lack + * bus-mastering DMA, which eliminates them from serious work. + * They also are not capable of autocalibration, which is a common + * feature in modern hardware. The default firmware is pretty bad, + * making it nearly impossible to write an RT compatible driver. + * It would make an interesting project to write a decent firmware + * for these boards. + * + * Data Translation originally wanted an NDA for the documentation + * for the 3k series. However, if you ask nicely, they might send + * you the docs without one, also. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI BAR0 - dual-ported RAM location definitions (dev->mmio) + */ +#define DPR_DAC_BUFFER (4 * 0x000) +#define DPR_ADC_BUFFER (4 * 0x800) +#define DPR_COMMAND (4 * 0xfd3) +#define DPR_SUBSYS (4 * 0xfd3) +#define DPR_SUBSYS_AI 0 +#define DPR_SUBSYS_AO 1 +#define DPR_SUBSYS_DIN 2 +#define DPR_SUBSYS_DOUT 3 +#define DPR_SUBSYS_MEM 4 +#define DPR_SUBSYS_CT 5 +#define DPR_ENCODE (4 * 0xfd4) +#define DPR_PARAMS(x) (4 * (0xfd5 + (x))) +#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_INTR_CMDONE BIT(7) +#define DPR_INTR_CTDONE BIT(6) +#define DPR_INTR_DAHWERR BIT(5) +#define DPR_INTR_DASWERR BIT(4) +#define DPR_INTR_DAEMPTY BIT(3) +#define DPR_INTR_ADHWERR BIT(2) +#define DPR_INTR_ADSWERR BIT(1) +#define DPR_INTR_ADFULL BIT(0) +#define DPR_RESPONSE_MBX (4 * 0xffe) +#define DPR_CMD_MBX (4 * 0xfff) +#define DPR_CMD_COMPLETION(x) ((x) << 8) +#define DPR_CMD_NOTPROCESSED DPR_CMD_COMPLETION(0x00) +#define DPR_CMD_NOERROR DPR_CMD_COMPLETION(0x55) +#define DPR_CMD_ERROR DPR_CMD_COMPLETION(0xaa) +#define DPR_CMD_NOTSUPPORTED DPR_CMD_COMPLETION(0xff) +#define DPR_CMD_COMPLETION_MASK DPR_CMD_COMPLETION(0xff) +#define DPR_CMD(x) ((x) << 0) +#define DPR_CMD_GETBRDINFO DPR_CMD(0) +#define DPR_CMD_CONFIG DPR_CMD(1) +#define DPR_CMD_GETCONFIG DPR_CMD(2) +#define DPR_CMD_START DPR_CMD(3) +#define DPR_CMD_STOP DPR_CMD(4) +#define DPR_CMD_READSINGLE DPR_CMD(5) +#define DPR_CMD_WRITESINGLE DPR_CMD(6) +#define DPR_CMD_CALCCLOCK DPR_CMD(7) +#define DPR_CMD_READEVENTS DPR_CMD(8) +#define DPR_CMD_WRITECTCTRL DPR_CMD(16) +#define DPR_CMD_READCTCTRL DPR_CMD(17) +#define DPR_CMD_WRITECT DPR_CMD(18) +#define DPR_CMD_READCT DPR_CMD(19) +#define DPR_CMD_WRITEDATA DPR_CMD(32) +#define DPR_CMD_READDATA DPR_CMD(33) +#define DPR_CMD_WRITEIO DPR_CMD(34) +#define DPR_CMD_READIO DPR_CMD(35) +#define DPR_CMD_WRITECODE DPR_CMD(36) +#define DPR_CMD_READCODE DPR_CMD(37) +#define DPR_CMD_EXECUTE DPR_CMD(38) +#define DPR_CMD_HALT DPR_CMD(48) +#define DPR_CMD_MASK DPR_CMD(0xff) + +#define DPR_PARAM5_AD_TRIG(x) (((x) & 0x7) << 2) +#define DPR_PARAM5_AD_TRIG_INT DPR_PARAM5_AD_TRIG(0) +#define DPR_PARAM5_AD_TRIG_EXT DPR_PARAM5_AD_TRIG(1) +#define DPR_PARAM5_AD_TRIG_INT_RETRIG DPR_PARAM5_AD_TRIG(2) +#define DPR_PARAM5_AD_TRIG_EXT_RETRIG DPR_PARAM5_AD_TRIG(3) +#define DPR_PARAM5_AD_TRIG_INT_RETRIG2 DPR_PARAM5_AD_TRIG(4) + +#define DPR_PARAM6_AD_DIFF BIT(0) + +#define DPR_AI_FIFO_DEPTH 2003 +#define DPR_AO_FIFO_DEPTH 2048 + +#define DPR_EXTERNAL_CLOCK 1 +#define DPR_RISING_EDGE 2 + +#define DPR_TMODE_MASK 0x1c + +#define DPR_CMD_TIMEOUT 100 + +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 ai_speed; + const struct comedi_lrange *adrange; + unsigned int ai_is_16bit:1; + unsigned int has_ao:1; +}; + +static const struct dt3k_boardtype dt3k_boardtypes[] = { + [BOARD_DT3001] = { + .name = "dt3001", + .adchan = 16, + .adrange = &range_dt3000_ai, + .ai_speed = 3000, + .has_ao = 1, + }, + [BOARD_DT3001_PGL] = { + .name = "dt3001-pgl", + .adchan = 16, + .adrange = &range_dt3000_ai_pgl, + .ai_speed = 3000, + .has_ao = 1, + }, + [BOARD_DT3002] = { + .name = "dt3002", + .adchan = 32, + .adrange = &range_dt3000_ai, + .ai_speed = 3000, + }, + [BOARD_DT3003] = { + .name = "dt3003", + .adchan = 64, + .adrange = &range_dt3000_ai, + .ai_speed = 3000, + .has_ao = 1, + }, + [BOARD_DT3003_PGL] = { + .name = "dt3003-pgl", + .adchan = 64, + .adrange = &range_dt3000_ai_pgl, + .ai_speed = 3000, + .has_ao = 1, + }, + [BOARD_DT3004] = { + .name = "dt3004", + .adchan = 16, + .adrange = &range_dt3000_ai, + .ai_speed = 10000, + .ai_is_16bit = 1, + .has_ao = 1, + }, + [BOARD_DT3005] = { + .name = "dt3005", /* a.k.a. 3004-200 */ + .adchan = 16, + .adrange = &range_dt3000_ai, + .ai_speed = 5000, + .ai_is_16bit = 1, + .has_ao = 1, + }, +}; + +struct dt3k_private { + unsigned int lock; + unsigned int ai_front; + unsigned int ai_rear; +}; + +static void dt3k_send_cmd(struct comedi_device *dev, unsigned int cmd) +{ + int i; + unsigned int status = 0; + + writew(cmd, dev->mmio + DPR_CMD_MBX); + + for (i = 0; i < DPR_CMD_TIMEOUT; i++) { + status = readw(dev->mmio + DPR_CMD_MBX); + status &= DPR_CMD_COMPLETION_MASK; + if (status != DPR_CMD_NOTPROCESSED) + break; + udelay(1); + } + + if (status != DPR_CMD_NOERROR) + dev_dbg(dev->class_dev, "%s: timeout/error status=0x%04x\n", + __func__, status); +} + +static unsigned int dt3k_readsingle(struct comedi_device *dev, + unsigned int subsys, unsigned int chan, + unsigned int gain) +{ + writew(subsys, dev->mmio + DPR_SUBSYS); + + writew(chan, dev->mmio + DPR_PARAMS(0)); + writew(gain, dev->mmio + DPR_PARAMS(1)); + + dt3k_send_cmd(dev, DPR_CMD_READSINGLE); + + return readw(dev->mmio + DPR_PARAMS(2)); +} + +static void dt3k_writesingle(struct comedi_device *dev, unsigned int subsys, + unsigned int chan, unsigned int data) +{ + writew(subsys, dev->mmio + DPR_SUBSYS); + + writew(chan, dev->mmio + DPR_PARAMS(0)); + writew(0, dev->mmio + DPR_PARAMS(1)); + writew(data, dev->mmio + DPR_PARAMS(2)); + + dt3k_send_cmd(dev, DPR_CMD_WRITESINGLE); +} + +static void dt3k_ai_empty_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dt3k_private *devpriv = dev->private; + int front; + int rear; + int count; + int i; + unsigned short data; + + front = readw(dev->mmio + DPR_AD_BUF_FRONT); + count = front - devpriv->ai_front; + if (count < 0) + count += DPR_AI_FIFO_DEPTH; + + rear = devpriv->ai_rear; + + for (i = 0; i < count; i++) { + data = readw(dev->mmio + DPR_ADC_BUFFER + rear); + comedi_buf_write_samples(s, &data, 1); + rear++; + if (rear >= DPR_AI_FIFO_DEPTH) + rear = 0; + } + + devpriv->ai_rear = rear; + writew(rear, dev->mmio + DPR_AD_BUF_REAR); +} + +static int dt3k_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writew(DPR_SUBSYS_AI, dev->mmio + DPR_SUBSYS); + dt3k_send_cmd(dev, DPR_CMD_STOP); + + writew(0, dev->mmio + DPR_INT_MASK); + + return 0; +} + +static int debug_n_ints; + +/* FIXME! Assumes shared interrupt is for this card. */ +/* What's this debug_n_ints stuff? Obviously needs some work... */ +static irqreturn_t dt3k_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + + if (!dev->attached) + return IRQ_NONE; + + status = readw(dev->mmio + DPR_INTR_FLAG); + + if (status & DPR_INTR_ADFULL) + dt3k_ai_empty_fifo(dev, s); + + if (status & (DPR_INTR_ADSWERR | DPR_INTR_ADHWERR)) + s->async->events |= COMEDI_CB_ERROR; + + debug_n_ints++; + if (debug_n_ints >= 10) + s->async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *nanosec, + unsigned int flags) +{ + unsigned int divider, base, prescale; + + /* This function needs improvement */ + /* Don't know if divider==0 works. */ + + for (prescale = 0; prescale < 16; prescale++) { + base = timer_base * (prescale + 1); + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + divider = DIV_ROUND_CLOSEST(*nanosec, base); + break; + case CMDF_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case CMDF_ROUND_UP: + divider = DIV_ROUND_UP(*nanosec, base); + break; + } + if (divider < 65536) { + *nanosec = divider * base; + return (prescale << 16) | (divider); + } + } + + prescale = 15; + base = timer_base * (prescale + 1); + 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 *board = dev->board_ptr; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ai_speed); + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + 100 * 16 * 65535); + } + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, + 50 * 16 * 65535); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + dt3k_ns_to_timer(100, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + dt3k_ns_to_timer(50, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, arg); + } + } + + if (err) + return 4; + + return 0; +} + +static int dt3k_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int i; + unsigned int chan, range, aref; + unsigned int divider; + unsigned int tscandiv; + + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + + writew((range << 6) | chan, dev->mmio + DPR_ADC_BUFFER + i); + } + aref = CR_AREF(cmd->chanlist[0]); + + writew(cmd->scan_end_arg, dev->mmio + DPR_PARAMS(0)); + + if (cmd->convert_src == TRIG_TIMER) { + divider = dt3k_ns_to_timer(50, &cmd->convert_arg, cmd->flags); + writew((divider >> 16), dev->mmio + DPR_PARAMS(1)); + writew((divider & 0xffff), dev->mmio + DPR_PARAMS(2)); + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + tscandiv = dt3k_ns_to_timer(100, &cmd->scan_begin_arg, + cmd->flags); + writew((tscandiv >> 16), dev->mmio + DPR_PARAMS(3)); + writew((tscandiv & 0xffff), dev->mmio + DPR_PARAMS(4)); + } + + writew(DPR_PARAM5_AD_TRIG_INT_RETRIG, dev->mmio + DPR_PARAMS(5)); + writew((aref == AREF_DIFF) ? DPR_PARAM6_AD_DIFF : 0, + dev->mmio + DPR_PARAMS(6)); + + writew(DPR_AI_FIFO_DEPTH / 2, dev->mmio + DPR_PARAMS(7)); + + writew(DPR_SUBSYS_AI, dev->mmio + DPR_SUBSYS); + dt3k_send_cmd(dev, DPR_CMD_CONFIG); + + writew(DPR_INTR_ADFULL | DPR_INTR_ADSWERR | DPR_INTR_ADHWERR, + dev->mmio + DPR_INT_MASK); + + debug_n_ints = 0; + + writew(DPR_SUBSYS_AI, dev->mmio + DPR_SUBSYS); + dt3k_send_cmd(dev, DPR_CMD_START); + + return 0; +} + +static int dt3k_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + unsigned int chan, gain; + + chan = CR_CHAN(insn->chanspec); + gain = CR_RANGE(insn->chanspec); + /* XXX docs don't explain how to select aref */ + + for (i = 0; i < insn->n; i++) + data[i] = dt3k_readsingle(dev, DPR_SUBSYS_AI, chan, gain); + + return i; +} + +static int dt3k_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + dt3k_writesingle(dev, DPR_SUBSYS_AO, chan, val); + } + s->readback[chan] = val; + + return insn->n; +} + +static void dt3k_dio_config(struct comedi_device *dev, int bits) +{ + /* XXX */ + writew(DPR_SUBSYS_DOUT, dev->mmio + DPR_SUBSYS); + + writew(bits, dev->mmio + DPR_PARAMS(0)); + + /* XXX write 0 to DPR_PARAMS(1) and DPR_PARAMS(2) ? */ + + dt3k_send_cmd(dev, DPR_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, DPR_SUBSYS_DOUT, 0, s->state); + + data[1] = dt3k_readsingle(dev, DPR_SUBSYS_DIN, 0, 0); + + return insn->n; +} + +static int dt3k_mem_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int addr = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + writew(DPR_SUBSYS_MEM, dev->mmio + DPR_SUBSYS); + writew(addr, dev->mmio + DPR_PARAMS(0)); + writew(1, dev->mmio + DPR_PARAMS(1)); + + dt3k_send_cmd(dev, DPR_CMD_READCODE); + + data[i] = readw(dev->mmio + DPR_PARAMS(2)); + } + + return i; +} + +static int dt3000_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct dt3k_boardtype *board = NULL; + struct dt3k_private *devpriv; + struct comedi_subdevice *s; + int ret = 0; + + if (context < ARRAY_SIZE(dt3k_boardtypes)) + board = &dt3k_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 < 0) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 0); + if (!dev->mmio) + return -ENOMEM; + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, dt3k_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = board->adchan; + s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff; + s->range_table = &range_dt3000_ai; /* XXX */ + s->insn_read = dt3k_ai_insn_read; + 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; + } + + /* 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 = &range_bipolar10; + s->insn_write = dt3k_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = dt3k_dio_insn_config; + s->insn_bits = dt3k_dio_insn_bits; + + /* Memory subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE; + s->n_chan = 0x1000; + s->maxdata = 0xff; + s->range_table = &range_unknown; + s->insn_read = dt3k_mem_insn_read; + + return 0; +} + +static struct comedi_driver dt3000_driver = { + .driver_name = "dt3000", + .module = THIS_MODULE, + .auto_attach = dt3000_auto_attach, + .detach = comedi_pci_detach, +}; + +static int dt3000_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &dt3000_driver, id->driver_data); +} + +static const struct pci_device_id dt3000_pci_table[] = { + { PCI_VDEVICE(DT, 0x0022), BOARD_DT3001 }, + { PCI_VDEVICE(DT, 0x0023), BOARD_DT3002 }, + { PCI_VDEVICE(DT, 0x0024), BOARD_DT3003 }, + { PCI_VDEVICE(DT, 0x0025), BOARD_DT3004 }, + { PCI_VDEVICE(DT, 0x0026), BOARD_DT3005 }, + { PCI_VDEVICE(DT, 0x0027), BOARD_DT3001_PGL }, + { PCI_VDEVICE(DT, 0x0028), BOARD_DT3003_PGL }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, dt3000_pci_table); + +static struct pci_driver dt3000_pci_driver = { + .name = "dt3000", + .id_table = dt3000_pci_table, + .probe = dt3000_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(dt3000_driver, dt3000_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Data Translation DT3000 series boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/dt9812.c b/drivers/comedi/drivers/dt9812.c new file mode 100644 index 000000000..b37b9d8ec --- /dev/null +++ b/drivers/comedi/drivers/dt9812.c @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* + * Driver: dt9812 + * Description: Data Translation DT9812 USB module + * Devices: [Data Translation] DT9812 (dt9812) + * Author: anders.blomdell@control.lth.se (Anders Blomdell) + * Status: in development + * 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/slab.h> +#include <linux/uaccess.h> +#include <linux/comedi/comedi_usb.h> + +#define DT9812_DIAGS_BOARD_INFO_ADDR 0xFBFF +#define DT9812_MAX_WRITE_CMD_PIPE_SIZE 32 +#define DT9812_MAX_READ_CMD_PIPE_SIZE 32 + +/* usb_bulk_msg() timeout 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 mutex mut; + struct { + __u8 addr; + size_t size; + } cmd_wr, cmd_rd; + u16 device; +}; + +static int dt9812_read_info(struct comedi_device *dev, + int offset, void *buf, size_t buf_size) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct dt9812_private *devpriv = dev->private; + struct dt9812_usb_cmd *cmd; + size_t tbuf_size; + int count, ret; + void *tbuf; + + tbuf_size = max(sizeof(*cmd), buf_size); + + tbuf = kzalloc(tbuf_size, GFP_KERNEL); + if (!tbuf) + return -ENOMEM; + + cmd = tbuf; + + 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, sizeof(*cmd), &count, DT9812_USB_TIMEOUT); + if (ret) + goto out; + + ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr), + tbuf, buf_size, &count, DT9812_USB_TIMEOUT); + if (!ret) { + if (count == buf_size) + memcpy(buf, tbuf, buf_size); + else + ret = -EREMOTEIO; + } +out: + kfree(tbuf); + + return ret; +} + +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; + size_t buf_size; + void *buf; + + buf_size = max_t(size_t, sizeof(*cmd), reg_count); + + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + cmd = buf; + + 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, sizeof(*cmd), &count, DT9812_USB_TIMEOUT); + if (ret) + goto out; + + ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr), + buf, reg_count, &count, DT9812_USB_TIMEOUT); + if (!ret) { + if (count == reg_count) + memcpy(value, buf, reg_count); + else + ret = -EREMOTEIO; + } +out: + kfree(buf); + + return ret; +} + +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; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + 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!! */ + ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr), + cmd, sizeof(*cmd), &count, DT9812_USB_TIMEOUT); + kfree(cmd); + + return ret; +} + +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; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + 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!! */ + ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr), + cmd, sizeof(*cmd), &count, DT9812_USB_TIMEOUT); + kfree(cmd); + + return ret; +} + +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; + + mutex_lock(&devpriv->mut); + 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); + } + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + ret = dt9812_write_multiple_registers(dev, 1, reg, value); + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + /* 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: + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + 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 lsb of DAC value first */ + rmw[1].address = F020_SFR_DAC0L; + rmw[1].and_mask = 0xff; + rmw[1].or_value = value & 0xff; + + /* 3. load msb 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 lsb of DAC value first */ + rmw[1].address = F020_SFR_DAC1L; + rmw[1].and_mask = 0xff; + rmw[1].or_value = value & 0xff; + + /* 3. load msb 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); + + mutex_unlock(&devpriv->mut); + + return ret; +} + +static int dt9812_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + u8 bits = 0; + int ret; + + ret = dt9812_digital_in(dev, &bits); + if (ret) + return ret; + + data[1] = bits; + + return insn->n; +} + +static int dt9812_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + dt9812_digital_out(dev, s->state); + + data[1] = s->state; + + return insn->n; +} + +static int dt9812_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + u16 val = 0; + int ret; + int i; + + for (i = 0; i < insn->n; i++) { + ret = dt9812_analog_in(dev, chan, &val, DT9812_GAIN_1); + if (ret) + return ret; + data[i] = val; + } + + return insn->n; +} + +static int dt9812_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dt9812_private *devpriv = dev->private; + int ret; + + mutex_lock(&devpriv->mut); + ret = comedi_readback_insn_read(dev, s, insn, data); + mutex_unlock(&devpriv->mut); + + return ret; +} + +static int dt9812_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + int ret; + + ret = dt9812_analog_out(dev, chan, val); + if (ret) + return ret; + + s->readback[chan] = val; + } + + return insn->n; +} + +static int dt9812_find_endpoints(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct usb_host_interface *host = intf->cur_altsetting; + struct dt9812_private *devpriv = dev->private; + struct usb_endpoint_descriptor *ep; + int i; + + if (host->desc.bNumEndpoints != 5) { + dev_err(dev->class_dev, "Wrong number of endpoints\n"); + return -ENODEV; + } + + for (i = 0; i < host->desc.bNumEndpoints; ++i) { + int dir = -1; + + ep = &host->endpoint[i].desc; + switch (i) { + case 0: + /* unused message pipe */ + dir = USB_DIR_IN; + break; + case 1: + dir = USB_DIR_OUT; + devpriv->cmd_wr.addr = ep->bEndpointAddress; + devpriv->cmd_wr.size = usb_endpoint_maxp(ep); + break; + case 2: + dir = USB_DIR_IN; + devpriv->cmd_rd.addr = ep->bEndpointAddress; + devpriv->cmd_rd.size = usb_endpoint_maxp(ep); + break; + case 3: + /* unused write stream */ + dir = USB_DIR_OUT; + break; + case 4: + /* unused read stream */ + dir = USB_DIR_IN; + break; + } + if ((ep->bEndpointAddress & USB_DIR_IN) != dir) { + dev_err(dev->class_dev, + "Endpoint has wrong direction\n"); + return -ENODEV; + } + } + return 0; +} + +static int dt9812_reset_device(struct comedi_device *dev) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct dt9812_private *devpriv = dev->private; + u32 serial; + u16 vendor; + u16 product; + u8 tmp8; + __le16 tmp16; + __le32 tmp32; + int ret; + int i; + + ret = dt9812_read_info(dev, 0, &tmp8, sizeof(tmp8)); + if (ret) { + /* + * Seems like a configuration reset is necessary if driver is + * reloaded while device is attached + */ + usb_reset_configuration(usb); + for (i = 0; i < 10; i++) { + ret = dt9812_read_info(dev, 1, &tmp8, sizeof(tmp8)); + if (ret == 0) + break; + } + if (ret) { + dev_err(dev->class_dev, + "unable to reset configuration\n"); + return ret; + } + } + + ret = dt9812_read_info(dev, 1, &tmp16, sizeof(tmp16)); + if (ret) { + dev_err(dev->class_dev, "failed to read vendor id\n"); + return ret; + } + vendor = le16_to_cpu(tmp16); + + ret = dt9812_read_info(dev, 3, &tmp16, sizeof(tmp16)); + if (ret) { + dev_err(dev->class_dev, "failed to read product id\n"); + return ret; + } + product = le16_to_cpu(tmp16); + + ret = dt9812_read_info(dev, 5, &tmp16, sizeof(tmp16)); + if (ret) { + dev_err(dev->class_dev, "failed to read device id\n"); + return ret; + } + devpriv->device = le16_to_cpu(tmp16); + + ret = dt9812_read_info(dev, 7, &tmp32, sizeof(tmp32)); + if (ret) { + dev_err(dev->class_dev, "failed to read serial number\n"); + return ret; + } + serial = le32_to_cpu(tmp32); + + /* let the user know what node this device is now attached to */ + dev_info(dev->class_dev, "USB DT9812 (%4.4x.%4.4x.%4.4x) #0x%8.8x\n", + vendor, product, devpriv->device, serial); + + if (devpriv->device != DT9812_DEVID_DT9812_10 && + devpriv->device != DT9812_DEVID_DT9812_2PT5) { + dev_err(dev->class_dev, "Unsupported device!\n"); + return -EINVAL; + } + + return 0; +} + +static int dt9812_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct dt9812_private *devpriv; + struct comedi_subdevice *s; + bool is_unipolar; + int ret; + int i; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + mutex_init(&devpriv->mut); + usb_set_intfdata(intf, devpriv); + + ret = dt9812_find_endpoints(dev); + if (ret) + return ret; + + ret = dt9812_reset_device(dev); + if (ret) + return ret; + + is_unipolar = (devpriv->device == DT9812_DEVID_DT9812_2PT5); + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt9812_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt9812_do_insn_bits; + + /* Analog Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 8; + s->maxdata = 0x0fff; + s->range_table = is_unipolar ? &range_unipolar2_5 : &range_bipolar10; + s->insn_read = dt9812_ai_insn_read; + + /* Analog Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = is_unipolar ? &range_unipolar2_5 : &range_bipolar10; + s->insn_write = dt9812_ao_insn_write; + s->insn_read = dt9812_ao_insn_read; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) + s->readback[i] = is_unipolar ? 0x0000 : 0x0800; + + return 0; +} + +static void dt9812_detach(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct dt9812_private *devpriv = dev->private; + + if (!devpriv) + return; + + mutex_destroy(&devpriv->mut); + usb_set_intfdata(intf, NULL); +} + +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/comedi/drivers/dyna_pci10xx.c b/drivers/comedi/drivers/dyna_pci10xx.c new file mode 100644 index 000000000..407a038fb --- /dev/null +++ b/drivers/comedi/drivers/dyna_pci10xx.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/dyna_pci10xx.c + * Copyright (C) 2011 Prashant Shah, pshah.mumbai@gmail.com + */ + +/* + * Driver: dyna_pci10xx + * Description: Dynalog India PCI DAQ Cards, http://www.dynalogindia.com/ + * Devices: [Dynalog] PCI-1050 (dyna_pci1050) + * Author: Prashant Shah <pshah.mumbai@gmail.com> + * Status: Stable + * + * Developed at Automation Labs, Chemical Dept., IIT Bombay, India. + * Prof. Kannan Moudgalya <kannan@iitb.ac.in> + * http://www.iitb.ac.in + * + * Notes : + * - Dynalog India Pvt. Ltd. does not have a registered PCI Vendor ID and + * they are using the PLX Technlogies Vendor ID since that is the PCI Chip + * used in the card. + * - Dynalog India Pvt. Ltd. has provided the internal register specification + * for their cards in their manuals. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/comedi/comedi_pci.h> + +#define READ_TIMEOUT 50 + +static const struct comedi_lrange range_pci1050_ai = { + 3, { + BIP_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(10) + } +}; + +static const char range_codes_pci1050_ai[] = { 0x00, 0x10, 0x30 }; + +struct dyna_pci10xx_private { + struct mutex mutex; + unsigned long BADR3; +}; + +static int dyna_pci10xx_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw_p(dev->iobase); + if (status & BIT(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); + usleep_range(10, 20); + + 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; + + mutex_lock(&devpriv->mutex); + for (n = 0; n < insn->n; n++) { + smp_mb(); + /* trigger conversion and write data */ + outw_p(data[n], dev->iobase); + usleep_range(10, 20); + } + 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); + usleep_range(10, 100); + + /* 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); + usleep_range(10, 100); + } + + 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->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 = 1; + s->maxdata = 0x0FFF; + s->range_table = &range_unipolar10; + s->insn_write = dyna_pci10xx_insn_write_ao; + + /* digital input */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dyna_pci10xx_di_insn_bits; + + /* digital output */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->state = 0; + s->insn_bits = dyna_pci10xx_do_insn_bits; + + return 0; +} + +static void dyna_pci10xx_detach(struct comedi_device *dev) +{ + struct dyna_pci10xx_private *devpriv = dev->private; + + comedi_pci_detach(dev); + if (devpriv) + mutex_destroy(&devpriv->mutex); +} + +static struct comedi_driver dyna_pci10xx_driver = { + .driver_name = "dyna_pci10xx", + .module = THIS_MODULE, + .auto_attach = dyna_pci10xx_auto_attach, + .detach = dyna_pci10xx_detach, +}; + +static int dyna_pci10xx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &dyna_pci10xx_driver, + id->driver_data); +} + +static const struct pci_device_id dyna_pci10xx_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_PLX, 0x1050) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, dyna_pci10xx_pci_table); + +static struct pci_driver dyna_pci10xx_pci_driver = { + .name = "dyna_pci10xx", + .id_table = dyna_pci10xx_pci_table, + .probe = dyna_pci10xx_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(dyna_pci10xx_driver, dyna_pci10xx_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Prashant Shah <pshah.mumbai@gmail.com>"); +MODULE_DESCRIPTION("Comedi based drivers for Dynalog PCI DAQ cards"); diff --git a/drivers/comedi/drivers/fl512.c b/drivers/comedi/drivers/fl512.c new file mode 100644 index 000000000..139e801fc --- /dev/null +++ b/drivers/comedi/drivers/fl512.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/delay.h> + +/* + * Register I/O map + */ +#define FL512_AI_LSB_REG 0x02 +#define FL512_AI_MSB_REG 0x03 +#define FL512_AI_MUX_REG 0x02 +#define FL512_AI_START_CONV_REG 0x03 +#define FL512_AO_DATA_REG(x) (0x04 + ((x) * 2)) +#define FL512_AO_TRIG_REG(x) (0x04 + ((x) * 2)) + +static const struct comedi_lrange range_fl512 = { + 4, { + BIP_RANGE(0.5), + BIP_RANGE(1), + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(5), + UNI_RANGE(10) + } +}; + +static int fl512_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + outb(chan, dev->iobase + FL512_AI_MUX_REG); + + for (i = 0; i < insn->n; i++) { + outb(0, dev->iobase + FL512_AI_START_CONV_REG); + + /* XXX should test "done" flag instead of delay */ + usleep_range(30, 100); + + val = inb(dev->iobase + FL512_AI_LSB_REG); + val |= (inb(dev->iobase + FL512_AI_MSB_REG) << 8); + val &= s->maxdata; + + data[i] = val; + } + + return insn->n; +} + +static int fl512_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + /* write LSB, MSB then trigger conversion */ + outb(val & 0x0ff, dev->iobase + FL512_AO_DATA_REG(chan)); + outb((val >> 8) & 0xf, dev->iobase + FL512_AO_DATA_REG(chan)); + inb(dev->iobase + FL512_AO_TRIG_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int fl512_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 16; + s->maxdata = 0x0fff; + s->range_table = &range_fl512; + s->insn_read = fl512_ai_insn_read; + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &range_fl512; + s->insn_write = fl512_ao_insn_write; + + return comedi_alloc_subdev_readback(s); +} + +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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/gsc_hpdi.c b/drivers/comedi/drivers/gsc_hpdi.c new file mode 100644 index 000000000..c09d135df --- /dev/null +++ b/drivers/comedi/drivers/gsc_hpdi.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: gsc_hpdi + * Description: General Standards Corporation High + * Speed Parallel Digital Interface rs485 boards + * Author: Frank Mori Hess <fmhess@users.sourceforge.net> + * Status: only receive mode works, transmit not supported + * Updated: Thu, 01 Nov 2012 16:17:38 +0000 + * Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi), + * PMC-HPDI32 + * + * Configuration options: + * None. + * + * Manual configuration of supported devices is not supported; they are + * configured automatically. + * + * There are some additional hpdi models available from GSC for which + * support could be added to this driver. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "plx9080.h" + +/* + * PCI BAR2 Register map (dev->mmio) + */ +#define FIRMWARE_REV_REG 0x00 +#define FEATURES_REG_PRESENT_BIT BIT(15) +#define BOARD_CONTROL_REG 0x04 +#define BOARD_RESET_BIT BIT(0) +#define TX_FIFO_RESET_BIT BIT(1) +#define RX_FIFO_RESET_BIT BIT(2) +#define TX_ENABLE_BIT BIT(4) +#define RX_ENABLE_BIT BIT(5) +#define DEMAND_DMA_DIRECTION_TX_BIT BIT(6) /* ch 0 only */ +#define LINE_VALID_ON_STATUS_VALID_BIT BIT(7) +#define START_TX_BIT BIT(8) +#define CABLE_THROTTLE_ENABLE_BIT BIT(9) +#define TEST_MODE_ENABLE_BIT BIT(31) +#define BOARD_STATUS_REG 0x08 +#define COMMAND_LINE_STATUS_MASK (0x7f << 0) +#define TX_IN_PROGRESS_BIT BIT(7) +#define TX_NOT_EMPTY_BIT BIT(8) +#define TX_NOT_ALMOST_EMPTY_BIT BIT(9) +#define TX_NOT_ALMOST_FULL_BIT BIT(10) +#define TX_NOT_FULL_BIT BIT(11) +#define RX_NOT_EMPTY_BIT BIT(12) +#define RX_NOT_ALMOST_EMPTY_BIT BIT(13) +#define RX_NOT_ALMOST_FULL_BIT BIT(14) +#define RX_NOT_FULL_BIT BIT(15) +#define BOARD_JUMPER0_INSTALLED_BIT BIT(16) +#define BOARD_JUMPER1_INSTALLED_BIT BIT(17) +#define TX_OVERRUN_BIT BIT(21) +#define RX_UNDERRUN_BIT BIT(22) +#define RX_OVERRUN_BIT BIT(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 BIT(0) +#define FIFO_WORDS_PRESENT_BIT BIT(1) +#define LEVEL_EDGE_INTERRUPTS_PRESENT_BIT BIT(2) +#define GPIO_SUPPORTED_BIT BIT(3) +#define PLX_DMA_CH1_SUPPORTED_BIT BIT(4) +#define OVERRUN_UNDERRUN_SUPPORTED_BIT BIT(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 BIT(0) +#define FRAME_VALID_END_INTR BIT(1) +#define TX_FIFO_EMPTY_INTR BIT(8) +#define TX_FIFO_ALMOST_EMPTY_INTR BIT(9) +#define TX_FIFO_ALMOST_FULL_INTR BIT(10) +#define TX_FIFO_FULL_INTR BIT(11) +#define RX_EMPTY_INTR BIT(12) +#define RX_ALMOST_EMPTY_INTR BIT(13) +#define RX_ALMOST_FULL_INTR BIT(14) +#define RX_FULL_INTR BIT(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_private { + void __iomem *plx9080_mmio; + u32 *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 */ + u32 *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; + + next = readl(devpriv->plx9080_mmio + PLX_REG_DMAPADR(channel)); + + 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(u32); + if (cmd->stop_src == TRIG_COUNT) { + if (size > devpriv->dio_count) + size = devpriv->dio_count; + devpriv->dio_count -= size; + } + comedi_buf_write_samples(s, devpriv->desc_dio_buffer[idx], + size); + idx++; + idx %= devpriv->num_dma_descriptors; + start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr); + + devpriv->dma_desc_index = idx; + } + /* XXX check for buffer overrun somehow */ +} + +static irqreturn_t gsc_hpdi_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct hpdi_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + u32 hpdi_intr_status, hpdi_board_status; + u32 plx_status; + u32 plx_bits; + u8 dma0_status, dma1_status; + unsigned long flags; + + if (!dev->attached) + return IRQ_NONE; + + plx_status = readl(devpriv->plx9080_mmio + PLX_REG_INTCSR); + if ((plx_status & + (PLX_INTCSR_DMA0IA | PLX_INTCSR_DMA1IA | PLX_INTCSR_PLIA)) == 0) + return IRQ_NONE; + + hpdi_intr_status = readl(dev->mmio + INTERRUPT_STATUS_REG); + hpdi_board_status = readl(dev->mmio + BOARD_STATUS_REG); + + if (hpdi_intr_status) + writel(hpdi_intr_status, dev->mmio + INTERRUPT_STATUS_REG); + + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma0_status = readb(devpriv->plx9080_mmio + PLX_REG_DMACSR0); + if (plx_status & PLX_INTCSR_DMA0IA) { + /* dma chan 0 interrupt */ + writeb((dma0_status & PLX_DMACSR_ENABLE) | PLX_DMACSR_CLEARINTR, + devpriv->plx9080_mmio + PLX_REG_DMACSR0); + + if (dma0_status & PLX_DMACSR_ENABLE) + 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_REG_DMACSR1); + if (plx_status & PLX_INTCSR_DMA1IA) { + /* XXX */ /* dma chan 1 interrupt */ + writeb((dma1_status & PLX_DMACSR_ENABLE) | PLX_DMACSR_CLEARINTR, + devpriv->plx9080_mmio + PLX_REG_DMACSR1); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* clear possible plx9080 interrupt sources */ + if (plx_status & PLX_INTCSR_LDBIA) { + /* clear local doorbell interrupt */ + plx_bits = readl(devpriv->plx9080_mmio + PLX_REG_L2PDBELL); + writel(plx_bits, devpriv->plx9080_mmio + PLX_REG_L2PDBELL); + } + + if (hpdi_board_status & RX_OVERRUN_BIT) { + dev_err(dev->class_dev, "rx fifo overrun\n"); + async->events |= COMEDI_CB_ERROR; + } + + if (hpdi_board_status & RX_UNDERRUN_BIT) { + dev_err(dev->class_dev, "rx fifo underrun\n"); + async->events |= COMEDI_CB_ERROR; + } + + if (devpriv->dio_count == 0) + async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static void gsc_hpdi_abort_dma(struct comedi_device *dev, unsigned int channel) +{ + struct hpdi_private *devpriv = dev->private; + unsigned long flags; + + /* spinlock for plx dma control/status reg */ + spin_lock_irqsave(&dev->spinlock, flags); + + plx9080_abort_dma(devpriv->plx9080_mmio, channel); + + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static int gsc_hpdi_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writel(0, dev->mmio + BOARD_CONTROL_REG); + writel(0, dev->mmio + INTERRUPT_CONTROL_REG); + + gsc_hpdi_abort_dma(dev, 0); + + return 0; +} + +static int gsc_hpdi_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct hpdi_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long flags; + u32 bits; + + if (s->io_bits) + return -EINVAL; + + writel(RX_FIFO_RESET_BIT, dev->mmio + BOARD_CONTROL_REG); + + gsc_hpdi_abort_dma(dev, 0); + + devpriv->dma_desc_index = 0; + + /* + * These register are supposedly unused during chained dma, + * but I have found that left over values from last operation + * occasionally cause problems with transfer of first dma + * block. Initializing them to zero seems to fix the problem. + */ + writel(0, devpriv->plx9080_mmio + PLX_REG_DMASIZ0); + writel(0, devpriv->plx9080_mmio + PLX_REG_DMAPADR0); + writel(0, devpriv->plx9080_mmio + PLX_REG_DMALADR0); + + /* give location of first dma descriptor */ + bits = devpriv->dma_desc_phys_addr | PLX_DMADPR_DESCPCI | + PLX_DMADPR_TCINTR | PLX_DMADPR_XFERL2P; + writel(bits, devpriv->plx9080_mmio + PLX_REG_DMADPR0); + + /* enable dma transfer */ + spin_lock_irqsave(&dev->spinlock, flags); + writeb(PLX_DMACSR_ENABLE | PLX_DMACSR_START | PLX_DMACSR_CLEARINTR, + devpriv->plx9080_mmio + PLX_REG_DMACSR0); + spin_unlock_irqrestore(&dev->spinlock, flags); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->dio_count = cmd->stop_arg; + else + devpriv->dio_count = 1; + + /* clear over/under run status flags */ + writel(RX_UNDERRUN_BIT | RX_OVERRUN_BIT, dev->mmio + BOARD_STATUS_REG); + + /* enable interrupts */ + writel(RX_FULL_INTR, dev->mmio + INTERRUPT_CONTROL_REG); + + writel(RX_ENABLE_BIT, dev->mmio + BOARD_CONTROL_REG); + + return 0; +} + +static int gsc_hpdi_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int i; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (chan != i) { + dev_dbg(dev->class_dev, + "chanlist must be ch 0 to 31 in order\n"); + return -EINVAL; + } + } + + return 0; +} + +static int gsc_hpdi_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + if (s->io_bits) + return -EINVAL; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (!cmd->chanlist_len || !cmd->chanlist) { + cmd->chanlist_len = 32; + err |= -EINVAL; + } + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= gsc_hpdi_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +/* setup dma descriptors so a link completes every 'len' bytes */ +static int gsc_hpdi_setup_dma_descriptors(struct comedi_device *dev, + unsigned int len) +{ + struct hpdi_private *devpriv = dev->private; + dma_addr_t phys_addr = devpriv->dma_desc_phys_addr; + u32 next_bits = PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR | + PLX_DMADPR_XFERL2P; + unsigned int offset = 0; + unsigned int idx = 0; + unsigned int i; + + if (len > DMA_BUFFER_SIZE) + len = DMA_BUFFER_SIZE; + len -= len % sizeof(u32); + 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(u32)); + + offset += len; + if (len + offset > DMA_BUFFER_SIZE) { + offset = 0; + idx++; + } + } + devpriv->num_dma_descriptors = i; + /* fix last descriptor to point back to first */ + devpriv->dma_desc[i - 1].next = cpu_to_le32(phys_addr | next_bits); + + devpriv->block_size = len; + + return len; +} + +static int gsc_hpdi_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + switch (data[0]) { + case INSN_CONFIG_BLOCK_SIZE: + ret = gsc_hpdi_setup_dma_descriptors(dev, data[1]); + if (ret) + return ret; + + data[1] = ret; + break; + default: + ret = comedi_dio_insn_config(dev, s, insn, data, 0xffffffff); + if (ret) + return ret; + break; + } + + return insn->n; +} + +static void gsc_hpdi_free_dma(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct hpdi_private *devpriv = dev->private; + int i; + + if (!devpriv) + return; + + /* free pci dma buffers */ + for (i = 0; i < NUM_DMA_BUFFERS; i++) { + if (devpriv->dio_buffer[i]) + dma_free_coherent(&pcidev->dev, + DMA_BUFFER_SIZE, + devpriv->dio_buffer[i], + devpriv->dio_buffer_phys_addr[i]); + } + /* free dma descriptors */ + if (devpriv->dma_desc) + dma_free_coherent(&pcidev->dev, + sizeof(struct plx_dma_desc) * + NUM_DMA_DESCRIPTORS, + devpriv->dma_desc, + devpriv->dma_desc_phys_addr); +} + +static int gsc_hpdi_init(struct comedi_device *dev) +{ + struct hpdi_private *devpriv = dev->private; + u32 plx_intcsr_bits; + + /* wait 10usec after reset before accessing fifos */ + writel(BOARD_RESET_BIT, dev->mmio + BOARD_CONTROL_REG); + usleep_range(10, 1000); + + writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32), + dev->mmio + RX_PROG_ALMOST_REG); + writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32), + dev->mmio + TX_PROG_ALMOST_REG); + + devpriv->tx_fifo_size = readl(dev->mmio + TX_FIFO_SIZE_REG) & + FIFO_SIZE_MASK; + devpriv->rx_fifo_size = readl(dev->mmio + RX_FIFO_SIZE_REG) & + FIFO_SIZE_MASK; + + writel(0, dev->mmio + INTERRUPT_CONTROL_REG); + + /* enable interrupts */ + plx_intcsr_bits = + PLX_INTCSR_LSEABORTEN | PLX_INTCSR_LSEPARITYEN | PLX_INTCSR_PIEN | + PLX_INTCSR_PLIEN | PLX_INTCSR_PABORTIEN | PLX_INTCSR_LIOEN | + PLX_INTCSR_DMA0IEN; + writel(plx_intcsr_bits, devpriv->plx9080_mmio + PLX_REG_INTCSR); + + return 0; +} + +static void gsc_hpdi_init_plx9080(struct comedi_device *dev) +{ + struct hpdi_private *devpriv = dev->private; + u32 bits; + void __iomem *plx_iobase = devpriv->plx9080_mmio; + +#ifdef __BIG_ENDIAN + bits = PLX_BIGEND_DMA0 | PLX_BIGEND_DMA1; +#else + bits = 0; +#endif + writel(bits, devpriv->plx9080_mmio + PLX_REG_BIGEND); + + writel(0, devpriv->plx9080_mmio + PLX_REG_INTCSR); + + gsc_hpdi_abort_dma(dev, 0); + gsc_hpdi_abort_dma(dev, 1); + + /* configure dma0 mode */ + bits = 0; + /* enable ready input */ + bits |= PLX_DMAMODE_READYIEN; + /* enable dma chaining */ + bits |= PLX_DMAMODE_CHAINEN; + /* + * enable interrupt on dma done + * (probably don't need this, since chain never finishes) + */ + bits |= PLX_DMAMODE_DONEIEN; + /* + * don't increment local address during transfers + * (we are transferring from a fixed fifo register) + */ + bits |= PLX_DMAMODE_LACONST; + /* route dma interrupt to pci bus */ + bits |= PLX_DMAMODE_INTRPCI; + /* enable demand mode */ + bits |= PLX_DMAMODE_DEMAND; + /* enable local burst mode */ + bits |= PLX_DMAMODE_BURSTEN; + bits |= PLX_DMAMODE_WIDTH_32; + writel(bits, plx_iobase + PLX_REG_DMAMODE0); +} + +static int gsc_hpdi_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct hpdi_private *devpriv; + struct comedi_subdevice *s; + int i; + int retval; + + dev->board_name = "pci-hpdi32"; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + retval = comedi_pci_enable(dev); + if (retval) + return retval; + pci_set_master(pcidev); + + devpriv->plx9080_mmio = pci_ioremap_bar(pcidev, 0); + dev->mmio = pci_ioremap_bar(pcidev, 2); + if (!devpriv->plx9080_mmio || !dev->mmio) { + dev_warn(dev->class_dev, "failed to remap io memory\n"); + return -ENOMEM; + } + + gsc_hpdi_init_plx9080(dev); + + /* get irq */ + if (request_irq(pcidev->irq, gsc_hpdi_interrupt, IRQF_SHARED, + dev->board_name, dev)) { + dev_warn(dev->class_dev, + "unable to allocate irq %u\n", pcidev->irq); + return -EINVAL; + } + dev->irq = pcidev->irq; + + dev_dbg(dev->class_dev, " irq %u\n", dev->irq); + + /* allocate pci dma buffers */ + for (i = 0; i < NUM_DMA_BUFFERS; i++) { + devpriv->dio_buffer[i] = + dma_alloc_coherent(&pcidev->dev, DMA_BUFFER_SIZE, + &devpriv->dio_buffer_phys_addr[i], + GFP_KERNEL); + if (!devpriv->dio_buffer[i]) { + dev_warn(dev->class_dev, + "failed to allocate DMA buffer\n"); + return -ENOMEM; + } + } + /* allocate dma descriptors */ + devpriv->dma_desc = dma_alloc_coherent(&pcidev->dev, + sizeof(struct plx_dma_desc) * + NUM_DMA_DESCRIPTORS, + &devpriv->dma_desc_phys_addr, + GFP_KERNEL); + if (!devpriv->dma_desc) { + dev_warn(dev->class_dev, + "failed to allocate DMA descriptors\n"); + return -ENOMEM; + } + if (devpriv->dma_desc_phys_addr & 0xf) { + dev_warn(dev->class_dev, + " dma descriptors not quad-word aligned (bug)\n"); + return -EIO; + } + + retval = gsc_hpdi_setup_dma_descriptors(dev, 0x1000); + if (retval < 0) + return retval; + + retval = comedi_alloc_subdevices(dev, 1); + if (retval) + return retval; + + /* Digital I/O subdevice */ + s = &dev->subdevices[0]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | + SDF_CMD_READ; + s->n_chan = 32; + s->len_chanlist = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = gsc_hpdi_dio_insn_config; + s->do_cmd = gsc_hpdi_cmd; + s->do_cmdtest = gsc_hpdi_cmd_test; + s->cancel = gsc_hpdi_cancel; + + return gsc_hpdi_init(dev); +} + +static void gsc_hpdi_detach(struct comedi_device *dev) +{ + struct hpdi_private *devpriv = dev->private; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->plx9080_mmio) { + writel(0, devpriv->plx9080_mmio + PLX_REG_INTCSR); + iounmap(devpriv->plx9080_mmio); + } + if (dev->mmio) + iounmap(dev->mmio); + } + comedi_pci_disable(dev); + gsc_hpdi_free_dma(dev); +} + +static struct comedi_driver gsc_hpdi_driver = { + .driver_name = "gsc_hpdi", + .module = THIS_MODULE, + .auto_attach = gsc_hpdi_auto_attach, + .detach = gsc_hpdi_detach, +}; + +static int gsc_hpdi_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &gsc_hpdi_driver, id->driver_data); +} + +static const struct pci_device_id gsc_hpdi_pci_table[] = { + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9080, + PCI_VENDOR_ID_PLX, 0x2400) }, + { 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for General Standards PCI-HPDI32/PMC-HPDI32"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/icp_multi.c b/drivers/comedi/drivers/icp_multi.c new file mode 100644 index 000000000..ac4b11dbd --- /dev/null +++ b/drivers/comedi/drivers/icp_multi.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * icp_multi.c + * Comedi driver for Inova ICP_MULTI board + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: icp_multi + * Description: Inova ICP_MULTI + * Devices: [Inova] ICP_MULTI (icp_multi) + * Author: Anne Smorthit <anne.smorthit@sfwte.ch> + * Status: works + * + * Configuration options: not applicable, uses PCI auto config + * + * 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 - not implemented + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedi_pci.h> + +#define ICP_MULTI_ADC_CSR 0x00 /* R/W: ADC command/status register */ +#define ICP_MULTI_ADC_CSR_ST BIT(0) /* Start ADC */ +#define ICP_MULTI_ADC_CSR_BSY BIT(0) /* ADC busy */ +#define ICP_MULTI_ADC_CSR_BI BIT(4) /* Bipolar input range */ +#define ICP_MULTI_ADC_CSR_RA BIT(5) /* Input range 0 = 5V, 1 = 10V */ +#define ICP_MULTI_ADC_CSR_DI BIT(6) /* Input mode 1 = differential */ +#define ICP_MULTI_ADC_CSR_DI_CHAN(x) (((x) & 0x7) << 9) +#define ICP_MULTI_ADC_CSR_SE_CHAN(x) (((x) & 0xf) << 8) +#define ICP_MULTI_AI 2 /* R: Analogue input data */ +#define ICP_MULTI_DAC_CSR 0x04 /* R/W: DAC command/status register */ +#define ICP_MULTI_DAC_CSR_ST BIT(0) /* Start DAC */ +#define ICP_MULTI_DAC_CSR_BSY BIT(0) /* DAC busy */ +#define ICP_MULTI_DAC_CSR_BI BIT(4) /* Bipolar output range */ +#define ICP_MULTI_DAC_CSR_RA BIT(5) /* Output range 0 = 5V, 1 = 10V */ +#define ICP_MULTI_DAC_CSR_CHAN(x) (((x) & 0x3) << 8) +#define ICP_MULTI_AO 6 /* R/W: Analogue output data */ +#define ICP_MULTI_DI 8 /* R/W: Digital inputs */ +#define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */ +#define ICP_MULTI_INT_EN 0x0c /* R/W: Interrupt enable register */ +#define ICP_MULTI_INT_STAT 0x0e /* R/W: Interrupt status register */ +#define ICP_MULTI_INT_ADC_RDY BIT(0) /* A/D conversion ready interrupt */ +#define ICP_MULTI_INT_DAC_RDY BIT(1) /* D/A conversion ready interrupt */ +#define ICP_MULTI_INT_DOUT_ERR BIT(2) /* Digital output error interrupt */ +#define ICP_MULTI_INT_DIN_STAT BIT(3) /* Digital input status change int. */ +#define ICP_MULTI_INT_CIE0 BIT(4) /* Counter 0 overrun interrupt */ +#define ICP_MULTI_INT_CIE1 BIT(5) /* Counter 1 overrun interrupt */ +#define ICP_MULTI_INT_CIE2 BIT(6) /* Counter 2 overrun interrupt */ +#define ICP_MULTI_INT_CIE3 BIT(7) /* Counter 3 overrun interrupt */ +#define ICP_MULTI_INT_MASK 0xff /* All interrupts */ +#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 */ + +/* analog input and output have the same range options */ +static const struct comedi_lrange icp_multi_ranges = { + 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; + +static int icp_multi_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readw(dev->mmio + ICP_MULTI_ADC_CSR); + if ((status & ICP_MULTI_ADC_CSR_BSY) == 0) + return 0; + return -EBUSY; +} + +static int icp_multi_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 adc_csr; + int ret = 0; + int n; + + /* Set mode and range data for specified channel */ + if (aref == AREF_DIFF) { + adc_csr = ICP_MULTI_ADC_CSR_DI_CHAN(chan) | + ICP_MULTI_ADC_CSR_DI; + } else { + adc_csr = ICP_MULTI_ADC_CSR_SE_CHAN(chan); + } + adc_csr |= range_codes_analog[range]; + writew(adc_csr, dev->mmio + ICP_MULTI_ADC_CSR); + + for (n = 0; n < insn->n; n++) { + /* Set start ADC bit */ + writew(adc_csr | ICP_MULTI_ADC_CSR_ST, + dev->mmio + ICP_MULTI_ADC_CSR); + + udelay(1); + + /* Wait for conversion to complete, or get fed up waiting */ + ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0); + if (ret) + break; + + data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff; + } + + return ret ? ret : n; +} + +static int icp_multi_ao_ready(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readw(dev->mmio + ICP_MULTI_DAC_CSR); + if ((status & ICP_MULTI_DAC_CSR_BSY) == 0) + return 0; + return -EBUSY; +} + +static int icp_multi_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int dac_csr; + int i; + + /* Select channel and range */ + dac_csr = ICP_MULTI_DAC_CSR_CHAN(chan); + dac_csr |= range_codes_analog[range]; + writew(dac_csr, dev->mmio + ICP_MULTI_DAC_CSR); + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + int ret; + + /* Wait for analog output to be ready for new data */ + ret = comedi_timeout(dev, s, insn, icp_multi_ao_ready, 0); + if (ret) + return ret; + + writew(val, dev->mmio + ICP_MULTI_AO); + + /* Set start conversion bit to write data to channel */ + writew(dac_csr | ICP_MULTI_DAC_CSR_ST, + dev->mmio + ICP_MULTI_DAC_CSR); + + s->readback[chan] = val; + } + + return insn->n; +} + +static int icp_multi_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = readw(dev->mmio + ICP_MULTI_DI); + + return insn->n; +} + +static int icp_multi_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)) + writew(s->state, dev->mmio + ICP_MULTI_DO); + + data[1] = s->state; + + return insn->n; +} + +static int icp_multi_reset(struct comedi_device *dev) +{ + int i; + + /* Disable all interrupts and clear any requests */ + writew(0, dev->mmio + ICP_MULTI_INT_EN); + writew(ICP_MULTI_INT_MASK, dev->mmio + ICP_MULTI_INT_STAT); + + /* Reset the analog output channels to 0V */ + for (i = 0; i < 4; i++) { + unsigned int dac_csr = ICP_MULTI_DAC_CSR_CHAN(i); + + /* Select channel and 0..5V range */ + writew(dac_csr, dev->mmio + ICP_MULTI_DAC_CSR); + + /* Output 0V */ + writew(0, dev->mmio + ICP_MULTI_AO); + + /* Set start conversion bit to write data to channel */ + writew(dac_csr | ICP_MULTI_DAC_CSR_ST, + dev->mmio + ICP_MULTI_DAC_CSR); + udelay(1); + } + + /* Digital outputs to 0 */ + writew(0, dev->mmio + ICP_MULTI_DO); + + return 0; +} + +static int icp_multi_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 2); + if (!dev->mmio) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + icp_multi_reset(dev); + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + s->n_chan = 16; + s->maxdata = 0x0fff; + s->range_table = &icp_multi_ranges; + s->insn_read = icp_multi_ai_insn_read; + + /* Analog Output subdevice */ + 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->range_table = &icp_multi_ranges; + s->insn_write = icp_multi_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = icp_multi_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = icp_multi_do_insn_bits; + + return 0; +} + +static struct comedi_driver icp_multi_driver = { + .driver_name = "icp_multi", + .module = THIS_MODULE, + .auto_attach = icp_multi_auto_attach, + .detach = comedi_pci_detach, +}; + +static int icp_multi_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data); +} + +static const struct pci_device_id icp_multi_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, icp_multi_pci_table); + +static struct pci_driver icp_multi_pci_driver = { + .name = "icp_multi", + .id_table = icp_multi_pci_table, + .probe = icp_multi_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Inova ICP_MULTI board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ii_pci20kc.c b/drivers/comedi/drivers/ii_pci20kc.c new file mode 100644 index 000000000..4a19bf846 --- /dev/null +++ b/drivers/comedi/drivers/ii_pci20kc.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 <linux/io.h> +#include <linux/comedi/comedidev.h> + +/* + * Register I/O map + */ +#define II20K_SIZE 0x400 +#define II20K_MOD_OFFSET 0x100 +#define II20K_ID_REG 0x00 +#define II20K_ID_MOD1_EMPTY BIT(7) +#define II20K_ID_MOD2_EMPTY BIT(6) +#define II20K_ID_MOD3_EMPTY BIT(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 BIT(7) +#define II20K_MOD_STATUS_IRQ_MOD2 BIT(6) +#define II20K_MOD_STATUS_IRQ_MOD3 BIT(5) +#define II20K_DIO0_REG 0x80 +#define II20K_DIO1_REG 0x81 +#define II20K_DIR_ENA_REG 0x82 +#define II20K_DIR_DIO3_OUT BIT(7) +#define II20K_DIR_DIO2_OUT BIT(6) +#define II20K_BUF_DISAB_DIO3 BIT(5) +#define II20K_BUF_DISAB_DIO2 BIT(4) +#define II20K_DIR_DIO1_OUT BIT(3) +#define II20K_DIR_DIO0_OUT BIT(2) +#define II20K_BUF_DISAB_DIO1 BIT(1) +#define II20K_BUF_DISAB_DIO0 BIT(0) +#define II20K_CTRL01_REG 0x83 +#define II20K_CTRL01_SET BIT(7) +#define II20K_CTRL01_DIO0_IN BIT(4) +#define II20K_CTRL01_DIO1_IN BIT(1) +#define II20K_DIO2_REG 0xc0 +#define II20K_DIO3_REG 0xc1 +#define II20K_CTRL23_REG 0xc3 +#define II20K_CTRL23_SET BIT(7) +#define II20K_CTRL23_DIO2_IN BIT(4) +#define II20K_CTRL23_DIO3_IN BIT(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 BIT(7) +#define II20K_AI_STATUS_CMD_HW_ENA BIT(1) +#define II20K_AI_STATUS_CMD_EXT_START BIT(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 BIT(2) +#define II20K_AI_OPT_REG 0x11 +#define II20K_AI_OPT_TRIG_ENA BIT(5) +#define II20K_AI_OPT_TRIG_INV BIT(4) +#define II20K_AI_OPT_TIMEBASE(x) (((x) & 0x3) << 1) +#define II20K_AI_OPT_BURST_MODE BIT(0) +#define II20K_AI_STATUS_REG 0x12 +#define II20K_AI_STATUS_INT BIT(7) +#define II20K_AI_STATUS_TRIG BIT(6) +#define II20K_AI_STATUS_TRIG_ENA BIT(5) +#define II20K_AI_STATUS_PACER_ERR BIT(2) +#define II20K_AI_STATUS_DATA_ERR BIT(1) +#define II20K_AI_STATUS_SET_TIME_ERR BIT(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 BIT(5) +#define II20K_AI_CHANLIST_GAIN(x) (((x) & 0x3) << 3) +#define II20K_AI_CHANLIST_MUX_ENA BIT(2) +#define II20K_AI_CHANLIST_CHAN(x) (((x) & 0x3) << 0) +#define II20K_AI_CHANLIST_LEN 0x80 + +/* the AO range is set by jumpers on the 20006M module */ +static const struct comedi_lrange ii20k_ao_ranges = { + 3, { + BIP_RANGE(5), /* Chan 0 - W1/W3 in Chan 1 - W2/W4 in */ + UNI_RANGE(10), /* Chan 0 - W1/W3 out Chan 1 - W2/W4 in */ + BIP_RANGE(10) /* Chan 0 - W1/W3 in Chan 1 - W2/W4 out */ + } +}; + +static const struct comedi_lrange ii20k_ai_ranges = { + 4, { + BIP_RANGE(5), /* gain 1 */ + BIP_RANGE(0.5), /* gain 10 */ + BIP_RANGE(0.05), /* gain 100 */ + BIP_RANGE(0.025) /* gain 200 */ + }, +}; + +static void __iomem *ii20k_module_iobase(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET; +} + +static int ii20k_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + void __iomem *iobase = ii20k_module_iobase(dev, s); + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + /* munge the offset binary data to 2's complement */ + val = comedi_offset_munge(s, val); + + writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan)); + writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan)); + writeb(0x00, iobase + II20K_AO_STRB_REG(chan)); + } + + return insn->n; +} + +static int ii20k_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + void __iomem *iobase = ii20k_module_iobase(dev, s); + unsigned char status; + + status = readb(iobase + II20K_AI_STATUS_REG); + if ((status & II20K_AI_STATUS_INT) == 0) + return 0; + return -EBUSY; +} + +static void ii20k_ai_setup(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec) +{ + void __iomem *iobase = ii20k_module_iobase(dev, s); + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned char val; + + /* initialize module */ + writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG); + + /* software conversion */ + writeb(0, iobase + II20K_AI_STATUS_CMD_REG); + + /* set the time base for the settling time counter based on the gain */ + val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2); + writeb(val, iobase + II20K_AI_OPT_REG); + + /* set the settling time counter based on the gain */ + val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99; + writeb(val, iobase + II20K_AI_SET_TIME_REG); + + /* set number of input channels */ + writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG); + + /* set the channel list byte */ + val = II20K_AI_CHANLIST_ONBOARD_ONLY | + II20K_AI_CHANLIST_MUX_ENA | + II20K_AI_CHANLIST_GAIN(range) | + II20K_AI_CHANLIST_CHAN(chan); + writeb(val, iobase + II20K_AI_CHANLIST_REG); + + /* reset settling time counter and trigger delay counter */ + writeb(0, iobase + II20K_AI_COUNT_RESET_REG); + + /* reset channel scanner */ + writeb(0, iobase + II20K_AI_CHAN_RESET_REG); +} + +static int ii20k_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + void __iomem *iobase = ii20k_module_iobase(dev, s); + int ret; + int i; + + ii20k_ai_setup(dev, s, insn->chanspec); + + for (i = 0; i < insn->n; i++) { + unsigned int val; + + /* generate a software start convert signal */ + readb(iobase + II20K_AI_PACER_RESET_REG); + + ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0); + if (ret) + return ret; + + val = readb(iobase + II20K_AI_LSB_REG); + val |= (readb(iobase + II20K_AI_MSB_REG) << 8); + + /* munge the 2's complement data to offset binary */ + data[i] = comedi_offset_munge(s, val); + } + + return insn->n; +} + +static void ii20k_dio_config(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned char ctrl01 = 0; + unsigned char ctrl23 = 0; + unsigned char dir_ena = 0; + + /* port 0 - channels 0-7 */ + if (s->io_bits & 0x000000ff) { + /* output port */ + ctrl01 &= ~II20K_CTRL01_DIO0_IN; + dir_ena &= ~II20K_BUF_DISAB_DIO0; + dir_ena |= II20K_DIR_DIO0_OUT; + } else { + /* input port */ + ctrl01 |= II20K_CTRL01_DIO0_IN; + dir_ena &= ~II20K_DIR_DIO0_OUT; + } + + /* port 1 - channels 8-15 */ + if (s->io_bits & 0x0000ff00) { + /* output port */ + ctrl01 &= ~II20K_CTRL01_DIO1_IN; + dir_ena &= ~II20K_BUF_DISAB_DIO1; + dir_ena |= II20K_DIR_DIO1_OUT; + } else { + /* input port */ + ctrl01 |= II20K_CTRL01_DIO1_IN; + dir_ena &= ~II20K_DIR_DIO1_OUT; + } + + /* port 2 - channels 16-23 */ + if (s->io_bits & 0x00ff0000) { + /* output port */ + ctrl23 &= ~II20K_CTRL23_DIO2_IN; + dir_ena &= ~II20K_BUF_DISAB_DIO2; + dir_ena |= II20K_DIR_DIO2_OUT; + } else { + /* input port */ + ctrl23 |= II20K_CTRL23_DIO2_IN; + dir_ena &= ~II20K_DIR_DIO2_OUT; + } + + /* port 3 - channels 24-31 */ + if (s->io_bits & 0xff000000) { + /* output port */ + ctrl23 &= ~II20K_CTRL23_DIO3_IN; + dir_ena &= ~II20K_BUF_DISAB_DIO3; + dir_ena |= II20K_DIR_DIO3_OUT; + } else { + /* input port */ + ctrl23 |= II20K_CTRL23_DIO3_IN; + dir_ena &= ~II20K_DIR_DIO3_OUT; + } + + ctrl23 |= II20K_CTRL01_SET; + ctrl23 |= II20K_CTRL23_SET; + + /* order is important */ + writeb(ctrl01, dev->mmio + II20K_CTRL01_REG); + writeb(ctrl23, dev->mmio + II20K_CTRL23_REG); + writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG); +} + +static int ii20k_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x000000ff; + else if (chan < 16) + mask = 0x0000ff00; + else if (chan < 24) + mask = 0x00ff0000; + else + mask = 0xff000000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + ii20k_dio_config(dev, s); + + return insn->n; +} + +static int ii20k_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0x000000ff) + writeb((s->state >> 0) & 0xff, + dev->mmio + II20K_DIO0_REG); + if (mask & 0x0000ff00) + writeb((s->state >> 8) & 0xff, + dev->mmio + II20K_DIO1_REG); + if (mask & 0x00ff0000) + writeb((s->state >> 16) & 0xff, + dev->mmio + II20K_DIO2_REG); + if (mask & 0xff000000) + writeb((s->state >> 24) & 0xff, + dev->mmio + II20K_DIO3_REG); + } + + data[1] = readb(dev->mmio + II20K_DIO0_REG); + data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8; + data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16; + data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24; + + return insn->n; +} + +static int ii20k_init_module(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + void __iomem *iobase = ii20k_module_iobase(dev, s); + unsigned char id; + int ret; + + id = readb(iobase + II20K_ID_REG); + switch (id) { + case II20K_ID_PCI20006M_1: + case II20K_ID_PCI20006M_2: + /* Analog Output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = (id == II20K_ID_PCI20006M_2) ? 2 : 1; + s->maxdata = 0xffff; + s->range_table = &ii20k_ao_ranges; + s->insn_write = ii20k_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + break; + case II20K_ID_PCI20341M_1: + /* Analog Input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF; + s->n_chan = 4; + s->maxdata = 0xffff; + s->range_table = &ii20k_ai_ranges; + s->insn_read = ii20k_ai_insn_read; + break; + default: + s->type = COMEDI_SUBD_UNUSED; + break; + } + + return 0; +} + +static int ii20k_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned int membase; + unsigned char id; + bool has_dio; + int ret; + + membase = it->options[0]; + if (!membase || (membase & ~(0x100000 - II20K_SIZE))) { + dev_warn(dev->class_dev, + "%s: invalid memory address specified\n", + dev->board_name); + return -EINVAL; + } + + if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) { + dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n", + dev->board_name, membase, II20K_SIZE); + return -EIO; + } + dev->iobase = membase; /* actually, a memory address */ + + dev->mmio = ioremap(membase, II20K_SIZE); + if (!dev->mmio) + return -ENOMEM; + + id = readb(dev->mmio + II20K_ID_REG); + switch (id & II20K_ID_MASK) { + case II20K_ID_PCI20001C_1A: + has_dio = false; + break; + case II20K_ID_PCI20001C_2A: + has_dio = true; + break; + default: + return -ENODEV; + } + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + if (id & II20K_ID_MOD1_EMPTY) { + s->type = COMEDI_SUBD_UNUSED; + } else { + ret = ii20k_init_module(dev, s); + if (ret) + return ret; + } + + s = &dev->subdevices[1]; + if (id & II20K_ID_MOD2_EMPTY) { + s->type = COMEDI_SUBD_UNUSED; + } else { + ret = ii20k_init_module(dev, s); + if (ret) + return ret; + } + + s = &dev->subdevices[2]; + if (id & II20K_ID_MOD3_EMPTY) { + s->type = COMEDI_SUBD_UNUSED; + } else { + ret = ii20k_init_module(dev, s); + if (ret) + return ret; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[3]; + if (has_dio) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ii20k_dio_insn_bits; + s->insn_config = ii20k_dio_insn_config; + + /* default all channels to input */ + ii20k_dio_config(dev, s); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static void ii20k_detach(struct comedi_device *dev) +{ + if (dev->mmio) + iounmap(dev->mmio); + if (dev->iobase) /* actually, a memory address */ + release_mem_region(dev->iobase, II20K_SIZE); +} + +static struct comedi_driver ii20k_driver = { + .driver_name = "ii_pci20kc", + .module = THIS_MODULE, + .attach = ii20k_attach, + .detach = ii20k_detach, +}; +module_comedi_driver(ii20k_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/jr3_pci.c b/drivers/comedi/drivers/jr3_pci.c new file mode 100644 index 000000000..951c23fa0 --- /dev/null +++ b/drivers/comedi/drivers/jr3_pci.c @@ -0,0 +1,800 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * 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 https://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/delay.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/comedi/comedi_pci.h> + +#include "jr3_pci.h" + +#define PCI_VENDOR_ID_JR3 0x1762 + +enum jr3_pci_boardid { + BOARD_JR3_1, + BOARD_JR3_2, + BOARD_JR3_3, + BOARD_JR3_4, +}; + +struct jr3_pci_board { + const char *name; + int n_subdevs; +}; + +static const struct jr3_pci_board jr3_pci_boards[] = { + [BOARD_JR3_1] = { + .name = "jr3_pci_1", + .n_subdevs = 1, + }, + [BOARD_JR3_2] = { + .name = "jr3_pci_2", + .n_subdevs = 2, + }, + [BOARD_JR3_3] = { + .name = "jr3_pci_3", + .n_subdevs = 3, + }, + [BOARD_JR3_4] = { + .name = "jr3_pci_4", + .n_subdevs = 4, + }, +}; + +struct jr3_pci_transform { + struct { + u16 link_type; + s16 link_amount; + } link[8]; +}; + +struct jr3_pci_poll_delay { + int min; + int max; +}; + +struct jr3_pci_dev_private { + struct timer_list timer; + struct comedi_device *dev; +}; + +union jr3_pci_single_range { + struct comedi_lrange l; + char _reserved[offsetof(struct comedi_lrange, range[1])]; +}; + +enum jr3_pci_poll_state { + 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 +}; + +struct jr3_pci_subdev_private { + struct jr3_sensor __iomem *sensor; + unsigned long next_time_min; + enum jr3_pci_poll_state state; + int serial_no; + int model_no; + union jr3_pci_single_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_sensor __iomem *sensor) +{ + return get_s16(&sensor->command_word0) == 0; +} + +static void set_transforms(struct jr3_sensor __iomem *sensor, + const 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(&sensor->transforms[num].link[i].link_type, + transf->link[i].link_type); + udelay(1); + set_s16(&sensor->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_sensor __iomem *sensor, + short transf_num) +{ + set_s16(&sensor->command_word0, 0x0500 + (transf_num & 0x000f)); +} + +static void use_offset(struct jr3_sensor __iomem *sensor, short offset_num) +{ + set_s16(&sensor->command_word0, 0x0600 + (offset_num & 0x000f)); +} + +static void set_offset(struct jr3_sensor __iomem *sensor) +{ + set_s16(&sensor->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_sensor __iomem *sensor, + struct six_axis_t full_scale) +{ + set_s16(&sensor->full_scale.fx, full_scale.fx); + set_s16(&sensor->full_scale.fy, full_scale.fy); + set_s16(&sensor->full_scale.fz, full_scale.fz); + set_s16(&sensor->full_scale.mx, full_scale.mx); + set_s16(&sensor->full_scale.my, full_scale.my); + set_s16(&sensor->full_scale.mz, full_scale.mz); + set_s16(&sensor->command_word0, 0x0a00); +} + +static struct six_axis_t get_max_full_scales(struct jr3_sensor __iomem *sensor) +{ + struct six_axis_t result; + + result.fx = get_s16(&sensor->max_full_scale.fx); + result.fy = get_s16(&sensor->max_full_scale.fy); + result.fz = get_s16(&sensor->max_full_scale.fz); + result.mx = get_s16(&sensor->max_full_scale.mx); + result.my = get_s16(&sensor->max_full_scale.my); + result.mz = get_s16(&sensor->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 int filter = chan / 8; + + switch (axis) { + case 0: + val = get_s16(&spriv->sensor->filter[filter].fx); + break; + case 1: + val = get_s16(&spriv->sensor->filter[filter].fy); + break; + case 2: + val = get_s16(&spriv->sensor->filter[filter].fz); + break; + case 3: + val = get_s16(&spriv->sensor->filter[filter].mx); + break; + case 4: + val = get_s16(&spriv->sensor->filter[filter].my); + break; + case 5: + val = get_s16(&spriv->sensor->filter[filter].mz); + break; + case 6: + val = get_s16(&spriv->sensor->filter[filter].v1); + break; + case 7: + val = get_s16(&spriv->sensor->filter[filter].v2); + break; + } + val += 0x4000; + } else if (chan == 56) { + val = get_u16(&spriv->sensor->model_no); + } else if (chan == 57) { + val = get_u16(&spriv->sensor->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; + + errors = get_u16(&spriv->sensor->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; + + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + spriv = s->private; + dev_dbg(dev->class_dev, "serial[%d]: %d\n", s->index, + spriv->serial_no); + } + return 0; +} + +static int read_idm_word(const u8 *data, size_t size, int *pos, + unsigned int *val) +{ + int result = 0; + int value; + + if (pos && val) { + /* Skip over non hex */ + for (; *pos < size && !isxdigit(data[*pos]); (*pos)++) + ; + /* Collect value */ + *val = 0; + for (; *pos < size; (*pos)++) { + value = hex_to_bin(data[*pos]); + if (value >= 0) { + result = 1; + *val = (*val << 4) + value; + } else { + break; + } + } + } + return result; +} + +static int jr3_check_firmware(struct comedi_device *dev, + const u8 *data, size_t size) +{ + int more = 1; + int pos = 0; + + /* + * IDM file format is: + * { count, address, data <count> } * + * ffff + */ + while (more) { + unsigned int count = 0; + unsigned int addr = 0; + + more = more && read_idm_word(data, size, &pos, &count); + if (more && count == 0xffff) + return 0; + + more = more && read_idm_word(data, size, &pos, &addr); + while (more && count > 0) { + unsigned int dummy = 0; + + more = more && read_idm_word(data, size, &pos, &dummy); + count--; + } + } + + return -ENODATA; +} + +static void jr3_write_firmware(struct comedi_device *dev, + int subdev, const u8 *data, size_t size) +{ + struct jr3_block __iomem *block = dev->mmio; + 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 = &block[subdev].program_lo[addr]; + hi = &block[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_sensor __iomem *sensor; + u16 model_no; + u16 serial_no; + int errors; + int i; + + sensor = spriv->sensor; + errors = get_u16(&sensor->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(&sensor->model_no); + serial_no = get_u16(&sensor->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(&sensor->model_no); + spriv->serial_no = get_u16(&sensor->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(sensor, &transf, 0); + use_transform(sensor, 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(sensor)) { + result = poll_delay_min_max(20, 100); + } else { + /* Set full scale */ + struct six_axis_t max_full_scale; + + max_full_scale = get_max_full_scales(sensor); + set_full_scales(sensor, 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(sensor)) { + result = poll_delay_min_max(20, 100); + } else { + struct force_array __iomem *fs = &sensor->full_scale; + union jr3_pci_single_range *r = spriv->range; + + /* Use ranges in kN or we will overflow around 2000N! */ + r[0].l.range[0].min = -get_s16(&fs->fx) * 1000; + r[0].l.range[0].max = get_s16(&fs->fx) * 1000; + r[1].l.range[0].min = -get_s16(&fs->fy) * 1000; + r[1].l.range[0].max = get_s16(&fs->fy) * 1000; + r[2].l.range[0].min = -get_s16(&fs->fz) * 1000; + r[2].l.range[0].max = get_s16(&fs->fz) * 1000; + r[3].l.range[0].min = -get_s16(&fs->mx) * 100; + r[3].l.range[0].max = get_s16(&fs->mx) * 100; + r[4].l.range[0].min = -get_s16(&fs->my) * 100; + r[4].l.range[0].max = get_s16(&fs->my) * 100; + r[5].l.range[0].min = -get_s16(&fs->mz) * 100; + /* the next five are questionable */ + r[5].l.range[0].max = get_s16(&fs->mz) * 100; + r[6].l.range[0].min = -get_s16(&fs->v1) * 100; + r[6].l.range[0].max = get_s16(&fs->v1) * 100; + r[7].l.range[0].min = -get_s16(&fs->v2) * 100; + r[7].l.range[0].max = get_s16(&fs->v2) * 100; + r[8].l.range[0].min = 0; + r[8].l.range[0].max = 65535; + + use_offset(sensor, 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(sensor)) { + result = poll_delay_min_max(20, 100); + } else { + set_s16(&sensor->offsets.fx, 0); + set_s16(&sensor->offsets.fy, 0); + set_s16(&sensor->offsets.fz, 0); + set_s16(&sensor->offsets.mx, 0); + set_s16(&sensor->offsets.my, 0); + set_s16(&sensor->offsets.mz, 0); + + set_offset(sensor); + + 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(struct timer_list *t) +{ + struct jr3_pci_dev_private *devpriv = from_timer(devpriv, t, timer); + struct comedi_device *dev = devpriv->dev; + 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 sensors that are ready to be polled */ + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + spriv = s->private; + + if (time_after_eq(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); + + if (sub_delay.max && sub_delay.max < delay) + /* + * Wake up as late as possible -> + * poll as many sensors 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_block __iomem *block = dev->mmio; + struct jr3_pci_subdev_private *spriv; + int j; + int k; + + spriv = comedi_alloc_spriv(s, sizeof(*spriv)); + if (!spriv) + return NULL; + + spriv->sensor = &block[s->index].sensor; + + for (j = 0; j < 8; j++) { + spriv->range[j].l.length = 1; + spriv->range[j].l.range[0].min = -1000000; + spriv->range[j].l.range[0].max = 1000000; + + for (k = 0; k < 7; k++) { + spriv->range_table_list[j + k * 8] = &spriv->range[j].l; + spriv->maxdata_list[j + k * 8] = 0x7fff; + } + } + spriv->range[8].l.length = 1; + spriv->range[8].l.range[0].min = 0; + spriv->range[8].l.range[0].max = 65535; + + spriv->range_table_list[56] = &spriv->range[8].l; + spriv->range_table_list[57] = &spriv->range[8].l; + spriv->maxdata_list[56] = 0xffff; + spriv->maxdata_list[57] = 0xffff; + + return spriv; +} + +static void jr3_pci_show_copyright(struct comedi_device *dev) +{ + struct jr3_block __iomem *block = dev->mmio; + struct jr3_sensor __iomem *sensor0 = &block[0].sensor; + char copy[ARRAY_SIZE(sensor0->copyright) + 1]; + int i; + + for (i = 0; i < ARRAY_SIZE(sensor0->copyright); i++) + copy[i] = (char)(get_u16(&sensor0->copyright[i]) >> 8); + copy[i] = '\0'; + dev_dbg(dev->class_dev, "Firmware copyright: %s\n", copy); +} + +static int jr3_pci_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + static const struct jr3_pci_board *board; + struct jr3_pci_dev_private *devpriv; + struct jr3_pci_subdev_private *spriv; + struct jr3_block __iomem *block; + struct comedi_subdevice *s; + int ret; + int i; + + BUILD_BUG_ON(sizeof(struct jr3_block) != 0x80000); + + if (context < ARRAY_SIZE(jr3_pci_boards)) + board = &jr3_pci_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + if (pci_resource_len(pcidev, 0) < board->n_subdevs * sizeof(*block)) + return -ENXIO; + + dev->mmio = pci_ioremap_bar(pcidev, 0); + if (!dev->mmio) + return -ENOMEM; + + block = dev->mmio; + + 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) + return -ENOMEM; + + /* Channel specific range and maxdata */ + s->range_table_list = spriv->range_table_list; + s->maxdata_list = spriv->maxdata_list; + } + + /* Reset DSP card */ + for (i = 0; i < dev->n_subdevices; i++) + writel(0, &block[i].reset); + + ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev, + "comedi/jr3pci.idm", + jr3_download_firmware, 0); + dev_dbg(dev->class_dev, "Firmware 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); + jr3_pci_show_copyright(dev); + + /* 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); + } + + devpriv->dev = dev; + timer_setup(&devpriv->timer, jr3_pci_poll_dev, 0); + 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); + + comedi_pci_detach(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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for JR3/PCI force sensor board"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("comedi/jr3pci.idm"); diff --git a/drivers/comedi/drivers/jr3_pci.h b/drivers/comedi/drivers/jr3_pci.h new file mode 100644 index 000000000..acd4e5456 --- /dev/null +++ b/drivers/comedi/drivers/jr3_pci.h @@ -0,0 +1,735 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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_sensor { + /* + * 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_block { + u32 program_lo[0x4000]; /* 0x00000 - 0x10000 */ + struct jr3_sensor sensor; /* 0x10000 - 0x10c00 */ + char pad2[0x30000 - 0x00c00]; /* 0x10c00 - 0x40000 */ + u32 program_hi[0x8000]; /* 0x40000 - 0x60000 */ + u32 reset; /* 0x60000 - 0x60004 */ + char pad3[0x20000 - 0x00004]; /* 0x60004 - 0x80000 */ +}; diff --git a/drivers/comedi/drivers/ke_counter.c b/drivers/comedi/drivers/ke_counter.c new file mode 100644 index 000000000..b825cf60e --- /dev/null +++ b/drivers/comedi/drivers/ke_counter.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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/comedi/comedi_pci.h> + +/* + * PCI BAR 0 Register I/O map + */ +#define KE_RESET_REG(x) (0x00 + ((x) * 0x20)) +#define KE_LATCH_REG(x) (0x00 + ((x) * 0x20)) +#define KE_LSB_REG(x) (0x04 + ((x) * 0x20)) +#define KE_MID_REG(x) (0x08 + ((x) * 0x20)) +#define KE_MSB_REG(x) (0x0c + ((x) * 0x20)) +#define KE_SIGN_REG(x) (0x10 + ((x) * 0x20)) +#define KE_OSC_SEL_REG 0xf8 +#define KE_OSC_SEL_CLK(x) (((x) & 0x3) << 0) +#define KE_OSC_SEL_EXT KE_OSC_SEL_CLK(1) +#define KE_OSC_SEL_4MHZ KE_OSC_SEL_CLK(2) +#define KE_OSC_SEL_20MHZ KE_OSC_SEL_CLK(3) +#define KE_DO_REG 0xfc + +static int ke_counter_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[0]; + + /* Order matters */ + outb((val >> 24) & 0xff, dev->iobase + KE_SIGN_REG(chan)); + outb((val >> 16) & 0xff, dev->iobase + KE_MSB_REG(chan)); + outb((val >> 8) & 0xff, dev->iobase + KE_MID_REG(chan)); + outb((val >> 0) & 0xff, dev->iobase + KE_LSB_REG(chan)); + } + + return insn->n; +} + +static int ke_counter_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + for (i = 0; i < insn->n; i++) { + /* Order matters */ + inb(dev->iobase + KE_LATCH_REG(chan)); + + val = inb(dev->iobase + KE_LSB_REG(chan)); + val |= (inb(dev->iobase + KE_MID_REG(chan)) << 8); + val |= (inb(dev->iobase + KE_MSB_REG(chan)) << 16); + val |= (inb(dev->iobase + KE_SIGN_REG(chan)) << 24); + + data[i] = val; + } + + return insn->n; +} + +static void ke_counter_reset(struct comedi_device *dev) +{ + unsigned int chan; + + for (chan = 0; chan < 3; chan++) + outb(0, dev->iobase + KE_RESET_REG(chan)); +} + +static int ke_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned char src; + + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + switch (data[1]) { + case KE_CLK_20MHZ: /* default */ + src = KE_OSC_SEL_20MHZ; + break; + case KE_CLK_4MHZ: /* option */ + src = KE_OSC_SEL_4MHZ; + break; + case KE_CLK_EXT: /* Pin 21 on D-sub */ + src = KE_OSC_SEL_EXT; + break; + default: + return -EINVAL; + } + outb(src, dev->iobase + KE_OSC_SEL_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + src = inb(dev->iobase + KE_OSC_SEL_REG); + switch (src) { + case KE_OSC_SEL_20MHZ: + data[1] = KE_CLK_20MHZ; + data[2] = 50; /* 50ns */ + break; + case KE_OSC_SEL_4MHZ: + data[1] = KE_CLK_4MHZ; + data[2] = 250; /* 250ns */ + break; + case KE_OSC_SEL_EXT: + data[1] = KE_CLK_EXT; + data[2] = 0; /* Unknown */ + break; + default: + return -EINVAL; + } + break; + case INSN_CONFIG_RESET: + ke_counter_reset(dev); + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int ke_counter_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outb(s->state, dev->iobase + KE_DO_REG); + + data[1] = s->state; + + return insn->n; +} + +static int ke_counter_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 0); + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE; + s->n_chan = 3; + s->maxdata = 0x01ffffff; + s->range_table = &range_unknown; + s->insn_read = ke_counter_insn_read; + s->insn_write = ke_counter_insn_write; + s->insn_config = ke_counter_insn_config; + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 3; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ke_counter_do_insn_bits; + + outb(KE_OSC_SEL_20MHZ, dev->iobase + KE_OSC_SEL_REG); + + ke_counter_reset(dev); + + return 0; +} + +static struct comedi_driver ke_counter_driver = { + .driver_name = "ke_counter", + .module = THIS_MODULE, + .auto_attach = ke_counter_auto_attach, + .detach = comedi_pci_detach, +}; + +static int ke_counter_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ke_counter_driver, + id->driver_data); +} + +static const struct pci_device_id ke_counter_pci_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_KOLTER, 0x0014) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ke_counter_pci_table); + +static struct pci_driver ke_counter_pci_driver = { + .name = "ke_counter", + .id_table = ke_counter_pci_table, + .probe = ke_counter_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(ke_counter_driver, ke_counter_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Kolter Electronic Counter Card"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/me4000.c b/drivers/comedi/drivers/me4000.c new file mode 100644 index 000000000..9aea02b86 --- /dev/null +++ b/drivers/comedi/drivers/me4000.c @@ -0,0 +1,1277 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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: untested + * + * 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 + * https://www.comedi.org. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8254.h> + +#include "plx9052.h" + +#define ME4000_FIRMWARE "me4000_firmware.bin" + +/* + * 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_MODE_0 BIT(0) +#define ME4000_AO_CTRL_MODE_1 BIT(1) +#define ME4000_AO_CTRL_STOP BIT(2) +#define ME4000_AO_CTRL_ENABLE_FIFO BIT(3) +#define ME4000_AO_CTRL_ENABLE_EX_TRIG BIT(4) +#define ME4000_AO_CTRL_EX_TRIG_EDGE BIT(5) +#define ME4000_AO_CTRL_IMMEDIATE_STOP BIT(7) +#define ME4000_AO_CTRL_ENABLE_DO BIT(8) +#define ME4000_AO_CTRL_ENABLE_IRQ BIT(9) +#define ME4000_AO_CTRL_RESET_IRQ BIT(10) +#define ME4000_AO_STATUS_REG(x) (0x04 + ME4000_AO_CHAN(x)) +#define ME4000_AO_STATUS_FSM BIT(0) +#define ME4000_AO_STATUS_FF BIT(1) +#define ME4000_AO_STATUS_HF BIT(2) +#define ME4000_AO_STATUS_EF BIT(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_MODE_0 BIT(0) +#define ME4000_AI_CTRL_MODE_1 BIT(1) +#define ME4000_AI_CTRL_MODE_2 BIT(2) +#define ME4000_AI_CTRL_SAMPLE_HOLD BIT(3) +#define ME4000_AI_CTRL_IMMEDIATE_STOP BIT(4) +#define ME4000_AI_CTRL_STOP BIT(5) +#define ME4000_AI_CTRL_CHANNEL_FIFO BIT(6) +#define ME4000_AI_CTRL_DATA_FIFO BIT(7) +#define ME4000_AI_CTRL_FULLSCALE BIT(8) +#define ME4000_AI_CTRL_OFFSET BIT(9) +#define ME4000_AI_CTRL_EX_TRIG_ANALOG BIT(10) +#define ME4000_AI_CTRL_EX_TRIG BIT(11) +#define ME4000_AI_CTRL_EX_TRIG_FALLING BIT(12) +#define ME4000_AI_CTRL_EX_IRQ BIT(13) +#define ME4000_AI_CTRL_EX_IRQ_RESET BIT(14) +#define ME4000_AI_CTRL_LE_IRQ BIT(15) +#define ME4000_AI_CTRL_LE_IRQ_RESET BIT(16) +#define ME4000_AI_CTRL_HF_IRQ BIT(17) +#define ME4000_AI_CTRL_HF_IRQ_RESET BIT(18) +#define ME4000_AI_CTRL_SC_IRQ BIT(19) +#define ME4000_AI_CTRL_SC_IRQ_RESET BIT(20) +#define ME4000_AI_CTRL_SC_RELOAD BIT(21) +#define ME4000_AI_STATUS_EF_CHANNEL BIT(22) +#define ME4000_AI_STATUS_HF_CHANNEL BIT(23) +#define ME4000_AI_STATUS_FF_CHANNEL BIT(24) +#define ME4000_AI_STATUS_EF_DATA BIT(25) +#define ME4000_AI_STATUS_HF_DATA BIT(26) +#define ME4000_AI_STATUS_FF_DATA BIT(27) +#define ME4000_AI_STATUS_LE BIT(28) +#define ME4000_AI_STATUS_FSM BIT(29) +#define ME4000_AI_CTRL_EX_TRIG_BOTH BIT(31) +#define ME4000_AI_CHANNEL_LIST_REG 0x78 +#define ME4000_AI_LIST_INPUT_DIFFERENTIAL BIT(5) +#define ME4000_AI_LIST_RANGE(x) ((3 - ((x) & 3)) << 6) +#define ME4000_AI_LIST_LAST_ENTRY BIT(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_EX BIT(0) +#define ME4000_IRQ_STATUS_LE BIT(1) +#define ME4000_IRQ_STATUS_AI_HF BIT(2) +#define ME4000_IRQ_STATUS_AO_0_HF BIT(3) +#define ME4000_IRQ_STATUS_AO_1_HF BIT(4) +#define ME4000_IRQ_STATUS_AO_2_HF BIT(5) +#define ME4000_IRQ_STATUS_AO_3_HF BIT(6) +#define ME4000_IRQ_STATUS_SC BIT(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_MODE_0 BIT(0) +#define ME4000_DIO_CTRL_MODE_1 BIT(1) +#define ME4000_DIO_CTRL_MODE_2 BIT(2) +#define ME4000_DIO_CTRL_MODE_3 BIT(3) +#define ME4000_DIO_CTRL_MODE_4 BIT(4) +#define ME4000_DIO_CTRL_MODE_5 BIT(5) +#define ME4000_DIO_CTRL_MODE_6 BIT(6) +#define ME4000_DIO_CTRL_MODE_7 BIT(7) +#define ME4000_DIO_CTRL_FUNCTION_0 BIT(8) +#define ME4000_DIO_CTRL_FUNCTION_1 BIT(9) +#define ME4000_DIO_CTRL_FIFO_HIGH_0 BIT(10) +#define ME4000_DIO_CTRL_FIFO_HIGH_1 BIT(11) +#define ME4000_DIO_CTRL_FIFO_HIGH_2 BIT(12) +#define ME4000_DIO_CTRL_FIFO_HIGH_3 BIT(13) +#define ME4000_AO_DEMUX_ADJUST_REG 0xbc +#define ME4000_AO_DEMUX_ADJUST_VALUE 0x4c +#define ME4000_AI_SAMPLE_COUNTER_REG 0xc0 + +#define ME4000_AI_FIFO_COUNT 2048 + +#define ME4000_AI_MIN_TICKS 66 +#define ME4000_AI_MIN_SAMPLE_TIME 2000 + +#define ME4000_AI_CHANNEL_LIST_COUNT 1024 + +struct me4000_private { + unsigned long plx_regbase; + unsigned int ai_ctrl_mode; + unsigned int ai_init_ticks; + unsigned int ai_scan_ticks; + unsigned int ai_chan_ticks; +}; + +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 ai_nchan; + unsigned int can_do_diff_ai:1; + unsigned int can_do_sh_ai:1; /* sample & hold (8 channels) */ + unsigned int ex_trig_analog:1; + unsigned int has_ao:1; + unsigned int has_ao_fifo:1; + unsigned int has_counter:1; +}; + +static const struct me4000_board me4000_boards[] = { + [BOARD_ME4650] = { + .name = "ME-4650", + .ai_nchan = 16, + }, + [BOARD_ME4660] = { + .name = "ME-4660", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .has_counter = 1, + }, + [BOARD_ME4660I] = { + .name = "ME-4660i", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .has_counter = 1, + }, + [BOARD_ME4660S] = { + .name = "ME-4660s", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .can_do_sh_ai = 1, + .has_counter = 1, + }, + [BOARD_ME4660IS] = { + .name = "ME-4660is", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .can_do_sh_ai = 1, + .has_counter = 1, + }, + [BOARD_ME4670] = { + .name = "ME-4670", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_counter = 1, + }, + [BOARD_ME4670I] = { + .name = "ME-4670i", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_counter = 1, + }, + [BOARD_ME4670S] = { + .name = "ME-4670s", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .can_do_sh_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_counter = 1, + }, + [BOARD_ME4670IS] = { + .name = "ME-4670is", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .can_do_sh_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_counter = 1, + }, + [BOARD_ME4680] = { + .name = "ME-4680", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_ao_fifo = 1, + .has_counter = 1, + }, + [BOARD_ME4680I] = { + .name = "ME-4680i", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_ao_fifo = 1, + .has_counter = 1, + }, + [BOARD_ME4680S] = { + .name = "ME-4680s", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .can_do_sh_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_ao_fifo = 1, + .has_counter = 1, + }, + [BOARD_ME4680IS] = { + .name = "ME-4680is", + .ai_nchan = 32, + .can_do_diff_ai = 1, + .can_do_sh_ai = 1, + .ex_trig_analog = 1, + .has_ao = 1, + .has_ao_fifo = 1, + .has_counter = 1, + }, +}; + +/* + * NOTE: the ranges here are inverted compared to the values + * written to the ME4000_AI_CHANNEL_LIST_REG, + * + * The ME4000_AI_LIST_RANGE() macro handles the inversion. + */ +static const struct comedi_lrange me4000_ai_range = { + 4, { + UNI_RANGE(2.5), + UNI_RANGE(10), + BIP_RANGE(2.5), + BIP_RANGE(10) + } +}; + +static int me4000_xilinx_download(struct comedi_device *dev, + const u8 *data, size_t size, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct me4000_private *devpriv = dev->private; + unsigned long xilinx_iobase = pci_resource_start(pcidev, 5); + unsigned int file_length; + unsigned int val; + unsigned int i; + + if (!xilinx_iobase) + return -ENODEV; + + /* + * Set PLX local interrupt 2 polarity to high. + * Interrupt is thrown by init pin of xilinx. + */ + outl(PLX9052_INTCSR_LI2POL, devpriv->plx_regbase + PLX9052_INTCSR); + + /* Set /CS and /WRITE of the Xilinx */ + val = inl(devpriv->plx_regbase + PLX9052_CNTRL); + val |= PLX9052_CNTRL_UIO2_DATA; + outl(val, devpriv->plx_regbase + PLX9052_CNTRL); + + /* Init Xilinx with CS1 */ + inb(xilinx_iobase + 0xC8); + + /* Wait until /INIT pin is set */ + usleep_range(20, 1000); + val = inl(devpriv->plx_regbase + PLX9052_INTCSR); + if (!(val & PLX9052_INTCSR_LI2STAT)) { + dev_err(dev->class_dev, "Can't init Xilinx\n"); + return -EIO; + } + + /* Reset /CS and /WRITE of the Xilinx */ + val = inl(devpriv->plx_regbase + PLX9052_CNTRL); + val &= ~PLX9052_CNTRL_UIO2_DATA; + outl(val, devpriv->plx_regbase + PLX9052_CNTRL); + + /* Download Xilinx firmware */ + file_length = (((unsigned int)data[0] & 0xff) << 24) + + (((unsigned int)data[1] & 0xff) << 16) + + (((unsigned int)data[2] & 0xff) << 8) + + ((unsigned int)data[3] & 0xff); + usleep_range(10, 1000); + + for (i = 0; i < file_length; i++) { + outb(data[16 + i], xilinx_iobase); + usleep_range(10, 1000); + + /* Check if BUSY flag is low */ + val = inl(devpriv->plx_regbase + PLX9052_CNTRL); + if (val & PLX9052_CNTRL_UIO1_DATA) { + dev_err(dev->class_dev, + "Xilinx is still busy (i = %d)\n", i); + return -EIO; + } + } + + /* If done flag is high download was successful */ + val = inl(devpriv->plx_regbase + PLX9052_CNTRL); + if (!(val & PLX9052_CNTRL_UIO0_DATA)) { + dev_err(dev->class_dev, "DONE flag is not set\n"); + dev_err(dev->class_dev, "Download not successful\n"); + return -EIO; + } + + /* Set /CS and /WRITE */ + val = inl(devpriv->plx_regbase + PLX9052_CNTRL); + val |= PLX9052_CNTRL_UIO2_DATA; + outl(val, devpriv->plx_regbase + PLX9052_CNTRL); + + return 0; +} + +static void me4000_ai_reset(struct comedi_device *dev) +{ + unsigned int ctrl; + + /* Stop any running conversion */ + ctrl = inl(dev->iobase + ME4000_AI_CTRL_REG); + ctrl |= ME4000_AI_CTRL_STOP | ME4000_AI_CTRL_IMMEDIATE_STOP; + outl(ctrl, dev->iobase + ME4000_AI_CTRL_REG); + + /* Clear the control register */ + outl(0x0, dev->iobase + ME4000_AI_CTRL_REG); +} + +static void me4000_reset(struct comedi_device *dev) +{ + struct me4000_private *devpriv = dev->private; + unsigned int val; + int chan; + + /* Disable interrupts on the PLX */ + outl(0, devpriv->plx_regbase + PLX9052_INTCSR); + + /* Software reset the PLX */ + val = inl(devpriv->plx_regbase + PLX9052_CNTRL); + val |= PLX9052_CNTRL_PCI_RESET; + outl(val, devpriv->plx_regbase + PLX9052_CNTRL); + val &= ~PLX9052_CNTRL_PCI_RESET; + outl(val, devpriv->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)); + + me4000_ai_reset(dev); + + /* Set both stop bits in the analog output control register */ + val = ME4000_AO_CTRL_IMMEDIATE_STOP | ME4000_AO_CTRL_STOP; + for (chan = 0; chan < 4; chan++) + outl(val, dev->iobase + ME4000_AO_CTRL_REG(chan)); + + /* 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); +} + +static unsigned int me4000_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + /* read two's complement value and munge to offset binary */ + val = inl(dev->iobase + ME4000_AI_DATA_REG); + return comedi_offset_munge(s, val); +} + +static int me4000_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inl(dev->iobase + ME4000_AI_STATUS_REG); + if (status & ME4000_AI_STATUS_EF_DATA) + return 0; + return -EBUSY; +} + +static int me4000_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 entry; + int ret = 0; + int i; + + entry = chan | ME4000_AI_LIST_RANGE(range); + if (aref == AREF_DIFF) { + if (!(s->subdev_flags & SDF_DIFF)) { + dev_err(dev->class_dev, + "Differential inputs are not available\n"); + return -EINVAL; + } + + if (!comedi_range_is_bipolar(s, range)) { + dev_err(dev->class_dev, + "Range must be bipolar when aref = diff\n"); + return -EINVAL; + } + + if (chan >= (s->n_chan / 2)) { + dev_err(dev->class_dev, + "Analog input is not available\n"); + return -EINVAL; + } + entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL; + } + + entry |= ME4000_AI_LIST_LAST_ENTRY; + + /* Enable channel list and data fifo for single acquisition mode */ + outl(ME4000_AI_CTRL_CHANNEL_FIFO | ME4000_AI_CTRL_DATA_FIFO, + 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); + + for (i = 0; i < insn->n; i++) { + unsigned int val; + + /* start conversion by dummy read */ + inl(dev->iobase + ME4000_AI_START_REG); + + ret = comedi_timeout(dev, s, insn, me4000_ai_eoc, 0); + if (ret) + break; + + val = me4000_ai_get_sample(dev, s); + data[i] = comedi_offset_munge(s, val); + } + + me4000_ai_reset(dev); + + return ret ? ret : insn->n; +} + +static int me4000_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + me4000_ai_reset(dev); + + return 0; +} + +static int me4000_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (aref != aref0) { + dev_dbg(dev->class_dev, + "Mode is not equal for all entries\n"); + return -EINVAL; + } + + if (aref == AREF_DIFF) { + if (!(s->subdev_flags & SDF_DIFF)) { + dev_err(dev->class_dev, + "Differential inputs are not available\n"); + return -EINVAL; + } + + if (chan >= (s->n_chan / 2)) { + 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 void me4000_ai_round_cmd_args(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct me4000_private *devpriv = dev->private; + int rest; + + devpriv->ai_init_ticks = 0; + devpriv->ai_scan_ticks = 0; + devpriv->ai_chan_ticks = 0; + + if (cmd->start_arg) { + devpriv->ai_init_ticks = (cmd->start_arg * 33) / 1000; + rest = (cmd->start_arg * 33) % 1000; + + if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) { + if (rest > 33) + devpriv->ai_init_ticks++; + } else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) { + if (rest) + devpriv->ai_init_ticks++; + } + } + + if (cmd->scan_begin_arg) { + devpriv->ai_scan_ticks = (cmd->scan_begin_arg * 33) / 1000; + rest = (cmd->scan_begin_arg * 33) % 1000; + + if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) { + if (rest > 33) + devpriv->ai_scan_ticks++; + } else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) { + if (rest) + devpriv->ai_scan_ticks++; + } + } + + if (cmd->convert_arg) { + devpriv->ai_chan_ticks = (cmd->convert_arg * 33) / 1000; + rest = (cmd->convert_arg * 33) % 1000; + + if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) { + if (rest > 33) + devpriv->ai_chan_ticks++; + } else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) { + if (rest) + devpriv->ai_chan_ticks++; + } + } +} + +static void me4000_ai_write_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]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + unsigned int entry; + + entry = chan | ME4000_AI_LIST_RANGE(range); + + if (aref == AREF_DIFF) + entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL; + + if (i == (cmd->chanlist_len - 1)) + entry |= ME4000_AI_LIST_LAST_ENTRY; + + outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG); + } +} + +static int me4000_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct me4000_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int ctrl; + + /* Write timer arguments */ + outl(devpriv->ai_init_ticks - 1, + dev->iobase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG); + outl(0x0, dev->iobase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG); + + if (devpriv->ai_scan_ticks) { + outl(devpriv->ai_scan_ticks - 1, + dev->iobase + ME4000_AI_SCAN_TIMER_LOW_REG); + outl(0x0, dev->iobase + ME4000_AI_SCAN_TIMER_HIGH_REG); + } + + outl(devpriv->ai_chan_ticks - 1, + dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG); + outl(devpriv->ai_chan_ticks - 1, + dev->iobase + ME4000_AI_CHAN_TIMER_REG); + + /* Start sources */ + ctrl = devpriv->ai_ctrl_mode | + ME4000_AI_CTRL_CHANNEL_FIFO | + ME4000_AI_CTRL_DATA_FIFO; + + /* Stop triggers */ + if (cmd->stop_src == TRIG_COUNT) { + outl(cmd->chanlist_len * cmd->stop_arg, + dev->iobase + ME4000_AI_SAMPLE_COUNTER_REG); + ctrl |= ME4000_AI_CTRL_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); + ctrl |= ME4000_AI_CTRL_SC_IRQ; + } + ctrl |= ME4000_AI_CTRL_HF_IRQ; + + /* Write the setup to the control register */ + outl(ctrl, dev->iobase + ME4000_AI_CTRL_REG); + + /* Write the channel list */ + me4000_ai_write_chanlist(dev, s, cmd); + + /* 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) +{ + struct me4000_private *devpriv = dev->private; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, + TRIG_NONE | TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE | TRIG_COUNT); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->scan_end_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_0; + } else if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_0; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_1; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_1; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_2; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_0 | + ME4000_AI_CTRL_MODE_1; + } else { + err |= -EINVAL; + } + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->chanlist_len < 1) { + cmd->chanlist_len = 1; + err |= -EINVAL; + } + + /* Round the timer arguments */ + me4000_ai_round_cmd_args(dev, s, cmd); + + if (devpriv->ai_init_ticks < 66) { + cmd->start_arg = 2000; + err |= -EINVAL; + } + if (devpriv->ai_scan_ticks && devpriv->ai_scan_ticks < 67) { + cmd->scan_begin_arg = 2031; + err |= -EINVAL; + } + if (devpriv->ai_chan_ticks < 66) { + cmd->convert_arg = 2000; + err |= -EINVAL; + } + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* + * Stage 4. Check for argument conflicts. + */ + if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + /* Check timer arguments */ + if (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_scan_ticks <= + cmd->chanlist_len * devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_scan_ticks <= + cmd->chanlist_len * devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_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 (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) { + dev_err(dev->class_dev, "Invalid start arg\n"); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + } + if (cmd->scan_end_src == TRIG_COUNT) { + if (cmd->scan_end_arg == 0) { + dev_err(dev->class_dev, "Invalid scan end arg\n"); + cmd->scan_end_arg = 1; + err++; + } + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= me4000_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static irqreturn_t me4000_ai_isr(int irq, void *dev_id) +{ + unsigned int tmp; + struct comedi_device *dev = dev_id; + struct comedi_subdevice *s = dev->read_subdev; + int i; + int c = 0; + unsigned short lval; + + if (!dev->attached) + return IRQ_NONE; + + if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) & + ME4000_IRQ_STATUS_AI_HF) { + /* Read status register to find out what happened */ + tmp = inl(dev->iobase + ME4000_AI_STATUS_REG); + + if (!(tmp & ME4000_AI_STATUS_FF_DATA) && + !(tmp & ME4000_AI_STATUS_HF_DATA) && + (tmp & ME4000_AI_STATUS_EF_DATA)) { + dev_err(dev->class_dev, "FIFO overflow\n"); + s->async->events |= COMEDI_CB_ERROR; + c = ME4000_AI_FIFO_COUNT; + } else if ((tmp & ME4000_AI_STATUS_FF_DATA) && + !(tmp & ME4000_AI_STATUS_HF_DATA) && + (tmp & ME4000_AI_STATUS_EF_DATA)) { + c = ME4000_AI_FIFO_COUNT / 2; + } else { + dev_err(dev->class_dev, "Undefined FIFO state\n"); + s->async->events |= COMEDI_CB_ERROR; + c = 0; + } + + for (i = 0; i < c; i++) { + lval = me4000_ai_get_sample(dev, s); + if (!comedi_buf_write_samples(s, &lval, 1)) + break; + } + + /* Work is done, so reset the interrupt */ + tmp |= ME4000_AI_CTRL_HF_IRQ_RESET; + outl(tmp, dev->iobase + ME4000_AI_CTRL_REG); + tmp &= ~ME4000_AI_CTRL_HF_IRQ_RESET; + outl(tmp, dev->iobase + ME4000_AI_CTRL_REG); + } + + if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) & + ME4000_IRQ_STATUS_SC) { + /* Acquisition is complete */ + s->async->events |= COMEDI_CB_EOA; + + /* Poll data until fifo empty */ + while (inl(dev->iobase + ME4000_AI_STATUS_REG) & + ME4000_AI_STATUS_EF_DATA) { + lval = me4000_ai_get_sample(dev, s); + if (!comedi_buf_write_samples(s, &lval, 1)) + break; + } + + /* Work is done, so reset the interrupt */ + tmp = inl(dev->iobase + ME4000_AI_CTRL_REG); + tmp |= ME4000_AI_CTRL_SC_IRQ_RESET; + outl(tmp, dev->iobase + ME4000_AI_CTRL_REG); + tmp &= ~ME4000_AI_CTRL_SC_IRQ_RESET; + outl(tmp, dev->iobase + ME4000_AI_CTRL_REG); + } + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int me4000_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int tmp; + + /* Stop any running conversion */ + tmp = inl(dev->iobase + ME4000_AO_CTRL_REG(chan)); + tmp |= ME4000_AO_CTRL_IMMEDIATE_STOP; + outl(tmp, dev->iobase + ME4000_AO_CTRL_REG(chan)); + + /* Clear control register and set to single mode */ + outl(0x0, dev->iobase + ME4000_AO_CTRL_REG(chan)); + + /* Write data value */ + outl(data[0], dev->iobase + ME4000_AO_SINGLE_REG(chan)); + + /* Store in the mirror */ + s->readback[chan] = data[0]; + + return 1; +} + +static int me4000_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) { + outl((s->state >> 0) & 0xFF, + dev->iobase + ME4000_DIO_PORT_0_REG); + outl((s->state >> 8) & 0xFF, + dev->iobase + ME4000_DIO_PORT_1_REG); + outl((s->state >> 16) & 0xFF, + dev->iobase + ME4000_DIO_PORT_2_REG); + outl((s->state >> 24) & 0xFF, + dev->iobase + ME4000_DIO_PORT_3_REG); + } + + data[1] = ((inl(dev->iobase + ME4000_DIO_PORT_0_REG) & 0xFF) << 0) | + ((inl(dev->iobase + ME4000_DIO_PORT_1_REG) & 0xFF) << 8) | + ((inl(dev->iobase + ME4000_DIO_PORT_2_REG) & 0xFF) << 16) | + ((inl(dev->iobase + ME4000_DIO_PORT_3_REG) & 0xFF) << 24); + + return insn->n; +} + +static int me4000_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + unsigned int tmp; + int ret; + + if (chan < 8) + mask = 0x000000ff; + else if (chan < 16) + mask = 0x0000ff00; + else if (chan < 24) + mask = 0x00ff0000; + else + mask = 0xff000000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + tmp = inl(dev->iobase + ME4000_DIO_CTRL_REG); + tmp &= ~(ME4000_DIO_CTRL_MODE_0 | ME4000_DIO_CTRL_MODE_1 | + ME4000_DIO_CTRL_MODE_2 | ME4000_DIO_CTRL_MODE_3 | + ME4000_DIO_CTRL_MODE_4 | ME4000_DIO_CTRL_MODE_5 | + ME4000_DIO_CTRL_MODE_6 | ME4000_DIO_CTRL_MODE_7); + if (s->io_bits & 0x000000ff) + tmp |= ME4000_DIO_CTRL_MODE_0; + if (s->io_bits & 0x0000ff00) + tmp |= ME4000_DIO_CTRL_MODE_2; + if (s->io_bits & 0x00ff0000) + tmp |= ME4000_DIO_CTRL_MODE_4; + if (s->io_bits & 0xff000000) + tmp |= ME4000_DIO_CTRL_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_MODE_0; + tmp &= ~(ME4000_DIO_CTRL_MODE_2 | ME4000_DIO_CTRL_MODE_3); + } + + outl(tmp, dev->iobase + ME4000_DIO_CTRL_REG); + + return insn->n; +} + +static int me4000_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct me4000_board *board = NULL; + struct me4000_private *devpriv; + struct comedi_subdevice *s; + int result; + + if (context < ARRAY_SIZE(me4000_boards)) + board = &me4000_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; + + result = comedi_pci_enable(dev); + if (result) + return result; + + devpriv->plx_regbase = pci_resource_start(pcidev, 1); + dev->iobase = pci_resource_start(pcidev, 2); + if (!devpriv->plx_regbase || !dev->iobase) + return -ENODEV; + + result = comedi_load_firmware(dev, &pcidev->dev, ME4000_FIRMWARE, + me4000_xilinx_download, 0); + if (result < 0) + return result; + + me4000_reset(dev); + + if (pcidev->irq > 0) { + result = request_irq(pcidev->irq, me4000_ai_isr, IRQF_SHARED, + dev->board_name, dev); + if (result == 0) { + dev->irq = pcidev->irq; + + /* Enable interrupts on the PLX */ + outl(PLX9052_INTCSR_LI1ENAB | PLX9052_INTCSR_LI1POL | + PLX9052_INTCSR_PCIENAB, + devpriv->plx_regbase + PLX9052_INTCSR); + } + } + + result = comedi_alloc_subdevices(dev, 4); + if (result) + return result; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; + if (board->can_do_diff_ai) + s->subdev_flags |= SDF_DIFF; + s->n_chan = board->ai_nchan; + s->maxdata = 0xffff; + 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; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_COMMON | SDF_GROUND; + s->n_chan = 4; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = me4000_ao_insn_write; + + result = comedi_alloc_subdev_readback(s); + if (result) + return result; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + 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 = me4000_dio_insn_bits; + s->insn_config = me4000_dio_insn_config; + + /* + * 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_MODE_0, + dev->iobase + ME4000_DIO_DIR_REG); + } + + /* Counter subdevice (8254) */ + s = &dev->subdevices[3]; + if (board->has_counter) { + unsigned long timer_base = pci_resource_start(pcidev, 3); + + if (!timer_base) + return -ENODEV; + + dev->pacer = comedi_8254_init(timer_base, 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + comedi_8254_subdevice_init(s, dev->pacer); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static void me4000_detach(struct comedi_device *dev) +{ + if (dev->irq) { + struct me4000_private *devpriv = dev->private; + + /* Disable interrupts on the PLX */ + outl(0, devpriv->plx_regbase + PLX9052_INTCSR); + } + comedi_pci_detach(dev); +} + +static struct comedi_driver me4000_driver = { + .driver_name = "me4000", + .module = THIS_MODULE, + .auto_attach = me4000_auto_attach, + .detach = me4000_detach, +}; + +static int me4000_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &me4000_driver, id->driver_data); +} + +static const struct pci_device_id me4000_pci_table[] = { + { PCI_VDEVICE(MEILHAUS, 0x4650), BOARD_ME4650 }, + { PCI_VDEVICE(MEILHAUS, 0x4660), BOARD_ME4660 }, + { PCI_VDEVICE(MEILHAUS, 0x4661), BOARD_ME4660I }, + { PCI_VDEVICE(MEILHAUS, 0x4662), BOARD_ME4660S }, + { PCI_VDEVICE(MEILHAUS, 0x4663), BOARD_ME4660IS }, + { PCI_VDEVICE(MEILHAUS, 0x4670), BOARD_ME4670 }, + { PCI_VDEVICE(MEILHAUS, 0x4671), BOARD_ME4670I }, + { PCI_VDEVICE(MEILHAUS, 0x4672), BOARD_ME4670S }, + { PCI_VDEVICE(MEILHAUS, 0x4673), BOARD_ME4670IS }, + { PCI_VDEVICE(MEILHAUS, 0x4680), BOARD_ME4680 }, + { PCI_VDEVICE(MEILHAUS, 0x4681), BOARD_ME4680I }, + { PCI_VDEVICE(MEILHAUS, 0x4682), BOARD_ME4680S }, + { PCI_VDEVICE(MEILHAUS, 0x4683), BOARD_ME4680IS }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, me4000_pci_table); + +static struct pci_driver me4000_pci_driver = { + .name = "me4000", + .id_table = me4000_pci_table, + .probe = me4000_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(me4000_driver, me4000_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Meilhaus ME-4000 series boards"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(ME4000_FIRMWARE); diff --git a/drivers/comedi/drivers/me_daq.c b/drivers/comedi/drivers/me_daq.c new file mode 100644 index 000000000..076b15097 --- /dev/null +++ b/drivers/comedi/drivers/me_daq.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: me_daq + * Description: Meilhaus PCI data acquisition cards + * Devices: [Meilhaus] ME-2600i (me-2600i), ME-2000i (me-2000i) + * Author: Michael Hillmann <hillmann@syscongroup.de> + * Status: experimental + * + * Configuration options: not applicable, uses PCI auto config + * + * Supports: + * Analog Input, Analog Output, Digital I/O + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/comedi/comedi_pci.h> + +#include "plx9052.h" + +#define ME2600_FIRMWARE "me2600_firmware.bin" + +#define XILINX_DOWNLOAD_RESET 0x42 /* Xilinx registers */ + +/* + * PCI BAR2 Memory map (dev->mmio) + */ +#define ME_CTRL1_REG 0x00 /* R (ai start) | W */ +#define ME_CTRL1_INT_ENA BIT(15) +#define ME_CTRL1_COUNTER_B_IRQ BIT(12) +#define ME_CTRL1_COUNTER_A_IRQ BIT(11) +#define ME_CTRL1_CHANLIST_READY_IRQ BIT(10) +#define ME_CTRL1_EXT_IRQ BIT(9) +#define ME_CTRL1_ADFIFO_HALFFULL_IRQ BIT(8) +#define ME_CTRL1_SCAN_COUNT_ENA BIT(5) +#define ME_CTRL1_SIMULTANEOUS_ENA BIT(4) +#define ME_CTRL1_TRIGGER_FALLING_EDGE BIT(3) +#define ME_CTRL1_CONTINUOUS_MODE BIT(2) +#define ME_CTRL1_ADC_MODE(x) (((x) & 0x3) << 0) +#define ME_CTRL1_ADC_MODE_DISABLE ME_CTRL1_ADC_MODE(0) +#define ME_CTRL1_ADC_MODE_SOFT_TRIG ME_CTRL1_ADC_MODE(1) +#define ME_CTRL1_ADC_MODE_SCAN_TRIG ME_CTRL1_ADC_MODE(2) +#define ME_CTRL1_ADC_MODE_EXT_TRIG ME_CTRL1_ADC_MODE(3) +#define ME_CTRL1_ADC_MODE_MASK ME_CTRL1_ADC_MODE(3) +#define ME_CTRL2_REG 0x02 /* R (dac update) | W */ +#define ME_CTRL2_ADFIFO_ENA BIT(10) +#define ME_CTRL2_CHANLIST_ENA BIT(9) +#define ME_CTRL2_PORT_B_ENA BIT(7) +#define ME_CTRL2_PORT_A_ENA BIT(6) +#define ME_CTRL2_COUNTER_B_ENA BIT(4) +#define ME_CTRL2_COUNTER_A_ENA BIT(3) +#define ME_CTRL2_DAC_ENA BIT(1) +#define ME_CTRL2_BUFFERED_DAC BIT(0) +#define ME_STATUS_REG 0x04 /* R | W (clears interrupts) */ +#define ME_STATUS_COUNTER_B_IRQ BIT(12) +#define ME_STATUS_COUNTER_A_IRQ BIT(11) +#define ME_STATUS_CHANLIST_READY_IRQ BIT(10) +#define ME_STATUS_EXT_IRQ BIT(9) +#define ME_STATUS_ADFIFO_HALFFULL_IRQ BIT(8) +#define ME_STATUS_ADFIFO_FULL BIT(4) +#define ME_STATUS_ADFIFO_HALFFULL BIT(3) +#define ME_STATUS_ADFIFO_EMPTY BIT(2) +#define ME_STATUS_CHANLIST_FULL BIT(1) +#define ME_STATUS_FST_ACTIVE BIT(0) +#define ME_DIO_PORT_A_REG 0x06 /* R | W */ +#define ME_DIO_PORT_B_REG 0x08 /* R | W */ +#define ME_TIMER_DATA_REG(x) (0x0a + ((x) * 2)) /* - | W */ +#define ME_AI_FIFO_REG 0x10 /* R (fifo) | W (chanlist) */ +#define ME_AI_FIFO_CHANLIST_DIFF BIT(7) +#define ME_AI_FIFO_CHANLIST_UNIPOLAR BIT(6) +#define ME_AI_FIFO_CHANLIST_GAIN(x) (((x) & 0x3) << 4) +#define ME_AI_FIFO_CHANLIST_CHAN(x) (((x) & 0xf) << 0) +#define ME_DAC_CTRL_REG 0x12 /* R (updates) | W */ +#define ME_DAC_CTRL_BIPOLAR(x) BIT(7 - ((x) & 0x3)) +#define ME_DAC_CTRL_GAIN(x) BIT(11 - ((x) & 0x3)) +#define ME_DAC_CTRL_MASK(x) (ME_DAC_CTRL_BIPOLAR(x) | \ + ME_DAC_CTRL_GAIN(x)) +#define ME_AO_DATA_REG(x) (0x14 + ((x) * 2)) /* - | W */ +#define ME_COUNTER_ENDDATA_REG(x) (0x1c + ((x) * 2)) /* - | W */ +#define ME_COUNTER_STARTDATA_REG(x) (0x20 + ((x) * 2)) /* - | W */ +#define ME_COUNTER_VALUE_REG(x) (0x20 + ((x) * 2)) /* R | - */ + +static const struct comedi_lrange me_ai_range = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange me_ao_range = { + 3, { + BIP_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(10) + } +}; + +enum me_boardid { + BOARD_ME2600, + BOARD_ME2000, +}; + +struct me_board { + const char *name; + int needs_firmware; + int has_ao; +}; + +static const struct me_board me_boards[] = { + [BOARD_ME2600] = { + .name = "me-2600i", + .needs_firmware = 1, + .has_ao = 1, + }, + [BOARD_ME2000] = { + .name = "me-2000i", + }, +}; + +struct me_private_data { + void __iomem *plx_regbase; /* PLX configuration base address */ + + unsigned short ctrl1; /* Mirror of CONTROL_1 register */ + unsigned short ctrl2; /* Mirror of CONTROL_2 register */ + unsigned short dac_ctrl; /* Mirror of the DAC_CONTROL register */ +}; + +static inline void sleep(unsigned int sec) +{ + schedule_timeout_interruptible(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->ctrl2 |= ME_CTRL2_PORT_A_ENA; + else + devpriv->ctrl2 &= ~ME_CTRL2_PORT_A_ENA; + if (s->io_bits & 0xffff0000) + devpriv->ctrl2 |= ME_CTRL2_PORT_B_ENA; + else + devpriv->ctrl2 &= ~ME_CTRL2_PORT_B_ENA; + + writew(devpriv->ctrl2, dev->mmio + ME_CTRL2_REG); + + return insn->n; +} + +static int me_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + void __iomem *mmio_porta = dev->mmio + ME_DIO_PORT_A_REG; + void __iomem *mmio_portb = dev->mmio + ME_DIO_PORT_B_REG; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0x0000ffff) + writew((s->state & 0xffff), mmio_porta); + if (mask & 0xffff0000) + writew(((s->state >> 16) & 0xffff), mmio_portb); + } + + if (s->io_bits & 0x0000ffff) + val = s->state & 0xffff; + else + val = readw(mmio_porta); + + if (s->io_bits & 0xffff0000) + val |= (s->state & 0xffff0000); + else + val |= (readw(mmio_portb) << 16); + + data[1] = val; + + return insn->n; +} + +static int me_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readw(dev->mmio + ME_STATUS_REG); + if ((status & ME_STATUS_ADFIFO_EMPTY) == 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 *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 val; + int ret = 0; + int i; + + /* + * For differential operation, there are only 8 input channels + * and only bipolar ranges are available. + */ + if (aref & AREF_DIFF) { + if (chan > 7 || comedi_range_is_unipolar(s, range)) + return -EINVAL; + } + + /* clear chanlist and ad fifo */ + devpriv->ctrl2 &= ~(ME_CTRL2_ADFIFO_ENA | ME_CTRL2_CHANLIST_ENA); + writew(devpriv->ctrl2, dev->mmio + ME_CTRL2_REG); + + writew(0x00, dev->mmio + ME_STATUS_REG); /* clear interrupts */ + + /* enable the chanlist and ADC fifo */ + devpriv->ctrl2 |= (ME_CTRL2_ADFIFO_ENA | ME_CTRL2_CHANLIST_ENA); + writew(devpriv->ctrl2, dev->mmio + ME_CTRL2_REG); + + /* write to channel list fifo */ + val = ME_AI_FIFO_CHANLIST_CHAN(chan) | ME_AI_FIFO_CHANLIST_GAIN(range); + if (comedi_range_is_unipolar(s, range)) + val |= ME_AI_FIFO_CHANLIST_UNIPOLAR; + if (aref & AREF_DIFF) + val |= ME_AI_FIFO_CHANLIST_DIFF; + writew(val, dev->mmio + ME_AI_FIFO_REG); + + /* set ADC mode to software trigger */ + devpriv->ctrl1 |= ME_CTRL1_ADC_MODE_SOFT_TRIG; + writew(devpriv->ctrl1, dev->mmio + ME_CTRL1_REG); + + for (i = 0; i < insn->n; i++) { + /* start ai conversion */ + readw(dev->mmio + ME_CTRL1_REG); + + /* wait for ADC fifo not empty flag */ + ret = comedi_timeout(dev, s, insn, me_ai_eoc, 0); + if (ret) + break; + + /* get value from ADC fifo */ + val = readw(dev->mmio + ME_AI_FIFO_REG) & s->maxdata; + + /* munge 2's complement value to offset binary */ + data[i] = comedi_offset_munge(s, val); + } + + /* stop any running conversion */ + devpriv->ctrl1 &= ~ME_CTRL1_ADC_MODE_MASK; + writew(devpriv->ctrl1, dev->mmio + ME_CTRL1_REG); + + return ret ? ret : insn->n; +} + +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 *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + /* Enable all DAC */ + devpriv->ctrl2 |= ME_CTRL2_DAC_ENA; + writew(devpriv->ctrl2, dev->mmio + ME_CTRL2_REG); + + /* and set DAC to "buffered" mode */ + devpriv->ctrl2 |= ME_CTRL2_BUFFERED_DAC; + writew(devpriv->ctrl2, dev->mmio + ME_CTRL2_REG); + + /* Set dac-control register */ + devpriv->dac_ctrl &= ~ME_DAC_CTRL_MASK(chan); + if (range == 0) + devpriv->dac_ctrl |= ME_DAC_CTRL_GAIN(chan); + if (comedi_range_is_bipolar(s, range)) + devpriv->dac_ctrl |= ME_DAC_CTRL_BIPOLAR(chan); + writew(devpriv->dac_ctrl, dev->mmio + ME_DAC_CTRL_REG); + + /* Update dac-control register */ + readw(dev->mmio + ME_DAC_CTRL_REG); + + /* Set data register */ + for (i = 0; i < insn->n; i++) { + val = data[i]; + + writew(val, dev->mmio + ME_AO_DATA_REG(chan)); + } + s->readback[chan] = val; + + /* Update dac with data registers */ + readw(dev->mmio + ME_CTRL2_REG); + + 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 *devpriv = dev->private; + unsigned int value; + unsigned int file_length; + unsigned int i; + + /* disable irq's on PLX */ + writel(0x00, devpriv->plx_regbase + PLX9052_INTCSR); + + /* First, make a dummy read to reset xilinx */ + value = readw(dev->mmio + XILINX_DOWNLOAD_RESET); + + /* Wait until reset is over */ + sleep(1); + + /* Write a dummy value to Xilinx */ + writeb(0x00, dev->mmio + 0x0); + sleep(1); + + /* + * Format of the firmware + * Build longs from the byte-wise coded header + * Byte 1-3: length of the array + * Byte 4-7: version + * Byte 8-11: date + * Byte 12-15: reserved + */ + if (size < 16) + return -EINVAL; + + file_length = (((unsigned int)data[0] & 0xff) << 24) + + (((unsigned int)data[1] & 0xff) << 16) + + (((unsigned int)data[2] & 0xff) << 8) + + ((unsigned int)data[3] & 0xff); + + /* + * Loop for writing firmware byte by byte to xilinx + * Firmware data start at offset 16 + */ + for (i = 0; i < file_length; i++) + writeb((data[16 + i] & 0xff), dev->mmio + 0x0); + + /* Write 5 dummy values to xilinx */ + for (i = 0; i < 5; i++) + writeb(0x00, dev->mmio + 0x0); + + /* Test if there was an error during download -> INTB was thrown */ + value = readl(devpriv->plx_regbase + PLX9052_INTCSR); + if (value & PLX9052_INTCSR_LI2STAT) { + /* Disable interrupt */ + writel(0x00, devpriv->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, + devpriv->plx_regbase + PLX9052_INTCSR); + + return 0; +} + +static int me_reset(struct comedi_device *dev) +{ + struct me_private_data *devpriv = dev->private; + + /* Reset board */ + writew(0x00, dev->mmio + ME_CTRL1_REG); + writew(0x00, dev->mmio + ME_CTRL2_REG); + writew(0x00, dev->mmio + ME_STATUS_REG); /* clear interrupts */ + writew(0x00, dev->mmio + ME_DAC_CTRL_REG); + + /* Save values in the board context */ + devpriv->dac_ctrl = 0; + devpriv->ctrl1 = 0; + devpriv->ctrl2 = 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 *devpriv; + 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; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + devpriv->plx_regbase = pci_ioremap_bar(pcidev, 0); + if (!devpriv->plx_regbase) + return -ENOMEM; + + dev->mmio = pci_ioremap_bar(pcidev, 2); + if (!dev->mmio) + return -ENOMEM; + + /* Download firmware and reset card */ + if (board->needs_firmware) { + ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev, + ME2600_FIRMWARE, + me2600_xilinx_download, 0); + if (ret < 0) + return ret; + } + me_reset(dev); + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_DIFF; + s->n_chan = 16; + s->maxdata = 0x0fff; + s->len_chanlist = 16; + s->range_table = &me_ai_range; + s->insn_read = me_ai_insn_read; + + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->len_chanlist = 4; + s->range_table = &me_ao_range; + s->insn_write = me_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->len_chanlist = 32; + s->range_table = &range_digital; + s->insn_bits = me_dio_insn_bits; + s->insn_config = me_dio_insn_config; + + return 0; +} + +static void me_detach(struct comedi_device *dev) +{ + struct me_private_data *devpriv = dev->private; + + if (devpriv) { + if (dev->mmio) + me_reset(dev); + if (devpriv->plx_regbase) + iounmap(devpriv->plx_regbase); + } + comedi_pci_detach(dev); +} + +static struct comedi_driver me_daq_driver = { + .driver_name = "me_daq", + .module = THIS_MODULE, + .auto_attach = me_auto_attach, + .detach = me_detach, +}; + +static int me_daq_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &me_daq_driver, id->driver_data); +} + +static const struct pci_device_id me_daq_pci_table[] = { + { PCI_VDEVICE(MEILHAUS, 0x2600), BOARD_ME2600 }, + { PCI_VDEVICE(MEILHAUS, 0x2000), BOARD_ME2000 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, me_daq_pci_table); + +static struct pci_driver me_daq_pci_driver = { + .name = "me_daq", + .id_table = me_daq_pci_table, + .probe = me_daq_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(me_daq_driver, me_daq_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(ME2600_FIRMWARE); diff --git a/drivers/comedi/drivers/mf6x4.c b/drivers/comedi/drivers/mf6x4.c new file mode 100644 index 000000000..14f1d5e9c --- /dev/null +++ b/drivers/comedi/drivers/mf6x4.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ +/* + * Driver: mf6x4 + * Description: Humusoft MF634 and MF624 Data acquisition card driver + * Devices: [Humusoft] MF634 (mf634), MF624 (mf624) + * Author: Rostislav Lisovy <lisovy@gmail.com> + * Status: works + * Updated: + * Configuration Options: none + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedi_pci.h> + +/* Registers present in BAR0 memory region */ +#define MF624_GPIOC_REG 0x54 + +#define MF6X4_GPIOC_EOLC BIT(17) /* End Of Last Conversion */ +#define MF6X4_GPIOC_LDAC BIT(23) /* Load DACs */ +#define MF6X4_GPIOC_DACEN BIT(26) + +/* BAR1 registers */ +#define MF6X4_ADDATA_REG 0x00 +#define MF6X4_ADCTRL_REG 0x00 +#define MF6X4_ADCTRL_CHAN(x) BIT(chan) +#define MF6X4_DIN_REG 0x10 +#define MF6X4_DIN_MASK 0xff +#define MF6X4_DOUT_REG 0x10 +#define MF6X4_ADSTART_REG 0x20 +#define MF6X4_DAC_REG(x) (0x20 + ((x) * 2)) + +/* BAR2 registers */ +#define MF634_GPIOC_REG 0x68 + +enum mf6x4_boardid { + BOARD_MF634, + BOARD_MF624, +}; + +struct mf6x4_board { + const char *name; + /* We need to keep track of the order of BARs used by the cards */ + unsigned int bar_nums[3]; +}; + +static const struct mf6x4_board mf6x4_boards[] = { + [BOARD_MF634] = { + .name = "mf634", + .bar_nums = {0, 2, 3}, + }, + [BOARD_MF624] = { + .name = "mf624", + .bar_nums = {0, 2, 4}, + }, +}; + +struct mf6x4_private { + /* + * Documentation for both MF634 and MF624 describes registers + * present in BAR0, 1 and 2 regions. + * The real (i.e. in HW) BAR numbers are different for MF624 + * and MF634 yet we will call them 0, 1, 2 + */ + void __iomem *bar0_mem; + void __iomem *bar2_mem; + + /* + * This configuration register has the same function and fields + * for both cards however it lies in different BARs on different + * offsets -- this variable makes the access easier + */ + void __iomem *gpioc_reg; +}; + +static int mf6x4_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = ioread16(dev->mmio + MF6X4_DIN_REG) & MF6X4_DIN_MASK; + + return insn->n; +} + +static int mf6x4_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + iowrite16(s->state, dev->mmio + MF6X4_DOUT_REG); + + 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; + + /* EOLC goes low at end of conversion. */ + status = ioread32(devpriv->gpioc_reg); + if ((status & MF6X4_GPIOC_EOLC) == 0) + 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) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int d; + int ret; + int i; + + /* Set the ADC channel number in the scan list */ + iowrite16(MF6X4_ADCTRL_CHAN(chan), dev->mmio + MF6X4_ADCTRL_REG); + + for (i = 0; i < insn->n; i++) { + /* Trigger ADC conversion by reading ADSTART */ + ioread16(dev->mmio + MF6X4_ADSTART_REG); + + ret = comedi_timeout(dev, s, insn, mf6x4_ai_eoc, 0); + if (ret) + return ret; + + /* Read the actual value */ + d = ioread16(dev->mmio + MF6X4_ADDATA_REG); + d &= s->maxdata; + /* munge the 2's complement data to offset binary */ + data[i] = comedi_offset_munge(s, d); + } + + iowrite16(0x0, dev->mmio + MF6X4_ADCTRL_REG); + + return insn->n; +} + +static int mf6x4_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct mf6x4_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + unsigned int gpioc; + int i; + + /* Enable instantaneous update of converters outputs + Enable DACs */ + gpioc = ioread32(devpriv->gpioc_reg); + iowrite32((gpioc & ~MF6X4_GPIOC_LDAC) | MF6X4_GPIOC_DACEN, + devpriv->gpioc_reg); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + iowrite16(val, dev->mmio + MF6X4_DAC_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int mf6x4_auto_attach(struct comedi_device *dev, unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct mf6x4_board *board = NULL; + struct mf6x4_private *devpriv; + struct comedi_subdevice *s; + int ret; + + if (context < ARRAY_SIZE(mf6x4_boards)) + board = &mf6x4_boards[context]; + else + return -ENODEV; + + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->bar0_mem = pci_ioremap_bar(pcidev, board->bar_nums[0]); + if (!devpriv->bar0_mem) + return -ENODEV; + + dev->mmio = pci_ioremap_bar(pcidev, board->bar_nums[1]); + if (!dev->mmio) + return -ENODEV; + + devpriv->bar2_mem = pci_ioremap_bar(pcidev, board->bar_nums[2]); + if (!devpriv->bar2_mem) + return -ENODEV; + + if (board == &mf6x4_boards[BOARD_MF634]) + devpriv->gpioc_reg = devpriv->bar2_mem + MF634_GPIOC_REG; + else + devpriv->gpioc_reg = devpriv->bar0_mem + MF624_GPIOC_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; + s->n_chan = 8; + s->maxdata = 0x3fff; + s->range_table = &range_bipolar10; + s->insn_read = mf6x4_ai_insn_read; + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 0x3fff; + s->range_table = &range_bipolar10; + s->insn_write = mf6x4_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = mf6x4_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = mf6x4_do_insn_bits; + + return 0; +} + +static void mf6x4_detach(struct comedi_device *dev) +{ + struct mf6x4_private *devpriv = dev->private; + + if (devpriv) { + if (devpriv->bar0_mem) + iounmap(devpriv->bar0_mem); + if (devpriv->bar2_mem) + iounmap(devpriv->bar2_mem); + } + comedi_pci_detach(dev); +} + +static struct comedi_driver mf6x4_driver = { + .driver_name = "mf6x4", + .module = THIS_MODULE, + .auto_attach = mf6x4_auto_attach, + .detach = mf6x4_detach, +}; + +static int mf6x4_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &mf6x4_driver, id->driver_data); +} + +static const struct pci_device_id mf6x4_pci_table[] = { + { PCI_VDEVICE(HUMUSOFT, 0x0634), BOARD_MF634 }, + { PCI_VDEVICE(HUMUSOFT, 0x0624), BOARD_MF624 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, mf6x4_pci_table); + +static struct pci_driver mf6x4_pci_driver = { + .name = "mf6x4", + .id_table = mf6x4_pci_table, + .probe = mf6x4_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; + +module_comedi_pci_driver(mf6x4_driver, mf6x4_pci_driver); + +MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>"); +MODULE_DESCRIPTION("Comedi MF634 and MF624 DAQ cards driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/mite.c b/drivers/comedi/drivers/mite.c new file mode 100644 index 000000000..88f3cd6f5 --- /dev/null +++ b/drivers/comedi/drivers/mite.c @@ -0,0 +1,937 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/log2.h> +#include <linux/comedi/comedi_pci.h> + +#include "mite.h" + +/* + * Mite registers + */ +#define MITE_UNKNOWN_DMA_BURST_REG 0x28 +#define UNKNOWN_DMA_BURST_ENABLE_BITS 0x600 + +#define MITE_PCI_CONFIG_OFFSET 0x300 +#define MITE_CSIGR 0x460 /* chip signature */ +#define CSIGR_TO_IOWINS(x) (((x) >> 29) & 0x7) +#define CSIGR_TO_WINS(x) (((x) >> 24) & 0x1f) +#define CSIGR_TO_WPDEP(x) (((x) >> 20) & 0x7) +#define CSIGR_TO_DMAC(x) (((x) >> 16) & 0xf) +#define CSIGR_TO_IMODE(x) (((x) >> 12) & 0x3) /* pci=0x3 */ +#define CSIGR_TO_MMODE(x) (((x) >> 8) & 0x3) /* minimite=1 */ +#define CSIGR_TO_TYPE(x) (((x) >> 4) & 0xf) /* mite=0, minimite=1 */ +#define CSIGR_TO_VER(x) (((x) >> 0) & 0xf) + +#define MITE_CHAN(x) (0x500 + 0x100 * (x)) +#define MITE_CHOR(x) (0x00 + MITE_CHAN(x)) /* channel operation */ +#define CHOR_DMARESET BIT(31) +#define CHOR_SET_SEND_TC BIT(11) +#define CHOR_CLR_SEND_TC BIT(10) +#define CHOR_SET_LPAUSE BIT(9) +#define CHOR_CLR_LPAUSE BIT(8) +#define CHOR_CLRDONE BIT(7) +#define CHOR_CLRRB BIT(6) +#define CHOR_CLRLC BIT(5) +#define CHOR_FRESET BIT(4) +#define CHOR_ABORT BIT(3) /* stop without emptying fifo */ +#define CHOR_STOP BIT(2) /* stop after emptying fifo */ +#define CHOR_CONT BIT(1) +#define CHOR_START BIT(0) +#define MITE_CHCR(x) (0x04 + MITE_CHAN(x)) /* channel control */ +#define CHCR_SET_DMA_IE BIT(31) +#define CHCR_CLR_DMA_IE BIT(30) +#define CHCR_SET_LINKP_IE BIT(29) +#define CHCR_CLR_LINKP_IE BIT(28) +#define CHCR_SET_SAR_IE BIT(27) +#define CHCR_CLR_SAR_IE BIT(26) +#define CHCR_SET_DONE_IE BIT(25) +#define CHCR_CLR_DONE_IE BIT(24) +#define CHCR_SET_MRDY_IE BIT(23) +#define CHCR_CLR_MRDY_IE BIT(22) +#define CHCR_SET_DRDY_IE BIT(21) +#define CHCR_CLR_DRDY_IE BIT(20) +#define CHCR_SET_LC_IE BIT(19) +#define CHCR_CLR_LC_IE BIT(18) +#define CHCR_SET_CONT_RB_IE BIT(17) +#define CHCR_CLR_CONT_RB_IE BIT(16) +#define CHCR_FIFO(x) (((x) & 0x1) << 15) +#define CHCR_FIFODIS CHCR_FIFO(1) +#define CHCR_FIFO_ON CHCR_FIFO(0) +#define CHCR_BURST(x) (((x) & 0x1) << 14) +#define CHCR_BURSTEN CHCR_BURST(1) +#define CHCR_NO_BURSTEN CHCR_BURST(0) +#define CHCR_BYTE_SWAP_DEVICE BIT(6) +#define CHCR_BYTE_SWAP_MEMORY BIT(4) +#define CHCR_DIR(x) (((x) & 0x1) << 3) +#define CHCR_DEV_TO_MEM CHCR_DIR(1) +#define CHCR_MEM_TO_DEV CHCR_DIR(0) +#define CHCR_MODE(x) (((x) & 0x7) << 0) +#define CHCR_NORMAL CHCR_MODE(0) +#define CHCR_CONTINUE CHCR_MODE(1) +#define CHCR_RINGBUFF CHCR_MODE(2) +#define CHCR_LINKSHORT CHCR_MODE(4) +#define CHCR_LINKLONG CHCR_MODE(5) +#define MITE_TCR(x) (0x08 + MITE_CHAN(x)) /* transfer count */ +#define MITE_MCR(x) (0x0c + MITE_CHAN(x)) /* memory config */ +#define MITE_MAR(x) (0x10 + MITE_CHAN(x)) /* memory address */ +#define MITE_DCR(x) (0x14 + MITE_CHAN(x)) /* device config */ +#define DCR_NORMAL BIT(29) +#define MITE_DAR(x) (0x18 + MITE_CHAN(x)) /* device address */ +#define MITE_LKCR(x) (0x1c + MITE_CHAN(x)) /* link config */ +#define MITE_LKAR(x) (0x20 + MITE_CHAN(x)) /* link address */ +#define MITE_LLKAR(x) (0x24 + MITE_CHAN(x)) /* see tnt5002 manual */ +#define MITE_BAR(x) (0x28 + MITE_CHAN(x)) /* base address */ +#define MITE_BCR(x) (0x2c + MITE_CHAN(x)) /* base count */ +#define MITE_SAR(x) (0x30 + MITE_CHAN(x)) /* ? address */ +#define MITE_WSCR(x) (0x34 + MITE_CHAN(x)) /* ? */ +#define MITE_WSER(x) (0x38 + MITE_CHAN(x)) /* ? */ +#define MITE_CHSR(x) (0x3c + MITE_CHAN(x)) /* channel status */ +#define CHSR_INT BIT(31) +#define CHSR_LPAUSES BIT(29) +#define CHSR_SARS BIT(27) +#define CHSR_DONE BIT(25) +#define CHSR_MRDY BIT(23) +#define CHSR_DRDY BIT(21) +#define CHSR_LINKC BIT(19) +#define CHSR_CONTS_RB BIT(17) +#define CHSR_ERROR BIT(15) +#define CHSR_SABORT BIT(14) +#define CHSR_HABORT BIT(13) +#define CHSR_STOPS BIT(12) +#define CHSR_OPERR(x) (((x) & 0x3) << 10) +#define CHSR_OPERR_MASK CHSR_OPERR(3) +#define CHSR_OPERR_NOERROR CHSR_OPERR(0) +#define CHSR_OPERR_FIFOERROR CHSR_OPERR(1) +#define CHSR_OPERR_LINKERROR CHSR_OPERR(1) /* ??? */ +#define CHSR_XFERR BIT(9) +#define CHSR_END BIT(8) +#define CHSR_DRQ1 BIT(7) +#define CHSR_DRQ0 BIT(6) +#define CHSR_LERR(x) (((x) & 0x3) << 4) +#define CHSR_LERR_MASK CHSR_LERR(3) +#define CHSR_LBERR CHSR_LERR(1) +#define CHSR_LRERR CHSR_LERR(2) +#define CHSR_LOERR CHSR_LERR(3) +#define CHSR_MERR(x) (((x) & 0x3) << 2) +#define CHSR_MERR_MASK CHSR_MERR(3) +#define CHSR_MBERR CHSR_MERR(1) +#define CHSR_MRERR CHSR_MERR(2) +#define CHSR_MOERR CHSR_MERR(3) +#define CHSR_DERR(x) (((x) & 0x3) << 0) +#define CHSR_DERR_MASK CHSR_DERR(3) +#define CHSR_DBERR CHSR_DERR(1) +#define CHSR_DRERR CHSR_DERR(2) +#define CHSR_DOERR CHSR_DERR(3) +#define MITE_FCR(x) (0x40 + MITE_CHAN(x)) /* fifo count */ + +/* common bits for the memory/device/link config registers */ +#define CR_RL(x) (((x) & 0x7) << 21) +#define CR_REQS(x) (((x) & 0x7) << 16) +#define CR_REQS_MASK CR_REQS(7) +#define CR_ASEQ(x) (((x) & 0x3) << 10) +#define CR_ASEQDONT CR_ASEQ(0) +#define CR_ASEQUP CR_ASEQ(1) +#define CR_ASEQDOWN CR_ASEQ(2) +#define CR_ASEQ_MASK CR_ASEQ(3) +#define CR_PSIZE(x) (((x) & 0x3) << 8) +#define CR_PSIZE8 CR_PSIZE(1) +#define CR_PSIZE16 CR_PSIZE(2) +#define CR_PSIZE32 CR_PSIZE(3) +#define CR_PORT(x) (((x) & 0x3) << 6) +#define CR_PORTCPU CR_PORT(0) +#define CR_PORTIO CR_PORT(1) +#define CR_PORTVXI CR_PORT(2) +#define CR_PORTMXI CR_PORT(3) +#define CR_AMDEVICE BIT(0) + +static unsigned int MITE_IODWBSR_1_WSIZE_bits(unsigned int size) +{ + return (ilog2(size) - 1) & 0x1f; +} + +static unsigned int mite_retry_limit(unsigned int retry_limit) +{ + unsigned int value = 0; + + if (retry_limit) + value = 1 + ilog2(retry_limit); + if (value > 0x7) + value = 0x7; + return CR_RL(value); +} + +static unsigned int mite_drq_reqs(unsigned int drq_line) +{ + /* This also works on m-series when using channels (drq_line) 4 or 5. */ + return CR_REQS((drq_line & 0x3) | 0x4); +} + +static unsigned int mite_fifo_size(struct mite *mite, unsigned int channel) +{ + unsigned int fcr_bits = readl(mite->mmio + MITE_FCR(channel)); + unsigned int empty_count = (fcr_bits >> 16) & 0xff; + unsigned int full_count = fcr_bits & 0xff; + + return empty_count + full_count; +} + +static u32 mite_device_bytes_transferred(struct mite_channel *mite_chan) +{ + struct mite *mite = mite_chan->mite; + + return readl(mite->mmio + MITE_DAR(mite_chan->channel)); +} + +/** + * mite_bytes_in_transit() - Returns the number of unread bytes in the fifo. + * @mite_chan: MITE dma channel. + */ +u32 mite_bytes_in_transit(struct mite_channel *mite_chan) +{ + struct mite *mite = mite_chan->mite; + + return readl(mite->mmio + MITE_FCR(mite_chan->channel)) & 0xff; +} +EXPORT_SYMBOL_GPL(mite_bytes_in_transit); + +/* returns lower bound for number of bytes transferred from device to memory */ +static 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); +} + +/* returns upper bound for number of bytes transferred from device to memory */ +static 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; +} + +/* returns lower bound for number of bytes read from memory to device */ +static 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); +} + +/* returns upper bound for number of bytes read from memory to device */ +static 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; +} + +static void 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; + } + + 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) { + comedi_buf_write_free(s, count); + comedi_inc_scan_progress(s, count); + async->events |= COMEDI_CB_BLOCK; + } +} + +static void mite_sync_output_dma(struct mite_channel *mite_chan, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u32 stop_count = cmd->stop_arg * comedi_bytes_per_scan(s); + unsigned int old_alloc_count = async->buf_read_alloc_count; + u32 nbytes_ub, nbytes_lb; + int count; + bool finite_regen = (cmd->stop_src == TRIG_NONE && stop_count != 0); + + /* 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 ((!finite_regen || stop_count > old_alloc_count) && + ((int)(nbytes_ub - old_alloc_count) > 0)) { + dev_warn(s->device->class_dev, "mite: DMA underrun\n"); + async->events |= COMEDI_CB_OVERFLOW; + return; + } + + if (finite_regen) { + /* + * This is a special case where we continuously output a finite + * buffer. In this case, we do not free any of the memory, + * hence we expect that old_alloc_count will reach a maximum of + * stop_count bytes. + */ + return; + } + + count = nbytes_lb - async->buf_read_count; + if (count > 0) { + comedi_buf_read_free(s, count); + async->events |= COMEDI_CB_BLOCK; + } +} + +/** + * mite_sync_dma() - Sync the MITE dma with the COMEDI async buffer. + * @mite_chan: MITE dma channel. + * @s: COMEDI subdevice. + */ +void mite_sync_dma(struct mite_channel *mite_chan, struct comedi_subdevice *s) +{ + if (mite_chan->dir == COMEDI_INPUT) + mite_sync_input_dma(mite_chan, s); + else + mite_sync_output_dma(mite_chan, s); +} +EXPORT_SYMBOL_GPL(mite_sync_dma); + +static unsigned int mite_get_status(struct mite_channel *mite_chan) +{ + struct mite *mite = mite_chan->mite; + unsigned int status; + unsigned long flags; + + spin_lock_irqsave(&mite->lock, flags); + status = readl(mite->mmio + MITE_CHSR(mite_chan->channel)); + if (status & CHSR_DONE) { + mite_chan->done = 1; + writel(CHOR_CLRDONE, + mite->mmio + MITE_CHOR(mite_chan->channel)); + } + spin_unlock_irqrestore(&mite->lock, flags); + return status; +} + +/** + * mite_ack_linkc() - Check and ack the LINKC interrupt, + * @mite_chan: MITE dma channel. + * @s: COMEDI subdevice. + * @sync: flag to force a mite_sync_dma(). + * + * This will also ack the DONE interrupt if active. + */ +void mite_ack_linkc(struct mite_channel *mite_chan, + struct comedi_subdevice *s, + bool sync) +{ + struct mite *mite = mite_chan->mite; + unsigned int status; + + status = mite_get_status(mite_chan); + if (status & CHSR_LINKC) { + writel(CHOR_CLRLC, mite->mmio + MITE_CHOR(mite_chan->channel)); + sync = true; + } + if (sync) + mite_sync_dma(mite_chan, s); + + if (status & CHSR_XFERR) { + dev_err(s->device->class_dev, + "mite: transfer error %08x\n", status); + s->async->events |= COMEDI_CB_ERROR; + } +} +EXPORT_SYMBOL_GPL(mite_ack_linkc); + +/** + * mite_done() - Check is a MITE dma transfer is complete. + * @mite_chan: MITE dma channel. + * + * This will also ack the DONE interrupt if active. + */ +int mite_done(struct mite_channel *mite_chan) +{ + struct mite *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 void mite_dma_reset(struct mite_channel *mite_chan) +{ + writel(CHOR_DMARESET | CHOR_FRESET, + mite_chan->mite->mmio + MITE_CHOR(mite_chan->channel)); +} + +/** + * mite_dma_arm() - Start a MITE dma transfer. + * @mite_chan: MITE dma channel. + */ +void mite_dma_arm(struct mite_channel *mite_chan) +{ + struct mite *mite = mite_chan->mite; + 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(); + spin_lock_irqsave(&mite->lock, flags); + mite_chan->done = 0; + /* arm */ + writel(CHOR_START, mite->mmio + MITE_CHOR(mite_chan->channel)); + spin_unlock_irqrestore(&mite->lock, flags); +} +EXPORT_SYMBOL_GPL(mite_dma_arm); + +/** + * mite_dma_disarm() - Stop a MITE dma transfer. + * @mite_chan: MITE dma channel. + */ +void mite_dma_disarm(struct mite_channel *mite_chan) +{ + struct mite *mite = mite_chan->mite; + + /* disarm */ + writel(CHOR_ABORT, mite->mmio + MITE_CHOR(mite_chan->channel)); +} +EXPORT_SYMBOL_GPL(mite_dma_disarm); + +/** + * mite_prep_dma() - Prepare a MITE dma channel for transfers. + * @mite_chan: MITE dma channel. + * @num_device_bits: device transfer size (8, 16, or 32-bits). + * @num_memory_bits: memory transfer size (8, 16, or 32-bits). + */ +void mite_prep_dma(struct mite_channel *mite_chan, + unsigned int num_device_bits, unsigned int num_memory_bits) +{ + struct mite *mite = mite_chan->mite; + unsigned int chcr, mcr, dcr, lkcr; + + mite_dma_reset(mite_chan); + + /* 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->mmio + MITE_CHCR(mite_chan->channel)); + + /* to/from memory */ + mcr = mite_retry_limit(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->mmio + MITE_MCR(mite_chan->channel)); + + /* from/to device */ + dcr = mite_retry_limit(64) | CR_ASEQUP; + dcr |= CR_PORTIO | CR_AMDEVICE | mite_drq_reqs(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->mmio + MITE_DCR(mite_chan->channel)); + + /* reset the DAR */ + writel(0, mite->mmio + MITE_DAR(mite_chan->channel)); + + /* the link is 32bits */ + lkcr = mite_retry_limit(64) | CR_ASEQUP | CR_PSIZE32; + writel(lkcr, mite->mmio + MITE_LKCR(mite_chan->channel)); + + /* starting address for link chaining */ + writel(mite_chan->ring->dma_addr, + mite->mmio + MITE_LKAR(mite_chan->channel)); +} +EXPORT_SYMBOL_GPL(mite_prep_dma); + +/** + * mite_request_channel_in_range() - Request a MITE dma channel. + * @mite: MITE device. + * @ring: MITE dma ring. + * @min_channel: minimum channel index to use. + * @max_channel: maximum channel index to use. + */ +struct mite_channel *mite_request_channel_in_range(struct mite *mite, + struct mite_ring *ring, + unsigned int min_channel, + unsigned int max_channel) +{ + struct mite_channel *mite_chan = NULL; + unsigned long flags; + int i; + + /* + * 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) { + mite_chan = &mite->channels[i]; + if (!mite_chan->ring) { + mite_chan->ring = ring; + break; + } + mite_chan = NULL; + } + spin_unlock_irqrestore(&mite->lock, flags); + return mite_chan; +} +EXPORT_SYMBOL_GPL(mite_request_channel_in_range); + +/** + * mite_request_channel() - Request a MITE dma channel. + * @mite: MITE device. + * @ring: MITE dma ring. + */ +struct mite_channel *mite_request_channel(struct mite *mite, + struct mite_ring *ring) +{ + return mite_request_channel_in_range(mite, ring, 0, + mite->num_channels - 1); +} +EXPORT_SYMBOL_GPL(mite_request_channel); + +/** + * mite_release_channel() - Release a MITE dma channel. + * @mite_chan: MITE dma channel. + */ +void mite_release_channel(struct mite_channel *mite_chan) +{ + struct mite *mite = mite_chan->mite; + unsigned long flags; + + /* spin lock to prevent races with mite_request_channel */ + spin_lock_irqsave(&mite->lock, flags); + if (mite_chan->ring) { + 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->mmio + MITE_CHCR(mite_chan->channel)); + mite_chan->ring = NULL; + } + spin_unlock_irqrestore(&mite->lock, flags); +} +EXPORT_SYMBOL_GPL(mite_release_channel); + +/** + * mite_init_ring_descriptors() - Initialize a MITE dma ring descriptors. + * @ring: MITE dma ring. + * @s: COMEDI subdevice. + * @nbytes: the size of the dma ring (in bytes). + * + * Initializes the ring buffer descriptors to provide correct DMA transfer + * links to the exact amount of memory required. When the ring buffer is + * allocated by mite_buf_change(), the default is to initialize the ring + * to refer to the entire DMA data buffer. A command may call this function + * later to re-initialize and shorten the amount of memory that will be + * transferred. + */ +int mite_init_ring_descriptors(struct mite_ring *ring, + struct comedi_subdevice *s, + unsigned int nbytes) +{ + struct comedi_async *async = s->async; + struct mite_dma_desc *desc = NULL; + unsigned int n_full_links = nbytes >> PAGE_SHIFT; + unsigned int remainder = nbytes % PAGE_SIZE; + int i; + + dev_dbg(s->device->class_dev, + "mite: init ring buffer to %u bytes\n", nbytes); + + if ((n_full_links + (remainder > 0 ? 1 : 0)) > ring->n_links) { + dev_err(s->device->class_dev, + "mite: ring buffer too small for requested init\n"); + return -ENOMEM; + } + + /* We set the descriptors for all full links. */ + for (i = 0; i < n_full_links; ++i) { + desc = &ring->descs[i]; + desc->count = cpu_to_le32(PAGE_SIZE); + desc->addr = cpu_to_le32(async->buf_map->page_list[i].dma_addr); + desc->next = cpu_to_le32(ring->dma_addr + + (i + 1) * sizeof(*desc)); + } + + /* the last link is either a remainder or was a full link. */ + if (remainder > 0) { + desc = &ring->descs[i]; + /* set the lesser count for the remainder link */ + desc->count = cpu_to_le32(remainder); + desc->addr = cpu_to_le32(async->buf_map->page_list[i].dma_addr); + } + + /* Assign the last link->next to point back to the head of the list. */ + desc->next = cpu_to_le32(ring->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_init_ring_descriptors); + +static void mite_free_dma_descs(struct mite_ring *ring) +{ + struct mite_dma_desc *descs = ring->descs; + + if (descs) { + dma_free_coherent(ring->hw_dev, + ring->n_links * sizeof(*descs), + descs, ring->dma_addr); + ring->descs = NULL; + ring->dma_addr = 0; + ring->n_links = 0; + } +} + +/** + * mite_buf_change() - COMEDI subdevice (*buf_change) for a MITE dma ring. + * @ring: MITE dma ring. + * @s: COMEDI subdevice. + */ +int mite_buf_change(struct mite_ring *ring, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct mite_dma_desc *descs; + unsigned int n_links; + + mite_free_dma_descs(ring); + + if (async->prealloc_bufsz == 0) + return 0; + + n_links = async->prealloc_bufsz >> PAGE_SHIFT; + + descs = dma_alloc_coherent(ring->hw_dev, + n_links * sizeof(*descs), + &ring->dma_addr, GFP_KERNEL); + if (!descs) { + dev_err(s->device->class_dev, + "mite: ring buffer allocation failed\n"); + return -ENOMEM; + } + ring->descs = descs; + ring->n_links = n_links; + + return mite_init_ring_descriptors(ring, s, n_links << PAGE_SHIFT); +} +EXPORT_SYMBOL_GPL(mite_buf_change); + +/** + * mite_alloc_ring() - Allocate a MITE dma ring. + * @mite: MITE device. + */ +struct mite_ring *mite_alloc_ring(struct mite *mite) +{ + struct mite_ring *ring; + + ring = kmalloc(sizeof(*ring), GFP_KERNEL); + if (!ring) + return NULL; + ring->hw_dev = get_device(&mite->pcidev->dev); + if (!ring->hw_dev) { + kfree(ring); + return NULL; + } + ring->n_links = 0; + ring->descs = NULL; + ring->dma_addr = 0; + return ring; +} +EXPORT_SYMBOL_GPL(mite_alloc_ring); + +/** + * mite_free_ring() - Free a MITE dma ring and its descriptors. + * @ring: MITE dma ring. + */ +void mite_free_ring(struct mite_ring *ring) +{ + if (ring) { + mite_free_dma_descs(ring); + put_device(ring->hw_dev); + kfree(ring); + } +} +EXPORT_SYMBOL_GPL(mite_free_ring); + +static int mite_setup(struct comedi_device *dev, struct mite *mite, + bool use_win1) +{ + resource_size_t daq_phys_addr; + unsigned long length; + int i; + u32 csigr_bits; + unsigned int unknown_dma_burst_bits; + unsigned int wpdep; + + pci_set_master(mite->pcidev); + + mite->mmio = pci_ioremap_bar(mite->pcidev, 0); + if (!mite->mmio) + return -ENOMEM; + + dev->mmio = pci_ioremap_bar(mite->pcidev, 1); + if (!dev->mmio) + return -ENOMEM; + daq_phys_addr = pci_resource_start(mite->pcidev, 1); + length = pci_resource_len(mite->pcidev, 1); + + if (use_win1) { + writel(0, mite->mmio + MITE_IODWBSR); + dev_dbg(dev->class_dev, + "mite: using I/O Window Base Size register 1\n"); + writel(daq_phys_addr | WENAB | + MITE_IODWBSR_1_WSIZE_bits(length), + mite->mmio + MITE_IODWBSR_1); + writel(0, mite->mmio + MITE_IODWCR_1); + } else { + writel(daq_phys_addr | WENAB, mite->mmio + 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. + * + * 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. + */ + unknown_dma_burst_bits = readl(mite->mmio + MITE_UNKNOWN_DMA_BURST_REG); + unknown_dma_burst_bits |= UNKNOWN_DMA_BURST_ENABLE_BITS; + writel(unknown_dma_burst_bits, mite->mmio + MITE_UNKNOWN_DMA_BURST_REG); + + csigr_bits = readl(mite->mmio + MITE_CSIGR); + mite->num_channels = CSIGR_TO_DMAC(csigr_bits); + if (mite->num_channels > MAX_MITE_DMA_CHANNELS) { + dev_warn(dev->class_dev, + "mite: bug? chip claims to have %i dma channels. Setting to %i.\n", + mite->num_channels, MAX_MITE_DMA_CHANNELS); + mite->num_channels = MAX_MITE_DMA_CHANNELS; + } + + /* get the wpdep bits and convert it to the write port fifo depth */ + wpdep = CSIGR_TO_WPDEP(csigr_bits); + if (wpdep) + wpdep = BIT(wpdep); + + dev_dbg(dev->class_dev, + "mite: version = %i, type = %i, mite mode = %i, interface mode = %i\n", + CSIGR_TO_VER(csigr_bits), CSIGR_TO_TYPE(csigr_bits), + CSIGR_TO_MMODE(csigr_bits), CSIGR_TO_IMODE(csigr_bits)); + dev_dbg(dev->class_dev, + "mite: num channels = %i, write post fifo depth = %i, wins = %i, iowins = %i\n", + CSIGR_TO_DMAC(csigr_bits), wpdep, + CSIGR_TO_WINS(csigr_bits), CSIGR_TO_IOWINS(csigr_bits)); + + for (i = 0; i < mite->num_channels; i++) { + writel(CHOR_DMARESET, mite->mmio + 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->mmio + MITE_CHCR(i)); + } + mite->fifo_size = mite_fifo_size(mite, 0); + dev_dbg(dev->class_dev, "mite: fifo size is %i.\n", mite->fifo_size); + return 0; +} + +/** + * mite_attach() - Allocate and initialize a MITE device for a comedi driver. + * @dev: COMEDI device. + * @use_win1: flag to use I/O Window 1 instead of I/O Window 0. + * + * Called by a COMEDI drivers (*auto_attach). + * + * Returns a pointer to the MITE device on success, or NULL if the MITE cannot + * be allocated or remapped. + */ +struct mite *mite_attach(struct comedi_device *dev, bool use_win1) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct mite *mite; + unsigned int i; + int ret; + + mite = kzalloc(sizeof(*mite), GFP_KERNEL); + if (!mite) + return NULL; + + 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; + } + + ret = mite_setup(dev, mite, use_win1); + if (ret) { + if (mite->mmio) + iounmap(mite->mmio); + kfree(mite); + return NULL; + } + + return mite; +} +EXPORT_SYMBOL_GPL(mite_attach); + +/** + * mite_detach() - Unmap and free a MITE device for a comedi driver. + * @mite: MITE device. + * + * Called by a COMEDI drivers (*detach). + */ +void mite_detach(struct mite *mite) +{ + if (!mite) + return; + + if (mite->mmio) + iounmap(mite->mmio); + + kfree(mite); +} +EXPORT_SYMBOL_GPL(mite_detach); + +static int __init mite_module_init(void) +{ + return 0; +} +module_init(mite_module_init); + +static void __exit mite_module_exit(void) +{ +} +module_exit(mite_module_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi helper for NI Mite PCI interface chip"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/mite.h b/drivers/comedi/drivers/mite.h new file mode 100644 index 000000000..c6c056069 --- /dev/null +++ b/drivers/comedi/drivers/mite.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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> + */ + +#ifndef _MITE_H_ +#define _MITE_H_ + +#include <linux/spinlock.h> + +#define MAX_MITE_DMA_CHANNELS 8 + +struct comedi_device; +struct comedi_subdevice; +struct device; +struct pci_dev; + +struct mite_dma_desc { + __le32 count; + __le32 addr; + __le32 next; + u32 dar; +}; + +struct mite_ring { + struct device *hw_dev; + unsigned int n_links; + struct mite_dma_desc *descs; + dma_addr_t dma_addr; +}; + +struct mite_channel { + struct mite *mite; + unsigned int channel; + int dir; + int done; + struct mite_ring *ring; +}; + +struct mite { + struct pci_dev *pcidev; + void __iomem *mmio; + struct mite_channel channels[MAX_MITE_DMA_CHANNELS]; + int num_channels; + unsigned int fifo_size; + /* protects mite_channel from being released by the driver */ + spinlock_t lock; +}; + +u32 mite_bytes_in_transit(struct mite_channel *mite_chan); + +void mite_sync_dma(struct mite_channel *mite_chan, struct comedi_subdevice *s); +void mite_ack_linkc(struct mite_channel *mite_chan, struct comedi_subdevice *s, + bool sync); +int mite_done(struct mite_channel *mite_chan); + +void mite_dma_arm(struct mite_channel *mite_chan); +void mite_dma_disarm(struct mite_channel *mite_chan); + +void mite_prep_dma(struct mite_channel *mite_chan, + unsigned int num_device_bits, unsigned int num_memory_bits); + +struct mite_channel *mite_request_channel_in_range(struct mite *mite, + struct mite_ring *ring, + unsigned int min_channel, + unsigned int max_channel); +struct mite_channel *mite_request_channel(struct mite *mite, + struct mite_ring *ring); +void mite_release_channel(struct mite_channel *mite_chan); + +int mite_init_ring_descriptors(struct mite_ring *ring, + struct comedi_subdevice *s, unsigned int nbytes); +int mite_buf_change(struct mite_ring *ring, struct comedi_subdevice *s); + +struct mite_ring *mite_alloc_ring(struct mite *mite); +void mite_free_ring(struct mite_ring *ring); + +struct mite *mite_attach(struct comedi_device *dev, bool use_win1); +void mite_detach(struct mite *mite); + +/* + * Mite registers (used outside of the mite driver) + */ +#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size */ +#define MITE_IODWBSR_1 0xc4 /* IO Device Window1 Base Size */ +#define WENAB BIT(7) /* window enable */ +#define MITE_IODWCR_1 0xf4 + +#endif diff --git a/drivers/comedi/drivers/mpc624.c b/drivers/comedi/drivers/mpc624.c new file mode 100644 index 000000000..9e51ff528 --- /dev/null +++ b/drivers/comedi/drivers/mpc624.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/delay.h> + +/* Offsets of different ports */ +#define MPC624_MASTER_CONTROL 0 /* not used */ +#define MPC624_GNMUXCH 1 /* Gain, Mux, Channel of ADC */ +#define MPC624_ADC 2 /* read/write to/from ADC */ +#define MPC624_EE 3 /* read/write to/from serial EEPROM via I2C */ +#define MPC624_LEDS 4 /* write to LEDs */ +#define MPC624_DIO 5 /* read/write to/from digital I/O ports */ +#define MPC624_IRQ_MASK 6 /* IRQ masking enable/disable */ + +/* Register bits' names */ +#define MPC624_ADBUSY BIT(5) +#define MPC624_ADSDO BIT(4) +#define MPC624_ADFO BIT(3) +#define MPC624_ADCS BIT(2) +#define MPC624_ADSCK BIT(1) +#define MPC624_ADSDI BIT(0) + +/* 32-bit output value bits' names */ +#define MPC624_EOC_BIT BIT(31) +#define MPC624_DMY_BIT BIT(30) +#define MPC624_SGN_BIT BIT(29) + +/* SDI Speed/Resolution Programming bits */ +#define MPC624_OSR(x) (((x) & 0x1f) << 27) +#define MPC624_SPEED_3_52_KHZ MPC624_OSR(0x11) +#define MPC624_SPEED_1_76_KHZ MPC624_OSR(0x12) +#define MPC624_SPEED_880_HZ MPC624_OSR(0x13) +#define MPC624_SPEED_440_HZ MPC624_OSR(0x14) +#define MPC624_SPEED_220_HZ MPC624_OSR(0x15) +#define MPC624_SPEED_110_HZ MPC624_OSR(0x16) +#define MPC624_SPEED_55_HZ MPC624_OSR(0x17) +#define MPC624_SPEED_27_5_HZ MPC624_OSR(0x18) +#define MPC624_SPEED_13_75_HZ MPC624_OSR(0x19) +#define MPC624_SPEED_6_875_HZ MPC624_OSR(0x1f) + +struct mpc624_private { + unsigned int ai_speed; +}; + +/* -------------------------------------------------------------------------- */ +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 unsigned int mpc624_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct mpc624_private *devpriv = dev->private; + unsigned int data_out = devpriv->ai_speed; + unsigned int data_in = 0; + unsigned int bit; + int i; + + /* Start reading data */ + udelay(1); + for (i = 0; i < 32; i++) { + /* Set the clock low */ + outb(0, dev->iobase + MPC624_ADC); + udelay(1); + + /* Set the ADSDI line for the next bit (send to MPC624) */ + bit = (data_out & BIT(31)) ? MPC624_ADSDI : 0; + outb(bit, dev->iobase + MPC624_ADC); + udelay(1); + + /* Set the clock high */ + outb(MPC624_ADSCK | bit, dev->iobase + MPC624_ADC); + udelay(1); + + /* Read ADSDO on high clock (receive from MPC624) */ + 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 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!"); + if (data_in & MPC624_DMY_BIT) + dev_dbg(dev->class_dev, "DMY bit is set!"); + + if (data_in & MPC624_SGN_BIT) { + /* + * Voltage is positive + * + * comedi operates on unsigned numbers, so mask off EOC + * and DMY and don't clear the SGN bit + */ + data_in &= 0x3fffffff; + } 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; + /* clear EOC and DMY bits */ + data_in &= ~(MPC624_EOC_BIT | MPC624_DMY_BIT); + data_in = 0x20000000 - data_in; + } + return data_in; +} + +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_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + int i; + + /* + * WARNING: + * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc + */ + outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH); + + for (i = 0; i < insn->n; i++) { + /* 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; + + data[i] = mpc624_ai_get_sample(dev, s); + } + + return insn->n; +} + +static int mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct mpc624_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + switch (it->options[1]) { + case 0: + devpriv->ai_speed = MPC624_SPEED_3_52_KHZ; + break; + case 1: + devpriv->ai_speed = MPC624_SPEED_1_76_KHZ; + break; + case 2: + devpriv->ai_speed = MPC624_SPEED_880_HZ; + break; + case 3: + devpriv->ai_speed = MPC624_SPEED_440_HZ; + break; + case 4: + devpriv->ai_speed = MPC624_SPEED_220_HZ; + break; + case 5: + devpriv->ai_speed = MPC624_SPEED_110_HZ; + break; + case 6: + devpriv->ai_speed = MPC624_SPEED_55_HZ; + break; + case 7: + devpriv->ai_speed = MPC624_SPEED_27_5_HZ; + break; + case 8: + devpriv->ai_speed = MPC624_SPEED_13_75_HZ; + break; + case 9: + devpriv->ai_speed = MPC624_SPEED_6_875_HZ; + break; + default: + devpriv->ai_speed = MPC624_SPEED_3_52_KHZ; + } + + 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_DIFF; + s->n_chan = 4; + s->maxdata = 0x3fffffff; + s->range_table = (it->options[1] == 0) ? &range_mpc624_bipolar1 + : &range_mpc624_bipolar10; + s->insn_read = mpc624_ai_insn_read; + + 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Micro/sys MPC-624 PC/104 board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/multiq3.c b/drivers/comedi/drivers/multiq3.c new file mode 100644 index 000000000..07ff5383d --- /dev/null +++ b/drivers/comedi/drivers/multiq3.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: multiq3 + * Description: Quanser Consulting MultiQ-3 + * Devices: [Quanser Consulting] MultiQ-3 (multiq3) + * Author: Anders Blomdell <anders.blomdell@control.lth.se> + * Status: works + * + * Configuration Options: + * [0] - I/O port base address + * [1] - IRQ (not used) + * [2] - Number of optional encoder chips installed on board + * 0 = none + * 1 = 2 inputs (Model -2E) + * 2 = 4 inputs (Model -4E) + * 3 = 6 inputs (Model -6E) + * 4 = 8 inputs (Model -8E) + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> + +/* + * Register map + */ +#define MULTIQ3_DI_REG 0x00 +#define MULTIQ3_DO_REG 0x00 +#define MULTIQ3_AO_REG 0x02 +#define MULTIQ3_AI_REG 0x04 +#define MULTIQ3_AI_CONV_REG 0x04 +#define MULTIQ3_STATUS_REG 0x06 +#define MULTIQ3_STATUS_EOC BIT(3) +#define MULTIQ3_STATUS_EOC_I BIT(4) +#define MULTIQ3_CTRL_REG 0x06 +#define MULTIQ3_CTRL_AO_CHAN(x) (((x) & 0x7) << 0) +#define MULTIQ3_CTRL_RC(x) (((x) & 0x3) << 0) +#define MULTIQ3_CTRL_AI_CHAN(x) (((x) & 0x7) << 3) +#define MULTIQ3_CTRL_E_CHAN(x) (((x) & 0x7) << 3) +#define MULTIQ3_CTRL_EN BIT(6) +#define MULTIQ3_CTRL_AZ BIT(7) +#define MULTIQ3_CTRL_CAL BIT(8) +#define MULTIQ3_CTRL_SH BIT(9) +#define MULTIQ3_CTRL_CLK BIT(10) +#define MULTIQ3_CTRL_LD (3 << 11) +#define MULTIQ3_CLK_REG 0x08 +#define MULTIQ3_ENC_DATA_REG 0x0c +#define MULTIQ3_ENC_CTRL_REG 0x0e + +/* + * Encoder chip commands (from the programming manual) + */ +#define MULTIQ3_CLOCK_DATA 0x00 /* FCK frequency divider */ +#define MULTIQ3_CLOCK_SETUP 0x18 /* xfer PR0 to PSC */ +#define MULTIQ3_INPUT_SETUP 0x41 /* enable inputs A and B */ +#define MULTIQ3_QUAD_X4 0x38 /* quadrature */ +#define MULTIQ3_BP_RESET 0x01 /* reset byte pointer */ +#define MULTIQ3_CNTR_RESET 0x02 /* reset counter */ +#define MULTIQ3_TRSFRPR_CTR 0x08 /* xfre preset reg to counter */ +#define MULTIQ3_TRSFRCNTR_OL 0x10 /* xfer CNTR to OL (x and y) */ +#define MULTIQ3_EFLAG_RESET 0x06 /* reset E bit of flag reg */ + +static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits) +{ + /* + * According to the programming manual, the SH and CLK bits should + * be kept high at all times. + */ + outw(MULTIQ3_CTRL_SH | MULTIQ3_CTRL_CLK | bits, + dev->iobase + MULTIQ3_CTRL_REG); +} + +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_REG); + 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) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int ret; + int i; + + multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_AI_CHAN(chan)); + + ret = comedi_timeout(dev, s, insn, multiq3_ai_status, + MULTIQ3_STATUS_EOC); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + outw(0, dev->iobase + MULTIQ3_AI_CONV_REG); + + ret = comedi_timeout(dev, s, insn, multiq3_ai_status, + MULTIQ3_STATUS_EOC_I); + if (ret) + return ret; + + /* get a 16-bit sample; mask it to the subdevice resolution */ + val = inb(dev->iobase + MULTIQ3_AI_REG) << 8; + val |= inb(dev->iobase + MULTIQ3_AI_REG); + val &= s->maxdata; + + /* munge the 2's complement value to offset binary */ + data[i] = comedi_offset_munge(s, val); + } + + return insn->n; +} + +static int multiq3_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + multiq3_set_ctrl(dev, MULTIQ3_CTRL_LD | + MULTIQ3_CTRL_AO_CHAN(chan)); + outw(val, dev->iobase + MULTIQ3_AO_REG); + multiq3_set_ctrl(dev, 0); + } + s->readback[chan] = val; + + return insn->n; +} + +static int multiq3_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = inw(dev->iobase + MULTIQ3_DI_REG); + + 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_DO_REG); + + 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) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val; + int i; + + for (i = 0; i < insn->n; i++) { + /* select encoder channel */ + multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | + MULTIQ3_CTRL_E_CHAN(chan)); + + /* reset the byte pointer */ + outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); + + /* latch the data */ + outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CTRL_REG); + + /* read the 24-bit encoder data (lsb/mid/msb) */ + val = inb(dev->iobase + MULTIQ3_ENC_DATA_REG); + val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 8); + val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 16); + + /* + * Munge the data so that the reset value is in the middle + * of the maxdata range, i.e.: + * + * real value comedi value + * 0xffffff 0x7fffff 1 negative count + * 0x000000 0x800000 reset value + * 0x000001 0x800001 1 positive count + * + * It's possible for the 24-bit counter to overflow but it + * would normally take _quite_ a few turns. A 2000 line + * encoder in quadrature results in 8000 counts/rev. So about + * 1048 turns in either direction can be measured without + * an overflow. + */ + data[i] = (val + ((s->maxdata + 1) >> 1)) & s->maxdata; + } + + return insn->n; +} + +static void multiq3_encoder_reset(struct comedi_device *dev, + unsigned int chan) +{ + multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_E_CHAN(chan)); + outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); + outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); + outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA_REG); + outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG); + outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG); + outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CTRL_REG); + outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); +} + +static int multiq3_encoder_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case INSN_CONFIG_RESET: + multiq3_encoder_reset(dev, chan); + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int multiq3_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + int i; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + 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; + s->n_chan = 8; + s->maxdata = 0x1fff; + s->range_table = &range_bipolar5; + s->insn_read = multiq3_ai_insn_read; + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 0x0fff; + s->range_table = &range_bipolar5; + s->insn_write = multiq3_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = multiq3_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 = multiq3_do_insn_bits; + + /* Encoder (Counter) subdevice */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = it->options[2] * 2; + s->maxdata = 0x00ffffff; + s->range_table = &range_unknown; + s->insn_read = multiq3_encoder_insn_read; + s->insn_config = multiq3_encoder_insn_config; + + for (i = 0; i < s->n_chan; i++) + multiq3_encoder_reset(dev, i); + + 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Quanser Consulting MultiQ-3 board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_6527.c b/drivers/comedi/drivers/ni_6527.c new file mode 100644 index 000000000..ac5820085 --- /dev/null +++ b/drivers/comedi/drivers/ni_6527.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: ni_6527 + * Description: National Instruments 6527 + * Devices: [National Instruments] PCI-6527 (pci-6527), PXI-6527 (pxi-6527) + * Author: David A. Schleef <ds@schleef.org> + * Updated: Sat, 25 Jan 2003 13:24:40 -0800 + * Status: works + * + * Configuration Options: not applicable, uses PCI auto config + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI BAR1 - Register memory map + * + * Manuals (available from ftp://ftp.natinst.com/support/manuals) + * 370106b.pdf 6527 Register Level Programmer Manual + */ +#define NI6527_DI_REG(x) (0x00 + (x)) +#define NI6527_DO_REG(x) (0x03 + (x)) +#define NI6527_ID_REG 0x06 +#define NI6527_CLR_REG 0x07 +#define NI6527_CLR_EDGE BIT(3) +#define NI6527_CLR_OVERFLOW BIT(2) +#define NI6527_CLR_FILT BIT(1) +#define NI6527_CLR_INTERVAL BIT(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 BIT(2) +#define NI6527_STATUS_OVERFLOW BIT(1) +#define NI6527_STATUS_EDGE BIT(0) +#define NI6527_CTRL_REG 0x15 +#define NI6527_CTRL_FALLING BIT(4) +#define NI6527_CTRL_RISING BIT(3) +#define NI6527_CTRL_IRQ BIT(2) +#define NI6527_CTRL_OVERFLOW BIT(1) +#define NI6527_CTRL_EDGE BIT(0) +#define NI6527_CTRL_DISABLE_IRQS 0 +#define NI6527_CTRL_ENABLE_IRQS (NI6527_CTRL_FALLING | \ + NI6527_CTRL_RISING | \ + NI6527_CTRL_IRQ | NI6527_CTRL_EDGE) +#define NI6527_RISING_EDGE_REG(x) (0x18 + (x)) +#define NI6527_FALLING_EDGE_REG(x) (0x20 + (x)) + +enum ni6527_boardid { + BOARD_PCI6527, + BOARD_PXI6527, +}; + +struct ni6527_board { + const char *name; +}; + +static const struct ni6527_board ni6527_boards[] = { + [BOARD_PCI6527] = { + .name = "pci-6527", + }, + [BOARD_PXI6527] = { + .name = "pxi-6527", + }, +}; + +struct ni6527_private { + unsigned int filter_interval; + unsigned int filter_enable; +}; + +static void ni6527_set_filter_interval(struct comedi_device *dev, + unsigned int val) +{ + struct ni6527_private *devpriv = dev->private; + + if (val != devpriv->filter_interval) { + writeb(val & 0xff, dev->mmio + NI6527_FILT_INTERVAL_REG(0)); + writeb((val >> 8) & 0xff, + dev->mmio + NI6527_FILT_INTERVAL_REG(1)); + writeb((val >> 16) & 0x0f, + dev->mmio + NI6527_FILT_INTERVAL_REG(2)); + + writeb(NI6527_CLR_INTERVAL, dev->mmio + NI6527_CLR_REG); + + devpriv->filter_interval = val; + } +} + +static void ni6527_set_filter_enable(struct comedi_device *dev, + unsigned int val) +{ + writeb(val & 0xff, dev->mmio + NI6527_FILT_ENA_REG(0)); + writeb((val >> 8) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(1)); + writeb((val >> 16) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(2)); +} + +static int ni6527_di_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni6527_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int interval; + + switch (data[0]) { + case INSN_CONFIG_FILTER: + /* + * The deglitch filter interval is specified in nanoseconds. + * The hardware supports intervals in 200ns increments. Round + * the user values up and return the actual interval. + */ + interval = (data[1] + 100) / 200; + data[1] = interval * 200; + + if (interval) { + ni6527_set_filter_interval(dev, interval); + devpriv->filter_enable |= 1 << chan; + } else { + devpriv->filter_enable &= ~(1 << chan); + } + ni6527_set_filter_enable(dev, devpriv->filter_enable); + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int ni6527_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int val; + + val = readb(dev->mmio + NI6527_DI_REG(0)); + val |= (readb(dev->mmio + NI6527_DI_REG(1)) << 8); + val |= (readb(dev->mmio + NI6527_DI_REG(2)) << 16); + + data[1] = val; + + return insn->n; +} + +static int ni6527_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + + mask = comedi_dio_update_state(s, data); + if (mask) { + /* Outputs are inverted */ + unsigned int val = s->state ^ 0xffffff; + + if (mask & 0x0000ff) + writeb(val & 0xff, dev->mmio + NI6527_DO_REG(0)); + if (mask & 0x00ff00) + writeb((val >> 8) & 0xff, + dev->mmio + NI6527_DO_REG(1)); + if (mask & 0xff0000) + writeb((val >> 16) & 0xff, + dev->mmio + NI6527_DO_REG(2)); + } + + data[1] = s->state; + + return insn->n; +} + +static irqreturn_t ni6527_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + + status = readb(dev->mmio + NI6527_STATUS_REG); + if (!(status & NI6527_STATUS_IRQ)) + return IRQ_NONE; + + if (status & NI6527_STATUS_EDGE) { + unsigned short val = 0; + + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + } + + writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG); + + return IRQ_HANDLED; +} + +static int ni6527_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int ni6527_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG); + writeb(NI6527_CTRL_ENABLE_IRQS, dev->mmio + NI6527_CTRL_REG); + + return 0; +} + +static int ni6527_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG); + + return 0; +} + +static int ni6527_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = 0; + return insn->n; +} + +static void ni6527_set_edge_detection(struct comedi_device *dev, + unsigned int mask, + unsigned int rising, + unsigned int falling) +{ + unsigned int i; + + rising &= mask; + falling &= mask; + for (i = 0; i < 2; i++) { + if (mask & 0xff) { + if (~mask & 0xff) { + /* preserve rising-edge detection channels */ + rising |= readb(dev->mmio + + NI6527_RISING_EDGE_REG(i)) & + (~mask & 0xff); + /* preserve falling-edge detection channels */ + falling |= readb(dev->mmio + + NI6527_FALLING_EDGE_REG(i)) & + (~mask & 0xff); + } + /* update rising-edge detection channels */ + writeb(rising & 0xff, + dev->mmio + NI6527_RISING_EDGE_REG(i)); + /* update falling-edge detection channels */ + writeb(falling & 0xff, + dev->mmio + NI6527_FALLING_EDGE_REG(i)); + } + rising >>= 8; + falling >>= 8; + mask >>= 8; + } +} + +static int ni6527_intr_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask = 0xffffffff; + unsigned int rising, falling, shift; + + switch (data[0]) { + case INSN_CONFIG_CHANGE_NOTIFY: + /* check_insn_config_length() does not check this instruction */ + if (insn->n != 3) + return -EINVAL; + rising = data[1]; + falling = data[2]; + ni6527_set_edge_detection(dev, mask, rising, falling); + break; + case INSN_CONFIG_DIGITAL_TRIG: + /* check trigger number */ + if (data[1] != 0) + return -EINVAL; + /* check digital trigger operation */ + switch (data[2]) { + case COMEDI_DIGITAL_TRIG_DISABLE: + rising = 0; + falling = 0; + break; + case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: + /* check shift amount */ + shift = data[3]; + if (shift >= 32) { + mask = 0; + rising = 0; + falling = 0; + } else { + mask <<= shift; + rising = data[4] << shift; + falling = data[5] << shift; + } + break; + default: + return -EINVAL; + } + ni6527_set_edge_detection(dev, mask, rising, falling); + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static void ni6527_reset(struct comedi_device *dev) +{ + /* disable deglitch filters on all channels */ + ni6527_set_filter_enable(dev, 0); + + /* disable edge detection */ + ni6527_set_edge_detection(dev, 0xffffffff, 0, 0); + + writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT, + dev->mmio + NI6527_CLR_REG); + writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG); +} + +static int ni6527_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct ni6527_board *board = NULL; + struct ni6527_private *devpriv; + struct comedi_subdevice *s; + int ret; + + if (context < ARRAY_SIZE(ni6527_boards)) + board = &ni6527_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 1); + if (!dev->mmio) + return -ENOMEM; + + /* make sure this is actually a 6527 device */ + if (readb(dev->mmio + NI6527_ID_REG) != 0x27) + return -ENODEV; + + ni6527_reset(dev); + + ret = request_irq(pcidev->irq, ni6527_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + + ret = comedi_alloc_subdevices(dev, 3); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 24; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = ni6527_di_insn_config; + s->insn_bits = ni6527_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni6527_do_insn_bits; + + /* Edge detection interrupt subdevice */ + s = &dev->subdevices[2]; + if (dev->irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = ni6527_intr_insn_config; + s->insn_bits = ni6527_intr_insn_bits; + s->len_chanlist = 1; + s->do_cmdtest = ni6527_intr_cmdtest; + s->do_cmd = ni6527_intr_cmd; + s->cancel = ni6527_intr_cancel; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static void ni6527_detach(struct comedi_device *dev) +{ + if (dev->mmio) + ni6527_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver ni6527_driver = { + .driver_name = "ni_6527", + .module = THIS_MODULE, + .auto_attach = ni6527_auto_attach, + .detach = ni6527_detach, +}; + +static int ni6527_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni6527_driver, id->driver_data); +} + +static const struct pci_device_id ni6527_pci_table[] = { + { PCI_VDEVICE(NI, 0x2b10), BOARD_PXI6527 }, + { PCI_VDEVICE(NI, 0x2b20), BOARD_PCI6527 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ni6527_pci_table); + +static struct pci_driver ni6527_pci_driver = { + .name = "ni_6527", + .id_table = ni6527_pci_table, + .probe = ni6527_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(ni6527_driver, ni6527_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for National Instruments PCI-6527"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_65xx.c b/drivers/comedi/drivers/ni_65xx.c new file mode 100644 index 000000000..58334de3b --- /dev/null +++ b/drivers/comedi/drivers/ni_65xx.c @@ -0,0 +1,822 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ni_65xx.c + * Comedi driver for National Instruments PCI-65xx static dio boards + * + * Copyright (C) 2006 Jon Grierson <jd@renko.co.uk> + * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org> + */ + +/* + * Driver: ni_65xx + * Description: National Instruments 65xx static dio boards + * Author: Jon Grierson <jd@renko.co.uk>, + * Frank Mori Hess <fmhess@users.sourceforge.net> + * Status: testing + * Devices: [National Instruments] PCI-6509 (pci-6509), PXI-6509 (pxi-6509), + * PCI-6510 (pci-6510), PCI-6511 (pci-6511), PXI-6511 (pxi-6511), + * PCI-6512 (pci-6512), PXI-6512 (pxi-6512), PCI-6513 (pci-6513), + * PXI-6513 (pxi-6513), PCI-6514 (pci-6514), PXI-6514 (pxi-6514), + * PCI-6515 (pxi-6515), PXI-6515 (pxi-6515), PCI-6516 (pci-6516), + * PCI-6517 (pci-6517), PCI-6518 (pci-6518), PCI-6519 (pci-6519), + * PCI-6520 (pci-6520), PCI-6521 (pci-6521), PXI-6521 (pxi-6521), + * PCI-6528 (pci-6528), PXI-6528 (pxi-6528) + * Updated: Mon, 21 Jul 2014 12:49:58 +0000 + * + * Configuration Options: not applicable, uses PCI auto config + * + * Based on the PCI-6527 driver by ds. + * The interrupt subdevice (subdevice 3) is probably broken for all + * boards except maybe the 6514. + * + * This driver previously inverted the outputs on PCI-6513 through to + * PCI-6519 and on PXI-6513 through to PXI-6515. It no longer inverts + * outputs on those cards by default as it didn't make much sense. If + * you require the outputs to be inverted on those cards for legacy + * reasons, set the module parameter "legacy_invert_outputs=true" when + * loading the module, or set "ni_65xx.legacy_invert_outputs=true" on + * the kernel command line if the driver is built in to the kernel. + */ + +/* + * Manuals (available from ftp://ftp.natinst.com/support/manuals) + * + * 370106b.pdf 6514 Register Level Programmer Manual + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +/* + * PCI BAR1 Register Map + */ + +/* Non-recurring Registers (8-bit except where noted) */ +#define NI_65XX_ID_REG 0x00 +#define NI_65XX_CLR_REG 0x01 +#define NI_65XX_CLR_WDOG_INT BIT(6) +#define NI_65XX_CLR_WDOG_PING BIT(5) +#define NI_65XX_CLR_WDOG_EXP BIT(4) +#define NI_65XX_CLR_EDGE_INT BIT(3) +#define NI_65XX_CLR_OVERFLOW_INT BIT(2) +#define NI_65XX_STATUS_REG 0x02 +#define NI_65XX_STATUS_WDOG_INT BIT(5) +#define NI_65XX_STATUS_FALL_EDGE BIT(4) +#define NI_65XX_STATUS_RISE_EDGE BIT(3) +#define NI_65XX_STATUS_INT BIT(2) +#define NI_65XX_STATUS_OVERFLOW_INT BIT(1) +#define NI_65XX_STATUS_EDGE_INT BIT(0) +#define NI_65XX_CTRL_REG 0x03 +#define NI_65XX_CTRL_WDOG_ENA BIT(5) +#define NI_65XX_CTRL_FALL_EDGE_ENA BIT(4) +#define NI_65XX_CTRL_RISE_EDGE_ENA BIT(3) +#define NI_65XX_CTRL_INT_ENA BIT(2) +#define NI_65XX_CTRL_OVERFLOW_ENA BIT(1) +#define NI_65XX_CTRL_EDGE_ENA BIT(0) +#define NI_65XX_REV_REG 0x04 /* 32-bit */ +#define NI_65XX_FILTER_REG 0x08 /* 32-bit */ +#define NI_65XX_RTSI_ROUTE_REG 0x0c /* 16-bit */ +#define NI_65XX_RTSI_EDGE_REG 0x0e /* 16-bit */ +#define NI_65XX_RTSI_WDOG_REG 0x10 /* 16-bit */ +#define NI_65XX_RTSI_TRIG_REG 0x12 /* 16-bit */ +#define NI_65XX_AUTO_CLK_SEL_REG 0x14 /* PXI-6528 only */ +#define NI_65XX_AUTO_CLK_SEL_STATUS BIT(1) +#define NI_65XX_AUTO_CLK_SEL_DISABLE BIT(0) +#define NI_65XX_WDOG_CTRL_REG 0x15 +#define NI_65XX_WDOG_CTRL_ENA BIT(0) +#define NI_65XX_RTSI_CFG_REG 0x16 +#define NI_65XX_RTSI_CFG_RISE_SENSE BIT(2) +#define NI_65XX_RTSI_CFG_FALL_SENSE BIT(1) +#define NI_65XX_RTSI_CFG_SYNC_DETECT BIT(0) +#define NI_65XX_WDOG_STATUS_REG 0x17 +#define NI_65XX_WDOG_STATUS_EXP BIT(0) +#define NI_65XX_WDOG_INTERVAL_REG 0x18 /* 32-bit */ + +/* Recurring port registers (8-bit) */ +#define NI_65XX_PORT(x) ((x) * 0x10) +#define NI_65XX_IO_DATA_REG(x) (0x40 + NI_65XX_PORT(x)) +#define NI_65XX_IO_SEL_REG(x) (0x41 + NI_65XX_PORT(x)) +#define NI_65XX_IO_SEL_OUTPUT 0 +#define NI_65XX_IO_SEL_INPUT BIT(0) +#define NI_65XX_RISE_EDGE_ENA_REG(x) (0x42 + NI_65XX_PORT(x)) +#define NI_65XX_FALL_EDGE_ENA_REG(x) (0x43 + NI_65XX_PORT(x)) +#define NI_65XX_FILTER_ENA(x) (0x44 + NI_65XX_PORT(x)) +#define NI_65XX_WDOG_HIZ_REG(x) (0x46 + NI_65XX_PORT(x)) +#define NI_65XX_WDOG_ENA(x) (0x47 + NI_65XX_PORT(x)) +#define NI_65XX_WDOG_HI_LO_REG(x) (0x48 + NI_65XX_PORT(x)) +#define NI_65XX_RTSI_ENA(x) (0x49 + NI_65XX_PORT(x)) + +#define NI_65XX_PORT_TO_CHAN(x) ((x) * 8) +#define NI_65XX_CHAN_TO_PORT(x) ((x) / 8) +#define NI_65XX_CHAN_TO_MASK(x) (1 << ((x) % 8)) + +enum ni_65xx_boardid { + BOARD_PCI6509, + BOARD_PXI6509, + BOARD_PCI6510, + BOARD_PCI6511, + BOARD_PXI6511, + BOARD_PCI6512, + BOARD_PXI6512, + BOARD_PCI6513, + BOARD_PXI6513, + BOARD_PCI6514, + BOARD_PXI6514, + BOARD_PCI6515, + BOARD_PXI6515, + BOARD_PCI6516, + BOARD_PCI6517, + BOARD_PCI6518, + BOARD_PCI6519, + BOARD_PCI6520, + BOARD_PCI6521, + BOARD_PXI6521, + BOARD_PCI6528, + BOARD_PXI6528, +}; + +struct ni_65xx_board { + const char *name; + unsigned int num_dio_ports; + unsigned int num_di_ports; + unsigned int num_do_ports; + unsigned int legacy_invert:1; +}; + +static const struct ni_65xx_board ni_65xx_boards[] = { + [BOARD_PCI6509] = { + .name = "pci-6509", + .num_dio_ports = 12, + }, + [BOARD_PXI6509] = { + .name = "pxi-6509", + .num_dio_ports = 12, + }, + [BOARD_PCI6510] = { + .name = "pci-6510", + .num_di_ports = 4, + }, + [BOARD_PCI6511] = { + .name = "pci-6511", + .num_di_ports = 8, + }, + [BOARD_PXI6511] = { + .name = "pxi-6511", + .num_di_ports = 8, + }, + [BOARD_PCI6512] = { + .name = "pci-6512", + .num_do_ports = 8, + }, + [BOARD_PXI6512] = { + .name = "pxi-6512", + .num_do_ports = 8, + }, + [BOARD_PCI6513] = { + .name = "pci-6513", + .num_do_ports = 8, + .legacy_invert = 1, + }, + [BOARD_PXI6513] = { + .name = "pxi-6513", + .num_do_ports = 8, + .legacy_invert = 1, + }, + [BOARD_PCI6514] = { + .name = "pci-6514", + .num_di_ports = 4, + .num_do_ports = 4, + .legacy_invert = 1, + }, + [BOARD_PXI6514] = { + .name = "pxi-6514", + .num_di_ports = 4, + .num_do_ports = 4, + .legacy_invert = 1, + }, + [BOARD_PCI6515] = { + .name = "pci-6515", + .num_di_ports = 4, + .num_do_ports = 4, + .legacy_invert = 1, + }, + [BOARD_PXI6515] = { + .name = "pxi-6515", + .num_di_ports = 4, + .num_do_ports = 4, + .legacy_invert = 1, + }, + [BOARD_PCI6516] = { + .name = "pci-6516", + .num_do_ports = 4, + .legacy_invert = 1, + }, + [BOARD_PCI6517] = { + .name = "pci-6517", + .num_do_ports = 4, + .legacy_invert = 1, + }, + [BOARD_PCI6518] = { + .name = "pci-6518", + .num_di_ports = 2, + .num_do_ports = 2, + .legacy_invert = 1, + }, + [BOARD_PCI6519] = { + .name = "pci-6519", + .num_di_ports = 2, + .num_do_ports = 2, + .legacy_invert = 1, + }, + [BOARD_PCI6520] = { + .name = "pci-6520", + .num_di_ports = 1, + .num_do_ports = 1, + }, + [BOARD_PCI6521] = { + .name = "pci-6521", + .num_di_ports = 1, + .num_do_ports = 1, + }, + [BOARD_PXI6521] = { + .name = "pxi-6521", + .num_di_ports = 1, + .num_do_ports = 1, + }, + [BOARD_PCI6528] = { + .name = "pci-6528", + .num_di_ports = 3, + .num_do_ports = 3, + }, + [BOARD_PXI6528] = { + .name = "pxi-6528", + .num_di_ports = 3, + .num_do_ports = 3, + }, +}; + +static bool ni_65xx_legacy_invert_outputs; +module_param_named(legacy_invert_outputs, ni_65xx_legacy_invert_outputs, + bool, 0444); +MODULE_PARM_DESC(legacy_invert_outputs, + "invert outputs of PCI/PXI-6513/6514/6515/6516/6517/6518/6519 for compatibility with old user code"); + +static unsigned int ni_65xx_num_ports(struct comedi_device *dev) +{ + const struct ni_65xx_board *board = dev->board_ptr; + + return board->num_dio_ports + board->num_di_ports + board->num_do_ports; +} + +static void ni_65xx_disable_input_filters(struct comedi_device *dev) +{ + unsigned int num_ports = ni_65xx_num_ports(dev); + int i; + + /* disable input filtering on all ports */ + for (i = 0; i < num_ports; ++i) + writeb(0x00, dev->mmio + NI_65XX_FILTER_ENA(i)); + + /* set filter interval to 0 (32bit reg) */ + writel(0x00000000, dev->mmio + NI_65XX_FILTER_REG); +} + +/* updates edge detection for base_chan to base_chan+31 */ +static void ni_65xx_update_edge_detection(struct comedi_device *dev, + unsigned int base_chan, + unsigned int rising, + unsigned int falling) +{ + unsigned int num_ports = ni_65xx_num_ports(dev); + unsigned int port; + + if (base_chan >= NI_65XX_PORT_TO_CHAN(num_ports)) + return; + + for (port = NI_65XX_CHAN_TO_PORT(base_chan); port < num_ports; port++) { + int bitshift = (int)(NI_65XX_PORT_TO_CHAN(port) - base_chan); + unsigned int port_mask, port_rising, port_falling; + + if (bitshift >= 32) + break; + + if (bitshift >= 0) { + port_mask = ~0U >> bitshift; + port_rising = rising >> bitshift; + port_falling = falling >> bitshift; + } else { + port_mask = ~0U << -bitshift; + port_rising = rising << -bitshift; + port_falling = falling << -bitshift; + } + if (port_mask & 0xff) { + if (~port_mask & 0xff) { + port_rising |= + readb(dev->mmio + + NI_65XX_RISE_EDGE_ENA_REG(port)) & + ~port_mask; + port_falling |= + readb(dev->mmio + + NI_65XX_FALL_EDGE_ENA_REG(port)) & + ~port_mask; + } + writeb(port_rising & 0xff, + dev->mmio + NI_65XX_RISE_EDGE_ENA_REG(port)); + writeb(port_falling & 0xff, + dev->mmio + NI_65XX_FALL_EDGE_ENA_REG(port)); + } + } +} + +static void ni_65xx_disable_edge_detection(struct comedi_device *dev) +{ + /* clear edge detection for channels 0 to 31 */ + ni_65xx_update_edge_detection(dev, 0, 0, 0); + /* clear edge detection for channels 32 to 63 */ + ni_65xx_update_edge_detection(dev, 32, 0, 0); + /* clear edge detection for channels 64 to 95 */ + ni_65xx_update_edge_detection(dev, 64, 0, 0); +} + +static int ni_65xx_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long base_port = (unsigned long)s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int chan_mask = NI_65XX_CHAN_TO_MASK(chan); + unsigned int port = base_port + NI_65XX_CHAN_TO_PORT(chan); + unsigned int interval; + unsigned int val; + + switch (data[0]) { + case INSN_CONFIG_FILTER: + /* + * The deglitch filter interval is specified in nanoseconds. + * The hardware supports intervals in 200ns increments. Round + * the user values up and return the actual interval. + */ + interval = (data[1] + 100) / 200; + if (interval > 0xfffff) + interval = 0xfffff; + data[1] = interval * 200; + + /* + * Enable/disable the channel for deglitch filtering. Note + * that the filter interval is never set to '0'. This is done + * because other channels might still be enabled for filtering. + */ + val = readb(dev->mmio + NI_65XX_FILTER_ENA(port)); + if (interval) { + writel(interval, dev->mmio + NI_65XX_FILTER_REG); + val |= chan_mask; + } else { + val &= ~chan_mask; + } + writeb(val, dev->mmio + NI_65XX_FILTER_ENA(port)); + break; + + case INSN_CONFIG_DIO_OUTPUT: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + writeb(NI_65XX_IO_SEL_OUTPUT, + dev->mmio + NI_65XX_IO_SEL_REG(port)); + break; + + case INSN_CONFIG_DIO_INPUT: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + writeb(NI_65XX_IO_SEL_INPUT, + dev->mmio + NI_65XX_IO_SEL_REG(port)); + break; + + case INSN_CONFIG_DIO_QUERY: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + val = readb(dev->mmio + NI_65XX_IO_SEL_REG(port)); + data[1] = (val == NI_65XX_IO_SEL_INPUT) ? COMEDI_INPUT + : COMEDI_OUTPUT; + break; + + default: + return -EINVAL; + } + + return insn->n; +} + +static int ni_65xx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long base_port = (unsigned long)s->private; + unsigned int base_chan = CR_CHAN(insn->chanspec); + int last_port_offset = NI_65XX_CHAN_TO_PORT(s->n_chan - 1); + unsigned int read_bits = 0; + int port_offset; + + for (port_offset = NI_65XX_CHAN_TO_PORT(base_chan); + port_offset <= last_port_offset; port_offset++) { + unsigned int port = base_port + port_offset; + int base_port_channel = NI_65XX_PORT_TO_CHAN(port_offset); + unsigned int port_mask, port_data, bits; + int bitshift = base_port_channel - base_chan; + + if (bitshift >= 32) + break; + port_mask = data[0]; + port_data = data[1]; + if (bitshift > 0) { + port_mask >>= bitshift; + port_data >>= bitshift; + } else { + port_mask <<= -bitshift; + port_data <<= -bitshift; + } + port_mask &= 0xff; + port_data &= 0xff; + + /* update the outputs */ + if (port_mask) { + bits = readb(dev->mmio + NI_65XX_IO_DATA_REG(port)); + bits ^= s->io_bits; /* invert if necessary */ + bits &= ~port_mask; + bits |= (port_data & port_mask); + bits ^= s->io_bits; /* invert back */ + writeb(bits, dev->mmio + NI_65XX_IO_DATA_REG(port)); + } + + /* read back the actual state */ + bits = readb(dev->mmio + NI_65XX_IO_DATA_REG(port)); + bits ^= s->io_bits; /* invert if necessary */ + if (bitshift > 0) + bits <<= bitshift; + else + bits >>= -bitshift; + + read_bits |= bits; + } + data[1] = read_bits; + return insn->n; +} + +static irqreturn_t ni_65xx_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int status; + unsigned short val = 0; + + status = readb(dev->mmio + NI_65XX_STATUS_REG); + if ((status & NI_65XX_STATUS_INT) == 0) + return IRQ_NONE; + if ((status & NI_65XX_STATUS_EDGE_INT) == 0) + return IRQ_NONE; + + writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, + dev->mmio + NI_65XX_CLR_REG); + + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int ni_65xx_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int ni_65xx_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, + dev->mmio + NI_65XX_CLR_REG); + writeb(NI_65XX_CTRL_FALL_EDGE_ENA | NI_65XX_CTRL_RISE_EDGE_ENA | + NI_65XX_CTRL_INT_ENA | NI_65XX_CTRL_EDGE_ENA, + dev->mmio + NI_65XX_CTRL_REG); + + return 0; +} + +static int ni_65xx_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(0x00, dev->mmio + NI_65XX_CTRL_REG); + + return 0; +} + +static int ni_65xx_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + return insn->n; +} + +static int ni_65xx_intr_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_CHANGE_NOTIFY: + /* add instruction to check_insn_config_length() */ + if (insn->n != 3) + return -EINVAL; + + /* update edge detection for channels 0 to 31 */ + ni_65xx_update_edge_detection(dev, 0, data[1], data[2]); + /* clear edge detection for channels 32 to 63 */ + ni_65xx_update_edge_detection(dev, 32, 0, 0); + /* clear edge detection for channels 64 to 95 */ + ni_65xx_update_edge_detection(dev, 64, 0, 0); + break; + case INSN_CONFIG_DIGITAL_TRIG: + /* check trigger number */ + if (data[1] != 0) + return -EINVAL; + /* check digital trigger operation */ + switch (data[2]) { + case COMEDI_DIGITAL_TRIG_DISABLE: + ni_65xx_disable_edge_detection(dev); + break; + case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: + /* + * update edge detection for channels data[3] + * to (data[3] + 31) + */ + ni_65xx_update_edge_detection(dev, data[3], + data[4], data[5]); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return insn->n; +} + +/* ripped from mite.h and mite_setup2() to avoid mite dependency */ +#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */ +#define WENAB BIT(7) /* window enable */ + +static int ni_65xx_mite_init(struct pci_dev *pcidev) +{ + void __iomem *mite_base; + u32 main_phys_addr; + + /* ioremap the MITE registers (BAR 0) temporarily */ + mite_base = pci_ioremap_bar(pcidev, 0); + if (!mite_base) + return -ENOMEM; + + /* set data window to main registers (BAR 1) */ + main_phys_addr = pci_resource_start(pcidev, 1); + writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR); + + /* finished with MITE registers */ + iounmap(mite_base); + return 0; +} + +static int ni_65xx_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct ni_65xx_board *board = NULL; + struct comedi_subdevice *s; + unsigned int i; + int ret; + + if (context < ARRAY_SIZE(ni_65xx_boards)) + board = &ni_65xx_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + ret = ni_65xx_mite_init(pcidev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 1); + if (!dev->mmio) + return -ENOMEM; + + writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, + dev->mmio + NI_65XX_CLR_REG); + writeb(0x00, dev->mmio + NI_65XX_CTRL_REG); + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, ni_65xx_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + dev_info(dev->class_dev, "board: %s, ID=0x%02x", dev->board_name, + readb(dev->mmio + NI_65XX_ID_REG)); + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + if (board->num_di_ports) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_di_ports); + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_65xx_dio_insn_bits; + s->insn_config = ni_65xx_dio_insn_config; + + /* the input ports always start at port 0 */ + s->private = (void *)0; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[1]; + if (board->num_do_ports) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_do_ports); + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_65xx_dio_insn_bits; + + /* the output ports always start after the input ports */ + s->private = (void *)(unsigned long)board->num_di_ports; + + /* + * Use the io_bits to handle the inverted outputs. Inverted + * outputs are only supported if the "legacy_invert_outputs" + * module parameter is set to "true". + */ + if (ni_65xx_legacy_invert_outputs && board->legacy_invert) + s->io_bits = 0xff; + + /* reset all output ports to comedi '0' */ + for (i = 0; i < board->num_do_ports; ++i) { + writeb(s->io_bits, /* inverted if necessary */ + dev->mmio + + NI_65XX_IO_DATA_REG(board->num_di_ports + i)); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + if (board->num_dio_ports) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_dio_ports); + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_65xx_dio_insn_bits; + s->insn_config = ni_65xx_dio_insn_config; + + /* the input/output ports always start at port 0 */ + s->private = (void *)0; + + /* configure all ports for input */ + for (i = 0; i < board->num_dio_ports; ++i) { + writeb(NI_65XX_IO_SEL_INPUT, + dev->mmio + NI_65XX_IO_SEL_REG(i)); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_65xx_intr_insn_bits; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 1; + s->insn_config = ni_65xx_intr_insn_config; + s->do_cmdtest = ni_65xx_intr_cmdtest; + s->do_cmd = ni_65xx_intr_cmd; + s->cancel = ni_65xx_intr_cancel; + } + + ni_65xx_disable_input_filters(dev); + ni_65xx_disable_edge_detection(dev); + + return 0; +} + +static void ni_65xx_detach(struct comedi_device *dev) +{ + if (dev->mmio) + writeb(0x00, dev->mmio + NI_65XX_CTRL_REG); + comedi_pci_detach(dev); +} + +static struct comedi_driver ni_65xx_driver = { + .driver_name = "ni_65xx", + .module = THIS_MODULE, + .auto_attach = ni_65xx_auto_attach, + .detach = ni_65xx_detach, +}; + +static int ni_65xx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni_65xx_driver, id->driver_data); +} + +static const struct pci_device_id ni_65xx_pci_table[] = { + { PCI_VDEVICE(NI, 0x1710), BOARD_PXI6509 }, + { PCI_VDEVICE(NI, 0x7085), BOARD_PCI6509 }, + { PCI_VDEVICE(NI, 0x7086), BOARD_PXI6528 }, + { PCI_VDEVICE(NI, 0x7087), BOARD_PCI6515 }, + { PCI_VDEVICE(NI, 0x7088), BOARD_PCI6514 }, + { PCI_VDEVICE(NI, 0x70a9), BOARD_PCI6528 }, + { PCI_VDEVICE(NI, 0x70c3), BOARD_PCI6511 }, + { PCI_VDEVICE(NI, 0x70c8), BOARD_PCI6513 }, + { PCI_VDEVICE(NI, 0x70c9), BOARD_PXI6515 }, + { PCI_VDEVICE(NI, 0x70cc), BOARD_PCI6512 }, + { PCI_VDEVICE(NI, 0x70cd), BOARD_PXI6514 }, + { PCI_VDEVICE(NI, 0x70d1), BOARD_PXI6513 }, + { PCI_VDEVICE(NI, 0x70d2), BOARD_PXI6512 }, + { PCI_VDEVICE(NI, 0x70d3), BOARD_PXI6511 }, + { PCI_VDEVICE(NI, 0x7124), BOARD_PCI6510 }, + { PCI_VDEVICE(NI, 0x7125), BOARD_PCI6516 }, + { PCI_VDEVICE(NI, 0x7126), BOARD_PCI6517 }, + { PCI_VDEVICE(NI, 0x7127), BOARD_PCI6518 }, + { PCI_VDEVICE(NI, 0x7128), BOARD_PCI6519 }, + { PCI_VDEVICE(NI, 0x718b), BOARD_PCI6521 }, + { PCI_VDEVICE(NI, 0x718c), BOARD_PXI6521 }, + { PCI_VDEVICE(NI, 0x71c5), BOARD_PCI6520 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ni_65xx_pci_table); + +static struct pci_driver ni_65xx_pci_driver = { + .name = "ni_65xx", + .id_table = ni_65xx_pci_table, + .probe = ni_65xx_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(ni_65xx_driver, ni_65xx_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for NI PCI-65xx static dio boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_660x.c b/drivers/comedi/drivers/ni_660x.c new file mode 100644 index 000000000..0679bc39e --- /dev/null +++ b/drivers/comedi/drivers/ni_660x.c @@ -0,0 +1,1254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware driver for NI 660x devices + */ + +/* + * Driver: ni_660x + * Description: National Instruments 660x counter/timer boards + * Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602, + * PCI-6608, PXI-6608, PCI-6624, 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: Mon, 16 Jan 2017 14:00:43 +0000 + * Status: experimental + * + * Encoders work. PulseGeneration (both single pulse and pulse train) + * works. Buffered commands work for input but not output. + * + * References: + * DAQ 660x Register-Level Programmer Manual (NI 370505A-01) + * DAQ 6601/6602 User Manual (NI 322137B-01) + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> + +#include "mite.h" +#include "ni_tio.h" +#include "ni_routes.h" + +/* See Register-Level Programmer Manual page 3.1 */ +enum ni_660x_register { + /* see enum ni_gpct_register */ + NI660X_STC_DIO_PARALLEL_INPUT = NITIO_NUM_REGS, + NI660X_STC_DIO_OUTPUT, + NI660X_STC_DIO_CONTROL, + NI660X_STC_DIO_SERIAL_INPUT, + 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, +}; + +#define NI660X_CLK_CFG_COUNTER_SWAP BIT(21) + +#define NI660X_GLOBAL_INT_COUNTER0 BIT(8) +#define NI660X_GLOBAL_INT_COUNTER1 BIT(9) +#define NI660X_GLOBAL_INT_COUNTER2 BIT(10) +#define NI660X_GLOBAL_INT_COUNTER3 BIT(11) +#define NI660X_GLOBAL_INT_CASCADE BIT(29) +#define NI660X_GLOBAL_INT_GLOBAL_POL BIT(30) +#define NI660X_GLOBAL_INT_GLOBAL BIT(31) + +#define NI660X_DMA_CFG_SEL(_c, _s) (((_s) & 0x1f) << (8 * (_c))) +#define NI660X_DMA_CFG_SEL_MASK(_c) NI660X_DMA_CFG_SEL((_c), 0x1f) +#define NI660X_DMA_CFG_SEL_NONE(_c) NI660X_DMA_CFG_SEL((_c), 0x1f) +#define NI660X_DMA_CFG_RESET(_c) NI660X_DMA_CFG_SEL((_c), 0x80) + +#define NI660X_IO_CFG(x) (NI660X_IO_CFG_0_1 + ((x) / 2)) +#define NI660X_IO_CFG_OUT_SEL(_c, _s) (((_s) & 0x3) << (((_c) % 2) ? 0 : 8)) +#define NI660X_IO_CFG_OUT_SEL_MASK(_c) NI660X_IO_CFG_OUT_SEL((_c), 0x3) +#define NI660X_IO_CFG_IN_SEL(_c, _s) (((_s) & 0x7) << (((_c) % 2) ? 4 : 12)) +#define NI660X_IO_CFG_IN_SEL_MASK(_c) NI660X_IO_CFG_IN_SEL((_c), 0x7) + +struct ni_660x_register_data { + int offset; /* Offset from base address from GPCT chip */ + char size; /* 2 or 4 bytes */ +}; + +static const struct ni_660x_register_data ni_660x_reg_data[NI660X_NUM_REGS] = { + [NITIO_G0_INT_ACK] = { 0x004, 2 }, /* write */ + [NITIO_G0_STATUS] = { 0x004, 2 }, /* read */ + [NITIO_G1_INT_ACK] = { 0x006, 2 }, /* write */ + [NITIO_G1_STATUS] = { 0x006, 2 }, /* read */ + [NITIO_G01_STATUS] = { 0x008, 2 }, /* read */ + [NITIO_G0_CMD] = { 0x00c, 2 }, /* write */ + [NI660X_STC_DIO_PARALLEL_INPUT] = { 0x00e, 2 }, /* read */ + [NITIO_G1_CMD] = { 0x00e, 2 }, /* write */ + [NITIO_G0_HW_SAVE] = { 0x010, 4 }, /* read */ + [NITIO_G1_HW_SAVE] = { 0x014, 4 }, /* read */ + [NI660X_STC_DIO_OUTPUT] = { 0x014, 2 }, /* write */ + [NI660X_STC_DIO_CONTROL] = { 0x016, 2 }, /* write */ + [NITIO_G0_SW_SAVE] = { 0x018, 4 }, /* read */ + [NITIO_G1_SW_SAVE] = { 0x01c, 4 }, /* read */ + [NITIO_G0_MODE] = { 0x034, 2 }, /* write */ + [NITIO_G01_STATUS1] = { 0x036, 2 }, /* read */ + [NITIO_G1_MODE] = { 0x036, 2 }, /* write */ + [NI660X_STC_DIO_SERIAL_INPUT] = { 0x038, 2 }, /* read */ + [NITIO_G0_LOADA] = { 0x038, 4 }, /* write */ + [NITIO_G01_STATUS2] = { 0x03a, 2 }, /* read */ + [NITIO_G0_LOADB] = { 0x03c, 4 }, /* write */ + [NITIO_G1_LOADA] = { 0x040, 4 }, /* write */ + [NITIO_G1_LOADB] = { 0x044, 4 }, /* write */ + [NITIO_G0_INPUT_SEL] = { 0x048, 2 }, /* write */ + [NITIO_G1_INPUT_SEL] = { 0x04a, 2 }, /* write */ + [NITIO_G0_AUTO_INC] = { 0x088, 2 }, /* write */ + [NITIO_G1_AUTO_INC] = { 0x08a, 2 }, /* write */ + [NITIO_G01_RESET] = { 0x090, 2 }, /* write */ + [NITIO_G0_INT_ENA] = { 0x092, 2 }, /* write */ + [NITIO_G1_INT_ENA] = { 0x096, 2 }, /* write */ + [NITIO_G0_CNT_MODE] = { 0x0b0, 2 }, /* write */ + [NITIO_G1_CNT_MODE] = { 0x0b2, 2 }, /* write */ + [NITIO_G0_GATE2] = { 0x0b4, 2 }, /* write */ + [NITIO_G1_GATE2] = { 0x0b6, 2 }, /* write */ + [NITIO_G0_DMA_CFG] = { 0x0b8, 2 }, /* write */ + [NITIO_G0_DMA_STATUS] = { 0x0b8, 2 }, /* read */ + [NITIO_G1_DMA_CFG] = { 0x0ba, 2 }, /* write */ + [NITIO_G1_DMA_STATUS] = { 0x0ba, 2 }, /* read */ + [NITIO_G2_INT_ACK] = { 0x104, 2 }, /* write */ + [NITIO_G2_STATUS] = { 0x104, 2 }, /* read */ + [NITIO_G3_INT_ACK] = { 0x106, 2 }, /* write */ + [NITIO_G3_STATUS] = { 0x106, 2 }, /* read */ + [NITIO_G23_STATUS] = { 0x108, 2 }, /* read */ + [NITIO_G2_CMD] = { 0x10c, 2 }, /* write */ + [NITIO_G3_CMD] = { 0x10e, 2 }, /* write */ + [NITIO_G2_HW_SAVE] = { 0x110, 4 }, /* read */ + [NITIO_G3_HW_SAVE] = { 0x114, 4 }, /* read */ + [NITIO_G2_SW_SAVE] = { 0x118, 4 }, /* read */ + [NITIO_G3_SW_SAVE] = { 0x11c, 4 }, /* read */ + [NITIO_G2_MODE] = { 0x134, 2 }, /* write */ + [NITIO_G23_STATUS1] = { 0x136, 2 }, /* read */ + [NITIO_G3_MODE] = { 0x136, 2 }, /* write */ + [NITIO_G2_LOADA] = { 0x138, 4 }, /* write */ + [NITIO_G23_STATUS2] = { 0x13a, 2 }, /* read */ + [NITIO_G2_LOADB] = { 0x13c, 4 }, /* write */ + [NITIO_G3_LOADA] = { 0x140, 4 }, /* write */ + [NITIO_G3_LOADB] = { 0x144, 4 }, /* write */ + [NITIO_G2_INPUT_SEL] = { 0x148, 2 }, /* write */ + [NITIO_G3_INPUT_SEL] = { 0x14a, 2 }, /* write */ + [NITIO_G2_AUTO_INC] = { 0x188, 2 }, /* write */ + [NITIO_G3_AUTO_INC] = { 0x18a, 2 }, /* write */ + [NITIO_G23_RESET] = { 0x190, 2 }, /* write */ + [NITIO_G2_INT_ENA] = { 0x192, 2 }, /* write */ + [NITIO_G3_INT_ENA] = { 0x196, 2 }, /* write */ + [NITIO_G2_CNT_MODE] = { 0x1b0, 2 }, /* write */ + [NITIO_G3_CNT_MODE] = { 0x1b2, 2 }, /* write */ + [NITIO_G2_GATE2] = { 0x1b4, 2 }, /* write */ + [NITIO_G3_GATE2] = { 0x1b6, 2 }, /* write */ + [NITIO_G2_DMA_CFG] = { 0x1b8, 2 }, /* write */ + [NITIO_G2_DMA_STATUS] = { 0x1b8, 2 }, /* read */ + [NITIO_G3_DMA_CFG] = { 0x1ba, 2 }, /* write */ + [NITIO_G3_DMA_STATUS] = { 0x1ba, 2 }, /* read */ + [NI660X_DIO32_INPUT] = { 0x414, 4 }, /* read */ + [NI660X_DIO32_OUTPUT] = { 0x510, 4 }, /* write */ + [NI660X_CLK_CFG] = { 0x73c, 4 }, /* write */ + [NI660X_GLOBAL_INT_STATUS] = { 0x754, 4 }, /* read */ + [NI660X_DMA_CFG] = { 0x76c, 4 }, /* write */ + [NI660X_GLOBAL_INT_CFG] = { 0x770, 4 }, /* write */ + [NI660X_IO_CFG_0_1] = { 0x77c, 2 }, /* read/write */ + [NI660X_IO_CFG_2_3] = { 0x77e, 2 }, /* read/write */ + [NI660X_IO_CFG_4_5] = { 0x780, 2 }, /* read/write */ + [NI660X_IO_CFG_6_7] = { 0x782, 2 }, /* read/write */ + [NI660X_IO_CFG_8_9] = { 0x784, 2 }, /* read/write */ + [NI660X_IO_CFG_10_11] = { 0x786, 2 }, /* read/write */ + [NI660X_IO_CFG_12_13] = { 0x788, 2 }, /* read/write */ + [NI660X_IO_CFG_14_15] = { 0x78a, 2 }, /* read/write */ + [NI660X_IO_CFG_16_17] = { 0x78c, 2 }, /* read/write */ + [NI660X_IO_CFG_18_19] = { 0x78e, 2 }, /* read/write */ + [NI660X_IO_CFG_20_21] = { 0x790, 2 }, /* read/write */ + [NI660X_IO_CFG_22_23] = { 0x792, 2 }, /* read/write */ + [NI660X_IO_CFG_24_25] = { 0x794, 2 }, /* read/write */ + [NI660X_IO_CFG_26_27] = { 0x796, 2 }, /* read/write */ + [NI660X_IO_CFG_28_29] = { 0x798, 2 }, /* read/write */ + [NI660X_IO_CFG_30_31] = { 0x79a, 2 }, /* read/write */ + [NI660X_IO_CFG_32_33] = { 0x79c, 2 }, /* read/write */ + [NI660X_IO_CFG_34_35] = { 0x79e, 2 }, /* read/write */ + [NI660X_IO_CFG_36_37] = { 0x7a0, 2 }, /* read/write */ + [NI660X_IO_CFG_38_39] = { 0x7a2, 2 } /* read/write */ +}; + +#define NI660X_CHIP_OFFSET 0x800 + +enum ni_660x_boardid { + BOARD_PCI6601, + BOARD_PCI6602, + BOARD_PXI6602, + BOARD_PCI6608, + BOARD_PXI6608, + BOARD_PCI6624, + BOARD_PXI6624 +}; + +struct ni_660x_board { + const char *name; + unsigned int 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_PCI6608] = { + .name = "PCI-6608", + .n_chips = 2, + }, + [BOARD_PXI6608] = { + .name = "PXI-6608", + .n_chips = 2, + }, + [BOARD_PCI6624] = { + .name = "PCI-6624", + .n_chips = 2, + }, + [BOARD_PXI6624] = { + .name = "PXI-6624", + .n_chips = 2, + }, +}; + +#define NI660X_NUM_PFI_CHANNELS 40 + +/* there are only up to 3 dma channels, but the register layout allows for 4 */ +#define NI660X_MAX_DMA_CHANNEL 4 + +#define NI660X_COUNTERS_PER_CHIP 4 +#define NI660X_MAX_CHIPS 2 +#define NI660X_MAX_COUNTERS (NI660X_MAX_CHIPS * \ + NI660X_COUNTERS_PER_CHIP) + +struct ni_660x_private { + struct mite *mite; + struct ni_gpct_device *counter_dev; + struct mite_ring *ring[NI660X_MAX_CHIPS][NI660X_COUNTERS_PER_CHIP]; + /* protects mite channel request/release */ + spinlock_t mite_channel_lock; + /* prevents races between interrupt and comedi_poll */ + spinlock_t interrupt_lock; + unsigned int dma_cfg[NI660X_MAX_CHIPS]; + unsigned int io_cfg[NI660X_NUM_PFI_CHANNELS]; + u64 io_dir; + struct ni_route_tables routing_tables; +}; + +static void ni_660x_write(struct comedi_device *dev, unsigned int chip, + unsigned int bits, unsigned int reg) +{ + unsigned int addr = (chip * NI660X_CHIP_OFFSET) + + ni_660x_reg_data[reg].offset; + + if (ni_660x_reg_data[reg].size == 2) + writew(bits, dev->mmio + addr); + else + writel(bits, dev->mmio + addr); +} + +static unsigned int ni_660x_read(struct comedi_device *dev, + unsigned int chip, unsigned int reg) +{ + unsigned int addr = (chip * NI660X_CHIP_OFFSET) + + ni_660x_reg_data[reg].offset; + + if (ni_660x_reg_data[reg].size == 2) + return readw(dev->mmio + addr); + return readl(dev->mmio + addr); +} + +static void ni_660x_gpct_write(struct ni_gpct *counter, unsigned int bits, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + + ni_660x_write(dev, counter->chip_index, bits, reg); +} + +static unsigned int ni_660x_gpct_read(struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + + return ni_660x_read(dev, counter->chip_index, reg); +} + +static inline void ni_660x_set_dma_channel(struct comedi_device *dev, + unsigned int mite_channel, + struct ni_gpct *counter) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned int chip = counter->chip_index; + + devpriv->dma_cfg[chip] &= ~NI660X_DMA_CFG_SEL_MASK(mite_channel); + devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL(mite_channel, + counter->counter_index); + ni_660x_write(dev, chip, devpriv->dma_cfg[chip] | + NI660X_DMA_CFG_RESET(mite_channel), + NI660X_DMA_CFG); +} + +static inline void ni_660x_unset_dma_channel(struct comedi_device *dev, + unsigned int mite_channel, + struct ni_gpct *counter) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned int chip = counter->chip_index; + + devpriv->dma_cfg[chip] &= ~NI660X_DMA_CFG_SEL_MASK(mite_channel); + devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL_NONE(mite_channel); + ni_660x_write(dev, chip, devpriv->dma_cfg[chip], NI660X_DMA_CFG); +} + +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; + struct mite_ring *ring; + struct mite_channel *mite_chan; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + ring = devpriv->ring[counter->chip_index][counter->counter_index]; + mite_chan = mite_request_channel(devpriv->mite, ring); + if (!mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, + "failed to reserve mite dma channel for counter\n"); + return -EBUSY; + } + mite_chan->dir = direction; + ni_tio_set_mite_channel(counter, mite_chan); + ni_660x_set_dma_channel(dev, mite_chan->channel, counter); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +static void ni_660x_release_mite_channel(struct comedi_device *dev, + struct ni_gpct *counter) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (counter->mite_chan) { + struct mite_channel *mite_chan = counter->mite_chan; + + ni_660x_unset_dma_channel(dev, mite_chan->channel, counter); + ni_tio_set_mite_channel(counter, NULL); + mite_release_channel(mite_chan); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + int retval; + + retval = ni_660x_request_mite_channel(dev, counter, COMEDI_INPUT); + if (retval) { + dev_err(dev->class_dev, + "no dma channel available for use by counter\n"); + return retval; + } + ni_tio_acknowledge(counter); + + return ni_tio_cmd(dev, s); +} + +static int ni_660x_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + int retval; + + retval = ni_tio_cancel(counter); + ni_660x_release_mite_channel(dev, counter); + return retval; +} + +static void set_tio_counterswap(struct comedi_device *dev, int chip) +{ + unsigned int 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 = NI660X_CLK_CFG_COUNTER_SWAP; + + ni_660x_write(dev, chip, bits, NI660X_CLK_CFG); +} + +static void ni_660x_handle_gpct_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + + ni_tio_handle_interrupt(counter, s); + comedi_handle_events(dev, s); +} + +static irqreturn_t ni_660x_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct ni_660x_private *devpriv = dev->private; + struct comedi_subdevice *s; + unsigned int i; + unsigned long flags; + + if (!dev->attached) + return IRQ_NONE; + /* make sure dev->attached is checked before doing anything else */ + smp_mb(); + + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&devpriv->interrupt_lock, flags); + for (i = 0; i < dev->n_subdevices; ++i) { + s = &dev->subdevices[i]; + if (s->type == COMEDI_SUBD_COUNTER) + 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_dma(counter->mite_chan, s); + spin_unlock_irqrestore(&devpriv->interrupt_lock, flags); + return comedi_buf_read_n_available(s); +} + +static int ni_660x_buf_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_660x_private *devpriv = dev->private; + struct ni_gpct *counter = s->private; + struct mite_ring *ring; + int ret; + + ring = devpriv->ring[counter->chip_index][counter->counter_index]; + ret = mite_buf_change(ring, s); + if (ret < 0) + return ret; + + return 0; +} + +static int ni_660x_allocate_private(struct comedi_device *dev) +{ + struct ni_660x_private *devpriv; + unsigned int 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); + for (i = 0; i < NI660X_NUM_PFI_CHANNELS; ++i) + devpriv->io_cfg[i] = NI_660X_PFI_OUTPUT_COUNTER; + + return 0; +} + +static int ni_660x_alloc_mite_rings(struct comedi_device *dev) +{ + const struct ni_660x_board *board = dev->board_ptr; + struct ni_660x_private *devpriv = dev->private; + unsigned int i; + unsigned int j; + + for (i = 0; i < board->n_chips; ++i) { + for (j = 0; j < NI660X_COUNTERS_PER_CHIP; ++j) { + devpriv->ring[i][j] = mite_alloc_ring(devpriv->mite); + if (!devpriv->ring[i][j]) + return -ENOMEM; + } + } + return 0; +} + +static void ni_660x_free_mite_rings(struct comedi_device *dev) +{ + const struct ni_660x_board *board = dev->board_ptr; + struct ni_660x_private *devpriv = dev->private; + unsigned int i; + unsigned int j; + + for (i = 0; i < board->n_chips; ++i) { + for (j = 0; j < NI660X_COUNTERS_PER_CHIP; ++j) + mite_free_ring(devpriv->ring[i][j]); + } +} + +static int ni_660x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int shift = CR_CHAN(insn->chanspec); + unsigned int mask = data[0] << shift; + unsigned int bits = data[1] << shift; + + /* + * There are 40 channels in this subdevice but only 32 are usable + * as DIO. The shift adjusts the mask/bits to account for the base + * channel in insn->chanspec. The state update can then be handled + * normally for the 32 usable channels. + */ + if (mask) { + s->state &= ~mask; + s->state |= (bits & mask); + ni_660x_write(dev, 0, s->state, NI660X_DIO32_OUTPUT); + } + + /* + * Return the input channels, shifted back to account for the base + * channel. + */ + data[1] = ni_660x_read(dev, 0, NI660X_DIO32_INPUT) >> shift; + + return insn->n; +} + +static void ni_660x_select_pfi_output(struct comedi_device *dev, + unsigned int chan, unsigned int out_sel) +{ + const struct ni_660x_board *board = dev->board_ptr; + unsigned int active_chip = 0; + unsigned int idle_chip = 0; + unsigned int bits; + + if (chan >= NI_PFI(0)) + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + + if (board->n_chips > 1) { + if (out_sel == NI_660X_PFI_OUTPUT_COUNTER && + chan >= 8 && chan <= 23) { + /* counters 4-7 pfi channels */ + active_chip = 1; + idle_chip = 0; + } else { + /* counters 0-3 pfi channels */ + active_chip = 0; + idle_chip = 1; + } + } + + if (idle_chip != active_chip) { + /* set the pfi channel to high-z on the inactive chip */ + bits = ni_660x_read(dev, idle_chip, NI660X_IO_CFG(chan)); + bits &= ~NI660X_IO_CFG_OUT_SEL_MASK(chan); + bits |= NI660X_IO_CFG_OUT_SEL(chan, 0); /* high-z */ + ni_660x_write(dev, idle_chip, bits, NI660X_IO_CFG(chan)); + } + + /* set the pfi channel output on the active chip */ + bits = ni_660x_read(dev, active_chip, NI660X_IO_CFG(chan)); + bits &= ~NI660X_IO_CFG_OUT_SEL_MASK(chan); + bits |= NI660X_IO_CFG_OUT_SEL(chan, out_sel); + ni_660x_write(dev, active_chip, bits, NI660X_IO_CFG(chan)); +} + +static void ni_660x_set_pfi_direction(struct comedi_device *dev, + unsigned int chan, + unsigned int direction) +{ + struct ni_660x_private *devpriv = dev->private; + u64 bit; + + if (chan >= NI_PFI(0)) + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + + bit = 1ULL << chan; + + if (direction == COMEDI_OUTPUT) { + devpriv->io_dir |= bit; + /* reset the output to currently assigned output value */ + ni_660x_select_pfi_output(dev, chan, devpriv->io_cfg[chan]); + } else { + devpriv->io_dir &= ~bit; + /* set pin to high-z; do not change currently assigned route */ + ni_660x_select_pfi_output(dev, chan, 0); + } +} + +static unsigned int ni_660x_get_pfi_direction(struct comedi_device *dev, + unsigned int chan) +{ + struct ni_660x_private *devpriv = dev->private; + u64 bit; + + if (chan >= NI_PFI(0)) + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + + bit = 1ULL << chan; + + return (devpriv->io_dir & bit) ? COMEDI_OUTPUT : COMEDI_INPUT; +} + +static int ni_660x_set_pfi_routing(struct comedi_device *dev, + unsigned int chan, unsigned int source) +{ + struct ni_660x_private *devpriv = dev->private; + + if (chan >= NI_PFI(0)) + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + + switch (source) { + case NI_660X_PFI_OUTPUT_COUNTER: + if (chan < 8) + return -EINVAL; + break; + case NI_660X_PFI_OUTPUT_DIO: + if (chan > 31) + return -EINVAL; + break; + default: + return -EINVAL; + } + + devpriv->io_cfg[chan] = source; + if (ni_660x_get_pfi_direction(dev, chan) == COMEDI_OUTPUT) + ni_660x_select_pfi_output(dev, chan, devpriv->io_cfg[chan]); + return 0; +} + +static int ni_660x_get_pfi_routing(struct comedi_device *dev, unsigned int chan) +{ + struct ni_660x_private *devpriv = dev->private; + + if (chan >= NI_PFI(0)) + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + + return devpriv->io_cfg[chan]; +} + +static void ni_660x_set_pfi_filter(struct comedi_device *dev, + unsigned int chan, unsigned int value) +{ + unsigned int val; + + if (chan >= NI_PFI(0)) + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + + val = ni_660x_read(dev, 0, NI660X_IO_CFG(chan)); + val &= ~NI660X_IO_CFG_IN_SEL_MASK(chan); + val |= NI660X_IO_CFG_IN_SEL(chan, value); + ni_660x_write(dev, 0, val, NI660X_IO_CFG(chan)); +} + +static int ni_660x_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); + int ret; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + ni_660x_set_pfi_direction(dev, chan, COMEDI_OUTPUT); + break; + + case INSN_CONFIG_DIO_INPUT: + ni_660x_set_pfi_direction(dev, chan, COMEDI_INPUT); + break; + + case INSN_CONFIG_DIO_QUERY: + data[1] = ni_660x_get_pfi_direction(dev, chan); + 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] = ni_660x_get_pfi_routing(dev, chan); + break; + + case INSN_CONFIG_FILTER: + ni_660x_set_pfi_filter(dev, chan, data[1]); + break; + + default: + return -EINVAL; + } + + return insn->n; +} + +static unsigned int _ni_get_valid_routes(struct comedi_device *dev, + unsigned int n_pairs, + unsigned int *pair_data) +{ + struct ni_660x_private *devpriv = dev->private; + + return ni_get_valid_routes(&devpriv->routing_tables, n_pairs, + pair_data); +} + +/* + * Retrieves the current source of the output selector for the given + * destination. If the terminal for the destination is not already configured + * as an output, this function returns -EINVAL as error. + * + * Return: The register value of the destination output selector; + * -EINVAL if terminal is not configured for output. + */ +static inline int get_output_select_source(int dest, struct comedi_device *dev) +{ + struct ni_660x_private *devpriv = dev->private; + int reg = -1; + + if (channel_is_pfi(dest)) { + if (ni_660x_get_pfi_direction(dev, dest) == COMEDI_OUTPUT) + reg = ni_660x_get_pfi_routing(dev, dest); + } else if (channel_is_rtsi(dest)) { + dev_dbg(dev->class_dev, + "%s: unhandled rtsi destination (%d) queried\n", + __func__, dest); + /* + * The following can be enabled when RTSI routing info is + * determined (not currently documented): + * if (ni_get_rtsi_direction(dev, dest) == COMEDI_OUTPUT) { + * reg = ni_get_rtsi_routing(dev, dest); + + * if (reg == NI_RTSI_OUTPUT_RGOUT0) { + * dest = NI_RGOUT0; ** prepare for lookup below ** + * reg = get_rgout0_reg(dev); + * } else if (reg >= NI_RTSI_OUTPUT_RTSI_BRD(0) && + * reg <= NI_RTSI_OUTPUT_RTSI_BRD(3)) { + * const int i = reg - NI_RTSI_OUTPUT_RTSI_BRD(0); + + * dest = NI_RTSI_BRD(i); ** prepare for lookup ** + * reg = get_ith_rtsi_brd_reg(i, dev); + * } + * } + */ + } else if (channel_is_ctr(dest)) { + reg = ni_tio_get_routing(devpriv->counter_dev, dest); + } else { + dev_dbg(dev->class_dev, + "%s: unhandled destination (%d) queried\n", + __func__, dest); + } + + if (reg >= 0) + return ni_find_route_source(CR_CHAN(reg), dest, + &devpriv->routing_tables); + return -EINVAL; +} + +/* + * Test a route: + * + * Return: -1 if not connectible; + * 0 if connectible and not connected; + * 1 if connectible and connected. + */ +static inline int test_route(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_660x_private *devpriv = dev->private; + s8 reg = ni_route_to_register(CR_CHAN(src), dest, + &devpriv->routing_tables); + + if (reg < 0) + return -1; + if (get_output_select_source(dest, dev) != CR_CHAN(src)) + return 0; + return 1; +} + +/* Connect the actual route. */ +static inline int connect_route(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_660x_private *devpriv = dev->private; + s8 reg = ni_route_to_register(CR_CHAN(src), dest, + &devpriv->routing_tables); + s8 current_src; + + if (reg < 0) + /* route is not valid */ + return -EINVAL; + + current_src = get_output_select_source(dest, dev); + if (current_src == CR_CHAN(src)) + return -EALREADY; + if (current_src >= 0) + /* destination mux is already busy. complain, don't overwrite */ + return -EBUSY; + + /* The route is valid and available. Now connect... */ + if (channel_is_pfi(CR_CHAN(dest))) { + /* + * set routing and then direction so that the output does not + * first get generated with the wrong pin + */ + ni_660x_set_pfi_routing(dev, dest, reg); + ni_660x_set_pfi_direction(dev, dest, COMEDI_OUTPUT); + } else if (channel_is_rtsi(CR_CHAN(dest))) { + dev_dbg(dev->class_dev, "%s: unhandled rtsi destination (%d)\n", + __func__, dest); + return -EINVAL; + /* + * The following can be enabled when RTSI routing info is + * determined (not currently documented): + * if (reg == NI_RTSI_OUTPUT_RGOUT0) { + * int ret = incr_rgout0_src_use(src, dev); + + * if (ret < 0) + * return ret; + * } else if (ni_rtsi_route_requires_mux(reg)) { + * ** Attempt to allocate and route (src->brd) ** + * int brd = incr_rtsi_brd_src_use(src, dev); + + * if (brd < 0) + * return brd; + + * ** Now lookup the register value for (brd->dest) ** + * reg = ni_lookup_route_register(brd, CR_CHAN(dest), + * &devpriv->routing_tables); + * } + + * ni_set_rtsi_direction(dev, dest, COMEDI_OUTPUT); + * ni_set_rtsi_routing(dev, dest, reg); + */ + } else if (channel_is_ctr(CR_CHAN(dest))) { + /* + * we are adding back the channel modifier info to set + * invert/edge info passed by the user + */ + ni_tio_set_routing(devpriv->counter_dev, dest, + reg | (src & ~CR_CHAN(-1))); + } else { + return -EINVAL; + } + return 0; +} + +static inline int disconnect_route(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_660x_private *devpriv = dev->private; + s8 reg = ni_route_to_register(CR_CHAN(src), CR_CHAN(dest), + &devpriv->routing_tables); + + if (reg < 0) + /* route is not valid */ + return -EINVAL; + if (get_output_select_source(dest, dev) != CR_CHAN(src)) + /* cannot disconnect something not connected */ + return -EINVAL; + + /* The route is valid and is connected. Now disconnect... */ + if (channel_is_pfi(CR_CHAN(dest))) { + unsigned int source = ((CR_CHAN(dest) - NI_PFI(0)) < 8) + ? NI_660X_PFI_OUTPUT_DIO + : NI_660X_PFI_OUTPUT_COUNTER; + + /* set the pfi to high impedance, and disconnect */ + ni_660x_set_pfi_direction(dev, dest, COMEDI_INPUT); + ni_660x_set_pfi_routing(dev, dest, source); + } else if (channel_is_rtsi(CR_CHAN(dest))) { + dev_dbg(dev->class_dev, "%s: unhandled rtsi destination (%d)\n", + __func__, dest); + return -EINVAL; + /* + * The following can be enabled when RTSI routing info is + * determined (not currently documented): + * if (reg == NI_RTSI_OUTPUT_RGOUT0) { + * int ret = decr_rgout0_src_use(src, dev); + + * if (ret < 0) + * return ret; + * } else if (ni_rtsi_route_requires_mux(reg)) { + * ** find which RTSI_BRD line is source for rtsi pin ** + * int brd = ni_find_route_source( + * ni_get_rtsi_routing(dev, dest), CR_CHAN(dest), + * &devpriv->routing_tables); + + * if (brd < 0) + * return brd; + + * ** decrement/disconnect RTSI_BRD line from source ** + * decr_rtsi_brd_src_use(src, brd, dev); + * } + + * ** set rtsi output selector to default state ** + * reg = default_rtsi_routing[CR_CHAN(dest) - TRIGGER_LINE(0)]; + * ni_set_rtsi_direction(dev, dest, COMEDI_INPUT); + * ni_set_rtsi_routing(dev, dest, reg); + */ + } else if (channel_is_ctr(CR_CHAN(dest))) { + ni_tio_unset_routing(devpriv->counter_dev, dest); + } else { + return -EINVAL; + } + return 0; +} + +static int ni_global_insn_config(struct comedi_device *dev, + struct comedi_insn *insn, + unsigned int *data) +{ + switch (data[0]) { + case INSN_DEVICE_CONFIG_TEST_ROUTE: + data[0] = test_route(data[1], data[2], dev); + return 2; + case INSN_DEVICE_CONFIG_CONNECT_ROUTE: + return connect_route(data[1], data[2], dev); + case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE: + return disconnect_route(data[1], data[2], dev); + /* + * This case is already handled one level up. + * case INSN_DEVICE_CONFIG_GET_ROUTES: + */ + default: + return -EINVAL; + } + return 1; +} + +static void ni_660x_init_tio_chips(struct comedi_device *dev, + unsigned int n_chips) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned int chip; + unsigned int chan; + + /* + * We use the ioconfig registers to control dio direction, so zero + * output enables in stc dio control reg. + */ + ni_660x_write(dev, 0, 0, NI660X_STC_DIO_CONTROL); + + for (chip = 0; chip < n_chips; ++chip) { + /* init dma configuration register */ + devpriv->dma_cfg[chip] = 0; + for (chan = 0; chan < NI660X_MAX_DMA_CHANNEL; ++chan) + devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL_NONE(chan); + ni_660x_write(dev, chip, devpriv->dma_cfg[chip], + NI660X_DMA_CFG); + + /* init ioconfig registers */ + for (chan = 0; chan < NI660X_NUM_PFI_CHANNELS; ++chan) + ni_660x_write(dev, chip, 0, NI660X_IO_CFG(chan)); + } +} + +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; + struct ni_gpct_device *gpct_dev; + unsigned int n_counters; + int subdev; + int ret; + unsigned int i; + unsigned int 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_attach(dev, true); /* use win1 */ + if (!devpriv->mite) + return -ENOMEM; + + ret = ni_660x_alloc_mite_rings(dev); + if (ret < 0) + return ret; + + ni_660x_init_tio_chips(dev, board->n_chips); + + /* prepare the device for globally-named routes. */ + if (ni_assign_device_routes("ni_660x", board->name, NULL, + &devpriv->routing_tables) < 0) { + dev_warn(dev->class_dev, "%s: %s device has no signal routing table.\n", + __func__, board->name); + dev_warn(dev->class_dev, "%s: High level NI signal names will not be available for this %s board.\n", + __func__, board->name); + } else { + /* + * only(?) assign insn_device_config if we have global names for + * this device. + */ + dev->insn_device_config = ni_global_insn_config; + dev->get_valid_routes = _ni_get_valid_routes; + } + + n_counters = board->n_chips * NI660X_COUNTERS_PER_CHIP; + gpct_dev = ni_gpct_device_construct(dev, + ni_660x_gpct_write, + ni_660x_gpct_read, + ni_gpct_variant_660x, + n_counters, + NI660X_COUNTERS_PER_CHIP, + &devpriv->routing_tables); + if (!gpct_dev) + return -ENOMEM; + devpriv->counter_dev = gpct_dev; + + ret = comedi_alloc_subdevices(dev, 2 + NI660X_MAX_COUNTERS); + if (ret) + return ret; + + subdev = 0; + + s = &dev->subdevices[subdev++]; + /* Old GENERAL-PURPOSE COUNTER/TIME (GPCT) subdevice, no longer used */ + s->type = COMEDI_SUBD_UNUSED; + + /* + * Digital I/O subdevice + * + * There are 40 channels but only the first 32 can be digital I/Os. + * The last 8 are dedicated to counters 0 and 1. + * + * Counter 0-3 signals are from the first TIO chip. + * Counter 4-7 signals are from the second TIO chip. + * + * Comedi External + * PFI Chan DIO Chan Counter Signal + * ------- -------- -------------- + * 0 0 + * 1 1 + * 2 2 + * 3 3 + * 4 4 + * 5 5 + * 6 6 + * 7 7 + * 8 8 CTR 7 OUT + * 9 9 CTR 7 AUX + * 10 10 CTR 7 GATE + * 11 11 CTR 7 SOURCE + * 12 12 CTR 6 OUT + * 13 13 CTR 6 AUX + * 14 14 CTR 6 GATE + * 15 15 CTR 6 SOURCE + * 16 16 CTR 5 OUT + * 17 17 CTR 5 AUX + * 18 18 CTR 5 GATE + * 19 19 CTR 5 SOURCE + * 20 20 CTR 4 OUT + * 21 21 CTR 4 AUX + * 22 22 CTR 4 GATE + * 23 23 CTR 4 SOURCE + * 24 24 CTR 3 OUT + * 25 25 CTR 3 AUX + * 26 26 CTR 3 GATE + * 27 27 CTR 3 SOURCE + * 28 28 CTR 2 OUT + * 29 29 CTR 2 AUX + * 30 30 CTR 2 GATE + * 31 31 CTR 2 SOURCE + * 32 CTR 1 OUT + * 33 CTR 1 AUX + * 34 CTR 1 GATE + * 35 CTR 1 SOURCE + * 36 CTR 0 OUT + * 37 CTR 0 AUX + * 38 CTR 0 GATE + * 39 CTR 0 SOURCE + */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = NI660X_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; + + /* + * Default the DIO channels as: + * chan 0-7: DIO inputs + * chan 8-39: counter signal inputs + */ + for (i = 0; i < s->n_chan; ++i) { + unsigned int source = (i < 8) ? NI_660X_PFI_OUTPUT_DIO + : NI_660X_PFI_OUTPUT_COUNTER; + + ni_660x_set_pfi_routing(dev, i, source); + ni_660x_set_pfi_direction(dev, i, COMEDI_INPUT);/* high-z */ + } + + /* Counter subdevices (4 NI TIO General Purpose Counters per chip) */ + for (i = 0; i < NI660X_MAX_COUNTERS; ++i) { + s = &dev->subdevices[subdev++]; + if (i < n_counters) { + struct ni_gpct *counter = &gpct_dev->counters[i]; + + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | + SDF_LSAMPL | SDF_CMD_READ; + s->n_chan = 3; + s->maxdata = 0xffffffff; + s->insn_read = ni_tio_insn_read; + s->insn_write = ni_tio_insn_write; + s->insn_config = ni_tio_insn_config; + s->len_chanlist = 1; + s->do_cmd = ni_660x_cmd; + s->do_cmdtest = ni_tio_cmdtest; + s->cancel = ni_660x_cancel; + s->poll = ni_660x_input_poll; + s->buf_change = ni_660x_buf_change; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->private = counter; + + ni_tio_init_counter(counter); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + } + + /* + * To be safe, set counterswap bits on tio chips after all the counter + * outputs have been set to high impedance mode. + */ + for (i = 0; i < board->n_chips; ++i) + set_tio_counterswap(dev, i); + + ret = request_irq(pcidev->irq, ni_660x_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret < 0) { + dev_warn(dev->class_dev, " irq not available\n"); + return ret; + } + dev->irq = pcidev->irq; + global_interrupt_config_bits = NI660X_GLOBAL_INT_GLOBAL; + if (board->n_chips > 1) + global_interrupt_config_bits |= NI660X_GLOBAL_INT_CASCADE; + ni_660x_write(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) { + ni_660x_write(dev, 0, 0, NI660X_GLOBAL_INT_CFG); + free_irq(dev->irq, dev); + } + if (devpriv) { + ni_gpct_device_destroy(devpriv->counter_dev); + ni_660x_free_mite_rings(dev); + mite_detach(devpriv->mite); + } + if (dev->mmio) + iounmap(dev->mmio); + comedi_pci_disable(dev); +} + +static struct comedi_driver ni_660x_driver = { + .driver_name = "ni_660x", + .module = THIS_MODULE, + .auto_attach = ni_660x_auto_attach, + .detach = ni_660x_detach, +}; + +static int ni_660x_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni_660x_driver, id->driver_data); +} + +static const struct pci_device_id ni_660x_pci_table[] = { + { PCI_VDEVICE(NI, 0x1310), BOARD_PCI6602 }, + { PCI_VDEVICE(NI, 0x1360), BOARD_PXI6602 }, + { PCI_VDEVICE(NI, 0x2c60), BOARD_PCI6601 }, + { PCI_VDEVICE(NI, 0x2db0), BOARD_PCI6608 }, + { PCI_VDEVICE(NI, 0x2cc0), BOARD_PXI6608 }, + { PCI_VDEVICE(NI, 0x1e30), BOARD_PCI6624 }, + { 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for NI 660x counter/timer boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_670x.c b/drivers/comedi/drivers/ni_670x.c new file mode 100644 index 000000000..c875d251c --- /dev/null +++ b/drivers/comedi/drivers/ni_670x.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for NI 670x devices + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-2001 David A. Schleef <ds@schleef.org> + */ + +/* + * 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. + * + * Manuals: + * 322110a.pdf PCI/PXI-6704 User Manual + * 322110b.pdf PCI/PXI-6703/6704 User Manual + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/comedi/comedi_pci.h> + +#define AO_VALUE_OFFSET 0x00 +#define AO_CHAN_OFFSET 0x0c +#define AO_STATUS_OFFSET 0x10 +#define AO_CONTROL_OFFSET 0x10 +#define DIO_PORT0_DIR_OFFSET 0x20 +#define DIO_PORT0_DATA_OFFSET 0x24 +#define DIO_PORT1_DIR_OFFSET 0x28 +#define DIO_PORT1_DATA_OFFSET 0x2c +#define MISC_STATUS_OFFSET 0x14 +#define MISC_CONTROL_OFFSET 0x14 + +enum ni_670x_boardid { + BOARD_PCI6703, + BOARD_PXI6704, + BOARD_PCI6704, +}; + +struct ni_670x_board { + const char *name; + unsigned short ao_chans; +}; + +static const struct ni_670x_board ni_670x_boards[] = { + [BOARD_PCI6703] = { + .name = "PCI-6703", + .ao_chans = 16, + }, + [BOARD_PXI6704] = { + .name = "PXI-6704", + .ao_chans = 32, + }, + [BOARD_PCI6704] = { + .name = "PCI-6704", + .ao_chans = 32, + }, +}; + +struct ni_670x_private { + int boardtype; + int dio; +}; + +static int ni_670x_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + /* + * Channel number mapping: + * + * NI 6703/ NI 6704 | NI 6704 Only + * ------------------------------- + * vch(0) : 0 | ich(16) : 1 + * vch(1) : 2 | ich(17) : 3 + * ... | ... + * vch(15) : 30 | ich(31) : 31 + */ + for (i = 0; i < insn->n; i++) { + val = data[i]; + /* First write in channel register which channel to use */ + writel(((chan & 15) << 1) | ((chan & 16) >> 4), + dev->mmio + AO_CHAN_OFFSET); + /* write channel value */ + writel(val, dev->mmio + AO_VALUE_OFFSET); + } + s->readback[chan] = val; + + return insn->n; +} + +static int ni_670x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + writel(s->state, dev->mmio + DIO_PORT0_DATA_OFFSET); + + data[1] = readl(dev->mmio + DIO_PORT0_DATA_OFFSET); + + return insn->n; +} + +static int ni_670x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + writel(s->io_bits, dev->mmio + DIO_PORT0_DIR_OFFSET); + + return insn->n; +} + +/* ripped from mite.h and mite_setup2() to avoid mite dependency */ +#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */ +#define WENAB BIT(7) /* window enable */ + +static int ni_670x_mite_init(struct pci_dev *pcidev) +{ + void __iomem *mite_base; + u32 main_phys_addr; + + /* ioremap the MITE registers (BAR 0) temporarily */ + mite_base = pci_ioremap_bar(pcidev, 0); + if (!mite_base) + return -ENOMEM; + + /* set data window to main registers (BAR 1) */ + main_phys_addr = pci_resource_start(pcidev, 1); + writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR); + + /* finished with MITE registers */ + iounmap(mite_base); + return 0; +} + +static int ni_670x_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct ni_670x_board *board = NULL; + struct ni_670x_private *devpriv; + struct comedi_subdevice *s; + int ret; + int i; + + if (context < ARRAY_SIZE(ni_670x_boards)) + board = &ni_670x_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; + + ret = ni_670x_mite_init(pcidev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 1); + if (!dev->mmio) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->ao_chans; + s->maxdata = 0xffff; + if (s->n_chan == 32) { + const struct comedi_lrange **range_table_list; + + range_table_list = kmalloc_array(32, + sizeof(struct comedi_lrange *), + GFP_KERNEL); + if (!range_table_list) + return -ENOMEM; + s->range_table_list = range_table_list; + for (i = 0; i < 16; i++) { + range_table_list[i] = &range_bipolar10; + range_table_list[16 + i] = &range_0_20mA; + } + } else { + s->range_table = &range_bipolar10; + } + s->insn_write = ni_670x_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[1]; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_670x_dio_insn_bits; + s->insn_config = ni_670x_dio_insn_config; + + /* Config of misc registers */ + writel(0x10, dev->mmio + MISC_CONTROL_OFFSET); + /* Config of ao registers */ + writel(0x00, dev->mmio + AO_CONTROL_OFFSET); + + return 0; +} + +static void ni_670x_detach(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + + comedi_pci_detach(dev); + if (dev->n_subdevices) { + s = &dev->subdevices[0]; + if (s) + kfree(s->range_table_list); + } +} + +static struct comedi_driver ni_670x_driver = { + .driver_name = "ni_670x", + .module = THIS_MODULE, + .auto_attach = ni_670x_auto_attach, + .detach = ni_670x_detach, +}; + +static int ni_670x_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni_670x_driver, id->driver_data); +} + +static const struct pci_device_id ni_670x_pci_table[] = { + { PCI_VDEVICE(NI, 0x1290), BOARD_PCI6704 }, + { PCI_VDEVICE(NI, 0x1920), BOARD_PXI6704 }, + { PCI_VDEVICE(NI, 0x2c90), BOARD_PCI6703 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ni_670x_pci_table); + +static struct pci_driver ni_670x_pci_driver = { + .name = "ni_670x", + .id_table = ni_670x_pci_table, + .probe = ni_670x_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(ni_670x_driver, ni_670x_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_at_a2150.c b/drivers/comedi/drivers/ni_at_a2150.c new file mode 100644 index 000000000..df8d219e6 --- /dev/null +++ b/drivers/comedi/drivers/ni_at_a2150.c @@ -0,0 +1,780 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi 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> + */ + +/* + * 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) + * + * 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. + * + * If you want to ac couple the board's inputs, use AREF_OTHER. + * + * The only difference in the boards is their master clock frequencies. + * + * References (from ftp://ftp.natinst.com/support/manuals): + * 320360.pdf AT-A2150 User Manual + * + * TODO: + * - analog level triggering + * - TRIG_WAKE_EOS + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> +#include <linux/comedi/comedi_isadma.h> + +#define A2150_DMA_BUFFER_SIZE 0xff00 /* size in bytes of dma buffer */ + +/* Registers and bits */ +#define CONFIG_REG 0x0 +#define CHANNEL_BITS(x) ((x) & 0x7) +#define CHANNEL_MASK 0x7 +#define CLOCK_SELECT_BITS(x) (((x) & 0x3) << 3) +#define CLOCK_DIVISOR_BITS(x) (((x) & 0x3) << 5) +#define CLOCK_MASK (0xf << 3) +/* enable (don't internally ground) channels 0 and 1 */ +#define ENABLE0_BIT 0x80 +/* enable (don't internally ground) channels 2 and 3 */ +#define ENABLE1_BIT 0x100 +#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 */ +/* dma terminal count interrupt has occurred */ +#define DMA_TC_BIT 0x80 +#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 */ +/* enable interrupt on dma terminal count */ +#define DMA_INTR_EN_BIT 0x800 +#define DMA_DEM_EN_BIT 0x1000 /* enables demand mode dma */ +#define I8253_BASE_REG 0x14 + +struct a2150_board { + const char *name; + int clock[4]; /* master clock periods, in nanoseconds */ + int num_clocks; /* number of available master clock speeds */ + int ai_speed; /* maximum conversion rate in nanoseconds */ +}; + +/* analog input range */ +static const struct comedi_lrange range_a2150 = { + 1, { + BIP_RANGE(2.828) + } +}; + +/* enum must match board indices */ +enum { a2150_c, a2150_s }; +static const struct a2150_board a2150_boards[] = { + { + .name = "at-a2150c", + .clock = {31250, 22676, 20833, 19531}, + .num_clocks = 4, + .ai_speed = 19531, + }, + { + .name = "at-a2150s", + .clock = {62500, 50000, 41667, 0}, + .num_clocks = 3, + .ai_speed = 41667, + }, +}; + +struct a2150_private { + struct comedi_isadma *dma; + unsigned int count; /* number of data points left to be taken */ + int irq_dma_bits; /* irq/dma register bits */ + int config_bits; /* config register bits */ +}; + +/* interrupt service routine */ +static irqreturn_t a2150_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct a2150_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[0]; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned short *buf = desc->virt_addr; + unsigned int max_points, num_points, residue, leftover; + unsigned short dpnt; + int status; + int i; + + if (!dev->attached) + return IRQ_HANDLED; + + status = inw(dev->iobase + STATUS_REG); + if ((status & INTR_BIT) == 0) + return IRQ_NONE; + + if (status & OVFL_BIT) { + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + } + + if ((status & DMA_TC_BIT) == 0) { + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + return IRQ_HANDLED; + } + + /* + * residue is the number of bytes left to be done on the dma + * transfer. It should always be zero at this point unless + * the stop_src is set to external triggering. + */ + residue = comedi_isadma_disable(desc->chan); + + /* figure out how many points to read */ + max_points = comedi_bytes_to_samples(s, desc->size); + num_points = max_points - comedi_bytes_to_samples(s, residue); + if (devpriv->count < num_points && cmd->stop_src == TRIG_COUNT) + num_points = devpriv->count; + + /* figure out how many points will be stored next time */ + leftover = 0; + if (cmd->stop_src == TRIG_NONE) { + leftover = comedi_bytes_to_samples(s, desc->size); + } else if (devpriv->count > max_points) { + leftover = devpriv->count - max_points; + if (leftover > max_points) + leftover = max_points; + } + /* + * There should only be a residue if collection was stopped by having + * the stop_src set to an external trigger, in which case there + * will be no more data + */ + if (residue) + leftover = 0; + + for (i = 0; i < num_points; i++) { + /* write data point to comedi buffer */ + dpnt = buf[i]; + /* convert from 2's complement to unsigned coding */ + dpnt ^= 0x8000; + comedi_buf_write_samples(s, &dpnt, 1); + if (cmd->stop_src == TRIG_COUNT) { + if (--devpriv->count == 0) { /* end of acquisition */ + async->events |= COMEDI_CB_EOA; + break; + } + } + } + /* re-enable dma */ + if (leftover) { + desc->size = comedi_samples_to_bytes(s, leftover); + comedi_isadma_program(desc); + } + + comedi_handle_events(dev, s); + + /* clear interrupt */ + outw(0x00, dev->iobase + DMA_TC_CLEAR_REG); + + return IRQ_HANDLED; +} + +static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct a2150_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[0]; + + /* disable dma on card */ + devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* disable computer's dma */ + comedi_isadma_disable(desc->chan); + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + return 0; +} + +/* + * sets bits in devpriv->clock_bits to nearest approximation of requested + * period, adjusts requested period to actual timing. + */ +static int a2150_get_timing(struct comedi_device *dev, unsigned int *period, + unsigned int flags) +{ + const struct a2150_board *board = dev->board_ptr; + struct a2150_private *devpriv = dev->private; + int lub, glb, temp; + int lub_divisor_shift, lub_index, glb_divisor_shift, glb_index; + int i, j; + + /* initialize greatest lower and least upper bounds */ + lub_divisor_shift = 3; + lub_index = 0; + lub = board->clock[lub_index] * (1 << lub_divisor_shift); + glb_divisor_shift = 0; + glb_index = board->num_clocks - 1; + glb = board->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 < board->num_clocks; j++) { + /* temp is the period in nanosec we are evaluating */ + temp = board->clock[j] * (1 << i); + /* if it is the best match yet */ + if (temp < lub && temp >= *period) { + lub_divisor_shift = i; + lub_index = j; + lub = temp; + } + if (temp > glb && temp <= *period) { + glb_divisor_shift = i; + glb_index = j; + glb = temp; + } + } + } + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + /* if least upper bound is better approximation */ + if (lub - *period < *period - glb) + *period = lub; + else + *period = glb; + break; + case CMDF_ROUND_UP: + *period = lub; + break; + case CMDF_ROUND_DOWN: + *period = glb; + break; + } + + /* set clock bits for config register appropriately */ + devpriv->config_bits &= ~CLOCK_MASK; + if (*period == lub) { + devpriv->config_bits |= + CLOCK_SELECT_BITS(lub_index) | + CLOCK_DIVISOR_BITS(lub_divisor_shift); + } else { + devpriv->config_bits |= + CLOCK_SELECT_BITS(glb_index) | + CLOCK_DIVISOR_BITS(glb_divisor_shift); + } + + return 0; +} + +static int a2150_set_chanlist(struct comedi_device *dev, + unsigned int start_channel, + unsigned int num_channels) +{ + struct a2150_private *devpriv = dev->private; + + if (start_channel + num_channels > 4) + return -1; + + devpriv->config_bits &= ~CHANNEL_MASK; + + switch (num_channels) { + case 1: + devpriv->config_bits |= CHANNEL_BITS(0x4 | start_channel); + break; + case 2: + if (start_channel == 0) + devpriv->config_bits |= CHANNEL_BITS(0x2); + else if (start_channel == 2) + devpriv->config_bits |= CHANNEL_BITS(0x3); + else + return -1; + break; + case 4: + devpriv->config_bits |= CHANNEL_BITS(0x1); + break; + default: + return -1; + } + + return 0; +} + +static int a2150_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + if (cmd->chanlist_len == 2 && (chan0 == 1 || chan0 == 3)) { + dev_dbg(dev->class_dev, + "length 2 chanlist must be channels 0,1 or channels 2,3\n"); + return -EINVAL; + } + + if (cmd->chanlist_len == 3) { + dev_dbg(dev->class_dev, + "chanlist must have 1,2 or 4 channels\n"); + return -EINVAL; + } + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (chan != (chan0 + i)) { + dev_dbg(dev->class_dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + return -EINVAL; + } + + if (chan == 2) + aref0 = aref; + if (aref != aref0) { + dev_dbg(dev->class_dev, + "channels 0/1 and 2/3 must have the same analog reference\n"); + return -EINVAL; + } + } + + return 0; +} + +static int a2150_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct a2150_board *board = dev->board_ptr; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + a2150_get_timing(dev, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= a2150_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int a2150_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct a2150_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[0]; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int old_config_bits = devpriv->config_bits; + unsigned int trigger_bits; + + if (cmd->flags & CMDF_PRIORITY) { + dev_err(dev->class_dev, + "dma incompatible with hard real-time interrupt (CMDF_PRIORITY), aborting\n"); + return -1; + } + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + /* setup chanlist */ + if (a2150_set_chanlist(dev, CR_CHAN(cmd->chanlist[0]), + cmd->chanlist_len) < 0) + return -1; + + /* setup ac/dc coupling */ + if (CR_AREF(cmd->chanlist[0]) == AREF_OTHER) + devpriv->config_bits |= AC0_BIT; + else + devpriv->config_bits &= ~AC0_BIT; + if (CR_AREF(cmd->chanlist[2]) == AREF_OTHER) + devpriv->config_bits |= AC1_BIT; + else + devpriv->config_bits &= ~AC1_BIT; + + /* setup timing */ + a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags); + + /* send timing, channel, config bits */ + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + /* initialize number of samples remaining */ + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + + comedi_isadma_disable(desc->chan); + + /* set size of transfer to fill in 1/3 second */ +#define ONE_THIRD_SECOND 333333333 + desc->size = comedi_bytes_per_sample(s) * cmd->chanlist_len * + ONE_THIRD_SECOND / cmd->scan_begin_arg; + if (desc->size > desc->maxsize) + desc->size = desc->maxsize; + if (desc->size < comedi_bytes_per_sample(s)) + desc->size = comedi_bytes_per_sample(s); + desc->size -= desc->size % comedi_bytes_per_sample(s); + + comedi_isadma_program(desc); + + /* + * Clear dma interrupt before enabling it, to try and get rid of + * that one spurious interrupt that has been happening. + */ + outw(0x00, dev->iobase + DMA_TC_CLEAR_REG); + + /* enable dma on card */ + devpriv->irq_dma_bits |= DMA_INTR_EN_BIT | DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* may need to wait 72 sampling periods if timing was changed */ + comedi_8254_load(dev->pacer, 2, 72, I8254_MODE0 | I8254_BINARY); + + /* setup start triggering */ + trigger_bits = 0; + /* decide if we need to wait 72 periods for valid data */ + if (cmd->start_src == TRIG_NOW && + (old_config_bits & CLOCK_MASK) != + (devpriv->config_bits & CLOCK_MASK)) { + /* set trigger source to delay trigger */ + trigger_bits |= DELAY_TRIGGER_BITS; + } else { + /* otherwise no delay */ + trigger_bits |= POST_TRIGGER_BITS; + } + /* enable external hardware trigger */ + if (cmd->start_src == TRIG_EXT) { + trigger_bits |= HW_TRIG_EN; + } else if (cmd->start_src == TRIG_OTHER) { + /* + * XXX add support for level/slope start trigger + * using TRIG_OTHER + */ + dev_err(dev->class_dev, "you shouldn't see this?\n"); + } + /* send trigger config bits */ + outw(trigger_bits, dev->iobase + TRIGGER_REG); + + /* start acquisition for soft trigger */ + if (cmd->start_src == TRIG_NOW) + outw(0, dev->iobase + FIFO_START_REG); + + return 0; +} + +static int a2150_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + STATUS_REG); + if (status & FNE_BIT) + return 0; + return -EBUSY; +} + +static int a2150_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct a2150_private *devpriv = dev->private; + unsigned int n; + int ret; + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + /* setup chanlist */ + if (a2150_set_chanlist(dev, CR_CHAN(insn->chanspec), 1) < 0) + return -1; + + /* set dc coupling */ + devpriv->config_bits &= ~AC0_BIT; + devpriv->config_bits &= ~AC1_BIT; + + /* send timing, channel, config bits */ + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + /* disable dma on card */ + devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* setup start triggering */ + outw(0, dev->iobase + TRIGGER_REG); + + /* start acquisition for soft trigger */ + outw(0, dev->iobase + FIFO_START_REG); + + /* + * there is a 35.6 sample delay for data to get through the + * antialias filter + */ + for (n = 0; n < 36; n++) { + ret = comedi_timeout(dev, s, insn, a2150_ai_eoc, 0); + if (ret) + return ret; + + inw(dev->iobase + FIFO_DATA_REG); + } + + /* read data */ + for (n = 0; n < insn->n; n++) { + ret = comedi_timeout(dev, s, insn, a2150_ai_eoc, 0); + if (ret) + return ret; + + data[n] = inw(dev->iobase + FIFO_DATA_REG); + data[n] ^= 0x8000; + } + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + return n; +} + +static void a2150_alloc_irq_and_dma(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct a2150_private *devpriv = dev->private; + unsigned int irq_num = it->options[1]; + unsigned int dma_chan = it->options[2]; + + /* + * Only IRQs 15, 14, 12-9, and 7-3 are valid. + * Only DMA channels 7-5 and 3-0 are valid. + */ + if (irq_num > 15 || dma_chan > 7 || + !((1 << irq_num) & 0xdef8) || !((1 << dma_chan) & 0xef)) + return; + + if (request_irq(irq_num, a2150_interrupt, 0, dev->board_name, dev)) + return; + + /* DMA uses 1 buffer */ + devpriv->dma = comedi_isadma_alloc(dev, 1, dma_chan, dma_chan, + A2150_DMA_BUFFER_SIZE, + COMEDI_ISADMA_READ); + if (!devpriv->dma) { + free_irq(irq_num, dev); + } else { + dev->irq = irq_num; + devpriv->irq_dma_bits = IRQ_LVL_BITS(irq_num) | + DMA_CHAN_BITS(dma_chan); + } +} + +static void a2150_free_dma(struct comedi_device *dev) +{ + struct a2150_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} + +static const struct a2150_board *a2150_probe(struct comedi_device *dev) +{ + int id = ID_BITS(inw(dev->iobase + STATUS_REG)); + + if (id >= ARRAY_SIZE(a2150_boards)) + return NULL; + + return &a2150_boards[id]; +} + +static int a2150_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct a2150_board *board; + struct a2150_private *devpriv; + struct comedi_subdevice *s; + static const int timeout = 2000; + int i; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x1c); + if (ret) + return ret; + + board = a2150_probe(dev); + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + /* an IRQ and DMA are required to support async commands */ + a2150_alloc_irq_and_dma(dev, it); + + dev->pacer = comedi_8254_init(dev->iobase + I8253_BASE_REG, + 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + /* analog input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_OTHER; + s->n_chan = 4; + s->maxdata = 0xffff; + s->range_table = &range_a2150; + s->insn_read = a2150_ai_rinsn; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmd = a2150_ai_cmd; + s->do_cmdtest = a2150_ai_cmdtest; + s->cancel = a2150_cancel; + } + + /* set card's irq and dma levels */ + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* reset and sync adc clock circuitry */ + outw_p(DPD_BIT | APD_BIT, dev->iobase + CONFIG_REG); + outw_p(DPD_BIT, dev->iobase + CONFIG_REG); + /* initialize configuration register */ + devpriv->config_bits = 0; + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + /* wait until offset calibration is done, then enable analog inputs */ + for (i = 0; i < timeout; i++) { + if ((DCAL_BIT & inw(dev->iobase + STATUS_REG)) == 0) + break; + usleep_range(1000, 3000); + } + if (i == timeout) { + dev_err(dev->class_dev, + "timed out waiting for offset calibration to complete\n"); + return -ETIME; + } + devpriv->config_bits |= ENABLE0_BIT | ENABLE1_BIT; + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + return 0; +}; + +static void a2150_detach(struct comedi_device *dev) +{ + if (dev->iobase) + outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG); + a2150_free_dma(dev); + comedi_legacy_detach(dev); +}; + +static struct comedi_driver ni_at_a2150_driver = { + .driver_name = "ni_at_a2150", + .module = THIS_MODULE, + .attach = a2150_attach, + .detach = a2150_detach, +}; +module_comedi_driver(ni_at_a2150_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_at_ao.c b/drivers/comedi/drivers/ni_at_ao.c new file mode 100644 index 000000000..9f3147b72 --- /dev/null +++ b/drivers/comedi/drivers/ni_at_ao.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: ni_at_ao + * Description: National Instruments AT-AO-6/10 + * Devices: [National Instruments] AT-AO-6 (at-ao-6), AT-AO-10 (at-ao-10) + * Status: should work + * Author: David A. Schleef <ds@schleef.org> + * Updated: Sun Dec 26 12:26:28 EST 2004 + * + * Configuration options: + * [0] - I/O port base address + * [1] - IRQ (unused) + * [2] - DMA (unused) + * [3] - analog output range, set by jumpers on hardware + * 0 for -10 to 10V bipolar + * 1 for 0V to 10V unipolar + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> + +/* + * Register map + * + * Register-level programming information can be found in NI + * document 320379.pdf. + */ +#define ATAO_DIO_REG 0x00 +#define ATAO_CFG2_REG 0x02 +#define ATAO_CFG2_CALLD_NOP (0 << 14) +#define ATAO_CFG2_CALLD(x) ((((x) >> 3) + 1) << 14) +#define ATAO_CFG2_FFRTEN BIT(13) +#define ATAO_CFG2_DACS(x) (1 << (((x) / 2) + 8)) +#define ATAO_CFG2_LDAC(x) (1 << (((x) / 2) + 3)) +#define ATAO_CFG2_PROMEN BIT(2) +#define ATAO_CFG2_SCLK BIT(1) +#define ATAO_CFG2_SDATA BIT(0) +#define ATAO_CFG3_REG 0x04 +#define ATAO_CFG3_DMAMODE BIT(6) +#define ATAO_CFG3_CLKOUT BIT(5) +#define ATAO_CFG3_RCLKEN BIT(4) +#define ATAO_CFG3_DOUTEN2 BIT(3) +#define ATAO_CFG3_DOUTEN1 BIT(2) +#define ATAO_CFG3_EN2_5V BIT(1) +#define ATAO_CFG3_SCANEN BIT(0) +#define ATAO_82C53_BASE 0x06 +#define ATAO_CFG1_REG 0x0a +#define ATAO_CFG1_EXTINT2EN BIT(15) +#define ATAO_CFG1_EXTINT1EN BIT(14) +#define ATAO_CFG1_CNTINT2EN BIT(13) +#define ATAO_CFG1_CNTINT1EN BIT(12) +#define ATAO_CFG1_TCINTEN BIT(11) +#define ATAO_CFG1_CNT1SRC BIT(10) +#define ATAO_CFG1_CNT2SRC BIT(9) +#define ATAO_CFG1_FIFOEN BIT(8) +#define ATAO_CFG1_GRP2WR BIT(7) +#define ATAO_CFG1_EXTUPDEN BIT(6) +#define ATAO_CFG1_DMARQ BIT(5) +#define ATAO_CFG1_DMAEN BIT(4) +#define ATAO_CFG1_CH(x) (((x) & 0xf) << 0) +#define ATAO_STATUS_REG 0x0a +#define ATAO_STATUS_FH BIT(6) +#define ATAO_STATUS_FE BIT(5) +#define ATAO_STATUS_FF BIT(4) +#define ATAO_STATUS_INT2 BIT(3) +#define ATAO_STATUS_INT1 BIT(2) +#define ATAO_STATUS_TCINT BIT(1) +#define ATAO_STATUS_PROMOUT BIT(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 BIT(0) +#define ATAO_2_RTSISTRB_REG 0x07 + +struct atao_board { + const char *name; + int n_ao_chans; +}; + +static const struct atao_board atao_boards[] = { + { + .name = "at-ao-6", + .n_ao_chans = 6, + }, { + .name = "at-ao-10", + .n_ao_chans = 10, + }, +}; + +struct atao_private { + unsigned short cfg1; + unsigned short cfg3; + + /* Used for caldac readback */ + unsigned char caldac[21]; +}; + +static void atao_select_reg_group(struct comedi_device *dev, int group) +{ + struct atao_private *devpriv = dev->private; + + if (group) + devpriv->cfg1 |= ATAO_CFG1_GRP2WR; + else + devpriv->cfg1 &= ~ATAO_CFG1_GRP2WR; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1_REG); +} + +static int atao_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + if (chan == 0) + atao_select_reg_group(dev, 1); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + + /* the hardware expects two's complement values */ + outw(comedi_offset_munge(s, val), + dev->iobase + ATAO_AO_REG(chan)); + } + s->readback[chan] = val; + + if (chan == 0) + atao_select_reg_group(dev, 0); + + return insn->n; +} + +static int atao_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + ATAO_DIO_REG); + + data[1] = inw(dev->iobase + ATAO_DIO_REG); + + return insn->n; +} + +static int atao_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct atao_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 4) + mask = 0x0f; + else + mask = 0xf0; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + if (s->io_bits & 0x0f) + devpriv->cfg3 |= ATAO_CFG3_DOUTEN1; + else + devpriv->cfg3 &= ~ATAO_CFG3_DOUTEN1; + if (s->io_bits & 0xf0) + devpriv->cfg3 |= ATAO_CFG3_DOUTEN2; + else + devpriv->cfg3 &= ~ATAO_CFG3_DOUTEN2; + + outw(devpriv->cfg3, dev->iobase + ATAO_CFG3_REG); + + return insn->n; +} + +/* + * There are three DAC8800 TrimDACs on the board. These are 8-channel, + * 8-bit DACs that are used to calibrate the Analog Output channels. + * The factory default calibration values are stored in the EEPROM. + * The TrimDACs, and EEPROM addresses, are mapped as: + * + * Channel EEPROM Description + * ----------------- ------ ----------------------------------- + * 0 - DAC0 Chan 0 0x30 AO Channel 0 Offset + * 1 - DAC0 Chan 1 0x31 AO Channel 0 Gain + * 2 - DAC0 Chan 2 0x32 AO Channel 1 Offset + * 3 - DAC0 Chan 3 0x33 AO Channel 1 Gain + * 4 - DAC0 Chan 4 0x34 AO Channel 2 Offset + * 5 - DAC0 Chan 5 0x35 AO Channel 2 Gain + * 6 - DAC0 Chan 6 0x36 AO Channel 3 Offset + * 7 - DAC0 Chan 7 0x37 AO Channel 3 Gain + * 8 - DAC1 Chan 0 0x38 AO Channel 4 Offset + * 9 - DAC1 Chan 1 0x39 AO Channel 4 Gain + * 10 - DAC1 Chan 2 0x3a AO Channel 5 Offset + * 11 - DAC1 Chan 3 0x3b AO Channel 5 Gain + * 12 - DAC1 Chan 4 0x3c 2.5V Offset + * 13 - DAC1 Chan 5 0x3d AO Channel 6 Offset (at-ao-10 only) + * 14 - DAC1 Chan 6 0x3e AO Channel 6 Gain (at-ao-10 only) + * 15 - DAC1 Chan 7 0x3f AO Channel 7 Offset (at-ao-10 only) + * 16 - DAC2 Chan 0 0x40 AO Channel 7 Gain (at-ao-10 only) + * 17 - DAC2 Chan 1 0x41 AO Channel 8 Offset (at-ao-10 only) + * 18 - DAC2 Chan 2 0x42 AO Channel 8 Gain (at-ao-10 only) + * 19 - DAC2 Chan 3 0x43 AO Channel 9 Offset (at-ao-10 only) + * 20 - DAC2 Chan 4 0x44 AO Channel 9 Gain (at-ao-10 only) + * DAC2 Chan 5 0x45 Reserved + * DAC2 Chan 6 0x46 Reserved + * DAC2 Chan 7 0x47 Reserved + */ +static int atao_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + if (insn->n) { + unsigned int val = data[insn->n - 1]; + unsigned int bitstring = ((chan & 0x7) << 8) | val; + unsigned int bits; + int bit; + + /* write the channel and last data value to the caldac */ + /* clock the bitstring to the caldac; MSB -> LSB */ + for (bit = BIT(10); bit; bit >>= 1) { + bits = (bit & bitstring) ? ATAO_CFG2_SDATA : 0; + + outw(bits, dev->iobase + ATAO_CFG2_REG); + outw(bits | ATAO_CFG2_SCLK, + dev->iobase + ATAO_CFG2_REG); + } + + /* strobe the caldac to load the value */ + outw(ATAO_CFG2_CALLD(chan), dev->iobase + ATAO_CFG2_REG); + outw(ATAO_CFG2_CALLD_NOP, dev->iobase + ATAO_CFG2_REG); + + s->readback[chan] = val; + } + + return insn->n; +} + +static void atao_reset(struct comedi_device *dev) +{ + struct atao_private *devpriv = dev->private; + + /* This is the reset sequence described in the manual */ + + devpriv->cfg1 = 0; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1_REG); + + /* Put outputs of counter 1 and counter 2 in a high state */ + comedi_8254_set_mode(dev->pacer, 0, I8254_MODE4 | I8254_BINARY); + comedi_8254_set_mode(dev->pacer, 1, I8254_MODE4 | I8254_BINARY); + comedi_8254_write(dev->pacer, 0, 0x0003); + + outw(ATAO_CFG2_CALLD_NOP, dev->iobase + ATAO_CFG2_REG); + + devpriv->cfg3 = 0; + outw(devpriv->cfg3, dev->iobase + ATAO_CFG3_REG); + + inw(dev->iobase + ATAO_FIFO_CLEAR_REG); + + atao_select_reg_group(dev, 1); + outw(0, dev->iobase + ATAO_2_INT1CLR_REG); + outw(0, dev->iobase + ATAO_2_INT2CLR_REG); + outw(0, dev->iobase + ATAO_2_DMATCCLR_REG); + atao_select_reg_group(dev, 0); +} + +static int atao_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct atao_board *board = dev->board_ptr; + struct atao_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x20); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + dev->pacer = comedi_8254_init(dev->iobase + ATAO_82C53_BASE, + 0, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->n_ao_chans; + s->maxdata = 0x0fff; + s->range_table = it->options[3] ? &range_unipolar10 : &range_bipolar10; + s->insn_write = atao_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = atao_dio_insn_bits; + s->insn_config = atao_dio_insn_config; + + /* caldac subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = (board->n_ao_chans * 2) + 1; + s->maxdata = 0xff; + s->insn_write = atao_calib_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* EEPROM subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_UNUSED; + + atao_reset(dev); + + return 0; +} + +static struct comedi_driver ni_at_ao_driver = { + .driver_name = "ni_at_ao", + .module = THIS_MODULE, + .attach = atao_attach, + .detach = comedi_legacy_detach, + .board_name = &atao_boards[0].name, + .offset = sizeof(struct atao_board), + .num_names = ARRAY_SIZE(atao_boards), +}; +module_comedi_driver(ni_at_ao_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for NI AT-AO-6/10 boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_atmio.c b/drivers/comedi/drivers/ni_atmio.c new file mode 100644 index 000000000..8876a1d24 --- /dev/null +++ b/drivers/comedi/drivers/ni_atmio.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/isapnp.h> +#include <linux/comedi/comedi_8255.h> + +#include "ni_stc.h" + +/* AT specific setup */ +static const struct ni_board_struct ni_boards[] = { + { + .name = "at-mio-16e-1", + .device_id = 44, + .isapnp_id = 0x0000, /* XXX unknown */ + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { mb88341 }, + }, { + .name = "at-mio-16e-2", + .device_id = 25, + .isapnp_id = 0x1900, + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 2048, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { mb88341 }, + }, { + .name = "at-mio-16e-10", + .device_id = 36, + .isapnp_id = 0x2400, + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 10000, + .caldac = { ad8804_debug }, + }, { + .name = "at-mio-16de-10", + .device_id = 37, + .isapnp_id = 0x2500, + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 10000, + .caldac = { ad8804_debug }, + .has_8255 = 1, + }, { + .name = "at-mio-64e-3", + .device_id = 38, + .isapnp_id = 0x2600, + .n_adchan = 64, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 2048, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { ad8804_debug }, + }, { + .name = "at-mio-16xe-50", + .device_id = 39, + .isapnp_id = 0x2700, + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_8, + .ai_speed = 50000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_bipolar10, + .ao_speed = 50000, + .caldac = { dac8800, dac8043 }, + }, { + .name = "at-mio-16xe-10", + .device_id = 50, + .isapnp_id = 0x0000, /* XXX unknown */ + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { dac8800, dac8043, ad8522 }, + }, { + .name = "at-ai-16xe-10", + .device_id = 51, + .isapnp_id = 0x0000, /* XXX unknown */ + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, /* unknown */ + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, +}; + +static const int ni_irqpin[] = { + -1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7 +}; + +#include "ni_mio_common.c" + +static const struct pnp_device_id device_ids[] = { + {.id = "NIC1900", .driver_data = 0}, + {.id = "NIC2400", .driver_data = 0}, + {.id = "NIC2500", .driver_data = 0}, + {.id = "NIC2600", .driver_data = 0}, + {.id = "NIC2700", .driver_data = 0}, + {.id = ""} +}; + +MODULE_DEVICE_TABLE(pnp, device_ids); + +static int ni_isapnp_find_board(struct pnp_dev **dev) +{ + struct pnp_dev *isapnp_dev = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { + isapnp_dev = + pnp_find_dev(NULL, + ISAPNP_VENDOR('N', 'I', 'C'), + ISAPNP_FUNCTION(ni_boards[i].isapnp_id), + NULL); + + if (!isapnp_dev || !isapnp_dev->card) + continue; + + if (pnp_device_attach(isapnp_dev) < 0) + continue; + + if (pnp_activate_dev(isapnp_dev) < 0) { + pnp_device_detach(isapnp_dev); + return -EAGAIN; + } + + if (!pnp_port_valid(isapnp_dev, 0) || + !pnp_irq_valid(isapnp_dev, 0)) { + pnp_device_detach(isapnp_dev); + return -ENOMEM; + } + break; + } + if (i == ARRAY_SIZE(ni_boards)) + return -ENODEV; + *dev = isapnp_dev; + return 0; +} + +static const struct ni_board_struct *ni_atmio_probe(struct comedi_device *dev) +{ + int device_id = ni_read_eeprom(dev, 511); + int i; + + for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { + const struct ni_board_struct *board = &ni_boards[i]; + + if (board->device_id == device_id) + return board; + } + if (device_id == 255) + dev_err(dev->class_dev, "can't find board\n"); + else if (device_id == 0) + dev_err(dev->class_dev, + "EEPROM read error (?) or device not found\n"); + else + dev_err(dev->class_dev, + "unknown device ID %d -- contact author\n", device_id); + + return NULL; +} + +static int ni_atmio_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct ni_board_struct *board; + struct pnp_dev *isapnp_dev; + int ret; + unsigned long iobase; + unsigned int irq; + + ret = ni_alloc_private(dev); + if (ret) + return ret; + + iobase = it->options[0]; + irq = it->options[1]; + isapnp_dev = NULL; + if (iobase == 0) { + ret = ni_isapnp_find_board(&isapnp_dev); + if (ret < 0) + return ret; + + iobase = pnp_port_start(isapnp_dev, 0); + irq = pnp_irq(isapnp_dev, 0); + comedi_set_hw_dev(dev, &isapnp_dev->dev); + } + + ret = comedi_request_region(dev, iobase, 0x20); + if (ret) + return ret; + + board = ni_atmio_probe(dev); + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + /* irq stuff */ + + if (irq != 0) { + if (irq > 15 || ni_irqpin[irq] == -1) + return -EINVAL; + ret = request_irq(irq, ni_E_interrupt, 0, + dev->board_name, dev); + if (ret < 0) + return -EINVAL; + dev->irq = irq; + } + + /* generic E series stuff in ni_mio_common.c */ + + ret = ni_E_init(dev, ni_irqpin[dev->irq], 0); + if (ret < 0) + return ret; + + return 0; +} + +static void ni_atmio_detach(struct comedi_device *dev) +{ + struct pnp_dev *isapnp_dev; + + mio_common_detach(dev); + comedi_legacy_detach(dev); + + isapnp_dev = dev->hw_dev ? to_pnp_dev(dev->hw_dev) : NULL; + if (isapnp_dev) + pnp_device_detach(isapnp_dev); +} + +static struct comedi_driver ni_atmio_driver = { + .driver_name = "ni_atmio", + .module = THIS_MODULE, + .attach = ni_atmio_attach, + .detach = ni_atmio_detach, +}; +module_comedi_driver(ni_atmio_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/comedi/drivers/ni_atmio16d.c b/drivers/comedi/drivers/ni_atmio16d.c new file mode 100644 index 000000000..9fa902529 --- /dev/null +++ b/drivers/comedi/drivers/ni_atmio16d.c @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for National Instruments AT-MIO16D board + * Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com> + */ + +/* + * 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) + * + * Configuration options: + * [0] - I/O port + * [1] - MIO irq (0 == no irq; or 3,4,5,6,7,9,10,11,12,14,15) + * [2] - DIO irq (0 == no irq; or 3,4,5,6,7,9) + * [3] - DMA1 channel (0 == no DMA; or 5,6,7) + * [4] - DMA2 channel (0 == no DMA; or 5,6,7) + * [5] - a/d mux (0=differential; 1=single) + * [6] - a/d range (0=bipolar10; 1=bipolar5; 2=unipolar10) + * [7] - dac0 range (0=bipolar; 1=unipolar) + * [8] - dac0 reference (0=internal; 1=external) + * [9] - dac0 coding (0=2's comp; 1=straight binary) + * [10] - dac1 range (same as dac0 options) + * [11] - dac1 reference (same as dac0 options) + * [12] - dac1 coding (same as dac0 options) + */ + +/* + * 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 <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +/* Configuration and Status Registers */ +#define COM_REG_1 0x00 /* wo 16 */ +#define STAT_REG 0x00 /* ro 16 */ +#define COM_REG_2 0x02 /* wo 16 */ +/* Event Strobe Registers */ +#define START_CONVERT_REG 0x08 /* wo 16 */ +#define START_DAQ_REG 0x0A /* wo 16 */ +#define AD_CLEAR_REG 0x0C /* wo 16 */ +#define EXT_STROBE_REG 0x0E /* wo 16 */ +/* Analog Output Registers */ +#define DAC0_REG 0x10 /* wo 16 */ +#define DAC1_REG 0x12 /* wo 16 */ +#define INT2CLR_REG 0x14 /* wo 16 */ +/* Analog Input Registers */ +#define MUX_CNTR_REG 0x04 /* wo 16 */ +#define MUX_GAIN_REG 0x06 /* wo 16 */ +#define AD_FIFO_REG 0x16 /* ro 16 */ +#define DMA_TC_INT_CLR_REG 0x16 /* wo 16 */ +/* AM9513A Counter/Timer Registers */ +#define AM9513A_DATA_REG 0x18 /* rw 16 */ +#define AM9513A_COM_REG 0x1A /* wo 16 */ +#define AM9513A_STAT_REG 0x1A /* ro 16 */ +/* MIO-16 Digital I/O Registers */ +#define MIO_16_DIG_IN_REG 0x1C /* ro 16 */ +#define MIO_16_DIG_OUT_REG 0x1C /* wo 16 */ +/* RTSI Switch Registers */ +#define RTSI_SW_SHIFT_REG 0x1E /* wo 8 */ +#define RTSI_SW_STROBE_REG 0x1F /* wo 8 */ +/* DIO-24 Registers */ +#define DIO_24_PORTA_REG 0x00 /* rw 8 */ +#define DIO_24_PORTB_REG 0x01 /* rw 8 */ +#define DIO_24_PORTC_REG 0x02 /* rw 8 */ +#define DIO_24_CNFG_REG 0x03 /* wo 8 */ + +/* Command Register bits */ +#define COMREG1_2SCADC 0x0001 +#define COMREG1_1632CNT 0x0002 +#define COMREG1_SCANEN 0x0008 +#define COMREG1_DAQEN 0x0010 +#define COMREG1_DMAEN 0x0020 +#define COMREG1_CONVINTEN 0x0080 +#define COMREG2_SCN2 0x0010 +#define COMREG2_INTEN 0x0080 +#define COMREG2_DOUTEN0 0x0100 +#define COMREG2_DOUTEN1 0x0200 +/* Status Register bits */ +#define STAT_AD_OVERRUN 0x0100 +#define STAT_AD_OVERFLOW 0x0200 +#define STAT_AD_DAQPROG 0x0800 +#define STAT_AD_CONVAVAIL 0x2000 +#define STAT_AD_DAQSTOPINT 0x4000 +/* AM9513A Counter/Timer defines */ +#define CLOCK_1_MHZ 0x8B25 +#define CLOCK_100_KHZ 0x8C25 +#define CLOCK_10_KHZ 0x8D25 +#define CLOCK_1_KHZ 0x8E25 +#define CLOCK_100_HZ 0x8F25 + +struct atmio16_board_t { + const char *name; + int has_8255; +}; + +/* range structs */ +static const struct comedi_lrange range_atmio16d_ai_10_bipolar = { + 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.02) + } +}; + +static const struct comedi_lrange range_atmio16d_ai_5_bipolar = { + 4, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_atmio16d_ai_unipolar = { + 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.02) + } +}; + +/* private data struct */ +struct atmio16d_private { + enum { adc_diff, adc_singleended } adc_mux; + enum { adc_bipolar10, adc_bipolar5, adc_unipolar10 } adc_range; + enum { adc_2comp, adc_straight } adc_coding; + enum { dac_bipolar, dac_unipolar } dac0_range, dac1_range; + enum { dac_internal, dac_external } dac0_reference, dac1_reference; + enum { dac_2comp, dac_straight } dac0_coding, dac1_coding; + const struct comedi_lrange *ao_range_type_list[2]; + unsigned int com_reg_1_state; /* current state of command register 1 */ + unsigned int com_reg_2_state; /* current state of command register 2 */ +}; + +static void reset_counters(struct comedi_device *dev) +{ + /* Counter 2 */ + outw(0xFFC2, dev->iobase + AM9513A_COM_REG); + outw(0xFF02, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0A, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF42, dev->iobase + AM9513A_COM_REG); + outw(0xFF42, dev->iobase + AM9513A_COM_REG); + /* Counter 3 */ + outw(0xFFC4, dev->iobase + AM9513A_COM_REG); + outw(0xFF03, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0B, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF44, dev->iobase + AM9513A_COM_REG); + outw(0xFF44, dev->iobase + AM9513A_COM_REG); + /* Counter 4 */ + outw(0xFFC8, dev->iobase + AM9513A_COM_REG); + outw(0xFF04, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0C, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + /* Counter 5 */ + outw(0xFFD0, dev->iobase + AM9513A_COM_REG); + outw(0xFF05, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0D, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF50, dev->iobase + AM9513A_COM_REG); + outw(0xFF50, dev->iobase + AM9513A_COM_REG); + + outw(0, dev->iobase + AD_CLEAR_REG); +} + +static void reset_atmio16d(struct comedi_device *dev) +{ + struct atmio16d_private *devpriv = dev->private; + int i; + + /* now we need to initialize the board */ + outw(0, dev->iobase + COM_REG_1); + outw(0, dev->iobase + COM_REG_2); + outw(0, dev->iobase + MUX_GAIN_REG); + /* init AM9513A timer */ + outw(0xFFFF, dev->iobase + AM9513A_COM_REG); + outw(0xFFEF, dev->iobase + AM9513A_COM_REG); + outw(0xFF17, dev->iobase + AM9513A_COM_REG); + outw(0xF000, dev->iobase + AM9513A_DATA_REG); + for (i = 1; i <= 5; ++i) { + outw(0xFF00 + i, dev->iobase + AM9513A_COM_REG); + outw(0x0004, dev->iobase + AM9513A_DATA_REG); + outw(0xFF08 + i, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + } + outw(0xFF5F, dev->iobase + AM9513A_COM_REG); + /* timer init done */ + outw(0, dev->iobase + AD_CLEAR_REG); + outw(0, dev->iobase + INT2CLR_REG); + /* select straight binary mode for Analog Input */ + devpriv->com_reg_1_state |= 1; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + devpriv->adc_coding = adc_straight; + /* zero the analog outputs */ + outw(2048, dev->iobase + DAC0_REG); + outw(2048, dev->iobase + DAC1_REG); +} + +static irqreturn_t atmio16d_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned short val; + + val = inw(dev->iobase + AD_FIFO_REG); + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int atmio16d_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + return 0; +} + +static int atmio16d_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct atmio16d_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int timer, base_clock; + unsigned int sample_count, tmp, chan, gain; + int i; + + /* + * This is slowly becoming a working command interface. + * It is still uber-experimental + */ + + reset_counters(dev); + + /* check if scanning multiple channels */ + if (cmd->chanlist_len < 2) { + devpriv->com_reg_1_state &= ~COMREG1_SCANEN; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + } else { + devpriv->com_reg_1_state |= COMREG1_SCANEN; + devpriv->com_reg_2_state |= COMREG2_SCN2; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2); + } + + /* Setup the Mux-Gain Counter */ + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + gain = CR_RANGE(cmd->chanlist[i]); + outw(i, dev->iobase + MUX_CNTR_REG); + tmp = chan | (gain << 6); + if (i == cmd->scan_end_arg - 1) + tmp |= 0x0010; /* set LASTONE bit */ + outw(tmp, dev->iobase + MUX_GAIN_REG); + } + + /* + * Now program the sample interval timer. + * Figure out which clock to use then get an appropriate timer value. + */ + if (cmd->convert_arg < 65536000) { + base_clock = CLOCK_1_MHZ; + timer = cmd->convert_arg / 1000; + } else if (cmd->convert_arg < 655360000) { + base_clock = CLOCK_100_KHZ; + timer = cmd->convert_arg / 10000; + } else /* cmd->convert_arg < 6553600000 */ { + base_clock = CLOCK_10_KHZ; + timer = cmd->convert_arg / 100000; + } + outw(0xFF03, dev->iobase + AM9513A_COM_REG); + outw(base_clock, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0B, dev->iobase + AM9513A_COM_REG); + outw(0x2, dev->iobase + AM9513A_DATA_REG); + outw(0xFF44, dev->iobase + AM9513A_COM_REG); + outw(0xFFF3, dev->iobase + AM9513A_COM_REG); + outw(timer, dev->iobase + AM9513A_DATA_REG); + outw(0xFF24, dev->iobase + AM9513A_COM_REG); + + /* Now figure out how many samples to get */ + /* and program the sample counter */ + sample_count = cmd->stop_arg * cmd->scan_end_arg; + outw(0xFF04, dev->iobase + AM9513A_COM_REG); + outw(0x1025, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0C, dev->iobase + AM9513A_COM_REG); + if (sample_count < 65536) { + /* use only Counter 4 */ + outw(sample_count, dev->iobase + AM9513A_DATA_REG); + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + outw(0xFFF4, dev->iobase + AM9513A_COM_REG); + outw(0xFF28, dev->iobase + AM9513A_COM_REG); + devpriv->com_reg_1_state &= ~COMREG1_1632CNT; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + } else { + /* Counter 4 and 5 are needed */ + + tmp = sample_count & 0xFFFF; + if (tmp) + outw(tmp - 1, dev->iobase + AM9513A_DATA_REG); + else + outw(0xFFFF, dev->iobase + AM9513A_DATA_REG); + + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + outw(0, dev->iobase + AM9513A_DATA_REG); + outw(0xFF28, dev->iobase + AM9513A_COM_REG); + outw(0xFF05, dev->iobase + AM9513A_COM_REG); + outw(0x25, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0D, dev->iobase + AM9513A_COM_REG); + tmp = sample_count & 0xFFFF; + if ((tmp == 0) || (tmp == 1)) { + outw((sample_count >> 16) & 0xFFFF, + dev->iobase + AM9513A_DATA_REG); + } else { + outw(((sample_count >> 16) & 0xFFFF) + 1, + dev->iobase + AM9513A_DATA_REG); + } + outw(0xFF70, dev->iobase + AM9513A_COM_REG); + devpriv->com_reg_1_state |= COMREG1_1632CNT; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + } + + /* + * Program the scan interval timer ONLY IF SCANNING IS ENABLED. + * Figure out which clock to use then get an appropriate timer value. + */ + if (cmd->chanlist_len > 1) { + if (cmd->scan_begin_arg < 65536000) { + base_clock = CLOCK_1_MHZ; + timer = cmd->scan_begin_arg / 1000; + } else if (cmd->scan_begin_arg < 655360000) { + base_clock = CLOCK_100_KHZ; + timer = cmd->scan_begin_arg / 10000; + } else /* cmd->scan_begin_arg < 6553600000 */ { + base_clock = CLOCK_10_KHZ; + timer = cmd->scan_begin_arg / 100000; + } + outw(0xFF02, dev->iobase + AM9513A_COM_REG); + outw(base_clock, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0A, dev->iobase + AM9513A_COM_REG); + outw(0x2, dev->iobase + AM9513A_DATA_REG); + outw(0xFF42, dev->iobase + AM9513A_COM_REG); + outw(0xFFF2, dev->iobase + AM9513A_COM_REG); + outw(timer, dev->iobase + AM9513A_DATA_REG); + outw(0xFF22, dev->iobase + AM9513A_COM_REG); + } + + /* Clear the A/D FIFO and reset the MUX counter */ + outw(0, dev->iobase + AD_CLEAR_REG); + outw(0, dev->iobase + MUX_CNTR_REG); + outw(0, dev->iobase + INT2CLR_REG); + /* enable this acquisition operation */ + devpriv->com_reg_1_state |= COMREG1_DAQEN; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + /* enable interrupts for conversion completion */ + devpriv->com_reg_1_state |= COMREG1_CONVINTEN; + devpriv->com_reg_2_state |= COMREG2_INTEN; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2); + /* apply a trigger. this starts the counters! */ + outw(0, dev->iobase + START_DAQ_REG); + + return 0; +} + +/* This will cancel a running acquisition operation */ +static int atmio16d_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + reset_atmio16d(dev); + + return 0; +} + +static int atmio16d_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + STAT_REG); + if (status & STAT_AD_CONVAVAIL) + return 0; + if (status & STAT_AD_OVERFLOW) { + outw(0, dev->iobase + AD_CLEAR_REG); + return -EOVERFLOW; + } + return -EBUSY; +} + +static int atmio16d_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct atmio16d_private *devpriv = dev->private; + int i; + int chan; + int gain; + int ret; + + chan = CR_CHAN(insn->chanspec); + gain = CR_RANGE(insn->chanspec); + + /* reset the Analog input circuitry */ + /* outw( 0, dev->iobase+AD_CLEAR_REG ); */ + /* reset the Analog Input MUX Counter to 0 */ + /* outw( 0, dev->iobase+MUX_CNTR_REG ); */ + + /* set the Input MUX gain */ + outw(chan | (gain << 6), dev->iobase + MUX_GAIN_REG); + + for (i = 0; i < insn->n; i++) { + /* start the conversion */ + outw(0, dev->iobase + START_CONVERT_REG); + + /* wait for it to finish */ + ret = comedi_timeout(dev, s, insn, atmio16d_ai_eoc, 0); + if (ret) + return ret; + + /* read the data now */ + data[i] = inw(dev->iobase + AD_FIFO_REG); + /* change to two's complement if need be */ + if (devpriv->adc_coding == adc_2comp) + data[i] ^= 0x800; + } + + return i; +} + +static int atmio16d_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct atmio16d_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int reg = (chan) ? DAC1_REG : DAC0_REG; + bool munge = false; + int i; + + if (chan == 0 && devpriv->dac0_coding == dac_2comp) + munge = true; + if (chan == 1 && devpriv->dac1_coding == dac_2comp) + munge = true; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + if (munge) + val ^= 0x800; + + outw(val, dev->iobase + reg); + } + + return insn->n; +} + +static int atmio16d_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + MIO_16_DIG_OUT_REG); + + data[1] = inw(dev->iobase + MIO_16_DIG_IN_REG); + + return insn->n; +} + +static int atmio16d_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct atmio16d_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 4) + mask = 0x0f; + else + mask = 0xf0; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + devpriv->com_reg_2_state &= ~(COMREG2_DOUTEN0 | COMREG2_DOUTEN1); + if (s->io_bits & 0x0f) + devpriv->com_reg_2_state |= COMREG2_DOUTEN0; + if (s->io_bits & 0xf0) + devpriv->com_reg_2_state |= COMREG2_DOUTEN1; + outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2); + + return insn->n; +} + +static int atmio16d_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct atmio16_board_t *board = dev->board_ptr; + struct atmio16d_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x20); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* reset the atmio16d hardware */ + reset_atmio16d(dev); + + if (it->options[1]) { + ret = request_irq(it->options[1], atmio16d_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + /* set device options */ + devpriv->adc_mux = it->options[5]; + devpriv->adc_range = it->options[6]; + + devpriv->dac0_range = it->options[7]; + devpriv->dac0_reference = it->options[8]; + devpriv->dac0_coding = it->options[9]; + devpriv->dac1_range = it->options[10]; + devpriv->dac1_reference = it->options[11]; + devpriv->dac1_coding = it->options[12]; + + /* setup sub-devices */ + s = &dev->subdevices[0]; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = (devpriv->adc_mux ? 16 : 8); + s->insn_read = atmio16d_ai_insn_read; + s->maxdata = 0xfff; /* 4095 decimal */ + switch (devpriv->adc_range) { + case adc_bipolar10: + s->range_table = &range_atmio16d_ai_10_bipolar; + break; + case adc_bipolar5: + s->range_table = &range_atmio16d_ai_5_bipolar; + break; + case adc_unipolar10: + s->range_table = &range_atmio16d_ai_unipolar; + break; + } + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 16; + s->do_cmdtest = atmio16d_ai_cmdtest; + s->do_cmd = atmio16d_ai_cmd; + s->cancel = atmio16d_ai_cancel; + } + + /* ao subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0xfff; /* 4095 decimal */ + s->range_table_list = devpriv->ao_range_type_list; + switch (devpriv->dac0_range) { + case dac_bipolar: + devpriv->ao_range_type_list[0] = &range_bipolar10; + break; + case dac_unipolar: + devpriv->ao_range_type_list[0] = &range_unipolar10; + break; + } + switch (devpriv->dac1_range) { + case dac_bipolar: + devpriv->ao_range_type_list[1] = &range_bipolar10; + break; + case dac_unipolar: + devpriv->ao_range_type_list[1] = &range_unipolar10; + break; + } + s->insn_write = atmio16d_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 8; + s->insn_bits = atmio16d_dio_insn_bits; + s->insn_config = atmio16d_dio_insn_config; + s->maxdata = 1; + s->range_table = &range_digital; + + /* 8255 subdevice */ + s = &dev->subdevices[3]; + if (board->has_8255) { + ret = subdev_8255_init(dev, s, NULL, 0x00); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + +/* don't yet know how to deal with counter/timers */ +#if 0 + s = &dev->subdevices[4]; + /* do */ + s->type = COMEDI_SUBD_TIMER; + s->n_chan = 0; + s->maxdata = 0 +#endif + + return 0; +} + +static void atmio16d_detach(struct comedi_device *dev) +{ + reset_atmio16d(dev); + comedi_legacy_detach(dev); +} + +static const struct atmio16_board_t atmio16_boards[] = { + { + .name = "atmio16", + .has_8255 = 0, + }, { + .name = "atmio16d", + .has_8255 = 1, + }, +}; + +static struct comedi_driver atmio16d_driver = { + .driver_name = "atmio16", + .module = THIS_MODULE, + .attach = atmio16d_attach, + .detach = atmio16d_detach, + .board_name = &atmio16_boards[0].name, + .num_names = ARRAY_SIZE(atmio16_boards), + .offset = sizeof(struct atmio16_board_t), +}; +module_comedi_driver(atmio16d_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_daq_700.c b/drivers/comedi/drivers/ni_daq_700.c new file mode 100644 index 000000000..0ef20e9a8 --- /dev/null +++ b/drivers/comedi/drivers/ni_daq_700.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: ni_daq_700 + * Description: National Instruments PCMCIA DAQCard-700 + * Author: Fred Brooks <nsaspook@nsaspook.com>, + * based on ni_daq_dio24 by Daniel Vecino Castel <dvecino@able.es> + * Devices: [National Instruments] PCMCIA DAQ-Card-700 (ni_daq_700) + * Status: works + * Updated: Wed, 21 May 2014 12:07:20 +0000 + * + * The daqcard-700 appears in Comedi as a digital I/O subdevice (0) with + * 16 channels and a analog input subdevice (1) with 16 single-ended channels + * or 8 differential channels, and three input ranges. + * + * Digital: The channel 0 corresponds to the daqcard-700's output + * port, bit 0; channel 8 corresponds to the input port, bit 0. + * + * Digital direction configuration: channels 0-7 output, 8-15 input. + * + * Analog: The input range is 0 to 4095 with a default of -10 to +10 volts. + * Valid ranges: + * 0 for -10 to 10V bipolar + * 1 for -5 to 5V bipolar + * 2 for -2.5 to 2.5V bipolar + * + * IRQ is assigned but not used. + * + * Manuals: Register level: https://www.ni.com/pdf/manuals/340698.pdf + * User Manual: https://www.ni.com/pdf/manuals/320676d.pdf + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pcmcia.h> + +/* daqcard700 registers */ +#define DIO_W 0x04 /* WO 8bit */ +#define DIO_R 0x05 /* RO 8bit */ +#define CMD_R1 0x00 /* WO 8bit */ +#define CMD_R2 0x07 /* RW 8bit */ +#define CMD_R3 0x05 /* W0 8bit */ +#define STA_R1 0x00 /* RO 8bit */ +#define STA_R2 0x01 /* RO 8bit */ +#define ADFIFO_R 0x02 /* RO 16bit */ +#define ADCLEAR_R 0x01 /* WO 8bit */ +#define CDA_R0 0x08 /* RW 8bit */ +#define CDA_R1 0x09 /* RW 8bit */ +#define CDA_R2 0x0A /* RW 8bit */ +#define CMO_R 0x0B /* RO 8bit */ +#define TIC_R 0x06 /* WO 8bit */ +/* daqcard700 modes */ +#define CMD_R3_DIFF 0x04 /* diff mode */ + +static const struct comedi_lrange range_daq700_ai = { + 3, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5) + } +}; + +static int daq700_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0xff) + outb(s->state & 0xff, dev->iobase + DIO_W); + } + + val = s->state & 0xff; + val |= inb(dev->iobase + DIO_R) << 8; + + data[1] = val; + + return insn->n; +} + +static int daq700_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + /* The DIO channels are not configurable, fix the io_bits */ + s->io_bits = 0x00ff; + + return insn->n; +} + +static int daq700_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + STA_R2); + if ((status & 0x03)) + return -EOVERFLOW; + status = inb(dev->iobase + STA_R1); + if ((status & 0x02)) + return -ENODATA; + if ((status & 0x11) == 0x01) + return 0; + return -EBUSY; +} + +static int daq700_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int d; + int ret; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int aref = CR_AREF(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int r3_bits = 0; + + /* set channel input modes */ + if (aref == AREF_DIFF) + r3_bits |= CMD_R3_DIFF; + /* write channel mode/range */ + if (range >= 1) + range++; /* convert range to hardware value */ + outb(r3_bits | (range & 0x03), dev->iobase + CMD_R3); + + /* write channel to multiplexer */ + /* set mask scan bit high to disable scanning */ + outb(chan | 0x80, dev->iobase + CMD_R1); + /* mux needs 2us to really settle [Fred Brooks]. */ + udelay(2); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion with out0 L to H */ + outb(0x00, dev->iobase + CMD_R2); /* enable ADC conversions */ + outb(0x30, dev->iobase + CMO_R); /* mode 0 out0 L, from H */ + outb(0x00, dev->iobase + ADCLEAR_R); /* clear the ADC FIFO */ + /* read 16bit junk from FIFO to clear */ + inw(dev->iobase + ADFIFO_R); + /* mode 1 out0 H, L to H, start conversion */ + outb(0x32, dev->iobase + CMO_R); + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, daq700_ai_eoc, 0); + if (ret) + return ret; + + /* read data */ + d = inw(dev->iobase + ADFIFO_R); + /* mangle the data as necessary */ + /* Bipolar Offset Binary: 0 to 4095 for -10 to +10 */ + d &= 0x0fff; + d ^= 0x0800; + data[n] = d; + } + return n; +} + +/* + * Data acquisition is enabled. + * The counter 0 output is high. + * The I/O connector pin CLK1 drives counter 1 source. + * Multiple-channel scanning is disabled. + * All interrupts are disabled. + * The analog input range is set to +-10 V + * The analog input mode is single-ended. + * The analog input circuitry is initialized to channel 0. + * The A/D FIFO is cleared. + */ +static void daq700_ai_config(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long iobase = dev->iobase; + + outb(0x80, iobase + CMD_R1); /* disable scanning, ADC to chan 0 */ + outb(0x00, iobase + CMD_R2); /* clear all bits */ + outb(0x00, iobase + CMD_R3); /* set +-10 range */ + outb(0x32, iobase + CMO_R); /* config counter mode1, out0 to H */ + outb(0x00, iobase + TIC_R); /* clear counter interrupt */ + outb(0x00, iobase + ADCLEAR_R); /* clear the ADC FIFO */ + inw(iobase + ADFIFO_R); /* read 16bit junk from FIFO to clear */ +} + +static int daq700_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + struct comedi_subdevice *s; + int ret; + + link->config_flags |= CONF_AUTO_SET_IO; + ret = comedi_pcmcia_enable(dev, NULL); + if (ret) + return ret; + dev->iobase = link->resource[0]->start; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* DAQCard-700 dio */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = daq700_dio_insn_bits; + s->insn_config = daq700_dio_insn_config; + s->io_bits = 0x00ff; + + /* DAQCard-700 ai */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 16; + s->maxdata = BIT(12) - 1; + s->range_table = &range_daq700_ai; + s->insn_read = daq700_ai_rinsn; + daq700_ai_config(dev, s); + + return 0; +} + +static struct comedi_driver daq700_driver = { + .driver_name = "ni_daq_700", + .module = THIS_MODULE, + .auto_attach = daq700_auto_attach, + .detach = comedi_pcmcia_disable, +}; + +static int daq700_cs_attach(struct pcmcia_device *link) +{ + return comedi_pcmcia_auto_config(link, &daq700_driver); +} + +static const struct pcmcia_device_id daq700_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4743), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, daq700_cs_ids); + +static struct pcmcia_driver daq700_cs_driver = { + .name = "ni_daq_700", + .owner = THIS_MODULE, + .id_table = daq700_cs_ids, + .probe = daq700_cs_attach, + .remove = comedi_pcmcia_auto_unconfig, +}; +module_comedi_pcmcia_driver(daq700_driver, daq700_cs_driver); + +MODULE_AUTHOR("Fred Brooks <nsaspook@nsaspook.com>"); +MODULE_DESCRIPTION( + "Comedi driver for National Instruments PCMCIA DAQCard-700 DIO/AI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_daq_dio24.c b/drivers/comedi/drivers/ni_daq_dio24.c new file mode 100644 index 000000000..487733111 --- /dev/null +++ b/drivers/comedi/drivers/ni_daq_dio24.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi 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. + */ + +/* + * 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 <linux/comedi/comedi_pcmcia.h> +#include <linux/comedi/comedi_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]; + return subdev_8255_init(dev, s, NULL, 0x00); +} + +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/comedi/drivers/ni_labpc.c b/drivers/comedi/drivers/ni_labpc.c new file mode 100644 index 000000000..b25a8e117 --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: ni_labpc + * Description: National Instruments Lab-PC (& compatibles) + * Devices: [National Instruments] Lab-PC-1200 (lab-pc-1200), + * Lab-PC-1200AI (lab-pc-1200ai), Lab-PC+ (lab-pc+) + * Author: Frank Mori Hess <fmhess@users.sourceforge.net> + * Status: works + * + * Configuration options - ISA boards: + * [0] - I/O port base address + * [1] - IRQ (optional, required for timed or externally triggered + * conversions) + * [2] - DMA channel (optional) + * + * Tested with lab-pc-1200. For the older Lab-PC+, not all input + * ranges and analog references will work, the available ranges/arefs + * will depend on how you have configured the jumpers on your board + * (see your owner's manual). + * + * Kernel-level ISA plug-and-play support for the lab-pc-1200 boards + * has not yet been added to the driver, mainly due to the fact that + * I don't know the device id numbers. If you have one of these boards, + * please file a bug report at https://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/comedi/comedidev.h> + +#include "ni_labpc.h" +#include "ni_labpc_isadma.h" + +static const struct labpc_boardinfo labpc_boards[] = { + { + .name = "lab-pc-1200", + .ai_speed = 10000, + .ai_scan_up = 1, + .has_ao = 1, + .is_labpc1200 = 1, + }, { + .name = "lab-pc-1200ai", + .ai_speed = 10000, + .ai_scan_up = 1, + .is_labpc1200 = 1, + }, { + .name = "lab-pc+", + .ai_speed = 12000, + .has_ao = 1, + }, +}; + +static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + unsigned int irq = it->options[1]; + unsigned int dma_chan = it->options[2]; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x20); + if (ret) + return ret; + + ret = labpc_common_attach(dev, irq, 0); + if (ret) + return ret; + + if (dev->irq) + labpc_init_dma_chan(dev, dma_chan); + + return 0; +} + +static void labpc_detach(struct comedi_device *dev) +{ + labpc_free_dma_chan(dev); + labpc_common_detach(dev); + comedi_legacy_detach(dev); +} + +static struct comedi_driver labpc_driver = { + .driver_name = "ni_labpc", + .module = THIS_MODULE, + .attach = labpc_attach, + .detach = labpc_detach, + .num_names = ARRAY_SIZE(labpc_boards), + .board_name = &labpc_boards[0].name, + .offset = sizeof(struct labpc_boardinfo), +}; +module_comedi_driver(labpc_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for NI Lab-PC ISA boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_labpc.h b/drivers/comedi/drivers/ni_labpc.h new file mode 100644 index 000000000..728e901f5 --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Header for ni_labpc ISA/PCMCIA/PCI drivers + * + * Copyright (C) 2003 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +#ifndef _NI_LABPC_H +#define _NI_LABPC_H + +enum transfer_type { fifo_not_empty_transfer, fifo_half_full_transfer, + isa_dma_transfer +}; + +struct labpc_boardinfo { + const char *name; + int ai_speed; /* maximum input speed in ns */ + unsigned ai_scan_up:1; /* can auto scan up in ai channels */ + unsigned has_ao:1; /* has analog outputs */ + unsigned is_labpc1200:1; /* has extra regs compared to pc+ */ +}; + +struct labpc_private { + struct comedi_isadma *dma; + struct comedi_8254 *counter; + + /* number of data points left to be taken */ + unsigned long long count; + /* software copys of bits written to command registers */ + unsigned int cmd1; + unsigned int cmd2; + unsigned int cmd3; + unsigned int cmd4; + unsigned int cmd5; + unsigned int cmd6; + /* store last read of board status registers */ + unsigned int stat1; + unsigned int stat2; + + /* we are using dma/fifo-half-full/etc. */ + enum transfer_type current_transfer; + /* + * function pointers so we can use inb/outb or readb/writeb as + * appropriate + */ + unsigned int (*read_byte)(struct comedi_device *dev, unsigned long reg); + void (*write_byte)(struct comedi_device *dev, + unsigned int byte, unsigned long reg); +}; + +int labpc_common_attach(struct comedi_device *dev, + unsigned int irq, unsigned long isr_flags); +void labpc_common_detach(struct comedi_device *dev); + +#endif /* _NI_LABPC_H */ diff --git a/drivers/comedi/drivers/ni_labpc_common.c b/drivers/comedi/drivers/ni_labpc_common.c new file mode 100644 index 000000000..763249653 --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc_common.c @@ -0,0 +1,1362 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_labpc_common.c + * + * Common support code for "ni_labpc", "ni_labpc_pci" and "ni_labpc_cs". + * + * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> +#include <linux/comedi/comedi_8254.h> + +#include "ni_labpc.h" +#include "ni_labpc_regs.h" +#include "ni_labpc_isadma.h" + +enum scan_mode { + MODE_SINGLE_CHAN, + MODE_SINGLE_CHAN_INTERVAL, + MODE_MULT_CHAN_UP, + MODE_MULT_CHAN_DOWN, +}; + +static const struct comedi_lrange range_labpc_plus_ai = { + 16, { + BIP_RANGE(5), + BIP_RANGE(4), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(10), + UNI_RANGE(8), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1) + } +}; + +static const struct comedi_lrange range_labpc_1200_ai = { + 14, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1) + } +}; + +static const struct comedi_lrange range_labpc_ao = { + 2, { + BIP_RANGE(5), + UNI_RANGE(10) + } +}; + +/* + * functions that do inb/outb and readb/writeb so we can use + * function pointers to decide which to use + */ +static unsigned int labpc_inb(struct comedi_device *dev, unsigned long reg) +{ + return inb(dev->iobase + reg); +} + +static void labpc_outb(struct comedi_device *dev, + unsigned int byte, unsigned long reg) +{ + outb(byte, dev->iobase + reg); +} + +static unsigned int labpc_readb(struct comedi_device *dev, unsigned long reg) +{ + return readb(dev->mmio + reg); +} + +static void labpc_writeb(struct comedi_device *dev, + unsigned int byte, unsigned long reg) +{ + writeb(byte, dev->mmio + reg); +} + +static int labpc_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct labpc_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG); + devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + devpriv->cmd3 = 0; + devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG); + + return 0; +} + +static void labpc_ai_set_chan_and_gain(struct comedi_device *dev, + enum scan_mode mode, + unsigned int chan, + unsigned int range, + unsigned int aref) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv = dev->private; + + if (board->is_labpc1200) { + /* + * The LabPC-1200 boards do not have a gain + * of '0x10'. Skip the range values that would + * result in this gain. + */ + range += (range > 0) + (range > 7); + } + + /* munge channel bits for differential/scan disabled mode */ + if ((mode == MODE_SINGLE_CHAN || mode == MODE_SINGLE_CHAN_INTERVAL) && + aref == AREF_DIFF) + chan *= 2; + devpriv->cmd1 = CMD1_MA(chan); + devpriv->cmd1 |= CMD1_GAIN(range); + + devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG); +} + +static void labpc_setup_cmd6_reg(struct comedi_device *dev, + struct comedi_subdevice *s, + enum scan_mode mode, + enum transfer_type xfer, + unsigned int range, + unsigned int aref, + bool ena_intr) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv = dev->private; + + if (!board->is_labpc1200) + return; + + /* reference inputs to ground or common? */ + if (aref != AREF_GROUND) + devpriv->cmd6 |= CMD6_NRSE; + else + devpriv->cmd6 &= ~CMD6_NRSE; + + /* bipolar or unipolar range? */ + if (comedi_range_is_unipolar(s, range)) + devpriv->cmd6 |= CMD6_ADCUNI; + else + devpriv->cmd6 &= ~CMD6_ADCUNI; + + /* interrupt on fifo half full? */ + if (xfer == fifo_half_full_transfer) + devpriv->cmd6 |= CMD6_HFINTEN; + else + devpriv->cmd6 &= ~CMD6_HFINTEN; + + /* enable interrupt on counter a1 terminal count? */ + if (ena_intr) + devpriv->cmd6 |= CMD6_DQINTEN; + else + devpriv->cmd6 &= ~CMD6_DQINTEN; + + /* are we scanning up or down through channels? */ + if (mode == MODE_MULT_CHAN_UP) + devpriv->cmd6 |= CMD6_SCANUP; + else + devpriv->cmd6 &= ~CMD6_SCANUP; + + devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG); +} + +static unsigned int labpc_read_adc_fifo(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + unsigned int lsb = devpriv->read_byte(dev, ADC_FIFO_REG); + unsigned int msb = devpriv->read_byte(dev, ADC_FIFO_REG); + + return (msb << 8) | lsb; +} + +static void labpc_clear_adc_fifo(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG); + labpc_read_adc_fifo(dev); +} + +static int labpc_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + struct labpc_private *devpriv = dev->private; + + devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG); + if (devpriv->stat1 & STAT1_DAVAIL) + return 0; + return -EBUSY; +} + +static int labpc_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct labpc_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int aref = CR_AREF(insn->chanspec); + int ret; + int i; + + /* disable timed conversions, interrupt generation and dma */ + labpc_cancel(dev, s); + + labpc_ai_set_chan_and_gain(dev, MODE_SINGLE_CHAN, chan, range, aref); + + labpc_setup_cmd6_reg(dev, s, MODE_SINGLE_CHAN, fifo_not_empty_transfer, + range, aref, false); + + /* setup cmd4 register */ + devpriv->cmd4 = 0; + devpriv->cmd4 |= CMD4_ECLKRCV; + /* single-ended/differential */ + if (aref == AREF_DIFF) + devpriv->cmd4 |= CMD4_SEDIFF; + devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG); + + /* initialize pacer counter to prevent any problems */ + comedi_8254_set_mode(devpriv->counter, 0, I8254_MODE2 | I8254_BINARY); + + labpc_clear_adc_fifo(dev); + + for (i = 0; i < insn->n; i++) { + /* trigger conversion */ + devpriv->write_byte(dev, 0x1, ADC_START_CONVERT_REG); + + ret = comedi_timeout(dev, s, insn, labpc_ai_eoc, 0); + if (ret) + return ret; + + data[i] = labpc_read_adc_fifo(dev); + } + + return insn->n; +} + +static bool labpc_use_continuous_mode(const struct comedi_cmd *cmd, + enum scan_mode mode) +{ + if (mode == MODE_SINGLE_CHAN || cmd->scan_begin_src == TRIG_FOLLOW) + return true; + + return false; +} + +static unsigned int labpc_ai_convert_period(const struct comedi_cmd *cmd, + enum scan_mode mode) +{ + if (cmd->convert_src != TRIG_TIMER) + return 0; + + if (mode == MODE_SINGLE_CHAN && cmd->scan_begin_src == TRIG_TIMER) + return cmd->scan_begin_arg; + + return cmd->convert_arg; +} + +static void labpc_set_ai_convert_period(struct comedi_cmd *cmd, + enum scan_mode mode, unsigned int ns) +{ + if (cmd->convert_src != TRIG_TIMER) + return; + + if (mode == MODE_SINGLE_CHAN && + cmd->scan_begin_src == TRIG_TIMER) { + cmd->scan_begin_arg = ns; + if (cmd->convert_arg > cmd->scan_begin_arg) + cmd->convert_arg = cmd->scan_begin_arg; + } else { + cmd->convert_arg = ns; + } +} + +static unsigned int labpc_ai_scan_period(const struct comedi_cmd *cmd, + enum scan_mode mode) +{ + if (cmd->scan_begin_src != TRIG_TIMER) + return 0; + + if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER) + return 0; + + return cmd->scan_begin_arg; +} + +static void labpc_set_ai_scan_period(struct comedi_cmd *cmd, + enum scan_mode mode, unsigned int ns) +{ + if (cmd->scan_begin_src != TRIG_TIMER) + return; + + if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER) + return; + + cmd->scan_begin_arg = ns; +} + +/* figures out what counter values to use based on command */ +static void labpc_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd, + enum scan_mode mode) +{ + struct comedi_8254 *pacer = dev->pacer; + unsigned int convert_period = labpc_ai_convert_period(cmd, mode); + unsigned int scan_period = labpc_ai_scan_period(cmd, mode); + unsigned int base_period; + + /* + * If both convert and scan triggers are TRIG_TIMER, then they + * both rely on counter b0. If only one TRIG_TIMER is used, we + * can use the generic cascaded timing functions. + */ + if (convert_period && scan_period) { + /* + * pick the lowest divisor value we can (for maximum input + * clock speed on convert and scan counters) + */ + pacer->next_div1 = (scan_period - 1) / + (pacer->osc_base * I8254_MAX_COUNT) + 1; + + comedi_check_trigger_arg_min(&pacer->next_div1, 2); + comedi_check_trigger_arg_max(&pacer->next_div1, + I8254_MAX_COUNT); + + base_period = pacer->osc_base * pacer->next_div1; + + /* set a0 for conversion frequency and b1 for scan frequency */ + switch (cmd->flags & CMDF_ROUND_MASK) { + default: + case CMDF_ROUND_NEAREST: + pacer->next_div = DIV_ROUND_CLOSEST(convert_period, + base_period); + pacer->next_div2 = DIV_ROUND_CLOSEST(scan_period, + base_period); + break; + case CMDF_ROUND_UP: + pacer->next_div = DIV_ROUND_UP(convert_period, + base_period); + pacer->next_div2 = DIV_ROUND_UP(scan_period, + base_period); + break; + case CMDF_ROUND_DOWN: + pacer->next_div = convert_period / base_period; + pacer->next_div2 = scan_period / base_period; + break; + } + /* make sure a0 and b1 values are acceptable */ + comedi_check_trigger_arg_min(&pacer->next_div, 2); + comedi_check_trigger_arg_max(&pacer->next_div, I8254_MAX_COUNT); + comedi_check_trigger_arg_min(&pacer->next_div2, 2); + comedi_check_trigger_arg_max(&pacer->next_div2, + I8254_MAX_COUNT); + + /* write corrected timings to command */ + labpc_set_ai_convert_period(cmd, mode, + base_period * pacer->next_div); + labpc_set_ai_scan_period(cmd, mode, + base_period * pacer->next_div2); + } else if (scan_period) { + /* + * calculate cascaded counter values + * that give desired scan timing + * (pacer->next_div2 / pacer->next_div1) + */ + comedi_8254_cascade_ns_to_timer(pacer, &scan_period, + cmd->flags); + labpc_set_ai_scan_period(cmd, mode, scan_period); + } else if (convert_period) { + /* + * calculate cascaded counter values + * that give desired conversion timing + * (pacer->next_div / pacer->next_div1) + */ + comedi_8254_cascade_ns_to_timer(pacer, &convert_period, + cmd->flags); + /* transfer div2 value so correct timer gets updated */ + pacer->next_div = pacer->next_div2; + labpc_set_ai_convert_period(cmd, mode, convert_period); + } +} + +static enum scan_mode labpc_ai_scan_mode(const struct comedi_cmd *cmd) +{ + unsigned int chan0; + unsigned int chan1; + + if (cmd->chanlist_len == 1) + return MODE_SINGLE_CHAN; + + /* chanlist may be NULL during cmdtest */ + if (!cmd->chanlist) + return MODE_MULT_CHAN_UP; + + chan0 = CR_CHAN(cmd->chanlist[0]); + chan1 = CR_CHAN(cmd->chanlist[1]); + + if (chan0 < chan1) + return MODE_MULT_CHAN_UP; + + if (chan0 > chan1) + return MODE_MULT_CHAN_DOWN; + + return MODE_SINGLE_CHAN_INTERVAL; +} + +static int labpc_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + enum scan_mode mode = labpc_ai_scan_mode(cmd); + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + switch (mode) { + case MODE_SINGLE_CHAN: + break; + case MODE_SINGLE_CHAN_INTERVAL: + if (chan != chan0) { + dev_dbg(dev->class_dev, + "channel scanning order specified in chanlist is not supported by hardware\n"); + return -EINVAL; + } + break; + case MODE_MULT_CHAN_UP: + if (chan != i) { + dev_dbg(dev->class_dev, + "channel scanning order specified in chanlist is not supported by hardware\n"); + return -EINVAL; + } + break; + case MODE_MULT_CHAN_DOWN: + if (chan != (cmd->chanlist_len - i - 1)) { + dev_dbg(dev->class_dev, + "channel scanning order specified in chanlist is not supported by hardware\n"); + return -EINVAL; + } + break; + } + + if (range != range0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same range\n"); + return -EINVAL; + } + + if (aref != aref0) { + dev_dbg(dev->class_dev, + "entries in chanlist must all have the same reference\n"); + return -EINVAL; + } + } + + return 0; +} + +static int labpc_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + int err = 0; + int tmp, tmp2; + unsigned int stop_mask; + enum scan_mode mode; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + + stop_mask = TRIG_COUNT | TRIG_NONE; + if (board->is_labpc1200) + stop_mask |= TRIG_EXT; + err |= comedi_check_trigger_src(&cmd->stop_src, stop_mask); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + /* can't have external stop and start triggers at once */ + if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT) + err++; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* start_arg value is ignored */ + break; + } + + if (!cmd->chanlist_len) + err |= -EINVAL; + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + } + + /* make sure scan timing is not too fast */ + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, + cmd->convert_arg * cmd->chanlist_len); + } + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, + board->ai_speed * cmd->chanlist_len); + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + break; + case TRIG_NONE: + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + break; + /* + * TRIG_EXT doesn't care since it doesn't + * trigger off a numbered channel + */ + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + tmp = cmd->convert_arg; + tmp2 = cmd->scan_begin_arg; + mode = labpc_ai_scan_mode(cmd); + labpc_adc_timing(dev, cmd, mode); + if (tmp != cmd->convert_arg || tmp2 != cmd->scan_begin_arg) + err++; + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= labpc_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int labpc_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + enum scan_mode mode = labpc_ai_scan_mode(cmd); + unsigned int chanspec = (mode == MODE_MULT_CHAN_UP) ? + cmd->chanlist[cmd->chanlist_len - 1] : + cmd->chanlist[0]; + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + enum transfer_type xfer; + unsigned long flags; + + /* make sure board is disabled before setting up acquisition */ + labpc_cancel(dev, s); + + /* initialize software conversion count */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + + /* setup hardware conversion counter */ + if (cmd->stop_src == TRIG_EXT) { + /* + * load counter a1 with count of 3 + * (pc+ manual says this is minimum allowed) using mode 0 + */ + comedi_8254_load(devpriv->counter, 1, + 3, I8254_MODE0 | I8254_BINARY); + } else { + /* just put counter a1 in mode 0 to set its output low */ + comedi_8254_set_mode(devpriv->counter, 1, + I8254_MODE0 | I8254_BINARY); + } + + /* figure out what method we will use to transfer data */ + if (devpriv->dma && + (cmd->flags & (CMDF_WAKE_EOS | CMDF_PRIORITY)) == 0) { + /* + * dma unsafe at RT priority, + * and too much setup time for CMDF_WAKE_EOS + */ + xfer = isa_dma_transfer; + } else if (board->is_labpc1200 && + (cmd->flags & CMDF_WAKE_EOS) == 0 && + (cmd->stop_src != TRIG_COUNT || devpriv->count > 256)) { + /* + * pc-plus has no fifo-half full interrupt + * wake-end-of-scan should interrupt on fifo not empty + * make sure we are taking more than just a few points + */ + xfer = fifo_half_full_transfer; + } else { + xfer = fifo_not_empty_transfer; + } + devpriv->current_transfer = xfer; + + labpc_ai_set_chan_and_gain(dev, mode, chan, range, aref); + + labpc_setup_cmd6_reg(dev, s, mode, xfer, range, aref, + (cmd->stop_src == TRIG_EXT)); + + /* manual says to set scan enable bit on second pass */ + if (mode == MODE_MULT_CHAN_UP || mode == MODE_MULT_CHAN_DOWN) { + devpriv->cmd1 |= CMD1_SCANEN; + /* + * Need a brief delay before enabling scan, or scan + * list will get screwed when you switch between + * scan up to scan down mode - dunno why. + */ + udelay(1); + devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG); + } + + devpriv->write_byte(dev, cmd->chanlist_len, INTERVAL_COUNT_REG); + /* load count */ + devpriv->write_byte(dev, 0x1, INTERVAL_STROBE_REG); + + if (cmd->convert_src == TRIG_TIMER || + cmd->scan_begin_src == TRIG_TIMER) { + struct comedi_8254 *pacer = dev->pacer; + struct comedi_8254 *counter = devpriv->counter; + + comedi_8254_update_divisors(pacer); + + /* set up pacing */ + comedi_8254_load(pacer, 0, pacer->divisor1, + I8254_MODE3 | I8254_BINARY); + + /* set up conversion pacing */ + comedi_8254_set_mode(counter, 0, I8254_MODE2 | I8254_BINARY); + if (labpc_ai_convert_period(cmd, mode)) + comedi_8254_write(counter, 0, pacer->divisor); + + /* set up scan pacing */ + if (labpc_ai_scan_period(cmd, mode)) + comedi_8254_load(pacer, 1, pacer->divisor2, + I8254_MODE2 | I8254_BINARY); + } + + labpc_clear_adc_fifo(dev); + + if (xfer == isa_dma_transfer) + labpc_setup_dma(dev, s); + + /* enable error interrupts */ + devpriv->cmd3 |= CMD3_ERRINTEN; + /* enable fifo not empty interrupt? */ + if (xfer == fifo_not_empty_transfer) + devpriv->cmd3 |= CMD3_FIFOINTEN; + devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG); + + /* setup any external triggering/pacing (cmd4 register) */ + devpriv->cmd4 = 0; + if (cmd->convert_src != TRIG_EXT) + devpriv->cmd4 |= CMD4_ECLKRCV; + /* + * XXX should discard first scan when using interval scanning + * since manual says it is not synced with scan clock. + */ + if (!labpc_use_continuous_mode(cmd, mode)) { + devpriv->cmd4 |= CMD4_INTSCAN; + if (cmd->scan_begin_src == TRIG_EXT) + devpriv->cmd4 |= CMD4_EOIRCV; + } + /* single-ended/differential */ + if (aref == AREF_DIFF) + devpriv->cmd4 |= CMD4_SEDIFF; + devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG); + + /* startup acquisition */ + + spin_lock_irqsave(&dev->spinlock, flags); + + /* use 2 cascaded counters for pacing */ + devpriv->cmd2 |= CMD2_TBSEL; + + devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG); + if (cmd->start_src == TRIG_EXT) + devpriv->cmd2 |= CMD2_HWTRIG; + else + devpriv->cmd2 |= CMD2_SWTRIG; + if (cmd->stop_src == TRIG_EXT) + devpriv->cmd2 |= (CMD2_HWTRIG | CMD2_PRETRIG); + + devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG); + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +/* read all available samples from ai fifo */ +static int labpc_drain_fifo(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + struct comedi_async *async = dev->read_subdev->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned short data; + const int timeout = 10000; + unsigned int i; + + devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG); + + for (i = 0; (devpriv->stat1 & STAT1_DAVAIL) && i < timeout; + i++) { + /* quit if we have all the data we want */ + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->count == 0) + break; + devpriv->count--; + } + data = labpc_read_adc_fifo(dev); + comedi_buf_write_samples(dev->read_subdev, &data, 1); + devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG); + } + if (i == timeout) { + dev_err(dev->class_dev, "ai timeout, fifo never empties\n"); + async->events |= COMEDI_CB_ERROR; + return -1; + } + + return 0; +} + +/* + * Makes sure all data acquired by board is transferred to comedi (used + * when acquisition is terminated by stop_src == TRIG_EXT). + */ +static void labpc_drain_dregs(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + if (devpriv->current_transfer == isa_dma_transfer) + labpc_drain_dma(dev); + + labpc_drain_fifo(dev); +} + +/* interrupt service routine */ +static irqreturn_t labpc_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + + if (!dev->attached) { + dev_err(dev->class_dev, "premature interrupt\n"); + return IRQ_HANDLED; + } + + async = s->async; + cmd = &async->cmd; + + /* read board status */ + devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG); + if (board->is_labpc1200) + devpriv->stat2 = devpriv->read_byte(dev, STAT2_REG); + + if ((devpriv->stat1 & (STAT1_GATA0 | STAT1_CNTINT | STAT1_OVERFLOW | + STAT1_OVERRUN | STAT1_DAVAIL)) == 0 && + (devpriv->stat2 & STAT2_OUTA1) == 0 && + (devpriv->stat2 & STAT2_FIFONHF)) { + return IRQ_NONE; + } + + if (devpriv->stat1 & STAT1_OVERRUN) { + /* clear error interrupt */ + devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG); + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + dev_err(dev->class_dev, "overrun\n"); + return IRQ_HANDLED; + } + + if (devpriv->current_transfer == isa_dma_transfer) + labpc_handle_dma_status(dev); + else + labpc_drain_fifo(dev); + + if (devpriv->stat1 & STAT1_CNTINT) { + dev_err(dev->class_dev, "handled timer interrupt?\n"); + /* clear it */ + devpriv->write_byte(dev, 0x1, TIMER_CLEAR_REG); + } + + if (devpriv->stat1 & STAT1_OVERFLOW) { + /* clear error interrupt */ + devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG); + async->events |= COMEDI_CB_ERROR; + comedi_handle_events(dev, s); + dev_err(dev->class_dev, "overflow\n"); + return IRQ_HANDLED; + } + /* handle external stop trigger */ + if (cmd->stop_src == TRIG_EXT) { + if (devpriv->stat2 & STAT2_OUTA1) { + labpc_drain_dregs(dev); + async->events |= COMEDI_CB_EOA; + } + } + + /* TRIG_COUNT end of acquisition */ + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->count == 0) + async->events |= COMEDI_CB_EOA; + } + + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static void labpc_ao_write(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, unsigned int val) +{ + struct labpc_private *devpriv = dev->private; + + devpriv->write_byte(dev, val & 0xff, DAC_LSB_REG(chan)); + devpriv->write_byte(dev, (val >> 8) & 0xff, DAC_MSB_REG(chan)); + + s->readback[chan] = val; +} + +static int labpc_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv = dev->private; + unsigned int channel; + unsigned int range; + unsigned int i; + unsigned long flags; + + channel = CR_CHAN(insn->chanspec); + + /* + * Turn off pacing of analog output channel. + * NOTE: hardware bug in daqcard-1200 means pacing cannot + * be independently enabled/disabled for its the two channels. + */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->cmd2 &= ~CMD2_LDAC(channel); + devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* set range */ + if (board->is_labpc1200) { + range = CR_RANGE(insn->chanspec); + if (comedi_range_is_unipolar(s, range)) + devpriv->cmd6 |= CMD6_DACUNI(channel); + else + devpriv->cmd6 &= ~CMD6_DACUNI(channel); + /* write to register */ + devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG); + } + /* send data */ + for (i = 0; i < insn->n; i++) + labpc_ao_write(dev, s, channel, data[i]); + + return insn->n; +} + +/* lowlevel write to eeprom/dac */ +static void labpc_serial_out(struct comedi_device *dev, unsigned int value, + unsigned int value_width) +{ + struct labpc_private *devpriv = dev->private; + int i; + + for (i = 1; i <= value_width; i++) { + /* clear serial clock */ + devpriv->cmd5 &= ~CMD5_SCLK; + /* send bits most significant bit first */ + if (value & (1 << (value_width - i))) + devpriv->cmd5 |= CMD5_SDATA; + else + devpriv->cmd5 &= ~CMD5_SDATA; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + /* set clock to load bit */ + devpriv->cmd5 |= CMD5_SCLK; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + } +} + +/* lowlevel read from eeprom */ +static unsigned int labpc_serial_in(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + unsigned int value = 0; + int i; + const int value_width = 8; /* number of bits wide values are */ + + for (i = 1; i <= value_width; i++) { + /* set serial clock */ + devpriv->cmd5 |= CMD5_SCLK; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + /* clear clock bit */ + devpriv->cmd5 &= ~CMD5_SCLK; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + /* read bits most significant bit first */ + udelay(1); + devpriv->stat2 = devpriv->read_byte(dev, STAT2_REG); + if (devpriv->stat2 & STAT2_PROMOUT) + value |= 1 << (value_width - i); + } + + return value; +} + +static unsigned int labpc_eeprom_read(struct comedi_device *dev, + unsigned int address) +{ + struct labpc_private *devpriv = dev->private; + unsigned int value; + /* bits to tell eeprom to expect a read */ + const int read_instruction = 0x3; + /* 8 bit write lengths to eeprom */ + const int write_length = 8; + + /* enable read/write to eeprom */ + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + /* send read instruction */ + labpc_serial_out(dev, read_instruction, write_length); + /* send 8 bit address to read from */ + labpc_serial_out(dev, address, write_length); + /* read result */ + value = labpc_serial_in(dev); + + /* disable read/write to eeprom */ + devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + return value; +} + +static unsigned int labpc_eeprom_read_status(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + unsigned int value; + const int read_status_instruction = 0x5; + const int write_length = 8; /* 8 bit write lengths to eeprom */ + + /* enable read/write to eeprom */ + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + /* send read status instruction */ + labpc_serial_out(dev, read_status_instruction, write_length); + /* read result */ + value = labpc_serial_in(dev); + + /* disable read/write to eeprom */ + devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + return value; +} + +static void labpc_eeprom_write(struct comedi_device *dev, + unsigned int address, unsigned int value) +{ + struct labpc_private *devpriv = dev->private; + const int write_enable_instruction = 0x6; + const int write_instruction = 0x2; + const int write_length = 8; /* 8 bit write lengths to eeprom */ + + /* enable read/write to eeprom */ + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + /* send write_enable instruction */ + labpc_serial_out(dev, write_enable_instruction, write_length); + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + /* send write instruction */ + devpriv->cmd5 |= CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + labpc_serial_out(dev, write_instruction, write_length); + /* send 8 bit address to write to */ + labpc_serial_out(dev, address, write_length); + /* write value */ + labpc_serial_out(dev, value, write_length); + devpriv->cmd5 &= ~CMD5_EEPROMCS; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + /* disable read/write to eeprom */ + devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); +} + +/* writes to 8 bit calibration dacs */ +static void write_caldac(struct comedi_device *dev, unsigned int channel, + unsigned int value) +{ + struct labpc_private *devpriv = dev->private; + + /* clear caldac load bit and make sure we don't write to eeprom */ + devpriv->cmd5 &= ~(CMD5_CALDACLD | CMD5_EEPROMCS | CMD5_WRTPRT); + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + + /* write 4 bit channel */ + labpc_serial_out(dev, channel, 4); + /* write 8 bit caldac value */ + labpc_serial_out(dev, value, 8); + + /* set and clear caldac bit to load caldac value */ + devpriv->cmd5 |= CMD5_CALDACLD; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + devpriv->cmd5 &= ~CMD5_CALDACLD; + udelay(1); + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); +} + +static int labpc_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* + * Only write the last data value to the caldac. Preceding + * data would be overwritten anyway. + */ + if (insn->n > 0) { + unsigned int val = data[insn->n - 1]; + + if (s->readback[chan] != val) { + write_caldac(dev, chan, val); + s->readback[chan] = val; + } + } + + return insn->n; +} + +static int labpc_eeprom_ready(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + /* make sure there isn't already a write in progress */ + status = labpc_eeprom_read_status(dev); + if ((status & 0x1) == 0) + return 0; + return -EBUSY; +} + +static int labpc_eeprom_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int ret; + + /* only allow writes to user area of eeprom */ + if (chan < 16 || chan > 127) + return -EINVAL; + + /* + * Only write the last data value to the eeprom. Preceding + * data would be overwritten anyway. + */ + if (insn->n > 0) { + unsigned int val = data[insn->n - 1]; + + ret = comedi_timeout(dev, s, insn, labpc_eeprom_ready, 0); + if (ret) + return ret; + + labpc_eeprom_write(dev, chan, val); + s->readback[chan] = val; + } + + return insn->n; +} + +int labpc_common_attach(struct comedi_device *dev, + unsigned int irq, unsigned long isr_flags) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv; + struct comedi_subdevice *s; + int ret; + int i; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + if (dev->mmio) { + devpriv->read_byte = labpc_readb; + devpriv->write_byte = labpc_writeb; + } else { + devpriv->read_byte = labpc_inb; + devpriv->write_byte = labpc_outb; + } + + /* initialize board's command registers */ + devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG); + devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG); + devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG); + devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG); + if (board->is_labpc1200) { + devpriv->write_byte(dev, devpriv->cmd5, CMD5_REG); + devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG); + } + + if (irq) { + ret = request_irq(irq, labpc_interrupt, isr_flags, + dev->board_name, dev); + if (ret == 0) + dev->irq = irq; + } + + if (dev->mmio) { + dev->pacer = comedi_8254_mm_init(dev->mmio + COUNTER_B_BASE_REG, + I8254_OSC_BASE_2MHZ, + I8254_IO8, 0); + devpriv->counter = comedi_8254_mm_init(dev->mmio + + COUNTER_A_BASE_REG, + I8254_OSC_BASE_2MHZ, + I8254_IO8, 0); + } else { + dev->pacer = comedi_8254_init(dev->iobase + COUNTER_B_BASE_REG, + I8254_OSC_BASE_2MHZ, + I8254_IO8, 0); + devpriv->counter = comedi_8254_init(dev->iobase + + COUNTER_A_BASE_REG, + I8254_OSC_BASE_2MHZ, + I8254_IO8, 0); + } + if (!dev->pacer || !devpriv->counter) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 5); + if (ret) + return ret; + + /* analog input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF; + s->n_chan = 8; + s->len_chanlist = 8; + s->maxdata = 0x0fff; + s->range_table = board->is_labpc1200 ? + &range_labpc_1200_ai : &range_labpc_plus_ai; + s->insn_read = labpc_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmd = labpc_ai_cmd; + s->do_cmdtest = labpc_ai_cmdtest; + s->cancel = labpc_cancel; + } + + /* analog output */ + s = &dev->subdevices[1]; + if (board->has_ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &range_labpc_ao; + s->insn_write = labpc_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* initialize analog outputs to a known value */ + for (i = 0; i < s->n_chan; i++) + labpc_ao_write(dev, s, i, s->maxdata / 2); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 dio */ + s = &dev->subdevices[2]; + if (dev->mmio) + ret = subdev_8255_mm_init(dev, s, NULL, DIO_BASE_REG); + else + ret = subdev_8255_init(dev, s, NULL, DIO_BASE_REG); + if (ret) + return ret; + + /* calibration subdevices for boards that have one */ + s = &dev->subdevices[3]; + if (board->is_labpc1200) { + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 16; + s->maxdata = 0xff; + s->insn_write = labpc_calib_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) { + write_caldac(dev, i, s->maxdata / 2); + s->readback[i] = s->maxdata / 2; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* EEPROM (256 bytes) */ + 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 = 256; + s->maxdata = 0xff; + s->insn_write = labpc_eeprom_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + for (i = 0; i < s->n_chan; i++) + s->readback[i] = labpc_eeprom_read(dev, i); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} +EXPORT_SYMBOL_GPL(labpc_common_attach); + +void labpc_common_detach(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + if (devpriv) + kfree(devpriv->counter); +} +EXPORT_SYMBOL_GPL(labpc_common_detach); + +static int __init labpc_common_init(void) +{ + return 0; +} +module_init(labpc_common_init); + +static void __exit labpc_common_exit(void) +{ +} +module_exit(labpc_common_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi helper for ni_labpc, ni_labpc_pci, ni_labpc_cs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_labpc_cs.c b/drivers/comedi/drivers/ni_labpc_cs.c new file mode 100644 index 000000000..62fecb50e --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc_cs.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +/* + * 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 <linux/comedi/comedi_pcmcia.h> + +#include "ni_labpc.h" + +static const struct labpc_boardinfo labpc_cs_boards[] = { + { + .name = "daqcard-1200", + .ai_speed = 10000, + .has_ao = 1, + .is_labpc1200 = 1, + }, +}; + +static int labpc_cs_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + int ret; + + /* The ni_labpc driver needs the board_ptr */ + dev->board_ptr = &labpc_cs_boards[0]; + + link->config_flags |= CONF_AUTO_SET_IO | + CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ; + ret = comedi_pcmcia_enable(dev, NULL); + if (ret) + return ret; + dev->iobase = link->resource[0]->start; + + if (!link->irq) + return -EINVAL; + + return labpc_common_attach(dev, link->irq, IRQF_SHARED); +} + +static void labpc_cs_detach(struct comedi_device *dev) +{ + labpc_common_detach(dev); + comedi_pcmcia_disable(dev); +} + +static struct comedi_driver driver_labpc_cs = { + .driver_name = "ni_labpc_cs", + .module = THIS_MODULE, + .auto_attach = labpc_cs_auto_attach, + .detach = labpc_cs_detach, +}; + +static int labpc_cs_attach(struct pcmcia_device *link) +{ + return comedi_pcmcia_auto_config(link, &driver_labpc_cs); +} + +static const struct pcmcia_device_id labpc_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0103), /* daqcard-1200 */ + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, labpc_cs_ids); + +static struct pcmcia_driver labpc_cs_driver = { + .name = "daqcard-1200", + .owner = THIS_MODULE, + .id_table = labpc_cs_ids, + .probe = labpc_cs_attach, + .remove = comedi_pcmcia_auto_unconfig, +}; +module_comedi_pcmcia_driver(driver_labpc_cs, labpc_cs_driver); + +MODULE_DESCRIPTION("Comedi driver for National Instruments Lab-PC"); +MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_labpc_isadma.c b/drivers/comedi/drivers/ni_labpc_isadma.c new file mode 100644 index 000000000..0652ca834 --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc_isadma.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_isadma.h> + +#include "ni_labpc.h" +#include "ni_labpc_regs.h" +#include "ni_labpc_isadma.h" + +/* size in bytes of dma buffer */ +#define LABPC_ISADMA_BUFFER_SIZE 0xff00 + +/* utility function that suggests a dma transfer size in bytes */ +static unsigned int labpc_suggest_transfer_size(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int maxbytes) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int sample_size = comedi_bytes_per_sample(s); + unsigned int size; + unsigned int freq; + + if (cmd->convert_src == TRIG_TIMER) + freq = 1000000000 / cmd->convert_arg; + else + /* return some default value */ + freq = 0xffffffff; + + /* make buffer fill in no more than 1/3 second */ + size = (freq / 3) * sample_size; + + /* set a minimum and maximum size allowed */ + if (size > maxbytes) + size = maxbytes; + else if (size < sample_size) + size = sample_size; + + return size; +} + +void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct labpc_private *devpriv = dev->private; + struct comedi_isadma_desc *desc = &devpriv->dma->desc[0]; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int sample_size = comedi_bytes_per_sample(s); + + /* set appropriate size of transfer */ + desc->size = labpc_suggest_transfer_size(dev, s, desc->maxsize); + if (cmd->stop_src == TRIG_COUNT && + devpriv->count * sample_size < desc->size) + desc->size = devpriv->count * sample_size; + + comedi_isadma_program(desc); + + /* set CMD3 bits for caller to enable DMA and interrupt */ + devpriv->cmd3 |= (CMD3_DMAEN | CMD3_DMATCINTEN); +} +EXPORT_SYMBOL_GPL(labpc_setup_dma); + +void labpc_drain_dma(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + struct comedi_isadma_desc *desc = &devpriv->dma->desc[0]; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int max_samples = comedi_bytes_to_samples(s, desc->size); + unsigned int residue; + unsigned int nsamples; + unsigned int leftover; + + /* + * residue is the number of bytes left to be done on the dma + * transfer. It should always be zero at this point unless + * the stop_src is set to external triggering. + */ + residue = comedi_isadma_disable(desc->chan); + + /* + * Figure out how many samples to read for this transfer and + * how many will be stored for next time. + */ + nsamples = max_samples - comedi_bytes_to_samples(s, residue); + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->count <= nsamples) { + nsamples = devpriv->count; + leftover = 0; + } else { + leftover = devpriv->count - nsamples; + if (leftover > max_samples) + leftover = max_samples; + } + devpriv->count -= nsamples; + } else { + leftover = max_samples; + } + desc->size = comedi_samples_to_bytes(s, leftover); + + comedi_buf_write_samples(s, desc->virt_addr, nsamples); +} +EXPORT_SYMBOL_GPL(labpc_drain_dma); + +static void handle_isa_dma(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + struct comedi_isadma_desc *desc = &devpriv->dma->desc[0]; + + labpc_drain_dma(dev); + + if (desc->size) + comedi_isadma_program(desc); + + /* clear dma tc interrupt */ + devpriv->write_byte(dev, 0x1, DMATC_CLEAR_REG); +} + +void labpc_handle_dma_status(struct comedi_device *dev) +{ + const struct labpc_boardinfo *board = dev->board_ptr; + struct labpc_private *devpriv = dev->private; + + /* + * if a dma terminal count of external stop trigger + * has occurred + */ + if (devpriv->stat1 & STAT1_GATA0 || + (board->is_labpc1200 && devpriv->stat2 & STAT2_OUTA1)) + handle_isa_dma(dev); +} +EXPORT_SYMBOL_GPL(labpc_handle_dma_status); + +void labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan) +{ + struct labpc_private *devpriv = dev->private; + + /* only DMA channels 3 and 1 are valid */ + if (dma_chan != 1 && dma_chan != 3) + return; + + /* DMA uses 1 buffer */ + devpriv->dma = comedi_isadma_alloc(dev, 1, dma_chan, dma_chan, + LABPC_ISADMA_BUFFER_SIZE, + COMEDI_ISADMA_READ); +} +EXPORT_SYMBOL_GPL(labpc_init_dma_chan); + +void labpc_free_dma_chan(struct comedi_device *dev) +{ + struct labpc_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} +EXPORT_SYMBOL_GPL(labpc_free_dma_chan); + +static int __init ni_labpc_isadma_init_module(void) +{ + return 0; +} +module_init(ni_labpc_isadma_init_module); + +static void __exit ni_labpc_isadma_cleanup_module(void) +{ +} +module_exit(ni_labpc_isadma_cleanup_module); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi NI Lab-PC ISA DMA support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_labpc_isadma.h b/drivers/comedi/drivers/ni_labpc_isadma.h new file mode 100644 index 000000000..f06f9353c --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc_isadma.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ni_labpc ISA DMA support. + */ + +#ifndef _NI_LABPC_ISADMA_H +#define _NI_LABPC_ISADMA_H + +#if IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISADMA) + +void labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan); +void labpc_free_dma_chan(struct comedi_device *dev); +void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s); +void labpc_drain_dma(struct comedi_device *dev); +void labpc_handle_dma_status(struct comedi_device *dev); + +#else + +static inline void labpc_init_dma_chan(struct comedi_device *dev, + unsigned int dma_chan) +{ +} + +static inline void labpc_free_dma_chan(struct comedi_device *dev) +{ +} + +static inline void labpc_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ +} + +static inline void labpc_drain_dma(struct comedi_device *dev) +{ +} + +static inline void labpc_handle_dma_status(struct comedi_device *dev) +{ +} + +#endif + +#endif /* _NI_LABPC_ISADMA_H */ diff --git a/drivers/comedi/drivers/ni_labpc_pci.c b/drivers/comedi/drivers/ni_labpc_pci.c new file mode 100644 index 000000000..e2a44bbd9 --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc_pci.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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/comedi/comedi_pci.h> + +#include "ni_labpc.h" + +enum labpc_pci_boardid { + BOARD_NI_PCI1200, +}; + +static const struct labpc_boardinfo labpc_pci_boards[] = { + [BOARD_NI_PCI1200] = { + .name = "ni_pci-1200", + .ai_speed = 10000, + .ai_scan_up = 1, + .has_ao = 1, + .is_labpc1200 = 1, + }, +}; + +/* ripped from mite.h and mite_setup2() to avoid mite dependency */ +#define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */ +#define WENAB BIT(7) /* window enable */ + +static int labpc_pci_mite_init(struct pci_dev *pcidev) +{ + void __iomem *mite_base; + u32 main_phys_addr; + + /* ioremap the MITE registers (BAR 0) temporarily */ + mite_base = pci_ioremap_bar(pcidev, 0); + if (!mite_base) + return -ENOMEM; + + /* set data window to main registers (BAR 1) */ + main_phys_addr = pci_resource_start(pcidev, 1); + writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR); + + /* finished with MITE registers */ + iounmap(mite_base); + return 0; +} + +static int labpc_pci_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct labpc_boardinfo *board = NULL; + int ret; + + if (context < ARRAY_SIZE(labpc_pci_boards)) + board = &labpc_pci_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + ret = labpc_pci_mite_init(pcidev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 1); + if (!dev->mmio) + return -ENOMEM; + + return labpc_common_attach(dev, pcidev->irq, IRQF_SHARED); +} + +static void labpc_pci_detach(struct comedi_device *dev) +{ + labpc_common_detach(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver labpc_pci_comedi_driver = { + .driver_name = "labpc_pci", + .module = THIS_MODULE, + .auto_attach = labpc_pci_auto_attach, + .detach = labpc_pci_detach, +}; + +static const struct pci_device_id labpc_pci_table[] = { + { PCI_VDEVICE(NI, 0x161), BOARD_NI_PCI1200 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, labpc_pci_table); + +static int labpc_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &labpc_pci_comedi_driver, + id->driver_data); +} + +static struct pci_driver labpc_pci_driver = { + .name = "labpc_pci", + .id_table = labpc_pci_table, + .probe = labpc_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(labpc_pci_comedi_driver, labpc_pci_driver); + +MODULE_DESCRIPTION("Comedi: National Instruments Lab-PC PCI-1200 driver"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_labpc_regs.h b/drivers/comedi/drivers/ni_labpc_regs.h new file mode 100644 index 000000000..ace40065a --- /dev/null +++ b/drivers/comedi/drivers/ni_labpc_regs.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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 BIT(0) +#define STAT1_OVERRUN BIT(1) +#define STAT1_OVERFLOW BIT(2) +#define STAT1_CNTINT BIT(3) +#define STAT1_GATA0 BIT(5) +#define STAT1_EXTGATA0 BIT(6) +#define CMD1_REG 0x00 /* W: Command 1 reg */ +#define CMD1_MA(x) (((x) & 0x7) << 0) +#define CMD1_TWOSCMP BIT(3) +#define CMD1_GAIN(x) (((x) & 0x7) << 4) +#define CMD1_SCANEN BIT(7) +#define CMD2_REG 0x01 /* W: Command 2 reg */ +#define CMD2_PRETRIG BIT(0) +#define CMD2_HWTRIG BIT(1) +#define CMD2_SWTRIG BIT(2) +#define CMD2_TBSEL BIT(3) +#define CMD2_2SDAC0 BIT(4) +#define CMD2_2SDAC1 BIT(5) +#define CMD2_LDAC(x) BIT(6 + ((x) & 0x1)) +#define CMD3_REG 0x02 /* W: Command 3 reg */ +#define CMD3_DMAEN BIT(0) +#define CMD3_DIOINTEN BIT(1) +#define CMD3_DMATCINTEN BIT(2) +#define CMD3_CNTINTEN BIT(3) +#define CMD3_ERRINTEN BIT(4) +#define CMD3_FIFOINTEN BIT(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 BIT(0) +#define CMD6_ADCUNI BIT(1) +#define CMD6_DACUNI(x) BIT(2 + ((x) & 0x1)) +#define CMD6_HFINTEN BIT(5) +#define CMD6_DQINTEN BIT(6) +#define CMD6_SCANUP BIT(7) +#define CMD4_REG 0x0f /* W: Command 3 reg */ +#define CMD4_INTSCAN BIT(0) +#define CMD4_EOIRCV BIT(1) +#define CMD4_ECLKDRV BIT(2) +#define CMD4_SEDIFF BIT(3) +#define CMD4_ECLKRCV BIT(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 BIT(2) +#define CMD5_DITHEREN BIT(3) +#define CMD5_CALDACLD BIT(4) +#define CMD5_SCLK BIT(5) +#define CMD5_SDATA BIT(6) +#define CMD5_EEPROMCS BIT(7) +#define STAT2_REG 0x1d /* R: Status 2 reg */ +#define STAT2_PROMOUT BIT(0) +#define STAT2_OUTA1 BIT(1) +#define STAT2_FIFONHF BIT(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/comedi/drivers/ni_mio_common.c b/drivers/comedi/drivers/ni_mio_common.c new file mode 100644 index 000000000..d39998565 --- /dev/null +++ b/drivers/comedi/drivers/ni_mio_common.c @@ -0,0 +1,6341 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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 (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 (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 <linux/comedi/comedi_8255.h> +#include "mite.h" + +/* A timeout count */ +#define NI_TIMEOUT 1000 + +/* Note: this table must match the ai_gain_* definitions */ +static const short ni_gainlkup[][16] = { + [ai_gain_16] = {0, 1, 2, 3, 4, 5, 6, 7, + 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107}, + [ai_gain_8] = {1, 2, 4, 7, 0x101, 0x102, 0x104, 0x107}, + [ai_gain_14] = {1, 2, 3, 4, 5, 6, 7, + 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107}, + [ai_gain_4] = {0, 1, 4, 7}, + [ai_gain_611x] = {0x00a, 0x00b, 0x001, 0x002, + 0x003, 0x004, 0x005, 0x006}, + [ai_gain_622x] = {0, 1, 4, 5}, + [ai_gain_628x] = {1, 2, 3, 4, 5, 6, 7}, + [ai_gain_6143] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, +}; + +static const struct comedi_lrange range_ni_E_ai = { + 16, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(20), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1) + } +}; + +static const struct comedi_lrange range_ni_E_ai_limited = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(1), + BIP_RANGE(0.1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(1), + UNI_RANGE(0.1) + } +}; + +static const struct comedi_lrange range_ni_E_ai_limited14 = { + 14, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.2), + BIP_RANGE(0.1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1) + } +}; + +static const struct comedi_lrange range_ni_E_ai_bipolar4 = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05) + } +}; + +static const struct comedi_lrange range_ni_E_ai_611x = { + 8, { + BIP_RANGE(50), + BIP_RANGE(20), + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.2) + } +}; + +static const struct comedi_lrange range_ni_M_ai_622x = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(1), + BIP_RANGE(0.2) + } +}; + +static const struct comedi_lrange range_ni_M_ai_628x = { + 7, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.2), + BIP_RANGE(0.1) + } +}; + +static const struct comedi_lrange range_ni_E_ao_ext = { + 4, { + BIP_RANGE(10), + UNI_RANGE(10), + RANGE_ext(-1, 1), + RANGE_ext(0, 1) + } +}; + +static const struct comedi_lrange *const ni_range_lkup[] = { + [ai_gain_16] = &range_ni_E_ai, + [ai_gain_8] = &range_ni_E_ai_limited, + [ai_gain_14] = &range_ni_E_ai_limited14, + [ai_gain_4] = &range_ni_E_ai_bipolar4, + [ai_gain_611x] = &range_ni_E_ai_611x, + [ai_gain_622x] = &range_ni_M_ai_622x, + [ai_gain_628x] = &range_ni_M_ai_628x, + [ai_gain_6143] = &range_bipolar5 +}; + +enum aimodes { + AIMODE_NONE = 0, + AIMODE_HALF_FULL = 1, + AIMODE_SCAN = 2, + AIMODE_SAMPLE = 3, +}; + +enum ni_common_subdevices { + NI_AI_SUBDEV, + NI_AO_SUBDEV, + NI_DIO_SUBDEV, + NI_8255_DIO_SUBDEV, + NI_UNUSED_SUBDEV, + NI_CALIBRATION_SUBDEV, + NI_EEPROM_SUBDEV, + NI_PFI_DIO_SUBDEV, + NI_CS5529_CALIBRATION_SUBDEV, + NI_SERIAL_SUBDEV, + NI_RTSI_SUBDEV, + NI_GPCT0_SUBDEV, + NI_GPCT1_SUBDEV, + NI_FREQ_OUT_SUBDEV, + NI_NUM_SUBDEVICES +}; + +#define NI_GPCT_SUBDEV(x) (NI_GPCT0_SUBDEV + (x)) + +enum timebase_nanoseconds { + TIMEBASE_1_NS = 50, + TIMEBASE_2_NS = 10000 +}; + +#define SERIAL_DISABLED 0 +#define SERIAL_600NS 600 +#define SERIAL_1_2US 1200 +#define SERIAL_10US 10000 + +static const int num_adc_stages_611x = 3; + +static void ni_writel(struct comedi_device *dev, unsigned int data, int reg) +{ + if (dev->mmio) + writel(data, dev->mmio + reg); + else + outl(data, dev->iobase + reg); +} + +static void ni_writew(struct comedi_device *dev, unsigned int data, int reg) +{ + if (dev->mmio) + writew(data, dev->mmio + reg); + else + outw(data, dev->iobase + reg); +} + +static void ni_writeb(struct comedi_device *dev, unsigned int data, int reg) +{ + if (dev->mmio) + writeb(data, dev->mmio + reg); + else + outb(data, dev->iobase + reg); +} + +static unsigned int ni_readl(struct comedi_device *dev, int reg) +{ + if (dev->mmio) + return readl(dev->mmio + reg); + + return inl(dev->iobase + reg); +} + +static unsigned int ni_readw(struct comedi_device *dev, int reg) +{ + if (dev->mmio) + return readw(dev->mmio + reg); + + return inw(dev->iobase + reg); +} + +static unsigned int ni_readb(struct comedi_device *dev, int reg) +{ + if (dev->mmio) + return readb(dev->mmio + reg); + + return inb(dev->iobase + reg); +} + +/* + * We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. + * + * The AT-MIO and DAQCard devices map the low 8 STC registers to + * iobase+reg*2. + * + * Most PCIMIO devices also map the low 8 STC registers but the + * 611x devices map the read registers to iobase+(addr-1)*2. + * For now non-windowed STC access is disabled if a PCIMIO device + * is detected (devpriv->mite has been initialized). + * + * The M series devices do not used windowed registers for the + * STC registers. The functions below handle the mapping of the + * windowed STC registers to the m series register offsets. + */ + +struct mio_regmap { + unsigned int mio_reg; + int size; +}; + +static const struct mio_regmap m_series_stc_write_regmap[] = { + [NISTC_INTA_ACK_REG] = { 0x104, 2 }, + [NISTC_INTB_ACK_REG] = { 0x106, 2 }, + [NISTC_AI_CMD2_REG] = { 0x108, 2 }, + [NISTC_AO_CMD2_REG] = { 0x10a, 2 }, + [NISTC_G0_CMD_REG] = { 0x10c, 2 }, + [NISTC_G1_CMD_REG] = { 0x10e, 2 }, + [NISTC_AI_CMD1_REG] = { 0x110, 2 }, + [NISTC_AO_CMD1_REG] = { 0x112, 2 }, + /* + * NISTC_DIO_OUT_REG maps to: + * { NI_M_DIO_REG, 4 } and { NI_M_SCXI_SER_DO_REG, 1 } + */ + [NISTC_DIO_OUT_REG] = { 0, 0 }, /* DOES NOT MAP CLEANLY */ + [NISTC_DIO_CTRL_REG] = { 0, 0 }, /* DOES NOT MAP CLEANLY */ + [NISTC_AI_MODE1_REG] = { 0x118, 2 }, + [NISTC_AI_MODE2_REG] = { 0x11a, 2 }, + [NISTC_AI_SI_LOADA_REG] = { 0x11c, 4 }, + [NISTC_AI_SI_LOADB_REG] = { 0x120, 4 }, + [NISTC_AI_SC_LOADA_REG] = { 0x124, 4 }, + [NISTC_AI_SC_LOADB_REG] = { 0x128, 4 }, + [NISTC_AI_SI2_LOADA_REG] = { 0x12c, 4 }, + [NISTC_AI_SI2_LOADB_REG] = { 0x130, 4 }, + [NISTC_G0_MODE_REG] = { 0x134, 2 }, + [NISTC_G1_MODE_REG] = { 0x136, 2 }, + [NISTC_G0_LOADA_REG] = { 0x138, 4 }, + [NISTC_G0_LOADB_REG] = { 0x13c, 4 }, + [NISTC_G1_LOADA_REG] = { 0x140, 4 }, + [NISTC_G1_LOADB_REG] = { 0x144, 4 }, + [NISTC_G0_INPUT_SEL_REG] = { 0x148, 2 }, + [NISTC_G1_INPUT_SEL_REG] = { 0x14a, 2 }, + [NISTC_AO_MODE1_REG] = { 0x14c, 2 }, + [NISTC_AO_MODE2_REG] = { 0x14e, 2 }, + [NISTC_AO_UI_LOADA_REG] = { 0x150, 4 }, + [NISTC_AO_UI_LOADB_REG] = { 0x154, 4 }, + [NISTC_AO_BC_LOADA_REG] = { 0x158, 4 }, + [NISTC_AO_BC_LOADB_REG] = { 0x15c, 4 }, + [NISTC_AO_UC_LOADA_REG] = { 0x160, 4 }, + [NISTC_AO_UC_LOADB_REG] = { 0x164, 4 }, + [NISTC_CLK_FOUT_REG] = { 0x170, 2 }, + [NISTC_IO_BIDIR_PIN_REG] = { 0x172, 2 }, + [NISTC_RTSI_TRIG_DIR_REG] = { 0x174, 2 }, + [NISTC_INT_CTRL_REG] = { 0x176, 2 }, + [NISTC_AI_OUT_CTRL_REG] = { 0x178, 2 }, + [NISTC_ATRIG_ETC_REG] = { 0x17a, 2 }, + [NISTC_AI_START_STOP_REG] = { 0x17c, 2 }, + [NISTC_AI_TRIG_SEL_REG] = { 0x17e, 2 }, + [NISTC_AI_DIV_LOADA_REG] = { 0x180, 4 }, + [NISTC_AO_START_SEL_REG] = { 0x184, 2 }, + [NISTC_AO_TRIG_SEL_REG] = { 0x186, 2 }, + [NISTC_G0_AUTOINC_REG] = { 0x188, 2 }, + [NISTC_G1_AUTOINC_REG] = { 0x18a, 2 }, + [NISTC_AO_MODE3_REG] = { 0x18c, 2 }, + [NISTC_RESET_REG] = { 0x190, 2 }, + [NISTC_INTA_ENA_REG] = { 0x192, 2 }, + [NISTC_INTA2_ENA_REG] = { 0, 0 }, /* E-Series only */ + [NISTC_INTB_ENA_REG] = { 0x196, 2 }, + [NISTC_INTB2_ENA_REG] = { 0, 0 }, /* E-Series only */ + [NISTC_AI_PERSONAL_REG] = { 0x19a, 2 }, + [NISTC_AO_PERSONAL_REG] = { 0x19c, 2 }, + [NISTC_RTSI_TRIGA_OUT_REG] = { 0x19e, 2 }, + [NISTC_RTSI_TRIGB_OUT_REG] = { 0x1a0, 2 }, + /* doc for following line: mhddk/nimseries/ChipObjects/tMSeries.h */ + [NISTC_RTSI_BOARD_REG] = { 0x1a2, 2 }, + [NISTC_CFG_MEM_CLR_REG] = { 0x1a4, 2 }, + [NISTC_ADC_FIFO_CLR_REG] = { 0x1a6, 2 }, + [NISTC_DAC_FIFO_CLR_REG] = { 0x1a8, 2 }, + [NISTC_AO_OUT_CTRL_REG] = { 0x1ac, 2 }, + [NISTC_AI_MODE3_REG] = { 0x1ae, 2 }, +}; + +static void m_series_stc_write(struct comedi_device *dev, + unsigned int data, unsigned int reg) +{ + const struct mio_regmap *regmap; + + if (reg < ARRAY_SIZE(m_series_stc_write_regmap)) { + regmap = &m_series_stc_write_regmap[reg]; + } else { + dev_warn(dev->class_dev, "%s: unhandled register=0x%x\n", + __func__, reg); + return; + } + + switch (regmap->size) { + case 4: + ni_writel(dev, data, regmap->mio_reg); + break; + case 2: + ni_writew(dev, data, regmap->mio_reg); + break; + default: + dev_warn(dev->class_dev, "%s: unmapped register=0x%x\n", + __func__, reg); + break; + } +} + +static const struct mio_regmap m_series_stc_read_regmap[] = { + [NISTC_AI_STATUS1_REG] = { 0x104, 2 }, + [NISTC_AO_STATUS1_REG] = { 0x106, 2 }, + [NISTC_G01_STATUS_REG] = { 0x108, 2 }, + [NISTC_AI_STATUS2_REG] = { 0, 0 }, /* Unknown */ + [NISTC_AO_STATUS2_REG] = { 0x10c, 2 }, + [NISTC_DIO_IN_REG] = { 0, 0 }, /* Unknown */ + [NISTC_G0_HW_SAVE_REG] = { 0x110, 4 }, + [NISTC_G1_HW_SAVE_REG] = { 0x114, 4 }, + [NISTC_G0_SAVE_REG] = { 0x118, 4 }, + [NISTC_G1_SAVE_REG] = { 0x11c, 4 }, + [NISTC_AO_UI_SAVE_REG] = { 0x120, 4 }, + [NISTC_AO_BC_SAVE_REG] = { 0x124, 4 }, + [NISTC_AO_UC_SAVE_REG] = { 0x128, 4 }, + [NISTC_STATUS1_REG] = { 0x136, 2 }, + [NISTC_DIO_SERIAL_IN_REG] = { 0x009, 1 }, + [NISTC_STATUS2_REG] = { 0x13a, 2 }, + [NISTC_AI_SI_SAVE_REG] = { 0x180, 4 }, + [NISTC_AI_SC_SAVE_REG] = { 0x184, 4 }, +}; + +static unsigned int m_series_stc_read(struct comedi_device *dev, + unsigned int reg) +{ + const struct mio_regmap *regmap; + + if (reg < ARRAY_SIZE(m_series_stc_read_regmap)) { + regmap = &m_series_stc_read_regmap[reg]; + } else { + dev_warn(dev->class_dev, "%s: unhandled register=0x%x\n", + __func__, reg); + return 0; + } + + switch (regmap->size) { + case 4: + return ni_readl(dev, regmap->mio_reg); + case 2: + return ni_readw(dev, regmap->mio_reg); + case 1: + return ni_readb(dev, regmap->mio_reg); + default: + dev_warn(dev->class_dev, "%s: unmapped register=0x%x\n", + __func__, reg); + return 0; + } +} + +static void ni_stc_writew(struct comedi_device *dev, + unsigned int data, int reg) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + if (devpriv->is_m_series) { + m_series_stc_write(dev, data, reg); + } else { + spin_lock_irqsave(&devpriv->window_lock, flags); + if (!devpriv->mite && reg < 8) { + ni_writew(dev, data, reg * 2); + } else { + ni_writew(dev, reg, NI_E_STC_WINDOW_ADDR_REG); + ni_writew(dev, data, NI_E_STC_WINDOW_DATA_REG); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); + } +} + +static void ni_stc_writel(struct comedi_device *dev, + unsigned int data, int reg) +{ + struct ni_private *devpriv = dev->private; + + if (devpriv->is_m_series) { + m_series_stc_write(dev, data, reg); + } else { + ni_stc_writew(dev, data >> 16, reg); + ni_stc_writew(dev, data & 0xffff, reg + 1); + } +} + +static unsigned int ni_stc_readw(struct comedi_device *dev, int reg) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + unsigned int val; + + if (devpriv->is_m_series) { + val = m_series_stc_read(dev, reg); + } else { + spin_lock_irqsave(&devpriv->window_lock, flags); + if (!devpriv->mite && reg < 8) { + val = ni_readw(dev, reg * 2); + } else { + ni_writew(dev, reg, NI_E_STC_WINDOW_ADDR_REG); + val = ni_readw(dev, NI_E_STC_WINDOW_DATA_REG); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); + } + return val; +} + +static unsigned int ni_stc_readl(struct comedi_device *dev, int reg) +{ + struct ni_private *devpriv = dev->private; + unsigned int val; + + if (devpriv->is_m_series) { + val = m_series_stc_read(dev, reg); + } else { + val = ni_stc_readw(dev, reg) << 16; + val |= ni_stc_readw(dev, reg + 1); + } + return val; +} + +static inline void ni_set_bitfield(struct comedi_device *dev, int reg, + unsigned int bit_mask, + unsigned int bit_values) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags); + switch (reg) { + case NISTC_INTA_ENA_REG: + devpriv->int_a_enable_reg &= ~bit_mask; + devpriv->int_a_enable_reg |= bit_values & bit_mask; + ni_stc_writew(dev, devpriv->int_a_enable_reg, reg); + break; + case NISTC_INTB_ENA_REG: + devpriv->int_b_enable_reg &= ~bit_mask; + devpriv->int_b_enable_reg |= bit_values & bit_mask; + ni_stc_writew(dev, devpriv->int_b_enable_reg, reg); + break; + case NISTC_IO_BIDIR_PIN_REG: + devpriv->io_bidirection_pin_reg &= ~bit_mask; + devpriv->io_bidirection_pin_reg |= bit_values & bit_mask; + ni_stc_writew(dev, devpriv->io_bidirection_pin_reg, reg); + break; + case NI_E_DMA_AI_AO_SEL_REG: + devpriv->ai_ao_select_reg &= ~bit_mask; + devpriv->ai_ao_select_reg |= bit_values & bit_mask; + ni_writeb(dev, devpriv->ai_ao_select_reg, reg); + break; + case NI_E_DMA_G0_G1_SEL_REG: + devpriv->g0_g1_select_reg &= ~bit_mask; + devpriv->g0_g1_select_reg |= bit_values & bit_mask; + ni_writeb(dev, devpriv->g0_g1_select_reg, reg); + break; + case NI_M_CDIO_DMA_SEL_REG: + devpriv->cdio_dma_select_reg &= ~bit_mask; + devpriv->cdio_dma_select_reg |= bit_values & bit_mask; + ni_writeb(dev, devpriv->cdio_dma_select_reg, reg); + break; + default: + dev_err(dev->class_dev, "called with invalid register %d\n", + reg); + break; + } + spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags); +} + +#ifdef PCIDMA + +/* selects the MITE channel to use for DMA */ +#define NI_STC_DMA_CHAN_SEL(x) (((x) < 4) ? BIT(x) : \ + ((x) == 4) ? 0x3 : \ + ((x) == 5) ? 0x5 : 0x0) + +/* DMA channel setup */ +static int ni_request_ai_mite_channel(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct mite_channel *mite_chan; + unsigned long flags; + unsigned int bits; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + mite_chan = mite_request_channel(devpriv->mite, devpriv->ai_mite_ring); + if (!mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, + "failed to reserve mite dma channel for analog input\n"); + return -EBUSY; + } + mite_chan->dir = COMEDI_INPUT; + devpriv->ai_mite_chan = mite_chan; + + bits = NI_STC_DMA_CHAN_SEL(mite_chan->channel); + ni_set_bitfield(dev, NI_E_DMA_AI_AO_SEL_REG, + NI_E_DMA_AI_SEL_MASK, NI_E_DMA_AI_SEL(bits)); + + 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; + struct mite_channel *mite_chan; + unsigned long flags; + unsigned int bits; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + mite_chan = mite_request_channel(devpriv->mite, devpriv->ao_mite_ring); + if (!mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, + "failed to reserve mite dma channel for analog output\n"); + return -EBUSY; + } + mite_chan->dir = COMEDI_OUTPUT; + devpriv->ao_mite_chan = mite_chan; + + bits = NI_STC_DMA_CHAN_SEL(mite_chan->channel); + ni_set_bitfield(dev, NI_E_DMA_AI_AO_SEL_REG, + NI_E_DMA_AO_SEL_MASK, NI_E_DMA_AO_SEL(bits)); + + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +static int ni_request_gpct_mite_channel(struct comedi_device *dev, + unsigned int gpct_index, + enum comedi_io_direction direction) +{ + struct ni_private *devpriv = dev->private; + struct ni_gpct *counter = &devpriv->counter_dev->counters[gpct_index]; + struct mite_channel *mite_chan; + unsigned long flags; + unsigned int bits; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + mite_chan = mite_request_channel(devpriv->mite, + devpriv->gpct_mite_ring[gpct_index]); + if (!mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, + "failed to reserve mite dma channel for counter\n"); + return -EBUSY; + } + mite_chan->dir = direction; + ni_tio_set_mite_channel(counter, mite_chan); + + bits = NI_STC_DMA_CHAN_SEL(mite_chan->channel); + ni_set_bitfield(dev, NI_E_DMA_G0_G1_SEL_REG, + NI_E_DMA_G0_G1_SEL_MASK(gpct_index), + NI_E_DMA_G0_G1_SEL(gpct_index, bits)); + + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +static int ni_request_cdo_mite_channel(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct mite_channel *mite_chan; + unsigned long flags; + unsigned int bits; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + mite_chan = mite_request_channel(devpriv->mite, devpriv->cdo_mite_ring); + if (!mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, + "failed to reserve mite dma channel for correlated digital output\n"); + return -EBUSY; + } + mite_chan->dir = COMEDI_OUTPUT; + devpriv->cdo_mite_chan = mite_chan; + + /* + * XXX just guessing NI_STC_DMA_CHAN_SEL() + * 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. + */ + bits = NI_STC_DMA_CHAN_SEL(mite_chan->channel); + ni_set_bitfield(dev, NI_M_CDIO_DMA_SEL_REG, + NI_M_CDIO_DMA_SEL_CDO_MASK, + NI_M_CDIO_DMA_SEL_CDO(bits)); + + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} +#endif /* PCIDMA */ + +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_bitfield(dev, NI_E_DMA_AI_AO_SEL_REG, + NI_E_DMA_AI_SEL_MASK, 0); + 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_bitfield(dev, NI_E_DMA_AI_AO_SEL_REG, + NI_E_DMA_AO_SEL_MASK, 0); + 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 int gpct_index) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + 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_bitfield(dev, NI_E_DMA_G0_G1_SEL_REG, + NI_E_DMA_G0_G1_SEL_MASK(gpct_index), 0); + 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); +} + +static void ni_release_cdo_mite_channel(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->cdo_mite_chan) { + ni_set_bitfield(dev, NI_M_CDIO_DMA_SEL_REG, + NI_M_CDIO_DMA_SEL_CDO_MASK, 0); + mite_release_channel(devpriv->cdo_mite_chan); + devpriv->cdo_mite_chan = NULL; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static void ni_e_series_enable_second_irq(struct comedi_device *dev, + unsigned int gpct_index, short enable) +{ + struct ni_private *devpriv = dev->private; + unsigned int val = 0; + int reg; + + if (devpriv->is_m_series || gpct_index > 1) + return; + + /* + * e-series boards use the second irq signals to generate + * dma requests for their counters + */ + if (gpct_index == 0) { + reg = NISTC_INTA2_ENA_REG; + if (enable) + val = NISTC_INTA_ENA_G0_GATE; + } else { + reg = NISTC_INTB2_ENA_REG; + if (enable) + val = NISTC_INTB_ENA_G1_GATE; + } + ni_stc_writew(dev, val, reg); +} +#endif /* PCIDMA */ + +static void ni_clear_ai_fifo(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + static const int timeout = 10000; + int i; + + if (devpriv->is_6143) { + /* Flush the 6143 data FIFO */ + ni_writel(dev, 0x10, NI6143_AI_FIFO_CTRL_REG); + ni_writel(dev, 0x00, NI6143_AI_FIFO_CTRL_REG); + /* Wait for complete */ + for (i = 0; i < timeout; i++) { + if (!(ni_readl(dev, NI6143_AI_FIFO_STATUS_REG) & 0x10)) + break; + udelay(1); + } + if (i == timeout) + dev_err(dev->class_dev, "FIFO flush timeout\n"); + } else { + ni_stc_writew(dev, 1, NISTC_ADC_FIFO_CLR_REG); + if (devpriv->is_625x) { + ni_writeb(dev, 0, NI_M_STATIC_AI_CTRL_REG(0)); + ni_writeb(dev, 1, NI_M_STATIC_AI_CTRL_REG(0)); +#if 0 + /* + * The NI example code does 3 convert pulses for 625x + * boards, But that appears to be wrong in practice. + */ + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); +#endif + } + } +} + +static inline void ni_ao_win_outw(struct comedi_device *dev, + unsigned int data, int addr) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(dev, addr, NI611X_AO_WINDOW_ADDR_REG); + ni_writew(dev, data, NI611X_AO_WINDOW_DATA_REG); + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static inline void ni_ao_win_outl(struct comedi_device *dev, + unsigned int data, int addr) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(dev, addr, NI611X_AO_WINDOW_ADDR_REG); + ni_writel(dev, data, NI611X_AO_WINDOW_DATA_REG); + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static inline unsigned short ni_ao_win_inw(struct comedi_device *dev, int addr) +{ + struct ni_private *devpriv = dev->private; + unsigned long flags; + unsigned short data; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(dev, addr, NI611X_AO_WINDOW_ADDR_REG); + data = ni_readw(dev, NI611X_AO_WINDOW_DATA_REG); + 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 int bits, unsigned int value) +{ + unsigned int bit_values; + + if (value) + bit_values = bits; + else + bit_values = 0; + ni_set_bitfield(dev, reg, bits, bit_values); +} + +#ifdef PCIDMA +static void ni_sync_ai_dma(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ai_mite_chan) + mite_sync_dma(devpriv->ai_mite_chan, s); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static int ni_ai_drain_dma(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int i; + static const int timeout = 10000; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ai_mite_chan) { + for (i = 0; i < timeout; i++) { + if ((ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E) && + mite_bytes_in_transit(devpriv->ai_mite_chan) == 0) + break; + udelay(5); + } + if (i == timeout) { + dev_err(dev->class_dev, "timed out\n"); + dev_err(dev->class_dev, + "mite_bytes_in_transit=%i, AI_Status1_Register=0x%x\n", + mite_bytes_in_transit(devpriv->ai_mite_chan), + ni_stc_readw(dev, NISTC_AI_STATUS1_REG)); + retval = -1; + } + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + ni_sync_ai_dma(dev); + + return retval; +} + +static int ni_ao_wait_for_dma_load(struct comedi_device *dev) +{ + static const int timeout = 10000; + int i; + + for (i = 0; i < timeout; i++) { + unsigned short b_status; + + b_status = ni_stc_readw(dev, NISTC_AO_STATUS1_REG); + if (b_status & NISTC_AO_STATUS1_FIFO_HF) + break; + /* + * If we poll too often, the pci bus activity seems + * to slow the dma transfer down. + */ + usleep_range(10, 100); + } + if (i == timeout) { + dev_err(dev->class_dev, "timed out waiting for dma load\n"); + return -EPIPE; + } + return 0; +} +#endif /* PCIDMA */ + +#ifndef PCIDMA + +static void ni_ao_fifo_load(struct comedi_device *dev, + struct comedi_subdevice *s, int n) +{ + struct ni_private *devpriv = dev->private; + int i; + unsigned short d; + unsigned int packed_data; + + for (i = 0; i < n; i++) { + comedi_buf_read_samples(s, &d, 1); + + if (devpriv->is_6xxx) { + packed_data = d & 0xffff; + /* 6711 only has 16 bit wide ao fifo */ + if (!devpriv->is_6711) { + comedi_buf_read_samples(s, &d, 1); + i++; + packed_data |= (d << 16) & 0xffff0000; + } + ni_writel(dev, packed_data, NI611X_AO_FIFO_DATA_REG); + } else { + ni_writew(dev, d, NI_E_AO_FIFO_DATA_REG); + } + } +} + +/* + * There's a small problem if the FIFO gets really low and we + * don't have the data to fill it. Basically, if after we fill + * the FIFO with all the data available, the FIFO is _still_ + * less than half full, we never clear the interrupt. If the + * IRQ is in edge mode, we never get another interrupt, because + * this one wasn't cleared. If in level mode, we get flooded + * with interrupts that we can't fulfill, because nothing ever + * gets put into the buffer. + * + * This kind of situation is recoverable, but it is easier to + * just pretend we had a FIFO underrun, since there is a good + * chance it will happen anyway. This is _not_ the case for + * RT code, as RT code might purposely be running close to the + * metal. Needs to be fixed eventually. + */ +static int ni_ao_fifo_half_empty(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct ni_board_struct *board = dev->board_ptr; + unsigned int nbytes; + unsigned int nsamples; + + nbytes = comedi_buf_read_n_available(s); + if (nbytes == 0) { + s->async->events |= COMEDI_CB_OVERFLOW; + return 0; + } + + nsamples = comedi_bytes_to_samples(s, nbytes); + if (nsamples > board->ao_fifo_depth / 2) + nsamples = board->ao_fifo_depth / 2; + + ni_ao_fifo_load(dev, s, nsamples); + + return 1; +} + +static int ni_ao_prep_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + unsigned int nbytes; + unsigned int nsamples; + + /* reset fifo */ + ni_stc_writew(dev, 1, NISTC_DAC_FIFO_CLR_REG); + if (devpriv->is_6xxx) + ni_ao_win_outl(dev, 0x6, NI611X_AO_FIFO_OFFSET_LOAD_REG); + + /* load some data */ + nbytes = comedi_buf_read_n_available(s); + if (nbytes == 0) + return 0; + + nsamples = comedi_bytes_to_samples(s, nbytes); + if (nsamples > board->ao_fifo_depth) + nsamples = board->ao_fifo_depth; + + ni_ao_fifo_load(dev, s, nsamples); + + return nsamples; +} + +static void ni_ai_fifo_read(struct comedi_device *dev, + struct comedi_subdevice *s, int n) +{ + struct ni_private *devpriv = dev->private; + struct comedi_async *async = s->async; + unsigned int dl; + unsigned short data; + int i; + + if (devpriv->is_611x) { + for (i = 0; i < n / 2; i++) { + dl = ni_readl(dev, NI611X_AI_FIFO_DATA_REG); + /* This may get the hi/lo data in the wrong order */ + data = (dl >> 16) & 0xffff; + comedi_buf_write_samples(s, &data, 1); + data = dl & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } + /* Check if there's a single sample stuck in the FIFO */ + if (n % 2) { + dl = ni_readl(dev, NI611X_AI_FIFO_DATA_REG); + data = dl & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } + } else if (devpriv->is_6143) { + /* + * This just reads the FIFO assuming the data is present, + * no checks on the FIFO status are performed. + */ + for (i = 0; i < n / 2; i++) { + dl = ni_readl(dev, NI6143_AI_FIFO_DATA_REG); + + data = (dl >> 16) & 0xffff; + comedi_buf_write_samples(s, &data, 1); + data = dl & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } + if (n % 2) { + /* Assume there is a single sample stuck in the FIFO */ + /* Get stranded sample into FIFO */ + ni_writel(dev, 0x01, NI6143_AI_FIFO_CTRL_REG); + dl = ni_readl(dev, NI6143_AI_FIFO_DATA_REG); + data = (dl >> 16) & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } + } else { + if (n > ARRAY_SIZE(devpriv->ai_fifo_buffer)) { + dev_err(dev->class_dev, + "bug! ai_fifo_buffer too small\n"); + async->events |= COMEDI_CB_ERROR; + return; + } + for (i = 0; i < n; i++) { + devpriv->ai_fifo_buffer[i] = + ni_readw(dev, NI_E_AI_FIFO_DATA_REG); + } + comedi_buf_write_samples(s, devpriv->ai_fifo_buffer, n); + } +} + +static void ni_handle_fifo_half_full(struct comedi_device *dev) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct comedi_subdevice *s = dev->read_subdev; + int n; + + n = board->ai_fifo_depth / 2; + + ni_ai_fifo_read(dev, s, n); +} +#endif + +/* Empties the AI fifo */ +static void ni_handle_fifo_dregs(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int dl; + unsigned short data; + int i; + + if (devpriv->is_611x) { + while ((ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E) == 0) { + dl = ni_readl(dev, NI611X_AI_FIFO_DATA_REG); + + /* This may get the hi/lo data in the wrong order */ + data = dl >> 16; + comedi_buf_write_samples(s, &data, 1); + data = dl & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } + } else if (devpriv->is_6143) { + i = 0; + while (ni_readl(dev, NI6143_AI_FIFO_STATUS_REG) & 0x04) { + dl = ni_readl(dev, NI6143_AI_FIFO_DATA_REG); + + /* This may get the hi/lo data in the wrong order */ + data = dl >> 16; + comedi_buf_write_samples(s, &data, 1); + data = dl & 0xffff; + comedi_buf_write_samples(s, &data, 1); + i += 2; + } + /* Check if stranded sample is present */ + if (ni_readl(dev, NI6143_AI_FIFO_STATUS_REG) & 0x01) { + /* Get stranded sample into FIFO */ + ni_writel(dev, 0x01, NI6143_AI_FIFO_CTRL_REG); + dl = ni_readl(dev, NI6143_AI_FIFO_DATA_REG); + data = (dl >> 16) & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } + + } else { + unsigned short fe; /* fifo empty */ + + fe = ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E; + while (fe == 0) { + for (i = 0; + i < ARRAY_SIZE(devpriv->ai_fifo_buffer); i++) { + fe = ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E; + if (fe) + break; + devpriv->ai_fifo_buffer[i] = + ni_readw(dev, NI_E_AI_FIFO_DATA_REG); + } + comedi_buf_write_samples(s, devpriv->ai_fifo_buffer, i); + } + } +} + +static void get_last_sample_611x(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned short data; + unsigned int dl; + + if (!devpriv->is_611x) + return; + + /* Check if there's a single sample stuck in the FIFO */ + if (ni_readb(dev, NI_E_STATUS_REG) & 0x80) { + dl = ni_readl(dev, NI611X_AI_FIFO_DATA_REG); + data = dl & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } +} + +static void get_last_sample_6143(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + unsigned short data; + unsigned int dl; + + if (!devpriv->is_6143) + return; + + /* Check if there's a single sample stuck in the FIFO */ + if (ni_readl(dev, NI6143_AI_FIFO_STATUS_REG) & 0x01) { + /* Get stranded sample into FIFO */ + ni_writel(dev, 0x01, NI6143_AI_FIFO_CTRL_REG); + dl = ni_readl(dev, NI6143_AI_FIFO_DATA_REG); + + /* This may get the hi/lo data in the wrong order */ + data = (dl >> 16) & 0xffff; + comedi_buf_write_samples(s, &data, 1); + } +} + +static void shutdown_ai_command(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->read_subdev; + +#ifdef PCIDMA + ni_ai_drain_dma(dev); +#endif + ni_handle_fifo_dregs(dev); + get_last_sample_611x(dev); + get_last_sample_6143(dev); + + s->async->events |= COMEDI_CB_EOA; +} + +static void ni_handle_eos(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + + if (devpriv->aimode == AIMODE_SCAN) { +#ifdef PCIDMA + static const int timeout = 10; + int i; + + for (i = 0; i < timeout; i++) { + ni_sync_ai_dma(dev); + if ((s->async->events & COMEDI_CB_EOS)) + break; + udelay(1); + } +#else + ni_handle_fifo_dregs(dev); + s->async->events |= COMEDI_CB_EOS; +#endif + } + /* handle special case of single scan */ + if (devpriv->ai_cmd2 & NISTC_AI_CMD2_END_ON_EOS) + shutdown_ai_command(dev); +} + +static void handle_gpct_interrupt(struct comedi_device *dev, + unsigned short counter_index) +{ +#ifdef PCIDMA + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s; + + s = &dev->subdevices[NI_GPCT_SUBDEV(counter_index)]; + + ni_tio_handle_interrupt(&devpriv->counter_dev->counters[counter_index], + s); + comedi_handle_events(dev, s); +#endif +} + +static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status) +{ + unsigned short ack = 0; + + if (a_status & NISTC_AI_STATUS1_SC_TC) + ack |= NISTC_INTA_ACK_AI_SC_TC; + if (a_status & NISTC_AI_STATUS1_START1) + ack |= NISTC_INTA_ACK_AI_START1; + if (a_status & NISTC_AI_STATUS1_START) + ack |= NISTC_INTA_ACK_AI_START; + if (a_status & NISTC_AI_STATUS1_STOP) + ack |= NISTC_INTA_ACK_AI_STOP; + if (a_status & NISTC_AI_STATUS1_OVER) + ack |= NISTC_INTA_ACK_AI_ERR; + if (ack) + ni_stc_writew(dev, ack, NISTC_INTA_ACK_REG); +} + +static void handle_a_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short status) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + /* test for all uncommon interrupt events at the same time */ + if (status & (NISTC_AI_STATUS1_ERR | + NISTC_AI_STATUS1_SC_TC | NISTC_AI_STATUS1_START1)) { + if (status == 0xffff) { + dev_err(dev->class_dev, "Card removed?\n"); + /* + * We probably aren't even running a command now, + * so it's a good idea to be careful. + */ + if (comedi_is_subdevice_running(s)) + s->async->events |= COMEDI_CB_ERROR; + return; + } + if (status & NISTC_AI_STATUS1_ERR) { + dev_err(dev->class_dev, "ai error a_status=%04x\n", + status); + + shutdown_ai_command(dev); + + s->async->events |= COMEDI_CB_ERROR; + if (status & NISTC_AI_STATUS1_OVER) + s->async->events |= COMEDI_CB_OVERFLOW; + return; + } + if (status & NISTC_AI_STATUS1_SC_TC) { + if (cmd->stop_src == TRIG_COUNT) + shutdown_ai_command(dev); + } + } +#ifndef PCIDMA + if (status & NISTC_AI_STATUS1_FIFO_HF) { + int i; + static const int timeout = 10; + /* + * PCMCIA cards (at least 6036) seem to stop producing + * interrupts if we fail to get the fifo less than half + * full, so loop to be sure. + */ + for (i = 0; i < timeout; ++i) { + ni_handle_fifo_half_full(dev); + if ((ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_HF) == 0) + break; + } + } +#endif /* !PCIDMA */ + + if (status & NISTC_AI_STATUS1_STOP) + ni_handle_eos(dev, s); +} + +static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status) +{ + unsigned short ack = 0; + + if (b_status & NISTC_AO_STATUS1_BC_TC) + ack |= NISTC_INTB_ACK_AO_BC_TC; + if (b_status & NISTC_AO_STATUS1_OVERRUN) + ack |= NISTC_INTB_ACK_AO_ERR; + if (b_status & NISTC_AO_STATUS1_START) + ack |= NISTC_INTB_ACK_AO_START; + if (b_status & NISTC_AO_STATUS1_START1) + ack |= NISTC_INTB_ACK_AO_START1; + if (b_status & NISTC_AO_STATUS1_UC_TC) + ack |= NISTC_INTB_ACK_AO_UC_TC; + if (b_status & NISTC_AO_STATUS1_UI2_TC) + ack |= NISTC_INTB_ACK_AO_UI2_TC; + if (b_status & NISTC_AO_STATUS1_UPDATE) + ack |= NISTC_INTB_ACK_AO_UPDATE; + if (ack) + ni_stc_writew(dev, ack, NISTC_INTB_ACK_REG); +} + +static void handle_b_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short b_status) +{ + if (b_status == 0xffff) + return; + if (b_status & NISTC_AO_STATUS1_OVERRUN) { + dev_err(dev->class_dev, + "AO FIFO underrun status=0x%04x status2=0x%04x\n", + b_status, ni_stc_readw(dev, NISTC_AO_STATUS2_REG)); + s->async->events |= COMEDI_CB_OVERFLOW; + } + + if (s->async->cmd.stop_src != TRIG_NONE && + b_status & NISTC_AO_STATUS1_BC_TC) + s->async->events |= COMEDI_CB_EOA; + +#ifndef PCIDMA + if (b_status & NISTC_AO_STATUS1_FIFO_REQ) { + int ret; + + ret = ni_ao_fifo_half_empty(dev, s); + if (!ret) { + dev_err(dev->class_dev, "AO buffer underrun\n"); + ni_set_bits(dev, NISTC_INTB_ENA_REG, + NISTC_INTB_ENA_AO_FIFO | + NISTC_INTB_ENA_AO_ERR, 0); + s->async->events |= COMEDI_CB_OVERFLOW; + } + } +#endif +} + +static void ni_ai_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int chan_index) +{ + struct ni_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int nsamples = comedi_bytes_to_samples(s, num_bytes); + unsigned short *array = data; + unsigned int *larray = data; + unsigned int i; +#ifdef PCIDMA + __le16 *barray = data; + __le32 *blarray = data; +#endif + + for (i = 0; i < nsamples; i++) { +#ifdef PCIDMA + if (s->subdev_flags & SDF_LSAMPL) + larray[i] = le32_to_cpu(blarray[i]); + else + array[i] = le16_to_cpu(barray[i]); +#endif + if (s->subdev_flags & SDF_LSAMPL) + larray[i] += devpriv->ai_offset[chan_index]; + else + array[i] += devpriv->ai_offset[chan_index]; + chan_index++; + chan_index %= cmd->chanlist_len; + } +} + +#ifdef PCIDMA + +static int ni_ai_setup_MITE_dma(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + int retval; + unsigned long flags; + + retval = ni_request_ai_mite_channel(dev); + if (retval) + return retval; + + /* write alloc the entire buffer */ + comedi_buf_write_alloc(s, s->async->prealloc_bufsz); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (!devpriv->ai_mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return -EIO; + } + + if (devpriv->is_611x || devpriv->is_6143) + mite_prep_dma(devpriv->ai_mite_chan, 32, 16); + else if (devpriv->is_628x) + mite_prep_dma(devpriv->ai_mite_chan, 32, 32); + else + mite_prep_dma(devpriv->ai_mite_chan, 16, 16); + + /*start the MITE */ + mite_dma_arm(devpriv->ai_mite_chan); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + return 0; +} + +static int ni_ao_setup_MITE_dma(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + int retval; + unsigned long flags; + + retval = ni_request_ao_mite_channel(dev); + if (retval) + return retval; + + /* read alloc the entire buffer */ + comedi_buf_read_alloc(s, s->async->prealloc_bufsz); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ao_mite_chan) { + if (devpriv->is_611x || devpriv->is_6713) { + mite_prep_dma(devpriv->ao_mite_chan, 32, 32); + } else { + /* + * Doing 32 instead of 16 bit wide transfers from + * memory makes the mite do 32 bit pci transfers, + * doubling pci bandwidth. + */ + mite_prep_dma(devpriv->ao_mite_chan, 16, 32); + } + mite_dma_arm(devpriv->ao_mite_chan); + } else { + retval = -EIO; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + return retval; +} + +#endif /* PCIDMA */ + +/* + * used for both cancel ioctl and board initialization + * + * this is pretty harsh for a cancel, but it works... + */ +static int ni_ai_reset(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + unsigned int ai_personal; + unsigned int ai_out_ctrl; + + ni_release_ai_mite_channel(dev); + /* ai configuration */ + ni_stc_writew(dev, NISTC_RESET_AI_CFG_START | NISTC_RESET_AI, + NISTC_RESET_REG); + + ni_set_bits(dev, NISTC_INTA_ENA_REG, NISTC_INTA_ENA_AI_MASK, 0); + + ni_clear_ai_fifo(dev); + + if (!devpriv->is_6143) + ni_writeb(dev, NI_E_MISC_CMD_EXT_ATRIG, NI_E_MISC_CMD_REG); + + ni_stc_writew(dev, NISTC_AI_CMD1_DISARM, NISTC_AI_CMD1_REG); + ni_stc_writew(dev, NISTC_AI_MODE1_START_STOP | + NISTC_AI_MODE1_RSVD + /*| NISTC_AI_MODE1_TRIGGER_ONCE */, + NISTC_AI_MODE1_REG); + ni_stc_writew(dev, 0, NISTC_AI_MODE2_REG); + /* generate FIFO interrupts on non-empty */ + ni_stc_writew(dev, NISTC_AI_MODE3_FIFO_MODE_NE, + NISTC_AI_MODE3_REG); + + ai_personal = NISTC_AI_PERSONAL_SHIFTIN_PW | + NISTC_AI_PERSONAL_SOC_POLARITY | + NISTC_AI_PERSONAL_LOCALMUX_CLK_PW; + ai_out_ctrl = NISTC_AI_OUT_CTRL_SCAN_IN_PROG_SEL(3) | + NISTC_AI_OUT_CTRL_EXTMUX_CLK_SEL(0) | + NISTC_AI_OUT_CTRL_LOCALMUX_CLK_SEL(2) | + NISTC_AI_OUT_CTRL_SC_TC_SEL(3); + if (devpriv->is_611x) { + ai_out_ctrl |= NISTC_AI_OUT_CTRL_CONVERT_HIGH; + } else if (devpriv->is_6143) { + ai_out_ctrl |= NISTC_AI_OUT_CTRL_CONVERT_LOW; + } else { + ai_personal |= NISTC_AI_PERSONAL_CONVERT_PW; + if (devpriv->is_622x) + ai_out_ctrl |= NISTC_AI_OUT_CTRL_CONVERT_HIGH; + else + ai_out_ctrl |= NISTC_AI_OUT_CTRL_CONVERT_LOW; + } + ni_stc_writew(dev, ai_personal, NISTC_AI_PERSONAL_REG); + ni_stc_writew(dev, ai_out_ctrl, NISTC_AI_OUT_CTRL_REG); + + /* 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: + * NISTC_AI_MODE1_REG + * NISTC_AI_MODE3_REG + * NISTC_AI_PERSONAL_REG + * NISTC_AI_OUT_CTRL_REG + */ + + /* clear interrupts */ + ni_stc_writew(dev, NISTC_INTA_ACK_AI_ALL, NISTC_INTA_ACK_REG); + + ni_stc_writew(dev, NISTC_RESET_AI_CFG_END, NISTC_RESET_REG); + + return 0; +} + +static int ni_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + int count; + + /* lock to avoid race with interrupt handler */ + spin_lock_irqsave(&dev->spinlock, flags); +#ifndef PCIDMA + ni_handle_fifo_dregs(dev); +#else + ni_sync_ai_dma(dev); +#endif + count = comedi_buf_n_bytes_ready(s); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return count; +} + +static void ni_prime_channelgain_list(struct comedi_device *dev) +{ + int i; + + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, NISTC_AI_CMD1_REG); + for (i = 0; i < NI_TIMEOUT; ++i) { + if (!(ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E)) { + ni_stc_writew(dev, 1, NISTC_ADC_FIFO_CLR_REG); + return; + } + udelay(1); + } + dev_err(dev->class_dev, "timeout loading channel/gain list\n"); +} + +static void ni_m_series_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, + unsigned int *list) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + unsigned int chan, range, aref; + unsigned int i; + unsigned int dither; + unsigned int range_code; + + ni_stc_writew(dev, 1, NISTC_CFG_MEM_CLR_REG); + + if ((list[0] & CR_ALT_SOURCE)) { + unsigned int 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 = NI_M_CFG_BYPASS_FIFO | + NI_M_CFG_BYPASS_AI_CHAN(chan) | + NI_M_CFG_BYPASS_AI_GAIN(range_code) | + devpriv->ai_calib_source; + if (dither) + bypass_bits |= NI_M_CFG_BYPASS_AI_DITHER; + /* don't use 2's complement encoding */ + bypass_bits |= NI_M_CFG_BYPASS_AI_POLARITY; + ni_writel(dev, bypass_bits, NI_M_CFG_BYPASS_FIFO_REG); + } else { + ni_writel(dev, 0, NI_M_CFG_BYPASS_FIFO_REG); + } + for (i = 0; i < n_chan; i++) { + unsigned int config_bits = 0; + + chan = CR_CHAN(list[i]); + aref = CR_AREF(list[i]); + range = CR_RANGE(list[i]); + dither = (list[i] & CR_ALT_FILTER) != 0; + + range_code = ni_gainlkup[board->gainlkup][range]; + devpriv->ai_offset[i] = 0; + switch (aref) { + case AREF_DIFF: + config_bits |= NI_M_AI_CFG_CHAN_TYPE_DIFF; + break; + case AREF_COMMON: + config_bits |= NI_M_AI_CFG_CHAN_TYPE_COMMON; + break; + case AREF_GROUND: + config_bits |= NI_M_AI_CFG_CHAN_TYPE_GROUND; + break; + case AREF_OTHER: + break; + } + config_bits |= NI_M_AI_CFG_CHAN_SEL(chan); + config_bits |= NI_M_AI_CFG_BANK_SEL(chan); + config_bits |= NI_M_AI_CFG_GAIN(range_code); + if (i == n_chan - 1) + config_bits |= NI_M_AI_CFG_LAST_CHAN; + if (dither) + config_bits |= NI_M_AI_CFG_DITHER; + /* don't use 2's complement encoding */ + config_bits |= NI_M_AI_CFG_POLARITY; + ni_writew(dev, config_bits, NI_M_AI_CFG_FIFO_DATA_REG); + } + ni_prime_channelgain_list(dev); +} + +/* + * Notes on the 6110 and 6111: + * These boards a slightly different than the rest of the series, since + * they have multiple A/D converters. + * From the driver side, the configuration memory is a + * little different. + * Configuration Memory Low: + * bits 15-9: same + * bit 8: unipolar/bipolar (should be 0 for bipolar) + * bits 0-3: gain. This is 4 bits instead of 3 for the other boards + * 1001 gain=0.1 (+/- 50) + * 1010 0.2 + * 1011 0.1 + * 0001 1 + * 0010 2 + * 0011 5 + * 0100 10 + * 0101 20 + * 0110 50 + * Configuration Memory High: + * bits 12-14: Channel Type + * 001 for differential + * 000 for calibration + * bit 11: coupling (this is not currently handled) + * 1 AC coupling + * 0 DC coupling + * bits 0-2: channel + * valid channels are 0-3 + */ +static void ni_load_channelgain_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int n_chan, unsigned int *list) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + unsigned int offset = (s->maxdata + 1) >> 1; + unsigned int chan, range, aref; + unsigned int i; + unsigned int hi, lo; + unsigned int dither; + + if (devpriv->is_m_series) { + ni_m_series_load_channelgain_list(dev, n_chan, list); + return; + } + if (n_chan == 1 && !devpriv->is_611x && !devpriv->is_6143) { + if (devpriv->changain_state && + devpriv->changain_spec == list[0]) { + /* ready to go. */ + return; + } + devpriv->changain_state = 1; + devpriv->changain_spec = list[0]; + } else { + devpriv->changain_state = 0; + } + + ni_stc_writew(dev, 1, NISTC_CFG_MEM_CLR_REG); + + /* Set up Calibration mode if required */ + if (devpriv->is_6143) { + if ((list[0] & CR_ALT_SOURCE) && + !devpriv->ai_calib_source_enabled) { + /* Strobe Relay enable bit */ + ni_writew(dev, devpriv->ai_calib_source | + NI6143_CALIB_CHAN_RELAY_ON, + NI6143_CALIB_CHAN_REG); + ni_writew(dev, devpriv->ai_calib_source, + NI6143_CALIB_CHAN_REG); + devpriv->ai_calib_source_enabled = 1; + /* Allow relays to change */ + msleep_interruptible(100); + } else if (!(list[0] & CR_ALT_SOURCE) && + devpriv->ai_calib_source_enabled) { + /* Strobe Relay disable bit */ + ni_writew(dev, devpriv->ai_calib_source | + NI6143_CALIB_CHAN_RELAY_OFF, + NI6143_CALIB_CHAN_REG); + ni_writew(dev, devpriv->ai_calib_source, + NI6143_CALIB_CHAN_REG); + devpriv->ai_calib_source_enabled = 0; + /* Allow relays to change */ + msleep_interruptible(100); + } + } + + for (i = 0; i < n_chan; i++) { + if (!devpriv->is_6143 && (list[i] & CR_ALT_SOURCE)) + chan = devpriv->ai_calib_source; + else + chan = CR_CHAN(list[i]); + aref = CR_AREF(list[i]); + range = CR_RANGE(list[i]); + dither = (list[i] & CR_ALT_FILTER) != 0; + + /* fix the external/internal range differences */ + range = ni_gainlkup[board->gainlkup][range]; + if (devpriv->is_611x) + devpriv->ai_offset[i] = offset; + else + devpriv->ai_offset[i] = (range & 0x100) ? 0 : offset; + + hi = 0; + if ((list[i] & CR_ALT_SOURCE)) { + if (devpriv->is_611x) + ni_writew(dev, CR_CHAN(list[i]) & 0x0003, + NI611X_CALIB_CHAN_SEL_REG); + } else { + if (devpriv->is_611x) + aref = AREF_DIFF; + else if (devpriv->is_6143) + aref = AREF_OTHER; + switch (aref) { + case AREF_DIFF: + hi |= NI_E_AI_CFG_HI_TYPE_DIFF; + break; + case AREF_COMMON: + hi |= NI_E_AI_CFG_HI_TYPE_COMMON; + break; + case AREF_GROUND: + hi |= NI_E_AI_CFG_HI_TYPE_GROUND; + break; + case AREF_OTHER: + break; + } + } + hi |= NI_E_AI_CFG_HI_CHAN(chan); + + ni_writew(dev, hi, NI_E_AI_CFG_HI_REG); + + if (!devpriv->is_6143) { + lo = NI_E_AI_CFG_LO_GAIN(range); + + if (i == n_chan - 1) + lo |= NI_E_AI_CFG_LO_LAST_CHAN; + if (dither) + lo |= NI_E_AI_CFG_LO_DITHER; + + ni_writew(dev, lo, NI_E_AI_CFG_LO_REG); + } + } + + /* prime the channel/gain list */ + if (!devpriv->is_611x && !devpriv->is_6143) + ni_prime_channelgain_list(dev); +} + +static int ni_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int mask = s->maxdata; + int i, n; + unsigned int signbits; + unsigned int d; + + ni_load_channelgain_list(dev, s, 1, &insn->chanspec); + + ni_clear_ai_fifo(dev); + + signbits = devpriv->ai_offset[0]; + if (devpriv->is_611x) { + for (n = 0; n < num_adc_stages_611x; n++) { + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); + udelay(1); + } + for (n = 0; n < insn->n; n++) { + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); + /* The 611x has screwy 32-bit FIFOs. */ + d = 0; + for (i = 0; i < NI_TIMEOUT; i++) { + if (ni_readb(dev, NI_E_STATUS_REG) & 0x80) { + d = ni_readl(dev, + NI611X_AI_FIFO_DATA_REG); + d >>= 16; + d &= 0xffff; + break; + } + if (!(ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E)) { + d = ni_readl(dev, + NI611X_AI_FIFO_DATA_REG); + d &= 0xffff; + break; + } + } + if (i == NI_TIMEOUT) { + dev_err(dev->class_dev, "timeout\n"); + return -ETIME; + } + d += signbits; + data[n] = d & 0xffff; + } + } else if (devpriv->is_6143) { + for (n = 0; n < insn->n; n++) { + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); + + /* + * The 6143 has 32-bit FIFOs. You need to strobe a + * bit to move a single 16bit stranded sample into + * the FIFO. + */ + d = 0; + for (i = 0; i < NI_TIMEOUT; i++) { + if (ni_readl(dev, NI6143_AI_FIFO_STATUS_REG) & + 0x01) { + /* Get stranded sample into FIFO */ + ni_writel(dev, 0x01, + NI6143_AI_FIFO_CTRL_REG); + d = ni_readl(dev, + NI6143_AI_FIFO_DATA_REG); + break; + } + } + if (i == NI_TIMEOUT) { + dev_err(dev->class_dev, "timeout\n"); + return -ETIME; + } + data[n] = (((d >> 16) & 0xFFFF) + signbits) & 0xFFFF; + } + } else { + for (n = 0; n < insn->n; n++) { + ni_stc_writew(dev, NISTC_AI_CMD1_CONVERT_PULSE, + NISTC_AI_CMD1_REG); + for (i = 0; i < NI_TIMEOUT; i++) { + if (!(ni_stc_readw(dev, NISTC_AI_STATUS1_REG) & + NISTC_AI_STATUS1_FIFO_E)) + break; + } + if (i == NI_TIMEOUT) { + dev_err(dev->class_dev, "timeout\n"); + return -ETIME; + } + if (devpriv->is_m_series) { + d = ni_readl(dev, NI_M_AI_FIFO_DATA_REG); + d &= mask; + data[n] = d; + } else { + d = ni_readw(dev, NI_E_AI_FIFO_DATA_REG); + d += signbits; + data[n] = d & 0xffff; + } + } + } + return insn->n; +} + +static int ni_ns_to_timer(const struct comedi_device *dev, + unsigned int nanosec, unsigned int flags) +{ + struct ni_private *devpriv = dev->private; + int divider; + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + divider = DIV_ROUND_CLOSEST(nanosec, devpriv->clock_ns); + break; + case CMDF_ROUND_DOWN: + divider = (nanosec) / devpriv->clock_ns; + break; + case CMDF_ROUND_UP: + divider = DIV_ROUND_UP(nanosec, devpriv->clock_ns); + break; + } + return divider - 1; +} + +static unsigned int ni_timer_to_ns(const struct comedi_device *dev, int timer) +{ + struct ni_private *devpriv = dev->private; + + return devpriv->clock_ns * (timer + 1); +} + +static void ni_cmd_set_mite_transfer(struct mite_ring *ring, + struct comedi_subdevice *sdev, + const struct comedi_cmd *cmd, + unsigned int max_count) +{ +#ifdef PCIDMA + unsigned int nbytes = max_count; + + if (cmd->stop_arg > 0 && cmd->stop_arg < max_count) + nbytes = cmd->stop_arg; + nbytes *= comedi_bytes_per_scan(sdev); + + if (nbytes > sdev->async->prealloc_bufsz) { + if (cmd->stop_arg > 0) + dev_err(sdev->device->class_dev, + "%s: tried exact data transfer limits greater than buffer size\n", + __func__); + + /* + * we can only transfer up to the size of the buffer. In this + * case, the user is expected to continue to write into the + * comedi buffer (already implemented as a ring buffer). + */ + nbytes = sdev->async->prealloc_bufsz; + } + + mite_init_ring_descriptors(ring, sdev, nbytes); +#else + dev_err(sdev->device->class_dev, + "%s: exact data transfer limits not implemented yet without DMA\n", + __func__); +#endif +} + +static unsigned int ni_min_ai_scan_period_ns(struct comedi_device *dev, + unsigned int num_channels) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + + /* simultaneously-sampled inputs */ + if (devpriv->is_611x || devpriv->is_6143) + return board->ai_speed; + + /* multiplexed inputs */ + return board->ai_speed * num_channels; +} + +static int ni_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + int err = 0; + unsigned int sources; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_INT | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + + sources = TRIG_TIMER | TRIG_EXT; + if (devpriv->is_611x || devpriv->is_6143) + sources |= TRIG_NOW; + err |= comedi_check_trigger_src(&cmd->convert_src, sources); + + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + case TRIG_INT: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + err |= ni_check_trigger_arg_roffs(CR_CHAN(cmd->start_arg), + NI_AI_StartTrigger, + &devpriv->routing_tables, 1); + break; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + ni_min_ai_scan_period_ns(dev, cmd->chanlist_len)); + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + devpriv->clock_ns * + 0xffffff); + } else if (cmd->scan_begin_src == TRIG_EXT) { + /* external trigger */ + err |= ni_check_trigger_arg_roffs(CR_CHAN(cmd->scan_begin_arg), + NI_AI_SampleClock, + &devpriv->routing_tables, 1); + } else { /* TRIG_OTHER */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } + + if (cmd->convert_src == TRIG_TIMER) { + if (devpriv->is_611x || devpriv->is_6143) { + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, + 0); + } else { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_speed); + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, + devpriv->clock_ns * + 0xffff); + } + } else if (cmd->convert_src == TRIG_EXT) { + /* external trigger */ + err |= ni_check_trigger_arg_roffs(CR_CHAN(cmd->convert_arg), + NI_AI_ConvertClock, + &devpriv->routing_tables, 1); + } else if (cmd->convert_src == TRIG_NOW) { + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) { + unsigned int max_count = 0x01000000; + + if (devpriv->is_611x) + max_count -= num_adc_stages_611x; + err |= comedi_check_trigger_arg_max(&cmd->stop_arg, max_count); + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + } else { + /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int tmp = cmd->scan_begin_arg; + + cmd->scan_begin_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->scan_begin_arg, + cmd->flags)); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if (!devpriv->is_611x && !devpriv->is_6143) { + unsigned int tmp = cmd->convert_arg; + + cmd->convert_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->convert_arg, + cmd->flags)); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + } + + if (err) + return 4; + + return 0; +} + +static int ni_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct ni_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + ni_stc_writew(dev, NISTC_AI_CMD2_START1_PULSE | devpriv->ai_cmd2, + NISTC_AI_CMD2_REG); + s->async->inttrig = NULL; + + return 1; +} + +static int ni_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + const struct comedi_cmd *cmd = &s->async->cmd; + int timer; + int mode1 = 0; /* mode1 is needed for both stop and convert */ + int mode2 = 0; + int start_stop_select = 0; + unsigned int stop_count; + int interrupt_a_enable = 0; + unsigned int ai_trig; + + if (dev->irq == 0) { + dev_err(dev->class_dev, "cannot run command without an irq\n"); + return -EIO; + } + ni_clear_ai_fifo(dev); + + ni_load_channelgain_list(dev, s, cmd->chanlist_len, cmd->chanlist); + + /* start configuration */ + ni_stc_writew(dev, NISTC_RESET_AI_CFG_START, NISTC_RESET_REG); + + /* + * Disable analog triggering for now, since it interferes + * with the use of pfi0. + */ + devpriv->an_trig_etc_reg &= ~NISTC_ATRIG_ETC_ENA; + ni_stc_writew(dev, devpriv->an_trig_etc_reg, NISTC_ATRIG_ETC_REG); + + ai_trig = NISTC_AI_TRIG_START2_SEL(0) | NISTC_AI_TRIG_START1_SYNC; + switch (cmd->start_src) { + case TRIG_INT: + case TRIG_NOW: + ai_trig |= NISTC_AI_TRIG_START1_EDGE | + NISTC_AI_TRIG_START1_SEL(0); + break; + case TRIG_EXT: + ai_trig |= NISTC_AI_TRIG_START1_SEL( + ni_get_reg_value_roffs( + CR_CHAN(cmd->start_arg), + NI_AI_StartTrigger, + &devpriv->routing_tables, 1)); + + if (cmd->start_arg & CR_INVERT) + ai_trig |= NISTC_AI_TRIG_START1_POLARITY; + if (cmd->start_arg & CR_EDGE) + ai_trig |= NISTC_AI_TRIG_START1_EDGE; + break; + } + ni_stc_writew(dev, ai_trig, NISTC_AI_TRIG_SEL_REG); + + mode2 &= ~NISTC_AI_MODE2_PRE_TRIGGER; + mode2 &= ~NISTC_AI_MODE2_SC_INIT_LOAD_SRC; + mode2 &= ~NISTC_AI_MODE2_SC_RELOAD_MODE; + ni_stc_writew(dev, mode2, NISTC_AI_MODE2_REG); + + if (cmd->chanlist_len == 1 || devpriv->is_611x || devpriv->is_6143) { + /* logic low */ + start_stop_select |= NISTC_AI_STOP_POLARITY | + NISTC_AI_STOP_SEL(31) | + NISTC_AI_STOP_SYNC; + } else { + /* ai configuration memory */ + start_stop_select |= NISTC_AI_STOP_SEL(19); + } + ni_stc_writew(dev, start_stop_select, NISTC_AI_START_STOP_REG); + + devpriv->ai_cmd2 = 0; + switch (cmd->stop_src) { + case TRIG_COUNT: + stop_count = cmd->stop_arg - 1; + + if (devpriv->is_611x) { + /* have to take 3 stage adc pipeline into account */ + stop_count += num_adc_stages_611x; + } + /* stage number of scans */ + ni_stc_writel(dev, stop_count, NISTC_AI_SC_LOADA_REG); + + mode1 |= NISTC_AI_MODE1_START_STOP | + NISTC_AI_MODE1_RSVD | + NISTC_AI_MODE1_TRIGGER_ONCE; + ni_stc_writew(dev, mode1, NISTC_AI_MODE1_REG); + /* load SC (Scan Count) */ + ni_stc_writew(dev, NISTC_AI_CMD1_SC_LOAD, NISTC_AI_CMD1_REG); + + if (stop_count == 0) { + devpriv->ai_cmd2 |= NISTC_AI_CMD2_END_ON_EOS; + interrupt_a_enable |= NISTC_INTA_ENA_AI_STOP; + /* + * This is required to get the last sample for + * chanlist_len > 1, not sure why. + */ + if (cmd->chanlist_len > 1) + start_stop_select |= NISTC_AI_STOP_POLARITY | + NISTC_AI_STOP_EDGE; + } + break; + case TRIG_NONE: + /* stage number of scans */ + ni_stc_writel(dev, 0, NISTC_AI_SC_LOADA_REG); + + mode1 |= NISTC_AI_MODE1_START_STOP | + NISTC_AI_MODE1_RSVD | + NISTC_AI_MODE1_CONTINUOUS; + ni_stc_writew(dev, mode1, NISTC_AI_MODE1_REG); + + /* load SC (Scan Count) */ + ni_stc_writew(dev, NISTC_AI_CMD1_SC_LOAD, NISTC_AI_CMD1_REG); + break; + } + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + /* + * stop bits for non 611x boards + * NISTC_AI_MODE3_SI_TRIG_DELAY=0 + * NISTC_AI_MODE2_PRE_TRIGGER=0 + * NISTC_AI_START_STOP_REG: + * NISTC_AI_START_POLARITY=0 (?) rising edge + * NISTC_AI_START_EDGE=1 edge triggered + * NISTC_AI_START_SYNC=1 (?) + * NISTC_AI_START_SEL=0 SI_TC + * NISTC_AI_STOP_POLARITY=0 rising edge + * NISTC_AI_STOP_EDGE=0 level + * NISTC_AI_STOP_SYNC=1 + * NISTC_AI_STOP_SEL=19 external pin (configuration mem) + */ + start_stop_select |= NISTC_AI_START_EDGE | NISTC_AI_START_SYNC; + ni_stc_writew(dev, start_stop_select, NISTC_AI_START_STOP_REG); + + mode2 &= ~NISTC_AI_MODE2_SI_INIT_LOAD_SRC; /* A */ + mode2 |= NISTC_AI_MODE2_SI_RELOAD_MODE(0); + /* mode2 |= NISTC_AI_MODE2_SC_RELOAD_MODE; */ + ni_stc_writew(dev, mode2, NISTC_AI_MODE2_REG); + + /* load SI */ + timer = ni_ns_to_timer(dev, cmd->scan_begin_arg, + CMDF_ROUND_NEAREST); + ni_stc_writel(dev, timer, NISTC_AI_SI_LOADA_REG); + ni_stc_writew(dev, NISTC_AI_CMD1_SI_LOAD, NISTC_AI_CMD1_REG); + break; + case TRIG_EXT: + if (cmd->scan_begin_arg & CR_EDGE) + start_stop_select |= NISTC_AI_START_EDGE; + if (cmd->scan_begin_arg & CR_INVERT) /* falling edge */ + start_stop_select |= NISTC_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 |= NISTC_AI_START_SYNC; + + start_stop_select |= NISTC_AI_START_SEL( + ni_get_reg_value_roffs( + CR_CHAN(cmd->scan_begin_arg), + NI_AI_SampleClock, + &devpriv->routing_tables, 1)); + ni_stc_writew(dev, start_stop_select, NISTC_AI_START_STOP_REG); + break; + } + + switch (cmd->convert_src) { + case TRIG_TIMER: + case TRIG_NOW: + if (cmd->convert_arg == 0 || cmd->convert_src == TRIG_NOW) + timer = 1; + else + timer = ni_ns_to_timer(dev, cmd->convert_arg, + CMDF_ROUND_NEAREST); + /* 0,0 does not work */ + ni_stc_writew(dev, 1, NISTC_AI_SI2_LOADA_REG); + ni_stc_writew(dev, timer, NISTC_AI_SI2_LOADB_REG); + + mode2 &= ~NISTC_AI_MODE2_SI2_INIT_LOAD_SRC; /* A */ + mode2 |= NISTC_AI_MODE2_SI2_RELOAD_MODE; /* alternate */ + ni_stc_writew(dev, mode2, NISTC_AI_MODE2_REG); + + ni_stc_writew(dev, NISTC_AI_CMD1_SI2_LOAD, NISTC_AI_CMD1_REG); + + mode2 |= NISTC_AI_MODE2_SI2_INIT_LOAD_SRC; /* B */ + mode2 |= NISTC_AI_MODE2_SI2_RELOAD_MODE; /* alternate */ + ni_stc_writew(dev, mode2, NISTC_AI_MODE2_REG); + break; + case TRIG_EXT: + mode1 |= NISTC_AI_MODE1_CONVERT_SRC( + ni_get_reg_value_roffs( + CR_CHAN(cmd->convert_arg), + NI_AI_ConvertClock, + &devpriv->routing_tables, 1)); + if ((cmd->convert_arg & CR_INVERT) == 0) + mode1 |= NISTC_AI_MODE1_CONVERT_POLARITY; + ni_stc_writew(dev, mode1, NISTC_AI_MODE1_REG); + + mode2 |= NISTC_AI_MODE2_SC_GATE_ENA | + NISTC_AI_MODE2_START_STOP_GATE_ENA; + ni_stc_writew(dev, mode2, NISTC_AI_MODE2_REG); + + break; + } + + if (dev->irq) { + /* interrupt on FIFO, errors, SC_TC */ + interrupt_a_enable |= NISTC_INTA_ENA_AI_ERR | + NISTC_INTA_ENA_AI_SC_TC; + +#ifndef PCIDMA + interrupt_a_enable |= NISTC_INTA_ENA_AI_FIFO; +#endif + + if ((cmd->flags & CMDF_WAKE_EOS) || + (devpriv->ai_cmd2 & NISTC_AI_CMD2_END_ON_EOS)) { + /* wake on end-of-scan */ + devpriv->aimode = AIMODE_SCAN; + } else { + devpriv->aimode = AIMODE_HALF_FULL; + } + + switch (devpriv->aimode) { + case AIMODE_HALF_FULL: + /* FIFO interrupts and DMA requests on half-full */ +#ifdef PCIDMA + ni_stc_writew(dev, NISTC_AI_MODE3_FIFO_MODE_HF_E, + NISTC_AI_MODE3_REG); +#else + ni_stc_writew(dev, NISTC_AI_MODE3_FIFO_MODE_HF, + NISTC_AI_MODE3_REG); +#endif + break; + case AIMODE_SAMPLE: + /* generate FIFO interrupts on non-empty */ + ni_stc_writew(dev, NISTC_AI_MODE3_FIFO_MODE_NE, + NISTC_AI_MODE3_REG); + break; + case AIMODE_SCAN: +#ifdef PCIDMA + ni_stc_writew(dev, NISTC_AI_MODE3_FIFO_MODE_NE, + NISTC_AI_MODE3_REG); +#else + ni_stc_writew(dev, NISTC_AI_MODE3_FIFO_MODE_HF, + NISTC_AI_MODE3_REG); +#endif + interrupt_a_enable |= NISTC_INTA_ENA_AI_STOP; + break; + default: + break; + } + + /* clear interrupts */ + ni_stc_writew(dev, NISTC_INTA_ACK_AI_ALL, NISTC_INTA_ACK_REG); + + ni_set_bits(dev, NISTC_INTA_ENA_REG, interrupt_a_enable, 1); + } else { + /* interrupt on nothing */ + ni_set_bits(dev, NISTC_INTA_ENA_REG, ~0, 0); + + /* XXX start polling if necessary */ + } + + /* end configuration */ + ni_stc_writew(dev, NISTC_RESET_AI_CFG_END, NISTC_RESET_REG); + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + ni_stc_writew(dev, NISTC_AI_CMD1_SI2_ARM | + NISTC_AI_CMD1_SI_ARM | + NISTC_AI_CMD1_DIV_ARM | + NISTC_AI_CMD1_SC_ARM, + NISTC_AI_CMD1_REG); + break; + case TRIG_EXT: + ni_stc_writew(dev, NISTC_AI_CMD1_SI2_ARM | + NISTC_AI_CMD1_SI_ARM | /* XXX ? */ + NISTC_AI_CMD1_DIV_ARM | + NISTC_AI_CMD1_SC_ARM, + NISTC_AI_CMD1_REG); + break; + } + +#ifdef PCIDMA + { + int retval = ni_ai_setup_MITE_dma(dev); + + if (retval) + return retval; + } +#endif + + if (cmd->start_src == TRIG_NOW) { + ni_stc_writew(dev, NISTC_AI_CMD2_START1_PULSE | + devpriv->ai_cmd2, + NISTC_AI_CMD2_REG); + s->async->inttrig = NULL; + } else if (cmd->start_src == TRIG_EXT) { + s->async->inttrig = NULL; + } else { /* TRIG_INT */ + s->async->inttrig = ni_ai_inttrig; + } + + return 0; +} + +static int ni_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + + if (insn->n < 1) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_ALT_SOURCE: + if (devpriv->is_m_series) { + if (data[1] & ~NI_M_CFG_BYPASS_AI_CAL_MASK) + return -EINVAL; + devpriv->ai_calib_source = data[1]; + } else if (devpriv->is_6143) { + unsigned int calib_source; + + calib_source = data[1] & 0xf; + + devpriv->ai_calib_source = calib_source; + ni_writew(dev, calib_source, NI6143_CALIB_CHAN_REG); + } else { + unsigned int calib_source; + unsigned int calib_source_adjust; + + calib_source = data[1] & 0xf; + calib_source_adjust = (data[1] >> 4) & 0xff; + + if (calib_source >= 8) + return -EINVAL; + devpriv->ai_calib_source = calib_source; + if (devpriv->is_611x) { + ni_writeb(dev, calib_source_adjust, + NI611X_CAL_GAIN_SEL_REG); + } + } + return 2; + case INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS: + /* we don't care about actual channels */ + /* data[3] : chanlist_len */ + data[1] = ni_min_ai_scan_period_ns(dev, data[3]); + if (devpriv->is_611x || devpriv->is_6143) + data[2] = 0; /* simultaneous output */ + else + data[2] = board->ai_speed; + return 0; + default: + break; + } + + return -EINVAL; +} + +static void ni_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int chan_index) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int nsamples = comedi_bytes_to_samples(s, num_bytes); + unsigned short *array = data; + unsigned int i; +#ifdef PCIDMA + __le16 buf, *barray = data; +#endif + + for (i = 0; i < nsamples; i++) { + unsigned int range = CR_RANGE(cmd->chanlist[chan_index]); + unsigned short val = array[i]; + + /* + * Munge data from unsigned to two's complement for + * bipolar ranges. + */ + if (comedi_range_is_bipolar(s, range)) + val = comedi_offset_munge(s, val); +#ifdef PCIDMA + buf = cpu_to_le16(val); + barray[i] = buf; +#else + array[i] = val; +#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) +{ + struct ni_private *devpriv = dev->private; + unsigned int range; + unsigned int chan; + unsigned int conf; + int i; + int invert = 0; + + if (timed) { + for (i = 0; i < s->n_chan; ++i) { + devpriv->ao_conf[i] &= ~NI_M_AO_CFG_BANK_UPDATE_TIMED; + ni_writeb(dev, devpriv->ao_conf[i], + NI_M_AO_CFG_BANK_REG(i)); + ni_writeb(dev, 0xf, NI_M_AO_WAVEFORM_ORDER_REG(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 |= NI_M_AO_CFG_BANK_REF_INT_10V; + ni_writeb(dev, 0, NI_M_AO_REF_ATTENUATION_REG(chan)); + break; + case 10000000: + conf |= NI_M_AO_CFG_BANK_REF_INT_5V; + ni_writeb(dev, 0, NI_M_AO_REF_ATTENUATION_REG(chan)); + break; + case 4000000: + conf |= NI_M_AO_CFG_BANK_REF_INT_10V; + ni_writeb(dev, NI_M_AO_REF_ATTENUATION_X5, + NI_M_AO_REF_ATTENUATION_REG(chan)); + break; + case 2000000: + conf |= NI_M_AO_CFG_BANK_REF_INT_5V; + ni_writeb(dev, NI_M_AO_REF_ATTENUATION_X5, + NI_M_AO_REF_ATTENUATION_REG(chan)); + break; + default: + dev_err(dev->class_dev, + "bug! unhandled ao reference voltage\n"); + break; + } + switch (krange->max + krange->min) { + case 0: + conf |= NI_M_AO_CFG_BANK_OFFSET_0V; + break; + case 10000000: + conf |= NI_M_AO_CFG_BANK_OFFSET_5V; + break; + default: + dev_err(dev->class_dev, + "bug! unhandled ao offset voltage\n"); + break; + } + if (timed) + conf |= NI_M_AO_CFG_BANK_UPDATE_TIMED; + ni_writeb(dev, conf, NI_M_AO_CFG_BANK_REG(chan)); + devpriv->ao_conf[chan] = conf; + ni_writeb(dev, i, NI_M_AO_WAVEFORM_ORDER_REG(chan)); + } + return invert; +} + +static int ni_old_ao_config_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec[], + unsigned int n_chans) +{ + struct ni_private *devpriv = dev->private; + unsigned int range; + unsigned int chan; + unsigned int conf; + int i; + int invert = 0; + + for (i = 0; i < n_chans; i++) { + chan = CR_CHAN(chanspec[i]); + range = CR_RANGE(chanspec[i]); + conf = NI_E_AO_DACSEL(chan); + + if (comedi_range_is_bipolar(s, range)) { + conf |= NI_E_AO_CFG_BIP; + invert = (s->maxdata + 1) >> 1; + } else { + invert = 0; + } + if (comedi_range_is_external(s, range)) + conf |= NI_E_AO_EXT_REF; + + /* not all boards can deglitch, but this shouldn't hurt */ + if (chanspec[i] & CR_DEGLITCH) + conf |= NI_E_AO_DEGLITCH; + + /* analog reference */ + /* AREF_OTHER connects AO ground to AI ground, i think */ + if (CR_AREF(chanspec[i]) == AREF_OTHER) + conf |= NI_E_AO_GROUND_REF; + + ni_writew(dev, conf, NI_E_AO_CFG_REG); + devpriv->ao_conf[chan] = conf; + } + return invert; +} + +static int ni_ao_config_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec[], unsigned int n_chans, + int timed) +{ + struct ni_private *devpriv = dev->private; + + if (devpriv->is_m_series) + return ni_m_series_ao_config_chanlist(dev, s, chanspec, n_chans, + timed); + else + return ni_old_ao_config_chanlist(dev, s, chanspec, n_chans); +} + +static int ni_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int reg; + int i; + + if (devpriv->is_6xxx) { + ni_ao_win_outw(dev, 1 << chan, NI671X_AO_IMMEDIATE_REG); + + reg = NI671X_DAC_DIRECT_DATA_REG(chan); + } else if (devpriv->is_m_series) { + reg = NI_M_DAC_DIRECT_DATA_REG(chan); + } else { + reg = NI_E_DAC_DIRECT_DATA_REG(chan); + } + + ni_ao_config_chanlist(dev, s, &insn->chanspec, 1, 0); + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + if (devpriv->is_6xxx) { + /* + * 6xxx boards have bipolar outputs, munge the + * unsigned comedi values to 2's complement + */ + val = comedi_offset_munge(s, val); + + ni_ao_win_outw(dev, val, reg); + } else if (devpriv->is_m_series) { + /* + * M-series boards use offset binary values for + * bipolar and uinpolar outputs + */ + ni_writew(dev, val, reg); + } else { + /* + * Non-M series boards need two's complement values + * for bipolar ranges. + */ + if (comedi_range_is_bipolar(s, range)) + val = comedi_offset_munge(s, val); + + ni_writew(dev, val, reg); + } + } + + return insn->n; +} + +/* + * Arms the AO device in preparation for a trigger event. + * This function also allocates and prepares a DMA channel (or FIFO if DMA is + * not used). As a part of this preparation, this function preloads the DAC + * registers with the first values of the output stream. This ensures that the + * first clock cycle after the trigger can be used for output. + * + * Note that this function _must_ happen after a user has written data to the + * output buffers via either mmap or write(fileno,...). + */ +static int ni_ao_arm(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + int ret; + int interrupt_b_bits; + int i; + static const int timeout = 1000; + + /* + * Prevent ao from doing things like trying to allocate the ao dma + * channel multiple times. + */ + if (!devpriv->ao_needs_arming) { + dev_dbg(dev->class_dev, "%s: device does not need arming!\n", + __func__); + return -EINVAL; + } + + devpriv->ao_needs_arming = 0; + + ni_set_bits(dev, NISTC_INTB_ENA_REG, + NISTC_INTB_ENA_AO_FIFO | NISTC_INTB_ENA_AO_ERR, 0); + interrupt_b_bits = NISTC_INTB_ENA_AO_ERR; +#ifdef PCIDMA + ni_stc_writew(dev, 1, NISTC_DAC_FIFO_CLR_REG); + if (devpriv->is_6xxx) + ni_ao_win_outl(dev, 0x6, NI611X_AO_FIFO_OFFSET_LOAD_REG); + 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 |= NISTC_INTB_ENA_AO_FIFO; +#endif + + ni_stc_writew(dev, devpriv->ao_mode3 | NISTC_AO_MODE3_NOT_AN_UPDATE, + NISTC_AO_MODE3_REG); + ni_stc_writew(dev, devpriv->ao_mode3, NISTC_AO_MODE3_REG); + /* wait for DACs to be loaded */ + for (i = 0; i < timeout; i++) { + udelay(1); + if ((ni_stc_readw(dev, NISTC_STATUS2_REG) & + NISTC_STATUS2_AO_TMRDACWRS_IN_PROGRESS) == 0) + break; + } + if (i == timeout) { + dev_err(dev->class_dev, + "timed out waiting for AO_TMRDACWRs_In_Progress_St to clear\n"); + return -EIO; + } + /* + * stc manual says we are need to clear error interrupt after + * AO_TMRDACWRs_In_Progress_St clears + */ + ni_stc_writew(dev, NISTC_INTB_ACK_AO_ERR, NISTC_INTB_ACK_REG); + + ni_set_bits(dev, NISTC_INTB_ENA_REG, interrupt_b_bits, 1); + + ni_stc_writew(dev, NISTC_AO_CMD1_UI_ARM | + NISTC_AO_CMD1_UC_ARM | + NISTC_AO_CMD1_BC_ARM | + devpriv->ao_cmd1, + NISTC_AO_CMD1_REG); + + return 0; +} + +static int ni_ao_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + unsigned int nbytes; + + switch (data[0]) { + case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: + switch (data[1]) { + case COMEDI_OUTPUT: + nbytes = comedi_samples_to_bytes(s, + board->ao_fifo_depth); + data[2] = 1 + nbytes; + if (devpriv->mite) + data[2] += devpriv->mite->fifo_size; + break; + case COMEDI_INPUT: + data[2] = 0; + break; + default: + return -EINVAL; + } + return 0; + case INSN_CONFIG_ARM: + return ni_ao_arm(dev, s); + case INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS: + /* we don't care about actual channels */ + /* data[3] : chanlist_len */ + data[1] = board->ao_speed * data[3]; + data[2] = 0; + return 0; + default: + break; + } + + return -EINVAL; +} + +static int ni_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct ni_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int ret; + + /* + * Require trig_num == cmd->start_arg when cmd->start_src == TRIG_INT. + * For backwards compatibility, also allow trig_num == 0 when + * cmd->start_src != TRIG_INT (i.e. when cmd->start_src == TRIG_EXT); + * in that case, the internal trigger is being used as a pre-trigger + * before the external trigger. + */ + if (!(trig_num == cmd->start_arg || + (trig_num == 0 && cmd->start_src != TRIG_INT))) + return -EINVAL; + + /* + * Null trig at beginning prevent ao start trigger from executing more + * than once per command. + */ + s->async->inttrig = NULL; + + if (devpriv->ao_needs_arming) { + /* only arm this device if it still needs arming */ + ret = ni_ao_arm(dev, s); + if (ret) + return ret; + } + + ni_stc_writew(dev, NISTC_AO_CMD2_START1_PULSE | devpriv->ao_cmd2, + NISTC_AO_CMD2_REG); + + return 0; +} + +/* + * begin ni_ao_cmd. + * Organized similar to NI-STC and MHDDK examples. + * ni_ao_cmd is broken out into configuration sub-routines for clarity. + */ + +static void ni_ao_cmd_personalize(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + const struct ni_board_struct *board = dev->board_ptr; + unsigned int bits; + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + bits = + /* fast CPU interface--only eseries */ + /* ((slow CPU interface) ? 0 : AO_Fast_CPU) | */ + NISTC_AO_PERSONAL_BC_SRC_SEL | + 0 /* (use_original_pulse ? 0 : NISTC_AO_PERSONAL_UPDATE_TIMEBASE) */ | + /* + * FIXME: start setting following bit when appropriate. Need to + * determine whether board is E4 or E1. + * FROM MHHDK: + * if board is E4 or E1 + * Set bit "NISTC_AO_PERSONAL_UPDATE_PW" to 0 + * else + * set it to 1 + */ + NISTC_AO_PERSONAL_UPDATE_PW | + /* FIXME: when should we set following bit to zero? */ + NISTC_AO_PERSONAL_TMRDACWR_PW | + (board->ao_fifo_depth ? + NISTC_AO_PERSONAL_FIFO_ENA : NISTC_AO_PERSONAL_DMA_PIO_CTRL) + ; +#if 0 + /* + * FIXME: + * add something like ".has_individual_dacs = 0" to ni_board_struct + * since, as F Hess pointed out, not all in m series have singles. not + * sure if e-series all have duals... + */ + + /* + * F Hess: windows driver does not set NISTC_AO_PERSONAL_NUM_DAC bit for + * 6281, verified with bus analyzer. + */ + if (devpriv->is_m_series) + bits |= NISTC_AO_PERSONAL_NUM_DAC; +#endif + ni_stc_writew(dev, bits, NISTC_AO_PERSONAL_REG); + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); +} + +static void ni_ao_cmd_set_trigger(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct ni_private *devpriv = dev->private; + unsigned int trigsel; + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + /* sync */ + if (cmd->stop_src == TRIG_NONE) { + devpriv->ao_mode1 |= NISTC_AO_MODE1_CONTINUOUS; + devpriv->ao_mode1 &= ~NISTC_AO_MODE1_TRIGGER_ONCE; + } else { + devpriv->ao_mode1 &= ~NISTC_AO_MODE1_CONTINUOUS; + devpriv->ao_mode1 |= NISTC_AO_MODE1_TRIGGER_ONCE; + } + ni_stc_writew(dev, devpriv->ao_mode1, NISTC_AO_MODE1_REG); + + if (cmd->start_src == TRIG_INT) { + trigsel = NISTC_AO_TRIG_START1_EDGE | + NISTC_AO_TRIG_START1_SYNC; + } else { /* TRIG_EXT */ + trigsel = NISTC_AO_TRIG_START1_SEL( + ni_get_reg_value_roffs( + CR_CHAN(cmd->start_arg), + NI_AO_StartTrigger, + &devpriv->routing_tables, 1)); + + /* 0=active high, 1=active low. see daq-stc 3-24 (p186) */ + if (cmd->start_arg & CR_INVERT) + trigsel |= NISTC_AO_TRIG_START1_POLARITY; + /* 0=edge detection disabled, 1=enabled */ + if (cmd->start_arg & CR_EDGE) + trigsel |= NISTC_AO_TRIG_START1_EDGE; + } + ni_stc_writew(dev, trigsel, NISTC_AO_TRIG_SEL_REG); + + /* AO_Delayed_START1 = 0, we do not support delayed start...yet */ + + /* sync */ + /* select DA_START1 as PFI6/AO_START1 when configured as an output */ + devpriv->ao_mode3 &= ~NISTC_AO_MODE3_TRIG_LEN; + ni_stc_writew(dev, devpriv->ao_mode3, NISTC_AO_MODE3_REG); + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); +} + +static void ni_ao_cmd_set_counters(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct ni_private *devpriv = dev->private; + /* Not supporting 'waveform staging' or 'local buffer with pauses' */ + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + /* + * This relies on ao_mode1/(Trigger_Once | Continuous) being set in + * set_trigger above. It is unclear whether we really need to re-write + * this register with these values. The mhddk examples for e-series + * show writing this in both places, but the examples for m-series show + * a single write in the set_counters function (here). + */ + ni_stc_writew(dev, devpriv->ao_mode1, NISTC_AO_MODE1_REG); + + /* sync (upload number of buffer iterations -1) */ + /* indicate that we want to use BC_Load_A_Register as the source */ + devpriv->ao_mode2 &= ~NISTC_AO_MODE2_BC_INIT_LOAD_SRC; + ni_stc_writew(dev, devpriv->ao_mode2, NISTC_AO_MODE2_REG); + + /* + * if the BC_TC interrupt is still issued in spite of UC, BC, UI + * ignoring BC_TC, then we will need to find a way to ignore that + * interrupt in continuous mode. + */ + ni_stc_writel(dev, 0, NISTC_AO_BC_LOADA_REG); /* iter once */ + + /* sync (issue command to load number of buffer iterations -1) */ + ni_stc_writew(dev, NISTC_AO_CMD1_BC_LOAD, NISTC_AO_CMD1_REG); + + /* sync (upload number of updates in buffer) */ + /* indicate that we want to use UC_Load_A_Register as the source */ + devpriv->ao_mode2 &= ~NISTC_AO_MODE2_UC_INIT_LOAD_SRC; + ni_stc_writew(dev, devpriv->ao_mode2, NISTC_AO_MODE2_REG); + + /* + * if a user specifies '0', this automatically assumes the entire 24bit + * address space is available for the (multiple iterations of single + * buffer) MISB. Otherwise, stop_arg specifies the MISB length that + * will be used, regardless of whether we are in continuous mode or not. + * In continuous mode, the output will just iterate indefinitely over + * the MISB. + */ + { + unsigned int stop_arg = cmd->stop_arg > 0 ? + (cmd->stop_arg & 0xffffff) : 0xffffff; + + if (devpriv->is_m_series) { + /* + * this is how the NI example code does it for m-series + * boards, verified correct with 6259 + */ + ni_stc_writel(dev, stop_arg - 1, NISTC_AO_UC_LOADA_REG); + + /* sync (issue cmd to load number of updates in MISB) */ + ni_stc_writew(dev, NISTC_AO_CMD1_UC_LOAD, + NISTC_AO_CMD1_REG); + } else { + ni_stc_writel(dev, stop_arg, NISTC_AO_UC_LOADA_REG); + + /* sync (issue cmd to load number of updates in MISB) */ + ni_stc_writew(dev, NISTC_AO_CMD1_UC_LOAD, + NISTC_AO_CMD1_REG); + + /* + * sync (upload number of updates-1 in MISB) + * --eseries only? + */ + ni_stc_writel(dev, stop_arg - 1, NISTC_AO_UC_LOADA_REG); + } + } + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); +} + +static void ni_ao_cmd_set_update(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct ni_private *devpriv = dev->private; + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + /* + * zero out these bit fields to be set below. Does an ao-reset do this + * automatically? + */ + devpriv->ao_mode1 &= ~(NISTC_AO_MODE1_UI_SRC_MASK | + NISTC_AO_MODE1_UI_SRC_POLARITY | + NISTC_AO_MODE1_UPDATE_SRC_MASK | + NISTC_AO_MODE1_UPDATE_SRC_POLARITY); + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int trigvar; + + devpriv->ao_cmd2 &= ~NISTC_AO_CMD2_BC_GATE_ENA; + + /* + * NOTE: there are several other ways of configuring internal + * updates, but we'll only support one for now: using + * AO_IN_TIMEBASE, w/o waveform staging, w/o a delay between + * START1 and first update, and also w/o local buffer mode w/ + * pauses. + */ + + /* + * This is already done above: + * devpriv->ao_mode1 &= ~( + * // set UPDATE_Source to UI_TC: + * NISTC_AO_MODE1_UPDATE_SRC_MASK | + * // set UPDATE_Source_Polarity to rising (required?) + * NISTC_AO_MODE1_UPDATE_SRC_POLARITY | + * // set UI_Source to AO_IN_TIMEBASE1: + * NISTC_AO_MODE1_UI_SRC_MASK | + * // set UI_Source_Polarity to rising (required?) + * NISTC_AO_MODE1_UI_SRC_POLARITY + * ); + */ + + /* + * TODO: use ao_ui_clock_source to allow all possible signals + * to be routed to UI_Source_Select. See tSTC.h for + * eseries/ni67xx and tMSeries.h for mseries. + */ + + trigvar = ni_ns_to_timer(dev, cmd->scan_begin_arg, + CMDF_ROUND_NEAREST); + + /* + * Wait N TB3 ticks after the start trigger before + * clocking (N must be >=2). + */ + /* following line: 2-1 per STC */ + ni_stc_writel(dev, 1, NISTC_AO_UI_LOADA_REG); + ni_stc_writew(dev, NISTC_AO_CMD1_UI_LOAD, NISTC_AO_CMD1_REG); + ni_stc_writel(dev, trigvar, NISTC_AO_UI_LOADA_REG); + } else { /* TRIG_EXT */ + /* FIXME: assert scan_begin_arg != 0, ret failure otherwise */ + devpriv->ao_cmd2 |= NISTC_AO_CMD2_BC_GATE_ENA; + devpriv->ao_mode1 |= NISTC_AO_MODE1_UPDATE_SRC( + ni_get_reg_value( + CR_CHAN(cmd->scan_begin_arg), + NI_AO_SampleClock, + &devpriv->routing_tables)); + if (cmd->scan_begin_arg & CR_INVERT) + devpriv->ao_mode1 |= NISTC_AO_MODE1_UPDATE_SRC_POLARITY; + } + + ni_stc_writew(dev, devpriv->ao_cmd2, NISTC_AO_CMD2_REG); + ni_stc_writew(dev, devpriv->ao_mode1, NISTC_AO_MODE1_REG); + devpriv->ao_mode2 &= ~(NISTC_AO_MODE2_UI_RELOAD_MODE(3) | + NISTC_AO_MODE2_UI_INIT_LOAD_SRC); + ni_stc_writew(dev, devpriv->ao_mode2, NISTC_AO_MODE2_REG); + + /* Configure DAQ-STC for Timed update mode */ + devpriv->ao_cmd1 |= NISTC_AO_CMD1_DAC1_UPDATE_MODE | + NISTC_AO_CMD1_DAC0_UPDATE_MODE; + /* We are not using UPDATE2-->don't have to set DACx_Source_Select */ + ni_stc_writew(dev, devpriv->ao_cmd1, NISTC_AO_CMD1_REG); + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); +} + +static void ni_ao_cmd_set_channels(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + const struct comedi_cmd *cmd = &s->async->cmd; + unsigned int bits = 0; + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + if (devpriv->is_6xxx) { + unsigned int i; + + bits = 0; + for (i = 0; i < cmd->chanlist_len; ++i) { + int chan = CR_CHAN(cmd->chanlist[i]); + + bits |= 1 << chan; + ni_ao_win_outw(dev, chan, NI611X_AO_WAVEFORM_GEN_REG); + } + ni_ao_win_outw(dev, bits, NI611X_AO_TIMED_REG); + } + + ni_ao_config_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len, 1); + + if (cmd->scan_end_arg > 1) { + devpriv->ao_mode1 |= NISTC_AO_MODE1_MULTI_CHAN; + bits = NISTC_AO_OUT_CTRL_CHANS(cmd->scan_end_arg - 1) + | NISTC_AO_OUT_CTRL_UPDATE_SEL_HIGHZ; + + } else { + devpriv->ao_mode1 &= ~NISTC_AO_MODE1_MULTI_CHAN; + bits = NISTC_AO_OUT_CTRL_UPDATE_SEL_HIGHZ; + if (devpriv->is_m_series | devpriv->is_6xxx) + bits |= NISTC_AO_OUT_CTRL_CHANS(0); + else + bits |= NISTC_AO_OUT_CTRL_CHANS( + CR_CHAN(cmd->chanlist[0])); + } + + ni_stc_writew(dev, devpriv->ao_mode1, NISTC_AO_MODE1_REG); + ni_stc_writew(dev, bits, NISTC_AO_OUT_CTRL_REG); + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); +} + +static void ni_ao_cmd_set_stop_conditions(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + struct ni_private *devpriv = dev->private; + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + devpriv->ao_mode3 |= NISTC_AO_MODE3_STOP_ON_OVERRUN_ERR; + ni_stc_writew(dev, devpriv->ao_mode3, NISTC_AO_MODE3_REG); + + /* + * Since we are not supporting waveform staging, we ignore these errors: + * NISTC_AO_MODE3_STOP_ON_BC_TC_ERR, + * NISTC_AO_MODE3_STOP_ON_BC_TC_TRIG_ERR + */ + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); +} + +static void ni_ao_cmd_set_fifo_mode(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + devpriv->ao_mode2 &= ~NISTC_AO_MODE2_FIFO_MODE_MASK; +#ifdef PCIDMA + devpriv->ao_mode2 |= NISTC_AO_MODE2_FIFO_MODE_HF_F; +#else + devpriv->ao_mode2 |= NISTC_AO_MODE2_FIFO_MODE_HF; +#endif + /* NOTE: this is where use_onboard_memory=True would be implemented */ + devpriv->ao_mode2 &= ~NISTC_AO_MODE2_FIFO_REXMIT_ENA; + ni_stc_writew(dev, devpriv->ao_mode2, NISTC_AO_MODE2_REG); + + /* enable sending of ao fifo requests (dma request) */ + ni_stc_writew(dev, NISTC_AO_START_AOFREQ_ENA, NISTC_AO_START_SEL_REG); + + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); + + /* we are not supporting boards with virtual fifos */ +} + +static void ni_ao_cmd_set_interrupts(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + if (s->async->cmd.stop_src == TRIG_COUNT) + ni_set_bits(dev, NISTC_INTB_ENA_REG, + NISTC_INTB_ENA_AO_BC_TC, 1); + + s->async->inttrig = ni_ao_inttrig; +} + +static int ni_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + const struct comedi_cmd *cmd = &s->async->cmd; + + if (dev->irq == 0) { + dev_err(dev->class_dev, "cannot run command without an irq"); + return -EIO; + } + + /* ni_ao_reset should have already been done */ + ni_ao_cmd_personalize(dev, cmd); + /* clearing fifo and preload happens elsewhere */ + + ni_ao_cmd_set_trigger(dev, cmd); + ni_ao_cmd_set_counters(dev, cmd); + ni_ao_cmd_set_update(dev, cmd); + ni_ao_cmd_set_channels(dev, s); + ni_ao_cmd_set_stop_conditions(dev, cmd); + ni_ao_cmd_set_fifo_mode(dev); + ni_cmd_set_mite_transfer(devpriv->ao_mite_ring, s, cmd, 0x00ffffff); + ni_ao_cmd_set_interrupts(dev, s); + + /* + * arm(ing) must happen later so that DMA can be setup and DACs + * preloaded with the actual output buffer before starting. + * + * start(ing) must happen _after_ arming is completed. Starting can be + * done either via ni_ao_inttrig, or via an external trigger. + * + * **Currently, ni_ao_inttrig will automatically attempt a call to + * ni_ao_arm if the device still needs arming at that point. This + * allows backwards compatibility. + */ + devpriv->ao_needs_arming = 1; + return 0; +} + +/* end ni_ao_cmd */ + +static int ni_ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + int err = 0; + unsigned int tmp; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_INT: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + err |= ni_check_trigger_arg_roffs(CR_CHAN(cmd->start_arg), + NI_AO_StartTrigger, + &devpriv->routing_tables, 1); + break; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + board->ao_speed); + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + devpriv->clock_ns * + 0xffffff); + } else { /* TRIG_EXT */ + err |= ni_check_trigger_arg(CR_CHAN(cmd->scan_begin_arg), + NI_AO_SampleClock, + &devpriv->routing_tables); + } + + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + cmd->scan_begin_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->scan_begin_arg, + cmd->flags)); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (err) + return 4; + + return 0; +} + +static int ni_ao_reset(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* See 3.6.1.2 "Resetting", of DAQ-STC Technical Reference Manual */ + + /* + * In the following, the "--sync" comments are meant to denote + * asynchronous boundaries for setting the registers as described in the + * DAQ-STC mostly in the order also described in the DAQ-STC. + */ + + struct ni_private *devpriv = dev->private; + + ni_release_ao_mite_channel(dev); + + /* --sync (reset AO) */ + if (devpriv->is_m_series) + /* following example in mhddk for m-series */ + ni_stc_writew(dev, NISTC_RESET_AO, NISTC_RESET_REG); + + /*--sync (start config) */ + ni_stc_writew(dev, NISTC_RESET_AO_CFG_START, NISTC_RESET_REG); + + /*--sync (Disarm) */ + ni_stc_writew(dev, NISTC_AO_CMD1_DISARM, NISTC_AO_CMD1_REG); + + /* + * --sync + * (clear bunch of registers--mseries mhddk examples do not include + * this) + */ + devpriv->ao_cmd1 = 0; + devpriv->ao_cmd2 = 0; + devpriv->ao_mode1 = 0; + devpriv->ao_mode2 = 0; + if (devpriv->is_m_series) + devpriv->ao_mode3 = NISTC_AO_MODE3_LAST_GATE_DISABLE; + else + devpriv->ao_mode3 = 0; + + ni_stc_writew(dev, 0, NISTC_AO_PERSONAL_REG); + ni_stc_writew(dev, 0, NISTC_AO_CMD1_REG); + ni_stc_writew(dev, 0, NISTC_AO_CMD2_REG); + ni_stc_writew(dev, 0, NISTC_AO_MODE1_REG); + ni_stc_writew(dev, 0, NISTC_AO_MODE2_REG); + ni_stc_writew(dev, 0, NISTC_AO_OUT_CTRL_REG); + ni_stc_writew(dev, devpriv->ao_mode3, NISTC_AO_MODE3_REG); + ni_stc_writew(dev, 0, NISTC_AO_START_SEL_REG); + ni_stc_writew(dev, 0, NISTC_AO_TRIG_SEL_REG); + + /*--sync (disable interrupts) */ + ni_set_bits(dev, NISTC_INTB_ENA_REG, ~0, 0); + + /*--sync (ack) */ + ni_stc_writew(dev, NISTC_AO_PERSONAL_BC_SRC_SEL, NISTC_AO_PERSONAL_REG); + ni_stc_writew(dev, NISTC_INTB_ACK_AO_ALL, NISTC_INTB_ACK_REG); + + /*--not in DAQ-STC. which doc? */ + if (devpriv->is_6xxx) { + ni_ao_win_outw(dev, (1u << s->n_chan) - 1u, + NI671X_AO_IMMEDIATE_REG); + ni_ao_win_outw(dev, NI611X_AO_MISC_CLEAR_WG, + NI611X_AO_MISC_REG); + } + ni_stc_writew(dev, NISTC_RESET_AO_CFG_END, NISTC_RESET_REG); + /*--end */ + + 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 &= ~NISTC_DIO_CTRL_DIR_MASK; + devpriv->dio_control |= NISTC_DIO_CTRL_DIR(s->io_bits); + ni_stc_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + + 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] & (NISTC_DIO_SDIN | NISTC_DIO_SDOUT)) && + devpriv->serial_interval_ns) + return -EBUSY; + + if (comedi_dio_update_state(s, data)) { + devpriv->dio_output &= ~NISTC_DIO_OUT_PARALLEL_MASK; + devpriv->dio_output |= NISTC_DIO_OUT_PARALLEL(s->state); + ni_stc_writew(dev, devpriv->dio_output, NISTC_DIO_OUT_REG); + } + + data[1] = ni_stc_readw(dev, NISTC_DIO_IN_REG); + + return insn->n; +} + +#ifdef PCIDMA +static int ni_m_series_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { + const struct ni_board_struct *board = dev->board_ptr; + + /* we don't care about actual channels */ + data[1] = board->dio_speed; + data[2] = 0; + return 0; + } + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + ni_writel(dev, s->io_bits, NI_M_DIO_DIR_REG); + + return insn->n; +} + +static int ni_m_series_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + ni_writel(dev, s->state, NI_M_DIO_REG); + + data[1] = ni_readl(dev, NI_M_DIO_REG); + + 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) +{ + struct ni_private *devpriv = dev->private; + unsigned int bytes_per_scan; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + /* + * Although NI_D[IO]_SampleClock are the same, perhaps we should still, + * for completeness, test whether the cmd is output or input? + */ + err |= ni_check_trigger_arg(CR_CHAN(cmd->scan_begin_arg), + NI_DO_SampleClock, + &devpriv->routing_tables); + if (CR_RANGE(cmd->scan_begin_arg) != 0 || + CR_AREF(cmd->scan_begin_arg) != 0) + err |= -EINVAL; + + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + bytes_per_scan = comedi_bytes_per_scan_cmd(s, cmd); + if (bytes_per_scan) { + err |= comedi_check_trigger_arg_max(&cmd->stop_arg, + s->async->prealloc_bufsz / + bytes_per_scan); + } + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= ni_cdio_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int ni_cdo_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + const unsigned int timeout = 1000; + int retval = 0; + unsigned int i; + struct ni_private *devpriv = dev->private; + unsigned long flags; + + 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); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->cdo_mite_chan) { + mite_prep_dma(devpriv->cdo_mite_chan, 32, 32); + mite_dma_arm(devpriv->cdo_mite_chan); + } else { + dev_err(dev->class_dev, "BUG: no cdo mite channel?\n"); + retval = -EIO; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + if (retval < 0) + return retval; + + /* + * XXX not sure what interrupt C group does + * wait for dma to fill output fifo + * ni_writeb(dev, NI_M_INTC_ENA, NI_M_INTC_ENA_REG); + */ + for (i = 0; i < timeout; ++i) { + if (ni_readl(dev, NI_M_CDIO_STATUS_REG) & + NI_M_CDIO_STATUS_CDO_FIFO_FULL) + break; + usleep_range(10, 100); + } + if (i == timeout) { + dev_err(dev->class_dev, "dma failed to fill cdo fifo!\n"); + s->cancel(dev, s); + return -EIO; + } + ni_writel(dev, NI_M_CDO_CMD_ARM | + NI_M_CDO_CMD_ERR_INT_ENA_SET | + NI_M_CDO_CMD_F_E_INT_ENA_SET, + NI_M_CDIO_CMD_REG); + return retval; +} + +static int ni_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + const struct comedi_cmd *cmd = &s->async->cmd; + unsigned int cdo_mode_bits; + int retval; + + ni_writel(dev, NI_M_CDO_CMD_RESET, NI_M_CDIO_CMD_REG); + /* + * Although NI_D[IO]_SampleClock are the same, perhaps we should still, + * for completeness, test whether the cmd is output or input(?) + */ + cdo_mode_bits = NI_M_CDO_MODE_FIFO_MODE | + NI_M_CDO_MODE_HALT_ON_ERROR | + NI_M_CDO_MODE_SAMPLE_SRC( + ni_get_reg_value( + CR_CHAN(cmd->scan_begin_arg), + NI_DO_SampleClock, + &devpriv->routing_tables)); + if (cmd->scan_begin_arg & CR_INVERT) + cdo_mode_bits |= NI_M_CDO_MODE_POLARITY; + ni_writel(dev, cdo_mode_bits, NI_M_CDO_MODE_REG); + if (s->io_bits) { + ni_writel(dev, s->state, NI_M_CDO_FIFO_DATA_REG); + ni_writel(dev, NI_M_CDO_CMD_SW_UPDATE, NI_M_CDIO_CMD_REG); + ni_writel(dev, s->io_bits, NI_M_CDO_MASK_ENA_REG); + } else { + dev_err(dev->class_dev, + "attempted to run digital output command with no lines configured as outputs\n"); + return -EIO; + } + retval = ni_request_cdo_mite_channel(dev); + if (retval < 0) + return retval; + + ni_cmd_set_mite_transfer(devpriv->cdo_mite_ring, s, cmd, + s->async->prealloc_bufsz / + comedi_bytes_per_scan(s)); + + s->async->inttrig = ni_cdo_inttrig; + + return 0; +} + +static int ni_cdio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + ni_writel(dev, NI_M_CDO_CMD_DISARM | + NI_M_CDO_CMD_ERR_INT_ENA_CLR | + NI_M_CDO_CMD_F_E_INT_ENA_CLR | + NI_M_CDO_CMD_F_REQ_INT_ENA_CLR, + NI_M_CDIO_CMD_REG); + /* + * XXX not sure what interrupt C group does + * ni_writeb(dev, 0, NI_M_INTC_ENA_REG); + */ + ni_writel(dev, 0, NI_M_CDO_MASK_ENA_REG); + ni_release_cdo_mite_channel(dev); + return 0; +} + +static void handle_cdio_interrupt(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + unsigned int cdio_status; + struct comedi_subdevice *s = &dev->subdevices[NI_DIO_SUBDEV]; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->cdo_mite_chan) + mite_ack_linkc(devpriv->cdo_mite_chan, s, true); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + cdio_status = ni_readl(dev, NI_M_CDIO_STATUS_REG); + if (cdio_status & NI_M_CDIO_STATUS_CDO_ERROR) { + /* XXX just guessing this is needed and does something useful */ + ni_writel(dev, NI_M_CDO_CMD_ERR_INT_CONFIRM, + NI_M_CDIO_CMD_REG); + s->async->events |= COMEDI_CB_OVERFLOW; + } + if (cdio_status & NI_M_CDIO_STATUS_CDO_FIFO_EMPTY) { + ni_writel(dev, NI_M_CDO_CMD_F_E_INT_ENA_CLR, + NI_M_CDIO_CMD_REG); + /* s->async->events |= COMEDI_CB_EOA; */ + } + comedi_handle_events(dev, s); +} +#endif /* PCIDMA */ + +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 &= ~NISTC_DIO_OUT_SERIAL_MASK; + devpriv->dio_output |= NISTC_DIO_OUT_SERIAL(data_out); + ni_stc_writew(dev, devpriv->dio_output, NISTC_DIO_OUT_REG); + + status1 = ni_stc_readw(dev, NISTC_STATUS1_REG); + if (status1 & NISTC_STATUS1_SERIO_IN_PROG) { + err = -EBUSY; + goto error; + } + + devpriv->dio_control |= NISTC_DIO_CTRL_HW_SER_START; + ni_stc_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + devpriv->dio_control &= ~NISTC_DIO_CTRL_HW_SER_START; + + /* Wait until STC says we're done, but don't loop infinitely. */ + while ((status1 = ni_stc_readw(dev, NISTC_STATUS1_REG)) & + NISTC_STATUS1_SERIO_IN_PROG) { + /* Delay one bit per loop */ + udelay((devpriv->serial_interval_ns + 999) / 1000); + if (--count < 0) { + dev_err(dev->class_dev, + "SPI serial I/O didn't finish in time!\n"); + err = -ETIME; + goto error; + } + } + + /* + * Delay for last bit. This delay is absolutely necessary, because + * NISTC_STATUS1_SERIO_IN_PROG goes high one bit too early. + */ + udelay((devpriv->serial_interval_ns + 999) / 1000); + + if (data_in) + *data_in = ni_stc_readw(dev, NISTC_DIO_SERIAL_IN_REG); + +error: + ni_stc_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + + 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 &= ~NISTC_DIO_SDOUT; + if (data_out & mask) + devpriv->dio_output |= NISTC_DIO_SDOUT; + ni_stc_writew(dev, devpriv->dio_output, NISTC_DIO_OUT_REG); + + /* + * Assert SDCLK (active low, inverted), wait for half of + * the delay, deassert SDCLK, and wait for the other half. + */ + devpriv->dio_control |= NISTC_DIO_SDCLK; + ni_stc_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + + udelay((devpriv->serial_interval_ns + 999) / 2000); + + devpriv->dio_control &= ~NISTC_DIO_SDCLK; + ni_stc_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + + udelay((devpriv->serial_interval_ns + 999) / 2000); + + /* Input current bit */ + if (ni_stc_readw(dev, NISTC_DIO_IN_REG) & NISTC_DIO_SDIN) + input |= mask; + } + + if (data_in) + *data_in = input; + + return 0; +} + +static int ni_serial_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int clk_fout = devpriv->clock_and_fout; + 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 |= NISTC_DIO_CTRL_HW_SER_ENA; + + if (data[1] == SERIAL_DISABLED) { + devpriv->serial_hw_mode = 0; + devpriv->dio_control &= ~(NISTC_DIO_CTRL_HW_SER_ENA | + NISTC_DIO_SDCLK); + 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 &= ~NISTC_DIO_CTRL_HW_SER_TIMEBASE; + clk_fout |= NISTC_CLK_FOUT_SLOW_TIMEBASE; + clk_fout &= ~NISTC_CLK_FOUT_DIO_SER_OUT_DIV2; + data[1] = SERIAL_600NS; + devpriv->serial_interval_ns = data[1]; + } else if (data[1] <= SERIAL_1_2US) { + devpriv->dio_control &= ~NISTC_DIO_CTRL_HW_SER_TIMEBASE; + clk_fout |= NISTC_CLK_FOUT_SLOW_TIMEBASE | + NISTC_CLK_FOUT_DIO_SER_OUT_DIV2; + data[1] = SERIAL_1_2US; + devpriv->serial_interval_ns = data[1]; + } else if (data[1] <= SERIAL_10US) { + devpriv->dio_control |= NISTC_DIO_CTRL_HW_SER_TIMEBASE; + clk_fout |= NISTC_CLK_FOUT_SLOW_TIMEBASE | + NISTC_CLK_FOUT_DIO_SER_OUT_DIV2; + /* + * Note: NISTC_CLK_FOUT_DIO_SER_OUT_DIV2 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 &= ~(NISTC_DIO_CTRL_HW_SER_ENA | + NISTC_DIO_SDCLK); + devpriv->serial_hw_mode = 0; + data[1] = (data[1] / 1000) * 1000; + devpriv->serial_interval_ns = data[1]; + } + devpriv->clock_and_fout = clk_fout; + + ni_stc_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + ni_stc_writew(dev, devpriv->clock_and_fout, NISTC_CLK_FOUT_REG); + return 1; + + case INSN_CONFIG_BIDIRECTIONAL_DATA: + + if (devpriv->serial_interval_ns == 0) + return -EINVAL; + + byte_out = data[1] & 0xFF; + + if (devpriv->serial_hw_mode) { + err = ni_serial_hw_readwrite8(dev, s, byte_out, + &byte_in); + } else if (devpriv->serial_interval_ns > 0) { + err = ni_serial_sw_readwrite8(dev, s, byte_out, + &byte_in); + } else { + dev_err(dev->class_dev, "serial disabled!\n"); + return -EINVAL; + } + if (err < 0) + return err; + data[1] = byte_in & 0xFF; + return insn->n; + + break; + default: + return -EINVAL; + } +} + +static void init_ao_67xx(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int i; + + for (i = 0; i < s->n_chan; i++) { + ni_ao_win_outw(dev, NI_E_AO_DACSEL(i) | 0x0, + NI67XX_AO_CFG2_REG); + } + ni_ao_win_outw(dev, 0x0, NI67XX_AO_SP_UPDATES_REG); +} + +static const struct mio_regmap ni_gpct_to_stc_regmap[] = { + [NITIO_G0_AUTO_INC] = { NISTC_G0_AUTOINC_REG, 2 }, + [NITIO_G1_AUTO_INC] = { NISTC_G1_AUTOINC_REG, 2 }, + [NITIO_G0_CMD] = { NISTC_G0_CMD_REG, 2 }, + [NITIO_G1_CMD] = { NISTC_G1_CMD_REG, 2 }, + [NITIO_G0_HW_SAVE] = { NISTC_G0_HW_SAVE_REG, 4 }, + [NITIO_G1_HW_SAVE] = { NISTC_G1_HW_SAVE_REG, 4 }, + [NITIO_G0_SW_SAVE] = { NISTC_G0_SAVE_REG, 4 }, + [NITIO_G1_SW_SAVE] = { NISTC_G1_SAVE_REG, 4 }, + [NITIO_G0_MODE] = { NISTC_G0_MODE_REG, 2 }, + [NITIO_G1_MODE] = { NISTC_G1_MODE_REG, 2 }, + [NITIO_G0_LOADA] = { NISTC_G0_LOADA_REG, 4 }, + [NITIO_G1_LOADA] = { NISTC_G1_LOADA_REG, 4 }, + [NITIO_G0_LOADB] = { NISTC_G0_LOADB_REG, 4 }, + [NITIO_G1_LOADB] = { NISTC_G1_LOADB_REG, 4 }, + [NITIO_G0_INPUT_SEL] = { NISTC_G0_INPUT_SEL_REG, 2 }, + [NITIO_G1_INPUT_SEL] = { NISTC_G1_INPUT_SEL_REG, 2 }, + [NITIO_G0_CNT_MODE] = { 0x1b0, 2 }, /* M-Series only */ + [NITIO_G1_CNT_MODE] = { 0x1b2, 2 }, /* M-Series only */ + [NITIO_G0_GATE2] = { 0x1b4, 2 }, /* M-Series only */ + [NITIO_G1_GATE2] = { 0x1b6, 2 }, /* M-Series only */ + [NITIO_G01_STATUS] = { NISTC_G01_STATUS_REG, 2 }, + [NITIO_G01_RESET] = { NISTC_RESET_REG, 2 }, + [NITIO_G01_STATUS1] = { NISTC_STATUS1_REG, 2 }, + [NITIO_G01_STATUS2] = { NISTC_STATUS2_REG, 2 }, + [NITIO_G0_DMA_CFG] = { 0x1b8, 2 }, /* M-Series only */ + [NITIO_G1_DMA_CFG] = { 0x1ba, 2 }, /* M-Series only */ + [NITIO_G0_DMA_STATUS] = { 0x1b8, 2 }, /* M-Series only */ + [NITIO_G1_DMA_STATUS] = { 0x1ba, 2 }, /* M-Series only */ + [NITIO_G0_ABZ] = { 0x1c0, 2 }, /* M-Series only */ + [NITIO_G1_ABZ] = { 0x1c2, 2 }, /* M-Series only */ + [NITIO_G0_INT_ACK] = { NISTC_INTA_ACK_REG, 2 }, + [NITIO_G1_INT_ACK] = { NISTC_INTB_ACK_REG, 2 }, + [NITIO_G0_STATUS] = { NISTC_AI_STATUS1_REG, 2 }, + [NITIO_G1_STATUS] = { NISTC_AO_STATUS1_REG, 2 }, + [NITIO_G0_INT_ENA] = { NISTC_INTA_ENA_REG, 2 }, + [NITIO_G1_INT_ENA] = { NISTC_INTB_ENA_REG, 2 }, +}; + +static unsigned int ni_gpct_to_stc_register(struct comedi_device *dev, + enum ni_gpct_register reg) +{ + const struct mio_regmap *regmap; + + if (reg < ARRAY_SIZE(ni_gpct_to_stc_regmap)) { + regmap = &ni_gpct_to_stc_regmap[reg]; + } else { + dev_warn(dev->class_dev, "%s: unhandled register=0x%x\n", + __func__, reg); + return 0; + } + + return regmap->mio_reg; +} + +static void ni_gpct_write_register(struct ni_gpct *counter, unsigned int bits, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + unsigned int stc_register = ni_gpct_to_stc_register(dev, reg); + + if (stc_register == 0) + return; + + switch (reg) { + /* m-series only registers */ + case NITIO_G0_CNT_MODE: + case NITIO_G1_CNT_MODE: + case NITIO_G0_GATE2: + case NITIO_G1_GATE2: + case NITIO_G0_DMA_CFG: + case NITIO_G1_DMA_CFG: + case NITIO_G0_ABZ: + case NITIO_G1_ABZ: + ni_writew(dev, bits, stc_register); + break; + + /* 32 bit registers */ + case NITIO_G0_LOADA: + case NITIO_G1_LOADA: + case NITIO_G0_LOADB: + case NITIO_G1_LOADB: + ni_stc_writel(dev, bits, stc_register); + break; + + /* 16 bit registers */ + case NITIO_G0_INT_ENA: + ni_set_bitfield(dev, stc_register, + NISTC_INTA_ENA_G0_GATE | NISTC_INTA_ENA_G0_TC, + bits); + break; + case NITIO_G1_INT_ENA: + ni_set_bitfield(dev, stc_register, + NISTC_INTB_ENA_G1_GATE | NISTC_INTB_ENA_G1_TC, + bits); + break; + default: + ni_stc_writew(dev, bits, stc_register); + } +} + +static unsigned int ni_gpct_read_register(struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + unsigned int stc_register = ni_gpct_to_stc_register(dev, reg); + + if (stc_register == 0) + return 0; + + switch (reg) { + /* m-series only registers */ + case NITIO_G0_DMA_STATUS: + case NITIO_G1_DMA_STATUS: + return ni_readw(dev, stc_register); + + /* 32 bit registers */ + case NITIO_G0_HW_SAVE: + case NITIO_G1_HW_SAVE: + case NITIO_G0_SW_SAVE: + case NITIO_G1_SW_SAVE: + return ni_stc_readl(dev, stc_register); + + /* 16 bit registers */ + default: + return ni_stc_readw(dev, stc_register); + } +} + +static int ni_freq_out_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int val = NISTC_CLK_FOUT_TO_DIVIDER(devpriv->clock_and_fout); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = val; + + return insn->n; +} + +static int ni_freq_out_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + + if (insn->n) { + unsigned int val = data[insn->n - 1]; + + devpriv->clock_and_fout &= ~NISTC_CLK_FOUT_ENA; + ni_stc_writew(dev, devpriv->clock_and_fout, NISTC_CLK_FOUT_REG); + devpriv->clock_and_fout &= ~NISTC_CLK_FOUT_DIVIDER_MASK; + + /* use the last data value to set the fout divider */ + devpriv->clock_and_fout |= NISTC_CLK_FOUT_DIVIDER(val); + + devpriv->clock_and_fout |= NISTC_CLK_FOUT_ENA; + ni_stc_writew(dev, devpriv->clock_and_fout, NISTC_CLK_FOUT_REG); + } + return insn->n; +} + +static int ni_freq_out_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + switch (data[1]) { + case NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC: + devpriv->clock_and_fout &= ~NISTC_CLK_FOUT_TIMEBASE_SEL; + break; + case NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC: + devpriv->clock_and_fout |= NISTC_CLK_FOUT_TIMEBASE_SEL; + break; + default: + return -EINVAL; + } + ni_stc_writew(dev, devpriv->clock_and_fout, NISTC_CLK_FOUT_REG); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + if (devpriv->clock_and_fout & NISTC_CLK_FOUT_TIMEBASE_SEL) { + data[1] = NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC; + data[2] = TIMEBASE_2_NS; + } else { + data[1] = NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC; + data[2] = TIMEBASE_1_NS * 2; + } + break; + default: + return -EINVAL; + } + return insn->n; +} + +static int ni_8255_callback(struct comedi_device *dev, + int dir, int port, int data, unsigned long iobase) +{ + if (dir) { + ni_writeb(dev, data, iobase + 2 * port); + return 0; + } + + return ni_readb(dev, iobase + 2 * port); +} + +static int ni_get_pwm_config(struct comedi_device *dev, unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + + data[1] = devpriv->pwm_up_count * devpriv->clock_ns; + data[2] = devpriv->pwm_down_count * devpriv->clock_ns; + return 3; +} + +static int ni_m_series_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int up_count, down_count; + + switch (data[0]) { + case INSN_CONFIG_PWM_OUTPUT: + switch (data[1]) { + case CMDF_ROUND_NEAREST: + up_count = DIV_ROUND_CLOSEST(data[2], + devpriv->clock_ns); + break; + case CMDF_ROUND_DOWN: + up_count = data[2] / devpriv->clock_ns; + break; + case CMDF_ROUND_UP: + up_count = + DIV_ROUND_UP(data[2], devpriv->clock_ns); + break; + default: + return -EINVAL; + } + switch (data[3]) { + case CMDF_ROUND_NEAREST: + down_count = DIV_ROUND_CLOSEST(data[4], + devpriv->clock_ns); + break; + case CMDF_ROUND_DOWN: + down_count = data[4] / devpriv->clock_ns; + break; + case CMDF_ROUND_UP: + down_count = + DIV_ROUND_UP(data[4], devpriv->clock_ns); + break; + default: + return -EINVAL; + } + if (up_count * devpriv->clock_ns != data[2] || + down_count * devpriv->clock_ns != data[4]) { + data[2] = up_count * devpriv->clock_ns; + data[4] = down_count * devpriv->clock_ns; + return -EAGAIN; + } + ni_writel(dev, NI_M_CAL_PWM_HIGH_TIME(up_count) | + NI_M_CAL_PWM_LOW_TIME(down_count), + NI_M_CAL_PWM_REG); + devpriv->pwm_up_count = up_count; + devpriv->pwm_down_count = down_count; + return 5; + case INSN_CONFIG_GET_PWM_OUTPUT: + return ni_get_pwm_config(dev, data); + default: + return -EINVAL; + } + return 0; +} + +static int ni_6143_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int up_count, down_count; + + switch (data[0]) { + case INSN_CONFIG_PWM_OUTPUT: + switch (data[1]) { + case CMDF_ROUND_NEAREST: + up_count = DIV_ROUND_CLOSEST(data[2], + devpriv->clock_ns); + break; + case CMDF_ROUND_DOWN: + up_count = data[2] / devpriv->clock_ns; + break; + case CMDF_ROUND_UP: + up_count = + DIV_ROUND_UP(data[2], devpriv->clock_ns); + break; + default: + return -EINVAL; + } + switch (data[3]) { + case CMDF_ROUND_NEAREST: + down_count = DIV_ROUND_CLOSEST(data[4], + devpriv->clock_ns); + break; + case CMDF_ROUND_DOWN: + down_count = data[4] / devpriv->clock_ns; + break; + case CMDF_ROUND_UP: + down_count = + DIV_ROUND_UP(data[4], devpriv->clock_ns); + break; + default: + return -EINVAL; + } + if (up_count * devpriv->clock_ns != data[2] || + down_count * devpriv->clock_ns != data[4]) { + data[2] = up_count * devpriv->clock_ns; + data[4] = down_count * devpriv->clock_ns; + return -EAGAIN; + } + ni_writel(dev, up_count, NI6143_CALIB_HI_TIME_REG); + devpriv->pwm_up_count = up_count; + ni_writel(dev, down_count, NI6143_CALIB_LO_TIME_REG); + devpriv->pwm_down_count = down_count; + return 5; + case INSN_CONFIG_GET_PWM_OUTPUT: + return ni_get_pwm_config(dev, data); + default: + return -EINVAL; + } + return 0; +} + +static int pack_mb88341(int addr, int val, int *bitstring) +{ + /* + * Fujitsu MB 88341 + * Note that address bits are reversed. Thanks to + * Ingo Keen for noticing this. + * + * Note also that the 88341 expects address values from + * 1-12, whereas we use channel numbers 0-11. The NI + * docs use 1-12, also, so be careful here. + */ + addr++; + *bitstring = ((addr & 0x1) << 11) | + ((addr & 0x2) << 9) | + ((addr & 0x4) << 7) | ((addr & 0x8) << 5) | (val & 0xff); + return 12; +} + +static int pack_dac8800(int addr, int val, int *bitstring) +{ + *bitstring = ((addr & 0x7) << 8) | (val & 0xff); + return 11; +} + +static int pack_dac8043(int addr, int val, int *bitstring) +{ + *bitstring = val & 0xfff; + return 12; +} + +static int pack_ad8522(int addr, int val, int *bitstring) +{ + *bitstring = (val & 0xfff) | (addr ? 0xc000 : 0xa000); + return 16; +} + +static int pack_ad8804(int addr, int val, int *bitstring) +{ + *bitstring = ((addr & 0xf) << 8) | (val & 0xff); + return 12; +} + +static int pack_ad8842(int addr, int val, int *bitstring) +{ + *bitstring = ((addr + 1) << 8) | (val & 0xff); + return 12; +} + +struct caldac_struct { + int n_chans; + int n_bits; + int (*packbits)(int address, int value, int *bitstring); +}; + +static struct caldac_struct caldacs[] = { + [mb88341] = {12, 8, pack_mb88341}, + [dac8800] = {8, 8, pack_dac8800}, + [dac8043] = {1, 12, pack_dac8043}, + [ad8522] = {2, 12, pack_ad8522}, + [ad8804] = {12, 8, pack_ad8804}, + [ad8842] = {8, 8, pack_ad8842}, + [ad8804_debug] = {16, 8, pack_ad8804}, +}; + +static void ni_write_caldac(struct comedi_device *dev, int addr, int val) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + unsigned int loadbit = 0, bits = 0, bit, bitstring = 0; + unsigned int cmd; + int i; + int type; + + if (devpriv->caldacs[addr] == val) + return; + devpriv->caldacs[addr] = val; + + for (i = 0; i < 3; i++) { + type = board->caldac[i]; + if (type == caldac_none) + break; + if (addr < caldacs[type].n_chans) { + bits = caldacs[type].packbits(addr, val, &bitstring); + loadbit = NI_E_SERIAL_CMD_DAC_LD(i); + break; + } + addr -= caldacs[type].n_chans; + } + + /* bits will be 0 if there is no caldac for the given addr */ + if (bits == 0) + return; + + for (bit = 1 << (bits - 1); bit; bit >>= 1) { + cmd = (bit & bitstring) ? NI_E_SERIAL_CMD_SDATA : 0; + ni_writeb(dev, cmd, NI_E_SERIAL_CMD_REG); + udelay(1); + ni_writeb(dev, NI_E_SERIAL_CMD_SCLK | cmd, NI_E_SERIAL_CMD_REG); + udelay(1); + } + ni_writeb(dev, loadbit, NI_E_SERIAL_CMD_REG); + udelay(1); + ni_writeb(dev, 0, NI_E_SERIAL_CMD_REG); +} + +static int ni_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n) { + /* only bother writing the last sample to the channel */ + ni_write_caldac(dev, CR_CHAN(insn->chanspec), + data[insn->n - 1]); + } + + return insn->n; +} + +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; + unsigned int i; + + for (i = 0; i < insn->n; i++) + data[0] = devpriv->caldacs[CR_CHAN(insn->chanspec)]; + + return insn->n; +} + +static void caldac_setup(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + int i, j; + int n_dacs; + int n_chans = 0; + int n_bits; + int diffbits = 0; + int type; + int chan; + + type = board->caldac[0]; + if (type == caldac_none) + return; + n_bits = caldacs[type].n_bits; + for (i = 0; i < 3; i++) { + type = board->caldac[i]; + if (type == caldac_none) + break; + if (caldacs[type].n_bits != n_bits) + diffbits = 1; + n_chans += caldacs[type].n_chans; + } + n_dacs = i; + s->n_chan = n_chans; + + if (diffbits) { + unsigned int *maxdata_list = devpriv->caldac_maxdata_list; + + if (n_chans > MAX_N_CALDACS) + dev_err(dev->class_dev, + "BUG! MAX_N_CALDACS too small\n"); + s->maxdata_list = maxdata_list; + chan = 0; + for (i = 0; i < n_dacs; i++) { + type = board->caldac[i]; + for (j = 0; j < caldacs[type].n_chans; j++) { + maxdata_list[chan] = + (1 << caldacs[type].n_bits) - 1; + chan++; + } + } + + for (chan = 0; chan < s->n_chan; chan++) + ni_write_caldac(dev, i, s->maxdata_list[i] / 2); + } else { + type = board->caldac[0]; + s->maxdata = (1 << caldacs[type].n_bits) - 1; + + for (chan = 0; chan < s->n_chan; chan++) + ni_write_caldac(dev, i, s->maxdata / 2); + } +} + +static int ni_read_eeprom(struct comedi_device *dev, int addr) +{ + unsigned int cmd = NI_E_SERIAL_CMD_EEPROM_CS; + int bit; + int bitstring; + + bitstring = 0x0300 | ((addr & 0x100) << 3) | (addr & 0xff); + ni_writeb(dev, cmd, NI_E_SERIAL_CMD_REG); + for (bit = 0x8000; bit; bit >>= 1) { + if (bit & bitstring) + cmd |= NI_E_SERIAL_CMD_SDATA; + else + cmd &= ~NI_E_SERIAL_CMD_SDATA; + + ni_writeb(dev, cmd, NI_E_SERIAL_CMD_REG); + ni_writeb(dev, NI_E_SERIAL_CMD_SCLK | cmd, NI_E_SERIAL_CMD_REG); + } + cmd = NI_E_SERIAL_CMD_EEPROM_CS; + bitstring = 0; + for (bit = 0x80; bit; bit >>= 1) { + ni_writeb(dev, cmd, NI_E_SERIAL_CMD_REG); + ni_writeb(dev, NI_E_SERIAL_CMD_SCLK | cmd, NI_E_SERIAL_CMD_REG); + if (ni_readb(dev, NI_E_STATUS_REG) & NI_E_STATUS_PROMOUT) + bitstring |= bit; + } + ni_writeb(dev, 0, NI_E_SERIAL_CMD_REG); + + return bitstring; +} + +static int ni_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int val; + unsigned int i; + + if (insn->n) { + val = ni_read_eeprom(dev, CR_CHAN(insn->chanspec)); + for (i = 0; i < insn->n; i++) + data[i] = val; + } + return insn->n; +} + +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; + unsigned int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->eeprom_buffer[CR_CHAN(insn->chanspec)]; + + return insn->n; +} + +static unsigned int ni_old_get_pfi_routing(struct comedi_device *dev, + unsigned int chan) +{ + /* pre-m-series boards have fixed signals on pfi pins */ + switch (chan) { + case 0: + return NI_PFI_OUTPUT_AI_START1; + case 1: + return NI_PFI_OUTPUT_AI_START2; + case 2: + return NI_PFI_OUTPUT_AI_CONVERT; + case 3: + return NI_PFI_OUTPUT_G_SRC1; + case 4: + return NI_PFI_OUTPUT_G_GATE1; + case 5: + return NI_PFI_OUTPUT_AO_UPDATE_N; + case 6: + return NI_PFI_OUTPUT_AO_START1; + case 7: + return NI_PFI_OUTPUT_AI_START_PULSE; + case 8: + return NI_PFI_OUTPUT_G_SRC0; + case 9: + return NI_PFI_OUTPUT_G_GATE0; + default: + dev_err(dev->class_dev, "bug, unhandled case in switch.\n"); + break; + } + return 0; +} + +static int ni_old_set_pfi_routing(struct comedi_device *dev, + unsigned int chan, unsigned int source) +{ + /* pre-m-series boards have fixed signals on pfi pins */ + if (source != ni_old_get_pfi_routing(dev, chan)) + return -EINVAL; + return 2; +} + +static unsigned int ni_m_series_get_pfi_routing(struct comedi_device *dev, + unsigned int chan) +{ + struct ni_private *devpriv = dev->private; + const unsigned int array_offset = chan / 3; + + return NI_M_PFI_OUT_SEL_TO_SRC(chan, + devpriv->pfi_output_select_reg[array_offset]); +} + +static int ni_m_series_set_pfi_routing(struct comedi_device *dev, + unsigned int chan, unsigned int source) +{ + struct ni_private *devpriv = dev->private; + unsigned int index = chan / 3; + unsigned short val = devpriv->pfi_output_select_reg[index]; + + if ((source & 0x1f) != source) + return -EINVAL; + + val &= ~NI_M_PFI_OUT_SEL_MASK(chan); + val |= NI_M_PFI_OUT_SEL(chan, source); + ni_writew(dev, val, NI_M_PFI_OUT_SEL_REG(index)); + devpriv->pfi_output_select_reg[index] = val; + + return 2; +} + +static unsigned int ni_get_pfi_routing(struct comedi_device *dev, + unsigned int chan) +{ + struct ni_private *devpriv = dev->private; + + if (chan >= NI_PFI(0)) { + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + } + return (devpriv->is_m_series) + ? ni_m_series_get_pfi_routing(dev, chan) + : ni_old_get_pfi_routing(dev, chan); +} + +/* Sets the output mux for the specified PFI channel. */ +static int ni_set_pfi_routing(struct comedi_device *dev, + unsigned int chan, unsigned int source) +{ + struct ni_private *devpriv = dev->private; + + if (chan >= NI_PFI(0)) { + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + } + return (devpriv->is_m_series) + ? ni_m_series_set_pfi_routing(dev, chan, source) + : ni_old_set_pfi_routing(dev, chan, source); +} + +static int ni_config_pfi_filter(struct comedi_device *dev, + unsigned int chan, + enum ni_pfi_filter_select filter) +{ + struct ni_private *devpriv = dev->private; + unsigned int bits; + + if (!devpriv->is_m_series) + return -ENOTSUPP; + + if (chan >= NI_PFI(0)) { + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + } + + bits = ni_readl(dev, NI_M_PFI_FILTER_REG); + bits &= ~NI_M_PFI_FILTER_SEL_MASK(chan); + bits |= NI_M_PFI_FILTER_SEL(chan, filter); + ni_writel(dev, bits, NI_M_PFI_FILTER_REG); + return 0; +} + +static void ni_set_pfi_direction(struct comedi_device *dev, int chan, + unsigned int direction) +{ + if (chan >= NI_PFI(0)) { + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + } + direction = (direction == COMEDI_OUTPUT) ? 1u : 0u; + ni_set_bits(dev, NISTC_IO_BIDIR_PIN_REG, 1 << chan, direction); +} + +static int ni_get_pfi_direction(struct comedi_device *dev, int chan) +{ + struct ni_private *devpriv = dev->private; + + if (chan >= NI_PFI(0)) { + /* allow new and old names of pfi channels to work. */ + chan -= NI_PFI(0); + } + return devpriv->io_bidirection_pin_reg & (1 << chan) ? + COMEDI_OUTPUT : COMEDI_INPUT; +} + +static int ni_pfi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan; + + if (insn->n < 1) + return -EINVAL; + + chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case COMEDI_OUTPUT: + case COMEDI_INPUT: + ni_set_pfi_direction(dev, chan, data[0]); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = ni_get_pfi_direction(dev, chan); + break; + case INSN_CONFIG_SET_ROUTING: + return ni_set_pfi_routing(dev, chan, data[1]); + case INSN_CONFIG_GET_ROUTING: + data[1] = ni_get_pfi_routing(dev, chan); + break; + case INSN_CONFIG_FILTER: + return ni_config_pfi_filter(dev, chan, data[1]); + default: + return -EINVAL; + } + return 0; +} + +static int ni_pfi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + + if (!devpriv->is_m_series) + return -ENOTSUPP; + + if (comedi_dio_update_state(s, data)) + ni_writew(dev, s->state, NI_M_PFI_DO_REG); + + data[1] = ni_readw(dev, NI_M_PFI_DI_REG); + + return insn->n; +} + +static int cs5529_wait_for_idle(struct comedi_device *dev) +{ + unsigned short status; + const int timeout = HZ; + int i; + + for (i = 0; i < timeout; i++) { + status = ni_ao_win_inw(dev, NI67XX_CAL_STATUS_REG); + if ((status & NI67XX_CAL_STATUS_BUSY) == 0) + break; + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(1)) + return -EIO; + } + if (i == timeout) { + dev_err(dev->class_dev, "timeout\n"); + return -ETIME; + } + return 0; +} + +static void cs5529_command(struct comedi_device *dev, unsigned short value) +{ + static const int timeout = 100; + int i; + + ni_ao_win_outw(dev, value, NI67XX_CAL_CMD_REG); + /* give time for command to start being serially clocked into cs5529. + * this insures that the NI67XX_CAL_STATUS_BUSY bit will get properly + * set before we exit this function. + */ + for (i = 0; i < timeout; i++) { + if (ni_ao_win_inw(dev, NI67XX_CAL_STATUS_REG) & + NI67XX_CAL_STATUS_BUSY) + break; + udelay(1); + } + if (i == timeout) + dev_err(dev->class_dev, + "possible problem - never saw adc go busy?\n"); +} + +static int cs5529_do_conversion(struct comedi_device *dev, + unsigned short *data) +{ + int retval; + unsigned short status; + + cs5529_command(dev, CS5529_CMD_CB | CS5529_CMD_SINGLE_CONV); + retval = cs5529_wait_for_idle(dev); + if (retval) { + dev_err(dev->class_dev, + "timeout or signal in %s()\n", __func__); + return -ETIME; + } + status = ni_ao_win_inw(dev, NI67XX_CAL_STATUS_REG); + if (status & NI67XX_CAL_STATUS_OSC_DETECT) { + dev_err(dev->class_dev, + "cs5529 conversion error, status CSS_OSC_DETECT\n"); + return -EIO; + } + if (status & NI67XX_CAL_STATUS_OVERRANGE) { + dev_err(dev->class_dev, + "cs5529 conversion error, overrange (ignoring)\n"); + } + if (data) { + *data = ni_ao_win_inw(dev, NI67XX_CAL_DATA_REG); + /* cs5529 returns 16 bit signed data in bipolar mode */ + *data ^= BIT(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, NI67XX_AO_CAL_CHAN_SEL_REG); + + for (n = 0; n < insn->n; n++) { + retval = cs5529_do_conversion(dev, &sample); + if (retval < 0) + return retval; + data[n] = sample; + } + return insn->n; +} + +static void cs5529_config_write(struct comedi_device *dev, unsigned int value, + unsigned int reg_select_bits) +{ + ni_ao_win_outw(dev, (value >> 16) & 0xff, NI67XX_CAL_CFG_HI_REG); + ni_ao_win_outw(dev, value & 0xffff, NI67XX_CAL_CFG_LO_REG); + reg_select_bits &= CS5529_CMD_REG_MASK; + cs5529_command(dev, CS5529_CMD_CB | reg_select_bits); + if (cs5529_wait_for_idle(dev)) + dev_err(dev->class_dev, + "timeout or signal in %s\n", __func__); +} + +static int init_cs5529(struct comedi_device *dev) +{ + unsigned int config_bits = CS5529_CFG_PORT_FLAG | + CS5529_CFG_WORD_RATE_2180; + +#if 1 + /* do self-calibration */ + cs5529_config_write(dev, config_bits | CS5529_CFG_CALIB_BOTH_SELF, + CS5529_CFG_REG); + /* 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, CS5529_GAIN_REG); + cs5529_config_write(dev, config_bits | CS5529_CFG_CALIB_OFFSET_SELF, + CS5529_CFG_REG); + if (cs5529_wait_for_idle(dev)) + dev_err(dev->class_dev, + "timeout or signal in %s\n", __func__); +#endif + return 0; +} + +/* + * Find best multiplier/divider to try and get the PLL running at 80 MHz + * given an arbitrary frequency input clock. + */ +static int ni_mseries_get_pll_parameters(unsigned int reference_period_ns, + unsigned int *freq_divider, + unsigned int *freq_multiplier, + unsigned int *actual_period_ns) +{ + unsigned int div; + unsigned int best_div = 1; + unsigned int mult; + unsigned int best_mult = 1; + static const unsigned int pico_per_nano = 1000; + const unsigned int 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 int target_picosec = 12500; + int best_period_picosec = 0; + + for (div = 1; div <= NI_M_PLL_MAX_DIVISOR; ++div) { + for (mult = 1; mult <= NI_M_PLL_MAX_MULTIPLIER; ++mult) { + unsigned int new_period_ps = + (reference_picosec * div) / mult; + if (abs(new_period_ps - target_picosec) < + abs(best_period_picosec - target_picosec)) { + best_period_picosec = new_period_ps; + best_div = div; + best_mult = mult; + } + } + } + if (best_period_picosec == 0) + return -EIO; + + *freq_divider = best_div; + *freq_multiplier = best_mult; + /* return the actual period (* fudge factor for 80 to 20 MHz) */ + *actual_period_ns = DIV_ROUND_CLOSEST(best_period_picosec * 4, + pico_per_nano); + return 0; +} + +static int ni_mseries_set_pll_master_clock(struct comedi_device *dev, + unsigned int source, + unsigned int period_ns) +{ + struct ni_private *devpriv = dev->private; + static const unsigned int min_period_ns = 50; + static const unsigned int max_period_ns = 1000; + static const unsigned int timeout = 1000; + unsigned int pll_control_bits; + unsigned int freq_divider; + unsigned int freq_multiplier; + unsigned int rtsi; + unsigned int i; + int retval; + + if (source == NI_MIO_PLL_PXI10_CLOCK) + period_ns = 100; + /* + * These limits are somewhat arbitrary, but NI advertises 1 to 20MHz + * range so we'll use that. + */ + if (period_ns < min_period_ns || period_ns > max_period_ns) { + dev_err(dev->class_dev, + "%s: you must specify an input clock frequency between %i and %i nanosec for the phased-lock loop\n", + __func__, min_period_ns, max_period_ns); + return -EINVAL; + } + devpriv->rtsi_trig_direction_reg &= ~NISTC_RTSI_TRIG_USE_CLK; + ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg, + NISTC_RTSI_TRIG_DIR_REG); + pll_control_bits = NI_M_PLL_CTRL_ENA | NI_M_PLL_CTRL_VCO_MODE_75_150MHZ; + devpriv->clock_and_fout2 |= NI_M_CLK_FOUT2_TIMEBASE1_PLL | + NI_M_CLK_FOUT2_TIMEBASE3_PLL; + devpriv->clock_and_fout2 &= ~NI_M_CLK_FOUT2_PLL_SRC_MASK; + switch (source) { + case NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK: + devpriv->clock_and_fout2 |= NI_M_CLK_FOUT2_PLL_SRC_STAR; + break; + case NI_MIO_PLL_PXI10_CLOCK: + /* pxi clock is 10MHz */ + devpriv->clock_and_fout2 |= NI_M_CLK_FOUT2_PLL_SRC_PXI10; + break; + default: + for (rtsi = 0; rtsi <= NI_M_MAX_RTSI_CHAN; ++rtsi) { + if (source == NI_MIO_PLL_RTSI_CLOCK(rtsi)) { + devpriv->clock_and_fout2 |= + NI_M_CLK_FOUT2_PLL_SRC_RTSI(rtsi); + break; + } + } + if (rtsi > NI_M_MAX_RTSI_CHAN) + return -EINVAL; + break; + } + retval = ni_mseries_get_pll_parameters(period_ns, + &freq_divider, + &freq_multiplier, + &devpriv->clock_ns); + if (retval < 0) { + dev_err(dev->class_dev, + "bug, failed to find pll parameters\n"); + return retval; + } + + ni_writew(dev, devpriv->clock_and_fout2, NI_M_CLK_FOUT2_REG); + pll_control_bits |= NI_M_PLL_CTRL_DIVISOR(freq_divider) | + NI_M_PLL_CTRL_MULTIPLIER(freq_multiplier); + + ni_writew(dev, pll_control_bits, NI_M_PLL_CTRL_REG); + devpriv->clock_source = source; + /* it takes a few hundred microseconds for PLL to lock */ + for (i = 0; i < timeout; ++i) { + if (ni_readw(dev, NI_M_PLL_STATUS_REG) & NI_M_PLL_STATUS_LOCKED) + break; + udelay(1); + } + if (i == timeout) { + dev_err(dev->class_dev, + "%s: timed out waiting for PLL to lock to reference clock source %i with period %i ns\n", + __func__, source, period_ns); + return -ETIMEDOUT; + } + return 3; +} + +static int ni_set_master_clock(struct comedi_device *dev, + unsigned int source, unsigned int period_ns) +{ + struct ni_private *devpriv = dev->private; + + if (source == NI_MIO_INTERNAL_CLOCK) { + devpriv->rtsi_trig_direction_reg &= ~NISTC_RTSI_TRIG_USE_CLK; + ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg, + NISTC_RTSI_TRIG_DIR_REG); + devpriv->clock_ns = TIMEBASE_1_NS; + if (devpriv->is_m_series) { + devpriv->clock_and_fout2 &= + ~(NI_M_CLK_FOUT2_TIMEBASE1_PLL | + NI_M_CLK_FOUT2_TIMEBASE3_PLL); + ni_writew(dev, devpriv->clock_and_fout2, + NI_M_CLK_FOUT2_REG); + ni_writew(dev, 0, NI_M_PLL_CTRL_REG); + } + devpriv->clock_source = source; + } else { + if (devpriv->is_m_series) { + return ni_mseries_set_pll_master_clock(dev, source, + period_ns); + } else { + if (source == NI_MIO_RTSI_CLOCK) { + devpriv->rtsi_trig_direction_reg |= + NISTC_RTSI_TRIG_USE_CLK; + ni_stc_writew(dev, + devpriv->rtsi_trig_direction_reg, + NISTC_RTSI_TRIG_DIR_REG); + if (period_ns == 0) { + dev_err(dev->class_dev, + "we don't handle an unspecified clock period correctly yet, returning error\n"); + return -EINVAL; + } + devpriv->clock_ns = period_ns; + devpriv->clock_source = source; + } else { + return -EINVAL; + } + } + } + return 3; +} + +static int ni_valid_rtsi_output_source(struct comedi_device *dev, + unsigned int chan, unsigned int source) +{ + struct ni_private *devpriv = dev->private; + + if (chan >= NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series)) { + if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) { + if (source == NI_RTSI_OUTPUT_RTSI_OSC) + return 1; + + dev_err(dev->class_dev, + "%s: invalid source for channel=%i, channel %i is always the RTSI clock for pre-m-series boards\n", + __func__, chan, NISTC_RTSI_TRIG_OLD_CLK_CHAN); + 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): + case NI_RTSI_OUTPUT_RTSI_BRD(1): + case NI_RTSI_OUTPUT_RTSI_BRD(2): + case NI_RTSI_OUTPUT_RTSI_BRD(3): + return 1; + case NI_RTSI_OUTPUT_RTSI_OSC: + return (devpriv->is_m_series) ? 1 : 0; + default: + return 0; + } +} + +static int ni_set_rtsi_routing(struct comedi_device *dev, + unsigned int chan, unsigned int src) +{ + struct ni_private *devpriv = dev->private; + + if (chan >= TRIGGER_LINE(0)) + /* allow new and old names of rtsi channels to work. */ + chan -= TRIGGER_LINE(0); + + if (ni_valid_rtsi_output_source(dev, chan, src) == 0) + return -EINVAL; + if (chan < 4) { + devpriv->rtsi_trig_a_output_reg &= ~NISTC_RTSI_TRIG_MASK(chan); + devpriv->rtsi_trig_a_output_reg |= NISTC_RTSI_TRIG(chan, src); + ni_stc_writew(dev, devpriv->rtsi_trig_a_output_reg, + NISTC_RTSI_TRIGA_OUT_REG); + } else if (chan < NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series)) { + devpriv->rtsi_trig_b_output_reg &= ~NISTC_RTSI_TRIG_MASK(chan); + devpriv->rtsi_trig_b_output_reg |= NISTC_RTSI_TRIG(chan, src); + ni_stc_writew(dev, devpriv->rtsi_trig_b_output_reg, + NISTC_RTSI_TRIGB_OUT_REG); + } else if (chan != NISTC_RTSI_TRIG_OLD_CLK_CHAN) { + /* probably should never reach this, since the + * ni_valid_rtsi_output_source above errors out if chan is too + * high + */ + dev_err(dev->class_dev, "%s: unknown rtsi channel\n", __func__); + return -EINVAL; + } + return 2; +} + +static unsigned int ni_get_rtsi_routing(struct comedi_device *dev, + unsigned int chan) +{ + struct ni_private *devpriv = dev->private; + + if (chan >= TRIGGER_LINE(0)) + /* allow new and old names of rtsi channels to work. */ + chan -= TRIGGER_LINE(0); + + if (chan < 4) { + return NISTC_RTSI_TRIG_TO_SRC(chan, + devpriv->rtsi_trig_a_output_reg); + } else if (chan < NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series)) { + return NISTC_RTSI_TRIG_TO_SRC(chan, + devpriv->rtsi_trig_b_output_reg); + } else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) { + return NI_RTSI_OUTPUT_RTSI_OSC; + } + + dev_err(dev->class_dev, "%s: unknown rtsi channel\n", __func__); + return -EINVAL; +} + +static void ni_set_rtsi_direction(struct comedi_device *dev, int chan, + unsigned int direction) +{ + struct ni_private *devpriv = dev->private; + unsigned int max_chan = NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series); + + if (chan >= TRIGGER_LINE(0)) + /* allow new and old names of rtsi channels to work. */ + chan -= TRIGGER_LINE(0); + + if (direction == COMEDI_OUTPUT) { + if (chan < max_chan) { + devpriv->rtsi_trig_direction_reg |= + NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series); + } else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) { + devpriv->rtsi_trig_direction_reg |= + NISTC_RTSI_TRIG_DRV_CLK; + } + } else { + if (chan < max_chan) { + devpriv->rtsi_trig_direction_reg &= + ~NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series); + } else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) { + devpriv->rtsi_trig_direction_reg &= + ~NISTC_RTSI_TRIG_DRV_CLK; + } + } + ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg, + NISTC_RTSI_TRIG_DIR_REG); +} + +static int ni_get_rtsi_direction(struct comedi_device *dev, int chan) +{ + struct ni_private *devpriv = dev->private; + unsigned int max_chan = NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series); + + if (chan >= TRIGGER_LINE(0)) + /* allow new and old names of rtsi channels to work. */ + chan -= TRIGGER_LINE(0); + + if (chan < max_chan) { + return (devpriv->rtsi_trig_direction_reg & + NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series)) + ? COMEDI_OUTPUT : COMEDI_INPUT; + } else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) { + return (devpriv->rtsi_trig_direction_reg & + NISTC_RTSI_TRIG_DRV_CLK) + ? COMEDI_OUTPUT : COMEDI_INPUT; + } + return -EINVAL; +} + +static int ni_rtsi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case COMEDI_OUTPUT: + case COMEDI_INPUT: + ni_set_rtsi_direction(dev, chan, data[0]); + break; + case INSN_CONFIG_DIO_QUERY: { + int ret = ni_get_rtsi_direction(dev, chan); + + if (ret < 0) + return ret; + data[1] = ret; + return 2; + } + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_set_master_clock(dev, data[1], data[2]); + case INSN_CONFIG_GET_CLOCK_SRC: + data[1] = devpriv->clock_source; + data[2] = devpriv->clock_ns; + return 3; + case INSN_CONFIG_SET_ROUTING: + return ni_set_rtsi_routing(dev, chan, data[1]); + case INSN_CONFIG_GET_ROUTING: { + int ret = ni_get_rtsi_routing(dev, chan); + + if (ret < 0) + return ret; + data[1] = ret; + return 2; + } + default: + return -EINVAL; + } + return 1; +} + +static int ni_rtsi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + + return insn->n; +} + +/* + * Default routing for RTSI trigger lines. + * + * These values are used here in the init function, as well as in the + * disconnect_route function, after a RTSI route has been disconnected. + */ +static const int default_rtsi_routing[] = { + [0] = NI_RTSI_OUTPUT_ADR_START1, + [1] = NI_RTSI_OUTPUT_ADR_START2, + [2] = NI_RTSI_OUTPUT_SCLKG, + [3] = NI_RTSI_OUTPUT_DACUPDN, + [4] = NI_RTSI_OUTPUT_DA_START1, + [5] = NI_RTSI_OUTPUT_G_SRC0, + [6] = NI_RTSI_OUTPUT_G_GATE0, + [7] = NI_RTSI_OUTPUT_RTSI_OSC, +}; + +/* + * Route signals through RGOUT0 terminal. + * @reg: raw register value of RGOUT0 bits (only bit0 is important). + * @dev: comedi device handle. + */ +static void set_rgout0_reg(int reg, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + if (devpriv->is_m_series) { + devpriv->rtsi_trig_direction_reg &= + ~NISTC_RTSI_TRIG_DIR_SUB_SEL1; + devpriv->rtsi_trig_direction_reg |= + (reg << NISTC_RTSI_TRIG_DIR_SUB_SEL1_SHIFT) & + NISTC_RTSI_TRIG_DIR_SUB_SEL1; + ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg, + NISTC_RTSI_TRIG_DIR_REG); + } else { + devpriv->rtsi_trig_b_output_reg &= ~NISTC_RTSI_TRIGB_SUB_SEL1; + devpriv->rtsi_trig_b_output_reg |= + (reg << NISTC_RTSI_TRIGB_SUB_SEL1_SHIFT) & + NISTC_RTSI_TRIGB_SUB_SEL1; + ni_stc_writew(dev, devpriv->rtsi_trig_b_output_reg, + NISTC_RTSI_TRIGB_OUT_REG); + } +} + +static int get_rgout0_reg(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int reg; + + if (devpriv->is_m_series) + reg = (devpriv->rtsi_trig_direction_reg & + NISTC_RTSI_TRIG_DIR_SUB_SEL1) + >> NISTC_RTSI_TRIG_DIR_SUB_SEL1_SHIFT; + else + reg = (devpriv->rtsi_trig_b_output_reg & + NISTC_RTSI_TRIGB_SUB_SEL1) + >> NISTC_RTSI_TRIGB_SUB_SEL1_SHIFT; + return reg; +} + +static inline int get_rgout0_src(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int reg = get_rgout0_reg(dev); + + return ni_find_route_source(reg, NI_RGOUT0, &devpriv->routing_tables); +} + +/* + * Route signals through RGOUT0 terminal and increment the RGOUT0 use for this + * particular route. + * @src: device-global signal name + * @dev: comedi device handle + * + * Return: -EINVAL if the source is not valid to route to RGOUT0; + * -EBUSY if the RGOUT0 is already used; + * 0 if successful. + */ +static int incr_rgout0_src_use(int src, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + s8 reg = ni_lookup_route_register(CR_CHAN(src), NI_RGOUT0, + &devpriv->routing_tables); + + if (reg < 0) + return -EINVAL; + + if (devpriv->rgout0_usage > 0 && get_rgout0_reg(dev) != reg) + return -EBUSY; + + ++devpriv->rgout0_usage; + set_rgout0_reg(reg, dev); + return 0; +} + +/* + * Unroute signals through RGOUT0 terminal and deccrement the RGOUT0 use for + * this particular source. This function does not actually unroute anything + * with respect to RGOUT0. It does, on the other hand, decrement the usage + * counter for the current src->RGOUT0 mapping. + * + * Return: -EINVAL if the source is not already routed to RGOUT0 (or usage is + * already at zero); 0 if successful. + */ +static int decr_rgout0_src_use(int src, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + s8 reg = ni_lookup_route_register(CR_CHAN(src), NI_RGOUT0, + &devpriv->routing_tables); + + if (devpriv->rgout0_usage > 0 && get_rgout0_reg(dev) == reg) { + --devpriv->rgout0_usage; + if (!devpriv->rgout0_usage) + set_rgout0_reg(0, dev); /* ok default? */ + return 0; + } + return -EINVAL; +} + +/* + * Route signals through given NI_RTSI_BRD mux. + * @i: index of mux to route + * @reg: raw register value of RTSI_BRD bits + * @dev: comedi device handle + */ +static void set_ith_rtsi_brd_reg(int i, int reg, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int reg_i_sz = 3; /* value for e-series */ + int reg_i_mask; + int reg_i_shift; + + if (devpriv->is_m_series) + reg_i_sz = 4; + reg_i_mask = ~((~0) << reg_i_sz); + reg_i_shift = i * reg_i_sz; + + /* clear out the current reg_i for ith brd */ + devpriv->rtsi_shared_mux_reg &= ~(reg_i_mask << reg_i_shift); + /* (softcopy) write the new reg_i for ith brd */ + devpriv->rtsi_shared_mux_reg |= (reg & reg_i_mask) << reg_i_shift; + /* (hardcopy) write the new reg_i for ith brd */ + ni_stc_writew(dev, devpriv->rtsi_shared_mux_reg, NISTC_RTSI_BOARD_REG); +} + +static int get_ith_rtsi_brd_reg(int i, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int reg_i_sz = 3; /* value for e-series */ + int reg_i_mask; + int reg_i_shift; + + if (devpriv->is_m_series) + reg_i_sz = 4; + reg_i_mask = ~((~0) << reg_i_sz); + reg_i_shift = i * reg_i_sz; + + return (devpriv->rtsi_shared_mux_reg >> reg_i_shift) & reg_i_mask; +} + +static inline int get_rtsi_brd_src(int brd, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int brd_index = brd; + int reg; + + if (brd >= NI_RTSI_BRD(0)) + brd_index = brd - NI_RTSI_BRD(0); + else + brd = NI_RTSI_BRD(brd); + /* + * And now: + * brd : device-global name + * brd_index : index number of RTSI_BRD mux + */ + + reg = get_ith_rtsi_brd_reg(brd_index, dev); + + return ni_find_route_source(reg, brd, &devpriv->routing_tables); +} + +/* + * Route signals through NI_RTSI_BRD mux and increment the use counter for this + * particular route. + * + * Return: -EINVAL if the source is not valid to route to NI_RTSI_BRD(i); + * -EBUSY if all NI_RTSI_BRD muxes are already used; + * NI_RTSI_BRD(i) of allocated ith mux if successful. + */ +static int incr_rtsi_brd_src_use(int src, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int first_available = -1; + int err = -EINVAL; + s8 reg; + int i; + + /* first look for a mux that is already configured to provide src */ + for (i = 0; i < NUM_RTSI_SHARED_MUXS; ++i) { + reg = ni_lookup_route_register(CR_CHAN(src), NI_RTSI_BRD(i), + &devpriv->routing_tables); + + if (reg < 0) + continue; /* invalid route */ + + if (!devpriv->rtsi_shared_mux_usage[i]) { + if (first_available < 0) + /* found the first unused, but usable mux */ + first_available = i; + } else { + /* + * we've seen at least one possible route, so change the + * final error to -EBUSY in case there are no muxes + * available. + */ + err = -EBUSY; + + if (get_ith_rtsi_brd_reg(i, dev) == reg) { + /* + * we've found a mux that is already being used + * to provide the requested signal. Reuse it. + */ + goto success; + } + } + } + + if (first_available < 0) + return err; + + /* we did not find a mux to reuse, but there is at least one usable */ + i = first_available; + +success: + ++devpriv->rtsi_shared_mux_usage[i]; + set_ith_rtsi_brd_reg(i, reg, dev); + return NI_RTSI_BRD(i); +} + +/* + * Unroute signals through NI_RTSI_BRD mux and decrement the user counter for + * this particular route. + * + * Return: -EINVAL if the source is not already routed to rtsi_brd(i) (or usage + * is already at zero); 0 if successful. + */ +static int decr_rtsi_brd_src_use(int src, int rtsi_brd, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + s8 reg = ni_lookup_route_register(CR_CHAN(src), rtsi_brd, + &devpriv->routing_tables); + const int i = rtsi_brd - NI_RTSI_BRD(0); + + if (devpriv->rtsi_shared_mux_usage[i] > 0 && + get_ith_rtsi_brd_reg(i, dev) == reg) { + --devpriv->rtsi_shared_mux_usage[i]; + if (!devpriv->rtsi_shared_mux_usage[i]) + set_ith_rtsi_brd_reg(i, 0, dev); /* ok default? */ + return 0; + } + + return -EINVAL; +} + +static void ni_rtsi_init(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int i; + + /* Initialises the RTSI bus signal switch to a default state */ + + /* + * 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 + */ + devpriv->clock_and_fout2 = NI_M_CLK_FOUT2_RTSI_10MHZ; + /* Set clock mode to internal */ + if (ni_set_master_clock(dev, NI_MIO_INTERNAL_CLOCK, 0) < 0) + dev_err(dev->class_dev, "ni_set_master_clock failed, bug?\n"); + + /* default internal lines routing to RTSI bus lines */ + for (i = 0; i < 8; ++i) { + ni_set_rtsi_direction(dev, i, COMEDI_INPUT); + ni_set_rtsi_routing(dev, i, default_rtsi_routing[i]); + } + + /* + * Sets the source and direction of the 4 on board lines. + * This configures all board lines to be: + * for e-series: + * 1) inputs (not sure what "output" would mean) + * 2) copying TRIGGER_LINE(0) (or RTSI0) output + * for m-series: + * copying NI_PFI(0) output + */ + devpriv->rtsi_shared_mux_reg = 0; + for (i = 0; i < 4; ++i) + set_ith_rtsi_brd_reg(i, 0, dev); + memset(devpriv->rtsi_shared_mux_usage, 0, + sizeof(devpriv->rtsi_shared_mux_usage)); + + /* initialize rgout0 pin as unused. */ + devpriv->rgout0_usage = 0; + set_rgout0_reg(0, dev); +} + +/* Get route of GPFO_i/CtrOut pins */ +static inline int ni_get_gout_routing(unsigned int dest, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + unsigned int reg = devpriv->an_trig_etc_reg; + + switch (dest) { + case 0: + if (reg & NISTC_ATRIG_ETC_GPFO_0_ENA) + return NISTC_ATRIG_ETC_GPFO_0_SEL_TO_SRC(reg); + break; + case 1: + if (reg & NISTC_ATRIG_ETC_GPFO_1_ENA) + return NISTC_ATRIG_ETC_GPFO_1_SEL_TO_SRC(reg); + break; + } + + return -EINVAL; +} + +/* Set route of GPFO_i/CtrOut pins */ +static inline int ni_disable_gout_routing(unsigned int dest, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + switch (dest) { + case 0: + devpriv->an_trig_etc_reg &= ~NISTC_ATRIG_ETC_GPFO_0_ENA; + break; + case 1: + devpriv->an_trig_etc_reg &= ~NISTC_ATRIG_ETC_GPFO_1_ENA; + break; + default: + return -EINVAL; + } + + ni_stc_writew(dev, devpriv->an_trig_etc_reg, NISTC_ATRIG_ETC_REG); + return 0; +} + +/* Set route of GPFO_i/CtrOut pins */ +static inline int ni_set_gout_routing(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + switch (dest) { + case 0: + /* clear reg */ + devpriv->an_trig_etc_reg &= ~NISTC_ATRIG_ETC_GPFO_0_SEL(-1); + /* set reg */ + devpriv->an_trig_etc_reg |= NISTC_ATRIG_ETC_GPFO_0_ENA + | NISTC_ATRIG_ETC_GPFO_0_SEL(src); + break; + case 1: + /* clear reg */ + devpriv->an_trig_etc_reg &= ~NISTC_ATRIG_ETC_GPFO_1_SEL; + src = src ? NISTC_ATRIG_ETC_GPFO_1_SEL : 0; + /* set reg */ + devpriv->an_trig_etc_reg |= NISTC_ATRIG_ETC_GPFO_1_ENA | src; + break; + default: + return -EINVAL; + } + + ni_stc_writew(dev, devpriv->an_trig_etc_reg, NISTC_ATRIG_ETC_REG); + return 0; +} + +/* + * Retrieves the current source of the output selector for the given + * destination. If the terminal for the destination is not already configured + * as an output, this function returns -EINVAL as error. + * + * Return: the register value of the destination output selector; + * -EINVAL if terminal is not configured for output. + */ +static int get_output_select_source(int dest, struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + int reg = -1; + + if (channel_is_pfi(dest)) { + if (ni_get_pfi_direction(dev, dest) == COMEDI_OUTPUT) + reg = ni_get_pfi_routing(dev, dest); + } else if (channel_is_rtsi(dest)) { + if (ni_get_rtsi_direction(dev, dest) == COMEDI_OUTPUT) { + reg = ni_get_rtsi_routing(dev, dest); + + if (reg == NI_RTSI_OUTPUT_RGOUT0) { + dest = NI_RGOUT0; /* prepare for lookup below */ + reg = get_rgout0_reg(dev); + } else if (reg >= NI_RTSI_OUTPUT_RTSI_BRD(0) && + reg <= NI_RTSI_OUTPUT_RTSI_BRD(3)) { + const int i = reg - NI_RTSI_OUTPUT_RTSI_BRD(0); + + dest = NI_RTSI_BRD(i); /* prepare for lookup */ + reg = get_ith_rtsi_brd_reg(i, dev); + } + } + } else if (dest >= NI_CtrOut(0) && dest <= NI_CtrOut(-1)) { + /* + * not handled by ni_tio. Only available for GPFO registers in + * e/m series. + */ + dest -= NI_CtrOut(0); + if (dest > 1) + /* there are only two g_out outputs. */ + return -EINVAL; + reg = ni_get_gout_routing(dest, dev); + } else if (channel_is_ctr(dest)) { + reg = ni_tio_get_routing(devpriv->counter_dev, dest); + } else { + dev_dbg(dev->class_dev, "%s: unhandled destination (%d) queried\n", + __func__, dest); + } + + if (reg >= 0) + return ni_find_route_source(CR_CHAN(reg), dest, + &devpriv->routing_tables); + return -EINVAL; +} + +/* + * Test a route: + * + * Return: -1 if not connectible; + * 0 if connectible and not connected; + * 1 if connectible and connected. + */ +static int test_route(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + s8 reg = ni_route_to_register(CR_CHAN(src), dest, + &devpriv->routing_tables); + + if (reg < 0) + return -1; + if (get_output_select_source(dest, dev) != CR_CHAN(src)) + return 0; + return 1; +} + +/* Connect the actual route. */ +static int connect_route(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + s8 reg = ni_route_to_register(CR_CHAN(src), dest, + &devpriv->routing_tables); + s8 current_src; + + if (reg < 0) + /* route is not valid */ + return -EINVAL; + + current_src = get_output_select_source(dest, dev); + if (current_src == CR_CHAN(src)) + return -EALREADY; + if (current_src >= 0) + /* destination mux is already busy. complain, don't overwrite */ + return -EBUSY; + + /* The route is valid and available. Now connect... */ + if (channel_is_pfi(dest)) { + /* set routing source, then open output */ + ni_set_pfi_routing(dev, dest, reg); + ni_set_pfi_direction(dev, dest, COMEDI_OUTPUT); + } else if (channel_is_rtsi(dest)) { + if (reg == NI_RTSI_OUTPUT_RGOUT0) { + int ret = incr_rgout0_src_use(src, dev); + + if (ret < 0) + return ret; + } else if (ni_rtsi_route_requires_mux(reg)) { + /* Attempt to allocate and route (src->brd) */ + int brd = incr_rtsi_brd_src_use(src, dev); + + if (brd < 0) + return brd; + + /* Now lookup the register value for (brd->dest) */ + reg = ni_lookup_route_register( + brd, dest, &devpriv->routing_tables); + } + + ni_set_rtsi_direction(dev, dest, COMEDI_OUTPUT); + ni_set_rtsi_routing(dev, dest, reg); + } else if (dest >= NI_CtrOut(0) && dest <= NI_CtrOut(-1)) { + /* + * not handled by ni_tio. Only available for GPFO registers in + * e/m series. + */ + dest -= NI_CtrOut(0); + if (dest > 1) + /* there are only two g_out outputs. */ + return -EINVAL; + if (ni_set_gout_routing(src, dest, dev)) + return -EINVAL; + } else if (channel_is_ctr(dest)) { + /* + * we are adding back the channel modifier info to set + * invert/edge info passed by the user + */ + ni_tio_set_routing(devpriv->counter_dev, dest, + reg | (src & ~CR_CHAN(-1))); + } else { + return -EINVAL; + } + return 0; +} + +static int disconnect_route(unsigned int src, unsigned int dest, + struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + s8 reg = ni_route_to_register(CR_CHAN(src), dest, + &devpriv->routing_tables); + + if (reg < 0) + /* route is not valid */ + return -EINVAL; + if (get_output_select_source(dest, dev) != src) + /* cannot disconnect something not connected */ + return -EINVAL; + + /* The route is valid and is connected. Now disconnect... */ + if (channel_is_pfi(dest)) { + /* set the pfi to high impedance, and disconnect */ + ni_set_pfi_direction(dev, dest, COMEDI_INPUT); + ni_set_pfi_routing(dev, dest, NI_PFI_OUTPUT_PFI_DEFAULT); + } else if (channel_is_rtsi(dest)) { + if (reg == NI_RTSI_OUTPUT_RGOUT0) { + int ret = decr_rgout0_src_use(src, dev); + + if (ret < 0) + return ret; + } else if (ni_rtsi_route_requires_mux(reg)) { + /* find which RTSI_BRD line is source for rtsi pin */ + int brd = ni_find_route_source( + ni_get_rtsi_routing(dev, dest), dest, + &devpriv->routing_tables); + + if (brd < 0) + return brd; + + /* decrement/disconnect RTSI_BRD line from source */ + decr_rtsi_brd_src_use(src, brd, dev); + } + + /* set rtsi output selector to default state */ + reg = default_rtsi_routing[dest - TRIGGER_LINE(0)]; + ni_set_rtsi_direction(dev, dest, COMEDI_INPUT); + ni_set_rtsi_routing(dev, dest, reg); + } else if (dest >= NI_CtrOut(0) && dest <= NI_CtrOut(-1)) { + /* + * not handled by ni_tio. Only available for GPFO registers in + * e/m series. + */ + dest -= NI_CtrOut(0); + if (dest > 1) + /* there are only two g_out outputs. */ + return -EINVAL; + reg = ni_disable_gout_routing(dest, dev); + } else if (channel_is_ctr(dest)) { + ni_tio_unset_routing(devpriv->counter_dev, dest); + } else { + return -EINVAL; + } + return 0; +} + +static int ni_global_insn_config(struct comedi_device *dev, + struct comedi_insn *insn, + unsigned int *data) +{ + switch (data[0]) { + case INSN_DEVICE_CONFIG_TEST_ROUTE: + data[0] = test_route(data[1], data[2], dev); + return 2; + case INSN_DEVICE_CONFIG_CONNECT_ROUTE: + return connect_route(data[1], data[2], dev); + case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE: + return disconnect_route(data[1], data[2], dev); + /* + * This case is already handled one level up. + * case INSN_DEVICE_CONFIG_GET_ROUTES: + */ + default: + return -EINVAL; + } + return 1; +} + +#ifdef PCIDMA +static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + int retval; + + retval = ni_request_gpct_mite_channel(dev, counter->counter_index, + COMEDI_INPUT); + if (retval) { + dev_err(dev->class_dev, + "no dma channel available for use by counter\n"); + return retval; + } + ni_tio_acknowledge(counter); + ni_e_series_enable_second_irq(dev, counter->counter_index, 1); + + return ni_tio_cmd(dev, s); +} + +static int ni_gpct_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + int retval; + + retval = ni_tio_cancel(counter); + ni_e_series_enable_second_irq(dev, counter->counter_index, 0); + ni_release_gpct_mite_channel(dev, counter->counter_index); + return retval; +} +#endif + +static irqreturn_t ni_E_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s_ai = dev->read_subdev; + struct comedi_subdevice *s_ao = dev->write_subdev; + unsigned short a_status; + unsigned short b_status; + unsigned long flags; +#ifdef PCIDMA + struct ni_private *devpriv = dev->private; +#endif + + if (!dev->attached) + return IRQ_NONE; + smp_mb(); /* make sure dev->attached is checked */ + + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&dev->spinlock, flags); + a_status = ni_stc_readw(dev, NISTC_AI_STATUS1_REG); + b_status = ni_stc_readw(dev, NISTC_AO_STATUS1_REG); +#ifdef PCIDMA + if (devpriv->mite) { + unsigned long flags_too; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags_too); + if (s_ai && devpriv->ai_mite_chan) + mite_ack_linkc(devpriv->ai_mite_chan, s_ai, false); + if (s_ao && devpriv->ao_mite_chan) + mite_ack_linkc(devpriv->ao_mite_chan, s_ao, false); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags_too); + } +#endif + ack_a_interrupt(dev, a_status); + ack_b_interrupt(dev, b_status); + if (s_ai) { + if (a_status & NISTC_AI_STATUS1_INTA) + handle_a_interrupt(dev, s_ai, a_status); + /* handle any interrupt or dma events */ + comedi_handle_events(dev, s_ai); + } + if (s_ao) { + if (b_status & NISTC_AO_STATUS1_INTB) + handle_b_interrupt(dev, s_ao, b_status); + /* handle any interrupt or dma events */ + comedi_handle_events(dev, s_ao); + } + handle_gpct_interrupt(dev, 0); + handle_gpct_interrupt(dev, 1); +#ifdef PCIDMA + if (devpriv->is_m_series) + handle_cdio_interrupt(dev); +#endif + + spin_unlock_irqrestore(&dev->spinlock, flags); + return IRQ_HANDLED; +} + +static int ni_alloc_private(struct comedi_device *dev) +{ + struct ni_private *devpriv; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + spin_lock_init(&devpriv->window_lock); + spin_lock_init(&devpriv->soft_reg_copy_lock); + spin_lock_init(&devpriv->mite_channel_lock); + + return 0; +} + +static unsigned int _ni_get_valid_routes(struct comedi_device *dev, + unsigned int n_pairs, + unsigned int *pair_data) +{ + struct ni_private *devpriv = dev->private; + + return ni_get_valid_routes(&devpriv->routing_tables, n_pairs, + pair_data); +} + +static int ni_E_init(struct comedi_device *dev, + unsigned int interrupt_pin, unsigned int irq_polarity) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + struct comedi_subdevice *s; + int ret; + int i; + const char *dev_family = devpriv->is_m_series ? "ni_mseries" + : "ni_eseries"; + + /* prepare the device for globally-named routes. */ + if (ni_assign_device_routes(dev_family, board->name, + board->alt_route_name, + &devpriv->routing_tables) < 0) { + dev_warn(dev->class_dev, "%s: %s device has no signal routing table.\n", + __func__, board->name); + dev_warn(dev->class_dev, "%s: High level NI signal names will not be available for this %s board.\n", + __func__, board->name); + } else { + /* + * only(?) assign insn_device_config if we have global names for + * this device. + */ + dev->insn_device_config = ni_global_insn_config; + dev->get_valid_routes = _ni_get_valid_routes; + } + + if (board->n_aochan > MAX_N_AO_CHAN) { + dev_err(dev->class_dev, "bug! n_aochan > MAX_N_AO_CHAN\n"); + return -EINVAL; + } + + /* initialize clock dividers */ + devpriv->clock_and_fout = NISTC_CLK_FOUT_SLOW_DIV2 | + NISTC_CLK_FOUT_SLOW_TIMEBASE | + NISTC_CLK_FOUT_TO_BOARD_DIV2 | + NISTC_CLK_FOUT_TO_BOARD; + if (!devpriv->is_6xxx) { + /* BEAM is this needed for PCI-6143 ?? */ + devpriv->clock_and_fout |= (NISTC_CLK_FOUT_AI_OUT_DIV2 | + NISTC_CLK_FOUT_AO_OUT_DIV2); + } + ni_stc_writew(dev, devpriv->clock_and_fout, NISTC_CLK_FOUT_REG); + + ret = comedi_alloc_subdevices(dev, NI_NUM_SUBDEVICES); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[NI_AI_SUBDEV]; + if (board->n_adchan) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_DITHER; + if (!devpriv->is_611x) + s->subdev_flags |= SDF_GROUND | SDF_COMMON | SDF_OTHER; + if (board->ai_maxdata > 0xffff) + s->subdev_flags |= SDF_LSAMPL; + if (devpriv->is_m_series) + s->subdev_flags |= SDF_SOFT_CALIBRATED; + s->n_chan = board->n_adchan; + s->maxdata = board->ai_maxdata; + s->range_table = ni_range_lkup[board->gainlkup]; + s->insn_read = ni_ai_insn_read; + s->insn_config = ni_ai_insn_config; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 512; + s->do_cmdtest = ni_ai_cmdtest; + s->do_cmd = ni_ai_cmd; + s->cancel = ni_ai_reset; + s->poll = ni_ai_poll; + s->munge = ni_ai_munge; + + if (devpriv->mite) + s->async_dma_dir = DMA_FROM_DEVICE; + } + + /* reset the analog input configuration */ + ni_ai_reset(dev, s); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[NI_AO_SUBDEV]; + if (board->n_aochan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_DEGLITCH | SDF_GROUND; + if (devpriv->is_m_series) + s->subdev_flags |= SDF_SOFT_CALIBRATED; + s->n_chan = board->n_aochan; + s->maxdata = board->ao_maxdata; + s->range_table = board->ao_range_table; + s->insn_config = ni_ao_insn_config; + s->insn_write = ni_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* + * Along with the IRQ we need either a FIFO or DMA for + * async command support. + */ + if (dev->irq && (board->ao_fifo_depth || devpriv->mite)) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->len_chanlist = s->n_chan; + s->do_cmdtest = ni_ao_cmdtest; + s->do_cmd = ni_ao_cmd; + s->cancel = ni_ao_reset; + if (!devpriv->is_m_series) + s->munge = ni_ao_munge; + + if (devpriv->mite) + s->async_dma_dir = DMA_TO_DEVICE; + } + + if (devpriv->is_67xx) + init_ao_67xx(dev, s); + + /* reset the analog output configuration */ + ni_ao_reset(dev, s); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[NI_DIO_SUBDEV]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = board->has_32dio_chan ? 32 : 8; + s->maxdata = 1; + s->range_table = &range_digital; + if (devpriv->is_m_series) { +#ifdef PCIDMA + s->subdev_flags |= SDF_LSAMPL; + s->insn_bits = ni_m_series_dio_insn_bits; + s->insn_config = ni_m_series_dio_insn_config; + if (dev->irq) { + s->subdev_flags |= SDF_CMD_WRITE /* | SDF_CMD_READ */; + s->len_chanlist = s->n_chan; + s->do_cmdtest = ni_cdio_cmdtest; + s->do_cmd = ni_cdio_cmd; + s->cancel = ni_cdio_cancel; + + /* M-series boards use DMA */ + s->async_dma_dir = DMA_BIDIRECTIONAL; + } + + /* reset DIO and set all channels to inputs */ + ni_writel(dev, NI_M_CDO_CMD_RESET | + NI_M_CDI_CMD_RESET, + NI_M_CDIO_CMD_REG); + ni_writel(dev, s->io_bits, NI_M_DIO_DIR_REG); +#endif /* PCIDMA */ + } else { + s->insn_bits = ni_dio_insn_bits; + s->insn_config = ni_dio_insn_config; + + /* set all channels to inputs */ + devpriv->dio_control = NISTC_DIO_CTRL_DIR(s->io_bits); + ni_writew(dev, devpriv->dio_control, NISTC_DIO_CTRL_REG); + } + + /* 8255 device */ + s = &dev->subdevices[NI_8255_DIO_SUBDEV]; + if (board->has_8255) { + ret = subdev_8255_init(dev, s, ni_8255_callback, + NI_E_8255_BASE); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* formerly general purpose counter/timer device, but no longer used */ + s = &dev->subdevices[NI_UNUSED_SUBDEV]; + s->type = COMEDI_SUBD_UNUSED; + + /* Calibration subdevice */ + s = &dev->subdevices[NI_CALIBRATION_SUBDEV]; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_INTERNAL; + s->n_chan = 1; + s->maxdata = 0; + if (devpriv->is_m_series) { + /* internal PWM output used for AI nonlinearity calibration */ + s->insn_config = ni_m_series_pwm_config; + + ni_writel(dev, 0x0, NI_M_CAL_PWM_REG); + } else if (devpriv->is_6143) { + /* internal PWM output used for AI nonlinearity calibration */ + s->insn_config = ni_6143_pwm_config; + } else { + s->subdev_flags |= SDF_WRITABLE; + s->insn_read = ni_calib_insn_read; + s->insn_write = ni_calib_insn_write; + + /* setup the caldacs and find the real n_chan and maxdata */ + caldac_setup(dev, s); + } + + /* EEPROM subdevice */ + s = &dev->subdevices[NI_EEPROM_SUBDEV]; + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->maxdata = 0xff; + if (devpriv->is_m_series) { + s->n_chan = M_SERIES_EEPROM_SIZE; + s->insn_read = ni_m_series_eeprom_insn_read; + } else { + s->n_chan = 512; + s->insn_read = ni_eeprom_insn_read; + } + + /* Digital I/O (PFI) subdevice */ + s = &dev->subdevices[NI_PFI_DIO_SUBDEV]; + s->type = COMEDI_SUBD_DIO; + s->maxdata = 1; + if (devpriv->is_m_series) { + s->n_chan = 16; + s->insn_bits = ni_pfi_insn_bits; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + + ni_writew(dev, s->state, NI_M_PFI_DO_REG); + for (i = 0; i < NUM_PFI_OUTPUT_SELECT_REGS; ++i) { + ni_writew(dev, devpriv->pfi_output_select_reg[i], + NI_M_PFI_OUT_SEL_REG(i)); + } + } else { + s->n_chan = 10; + s->subdev_flags = SDF_INTERNAL; + } + s->insn_config = ni_pfi_insn_config; + + ni_set_bits(dev, NISTC_IO_BIDIR_PIN_REG, ~0, 0); + + /* cs5529 calibration adc */ + s = &dev->subdevices[NI_CS5529_CALIBRATION_SUBDEV]; + if (devpriv->is_67xx) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_INTERNAL; + /* one channel for each analog output channel */ + s->n_chan = board->n_aochan; + s->maxdata = BIT(16) - 1; + s->range_table = &range_unknown; /* XXX */ + s->insn_read = cs5529_ai_insn_read; + s->insn_config = NULL; + init_cs5529(dev); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Serial */ + s = &dev->subdevices[NI_SERIAL_SUBDEV]; + s->type = COMEDI_SUBD_SERIAL; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 1; + s->maxdata = 0xff; + s->insn_config = ni_serial_insn_config; + devpriv->serial_interval_ns = 0; + devpriv->serial_hw_mode = 0; + + /* RTSI */ + s = &dev->subdevices[NI_RTSI_SUBDEV]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 8; + s->maxdata = 1; + s->insn_bits = ni_rtsi_insn_bits; + s->insn_config = ni_rtsi_insn_config; + ni_rtsi_init(dev); + + /* allocate and initialize the gpct counter device */ + devpriv->counter_dev = ni_gpct_device_construct(dev, + ni_gpct_write_register, + ni_gpct_read_register, + (devpriv->is_m_series) + ? ni_gpct_variant_m_series + : ni_gpct_variant_e_series, + NUM_GPCT, + NUM_GPCT, + &devpriv->routing_tables); + if (!devpriv->counter_dev) + return -ENOMEM; + + /* Counter (gpct) subdevices */ + for (i = 0; i < NUM_GPCT; ++i) { + struct ni_gpct *gpct = &devpriv->counter_dev->counters[i]; + + /* setup and initialize the counter */ + ni_tio_init_counter(gpct); + + s = &dev->subdevices[NI_GPCT_SUBDEV(i)]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; + s->n_chan = 3; + s->maxdata = (devpriv->is_m_series) ? 0xffffffff + : 0x00ffffff; + s->insn_read = ni_tio_insn_read; + s->insn_write = ni_tio_insn_write; + s->insn_config = ni_tio_insn_config; +#ifdef PCIDMA + if (dev->irq && devpriv->mite) { + s->subdev_flags |= SDF_CMD_READ /* | SDF_CMD_WRITE */; + s->len_chanlist = 1; + s->do_cmdtest = ni_tio_cmdtest; + s->do_cmd = ni_gpct_cmd; + s->cancel = ni_gpct_cancel; + + s->async_dma_dir = DMA_BIDIRECTIONAL; + } +#endif + s->private = gpct; + } + + /* Initialize GPFO_{0,1} to produce output of counters */ + ni_set_gout_routing(0, 0, dev); /* output of counter 0; DAQ STC, p338 */ + ni_set_gout_routing(0, 1, dev); /* output of counter 1; DAQ STC, p338 */ + + /* Frequency output subdevice */ + s = &dev->subdevices[NI_FREQ_OUT_SUBDEV]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 1; + s->maxdata = 0xf; + s->insn_read = ni_freq_out_insn_read; + s->insn_write = ni_freq_out_insn_write; + s->insn_config = ni_freq_out_insn_config; + + if (dev->irq) { + ni_stc_writew(dev, + (irq_polarity ? NISTC_INT_CTRL_INT_POL : 0) | + (NISTC_INT_CTRL_3PIN_INT & 0) | + NISTC_INT_CTRL_INTA_ENA | + NISTC_INT_CTRL_INTB_ENA | + NISTC_INT_CTRL_INTA_SEL(interrupt_pin) | + NISTC_INT_CTRL_INTB_SEL(interrupt_pin), + NISTC_INT_CTRL_REG); + } + + /* DMA setup */ + ni_writeb(dev, devpriv->ai_ao_select_reg, NI_E_DMA_AI_AO_SEL_REG); + ni_writeb(dev, devpriv->g0_g1_select_reg, NI_E_DMA_G0_G1_SEL_REG); + + if (devpriv->is_6xxx) { + ni_writeb(dev, 0, NI611X_MAGIC_REG); + } else if (devpriv->is_m_series) { + int channel; + + for (channel = 0; channel < board->n_aochan; ++channel) { + ni_writeb(dev, 0xf, + NI_M_AO_WAVEFORM_ORDER_REG(channel)); + ni_writeb(dev, 0x0, + NI_M_AO_REF_ATTENUATION_REG(channel)); + } + ni_writeb(dev, 0x0, NI_M_AO_CALIB_REG); + } + + return 0; +} + +static void mio_common_detach(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + if (devpriv) + ni_gpct_device_destroy(devpriv->counter_dev); +} diff --git a/drivers/comedi/drivers/ni_mio_cs.c b/drivers/comedi/drivers/ni_mio_cs.c new file mode 100644 index 000000000..796f0b743 --- /dev/null +++ b/drivers/comedi/drivers/ni_mio_cs.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi 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> + */ + +/* + * Driver: ni_mio_cs + * Description: National Instruments DAQCard E series + * Author: ds + * Status: works + * Devices: [National Instruments] DAQCard-AI-16XE-50 (ni_mio_cs), + * DAQCard-AI-16E-4, DAQCard-6062E, DAQCard-6024E, DAQCard-6036E + * Updated: Thu Oct 23 19:43:17 CDT 2003 + * + * See the notes in the ni_atmio.o driver. + */ + +/* + * The real guts of the driver is in ni_mio_common.c, which is + * included by all the E series drivers. + * + * References for specifications: + * 341080a.pdf DAQCard E Series Register Level Programmer Manual + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/comedi/comedi_pcmcia.h> +#include <linux/comedi/comedi_8255.h> + +#include "ni_stc.h" + +/* + * AT specific setup + */ + +static const struct ni_board_struct ni_boards[] = { + { + .name = "DAQCard-ai-16xe-50", + .device_id = 0x010d, + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_8, + .ai_speed = 5000, + .caldac = { dac8800, dac8043 }, + }, { + .name = "DAQCard-ai-16e-4", + .device_id = 0x010c, + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_16, + .ai_speed = 4000, + .caldac = { mb88341 }, /* verified */ + }, { + .name = "DAQCard-6062E", + .device_id = 0x02c4, + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_bipolar10, + .ao_speed = 1176, + .caldac = { ad8804_debug }, /* verified */ + }, { + /* specs incorrect! */ + .name = "DAQCard-6024E", + .device_id = 0x075e, + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000000, + .caldac = { ad8804_debug }, + }, { + /* specs incorrect! */ + .name = "DAQCard-6036E", + .device_id = 0x0245, + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 1024, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000000, + .caldac = { ad8804_debug }, + }, +#if 0 + { + .name = "DAQCard-6715", + .device_id = 0x0000, /* unknown */ + .n_aochan = 8, + .ao_maxdata = 0x0fff, + .ao_671x = 8192, + .caldac = { mb88341, mb88341 }, + }, +#endif +}; + +#include "ni_mio_common.c" + +static const void *ni_getboardtype(struct comedi_device *dev, + struct pcmcia_device *link) +{ + static const struct ni_board_struct *board; + int i; + + for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { + board = &ni_boards[i]; + if (board->device_id == link->card_id) + return board; + } + return NULL; +} + +static int mio_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data) +{ + int base, ret; + + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16; + + for (base = 0x000; base < 0x400; base += 0x20) { + p_dev->resource[0]->start = base; + ret = pcmcia_request_io(p_dev); + if (!ret) + return 0; + } + return -ENODEV; +} + +static int mio_cs_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + static const struct ni_board_struct *board; + int ret; + + board = ni_getboardtype(dev, link); + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ; + ret = comedi_pcmcia_enable(dev, mio_pcmcia_config_loop); + if (ret) + return ret; + dev->iobase = link->resource[0]->start; + + link->priv = dev; + ret = pcmcia_request_irq(link, ni_E_interrupt); + if (ret) + return ret; + dev->irq = link->irq; + + ret = ni_alloc_private(dev); + if (ret) + return ret; + + return ni_E_init(dev, 0, 1); +} + +static void mio_cs_detach(struct comedi_device *dev) +{ + mio_common_detach(dev); + comedi_pcmcia_disable(dev); +} + +static struct comedi_driver driver_ni_mio_cs = { + .driver_name = "ni_mio_cs", + .module = THIS_MODULE, + .auto_attach = mio_cs_auto_attach, + .detach = mio_cs_detach, +}; + +static int cs_attach(struct pcmcia_device *link) +{ + return comedi_pcmcia_auto_config(link, &driver_ni_mio_cs); +} + +static const struct pcmcia_device_id ni_mio_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010d), /* DAQCard-ai-16xe-50 */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010c), /* DAQCard-ai-16e-4 */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x02c4), /* DAQCard-6062E */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x075e), /* DAQCard-6024E */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0245), /* DAQCard-6036E */ + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, ni_mio_cs_ids); + +static struct pcmcia_driver ni_mio_cs_driver = { + .name = "ni_mio_cs", + .owner = THIS_MODULE, + .id_table = ni_mio_cs_ids, + .probe = cs_attach, + .remove = comedi_pcmcia_auto_unconfig, +}; +module_comedi_pcmcia_driver(driver_ni_mio_cs, ni_mio_cs_driver); + +MODULE_DESCRIPTION("Comedi driver for National Instruments DAQCard E series"); +MODULE_AUTHOR("David A. Schleef <ds@schleef.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_pcidio.c b/drivers/comedi/drivers/ni_pcidio.c new file mode 100644 index 000000000..2d58e8342 --- /dev/null +++ b/drivers/comedi/drivers/ni_pcidio.c @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi driver for National Instruments PCI-DIO-32HS + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1999,2002 David A. Schleef <ds@schleef.org> + */ + +/* + * 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 + * https://www.comedi.org + */ + +#define USE_DMA + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/comedi/comedi_pci.h> + +#include "mite.h" + +/* defines for the PCI-DIO-32HS */ + +#define WINDOW_ADDRESS 4 /* W */ +#define INTERRUPT_AND_WINDOW_STATUS 4 /* R */ +#define INT_STATUS_1 BIT(0) +#define INT_STATUS_2 BIT(1) +#define WINDOW_ADDRESS_STATUS_MASK 0x7c + +#define MASTER_DMA_AND_INTERRUPT_CONTROL 5 /* W */ +#define INTERRUPT_LINE(x) ((x) & 3) +#define OPEN_INT BIT(2) +#define GROUP_STATUS 5 /* R */ +#define DATA_LEFT BIT(0) +#define REQ BIT(2) +#define STOP_TRIG BIT(3) + +#define GROUP_1_FLAGS 6 /* R */ +#define GROUP_2_FLAGS 7 /* R */ +#define TRANSFER_READY BIT(0) +#define COUNT_EXPIRED BIT(1) +#define WAITED BIT(5) +#define PRIMARY_TC BIT(6) +#define SECONDARY_TC BIT(7) + /* #define SerialRose */ + /* #define ReqRose */ + /* #define Paused */ + +#define GROUP_1_FIRST_CLEAR 6 /* W */ +#define GROUP_2_FIRST_CLEAR 7 /* W */ +#define CLEAR_WAITED BIT(3) +#define CLEAR_PRIMARY_TC BIT(4) +#define CLEAR_SECONDARY_TC BIT(5) +#define DMA_RESET BIT(6) +#define FIFO_RESET BIT(7) +#define CLEAR_ALL 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 RTSI_CLOCKING(x) (((x) & 3) << 4) + +#define GROUP_1_SECOND_CLEAR 46 /* W */ +#define GROUP_2_SECOND_CLEAR 47 /* W */ +#define CLEAR_EXPIRED BIT(0) + +#define PORT_PATTERN(x) (48 + (x)) + +#define DATA_PATH 64 +#define FIFO_ENABLE_A BIT(0) +#define FIFO_ENABLE_B BIT(1) +#define FIFO_ENABLE_C BIT(2) +#define FIFO_ENABLE_D BIT(3) +#define FUNNELING(x) (((x) & 3) << 4) +#define GROUP_DIRECTION BIT(7) + +#define PROTOCOL_REGISTER_1 65 +#define OP_MODE PROTOCOL_REGISTER_1 +#define RUN_MODE(x) ((x) & 7) +#define NUMBERED BIT(3) + +#define PROTOCOL_REGISTER_2 66 +#define CLOCK_REG PROTOCOL_REGISTER_2 +#define CLOCK_LINE(x) (((x) & 3) << 5) +#define INVERT_STOP_TRIG BIT(7) +#define DATA_LATCHING(x) (((x) & 3) << 5) + +#define PROTOCOL_REGISTER_3 67 +#define SEQUENCE PROTOCOL_REGISTER_3 + +#define PROTOCOL_REGISTER_14 68 /* 16 bit */ +#define CLOCK_SPEED PROTOCOL_REGISTER_14 + +#define PROTOCOL_REGISTER_4 70 +#define REQ_REG PROTOCOL_REGISTER_4 +#define REQ_CONDITIONING(x) (((x) & 7) << 3) + +#define PROTOCOL_REGISTER_5 71 +#define BLOCK_MODE PROTOCOL_REGISTER_5 + +#define FIFO_Control 72 +#define READY_LEVEL(x) ((x) & 7) + +#define PROTOCOL_REGISTER_6 73 +#define LINE_POLARITIES PROTOCOL_REGISTER_6 +#define INVERT_ACK BIT(0) +#define INVERT_REQ BIT(1) +#define INVERT_CLOCK BIT(2) +#define INVERT_SERIAL BIT(3) +#define OPEN_ACK BIT(4) +#define OPEN_CLOCK BIT(5) + +#define PROTOCOL_REGISTER_7 74 +#define ACK_SER PROTOCOL_REGISTER_7 +#define ACK_LINE(x) (((x) & 3) << 2) +#define EXCHANGE_PINS BIT(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 int primary_DMAChannel_bits(unsigned int channel) +{ + return channel & 0x3; +} + +static inline unsigned int secondary_DMAChannel_bits(unsigned int channel) +{ + return (channel << 2) & 0xc; +} + +#define TRANSFER_SIZE_CONTROL 77 +#define TRANSFER_WIDTH(x) ((x) & 3) +#define TRANSFER_LENGTH(x) (((x) & 3) << 3) +#define REQUIRE_R_LEVEL BIT(5) + +#define PROTOCOL_REGISTER_15 79 +#define DAQ_OPTIONS PROTOCOL_REGISTER_15 +#define START_SOURCE(x) ((x) & 0x3) +#define INVERT_START BIT(2) +#define STOP_SOURCE(x) (((x) & 0x3) << 3) +#define REQ_START BIT(6) +#define PRE_START BIT(7) + +#define PATTERN_DETECTION 81 +#define DETECTION_METHOD BIT(0) +#define INVERT_MATCH BIT(1) +#define IE_PATTERN_DETECTION BIT(2) + +#define PROTOCOL_REGISTER_9 82 +#define REQ_DELAY PROTOCOL_REGISTER_9 + +#define PROTOCOL_REGISTER_10 83 +#define REQ_NOT_DELAY PROTOCOL_REGISTER_10 + +#define PROTOCOL_REGISTER_11 84 +#define ACK_DELAY PROTOCOL_REGISTER_11 + +#define PROTOCOL_REGISTER_12 85 +#define ACK_NOT_DELAY PROTOCOL_REGISTER_12 + +#define PROTOCOL_REGISTER_13 86 +#define DATA_1_DELAY PROTOCOL_REGISTER_13 + +#define PROTOCOL_REGISTER_8 88 /* 32 bit */ +#define START_DELAY 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 INT_EN (COUNT_EXPIRED | WAITED | PRIMARY_TC | SECONDARY_TC) +#else +#define INT_EN (TRANSFER_READY | COUNT_EXPIRED | WAITED \ + | PRIMARY_TC | SECONDARY_TC) +#endif + +enum nidio_boardid { + BOARD_PCIDIO_32HS, + BOARD_PXI6533, + BOARD_PCI6534, +}; + +struct nidio_board { + const char *name; + unsigned int uses_firmware:1; + unsigned int dio_speed; +}; + +static const struct nidio_board nidio_boards[] = { + [BOARD_PCIDIO_32HS] = { + .name = "pci-dio-32hs", + .dio_speed = 50, + }, + [BOARD_PXI6533] = { + .name = "pxi-6533", + .dio_speed = 50, + }, + [BOARD_PCI6534] = { + .name = "pci-6534", + .uses_firmware = 1, + .dio_speed = 50, + }, +}; + +struct nidio96_private { + struct mite *mite; + int boardtype; + int dio; + unsigned short OP_MODEBits; + struct mite_channel *di_mite_chan; + struct mite_ring *di_mite_ring; + spinlock_t mite_channel_lock; +}; + +static int ni_pcidio_request_di_mite_channel(struct comedi_device *dev) +{ + struct nidio96_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(devpriv->di_mite_chan); + devpriv->di_mite_chan = + mite_request_channel_in_range(devpriv->mite, + devpriv->di_mite_ring, 1, 2); + if (!devpriv->di_mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, "failed to reserve mite dma channel\n"); + return -EBUSY; + } + devpriv->di_mite_chan->dir = COMEDI_INPUT; + writeb(primary_DMAChannel_bits(devpriv->di_mite_chan->channel) | + secondary_DMAChannel_bits(devpriv->di_mite_chan->channel), + dev->mmio + DMA_LINE_CONTROL_GROUP1); + 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_release_channel(devpriv->di_mite_chan); + devpriv->di_mite_chan = NULL; + writeb(primary_DMAChannel_bits(0) | + secondary_DMAChannel_bits(0), + dev->mmio + DMA_LINE_CONTROL_GROUP1); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static int setup_mite_dma(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct nidio96_private *devpriv = dev->private; + int retval; + unsigned long flags; + + retval = ni_pcidio_request_di_mite_channel(dev); + if (retval) + return retval; + + /* write alloc the entire buffer */ + comedi_buf_write_alloc(s, s->async->prealloc_bufsz); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->di_mite_chan) { + mite_prep_dma(devpriv->di_mite_chan, 32, 32); + mite_dma_arm(devpriv->di_mite_chan); + } else { + retval = -EIO; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + return retval; +} + +static int ni_pcidio_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct nidio96_private *devpriv = dev->private; + unsigned long irq_flags; + int count; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + spin_lock(&devpriv->mite_channel_lock); + if (devpriv->di_mite_chan) + mite_sync_dma(devpriv->di_mite_chan, s); + spin_unlock(&devpriv->mite_channel_lock); + count = comedi_buf_n_bytes_ready(s); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + return count; +} + +static irqreturn_t nidio_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct nidio96_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + unsigned int auxdata; + int flags; + int status; + int work = 0; + + /* interrupcions parasites */ + if (!dev->attached) { + /* assume it's from another card */ + return IRQ_NONE; + } + + /* Lock to avoid race with comedi_poll */ + spin_lock(&dev->spinlock); + + status = readb(dev->mmio + INTERRUPT_AND_WINDOW_STATUS); + flags = readb(dev->mmio + GROUP_1_FLAGS); + + spin_lock(&devpriv->mite_channel_lock); + if (devpriv->di_mite_chan) { + mite_ack_linkc(devpriv->di_mite_chan, s, false); + /* XXX need to byteswap sync'ed dma */ + } + spin_unlock(&devpriv->mite_channel_lock); + + while (status & DATA_LEFT) { + work++; + if (work > 20) { + dev_dbg(dev->class_dev, "too much work in interrupt\n"); + writeb(0x00, + dev->mmio + MASTER_DMA_AND_INTERRUPT_CONTROL); + break; + } + + flags &= INT_EN; + + if (flags & TRANSFER_READY) { + while (flags & TRANSFER_READY) { + work++; + if (work > 100) { + dev_dbg(dev->class_dev, + "too much work in interrupt\n"); + writeb(0x00, dev->mmio + + MASTER_DMA_AND_INTERRUPT_CONTROL + ); + goto out; + } + auxdata = readl(dev->mmio + GROUP_1_FIFO); + comedi_buf_write_samples(s, &auxdata, 1); + flags = readb(dev->mmio + GROUP_1_FLAGS); + } + } + + if (flags & COUNT_EXPIRED) { + writeb(CLEAR_EXPIRED, dev->mmio + GROUP_1_SECOND_CLEAR); + async->events |= COMEDI_CB_EOA; + + writeb(0x00, dev->mmio + OP_MODE); + break; + } else if (flags & WAITED) { + writeb(CLEAR_WAITED, dev->mmio + GROUP_1_FIRST_CLEAR); + async->events |= COMEDI_CB_ERROR; + break; + } else if (flags & PRIMARY_TC) { + writeb(CLEAR_PRIMARY_TC, + dev->mmio + GROUP_1_FIRST_CLEAR); + async->events |= COMEDI_CB_EOA; + } else if (flags & SECONDARY_TC) { + writeb(CLEAR_SECONDARY_TC, + dev->mmio + GROUP_1_FIRST_CLEAR); + async->events |= COMEDI_CB_EOA; + } + + flags = readb(dev->mmio + GROUP_1_FLAGS); + status = readb(dev->mmio + INTERRUPT_AND_WINDOW_STATUS); + } + +out: + comedi_handle_events(dev, s); +#if 0 + if (!tag) + writeb(0x03, dev->mmio + MASTER_DMA_AND_INTERRUPT_CONTROL); +#endif + + spin_unlock(&dev->spinlock); + return IRQ_HANDLED; +} + +static int ni_pcidio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { + const struct nidio_board *board = dev->board_ptr; + + /* we don't care about actual channels */ + data[1] = board->dio_speed; + data[2] = 0; + return 0; + } + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + writel(s->io_bits, dev->mmio + PORT_PIN_DIRECTIONS(0)); + + return insn->n; +} + +static int ni_pcidio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + writel(s->state, dev->mmio + PORT_IO(0)); + + data[1] = readl(dev->mmio + PORT_IO(0)); + + return insn->n; +} + +static int ni_pcidio_ns_to_timer(int *nanosec, unsigned int flags) +{ + int divider, base; + + base = TIMER_BASE; + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + divider = DIV_ROUND_CLOSEST(*nanosec, base); + break; + case CMDF_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case CMDF_ROUND_UP: + divider = DIV_ROUND_UP(*nanosec, base); + break; + } + + *nanosec = base * divider; + return divider; +} + +static int ni_pcidio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + +#define MAX_SPEED (TIMER_BASE) /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SPEED); + /* no minimum speed */ + } else { + /* TRIG_EXT */ + /* should be level/edge, hi/lo specification here */ + if ((cmd->scan_begin_arg & ~(CR_EDGE | CR_INVERT)) != 0) { + cmd->scan_begin_arg &= (CR_EDGE | CR_INVERT); + err |= -EINVAL; + } + } + + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + ni_pcidio_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + return 0; +} + +static int ni_pcidio_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct nidio96_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + writeb(devpriv->OP_MODEBits, dev->mmio + OP_MODE); + s->async->inttrig = NULL; + + return 1; +} + +static int ni_pcidio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct nidio96_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + /* XXX configure ports for input */ + writel(0x0000, dev->mmio + PORT_PIN_DIRECTIONS(0)); + + if (1) { + /* enable fifos A B C D */ + writeb(0x0f, dev->mmio + DATA_PATH); + + /* set transfer width a 32 bits */ + writeb(TRANSFER_WIDTH(0) | TRANSFER_LENGTH(0), + dev->mmio + TRANSFER_SIZE_CONTROL); + } else { + writeb(0x03, dev->mmio + DATA_PATH); + writeb(TRANSFER_WIDTH(3) | TRANSFER_LENGTH(0), + dev->mmio + TRANSFER_SIZE_CONTROL); + } + + /* protocol configuration */ + if (cmd->scan_begin_src == TRIG_TIMER) { + /* page 4-5, "input with internal REQs" */ + writeb(0, dev->mmio + OP_MODE); + writeb(0x00, dev->mmio + CLOCK_REG); + writeb(1, dev->mmio + SEQUENCE); + writeb(0x04, dev->mmio + REQ_REG); + writeb(4, dev->mmio + BLOCK_MODE); + writeb(3, dev->mmio + LINE_POLARITIES); + writeb(0xc0, dev->mmio + ACK_SER); + writel(ni_pcidio_ns_to_timer(&cmd->scan_begin_arg, + CMDF_ROUND_NEAREST), + dev->mmio + START_DELAY); + writeb(1, dev->mmio + REQ_DELAY); + writeb(1, dev->mmio + REQ_NOT_DELAY); + writeb(1, dev->mmio + ACK_DELAY); + writeb(0x0b, dev->mmio + ACK_NOT_DELAY); + writeb(0x01, dev->mmio + DATA_1_DELAY); + /* + * manual, page 4-5: + * CLOCK_SPEED comment is incorrectly listed on DAQ_OPTIONS + */ + writew(0, dev->mmio + CLOCK_SPEED); + writeb(0, dev->mmio + DAQ_OPTIONS); + } else { + /* TRIG_EXT */ + /* page 4-5, "input with external REQs" */ + writeb(0, dev->mmio + OP_MODE); + writeb(0x00, dev->mmio + CLOCK_REG); + writeb(0, dev->mmio + SEQUENCE); + writeb(0x00, dev->mmio + REQ_REG); + writeb(4, dev->mmio + BLOCK_MODE); + if (!(cmd->scan_begin_arg & CR_INVERT)) /* Leading Edge */ + writeb(0, dev->mmio + LINE_POLARITIES); + else /* Trailing Edge */ + writeb(2, dev->mmio + LINE_POLARITIES); + writeb(0x00, dev->mmio + ACK_SER); + writel(1, dev->mmio + START_DELAY); + writeb(1, dev->mmio + REQ_DELAY); + writeb(1, dev->mmio + REQ_NOT_DELAY); + writeb(1, dev->mmio + ACK_DELAY); + writeb(0x0C, dev->mmio + ACK_NOT_DELAY); + writeb(0x10, dev->mmio + DATA_1_DELAY); + writew(0, dev->mmio + CLOCK_SPEED); + writeb(0x60, dev->mmio + DAQ_OPTIONS); + } + + if (cmd->stop_src == TRIG_COUNT) { + writel(cmd->stop_arg, + dev->mmio + TRANSFER_COUNT); + } else { + /* XXX */ + } + +#ifdef USE_DMA + writeb(CLEAR_PRIMARY_TC | CLEAR_SECONDARY_TC, + dev->mmio + GROUP_1_FIRST_CLEAR); + + { + int retval = setup_mite_dma(dev, s); + + if (retval) + return retval; + } +#else + writeb(0x00, dev->mmio + DMA_LINE_CONTROL_GROUP1); +#endif + writeb(0x00, dev->mmio + DMA_LINE_CONTROL_GROUP2); + + /* clear and enable interrupts */ + writeb(0xff, dev->mmio + GROUP_1_FIRST_CLEAR); + /* writeb(CLEAR_EXPIRED, dev->mmio+GROUP_1_SECOND_CLEAR); */ + + writeb(INT_EN, dev->mmio + INTERRUPT_CONTROL); + writeb(0x03, dev->mmio + MASTER_DMA_AND_INTERRUPT_CONTROL); + + if (cmd->stop_src == TRIG_NONE) { + devpriv->OP_MODEBits = DATA_LATCHING(0) | RUN_MODE(7); + } else { /* TRIG_TIMER */ + devpriv->OP_MODEBits = NUMBERED | RUN_MODE(7); + } + if (cmd->start_src == TRIG_NOW) { + /* start */ + writeb(devpriv->OP_MODEBits, dev->mmio + OP_MODE); + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + s->async->inttrig = ni_pcidio_inttrig; + } + + return 0; +} + +static int ni_pcidio_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(0x00, dev->mmio + MASTER_DMA_AND_INTERRUPT_CONTROL); + ni_pcidio_release_di_mite_channel(dev); + + return 0; +} + +static int ni_pcidio_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct nidio96_private *devpriv = dev->private; + int ret; + + ret = mite_buf_change(devpriv->di_mite_ring, s); + if (ret < 0) + return ret; + + memset(s->async->prealloc_buf, 0xaa, s->async->prealloc_bufsz); + + return 0; +} + +static int pci_6534_load_fpga(struct comedi_device *dev, + const u8 *data, size_t data_len, + unsigned long context) +{ + static const int timeout = 1000; + int fpga_index = context; + int i; + size_t j; + + writew(0x80 | fpga_index, dev->mmio + Firmware_Control_Register); + writew(0xc0 | fpga_index, dev->mmio + Firmware_Control_Register); + for (i = 0; + (readw(dev->mmio + Firmware_Status_Register) & 0x2) == 0 && + i < timeout; ++i) { + udelay(1); + } + if (i == timeout) { + dev_warn(dev->class_dev, + "ni_pcidio: failed to load fpga %i, waiting for status 0x2\n", + fpga_index); + return -EIO; + } + writew(0x80 | fpga_index, dev->mmio + Firmware_Control_Register); + for (i = 0; + readw(dev->mmio + Firmware_Status_Register) != 0x3 && + i < timeout; ++i) { + udelay(1); + } + if (i == timeout) { + dev_warn(dev->class_dev, + "ni_pcidio: failed to load fpga %i, waiting for status 0x3\n", + fpga_index); + return -EIO; + } + for (j = 0; j + 1 < data_len;) { + unsigned int value = data[j++]; + + value |= data[j++] << 8; + writew(value, dev->mmio + Firmware_Data_Register); + for (i = 0; + (readw(dev->mmio + Firmware_Status_Register) & 0x2) == 0 + && i < timeout; ++i) { + udelay(1); + } + if (i == timeout) { + dev_warn(dev->class_dev, + "ni_pcidio: failed to load word into fpga %i\n", + fpga_index); + return -EIO; + } + if (need_resched()) + schedule(); + } + writew(0x0, dev->mmio + Firmware_Control_Register); + return 0; +} + +static int pci_6534_reset_fpga(struct comedi_device *dev, int fpga_index) +{ + return pci_6534_load_fpga(dev, NULL, 0, fpga_index); +} + +static int pci_6534_reset_fpgas(struct comedi_device *dev) +{ + int ret; + int i; + + writew(0x0, dev->mmio + Firmware_Control_Register); + for (i = 0; i < 3; ++i) { + ret = pci_6534_reset_fpga(dev, i); + if (ret < 0) + break; + } + writew(0x0, dev->mmio + Firmware_Mask_Register); + return ret; +} + +static void pci_6534_init_main_fpga(struct comedi_device *dev) +{ + writel(0, dev->mmio + FPGA_Control1_Register); + writel(0, dev->mmio + FPGA_Control2_Register); + writel(0, dev->mmio + FPGA_SCALS_Counter_Register); + writel(0, dev->mmio + FPGA_SCAMS_Counter_Register); + writel(0, dev->mmio + FPGA_SCBLS_Counter_Register); + writel(0, dev->mmio + FPGA_SCBMS_Counter_Register); +} + +static int pci_6534_upload_firmware(struct comedi_device *dev) +{ + struct nidio96_private *devpriv = dev->private; + static const char *const fw_file[3] = { + FW_PCI_6534_SCARAB_DI, /* loaded into scarab A for DI */ + FW_PCI_6534_SCARAB_DO, /* loaded into scarab B for DO */ + FW_PCI_6534_MAIN, /* loaded into main FPGA */ + }; + int ret; + int n; + + ret = pci_6534_reset_fpgas(dev); + if (ret < 0) + return ret; + /* load main FPGA first, then the two scarabs */ + for (n = 2; n >= 0; n--) { + ret = comedi_load_firmware(dev, &devpriv->mite->pcidev->dev, + fw_file[n], + pci_6534_load_fpga, n); + if (ret == 0 && n == 2) + pci_6534_init_main_fpga(dev); + if (ret < 0) + break; + } + return ret; +} + +static void nidio_reset_board(struct comedi_device *dev) +{ + writel(0, dev->mmio + PORT_IO(0)); + writel(0, dev->mmio + PORT_PIN_DIRECTIONS(0)); + writel(0, dev->mmio + PORT_PIN_MASK(0)); + + /* disable interrupts on board */ + writeb(0, dev->mmio + MASTER_DMA_AND_INTERRUPT_CONTROL); +} + +static int nidio_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct nidio_board *board = NULL; + struct nidio96_private *devpriv; + struct comedi_subdevice *s; + int ret; + unsigned int irq; + + if (context < ARRAY_SIZE(nidio_boards)) + board = &nidio_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + spin_lock_init(&devpriv->mite_channel_lock); + + devpriv->mite = mite_attach(dev, false); /* use win0 */ + if (!devpriv->mite) + return -ENOMEM; + + devpriv->di_mite_ring = mite_alloc_ring(devpriv->mite); + if (!devpriv->di_mite_ring) + return -ENOMEM; + + if (board->uses_firmware) { + ret = pci_6534_upload_firmware(dev); + if (ret < 0) + return ret; + } + + nidio_reset_board(dev); + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + dev_info(dev->class_dev, "%s rev=%d\n", dev->board_name, + readb(dev->mmio + CHIP_VERSION)); + + s = &dev->subdevices[0]; + + dev->read_subdev = s; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | SDF_PACKED | + SDF_CMD_READ; + s->n_chan = 32; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = &ni_pcidio_insn_config; + s->insn_bits = &ni_pcidio_insn_bits; + s->do_cmd = &ni_pcidio_cmd; + s->do_cmdtest = &ni_pcidio_cmdtest; + s->cancel = &ni_pcidio_cancel; + s->len_chanlist = 32; /* XXX */ + s->buf_change = &ni_pcidio_change; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->poll = &ni_pcidio_poll; + + irq = pcidev->irq; + if (irq) { + ret = request_irq(irq, nidio_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = irq; + } + + return 0; +} + +static void nidio_detach(struct comedi_device *dev) +{ + struct nidio96_private *devpriv = dev->private; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->di_mite_ring) { + mite_free_ring(devpriv->di_mite_ring); + devpriv->di_mite_ring = NULL; + } + mite_detach(devpriv->mite); + } + if (dev->mmio) + iounmap(dev->mmio); + comedi_pci_disable(dev); +} + +static struct comedi_driver ni_pcidio_driver = { + .driver_name = "ni_pcidio", + .module = THIS_MODULE, + .auto_attach = nidio_auto_attach, + .detach = nidio_detach, +}; + +static int ni_pcidio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni_pcidio_driver, id->driver_data); +} + +static const struct pci_device_id ni_pcidio_pci_table[] = { + { PCI_VDEVICE(NI, 0x1150), BOARD_PCIDIO_32HS }, + { PCI_VDEVICE(NI, 0x12b0), BOARD_PCI6534 }, + { PCI_VDEVICE(NI, 0x1320), BOARD_PXI6533 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ni_pcidio_pci_table); + +static struct pci_driver ni_pcidio_pci_driver = { + .name = "ni_pcidio", + .id_table = ni_pcidio_pci_table, + .probe = ni_pcidio_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(ni_pcidio_driver, ni_pcidio_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_pcimio.c b/drivers/comedi/drivers/ni_pcimio.c new file mode 100644 index 000000000..0b0553210 --- /dev/null +++ b/drivers/comedi/drivers/ni_pcimio.c @@ -0,0 +1,1475 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Comedi 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> + */ + +/* + * 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, PXI-6220, + * PCI-6221, PXI-6221, PCI-6224, PXI-6224, PCI-6225, PXI-6225, + * PCI-6229, PXI-6229, PCI-6250, PXI-6250, PCI-6251, PXI-6251, + * PCIe-6251, PXIe-6251, PCI-6254, PXI-6254, PCI-6259, PXI-6259, + * PCIe-6259, PXIe-6259, PCI-6280, PXI-6280, PCI-6281, PXI-6281, + * PCI-6284, PXI-6284, PCI-6289, PXI-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, 16 Jan 2017 12:56:04 +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 <linux/comedi/comedi_pci.h> +#include <asm/byteorder.h> + +#include "ni_stc.h" +#include "mite.h" + +#define PCIDMA + +/* + * These are not all the possible ao ranges for 628x boards. + * They can do OFFSET +- REFERENCE where OFFSET can be + * 0V, 5V, APFI<0,1>, or AO<0...3> and RANGE can + * be 10V, 5V, 2V, 1V, APFI<0,1>, AO<0...3>. That's + * 63 different possibilities. An AO channel + * can not act as it's own OFFSET or REFERENCE. + */ +static const struct comedi_lrange range_ni_M_628x_ao = { + 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + RANGE(-5, 15), + UNI_RANGE(10), + RANGE(3, 7), + RANGE(4, 6), + RANGE_ext(-1, 1) + } +}; + +static const struct comedi_lrange range_ni_M_625x_ao = { + 3, { + BIP_RANGE(10), + BIP_RANGE(5), + RANGE_ext(-1, 1) + } +}; + +enum ni_pcimio_boardid { + BOARD_PCIMIO_16XE_50, + BOARD_PCIMIO_16XE_10, + BOARD_PCI6014, + BOARD_PXI6030E, + BOARD_PCIMIO_16E_1, + BOARD_PCIMIO_16E_4, + BOARD_PXI6040E, + BOARD_PCI6031E, + BOARD_PCI6032E, + BOARD_PCI6033E, + BOARD_PCI6071E, + BOARD_PCI6023E, + BOARD_PCI6024E, + BOARD_PCI6025E, + BOARD_PXI6025E, + BOARD_PCI6034E, + BOARD_PCI6035E, + BOARD_PCI6052E, + BOARD_PCI6110, + BOARD_PCI6111, + /* BOARD_PCI6115, */ + /* BOARD_PXI6115, */ + BOARD_PCI6711, + BOARD_PXI6711, + BOARD_PCI6713, + BOARD_PXI6713, + BOARD_PCI6731, + /* BOARD_PXI6731, */ + BOARD_PCI6733, + BOARD_PXI6733, + BOARD_PXI6071E, + BOARD_PXI6070E, + BOARD_PXI6052E, + BOARD_PXI6031E, + BOARD_PCI6036E, + BOARD_PCI6220, + BOARD_PXI6220, + BOARD_PCI6221, + BOARD_PCI6221_37PIN, + BOARD_PXI6221, + BOARD_PCI6224, + BOARD_PXI6224, + BOARD_PCI6225, + BOARD_PXI6225, + BOARD_PCI6229, + BOARD_PXI6229, + BOARD_PCI6250, + BOARD_PXI6250, + BOARD_PCI6251, + BOARD_PXI6251, + BOARD_PCIE6251, + BOARD_PXIE6251, + BOARD_PCI6254, + BOARD_PXI6254, + BOARD_PCI6259, + BOARD_PXI6259, + BOARD_PCIE6259, + BOARD_PXIE6259, + BOARD_PCI6280, + BOARD_PXI6280, + BOARD_PCI6281, + BOARD_PXI6281, + BOARD_PCI6284, + BOARD_PXI6284, + BOARD_PCI6289, + BOARD_PXI6289, + BOARD_PCI6143, + BOARD_PXI6143, +}; + +static const struct ni_board_struct ni_boards[] = { + [BOARD_PCIMIO_16XE_50] = { + .name = "pci-mio-16xe-50", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 2048, + .alwaysdither = 1, + .gainlkup = ai_gain_8, + .ai_speed = 50000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_bipolar10, + .ao_speed = 50000, + .caldac = { dac8800, dac8043 }, + }, + [BOARD_PCIMIO_16XE_10] = { + .name = "pci-mio-16xe-10", /* aka pci-6030E */ + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6014] = { + .name = "pci-6014", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6030E] = { + .name = "pxi-6030e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCIMIO_16E_1] = { + .name = "pci-mio-16e-1", /* aka pci-6070e */ + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { mb88341 }, + }, + [BOARD_PCIMIO_16E_4] = { + .name = "pci-mio-16e-4", /* aka pci-6040e */ + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + /* + * there have been reported problems with + * full speed on this board + */ + .ai_speed = 2000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 512, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { ad8804_debug }, /* doc says mb88341 */ + }, + [BOARD_PXI6040E] = { + .name = "pxi-6040e", + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 512, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { mb88341 }, + }, + [BOARD_PCI6031E] = { + .name = "pci-6031e", + .n_adchan = 64, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6032E] = { + .name = "pci-6032e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6033E] = { + .name = "pci-6033e", + .n_adchan = 64, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6071E] = { + .name = "pci-6071e", + .n_adchan = 64, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6023E] = { + .name = "pci-6023e", + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .caldac = { ad8804_debug }, /* manual is wrong */ + }, + [BOARD_PCI6024E] = { + .name = "pci-6024e", + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .caldac = { ad8804_debug }, /* manual is wrong */ + }, + [BOARD_PCI6025E] = { + .name = "pci-6025e", + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .caldac = { ad8804_debug }, /* manual is wrong */ + .has_8255 = 1, + }, + [BOARD_PXI6025E] = { + .name = "pxi-6025e", + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 100000, + .caldac = { ad8804_debug }, /* manual is wrong */ + .has_8255 = 1, + }, + [BOARD_PCI6034E] = { + .name = "pci-6034e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6035E] = { + .name = "pci-6035e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6052E] = { + .name = "pci-6052e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 3000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 3000, + /* manual is wrong */ + .caldac = { ad8804_debug, ad8804_debug, ad8522 }, + }, + [BOARD_PCI6110] = { + .name = "pci-6110", + .n_adchan = 4, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_611x, + .ai_speed = 200, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .reg_type = ni_reg_611x, + .ao_range_table = &range_bipolar10, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .caldac = { ad8804, ad8804 }, + }, + [BOARD_PCI6111] = { + .name = "pci-6111", + .n_adchan = 2, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_611x, + .ai_speed = 200, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .reg_type = ni_reg_611x, + .ao_range_table = &range_bipolar10, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .caldac = { ad8804, ad8804 }, + }, +#if 0 + /* The 6115 boards probably need their own driver */ + [BOARD_PCI6115] = { /* .device_id = 0x2ed0, */ + .name = "pci-6115", + .n_adchan = 4, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_611x, + .ai_speed = 100, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_671x = 1, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .reg_611x = 1, + /* XXX */ + .caldac = { ad8804_debug, ad8804_debug, ad8804_debug }, + }, +#endif +#if 0 + [BOARD_PXI6115] = { /* .device_id = ????, */ + .name = "pxi-6115", + .n_adchan = 4, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 8192, + .gainlkup = ai_gain_611x, + .ai_speed = 100, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_671x = 1, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .reg_611x = 1, + /* XXX */ + .caldac = { ad8804_debug, ad8804_debug, ad8804_debug }, + }, +#endif + [BOARD_PCI6711] = { + .name = "pci-6711", + .n_aochan = 4, + .ao_maxdata = 0x0fff, + /* data sheet says 8192, but fifo really holds 16384 samples */ + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6711] = { + .name = "pxi-6711", + .n_aochan = 4, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6713] = { + .name = "pci-6713", + .n_aochan = 8, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6713] = { + .name = "pxi-6713", + .n_aochan = 8, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PCI6731] = { + .name = "pci-6731", + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8192, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, +#if 0 + [BOARD_PXI6731] = { /* .device_id = ????, */ + .name = "pxi-6731", + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8192, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_6711, + .caldac = { ad8804_debug }, + }, +#endif + [BOARD_PCI6733] = { + .name = "pci-6733", + .n_aochan = 8, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6733] = { + .name = "pxi-6733", + .n_aochan = 8, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .reg_type = ni_reg_6713, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6071E] = { + .name = "pxi-6071e", + .n_adchan = 64, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6070E] = { + .name = "pxi-6070e", + .n_adchan = 16, + .ai_maxdata = 0x0fff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0x0fff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 1000, + .caldac = { ad8804_debug }, + }, + [BOARD_PXI6052E] = { + .name = "pxi-6052e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 3000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 3000, + .caldac = { mb88341, mb88341, ad8522 }, + }, + [BOARD_PXI6031E] = { + .name = "pxi-6031e", + .n_adchan = 64, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 10000, + .caldac = { dac8800, dac8043, ad8522 }, + }, + [BOARD_PCI6036E] = { + .name = "pci-6036e", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_range_table = &range_bipolar10, + .ao_speed = 100000, + .caldac = { ad8804_debug }, + }, + [BOARD_PCI6220] = { + .name = "pci-6220", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, /* FIXME: guess */ + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .reg_type = ni_reg_622x, + .caldac = { caldac_none }, + }, + [BOARD_PXI6220] = { + .name = "pxi-6220", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 512, /* FIXME: guess */ + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .reg_type = ni_reg_622x, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PCI6221] = { + .name = "pci-6221", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PCI6221_37PIN] = { + .name = "pci-6221_37pin", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .caldac = { caldac_none }, + }, + [BOARD_PXI6221] = { + .name = "pxi-6221", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PCI6224] = { + .name = "pci-6224", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .reg_type = ni_reg_622x, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PXI6224] = { + .name = "pxi-6224", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .reg_type = ni_reg_622x, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PCI6225] = { + .name = "pci-6225", + .n_adchan = 80, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PXI6225] = { + .name = "pxi-6225", + .n_adchan = 80, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PCI6229] = { + .name = "pci-6229", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + }, + [BOARD_PXI6229] = { + .name = "pxi-6229", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_bipolar10, + .reg_type = ni_reg_622x, + .ao_speed = 1200, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 1000, + }, + [BOARD_PCI6250] = { + .name = "pci-6250", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .reg_type = ni_reg_625x, + .caldac = { caldac_none }, + }, + [BOARD_PXI6250] = { + .name = "pxi-6250", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .reg_type = ni_reg_625x, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6251] = { + .name = "pci-6251", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PXI6251] = { + .name = "pxi-6251", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCIE6251] = { + .name = "pcie-6251", + .alt_route_name = "pci-6251", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PXIE6251] = { + .name = "pxie-6251", + .n_adchan = 16, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6254] = { + .name = "pci-6254", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .reg_type = ni_reg_625x, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + }, + [BOARD_PXI6254] = { + .name = "pxi-6254", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .reg_type = ni_reg_625x, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6259] = { + .name = "pci-6259", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + }, + [BOARD_PXI6259] = { + .name = "pxi-6259", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCIE6259] = { + .name = "pcie-6259", + .alt_route_name = "pci-6259", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + }, + [BOARD_PXIE6259] = { + .name = "pxie-6259", + .n_adchan = 32, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_speed = 350, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6280] = { + .name = "pci-6280", + .n_adchan = 16, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .ao_fifo_depth = 8191, + .reg_type = ni_reg_628x, + .caldac = { caldac_none }, + }, + [BOARD_PXI6280] = { + .name = "pxi-6280", + .n_adchan = 16, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .ao_fifo_depth = 8191, + .reg_type = ni_reg_628x, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6281] = { + .name = "pci-6281", + .n_adchan = 16, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_speed = 350, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PXI6281] = { + .name = "pxi-6281", + .n_adchan = 16, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 2, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_speed = 350, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6284] = { + .name = "pci-6284", + .n_adchan = 32, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .reg_type = ni_reg_628x, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + }, + [BOARD_PXI6284] = { + .name = "pxi-6284", + .n_adchan = 32, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .reg_type = ni_reg_628x, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6289] = { + .name = "pci-6289", + .n_adchan = 32, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_speed = 350, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + }, + [BOARD_PXI6289] = { + .name = "pxi-6289", + .n_adchan = 32, + .ai_maxdata = 0x3ffff, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 4, + .ao_maxdata = 0xffff, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_speed = 350, + .has_32dio_chan = 1, + .caldac = { caldac_none }, + .dio_speed = 100, + }, + [BOARD_PCI6143] = { + .name = "pci-6143", + .n_adchan = 8, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_6143, + .ai_speed = 4000, + .reg_type = ni_reg_6143, + .caldac = { ad8804_debug, ad8804_debug }, + }, + [BOARD_PXI6143] = { + .name = "pxi-6143", + .n_adchan = 8, + .ai_maxdata = 0xffff, + .ai_fifo_depth = 1024, + .gainlkup = ai_gain_6143, + .ai_speed = 4000, + .reg_type = ni_reg_6143, + .caldac = { ad8804_debug, ad8804_debug }, + }, +}; + +#include "ni_mio_common.c" + +static int pcimio_ai_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + int ret; + + ret = mite_buf_change(devpriv->ai_mite_ring, s); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_ao_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + int ret; + + ret = mite_buf_change(devpriv->ao_mite_ring, s); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_gpct0_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + int ret; + + ret = mite_buf_change(devpriv->gpct_mite_ring[0], s); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_gpct1_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + int ret; + + ret = mite_buf_change(devpriv->gpct_mite_ring[1], s); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_dio_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_private *devpriv = dev->private; + int ret; + + ret = mite_buf_change(devpriv->cdo_mite_ring, s); + if (ret < 0) + return ret; + + return 0; +} + +static void m_series_init_eeprom_buffer(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + struct mite *mite = devpriv->mite; + resource_size_t daq_phys_addr; + static const int start_cal_eeprom = 0x400; + static const unsigned int window_size = 10; + unsigned int old_iodwbsr_bits; + unsigned int old_iodwbsr1_bits; + unsigned int old_iodwcr1_bits; + int i; + + /* IO Window 1 needs to be temporarily mapped to read the eeprom */ + daq_phys_addr = pci_resource_start(mite->pcidev, 1); + + old_iodwbsr_bits = readl(mite->mmio + MITE_IODWBSR); + old_iodwbsr1_bits = readl(mite->mmio + MITE_IODWBSR_1); + old_iodwcr1_bits = readl(mite->mmio + MITE_IODWCR_1); + writel(0x0, mite->mmio + MITE_IODWBSR); + writel(((0x80 | window_size) | daq_phys_addr), + mite->mmio + MITE_IODWBSR_1); + writel(0x1 | old_iodwcr1_bits, mite->mmio + MITE_IODWCR_1); + writel(0xf, mite->mmio + 0x30); + + for (i = 0; i < M_SERIES_EEPROM_SIZE; ++i) + devpriv->eeprom_buffer[i] = ni_readb(dev, start_cal_eeprom + i); + + writel(old_iodwbsr1_bits, mite->mmio + MITE_IODWBSR_1); + writel(old_iodwbsr_bits, mite->mmio + MITE_IODWBSR); + writel(old_iodwcr1_bits, mite->mmio + MITE_IODWCR_1); + writel(0x0, mite->mmio + 0x30); +} + +static void init_6143(struct comedi_device *dev) +{ + const struct ni_board_struct *board = dev->board_ptr; + struct ni_private *devpriv = dev->private; + + /* Disable interrupts */ + ni_stc_writew(dev, 0, NISTC_INT_CTRL_REG); + + /* Initialise 6143 AI specific bits */ + + /* Set G0,G1 DMA mode to E series version */ + ni_writeb(dev, 0x00, NI6143_MAGIC_REG); + /* Set EOCMode, ADCMode and pipelinedelay */ + ni_writeb(dev, 0x80, NI6143_PIPELINE_DELAY_REG); + /* Set EOC Delay */ + ni_writeb(dev, 0x00, NI6143_EOC_SET_REG); + + /* Set the FIFO half full level */ + ni_writel(dev, board->ai_fifo_depth / 2, NI6143_AI_FIFO_FLAG_REG); + + /* Strobe Relay disable bit */ + devpriv->ai_calib_source_enabled = 0; + ni_writew(dev, devpriv->ai_calib_source | NI6143_CALIB_CHAN_RELAY_OFF, + NI6143_CALIB_CHAN_REG); + ni_writew(dev, devpriv->ai_calib_source, NI6143_CALIB_CHAN_REG); +} + +static void pcimio_detach(struct comedi_device *dev) +{ + struct ni_private *devpriv = dev->private; + + mio_common_detach(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + mite_free_ring(devpriv->ai_mite_ring); + mite_free_ring(devpriv->ao_mite_ring); + mite_free_ring(devpriv->cdo_mite_ring); + mite_free_ring(devpriv->gpct_mite_ring[0]); + mite_free_ring(devpriv->gpct_mite_ring[1]); + mite_detach(devpriv->mite); + } + if (dev->mmio) + iounmap(dev->mmio); + comedi_pci_disable(dev); +} + +static int pcimio_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct ni_board_struct *board = NULL; + struct ni_private *devpriv; + unsigned int irq; + int ret; + + if (context < ARRAY_SIZE(ni_boards)) + board = &ni_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + ret = ni_alloc_private(dev); + if (ret) + return ret; + devpriv = dev->private; + + devpriv->mite = mite_attach(dev, false); /* use win0 */ + if (!devpriv->mite) + return -ENOMEM; + + if (board->reg_type & ni_reg_m_series_mask) + devpriv->is_m_series = 1; + if (board->reg_type & ni_reg_6xxx_mask) + devpriv->is_6xxx = 1; + if (board->reg_type == ni_reg_611x) + devpriv->is_611x = 1; + if (board->reg_type == ni_reg_6143) + devpriv->is_6143 = 1; + if (board->reg_type == ni_reg_622x) + devpriv->is_622x = 1; + if (board->reg_type == ni_reg_625x) + devpriv->is_625x = 1; + if (board->reg_type == ni_reg_628x) + devpriv->is_628x = 1; + if (board->reg_type & ni_reg_67xx_mask) + devpriv->is_67xx = 1; + if (board->reg_type == ni_reg_6711) + devpriv->is_6711 = 1; + if (board->reg_type == ni_reg_6713) + devpriv->is_6713 = 1; + + devpriv->ai_mite_ring = mite_alloc_ring(devpriv->mite); + if (!devpriv->ai_mite_ring) + return -ENOMEM; + devpriv->ao_mite_ring = mite_alloc_ring(devpriv->mite); + if (!devpriv->ao_mite_ring) + return -ENOMEM; + devpriv->cdo_mite_ring = mite_alloc_ring(devpriv->mite); + if (!devpriv->cdo_mite_ring) + return -ENOMEM; + devpriv->gpct_mite_ring[0] = mite_alloc_ring(devpriv->mite); + if (!devpriv->gpct_mite_ring[0]) + return -ENOMEM; + devpriv->gpct_mite_ring[1] = mite_alloc_ring(devpriv->mite); + if (!devpriv->gpct_mite_ring[1]) + return -ENOMEM; + + if (devpriv->is_m_series) + m_series_init_eeprom_buffer(dev); + if (devpriv->is_6143) + init_6143(dev); + + irq = pcidev->irq; + if (irq) { + ret = request_irq(irq, ni_E_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = irq; + } + + ret = ni_E_init(dev, 0, 1); + if (ret < 0) + return ret; + + dev->subdevices[NI_AI_SUBDEV].buf_change = &pcimio_ai_change; + dev->subdevices[NI_AO_SUBDEV].buf_change = &pcimio_ao_change; + dev->subdevices[NI_GPCT_SUBDEV(0)].buf_change = &pcimio_gpct0_change; + dev->subdevices[NI_GPCT_SUBDEV(1)].buf_change = &pcimio_gpct1_change; + dev->subdevices[NI_DIO_SUBDEV].buf_change = &pcimio_dio_change; + + return 0; +} + +static struct comedi_driver ni_pcimio_driver = { + .driver_name = "ni_pcimio", + .module = THIS_MODULE, + .auto_attach = pcimio_auto_attach, + .detach = pcimio_detach, +}; + +static int ni_pcimio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni_pcimio_driver, id->driver_data); +} + +static const struct pci_device_id ni_pcimio_pci_table[] = { + { PCI_VDEVICE(NI, 0x0162), BOARD_PCIMIO_16XE_50 }, /* 0x1620? */ + { PCI_VDEVICE(NI, 0x1170), BOARD_PCIMIO_16XE_10 }, + { PCI_VDEVICE(NI, 0x1180), BOARD_PCIMIO_16E_1 }, + { PCI_VDEVICE(NI, 0x1190), BOARD_PCIMIO_16E_4 }, + { PCI_VDEVICE(NI, 0x11b0), BOARD_PXI6070E }, + { PCI_VDEVICE(NI, 0x11c0), BOARD_PXI6040E }, + { PCI_VDEVICE(NI, 0x11d0), BOARD_PXI6030E }, + { PCI_VDEVICE(NI, 0x1270), BOARD_PCI6032E }, + { PCI_VDEVICE(NI, 0x1330), BOARD_PCI6031E }, + { PCI_VDEVICE(NI, 0x1340), BOARD_PCI6033E }, + { PCI_VDEVICE(NI, 0x1350), BOARD_PCI6071E }, + { PCI_VDEVICE(NI, 0x14e0), BOARD_PCI6110 }, + { PCI_VDEVICE(NI, 0x14f0), BOARD_PCI6111 }, + { PCI_VDEVICE(NI, 0x1580), BOARD_PXI6031E }, + { PCI_VDEVICE(NI, 0x15b0), BOARD_PXI6071E }, + { PCI_VDEVICE(NI, 0x1880), BOARD_PCI6711 }, + { PCI_VDEVICE(NI, 0x1870), BOARD_PCI6713 }, + { PCI_VDEVICE(NI, 0x18b0), BOARD_PCI6052E }, + { PCI_VDEVICE(NI, 0x18c0), BOARD_PXI6052E }, + { PCI_VDEVICE(NI, 0x2410), BOARD_PCI6733 }, + { PCI_VDEVICE(NI, 0x2420), BOARD_PXI6733 }, + { PCI_VDEVICE(NI, 0x2430), BOARD_PCI6731 }, + { PCI_VDEVICE(NI, 0x2890), BOARD_PCI6036E }, + { PCI_VDEVICE(NI, 0x28c0), BOARD_PCI6014 }, + { PCI_VDEVICE(NI, 0x2a60), BOARD_PCI6023E }, + { PCI_VDEVICE(NI, 0x2a70), BOARD_PCI6024E }, + { PCI_VDEVICE(NI, 0x2a80), BOARD_PCI6025E }, + { PCI_VDEVICE(NI, 0x2ab0), BOARD_PXI6025E }, + { PCI_VDEVICE(NI, 0x2b80), BOARD_PXI6713 }, + { PCI_VDEVICE(NI, 0x2b90), BOARD_PXI6711 }, + { PCI_VDEVICE(NI, 0x2c80), BOARD_PCI6035E }, + { PCI_VDEVICE(NI, 0x2ca0), BOARD_PCI6034E }, + { PCI_VDEVICE(NI, 0x70aa), BOARD_PCI6229 }, + { PCI_VDEVICE(NI, 0x70ab), BOARD_PCI6259 }, + { PCI_VDEVICE(NI, 0x70ac), BOARD_PCI6289 }, + { PCI_VDEVICE(NI, 0x70ad), BOARD_PXI6251 }, + { PCI_VDEVICE(NI, 0x70ae), BOARD_PXI6220 }, + { PCI_VDEVICE(NI, 0x70af), BOARD_PCI6221 }, + { PCI_VDEVICE(NI, 0x70b0), BOARD_PCI6220 }, + { PCI_VDEVICE(NI, 0x70b1), BOARD_PXI6229 }, + { PCI_VDEVICE(NI, 0x70b2), BOARD_PXI6259 }, + { PCI_VDEVICE(NI, 0x70b3), BOARD_PXI6289 }, + { PCI_VDEVICE(NI, 0x70b4), BOARD_PCI6250 }, + { PCI_VDEVICE(NI, 0x70b5), BOARD_PXI6221 }, + { PCI_VDEVICE(NI, 0x70b6), BOARD_PCI6280 }, + { PCI_VDEVICE(NI, 0x70b7), BOARD_PCI6254 }, + { PCI_VDEVICE(NI, 0x70b8), BOARD_PCI6251 }, + { PCI_VDEVICE(NI, 0x70b9), BOARD_PXI6250 }, + { PCI_VDEVICE(NI, 0x70ba), BOARD_PXI6254 }, + { PCI_VDEVICE(NI, 0x70bb), BOARD_PXI6280 }, + { PCI_VDEVICE(NI, 0x70bc), BOARD_PCI6284 }, + { PCI_VDEVICE(NI, 0x70bd), BOARD_PCI6281 }, + { PCI_VDEVICE(NI, 0x70be), BOARD_PXI6284 }, + { 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, 0x717d), BOARD_PCIE6251 }, + { PCI_VDEVICE(NI, 0x717f), BOARD_PCIE6259 }, + { PCI_VDEVICE(NI, 0x71bc), BOARD_PCI6221_37PIN }, + { PCI_VDEVICE(NI, 0x72e8), BOARD_PXIE6251 }, + { PCI_VDEVICE(NI, 0x72e9), BOARD_PXIE6259 }, + { 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_routes.c b/drivers/comedi/drivers/ni_routes.c new file mode 100644 index 000000000..295a3a9ee --- /dev/null +++ b/drivers/comedi/drivers/ni_routes.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routes.c + * Route information for NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bsearch.h> +#include <linux/sort.h> +#include <linux/comedi.h> + +#include "ni_routes.h" +#include "ni_routing/ni_route_values.h" +#include "ni_routing/ni_device_routes.h" + +/* + * This is defined in ni_routing/ni_route_values.h: + * #define B(x) ((x) - NI_NAMES_BASE) + */ + +/* + * These are defined in ni_routing/ni_route_values.h to identify clearly + * elements of the table that were set. In other words, entries that are zero + * are invalid. To get the value to use for the register, one must mask out the + * high bit. + * + * #define V(x) ((x) | 0x80) + * + * #define UNMARK(x) ((x) & (~(0x80))) + * + */ + +/* Helper for accessing data. */ +#define RVi(table, src, dest) ((table)[(dest) * NI_NUM_NAMES + (src)]) + +/* + * Find the route values for a device family. + */ +static const u8 *ni_find_route_values(const char *device_family) +{ + const u8 *rv = NULL; + int i; + + for (i = 0; ni_all_route_values[i]; ++i) { + if (!strcmp(ni_all_route_values[i]->family, device_family)) { + rv = &ni_all_route_values[i]->register_values[0][0]; + break; + } + } + return rv; +} + +/* + * Find the valid routes for a board. + */ +static const struct ni_device_routes * +ni_find_valid_routes(const char *board_name) +{ + const struct ni_device_routes *dr = NULL; + int i; + + for (i = 0; ni_device_routes_list[i]; ++i) { + if (!strcmp(ni_device_routes_list[i]->device, board_name)) { + dr = ni_device_routes_list[i]; + break; + } + } + return dr; +} + +/* + * Find the proper route_values and ni_device_routes tables for this particular + * device. Possibly try an alternate board name if device routes not found + * for the actual board name. + * + * Return: -ENODATA if either was not found; 0 if both were found. + */ +static int ni_find_device_routes(const char *device_family, + const char *board_name, + const char *alt_board_name, + struct ni_route_tables *tables) +{ + const struct ni_device_routes *dr; + const u8 *rv; + + /* First, find the register_values table for this device family */ + rv = ni_find_route_values(device_family); + + /* Second, find the set of routes valid for this device. */ + dr = ni_find_valid_routes(board_name); + if (!dr && alt_board_name) + dr = ni_find_valid_routes(alt_board_name); + + tables->route_values = rv; + tables->valid_routes = dr; + + if (!rv || !dr) + return -ENODATA; + + return 0; +} + +/** + * ni_assign_device_routes() - Assign the proper lookup table for NI signal + * routing to the specified NI device. + * @device_family: Device family name (determines route values). + * @board_name: Board name (determines set of routes). + * @alt_board_name: Optional alternate board name to try on failure. + * @tables: Pointer to assigned routing information. + * + * Finds the route values for the device family and the set of valid routes + * for the board. If valid routes could not be found for the actual board + * name and an alternate board name has been specified, try that one. + * + * On failure, the assigned routing information may be partially filled + * (for example, with the route values but not the set of valid routes). + * + * Return: -ENODATA if assignment was not successful; 0 if successful. + */ +int ni_assign_device_routes(const char *device_family, + const char *board_name, + const char *alt_board_name, + struct ni_route_tables *tables) +{ + memset(tables, 0, sizeof(struct ni_route_tables)); + return ni_find_device_routes(device_family, board_name, alt_board_name, + tables); +} +EXPORT_SYMBOL_GPL(ni_assign_device_routes); + +/** + * ni_count_valid_routes() - Count the number of valid routes. + * @tables: Routing tables for which to count all valid routes. + */ +unsigned int ni_count_valid_routes(const struct ni_route_tables *tables) +{ + int total = 0; + int i; + + for (i = 0; i < tables->valid_routes->n_route_sets; ++i) { + const struct ni_route_set *R = &tables->valid_routes->routes[i]; + int j; + + for (j = 0; j < R->n_src; ++j) { + const int src = R->src[j]; + const int dest = R->dest; + const u8 *rv = tables->route_values; + + if (RVi(rv, B(src), B(dest))) + /* direct routing is valid */ + ++total; + else if (channel_is_rtsi(dest) && + (RVi(rv, B(src), B(NI_RGOUT0)) || + RVi(rv, B(src), B(NI_RTSI_BRD(0))) || + RVi(rv, B(src), B(NI_RTSI_BRD(1))) || + RVi(rv, B(src), B(NI_RTSI_BRD(2))) || + RVi(rv, B(src), B(NI_RTSI_BRD(3))))) { + ++total; + } + } + } + return total; +} +EXPORT_SYMBOL_GPL(ni_count_valid_routes); + +/** + * ni_get_valid_routes() - Implements INSN_DEVICE_CONFIG_GET_ROUTES. + * @tables: pointer to relevant set of routing tables. + * @n_pairs: Number of pairs for which memory is allocated by the user. If + * the user specifies '0', only the number of available pairs is + * returned. + * @pair_data: Pointer to memory allocated to return pairs back to user. Each + * even, odd indexed member of this array will hold source, + * destination of a route pair respectively. + * + * Return: the number of valid routes if n_pairs == 0; otherwise, the number of + * valid routes copied. + */ +unsigned int ni_get_valid_routes(const struct ni_route_tables *tables, + unsigned int n_pairs, + unsigned int *pair_data) +{ + unsigned int n_valid = ni_count_valid_routes(tables); + int i; + + if (n_pairs == 0 || n_valid == 0) + return n_valid; + + if (!pair_data) + return 0; + + n_valid = 0; + + for (i = 0; i < tables->valid_routes->n_route_sets; ++i) { + const struct ni_route_set *R = &tables->valid_routes->routes[i]; + int j; + + for (j = 0; j < R->n_src; ++j) { + const int src = R->src[j]; + const int dest = R->dest; + bool valid = false; + const u8 *rv = tables->route_values; + + if (RVi(rv, B(src), B(dest))) + /* direct routing is valid */ + valid = true; + else if (channel_is_rtsi(dest) && + (RVi(rv, B(src), B(NI_RGOUT0)) || + RVi(rv, B(src), B(NI_RTSI_BRD(0))) || + RVi(rv, B(src), B(NI_RTSI_BRD(1))) || + RVi(rv, B(src), B(NI_RTSI_BRD(2))) || + RVi(rv, B(src), B(NI_RTSI_BRD(3))))) { + /* indirect routing also valid */ + valid = true; + } + + if (valid) { + pair_data[2 * n_valid] = src; + pair_data[2 * n_valid + 1] = dest; + ++n_valid; + } + + if (n_valid >= n_pairs) + return n_valid; + } + } + return n_valid; +} +EXPORT_SYMBOL_GPL(ni_get_valid_routes); + +/* + * List of NI global signal names that, as destinations, are only routeable + * indirectly through the *_arg elements of the comedi_cmd structure. + */ +static const int NI_CMD_DESTS[] = { + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, +}; + +/** + * ni_is_cmd_dest() - Determine whether the given destination is only + * configurable via a comedi_cmd struct. + * @dest: Destination to test. + */ +bool ni_is_cmd_dest(int dest) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(NI_CMD_DESTS); ++i) + if (NI_CMD_DESTS[i] == dest) + return true; + return false; +} +EXPORT_SYMBOL_GPL(ni_is_cmd_dest); + +/* **** BEGIN Routes sort routines **** */ +static int _ni_sort_destcmp(const void *va, const void *vb) +{ + const struct ni_route_set *a = va; + const struct ni_route_set *b = vb; + + if (a->dest < b->dest) + return -1; + else if (a->dest > b->dest) + return 1; + return 0; +} + +static int _ni_sort_srccmp(const void *vsrc0, const void *vsrc1) +{ + const int *src0 = vsrc0; + const int *src1 = vsrc1; + + if (*src0 < *src1) + return -1; + else if (*src0 > *src1) + return 1; + return 0; +} + +/** + * ni_sort_device_routes() - Sort the list of valid device signal routes in + * preparation for use. + * @valid_routes: pointer to ni_device_routes struct to sort. + */ +void ni_sort_device_routes(struct ni_device_routes *valid_routes) +{ + unsigned int n; + + /* 1. Count and set the number of ni_route_set objects. */ + valid_routes->n_route_sets = 0; + while (valid_routes->routes[valid_routes->n_route_sets].dest != 0) + ++valid_routes->n_route_sets; + + /* 2. sort all ni_route_set objects by destination. */ + sort(valid_routes->routes, valid_routes->n_route_sets, + sizeof(struct ni_route_set), _ni_sort_destcmp, NULL); + + /* 3. Loop through each route_set for sorting. */ + for (n = 0; n < valid_routes->n_route_sets; ++n) { + struct ni_route_set *rs = &valid_routes->routes[n]; + + /* 3a. Count and set the number of sources. */ + rs->n_src = 0; + while (rs->src[rs->n_src]) + ++rs->n_src; + + /* 3a. Sort sources. */ + sort(valid_routes->routes[n].src, valid_routes->routes[n].n_src, + sizeof(int), _ni_sort_srccmp, NULL); + } +} +EXPORT_SYMBOL_GPL(ni_sort_device_routes); + +/* sort all valid device signal routes in prep for use */ +static void ni_sort_all_device_routes(void) +{ + unsigned int i; + + for (i = 0; ni_device_routes_list[i]; ++i) + ni_sort_device_routes(ni_device_routes_list[i]); +} + +/* **** BEGIN Routes search routines **** */ +static int _ni_bsearch_destcmp(const void *vkey, const void *velt) +{ + const int *key = vkey; + const struct ni_route_set *elt = velt; + + if (*key < elt->dest) + return -1; + else if (*key > elt->dest) + return 1; + return 0; +} + +static int _ni_bsearch_srccmp(const void *vkey, const void *velt) +{ + const int *key = vkey; + const int *elt = velt; + + if (*key < *elt) + return -1; + else if (*key > *elt) + return 1; + return 0; +} + +/** + * ni_find_route_set() - Finds the proper route set with the specified + * destination. + * @destination: Destination of which to search for the route set. + * @valid_routes: Pointer to device routes within which to search. + * + * Return: NULL if no route_set is found with the specified @destination; + * otherwise, a pointer to the route_set if found. + */ +const struct ni_route_set * +ni_find_route_set(const int destination, + const struct ni_device_routes *valid_routes) +{ + return bsearch(&destination, valid_routes->routes, + valid_routes->n_route_sets, sizeof(struct ni_route_set), + _ni_bsearch_destcmp); +} +EXPORT_SYMBOL_GPL(ni_find_route_set); + +/* + * ni_route_set_has_source() - Determines whether the given source is in + * included given route_set. + * + * Return: true if found; false otherwise. + */ +bool ni_route_set_has_source(const struct ni_route_set *routes, + const int source) +{ + if (!bsearch(&source, routes->src, routes->n_src, sizeof(int), + _ni_bsearch_srccmp)) + return false; + return true; +} +EXPORT_SYMBOL_GPL(ni_route_set_has_source); + +/** + * ni_lookup_route_register() - Look up a register value for a particular route + * without checking whether the route is valid for + * the particular device. + * @src: global-identifier for route source + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * + * Return: -EINVAL if the specified route is not valid for this device family. + */ +s8 ni_lookup_route_register(int src, int dest, + const struct ni_route_tables *tables) +{ + s8 regval; + + /* + * Be sure to use the B() macro to subtract off the NI_NAMES_BASE before + * indexing into the route_values array. + */ + src = B(src); + dest = B(dest); + if (src < 0 || src >= NI_NUM_NAMES || dest < 0 || dest >= NI_NUM_NAMES) + return -EINVAL; + regval = RVi(tables->route_values, src, dest); + if (!regval) + return -EINVAL; + /* mask out the valid-value marking bit */ + return UNMARK(regval); +} +EXPORT_SYMBOL_GPL(ni_lookup_route_register); + +/** + * ni_route_to_register() - Validates and converts the specified signal route + * (src-->dest) to the value used at the appropriate + * register. + * @src: global-identifier for route source + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * + * Generally speaking, most routes require the first six bits and a few require + * 7 bits. Special handling is given for the return value when the route is to + * be handled by the RTSI sub-device. In this case, the returned register may + * not be sufficient to define the entire route path, but rather may only + * indicate the intermediate route. For example, if the route must go through + * the RGOUT0 pin, the (src->RGOUT0) register value will be returned. + * Similarly, if the route must go through the NI_RTSI_BRD lines, the BIT(6) + * will be set: + * + * if route does not need RTSI_BRD lines: + * bits 0:7 : register value + * for a route that must go through RGOUT0 pin, this will be equal + * to the (src->RGOUT0) register value. + * else: * route is (src->RTSI_BRD(x), RTSI_BRD(x)->TRIGGER_LINE(i)) * + * bits 0:5 : zero + * bits 6 : set to 1 + * bits 7:7 : zero + * + * Return: register value to be used for source at destination with special + * cases given above; Otherwise, -1 if the specified route is not valid for + * this particular device. + */ +s8 ni_route_to_register(const int src, const int dest, + const struct ni_route_tables *tables) +{ + const struct ni_route_set *routes = + ni_find_route_set(dest, tables->valid_routes); + const u8 *rv; + s8 regval; + + /* first check to see if source is listed with bunch of destinations. */ + if (!routes) + return -1; + /* 2nd, check to see if destination is in list of source's targets. */ + if (!ni_route_set_has_source(routes, src)) + return -1; + /* + * finally, check to see if we know how to route... + * Be sure to use the B() macro to subtract off the NI_NAMES_BASE before + * indexing into the route_values array. + */ + rv = tables->route_values; + regval = RVi(rv, B(src), B(dest)); + + /* + * if we did not validate the route, we'll see if we can route through + * one of the muxes + */ + if (!regval && channel_is_rtsi(dest)) { + regval = RVi(rv, B(src), B(NI_RGOUT0)); + if (!regval && (RVi(rv, B(src), B(NI_RTSI_BRD(0))) || + RVi(rv, B(src), B(NI_RTSI_BRD(1))) || + RVi(rv, B(src), B(NI_RTSI_BRD(2))) || + RVi(rv, B(src), B(NI_RTSI_BRD(3))))) + regval = BIT(6); + } + + if (!regval) + return -1; + /* mask out the valid-value marking bit */ + return UNMARK(regval); +} +EXPORT_SYMBOL_GPL(ni_route_to_register); + +/* + * ni_find_route_source() - Finds the signal source corresponding to a signal + * route (src-->dest) of the specified routing register + * value and the specified route destination on the + * specified device. + * + * Note that this function does _not_ validate the source based on device + * routes. + * + * Return: The NI signal value (e.g. NI_PFI(0) or PXI_Clk10) if found. + * If the source was not found (i.e. the register value is not + * valid for any routes to the destination), -EINVAL is returned. + */ +int ni_find_route_source(const u8 src_sel_reg_value, int dest, + const struct ni_route_tables *tables) +{ + int src; + + if (!tables->route_values) + return -EINVAL; + + dest = B(dest); /* subtract NI names offset */ + /* ensure we are not going to under/over run the route value table */ + if (dest < 0 || dest >= NI_NUM_NAMES) + return -EINVAL; + for (src = 0; src < NI_NUM_NAMES; ++src) + if (RVi(tables->route_values, src, dest) == + V(src_sel_reg_value)) + return src + NI_NAMES_BASE; + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ni_find_route_source); + +/* **** END Routes search routines **** */ + +/* **** BEGIN simple module entry/exit functions **** */ +static int __init ni_routes_module_init(void) +{ + ni_sort_all_device_routes(); + return 0; +} + +static void __exit ni_routes_module_exit(void) +{ +} + +module_init(ni_routes_module_init); +module_exit(ni_routes_module_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi helper for routing signals-->terminals for NI"); +MODULE_LICENSE("GPL"); +/* **** END simple module entry/exit functions **** */ diff --git a/drivers/comedi/drivers/ni_routes.h b/drivers/comedi/drivers/ni_routes.h new file mode 100644 index 000000000..cff8a463a --- /dev/null +++ b/drivers/comedi/drivers/ni_routes.h @@ -0,0 +1,329 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routes.h + * Route information for NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_DRIVERS_NI_ROUTES_H +#define _COMEDI_DRIVERS_NI_ROUTES_H + +#include <linux/types.h> +#include <linux/errno.h> + +#ifndef NI_ROUTE_VALUE_EXTERNAL_CONVERSION +#include <linux/bitops.h> +#endif + +#include <linux/comedi.h> + +/** + * struct ni_route_set - Set of destinations with a common source. + * @dest: Destination of all sources in this route set. + * @n_src: Number of sources for this route set. + * @src: List of sources that all map to the same destination. + */ +struct ni_route_set { + int dest; + int n_src; + int *src; +}; + +/** + * struct ni_device_routes - List of all src->dest sets for a particular device. + * @device: Name of board/device (e.g. pxi-6733). + * @n_route_sets: Number of route sets that are valid for this device. + * @routes: List of route sets that are valid for this device. + */ +struct ni_device_routes { + const char *device; + int n_route_sets; + struct ni_route_set *routes; +}; + +/** + * struct ni_route_tables - Register values and valid routes for a device. + * @valid_routes: Pointer to a all valid route sets for a single device. + * @route_values: Pointer to register values for all routes for the family to + * which the device belongs. + * + * Link to the valid src->dest routes and the register values used to assign + * such routes for that particular device. + */ +struct ni_route_tables { + const struct ni_device_routes *valid_routes; + const u8 *route_values; +}; + +/* + * ni_assign_device_routes() - Assign the proper lookup table for NI signal + * routing to the specified NI device. + * + * Return: -ENODATA if assignment was not successful; 0 if successful. + */ +int ni_assign_device_routes(const char *device_family, + const char *board_name, + const char *alt_board_name, + struct ni_route_tables *tables); + +/* + * ni_find_route_set() - Finds the proper route set with the specified + * destination. + * @destination: Destination of which to search for the route set. + * @valid_routes: Pointer to device routes within which to search. + * + * Return: NULL if no route_set is found with the specified @destination; + * otherwise, a pointer to the route_set if found. + */ +const struct ni_route_set * +ni_find_route_set(const int destination, + const struct ni_device_routes *valid_routes); + +/* + * ni_route_set_has_source() - Determines whether the given source is in + * included given route_set. + * + * Return: true if found; false otherwise. + */ +bool ni_route_set_has_source(const struct ni_route_set *routes, const int src); + +/* + * ni_route_to_register() - Validates and converts the specified signal route + * (src-->dest) to the value used at the appropriate + * register. + * @src: global-identifier for route source + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * + * Generally speaking, most routes require the first six bits and a few require + * 7 bits. Special handling is given for the return value when the route is to + * be handled by the RTSI sub-device. In this case, the returned register may + * not be sufficient to define the entire route path, but rather may only + * indicate the intermediate route. For example, if the route must go through + * the RGOUT0 pin, the (src->RGOUT0) register value will be returned. + * Similarly, if the route must go through the NI_RTSI_BRD lines, the BIT(6) + * will be set: + * + * if route does not need RTSI_BRD lines: + * bits 0:7 : register value + * for a route that must go through RGOUT0 pin, this will be equal + * to the (src->RGOUT0) register value. + * else: * route is (src->RTSI_BRD(x), RTSI_BRD(x)->TRIGGER_LINE(i)) * + * bits 0:5 : zero + * bits 6 : set to 1 + * bits 7:7 : zero + * + * Return: register value to be used for source at destination with special + * cases given above; Otherwise, -1 if the specified route is not valid for + * this particular device. + */ +s8 ni_route_to_register(const int src, const int dest, + const struct ni_route_tables *tables); + +static inline bool ni_rtsi_route_requires_mux(s8 value) +{ + return value & BIT(6); +} + +/* + * ni_lookup_route_register() - Look up a register value for a particular route + * without checking whether the route is valid for + * the particular device. + * @src: global-identifier for route source + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * + * Return: -EINVAL if the specified route is not valid for this device family. + */ +s8 ni_lookup_route_register(int src, int dest, + const struct ni_route_tables *tables); + +/** + * route_is_valid() - Determines whether the specified signal route (src-->dest) + * is valid for the given NI comedi_device. + * @src: global-identifier for route source + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * + * Return: True if the route is valid, otherwise false. + */ +static inline bool route_is_valid(const int src, const int dest, + const struct ni_route_tables *tables) +{ + return ni_route_to_register(src, dest, tables) >= 0; +} + +/* + * ni_is_cmd_dest() - Determine whether the given destination is only + * configurable via a comedi_cmd struct. + * @dest: Destination to test. + */ +bool ni_is_cmd_dest(int dest); + +static inline bool channel_is_pfi(int channel) +{ + return NI_PFI(0) <= channel && channel <= NI_PFI(-1); +} + +static inline bool channel_is_rtsi(int channel) +{ + return TRIGGER_LINE(0) <= channel && channel <= TRIGGER_LINE(-1); +} + +static inline bool channel_is_ctr(int channel) +{ + return channel >= NI_COUNTER_NAMES_BASE && + channel <= NI_COUNTER_NAMES_MAX; +} + +/* + * ni_count_valid_routes() - Count the number of valid routes. + * @tables: Routing tables for which to count all valid routes. + */ +unsigned int ni_count_valid_routes(const struct ni_route_tables *tables); + +/* + * ni_get_valid_routes() - Implements INSN_DEVICE_CONFIG_GET_ROUTES. + * @tables: pointer to relevant set of routing tables. + * @n_pairs: Number of pairs for which memory is allocated by the user. If + * the user specifies '0', only the number of available pairs is + * returned. + * @pair_data: Pointer to memory allocated to return pairs back to user. Each + * even, odd indexed member of this array will hold source, + * destination of a route pair respectively. + * + * Return: the number of valid routes if n_pairs == 0; otherwise, the number of + * valid routes copied. + */ +unsigned int ni_get_valid_routes(const struct ni_route_tables *tables, + unsigned int n_pairs, + unsigned int *pair_data); + +/* + * ni_sort_device_routes() - Sort the list of valid device signal routes in + * preparation for use. + * @valid_routes: pointer to ni_device_routes struct to sort. + */ +void ni_sort_device_routes(struct ni_device_routes *valid_routes); + +/* + * ni_find_route_source() - Finds the signal source corresponding to a signal + * route (src-->dest) of the specified routing register + * value and the specified route destination on the + * specified device. + * + * Note that this function does _not_ validate the source based on device + * routes. + * + * Return: The NI signal value (e.g. NI_PFI(0) or PXI_Clk10) if found. + * If the source was not found (i.e. the register value is not + * valid for any routes to the destination), -EINVAL is returned. + */ +int ni_find_route_source(const u8 src_sel_reg_value, const int dest, + const struct ni_route_tables *tables); + +/** + * route_register_is_valid() - Determines whether the register value for the + * specified route destination on the specified + * device is valid. + */ +static inline bool route_register_is_valid(const u8 src_sel_reg_value, + const int dest, + const struct ni_route_tables *tables) +{ + return ni_find_route_source(src_sel_reg_value, dest, tables) >= 0; +} + +/** + * ni_get_reg_value_roffs() - Determines the proper register value for a + * particular valid NI signal/terminal route. + * @src: Either a direct register value or one of NI_* signal names. + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * @direct_reg_offset: + * Compatibility compensation argument. This argument allows us to + * arbitrarily apply an offset to src if src is a direct register + * value reference. This is necessary to be compatible with + * definitions of register values as previously exported directly + * to user space. + * + * Return: the register value (>0) to be used at the destination if the src is + * valid for the given destination; -1 otherwise. + */ +static inline s8 ni_get_reg_value_roffs(int src, const int dest, + const struct ni_route_tables *tables, + const int direct_reg_offset) +{ + if (src < NI_NAMES_BASE) { + src += direct_reg_offset; + /* + * In this case, the src is expected to actually be a register + * value. + */ + if (route_register_is_valid(src, dest, tables)) + return src; + return -1; + } + + /* + * Otherwise, the src is expected to be one of the abstracted NI + * signal/terminal names. + */ + return ni_route_to_register(src, dest, tables); +} + +static inline int ni_get_reg_value(const int src, const int dest, + const struct ni_route_tables *tables) +{ + return ni_get_reg_value_roffs(src, dest, tables, 0); +} + +/** + * ni_check_trigger_arg_roffs() - Checks the trigger argument (*_arg) of an NI + * device to ensure that the *_arg value + * corresponds to _either_ a valid register value + * to define a trigger source, _or_ a valid NI + * signal/terminal name that has a valid route to + * the destination on the particular device. + * @src: Either a direct register value or one of NI_* signal names. + * @dest: global-identifier for route destination + * @tables: pointer to relevant set of routing tables. + * @direct_reg_offset: + * Compatibility compensation argument. This argument allows us to + * arbitrarily apply an offset to src if src is a direct register + * value reference. This is necessary to be compatible with + * definitions of register values as previously exported directly + * to user space. + * + * Return: 0 if the src (either register value or NI signal/terminal name) is + * valid for the destination; -EINVAL otherwise. + */ +static inline +int ni_check_trigger_arg_roffs(int src, const int dest, + const struct ni_route_tables *tables, + const int direct_reg_offset) +{ + if (ni_get_reg_value_roffs(src, dest, tables, direct_reg_offset) < 0) + return -EINVAL; + return 0; +} + +static inline int ni_check_trigger_arg(const int src, const int dest, + const struct ni_route_tables *tables) +{ + return ni_check_trigger_arg_roffs(src, dest, tables, 0); +} + +#endif /* _COMEDI_DRIVERS_NI_ROUTES_H */ diff --git a/drivers/comedi/drivers/ni_routing/README b/drivers/comedi/drivers/ni_routing/README new file mode 100644 index 000000000..b65c4ebed --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/README @@ -0,0 +1,240 @@ +Framework for Maintaining Common National Instruments Terminal/Signal names + +The contents of this directory are primarily for maintaining and formatting all +known valid signal routes for various National Instruments devices. + +Some background: + There have been significant confusions over the past many years for users + when trying to understand how to connect to/from signals and terminals on + NI hardware using comedi. The major reason for this is that the actual + register values were exposed and required to be used by users. Several + major reasons exist why this caused major confusion for users: + + 1) The register values are _NOT_ in user documentation, but rather in + arcane locations, such as a few register programming manuals that are + increasingly hard to find and the NI-MHDDK (comments in in example code). + There is no one place to find the various valid values of the registers. + + 2) The register values are _NOT_ completely consistent. There is no way to + gain any sense of intuition of which values, or even enums one should use + for various registers. There was some attempt in prior use of comedi to + name enums such that a user might know which enums should be used for + varying purposes, but the end-user had to gain a knowledge of register + values to correctly wield this approach. + + 3) The names for signals and registers found in the various register level + programming manuals and vendor-provided documentation are _not_ even + close to the same names that are in the end-user documentation. + + 4) The sets of routes that are valid are not consistent from device to device. + One additional major challenge is that this information does not seem to be + obtainable in any programmatic fashion, neither through the proprietary + NIDAQmx(-base) c-libraries, nor with register level programming, _nor_ + through any documentation. In fact, the only consistent source of this + information is through the proprietary NI-MAX software, which currently only + runs on Windows platforms. A further challenge is that this information + cannot be exported from NI-MAX, except by screenshot. + + + +The content of this directory is part of an effort to greatly simplify the use +of signal routing capabilities of National Instruments data-acquisition and +control hardware. In order to facilitate the transfer of register-level +information _and_ the knowledge of valid routes per device, a few specific +choices were made: + + +1) The names of the National Instruments signals/terminals that are used in this + directory are chosen to be consistent with (a) the NI's user level + documentation, (b) NI's user-level code, (c) the information as provided by + the proprietary NI-MAX software, and (d) the user interface code provided by + the user-land comedilib library. + + The impact of this choice implies that one allows the use of CamelScript names + in the kernel. In short, the choice to use CamelScript and the exact names + below is for maintainability, clarity, similarity to manufacturer's + documentation, _and_ a mitigation for confusion that has plagued the use of + these drivers for years! + +2) The bulk of the real content for this directory is stored in two separate + collections (i.e. sub-directories) of tables stored in c source files: + + (a) ni_route_values/ni_[series-label]series.c + + This data represents all the various register values to use for the + multiple different signal MUXes for the specific device families. + + The values are all wrapped in one of three macros to help document and + track which values have been implemented and tested. + These macros are: + V(<value>) : register value is valid, tested, and implemented + I(<value>) : register value is implemented but needs testing + U(<value>) : register value is not implemented + + The actual function of these macros will depend on whether the code is + compiled in the kernel or whether it is compiled into the conversion + tools. For the conversion tools, it can be used to indicate the status + of the register value. For the kernel, V() and I() both perform the + same function and prepare data to be used; U() zeroes out the value to + ensure that it cannot be used. + + *** It would be a great help for users to test these values such that + these files can be correctly marked/documented *** + + (b) ni_device_routes/[board-name].c + + This data represents the known set of valid signal routes that are + possible for each specific board. Although the family defines the + register values to use for a particular signal MUX, not all possible + signals are actually available on each board. + + In order for a particular board to take advantage of the effort to + simplify/clarify signal routing on NI devices, a corresponding + [board-name].c file must be created. This file should reflect the known + valid _direct_ routing capabilities of the board. + + As noted above, the only known consistent source of information for + valid device routes comes from the proprietary National Instruments + Windows software, NI-MAX. Also, as noted above, this information can + only be visually conveyed from NI-MAX to other media. To make this + easier, the naming conventions used in the [board-name].c file are + similar to the naming conventions as presented by NI-MAX. + + +3) Two other files aggregate the above data to integrate it into comedi: + ni_route_values.c + ni_device_routes.c + + When adding a new [board-name].c file, be sure to also add in the line in + ni_device_routes.c to include this information into comedi. + + +4) Several tools have been included to convert from/to the c file formats. + These tools are best used/demonstrated via the included Makefile targets: + (a) `make csv-files` + Creates new csv-files using content of c-files of existing + ni_routing/* content. New csv files are placed in csv + sub-directory. + + As noted above, the only consistent source of information of valid + device routes comes from the proprietary National Instruments Windows + software, NI-MAX. Also, as noted above, this information can only be + visually conveyed from NI-MAX to other media. This make target creates + spreadsheet representations of the routing data. The choice of using a + spreadsheet (ala CSV) to copy this information allows for easy direct + visual comparison to the NI-MAX "Valid Routes" tables. + + Furthermore, the register-level information is much easier to identify and + correct when entire families of NI devices are shown side by side in table + format. This is made easy by using a file-storage format that can be + loaded into a spreadsheet application. + + Finally, .csv content is very easy to edit and read using a variety of + tools, including spreadsheets or various other scripting languages. In + fact, the tools provided here enable quick conversion of the + spreadsheet-like .csv format to c-files that follow the kernel coding + conventions. + + + (b) `make c-files` + Creates new c-files using content of csv sub-directory. These + new c-files can be compared to the active content in the + ni_routing directory. + (c) `make csv-blank` + Create a new blank csv file. This is useful for establishing a + new data table for either a device family (less likely) or a + specific board of an existing device family (more likely). + (d) `make clean` + Remove all generated files/directories. + (e) `make everything` + Build all csv-files, then all new c-files. + + + + +In summary, similar confusion about signal routing configuration, albeit less, +plagued NI's previous version of their own proprietary drivers. Earlier than +2003, NI greatly simplified the situation for users by releasing a new API that +abstracted the names of signals/terminals to a common and intuitive set of +names. In addition, this new API provided a much more common interface to use +for most of NI hardware. + +Comedi already provides such a common interface for data-acquisition and control +hardware. This effort complements comedi's abstraction layers by further +abstracting much more of the use cases for NI hardware, but allowing users _and_ +developers to directly refer to NI documentation (user-level, register-level, +and the register-level examples of the NI-MHDDK). + + + +-------------------------------------------------------------------------------- +Various naming conventions and relations: +-------------------------------------------------------------------------------- +These are various notes that help to relate the naming conventions used in the +NI-STC with those naming conventions used here. +-------------------------------------------------------------------------------- + + Signal sources for most signals-destinations are given a specific naming + convention, although the register values are not consistent. This next table + shows the mapping between the names used in comedi for NI and those names + typically used within the NI-STC documentation. + + (comedi) (NI-STC input or output) (NOTE) + ------------------------------------------------------------------------------ + TRIGGER_LINE(i) RTSI_Trig_i_Output_Select i in range [0..7] + NI_AI_STOP AI_STOP + NI_AI_SampleClock AI_START_Select + NI_AI_SampleClockTimebase AI_SI If internal sample + clock signal is used + NI_AI_StartTrigger AI_START1_Select + NI_AI_ReferenceTrigger AI_START2_Select for pre-triggered + acquisition---not + currently supported + in comedi + NI_AI_ConvertClock AI_CONVERT_Source_Select + NI_AI_ConvertClockTimebase AI_SI2 If internal convert + signal is used + NI_AI_HoldCompleteEvent + NI_AI_PauseTrigger AI_External_Gate + NI_AO_SampleClock AO_UPDATE + NI_AO_SampleClockTimebase AO_UI + NI_AO_StartTrigger AO_START1 + NI_AO_PauseTrigger AO_External_Gate + NI_DI_SampleClock + NI_DO_SampleClock + NI_MasterTimebase + NI_20MHzTimebase TIMEBASE 1 && TIMEBASE 3 if no higher clock exists + NI_80MHzTimebase TIMEBASE 3 + NI_100kHzTimebase TIMEBASE 2 + NI_10MHzRefClock + PXI_Clk10 + NI_CtrOut(0) GPFO_0 external ctr0out pin + NI_CtrOut(1) GPFO_1 external ctr1out pin + NI_CtrSource(0) + NI_CtrSource(1) + NI_CtrGate(0) + NI_CtrGate(1) + NI_CtrInternalOutput(0) G_OUT0, G0_TC for Ctr1Source, Ctr1Gate + NI_CtrInternalOutput(1) G_OUT1, G1_TC for Ctr0Source, Ctr0Gate + NI_RGOUT0 RGOUT0 internal signal + NI_FrequencyOutput + #NI_FrequencyOutputTimebase + NI_ChangeDetectionEvent + NI_RTSI_BRD(0) + NI_RTSI_BRD(1) + NI_RTSI_BRD(2) + NI_RTSI_BRD(3) + #NI_SoftwareStrobe + NI_LogicLow + NI_CtrA(0) G0_A_Select see M-Series user + manual (371022K-01) + NI_CtrA(1) G1_A_Select see M-Series user + manual (371022K-01) + NI_CtrB(0) G0_B_Select, up/down see M-Series user + manual (371022K-01) + NI_CtrB(1) G1_B_Select, up/down see M-Series user + manual (371022K-01) + NI_CtrZ(0) see M-Series user + manual (371022K-01) + NI_CtrZ(1) see M-Series user + manual (371022K-01) diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes.c b/drivers/comedi/drivers/ni_routing/ni_device_routes.c new file mode 100644 index 000000000..58654c2b1 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "ni_device_routes.h" +#include "ni_device_routes/all.h" + +struct ni_device_routes *const ni_device_routes_list[] = { + &ni_pxi_6030e_device_routes, + &ni_pci_6070e_device_routes, + &ni_pci_6220_device_routes, + &ni_pci_6221_device_routes, + &ni_pxi_6224_device_routes, + &ni_pxi_6225_device_routes, + &ni_pci_6229_device_routes, + &ni_pci_6251_device_routes, + &ni_pxi_6251_device_routes, + &ni_pxie_6251_device_routes, + &ni_pci_6254_device_routes, + &ni_pci_6259_device_routes, + &ni_pci_6534_device_routes, + &ni_pci_6602_device_routes, + &ni_pci_6713_device_routes, + &ni_pci_6723_device_routes, + &ni_pci_6733_device_routes, + &ni_pxi_6733_device_routes, + NULL, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes.h b/drivers/comedi/drivers/ni_routing/ni_device_routes.h new file mode 100644 index 000000000..09e4e172c --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routing/ni_device_routes.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 comedi/drivers/ni_routes.c + */ + +#ifndef _COMEDI_DRIVERS_NI_ROUTINT_NI_DEVICE_ROUTES_H +#define _COMEDI_DRIVERS_NI_ROUTINT_NI_DEVICE_ROUTES_H + +#include "../ni_routes.h" + +extern struct ni_device_routes *const ni_device_routes_list[]; + +#endif /* _COMEDI_DRIVERS_NI_ROUTINT_NI_DEVICE_ROUTES_H */ diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/all.h b/drivers/comedi/drivers/ni_routing/ni_device_routes/all.h new file mode 100644 index 000000000..001dbb88a --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/all.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routing/ni_device_routes/all.h + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#ifndef _COMEDI_DRIVERS_NI_ROUTING_NI_DEVICE_ROUTES_EXTERN_H +#define _COMEDI_DRIVERS_NI_ROUTING_NI_DEVICE_ROUTES_EXTERN_H + +#include "../ni_device_routes.h" + +extern struct ni_device_routes ni_pxi_6030e_device_routes; +extern struct ni_device_routes ni_pci_6070e_device_routes; +extern struct ni_device_routes ni_pci_6220_device_routes; +extern struct ni_device_routes ni_pci_6221_device_routes; +extern struct ni_device_routes ni_pxi_6224_device_routes; +extern struct ni_device_routes ni_pxi_6225_device_routes; +extern struct ni_device_routes ni_pci_6229_device_routes; +extern struct ni_device_routes ni_pci_6251_device_routes; +extern struct ni_device_routes ni_pxi_6251_device_routes; +extern struct ni_device_routes ni_pxie_6251_device_routes; +extern struct ni_device_routes ni_pci_6254_device_routes; +extern struct ni_device_routes ni_pci_6259_device_routes; +extern struct ni_device_routes ni_pci_6534_device_routes; +extern struct ni_device_routes ni_pxie_6535_device_routes; +extern struct ni_device_routes ni_pci_6602_device_routes; +extern struct ni_device_routes ni_pci_6713_device_routes; +extern struct ni_device_routes ni_pci_6723_device_routes; +extern struct ni_device_routes ni_pci_6733_device_routes; +extern struct ni_device_routes ni_pxi_6733_device_routes; +extern struct ni_device_routes ni_pxie_6738_device_routes; + +#endif //_COMEDI_DRIVERS_NI_ROUTING_NI_DEVICE_ROUTES_EXTERN_H diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6070e.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6070e.c new file mode 100644 index 000000000..7d3064c92 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6070e.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6070e.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6070e_device_routes = { + .device = "pci-6070e", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + NI_AI_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + NI_AI_ConvertClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + NI_AI_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(1), + .src = (int[]){ + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_AI_SampleClockTimebase, + NI_MasterTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_HoldComplete, + .src = (int[]){ + NI_AI_HoldCompleteEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6220.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6220.c new file mode 100644 index 000000000..e2c462edb --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6220.c @@ -0,0 +1,1417 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6220.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6220_device_routes = { + .device = "pci-6220", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6221.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6221.c new file mode 100644 index 000000000..9e02ec0a6 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6221.c @@ -0,0 +1,1601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6221.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6221_device_routes = { + .device = "pci-6221", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AI_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6229.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6229.c new file mode 100644 index 000000000..33f7fff61 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6229.c @@ -0,0 +1,1601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6229.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6229_device_routes = { + .device = "pci-6229", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AI_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6251.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6251.c new file mode 100644 index 000000000..dde676b73 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6251.c @@ -0,0 +1,1651 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6251.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6251_device_routes = { + .device = "pci-6251", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6254.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6254.c new file mode 100644 index 000000000..167a2da97 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6254.c @@ -0,0 +1,1463 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6254.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6254_device_routes = { + .device = "pci-6254", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6259.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6259.c new file mode 100644 index 000000000..ba990f985 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6259.c @@ -0,0 +1,1651 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6259.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6259_device_routes = { + .device = "pci-6259", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6534.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6534.c new file mode 100644 index 000000000..f8d2a91b6 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6534.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6534.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6534_device_routes = { + .device = "pci-6534", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(0), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6602.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6602.c new file mode 100644 index 000000000..2eee91f59 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6602.c @@ -0,0 +1,3377 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6602.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6602_device_routes = { + .device = "pci-6602", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(2), + .src = (int[]){ + NI_80MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_80MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_PFI(7), + NI_PFI(15), + NI_PFI(23), + NI_PFI(31), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_PFI(7), + NI_PFI(15), + NI_PFI(23), + NI_PFI(31), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + NI_CtrGate(7), + NI_LogicLow, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + NI_CtrSource(7), + NI_LogicLow, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + NI_PFI(6), + NI_PFI(14), + NI_PFI(22), + NI_PFI(30), + NI_PFI(38), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + NI_PFI(6), + NI_PFI(14), + NI_PFI(22), + NI_PFI(30), + NI_PFI(38), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + NI_CtrGate(6), + NI_LogicLow, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + NI_CtrSource(6), + NI_LogicLow, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(16), + .src = (int[]){ + NI_PFI(5), + NI_PFI(13), + NI_PFI(21), + NI_PFI(29), + NI_PFI(37), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(17), + .src = (int[]){ + NI_PFI(5), + NI_PFI(13), + NI_PFI(21), + NI_PFI(29), + NI_PFI(37), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(18), + .src = (int[]){ + NI_CtrGate(5), + NI_LogicLow, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(19), + .src = (int[]){ + NI_CtrSource(5), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(20), + .src = (int[]){ + NI_PFI(4), + NI_PFI(12), + NI_PFI(28), + NI_PFI(36), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(21), + .src = (int[]){ + NI_PFI(4), + NI_PFI(12), + NI_PFI(20), + NI_PFI(28), + NI_PFI(36), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(22), + .src = (int[]){ + NI_CtrGate(4), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(23), + .src = (int[]){ + NI_CtrSource(4), + NI_LogicLow, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(24), + .src = (int[]){ + NI_PFI(3), + NI_PFI(11), + NI_PFI(19), + NI_PFI(27), + NI_PFI(35), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(3), + NI_CtrSource(7), + NI_CtrGate(3), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(25), + .src = (int[]){ + NI_PFI(3), + NI_PFI(11), + NI_PFI(19), + NI_PFI(27), + NI_PFI(35), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(3), + NI_CtrSource(7), + NI_CtrGate(3), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(26), + .src = (int[]){ + NI_CtrGate(3), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(27), + .src = (int[]){ + NI_CtrSource(3), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(28), + .src = (int[]){ + NI_PFI(2), + NI_PFI(10), + NI_PFI(18), + NI_PFI(26), + NI_PFI(34), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(2), + NI_CtrSource(6), + NI_CtrGate(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(29), + .src = (int[]){ + NI_PFI(2), + NI_PFI(10), + NI_PFI(18), + NI_PFI(26), + NI_PFI(34), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(2), + NI_CtrSource(6), + NI_CtrGate(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(30), + .src = (int[]){ + NI_CtrGate(2), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(31), + .src = (int[]){ + NI_CtrSource(2), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(32), + .src = (int[]){ + NI_PFI(1), + NI_PFI(9), + NI_PFI(17), + NI_PFI(25), + NI_PFI(33), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(5), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(33), + .src = (int[]){ + NI_PFI(1), + NI_PFI(9), + NI_PFI(17), + NI_PFI(25), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(5), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(34), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(35), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(36), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(5), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(37), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(5), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(38), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(39), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(3), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(7), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(3), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(6), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(6), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(6), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(7), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(7), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(7), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + NI_PFI(16), + NI_PFI(17), + NI_PFI(18), + NI_PFI(19), + NI_PFI(20), + NI_PFI(21), + NI_PFI(22), + NI_PFI(23), + NI_PFI(24), + NI_PFI(25), + NI_PFI(26), + NI_PFI(27), + NI_PFI(28), + NI_PFI(29), + NI_PFI(30), + NI_PFI(31), + NI_PFI(32), + NI_PFI(33), + NI_PFI(34), + NI_PFI(35), + NI_PFI(36), + NI_PFI(37), + NI_PFI(38), + NI_PFI(39), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(4), + NI_CtrSource(5), + NI_CtrSource(6), + NI_CtrGate(4), + NI_CtrGate(5), + NI_CtrGate(6), + NI_CtrInternalOutput(4), + NI_CtrInternalOutput(5), + NI_CtrInternalOutput(6), + NI_LogicLow, + NI_LogicHigh, + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6713.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6713.c new file mode 100644 index 000000000..c07ef3584 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6713.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6713.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6713_device_routes = { + .device = "pci-6713", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(1), + .src = (int[]){ + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6723.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6723.c new file mode 100644 index 000000000..c37373f8f --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6723.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6723.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6723_device_routes = { + .device = "pci-6723", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(1), + .src = (int[]){ + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6733.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6733.c new file mode 100644 index 000000000..f252fbe19 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6733.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pci-6733.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pci_6733_device_routes = { + .device = "pci-6733", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(1), + .src = (int[]){ + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6030e.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6030e.c new file mode 100644 index 000000000..4ccba4fdf --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6030e.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxi-6030e.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxi_6030e_device_routes = { + .device = "pxi-6030e", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + NI_AI_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + NI_AI_ReferenceTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + NI_AI_ConvertClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + NI_AI_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(1), + .src = (int[]){ + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_AI_SampleClockTimebase, + NI_MasterTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_HoldComplete, + .src = (int[]){ + NI_AI_HoldCompleteEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6224.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6224.c new file mode 100644 index 000000000..84fdfa2ef --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6224.c @@ -0,0 +1,1431 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxi-6224.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxi_6224_device_routes = { + .device = "pxi-6224", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6225.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6225.c new file mode 100644 index 000000000..2b99ce0f8 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6225.c @@ -0,0 +1,1612 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxi-6225.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxi_6225_device_routes = { + .device = "pxi-6225", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6251.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6251.c new file mode 100644 index 000000000..1c5164c46 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6251.c @@ -0,0 +1,1654 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxi-6251.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxi_6251_device_routes = { + .device = "pxi-6251", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + PXI_Star, + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + PXI_Star, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + PXI_Star, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + PXI_Star, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Star, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6733.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6733.c new file mode 100644 index 000000000..a3402b1ca --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6733.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxi-6733.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxi_6733_device_routes = { + .device = "pxi-6733", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_CtrSource(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_CtrGate(1), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + NI_CtrSource(0), + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + NI_CtrGate(0), + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + PXI_Star, + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + PXI_Star, + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(1), + PXI_Star, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + PXI_Star, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(0), + PXI_Star, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrOut(1), + .src = (int[]){ + NI_CtrInternalOutput(1), + 0, /* Termination */ + } + }, + { + .dest = PXI_Star, + .src = (int[]){ + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrInternalOutput(0), + NI_CtrOut(0), + NI_AO_SampleClock, + NI_AO_StartTrigger, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_CtrInternalOutput(1), + PXI_Star, + NI_AO_SampleClockTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(7), + PXI_Star, + NI_MasterTimebase, + NI_20MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + PXI_Star, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + PXI_Star, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + PXI_Star, + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + PXI_Star, + NI_AO_SampleClock, + 0, /* Termination */ + } + }, + { + .dest = NI_MasterTimebase, + .src = (int[]){ + TRIGGER_LINE(7), + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6251.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6251.c new file mode 100644 index 000000000..defcc4cfe --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6251.c @@ -0,0 +1,1655 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxie-6251.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxie_6251_device_routes = { + .device = "pxie-6251", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(8), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(9), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(10), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(11), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(12), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(13), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(14), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(15), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_DI_SampleClock, + NI_DO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AI_ConvertClock, + NI_AI_PauseTrigger, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_10MHzRefClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(1), + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrGate(0), + PXI_Clk10, + NI_20MHzTimebase, + NI_80MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(1), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_AI_StartTrigger, + NI_AI_ReferenceTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_ConvertClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_ConvertClockTimebase, + .src = (int[]){ + NI_AI_SampleClockTimebase, + NI_20MHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AO_SampleClockTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100kHzTimebase, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AI_StartTrigger, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_PFI(8), + NI_PFI(9), + NI_PFI(10), + NI_PFI(11), + NI_PFI(12), + NI_PFI(13), + NI_PFI(14), + NI_PFI(15), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_AI_SampleClock, + NI_AI_ConvertClock, + NI_AO_SampleClock, + NI_FrequencyOutput, + NI_ChangeDetectionEvent, + NI_AnalogComparisonEvent, + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6535.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6535.c new file mode 100644 index 000000000..d2013b9e6 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6535.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxie-6535.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxie_6535_device_routes = { + .device = "pxie-6535", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_InputBufferFull, + NI_DI_ReadyForStartEvent, + NI_DI_ReadyForTransferEventBurst, + NI_DI_ReadyForTransferEventPipelined, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_OutputBufferFull, + NI_DO_DataActiveEvent, + NI_DO_ReadyForStartEvent, + NI_DO_ReadyForTransferEvent, + NI_ChangeDetectionEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(5), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_DI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(4), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { + .dest = NI_DO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { + .dest = NI_DO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6738.c b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6738.c new file mode 100644 index 000000000..89aff39a4 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6738.c @@ -0,0 +1,3082 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_device_routes/pxie-6738.c + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "all.h" + +struct ni_device_routes ni_pxie_6738_device_routes = { + .device = "pxie-6738", + .routes = (struct ni_route_set[]){ + { + .dest = NI_PFI(0), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(1), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(2), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(3), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(4), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(5), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(6), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_PFI(7), + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(4), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(5), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(6), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = TRIGGER_LINE(7), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrZ(0), + NI_CtrZ(1), + NI_CtrZ(2), + NI_CtrZ(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + PXI_Clk10, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + PXI_Clk10, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + PXI_Clk10, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSource(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + PXI_Clk10, + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrGate(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrAux(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrA(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrB(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrZ(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrArmStartTrigger(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSampleClock(0), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSampleClock(1), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSampleClock(2), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_CtrSampleClock(3), + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClockTimebase, + NI_DI_SampleClock, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_AO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_DI_SampleClockTimebase, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_ReferenceTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DI_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DO_SampleClock, + NI_DO_StartTrigger, + NI_DO_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClock, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_DO_SampleClockTimebase, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_SampleClockTimebase, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + PXI_Clk10, + NI_20MHzTimebase, + NI_100MHzTimebase, + NI_100kHzTimebase, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_StartTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_DO_PauseTrigger, + .src = (int[]){ + NI_PFI(0), + NI_PFI(1), + NI_PFI(2), + NI_PFI(3), + NI_PFI(4), + NI_PFI(5), + NI_PFI(6), + NI_PFI(7), + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + NI_CtrSource(0), + NI_CtrSource(1), + NI_CtrSource(2), + NI_CtrSource(3), + NI_CtrGate(0), + NI_CtrGate(1), + NI_CtrGate(2), + NI_CtrGate(3), + NI_CtrArmStartTrigger(0), + NI_CtrArmStartTrigger(1), + NI_CtrArmStartTrigger(2), + NI_CtrArmStartTrigger(3), + NI_CtrInternalOutput(0), + NI_CtrInternalOutput(1), + NI_CtrInternalOutput(2), + NI_CtrInternalOutput(3), + NI_CtrSampleClock(0), + NI_CtrSampleClock(1), + NI_CtrSampleClock(2), + NI_CtrSampleClock(3), + NI_AO_SampleClock, + NI_AO_StartTrigger, + NI_AO_PauseTrigger, + NI_DI_SampleClock, + NI_DI_StartTrigger, + NI_DI_ReferenceTrigger, + NI_DI_PauseTrigger, + NI_10MHzRefClock, + NI_ChangeDetectionEvent, + NI_WatchdogExpiredEvent, + 0, /* Termination */ + } + }, + { + .dest = NI_WatchdogExpirationTrigger, + .src = (int[]){ + TRIGGER_LINE(0), + TRIGGER_LINE(1), + TRIGGER_LINE(2), + TRIGGER_LINE(3), + TRIGGER_LINE(4), + TRIGGER_LINE(5), + TRIGGER_LINE(6), + TRIGGER_LINE(7), + 0, /* Termination */ + } + }, + { /* Termination of list */ + .dest = 0, + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_route_values.c b/drivers/comedi/drivers/ni_routing/ni_route_values.c new file mode 100644 index 000000000..54a740b39 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_route_values.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_route_values.c + * Route information for NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 includes the tables that are a list of all the values of various + * signals routes available on NI hardware. In many cases, one does not + * explicitly make these routes, rather one might indicate that something is + * used as the source of one particular trigger or another (using + * *_src=TRIG_EXT). + * + * The contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "ni_route_values.h" +#include "ni_route_values/all.h" + +const struct family_route_values *const ni_all_route_values[] = { + &ni_660x_route_values, + &ni_eseries_route_values, + &ni_mseries_route_values, + NULL, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_route_values.h b/drivers/comedi/drivers/ni_routing/ni_route_values.h new file mode 100644 index 000000000..80880083e --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_route_values.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routing/ni_route_values.h + * Route information for NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_DRIVERS_NI_ROUTINT_NI_ROUTE_VALUES_H +#define _COMEDI_DRIVERS_NI_ROUTINT_NI_ROUTE_VALUES_H + +#include <linux/comedi.h> +#include <linux/types.h> + +/* + * This file includes the tables that are a list of all the values of various + * signals routes available on NI hardware. In many cases, one does not + * explicitly make these routes, rather one might indicate that something is + * used as the source of one particular trigger or another (using + * *_src=TRIG_EXT). + * + * This file is meant to be included by comedi/drivers/ni_routes.c + */ + +#define B(x) ((x) - NI_NAMES_BASE) + +/** Marks a register value as valid, implemented, and tested. */ +#define V(x) (((x) & 0x7f) | 0x80) + +#ifndef NI_ROUTE_VALUE_EXTERNAL_CONVERSION + /** Marks a register value as implemented but needing testing. */ + #define I(x) V(x) + /** Marks a register value as not implemented. */ + #define U(x) 0x0 + + typedef u8 register_type; +#else + /** Marks a register value as implemented but needing testing. */ + #define I(x) (((x) & 0x7f) | 0x100) + /** Marks a register value as not implemented. */ + #define U(x) (((x) & 0x7f) | 0x200) + + /** Tests whether a register is marked as valid/implemented/tested */ + #define MARKED_V(x) (((x) & 0x80) != 0) + /** Tests whether a register is implemented but not tested */ + #define MARKED_I(x) (((x) & 0x100) != 0) + /** Tests whether a register is not implemented */ + #define MARKED_U(x) (((x) & 0x200) != 0) + + /* need more space to store extra marks */ + typedef u16 register_type; +#endif + +/* Mask out the marking bit(s). */ +#define UNMARK(x) ((x) & 0x7f) + +/* + * Gi_SRC(x,1) implements Gi_Src_SubSelect = 1 + * + * This appears to only really be a valid MUX for m-series devices. + */ +#define Gi_SRC(val, subsel) ((val) | ((subsel) << 6)) + +/** + * struct family_route_values - Register values for all routes for a particular + * family. + * @family: lower-case string representation of a specific series or family of + * devices from National Instruments where each member of this family + * shares the same register values for the various signal MUXes. It + * should be noted that not all devices of any family have access to + * all routes defined. + * @register_values: Table of all register values for various signal MUXes on + * National Instruments devices. The first index of this table is the + * signal destination (i.e. identification of the signal MUX). The + * second index of this table is the signal source (i.e. input of the + * signal MUX). + */ +struct family_route_values { + const char *family; + const register_type register_values[NI_NUM_NAMES][NI_NUM_NAMES]; + +}; + +extern const struct family_route_values *const ni_all_route_values[]; + +#endif /* _COMEDI_DRIVERS_NI_ROUTINT_NI_ROUTE_VALUES_H */ diff --git a/drivers/comedi/drivers/ni_routing/ni_route_values/all.h b/drivers/comedi/drivers/ni_routing/ni_route_values/all.h new file mode 100644 index 000000000..30761e55f --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_route_values/all.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routing/ni_route_values/all.h + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#ifndef _COMEDI_DRIVERS_NI_ROUTING_NI_ROUTE_VALUES_EXTERN_H +#define _COMEDI_DRIVERS_NI_ROUTING_NI_ROUTE_VALUES_EXTERN_H + +#include "../ni_route_values.h" + +extern const struct family_route_values ni_660x_route_values; +extern const struct family_route_values ni_eseries_route_values; +extern const struct family_route_values ni_mseries_route_values; + +#endif //_COMEDI_DRIVERS_NI_ROUTING_NI_ROUTE_VALUES_EXTERN_H diff --git a/drivers/comedi/drivers/ni_routing/ni_route_values/ni_660x.c b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_660x.c new file mode 100644 index 000000000..aace60e49 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_660x.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_route_values/ni_660x.c + * Route information for NI_660X boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 includes a list of all the values of various signals routes + * available on NI 660x hardware. In many cases, one does not explicitly make + * these routes, rather one might indicate that something is used as the source + * of one particular trigger or another (using *_src=TRIG_EXT). + * + * The contents of this file can be generated using the tools in + * comedi/drivers/ni_routing/tools. This file also contains specific notes to + * this family of devices. + * + * Please use those tools to help maintain the contents of this file, but be + * mindful to not lose the notes already made in this file, since these notes + * are critical to a complete undertsanding of the register values of this + * family. + */ + +#include "../ni_route_values.h" +#include "all.h" + +const struct family_route_values ni_660x_route_values = { + .family = "ni_660x", + .register_values = { + /* + * destination = { + * source = register value, + * ... + * } + */ + [B(NI_PFI(8))] = { + [B(NI_CtrInternalOutput(7))] = I(1), + }, + [B(NI_PFI(10))] = { + [B(NI_CtrGate(7))] = I(1), + }, + [B(NI_PFI(11))] = { + [B(NI_CtrSource(7))] = I(1), + }, + [B(NI_PFI(12))] = { + [B(NI_CtrInternalOutput(6))] = I(1), + }, + [B(NI_PFI(14))] = { + [B(NI_CtrGate(6))] = I(1), + }, + [B(NI_PFI(15))] = { + [B(NI_CtrSource(6))] = I(1), + }, + [B(NI_PFI(16))] = { + [B(NI_CtrInternalOutput(5))] = I(1), + }, + [B(NI_PFI(18))] = { + [B(NI_CtrGate(5))] = I(1), + }, + [B(NI_PFI(19))] = { + [B(NI_CtrSource(5))] = I(1), + }, + [B(NI_PFI(20))] = { + [B(NI_CtrInternalOutput(4))] = I(1), + }, + [B(NI_PFI(22))] = { + [B(NI_CtrGate(4))] = I(1), + }, + [B(NI_PFI(23))] = { + [B(NI_CtrSource(4))] = I(1), + }, + [B(NI_PFI(24))] = { + [B(NI_CtrInternalOutput(3))] = I(1), + }, + [B(NI_PFI(26))] = { + [B(NI_CtrGate(3))] = I(1), + }, + [B(NI_PFI(27))] = { + [B(NI_CtrSource(3))] = I(1), + }, + [B(NI_PFI(28))] = { + [B(NI_CtrInternalOutput(2))] = I(1), + }, + [B(NI_PFI(30))] = { + [B(NI_CtrGate(2))] = I(1), + }, + [B(NI_PFI(31))] = { + [B(NI_CtrSource(2))] = I(1), + }, + [B(NI_PFI(32))] = { + [B(NI_CtrInternalOutput(1))] = I(1), + }, + [B(NI_PFI(34))] = { + [B(NI_CtrGate(1))] = I(1), + }, + [B(NI_PFI(35))] = { + [B(NI_CtrSource(1))] = I(1), + }, + [B(NI_PFI(36))] = { + [B(NI_CtrInternalOutput(0))] = I(1), + }, + [B(NI_PFI(38))] = { + [B(NI_CtrGate(0))] = I(1), + }, + [B(NI_PFI(39))] = { + [B(NI_CtrSource(0))] = I(1), + }, + [B(NI_CtrSource(0))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2 /* or 1 */), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(1))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(1))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3 /* or 1 */), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(2))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(2))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4 /* or 1 */), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(3))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(3))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5 /* or 1 */), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(4))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(4))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6 /* or 1 */), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(5))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(5))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7 /* or 1 */), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(6))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(6))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9), + [B(NI_PFI(15))] = U(8 /* or 1 */), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(7))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(7))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(11))] = U(9 /* or 1 */), + [B(NI_PFI(15))] = U(8), + [B(NI_PFI(19))] = U(7), + [B(NI_PFI(23))] = U(6), + [B(NI_PFI(27))] = U(5), + [B(NI_PFI(31))] = U(4), + [B(NI_PFI(35))] = U(3), + [B(NI_PFI(39))] = U(2), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrGate(0))] = U(10), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(30), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrGate(0))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2 /* or 1 */), + [B(NI_PFI(39))] = I(0), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(1))] = I(10), + [B(NI_CtrInternalOutput(1))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(1))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3 /* or 1 */), + [B(NI_PFI(35))] = I(0), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(2))] = I(10), + [B(NI_CtrInternalOutput(2))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(2))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4 /* or 1 */), + [B(NI_PFI(31))] = I(0), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(3))] = I(10), + [B(NI_CtrInternalOutput(3))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(3))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5 /* or 1 */), + [B(NI_PFI(27))] = I(0), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(4))] = I(10), + [B(NI_CtrInternalOutput(4))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(4))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6 /* or 1 */), + [B(NI_PFI(23))] = I(0), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(5))] = I(10), + [B(NI_CtrInternalOutput(5))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(5))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7 /* or 1 */), + [B(NI_PFI(19))] = I(0), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(6))] = I(10), + [B(NI_CtrInternalOutput(6))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(6))] = { + [B(NI_PFI(10))] = I(9), + [B(NI_PFI(14))] = I(8 /* or 1 */), + [B(NI_PFI(15))] = I(0), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(7))] = I(10), + [B(NI_CtrInternalOutput(7))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrGate(7))] = { + [B(NI_PFI(10))] = I(9 /* or 1 */), + [B(NI_PFI(11))] = I(0), + [B(NI_PFI(14))] = I(8), + [B(NI_PFI(18))] = I(7), + [B(NI_PFI(22))] = I(6), + [B(NI_PFI(26))] = I(5), + [B(NI_PFI(30))] = I(4), + [B(NI_PFI(34))] = I(3), + [B(NI_PFI(38))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(0))] = I(10), + [B(NI_CtrInternalOutput(0))] = I(20), + [B(NI_LogicLow)] = I(31 /* or 30 */), + }, + [B(NI_CtrAux(0))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2 /* or 1 */), + [B(NI_PFI(39))] = I(0), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(1))] = I(10), + [B(NI_CtrGate(1))] = I(30), + [B(NI_CtrInternalOutput(1))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(1))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3 /* or 1 */), + [B(NI_PFI(35))] = I(0), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(2))] = I(10), + [B(NI_CtrGate(2))] = I(30), + [B(NI_CtrInternalOutput(2))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(2))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4 /* or 1 */), + [B(NI_PFI(31))] = I(0), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(3))] = I(10), + [B(NI_CtrGate(3))] = I(30), + [B(NI_CtrInternalOutput(3))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(3))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5 /* or 1 */), + [B(NI_PFI(27))] = I(0), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(4))] = I(10), + [B(NI_CtrGate(4))] = I(30), + [B(NI_CtrInternalOutput(4))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(4))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6 /* or 1 */), + [B(NI_PFI(23))] = I(0), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(5))] = I(10), + [B(NI_CtrGate(5))] = I(30), + [B(NI_CtrInternalOutput(5))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(5))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7 /* or 1 */), + [B(NI_PFI(19))] = I(0), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(6))] = I(10), + [B(NI_CtrGate(6))] = I(30), + [B(NI_CtrInternalOutput(6))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(6))] = { + [B(NI_PFI(9))] = I(9), + [B(NI_PFI(13))] = I(8 /* or 1 */), + [B(NI_PFI(15))] = I(0), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(7))] = I(10), + [B(NI_CtrGate(7))] = I(30), + [B(NI_CtrInternalOutput(7))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(7))] = { + [B(NI_PFI(9))] = I(9 /* or 1 */), + [B(NI_PFI(11))] = I(0), + [B(NI_PFI(13))] = I(8), + [B(NI_PFI(17))] = I(7), + [B(NI_PFI(21))] = I(6), + [B(NI_PFI(25))] = I(5), + [B(NI_PFI(29))] = I(4), + [B(NI_PFI(33))] = I(3), + [B(NI_PFI(37))] = I(2), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrSource(0))] = I(10), + [B(NI_CtrGate(0))] = I(30), + [B(NI_CtrInternalOutput(0))] = I(20), + [B(NI_LogicLow)] = I(31), + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_route_values/ni_eseries.c b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_eseries.c new file mode 100644 index 000000000..7a52f024c --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_eseries.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_route_values/ni_eseries.c + * Route information for NI_ESERIES boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 includes a list of all the values of various signals routes + * available on NI 660x hardware. In many cases, one does not explicitly make + * these routes, rather one might indicate that something is used as the source + * of one particular trigger or another (using *_src=TRIG_EXT). + * + * The contents of this file can be generated using the tools in + * comedi/drivers/ni_routing/tools. This file also contains specific notes to + * this family of devices. + * + * Please use those tools to help maintain the contents of this file, but be + * mindful to not lose the notes already made in this file, since these notes + * are critical to a complete undertsanding of the register values of this + * family. + */ + +#include "../ni_route_values.h" +#include "all.h" + +/* + * Note that for e-series devices, the backplane TRIGGER_LINE(6) is generally + * not connected to RTSI(6). + */ + +const struct family_route_values ni_eseries_route_values = { + .family = "ni_eseries", + .register_values = { + /* + * destination = { + * source = register value, + * ... + * } + */ + [B(NI_PFI(0))] = { + [B(NI_AI_StartTrigger)] = I(NI_PFI_OUTPUT_AI_START1), + }, + [B(NI_PFI(1))] = { + [B(NI_AI_ReferenceTrigger)] = I(NI_PFI_OUTPUT_AI_START2), + }, + [B(NI_PFI(2))] = { + [B(NI_AI_ConvertClock)] = I(NI_PFI_OUTPUT_AI_CONVERT), + }, + [B(NI_PFI(3))] = { + [B(NI_CtrSource(1))] = I(NI_PFI_OUTPUT_G_SRC1), + }, + [B(NI_PFI(4))] = { + [B(NI_CtrGate(1))] = I(NI_PFI_OUTPUT_G_GATE1), + }, + [B(NI_PFI(5))] = { + [B(NI_AO_SampleClock)] = I(NI_PFI_OUTPUT_AO_UPDATE_N), + }, + [B(NI_PFI(6))] = { + [B(NI_AO_StartTrigger)] = I(NI_PFI_OUTPUT_AO_START1), + }, + [B(NI_PFI(7))] = { + [B(NI_AI_SampleClock)] = I(NI_PFI_OUTPUT_AI_START_PULSE), + }, + [B(NI_PFI(8))] = { + [B(NI_CtrSource(0))] = I(NI_PFI_OUTPUT_G_SRC0), + }, + [B(NI_PFI(9))] = { + [B(NI_CtrGate(0))] = I(NI_PFI_OUTPUT_G_GATE0), + }, + [B(TRIGGER_LINE(0))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(1))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(2))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(3))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(4))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(5))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(6))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(7))] = { + [B(NI_20MHzTimebase)] = I(NI_RTSI_OUTPUT_RTSI_OSC), + }, + [B(NI_RTSI_BRD(0))] = { + [B(TRIGGER_LINE(0))] = I(0), + [B(TRIGGER_LINE(1))] = I(1), + [B(TRIGGER_LINE(2))] = I(2), + [B(TRIGGER_LINE(3))] = I(3), + [B(TRIGGER_LINE(4))] = I(4), + [B(TRIGGER_LINE(5))] = I(5), + [B(TRIGGER_LINE(6))] = I(6), + [B(PXI_Star)] = I(6), + [B(NI_AI_STOP)] = I(7), + }, + [B(NI_RTSI_BRD(1))] = { + [B(TRIGGER_LINE(0))] = I(0), + [B(TRIGGER_LINE(1))] = I(1), + [B(TRIGGER_LINE(2))] = I(2), + [B(TRIGGER_LINE(3))] = I(3), + [B(TRIGGER_LINE(4))] = I(4), + [B(TRIGGER_LINE(5))] = I(5), + [B(TRIGGER_LINE(6))] = I(6), + [B(PXI_Star)] = I(6), + [B(NI_AI_STOP)] = I(7), + }, + [B(NI_RTSI_BRD(2))] = { + [B(TRIGGER_LINE(0))] = I(0), + [B(TRIGGER_LINE(1))] = I(1), + [B(TRIGGER_LINE(2))] = I(2), + [B(TRIGGER_LINE(3))] = I(3), + [B(TRIGGER_LINE(4))] = I(4), + [B(TRIGGER_LINE(5))] = I(5), + [B(TRIGGER_LINE(6))] = I(6), + [B(PXI_Star)] = I(6), + [B(NI_AI_SampleClock)] = I(7), + }, + [B(NI_RTSI_BRD(3))] = { + [B(TRIGGER_LINE(0))] = I(0), + [B(TRIGGER_LINE(1))] = I(1), + [B(TRIGGER_LINE(2))] = I(2), + [B(TRIGGER_LINE(3))] = I(3), + [B(TRIGGER_LINE(4))] = I(4), + [B(TRIGGER_LINE(5))] = I(5), + [B(TRIGGER_LINE(6))] = I(6), + [B(PXI_Star)] = I(6), + [B(NI_AI_SampleClock)] = I(7), + }, + [B(NI_CtrSource(0))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrInternalOutput(1))] = U(19), + [B(PXI_Star)] = U(17), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(1))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(NI_CtrInternalOutput(0))] = U(19), + [B(PXI_Star)] = U(17), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrGate(0))] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrInternalOutput(1))] = I(20), + [B(PXI_Star)] = I(17), + [B(NI_AI_StartTrigger)] = I(21), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrGate(1))] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrInternalOutput(0))] = I(20), + [B(PXI_Star)] = I(17), + [B(NI_AI_StartTrigger)] = I(21), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrOut(0))] = { + [B(TRIGGER_LINE(0))] = I(1), + [B(TRIGGER_LINE(1))] = I(2), + [B(TRIGGER_LINE(2))] = I(3), + [B(TRIGGER_LINE(3))] = I(4), + [B(TRIGGER_LINE(4))] = I(5), + [B(TRIGGER_LINE(5))] = I(6), + [B(TRIGGER_LINE(6))] = I(7), + [B(NI_CtrInternalOutput(0))] = I(0), + [B(PXI_Star)] = I(7), + }, + [B(NI_CtrOut(1))] = { + [B(NI_CtrInternalOutput(1))] = I(0), + }, + [B(NI_AI_SampleClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrInternalOutput(0))] = I(19), + [B(PXI_Star)] = I(17), + [B(NI_AI_SampleClockTimebase)] = I(0), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AI_SampleClockTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(PXI_Star)] = U(17), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_100kHzTimebase)] = U(19), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_AI_StartTrigger)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrInternalOutput(0))] = I(18), + [B(PXI_Star)] = I(17), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AI_ReferenceTrigger)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(PXI_Star)] = U(17), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_AI_ConvertClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrInternalOutput(0))] = I(19), + [B(PXI_Star)] = I(17), + [B(NI_AI_ConvertClockTimebase)] = I(0), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AI_ConvertClockTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(NI_AI_SampleClockTimebase)] = U(0), + [B(NI_20MHzTimebase)] = U(1), + }, + [B(NI_AI_PauseTrigger)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(PXI_Star)] = U(17), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_AO_SampleClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(NI_CtrInternalOutput(1))] = I(19), + [B(PXI_Star)] = I(17), + [B(NI_AO_SampleClockTimebase)] = I(0), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AO_SampleClockTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(PXI_Star)] = U(17), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_100kHzTimebase)] = U(19), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_AO_StartTrigger)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(PXI_Star)] = I(17), + /* + * for the signal route + * (NI_AI_StartTrigger->NI_AO_StartTrigger), MHDDK says + * used register value 18 and DAQ-STC says 19. + * Hoping that the MHDDK is correct--being a "working" + * example. + */ + [B(NI_AI_StartTrigger)] = I(18), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AO_PauseTrigger)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(PXI_Star)] = U(17), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_MasterTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(TRIGGER_LINE(7))] = U(1), + [B(PXI_Star)] = U(2), + [B(PXI_Clk10)] = U(3), + [B(NI_10MHzRefClock)] = U(0), + }, + /* + * This symbol is not defined and nothing for this is + * implemented--just including this because data was found in + * the NI-STC for it--can't remember where. + * [B(NI_FrequencyOutTimebase)] = { + * ** These are not currently implemented in ni modules ** + * [B(NI_20MHzTimebase)] = U(0), + * [B(NI_100kHzTimebase)] = U(1), + * }, + */ + [B(NI_RGOUT0)] = { + [B(NI_CtrInternalOutput(0))] = I(0), + [B(NI_CtrOut(0))] = I(1), + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/ni_route_values/ni_mseries.c b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_mseries.c new file mode 100644 index 000000000..d1ddd13b3 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_mseries.c @@ -0,0 +1,1751 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/ni_route_values/ni_mseries.c + * Route information for NI_MSERIES boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 includes a list of all the values of various signals routes + * available on NI 660x hardware. In many cases, one does not explicitly make + * these routes, rather one might indicate that something is used as the source + * of one particular trigger or another (using *_src=TRIG_EXT). + * + * The contents of this file can be generated using the tools in + * comedi/drivers/ni_routing/tools. This file also contains specific notes to + * this family of devices. + * + * Please use those tools to help maintain the contents of this file, but be + * mindful to not lose the notes already made in this file, since these notes + * are critical to a complete undertsanding of the register values of this + * family. + */ + +#include "../ni_route_values.h" +#include "all.h" + +/* + * GATE SELECT NOTE: + * CtrAux and CtrArmStartrigger register values are not documented in the + * DAQ-STC. There is some evidence that using CtrGate values is valid (see + * comedi.h). Some information and hints exist in the M-Series user manual + * (ni-62xx user-manual 371022K-01). + */ + +const struct family_route_values ni_mseries_route_values = { + .family = "ni_mseries", + .register_values = { + /* + * destination = { + * source = register value, + * ... + * } + */ + [B(NI_PFI(0))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(1))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(2))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(3))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(4))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(5))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(6))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(7))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(8))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(9))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(10))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(11))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(12))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(13))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(14))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(NI_PFI(15))] = { + [B(TRIGGER_LINE(0))] = I(18), + [B(TRIGGER_LINE(1))] = I(19), + [B(TRIGGER_LINE(2))] = I(20), + [B(TRIGGER_LINE(3))] = I(21), + [B(TRIGGER_LINE(4))] = I(22), + [B(TRIGGER_LINE(5))] = I(23), + [B(TRIGGER_LINE(6))] = I(24), + [B(TRIGGER_LINE(7))] = I(25), + [B(NI_CtrSource(0))] = I(9), + [B(NI_CtrSource(1))] = I(4), + [B(NI_CtrGate(0))] = I(10), + [B(NI_CtrGate(1))] = I(5), + [B(NI_CtrInternalOutput(0))] = I(13), + [B(NI_CtrInternalOutput(1))] = I(14), + [B(PXI_Star)] = I(26), + [B(NI_AI_SampleClock)] = I(8), + [B(NI_AI_StartTrigger)] = I(1), + [B(NI_AI_ReferenceTrigger)] = I(2), + [B(NI_AI_ConvertClock)] = I(3), + [B(NI_AI_ExternalMUXClock)] = I(12), + [B(NI_AO_SampleClock)] = I(6), + [B(NI_AO_StartTrigger)] = I(7), + [B(NI_DI_SampleClock)] = I(29), + [B(NI_DO_SampleClock)] = I(30), + [B(NI_FrequencyOutput)] = I(15), + [B(NI_ChangeDetectionEvent)] = I(28), + [B(NI_AnalogComparisonEvent)] = I(17), + [B(NI_SCXI_Trig1)] = I(27), + [B(NI_ExternalStrobe)] = I(11), + [B(NI_PFI_DO)] = I(16), + }, + [B(TRIGGER_LINE(0))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(1))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(2))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(3))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(4))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(5))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(6))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(TRIGGER_LINE(7))] = { + [B(NI_RTSI_BRD(0))] = I(8), + [B(NI_RTSI_BRD(1))] = I(9), + [B(NI_RTSI_BRD(2))] = I(10), + [B(NI_RTSI_BRD(3))] = I(11), + [B(NI_CtrSource(0))] = I(5), + [B(NI_CtrGate(0))] = I(6), + [B(NI_AI_StartTrigger)] = I(0), + [B(NI_AI_ReferenceTrigger)] = I(1), + [B(NI_AI_ConvertClock)] = I(2), + [B(NI_AO_SampleClock)] = I(3), + [B(NI_AO_StartTrigger)] = I(4), + /* + * for (*->TRIGGER_LINE(*)) MUX, a value of 12 should be + * RTSI_OSC according to MHDDK mseries source. There + * are hints in comedi that show that this is actually a + * 20MHz source for 628x cards(?) + */ + [B(NI_10MHzRefClock)] = I(12), + [B(NI_RGOUT0)] = I(7), + }, + [B(NI_RTSI_BRD(0))] = { + [B(NI_PFI(0))] = I(0), + [B(NI_PFI(1))] = I(1), + [B(NI_PFI(2))] = I(2), + [B(NI_PFI(3))] = I(3), + [B(NI_PFI(4))] = I(4), + [B(NI_PFI(5))] = I(5), + [B(NI_CtrSource(1))] = I(11), + [B(NI_CtrGate(1))] = I(10), + [B(NI_CtrZ(0))] = I(13), + [B(NI_CtrZ(1))] = I(12), + [B(NI_CtrOut(1))] = I(9), + [B(NI_AI_SampleClock)] = I(15), + [B(NI_AI_PauseTrigger)] = I(7), + [B(NI_AO_PauseTrigger)] = I(6), + [B(NI_FrequencyOutput)] = I(8), + [B(NI_AnalogComparisonEvent)] = I(14), + }, + [B(NI_RTSI_BRD(1))] = { + [B(NI_PFI(0))] = I(0), + [B(NI_PFI(1))] = I(1), + [B(NI_PFI(2))] = I(2), + [B(NI_PFI(3))] = I(3), + [B(NI_PFI(4))] = I(4), + [B(NI_PFI(5))] = I(5), + [B(NI_CtrSource(1))] = I(11), + [B(NI_CtrGate(1))] = I(10), + [B(NI_CtrZ(0))] = I(13), + [B(NI_CtrZ(1))] = I(12), + [B(NI_CtrOut(1))] = I(9), + [B(NI_AI_SampleClock)] = I(15), + [B(NI_AI_PauseTrigger)] = I(7), + [B(NI_AO_PauseTrigger)] = I(6), + [B(NI_FrequencyOutput)] = I(8), + [B(NI_AnalogComparisonEvent)] = I(14), + }, + [B(NI_RTSI_BRD(2))] = { + [B(NI_PFI(0))] = I(0), + [B(NI_PFI(1))] = I(1), + [B(NI_PFI(2))] = I(2), + [B(NI_PFI(3))] = I(3), + [B(NI_PFI(4))] = I(4), + [B(NI_PFI(5))] = I(5), + [B(NI_CtrSource(1))] = I(11), + [B(NI_CtrGate(1))] = I(10), + [B(NI_CtrZ(0))] = I(13), + [B(NI_CtrZ(1))] = I(12), + [B(NI_CtrOut(1))] = I(9), + [B(NI_AI_SampleClock)] = I(15), + [B(NI_AI_PauseTrigger)] = I(7), + [B(NI_AO_PauseTrigger)] = I(6), + [B(NI_FrequencyOutput)] = I(8), + [B(NI_AnalogComparisonEvent)] = I(14), + }, + [B(NI_RTSI_BRD(3))] = { + [B(NI_PFI(0))] = I(0), + [B(NI_PFI(1))] = I(1), + [B(NI_PFI(2))] = I(2), + [B(NI_PFI(3))] = I(3), + [B(NI_PFI(4))] = I(4), + [B(NI_PFI(5))] = I(5), + [B(NI_CtrSource(1))] = I(11), + [B(NI_CtrGate(1))] = I(10), + [B(NI_CtrZ(0))] = I(13), + [B(NI_CtrZ(1))] = I(12), + [B(NI_CtrOut(1))] = I(9), + [B(NI_AI_SampleClock)] = I(15), + [B(NI_AI_PauseTrigger)] = I(7), + [B(NI_AO_PauseTrigger)] = I(6), + [B(NI_FrequencyOutput)] = I(8), + [B(NI_AnalogComparisonEvent)] = I(14), + }, + [B(NI_CtrSource(0))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(NI_CtrGate(1))] = U(Gi_SRC(20, 0)), + [B(NI_CtrInternalOutput(1))] = U(19), + [B(PXI_Star)] = U(Gi_SRC(20, 1)), + [B(PXI_Clk10)] = U(29), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(Gi_SRC(30, 0)), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_AnalogComparisonEvent)] = U(Gi_SRC(30, 1)), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrSource(1))] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(NI_CtrGate(0))] = U(Gi_SRC(20, 0)), + [B(NI_CtrInternalOutput(0))] = U(19), + [B(PXI_Star)] = U(Gi_SRC(20, 1)), + [B(PXI_Clk10)] = U(29), + [B(NI_20MHzTimebase)] = U(0), + [B(NI_80MHzTimebase)] = U(Gi_SRC(30, 0)), + [B(NI_100kHzTimebase)] = U(18), + [B(NI_AnalogComparisonEvent)] = U(Gi_SRC(30, 1)), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_CtrGate(0))] = { + [B(NI_PFI(0))] = I(1 /* source: mhddk examples */), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrSource(1))] = I(29), + /* source for following line: mhddk GP examples */ + [B(NI_CtrInternalOutput(1))] = I(20), + [B(PXI_Star)] = I(19), + [B(NI_AI_StartTrigger)] = I(28), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrGate(1))] = { + /* source for following line: mhddk examples */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrSource(0))] = I(29), + /* source for following line: mhddk GP examples */ + [B(NI_CtrInternalOutput(0))] = I(20), + [B(PXI_Star)] = I(19), + [B(NI_AI_StartTrigger)] = I(28), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(0))] = { + /* these are just a guess; see GATE SELECT NOTE */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrSource(1))] = I(29), + /* source for following line: mhddk GP examples */ + [B(NI_CtrInternalOutput(1))] = I(20), + [B(PXI_Star)] = I(19), + [B(NI_AI_StartTrigger)] = I(28), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrAux(1))] = { + /* these are just a guess; see GATE SELECT NOTE */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrSource(0))] = I(29), + /* source for following line: mhddk GP examples */ + [B(NI_CtrInternalOutput(0))] = I(20), + [B(PXI_Star)] = I(19), + [B(NI_AI_StartTrigger)] = I(28), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrA(0))] = { + /* + * See nimseries/Examples for outputs; inputs a guess + * from device routes shown on NI-MAX. + * see M-Series user manual (371022K-01) + */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrA(1))] = { + /* + * See nimseries/Examples for outputs; inputs a guess + * from device routes shown on NI-MAX. + * see M-Series user manual (371022K-01) + */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrB(0))] = { + /* + * See nimseries/Examples for outputs; inputs a guess + * from device routes shown on NI-MAX. + * see M-Series user manual (371022K-01) + */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrB(1))] = { + /* + * See nimseries/Examples for outputs; inputs a guess + * from device routes shown on NI-MAX. + * see M-Series user manual (371022K-01) + */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrZ(0))] = { + /* + * See nimseries/Examples for outputs; inputs a guess + * from device routes shown on NI-MAX. + * see M-Series user manual (371022K-01) + */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrZ(1))] = { + /* + * See nimseries/Examples for outputs; inputs a guess + * from device routes shown on NI-MAX. + * see M-Series user manual (371022K-01) + */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrArmStartTrigger(0))] = { + /* these are just a guess; see GATE SELECT NOTE */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrSource(1))] = I(29), + /* source for following line: mhddk GP examples */ + [B(NI_CtrInternalOutput(1))] = I(20), + [B(PXI_Star)] = I(19), + [B(NI_AI_StartTrigger)] = I(28), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrArmStartTrigger(1))] = { + /* these are just a guess; see GATE SELECT NOTE */ + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrSource(0))] = I(29), + /* source for following line: mhddk GP examples */ + [B(NI_CtrInternalOutput(0))] = I(20), + [B(PXI_Star)] = I(19), + [B(NI_AI_StartTrigger)] = I(28), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_CtrOut(0))] = { + [B(TRIGGER_LINE(0))] = I(1), + [B(TRIGGER_LINE(1))] = I(2), + [B(TRIGGER_LINE(2))] = I(3), + [B(TRIGGER_LINE(3))] = I(4), + [B(TRIGGER_LINE(4))] = I(5), + [B(TRIGGER_LINE(5))] = I(6), + [B(TRIGGER_LINE(6))] = I(7), + [B(NI_CtrInternalOutput(0))] = I(0), + }, + [B(NI_CtrOut(1))] = { + [B(NI_CtrInternalOutput(1))] = I(0), + }, + [B(NI_AI_SampleClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrInternalOutput(0))] = I(19), + [B(NI_CtrInternalOutput(1))] = I(28), + [B(PXI_Star)] = I(20), + [B(NI_AI_SampleClockTimebase)] = I(0), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_SCXI_Trig1)] = I(29), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AI_SampleClockTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(PXI_Star)] = U(20), + [B(PXI_Clk10)] = U(29), + /* + * For routes (*->NI_AI_SampleClockTimebase) and + * (*->NI_AO_SampleClockTimebase), tMSeries.h of MHDDK + * shows 0 value as selecting ground (case ground?) and + * 28 value selecting TIMEBASE 1. + */ + [B(NI_20MHzTimebase)] = U(28), + [B(NI_100kHzTimebase)] = U(19), + [B(NI_AnalogComparisonEvent)] = U(30), + [B(NI_LogicLow)] = U(31), + [B(NI_CaseGround)] = U(0), + }, + [B(NI_AI_StartTrigger)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrInternalOutput(0))] = I(18), + [B(NI_CtrInternalOutput(1))] = I(19), + [B(PXI_Star)] = I(20), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AI_ReferenceTrigger)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(PXI_Star)] = U(20), + [B(NI_AnalogComparisonEvent)] = U(30), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_AI_ConvertClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + /* source for following line: mhddk example headers */ + [B(NI_CtrInternalOutput(0))] = I(19), + /* source for following line: mhddk example headers */ + [B(NI_CtrInternalOutput(1))] = I(18), + [B(PXI_Star)] = I(20), + [B(NI_AI_ConvertClockTimebase)] = I(0), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AI_ConvertClockTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(NI_AI_SampleClockTimebase)] = U(0), + [B(NI_20MHzTimebase)] = U(1), + }, + [B(NI_AI_PauseTrigger)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(PXI_Star)] = U(20), + [B(NI_AnalogComparisonEvent)] = U(30), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_AO_SampleClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrInternalOutput(0))] = I(18), + [B(NI_CtrInternalOutput(1))] = I(19), + [B(PXI_Star)] = I(20), + [B(NI_AO_SampleClockTimebase)] = I(0), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AO_SampleClockTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(PXI_Star)] = U(20), + [B(PXI_Clk10)] = U(29), + /* + * For routes (*->NI_AI_SampleClockTimebase) and + * (*->NI_AO_SampleClockTimebase), tMSeries.h of MHDDK + * shows 0 value as selecting ground (case ground?) and + * 28 value selecting TIMEBASE 1. + */ + [B(NI_20MHzTimebase)] = U(28), + [B(NI_100kHzTimebase)] = U(19), + [B(NI_AnalogComparisonEvent)] = U(30), + [B(NI_LogicLow)] = U(31), + [B(NI_CaseGround)] = U(0), + }, + [B(NI_AO_StartTrigger)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(PXI_Star)] = I(20), + /* + * for the signal route + * (NI_AI_StartTrigger->NI_AO_StartTrigger), DAQ-STC & + * MHDDK disagreed for e-series. MHDDK for m-series + * agrees with DAQ-STC description and uses the value 18 + * for the route + * (NI_AI_ReferenceTrigger->NI_AO_StartTrigger). The + * m-series devices are supposed to have DAQ-STC2. + * There are no DAQ-STC2 docs to compare with. + */ + [B(NI_AI_StartTrigger)] = I(19), + [B(NI_AI_ReferenceTrigger)] = I(18), + [B(NI_AnalogComparisonEvent)] = I(30), + [B(NI_LogicLow)] = I(31), + }, + [B(NI_AO_PauseTrigger)] = { + /* These are not currently implemented in ni modules */ + [B(NI_PFI(0))] = U(1), + [B(NI_PFI(1))] = U(2), + [B(NI_PFI(2))] = U(3), + [B(NI_PFI(3))] = U(4), + [B(NI_PFI(4))] = U(5), + [B(NI_PFI(5))] = U(6), + [B(NI_PFI(6))] = U(7), + [B(NI_PFI(7))] = U(8), + [B(NI_PFI(8))] = U(9), + [B(NI_PFI(9))] = U(10), + [B(NI_PFI(10))] = U(21), + [B(NI_PFI(11))] = U(22), + [B(NI_PFI(12))] = U(23), + [B(NI_PFI(13))] = U(24), + [B(NI_PFI(14))] = U(25), + [B(NI_PFI(15))] = U(26), + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(PXI_Star)] = U(20), + [B(NI_AnalogComparisonEvent)] = U(30), + [B(NI_LogicLow)] = U(31), + }, + [B(NI_DI_SampleClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrInternalOutput(0))] = I(28), + [B(NI_CtrInternalOutput(1))] = I(29), + [B(PXI_Star)] = I(20), + [B(NI_AI_SampleClock)] = I(18), + [B(NI_AI_ConvertClock)] = I(19), + [B(NI_AO_SampleClock)] = I(31), + [B(NI_FrequencyOutput)] = I(32), + [B(NI_ChangeDetectionEvent)] = I(33), + [B(NI_CaseGround)] = I(0), + }, + [B(NI_DO_SampleClock)] = { + [B(NI_PFI(0))] = I(1), + [B(NI_PFI(1))] = I(2), + [B(NI_PFI(2))] = I(3), + [B(NI_PFI(3))] = I(4), + [B(NI_PFI(4))] = I(5), + [B(NI_PFI(5))] = I(6), + [B(NI_PFI(6))] = I(7), + [B(NI_PFI(7))] = I(8), + [B(NI_PFI(8))] = I(9), + [B(NI_PFI(9))] = I(10), + [B(NI_PFI(10))] = I(21), + [B(NI_PFI(11))] = I(22), + [B(NI_PFI(12))] = I(23), + [B(NI_PFI(13))] = I(24), + [B(NI_PFI(14))] = I(25), + [B(NI_PFI(15))] = I(26), + [B(TRIGGER_LINE(0))] = I(11), + [B(TRIGGER_LINE(1))] = I(12), + [B(TRIGGER_LINE(2))] = I(13), + [B(TRIGGER_LINE(3))] = I(14), + [B(TRIGGER_LINE(4))] = I(15), + [B(TRIGGER_LINE(5))] = I(16), + [B(TRIGGER_LINE(6))] = I(17), + [B(TRIGGER_LINE(7))] = I(27), + [B(NI_CtrInternalOutput(0))] = I(28), + [B(NI_CtrInternalOutput(1))] = I(29), + [B(PXI_Star)] = I(20), + [B(NI_AI_SampleClock)] = I(18), + [B(NI_AI_ConvertClock)] = I(19), + [B(NI_AO_SampleClock)] = I(31), + [B(NI_FrequencyOutput)] = I(32), + [B(NI_ChangeDetectionEvent)] = I(33), + [B(NI_CaseGround)] = I(0), + }, + [B(NI_MasterTimebase)] = { + /* These are not currently implemented in ni modules */ + [B(TRIGGER_LINE(0))] = U(11), + [B(TRIGGER_LINE(1))] = U(12), + [B(TRIGGER_LINE(2))] = U(13), + [B(TRIGGER_LINE(3))] = U(14), + [B(TRIGGER_LINE(4))] = U(15), + [B(TRIGGER_LINE(5))] = U(16), + [B(TRIGGER_LINE(6))] = U(17), + [B(TRIGGER_LINE(7))] = U(27), + [B(PXI_Star)] = U(20), + [B(PXI_Clk10)] = U(29), + [B(NI_10MHzRefClock)] = U(0), + }, + /* + * This symbol is not defined and nothing for this is + * implemented--just including this because data was found in + * the NI-STC for it--can't remember where. + * [B(NI_FrequencyOutTimebase)] = { + * ** These are not currently implemented in ni modules ** + * [B(NI_20MHzTimebase)] = U(0), + * [B(NI_100kHzTimebase)] = U(1), + * }, + */ + [B(NI_RGOUT0)] = { + [B(NI_CtrInternalOutput(0))] = I(0), + [B(NI_CtrOut(0))] = I(1), + }, + }, +}; diff --git a/drivers/comedi/drivers/ni_routing/tools/.gitignore b/drivers/comedi/drivers/ni_routing/tools/.gitignore new file mode 100644 index 000000000..c12f825db --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/.gitignore @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +comedi_h.py +*.pyc +ni_values.py +convert_c_to_py +c/ +csv/ +linux/ +all_cfiles.c diff --git a/drivers/comedi/drivers/ni_routing/tools/Makefile b/drivers/comedi/drivers/ni_routing/tools/Makefile new file mode 100644 index 000000000..31212101b --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/Makefile @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: GPL-2.0 +# this make file is simply to help autogenerate these files: +# ni_route_values.h +# ni_device_routes.h +# in order to do this, we are also generating a python representation (using +# ctypesgen) of ../../../../../include/uapi/linux/comedi.h. +# This allows us to sort NI signal/terminal names numerically to use a binary +# search through the device_routes tables to find valid routes. + +ALL: + @echo Typical targets: + @echo "\`make csv-files\`" + @echo " Creates new csv-files using content of c-files of existing" + @echo " ni_routing/* content. New csv files are placed in csv" + @echo " sub-directory." + @echo "\`make c-files\`" + @echo " Creates new c-files using content of csv sub-directory. These" + @echo " new c-files can be compared to the active content in the" + @echo " ni_routing directory." + @echo "\`make csv-blank\`" + @echo " Create a new blank csv file. This is useful for establishing a" + @echo " new data table for either a device family \(less likely\) or a" + @echo " specific board of an existing device family \(more likely\)." + @echo "\`make clean-partial\`" + @echo " Remove all generated files/directories EXCEPT for csv/c files." + @echo "\`make clean\`" + @echo " Remove all generated files/directories." + @echo "\`make everything\`" + @echo " Build all csv-files, then all new c-files." + +everything : csv-files c-files csv-blank + +CPPFLAGS = -D__user= +INC_UAPI = ../../../../../include/uapi + +comedi_h.py: $(INC_UAPI)/linux/comedi.h + ctypesgen $< --include "sys/ioctl.h" --cpp 'gcc -E $(CPPFLAGS)' -o $@ + +convert_c_to_py: all_cfiles.c linux/comedi.h + gcc -g -I. convert_c_to_py.c -o convert_c_to_py -std=c99 + +# Create a local 'linux/comedi.h' for use when compiling 'convert_c_to_py.c' +# with the '-I.' option. (Cannot specify '-I../../../../../include/uapi' +# because that interferes with inclusion of other system headers.) +linux/comedi.h: $(INC_UAPI)/linux/comedi.h + mkdir -p linux + ln -snf ../$< $@ + +ni_values.py: convert_c_to_py + ./convert_c_to_py + +csv-files : ni_values.py comedi_h.py + ./convert_py_to_csv.py + +csv-blank : comedi_h.py + ./make_blank_csv.py + @echo New blank csv signal table in csv/blank_route_table.csv + +c-files : comedi_h.py + ./convert_csv_to_c.py --route_values --device_routes + +ROUTE_VALUES_SRC=$(wildcard ../ni_route_values/*.c) +DEVICE_ROUTES_SRC=$(wildcard ../ni_device_routes/*.c) +all_cfiles.c : $(DEVICE_ROUTES_SRC) $(ROUTE_VALUES_SRC) + @for i in $(DEVICE_ROUTES_SRC) $(ROUTE_VALUES_SRC); do \ + echo "#include \"$$i\"" >> all_cfiles.c; \ + done + +clean-partial : + $(RM) -rf comedi_h.py ni_values.py convert_c_to_py all_cfiles.c *.pyc \ + __pycache__/ + +clean : clean-partial + $(RM) -rf c/ csv/ linux/ + +# Note: One could also use ctypeslib in order to generate these files. The +# caveat is that ctypeslib does not do a great job at handling macro functions. +# The make rules are as follows: +# comedi.h.xml : $(INC_UAPI)/linux/comedi.h +# # note that we have to use PWD here to avoid h2xml finding a system +# # installed version of the comedilib/comedi.h file +# h2xml ${PWD}/$(INC_UAPI)/linux/comedi.h -c D__user="" -o comedi.h.xml +# +# comedi_h.py : comedi.h.xml +# xml2py ./comedi.h.xml -o comedi_h.py +# clean : +# rm -f comedi.h.xml comedi_h.py comedi_h.pyc diff --git a/drivers/comedi/drivers/ni_routing/tools/convert_c_to_py.c b/drivers/comedi/drivers/ni_routing/tools/convert_c_to_py.c new file mode 100644 index 000000000..d55521b5b --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/convert_c_to_py.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <stdint.h> +#include <stdbool.h> +#include <stddef.h> +#include <errno.h> +#include <stdlib.h> + +typedef uint8_t u8; +typedef uint16_t u16; +typedef int8_t s8; +#define __user +#define BIT(x) (1UL << (x)) + +#define NI_ROUTE_VALUE_EXTERNAL_CONVERSION 1 + +#include "../ni_route_values.c" +#include "../ni_device_routes.c" +#include "all_cfiles.c" + +#include <stdio.h> + +#define RVij(rv, src, dest) ((rv)->register_values[(dest)][(src)]) + +/* + * write out + * { + * "family" : "<family-name>", + * "register_values": { + * <destination0>:[src0, src1, ...], + * <destination0>:[src0, src1, ...], + * ... + * } + * } + */ +void family_write(const struct family_route_values *rv, FILE *fp) +{ + fprintf(fp, + " \"%s\" : {\n" + " # dest -> {src0:val0, src1:val1, ...}\n" + , rv->family); + for (unsigned int dest = NI_NAMES_BASE; + dest < (NI_NAMES_BASE + NI_NUM_NAMES); + ++dest) { + unsigned int src = NI_NAMES_BASE; + + for (; src < (NI_NAMES_BASE + NI_NUM_NAMES) && + RVij(rv, B(src), B(dest)) == 0; ++src) + ; + + if (src >= (NI_NAMES_BASE + NI_NUM_NAMES)) + continue; /* no data here */ + + fprintf(fp, " %u : {\n", dest); + for (src = NI_NAMES_BASE; src < (NI_NAMES_BASE + NI_NUM_NAMES); + ++src) { + register_type r = RVij(rv, B(src), B(dest)); + const char *M; + + if (r == 0) { + continue; + } else if (MARKED_V(r)) { + M = "V"; + } else if (MARKED_I(r)) { + M = "I"; + } else if (MARKED_U(r)) { + M = "U"; + } else { + fprintf(stderr, + "Invalid register marking %s[%u][%u] = %u\n", + rv->family, dest, src, r); + exit(1); + } + + fprintf(fp, " %u : \"%s(%u)\",\n", + src, M, UNMARK(r)); + } + fprintf(fp, " },\n"); + } + fprintf(fp, " },\n\n"); +} + +bool is_valid_ni_sig(unsigned int sig) +{ + return (sig >= NI_NAMES_BASE) && (sig < (NI_NAMES_BASE + NI_NUM_NAMES)); +} + +/* + * write out + * { + * "family" : "<family-name>", + * "register_values": { + * <destination0>:[src0, src1, ...], + * <destination0>:[src0, src1, ...], + * ... + * } + * } + */ +void device_write(const struct ni_device_routes *dR, FILE *fp) +{ + fprintf(fp, + " \"%s\" : {\n" + " # dest -> [src0, src1, ...]\n" + , dR->device); + + unsigned int i = 0; + + while (dR->routes[i].dest != 0) { + if (!is_valid_ni_sig(dR->routes[i].dest)) { + fprintf(stderr, + "Invalid NI signal value [%u] for destination %s.[%u]\n", + dR->routes[i].dest, dR->device, i); + exit(1); + } + + fprintf(fp, " %u : [", dR->routes[i].dest); + + unsigned int j = 0; + + while (dR->routes[i].src[j] != 0) { + if (!is_valid_ni_sig(dR->routes[i].src[j])) { + fprintf(stderr, + "Invalid NI signal value [%u] for source %s.[%u].[%u]\n", + dR->routes[i].src[j], dR->device, i, j); + exit(1); + } + + fprintf(fp, "%u,", dR->routes[i].src[j]); + + ++j; + } + fprintf(fp, "],\n"); + + ++i; + } + fprintf(fp, " },\n\n"); +} + +int main(void) +{ + FILE *fp = fopen("ni_values.py", "w"); + + /* write route register values */ + fprintf(fp, "ni_route_values = {\n"); + for (int i = 0; ni_all_route_values[i]; ++i) + family_write(ni_all_route_values[i], fp); + fprintf(fp, "}\n\n"); + + /* write valid device routes */ + fprintf(fp, "ni_device_routes = {\n"); + for (int i = 0; ni_device_routes_list[i]; ++i) + device_write(ni_device_routes_list[i], fp); + fprintf(fp, "}\n"); + + /* finish; close file */ + fclose(fp); + return 0; +} diff --git a/drivers/comedi/drivers/ni_routing/tools/convert_csv_to_c.py b/drivers/comedi/drivers/ni_routing/tools/convert_csv_to_c.py new file mode 100755 index 000000000..90378fb50 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/convert_csv_to_c.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ + +# This is simply to aide in creating the entries in the order of the value of +# the device-global NI signal/terminal constants defined in comedi.h +import comedi_h +import os, sys, re +from csv_collection import CSVCollection + + +def c_to_o(filename, prefix='\t\t\t\t\t ni_routing/', suffix=' \\'): + if not filename.endswith('.c'): + return '' + return prefix + filename.rpartition('.c')[0] + '.o' + suffix + + +def routedict_to_structinit_single(name, D, return_name=False): + Locals = dict() + lines = [ + '\t.family = "{}",'.format(name), + '\t.register_values = {', + '\t\t/*', + '\t\t * destination = {', + '\t\t * source = register value,', + '\t\t * ...', + '\t\t * }', + '\t\t */', + ] + if (False): + # print table with index0:src, index1:dest + D0 = D # (src-> dest->reg_value) + #D1 : destD + else: + D0 = dict() + for src, destD in D.items(): + for dest, val in destD.items(): + D0.setdefault(dest, {})[src] = val + + + D0 = sorted(D0.items(), key=lambda i: eval(i[0], comedi_h.__dict__, Locals)) + + for D0_sig, D1_D in D0: + D1 = sorted(D1_D.items(), key=lambda i: eval(i[0], comedi_h.__dict__, Locals)) + + lines.append('\t\t[B({})] = {{'.format(D0_sig)) + for D1_sig, value in D1: + if not re.match('[VIU]\([^)]*\)', value): + sys.stderr.write('Invalid register format: {}\n'.format(repr(value))) + sys.stderr.write( + 'Register values should be formatted with V(),I(),or U()\n') + raise RuntimeError('Invalid register values format') + lines.append('\t\t\t[B({})]\t= {},'.format(D1_sig, value)) + lines.append('\t\t},') + lines.append('\t},') + + lines = '\n'.join(lines) + if return_name: + return N, lines + else: + return lines + + +def routedict_to_routelist_single(name, D, indent=1): + Locals = dict() + + indents = dict( + I0 = '\t'*(indent), + I1 = '\t'*(indent+1), + I2 = '\t'*(indent+2), + I3 = '\t'*(indent+3), + I4 = '\t'*(indent+4), + ) + + if (False): + # data is src -> dest-list + D0 = D + keyname = 'src' + valname = 'dest' + else: + # data is dest -> src-list + keyname = 'dest' + valname = 'src' + D0 = dict() + for src, destD in D.items(): + for dest, val in destD.items(): + D0.setdefault(dest, {})[src] = val + + # Sort by order of device-global names (numerically) + D0 = sorted(D0.items(), key=lambda i: eval(i[0], comedi_h.__dict__, Locals)) + + lines = [ '{I0}.device = "{name}",\n' + '{I0}.routes = (struct ni_route_set[]){{' + .format(name=name, **indents) ] + for D0_sig, D1_D in D0: + D1 = [ k for k,v in D1_D.items() if v ] + D1.sort(key=lambda i: eval(i, comedi_h.__dict__, Locals)) + + lines.append('{I1}{{\n{I2}.{keyname} = {D0_sig},\n' + '{I2}.{valname} = (int[]){{' + .format(keyname=keyname, valname=valname, D0_sig=D0_sig, **indents) + ) + for D1_sig in D1: + lines.append( '{I3}{D1_sig},'.format(D1_sig=D1_sig, **indents) ) + lines.append( '{I3}0, /* Termination */'.format(**indents) ) + + lines.append('{I2}}}\n{I1}}},'.format(**indents)) + + lines.append('{I1}{{ /* Termination of list */\n{I2}.{keyname} = 0,\n{I1}}},' + .format(keyname=keyname, **indents)) + + lines.append('{I0}}},'.format(**indents)) + + return '\n'.join(lines) + + +class DeviceRoutes(CSVCollection): + MKFILE_SEGMENTS = 'device-route.mk' + SET_C = 'ni_device_routes.c' + ITEMS_DIR = 'ni_device_routes' + EXTERN_H = 'all.h' + OUTPUT_DIR = 'c' + + output_file_top = """\ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/{filename} + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "ni_device_routes.h" +#include "{extern_h}"\ +""".format(filename=SET_C, extern_h=os.path.join(ITEMS_DIR, EXTERN_H)) + + extern_header = """\ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routing/{filename} + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#ifndef _COMEDI_DRIVERS_NI_ROUTING_NI_DEVICE_ROUTES_EXTERN_H +#define _COMEDI_DRIVERS_NI_ROUTING_NI_DEVICE_ROUTES_EXTERN_H + +#include "../ni_device_routes.h" + +{externs} + +#endif //_COMEDI_DRIVERS_NI_ROUTING_NI_DEVICE_ROUTES_EXTERN_H +""" + + single_output_file_top = """\ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/{filename} + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "../ni_device_routes.h" +#include "{extern_h}" + +struct ni_device_routes {table_name} = {{\ +""" + + def __init__(self, pattern='csv/device_routes/*.csv'): + super(DeviceRoutes,self).__init__(pattern) + + def to_listinit(self): + chunks = [ self.output_file_top, + '', + 'struct ni_device_routes *const ni_device_routes_list[] = {' + ] + # put the sheets in lexical order of device numbers then bus + sheets = sorted(self.items(), key=lambda i : tuple(i[0].split('-')[::-1]) ) + + externs = [] + objs = [c_to_o(self.SET_C)] + + for sheet,D in sheets: + S = sheet.lower() + dev_table_name = 'ni_{}_device_routes'.format(S.replace('-','_')) + sheet_filename = os.path.join(self.ITEMS_DIR,'{}.c'.format(S)) + externs.append('extern struct ni_device_routes {};'.format(dev_table_name)) + + chunks.append('\t&{},'.format(dev_table_name)) + + s_chunks = [ + self.single_output_file_top.format( + filename = sheet_filename, + table_name = dev_table_name, + extern_h = self.EXTERN_H, + ), + routedict_to_routelist_single(S, D), + '};', + ] + + objs.append(c_to_o(sheet_filename)) + + with open(os.path.join(self.OUTPUT_DIR, sheet_filename), 'w') as f: + f.write('\n'.join(s_chunks)) + f.write('\n') + + with open(os.path.join(self.OUTPUT_DIR, self.MKFILE_SEGMENTS), 'w') as f: + f.write('# This is the segment that should be included in comedi/drivers/Makefile\n') + f.write('ni_routing-objs\t\t\t\t+= \\\n') + f.write('\n'.join(objs)) + f.write('\n') + + EXTERN_H = os.path.join(self.ITEMS_DIR, self.EXTERN_H) + with open(os.path.join(self.OUTPUT_DIR, EXTERN_H), 'w') as f: + f.write(self.extern_header.format( + filename=EXTERN_H, externs='\n'.join(externs))) + + chunks.append('\tNULL,') # terminate list + chunks.append('};') + return '\n'.join(chunks) + + def save(self): + filename=os.path.join(self.OUTPUT_DIR, self.SET_C) + + try: + os.makedirs(os.path.join(self.OUTPUT_DIR, self.ITEMS_DIR)) + except: + pass + with open(filename,'w') as f: + f.write( self.to_listinit() ) + f.write( '\n' ) + + +class RouteValues(CSVCollection): + MKFILE_SEGMENTS = 'route-values.mk' + SET_C = 'ni_route_values.c' + ITEMS_DIR = 'ni_route_values' + EXTERN_H = 'all.h' + OUTPUT_DIR = 'c' + + output_file_top = """\ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/{filename} + * Route information for NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 includes the tables that are a list of all the values of various + * signals routes available on NI hardware. In many cases, one does not + * explicitly make these routes, rather one might indicate that something is + * used as the source of one particular trigger or another (using + * *_src=TRIG_EXT). + * + * The contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#include "ni_route_values.h" +#include "{extern_h}"\ +""".format(filename=SET_C, extern_h=os.path.join(ITEMS_DIR, EXTERN_H)) + + extern_header = """\ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/ni_routing/{filename} + * List of valid routes for specific NI boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 contents of this file are generated using the tools in + * comedi/drivers/ni_routing/tools + * + * Please use those tools to help maintain the contents of this file. + */ + +#ifndef _COMEDI_DRIVERS_NI_ROUTING_NI_ROUTE_VALUES_EXTERN_H +#define _COMEDI_DRIVERS_NI_ROUTING_NI_ROUTE_VALUES_EXTERN_H + +#include "../ni_route_values.h" + +{externs} + +#endif //_COMEDI_DRIVERS_NI_ROUTING_NI_ROUTE_VALUES_EXTERN_H +""" + + single_output_file_top = """\ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_routing/{filename} + * Route information for {sheet} boards. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 includes a list of all the values of various signals routes + * available on NI 660x hardware. In many cases, one does not explicitly make + * these routes, rather one might indicate that something is used as the source + * of one particular trigger or another (using *_src=TRIG_EXT). + * + * The contents of this file can be generated using the tools in + * comedi/drivers/ni_routing/tools. This file also contains specific notes to + * this family of devices. + * + * Please use those tools to help maintain the contents of this file, but be + * mindful to not lose the notes already made in this file, since these notes + * are critical to a complete undertsanding of the register values of this + * family. + */ + +#include "../ni_route_values.h" +#include "{extern_h}" + +const struct family_route_values {table_name} = {{\ +""" + + def __init__(self, pattern='csv/route_values/*.csv'): + super(RouteValues,self).__init__(pattern) + + def to_structinit(self): + chunks = [ self.output_file_top, + '', + 'const struct family_route_values *const ni_all_route_values[] = {' + ] + # put the sheets in lexical order for consistency + sheets = sorted(self.items(), key=lambda i : i[0] ) + + externs = [] + objs = [c_to_o(self.SET_C)] + + for sheet,D in sheets: + S = sheet.lower() + fam_table_name = '{}_route_values'.format(S.replace('-','_')) + sheet_filename = os.path.join(self.ITEMS_DIR,'{}.c'.format(S)) + externs.append('extern const struct family_route_values {};'.format(fam_table_name)) + + chunks.append('\t&{},'.format(fam_table_name)) + + s_chunks = [ + self.single_output_file_top.format( + filename = sheet_filename, + sheet = sheet.upper(), + table_name = fam_table_name, + extern_h = self.EXTERN_H, + ), + routedict_to_structinit_single(S, D), + '};', + ] + + objs.append(c_to_o(sheet_filename)) + + with open(os.path.join(self.OUTPUT_DIR, sheet_filename), 'w') as f: + f.write('\n'.join(s_chunks)) + f.write( '\n' ) + + with open(os.path.join(self.OUTPUT_DIR, self.MKFILE_SEGMENTS), 'w') as f: + f.write('# This is the segment that should be included in comedi/drivers/Makefile\n') + f.write('ni_routing-objs\t\t\t\t+= \\\n') + f.write('\n'.join(objs)) + f.write('\n') + + EXTERN_H = os.path.join(self.ITEMS_DIR, self.EXTERN_H) + with open(os.path.join(self.OUTPUT_DIR, EXTERN_H), 'w') as f: + f.write(self.extern_header.format( + filename=EXTERN_H, externs='\n'.join(externs))) + + chunks.append('\tNULL,') # terminate list + chunks.append('};') + return '\n'.join(chunks) + + def save(self): + filename=os.path.join(self.OUTPUT_DIR, self.SET_C) + + try: + os.makedirs(os.path.join(self.OUTPUT_DIR, self.ITEMS_DIR)) + except: + pass + with open(filename,'w') as f: + f.write( self.to_structinit() ) + f.write( '\n' ) + + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument( '--route_values', action='store_true', + help='Extract route values from csv/route_values/*.csv' ) + parser.add_argument( '--device_routes', action='store_true', + help='Extract route values from csv/device_routes/*.csv' ) + args = parser.parse_args() + KL = list() + if args.route_values: + KL.append( RouteValues ) + if args.device_routes: + KL.append( DeviceRoutes ) + if not KL: + parser.error('nothing to do...') + for K in KL: + doc = K() + doc.save() diff --git a/drivers/comedi/drivers/ni_routing/tools/convert_py_to_csv.py b/drivers/comedi/drivers/ni_routing/tools/convert_py_to_csv.py new file mode 100755 index 000000000..a273b33ed --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/convert_py_to_csv.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ + +from os import path +import os, csv +from itertools import chain + +from csv_collection import CSVCollection +from ni_names import value_to_name +import ni_values + +CSV_DIR = 'csv' + +def iter_src_values(D): + return D.items() + +def iter_src(D): + for dest in D: + yield dest, 1 + +def create_csv(name, D, src_iter): + # have to change dest->{src:val} to src->{dest:val} + fieldnames = [value_to_name[i] for i in sorted(D.keys())] + fieldnames.insert(0, CSVCollection.source_column_name) + + S = dict() + for dest, srcD in D.items(): + for src,val in src_iter(srcD): + S.setdefault(src,{})[dest] = val + + S = sorted(S.items(), key = lambda src_destD : src_destD[0]) + + + csv_fname = path.join(CSV_DIR, name + '.csv') + with open(csv_fname, 'w') as F_csv: + dR = csv.DictWriter(F_csv, fieldnames, delimiter=';', quotechar='"') + dR.writeheader() + + # now change the json back into the csv dictionaries + rows = [ + dict(chain( + ((CSVCollection.source_column_name,value_to_name[src]),), + *(((value_to_name[dest],v),) for dest,v in destD.items()) + )) + for src, destD in S + ] + + dR.writerows(rows) + + +def to_csv(): + for d in ['route_values', 'device_routes']: + try: + os.makedirs(path.join(CSV_DIR,d)) + except: + pass + + for family, dst_src_map in ni_values.ni_route_values.items(): + create_csv(path.join('route_values',family), dst_src_map, iter_src_values) + + for device, dst_src_map in ni_values.ni_device_routes.items(): + create_csv(path.join('device_routes',device), dst_src_map, iter_src) + + +if __name__ == '__main__': + to_csv() diff --git a/drivers/comedi/drivers/ni_routing/tools/csv_collection.py b/drivers/comedi/drivers/ni_routing/tools/csv_collection.py new file mode 100644 index 000000000..db977ecb4 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/csv_collection.py @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0+ + +import os, csv, glob + +class CSVCollection(dict): + delimiter=';' + quotechar='"' + source_column_name = 'Sources / Destinations' + + """ + This class is a dictionary representation of the collection of sheets that + exist in a given .ODS file. + """ + def __init__(self, pattern, skip_commented_lines=True, strip_lines=True): + super(CSVCollection, self).__init__() + self.pattern = pattern + C = '#' if skip_commented_lines else 'blahblahblah' + + if strip_lines: + strip = lambda s:s.strip() + else: + strip = lambda s:s + + # load all CSV files + key = self.source_column_name + for fname in glob.glob(pattern): + with open(fname) as F: + dR = csv.DictReader(F, delimiter=self.delimiter, + quotechar=self.quotechar) + name = os.path.basename(fname).partition('.')[0] + D = { + r[key]:{f:strip(c) for f,c in r.items() + if f != key and f[:1] not in ['', C] and + strip(c)[:1] not in ['', C]} + for r in dR if r[key][:1] not in ['', C] + } + # now, go back through and eliminate all empty dictionaries + D = {k:v for k,v in D.items() if v} + self[name] = D diff --git a/drivers/comedi/drivers/ni_routing/tools/make_blank_csv.py b/drivers/comedi/drivers/ni_routing/tools/make_blank_csv.py new file mode 100755 index 000000000..c00eaf803 --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/make_blank_csv.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ + +from os import path +import os, csv + +from csv_collection import CSVCollection +from ni_names import value_to_name + +CSV_DIR = 'csv' + +def to_csv(): + try: + os.makedirs(CSV_DIR) + except: + pass + + csv_fname = path.join(CSV_DIR, 'blank_route_table.csv') + + fieldnames = [sig for sig_val, sig in sorted(value_to_name.items())] + fieldnames.insert(0, CSVCollection.source_column_name) + + with open(csv_fname, 'w') as F_csv: + dR = csv.DictWriter(F_csv, fieldnames, delimiter=';', quotechar='"') + dR.writeheader() + + for sig in fieldnames[1:]: + dR.writerow({CSVCollection.source_column_name: sig}) + +if __name__ == '__main__': + to_csv() diff --git a/drivers/comedi/drivers/ni_routing/tools/ni_names.py b/drivers/comedi/drivers/ni_routing/tools/ni_names.py new file mode 100644 index 000000000..d4df5f29e --- /dev/null +++ b/drivers/comedi/drivers/ni_routing/tools/ni_names.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0+ +""" +This file helps to extract string names of NI signals as included in comedi.h +between NI_NAMES_BASE and NI_NAMES_BASE+NI_NUM_NAMES. +""" + +# This is simply to aide in creating the entries in the order of the value of +# the device-global NI signal/terminal constants defined in comedi.h +import comedi_h + + +ni_macros = ( + 'NI_PFI', + 'TRIGGER_LINE', + 'NI_RTSI_BRD', + 'NI_CtrSource', + 'NI_CtrGate', + 'NI_CtrAux', + 'NI_CtrA', + 'NI_CtrB', + 'NI_CtrZ', + 'NI_CtrArmStartTrigger', + 'NI_CtrInternalOutput', + 'NI_CtrOut', + 'NI_CtrSampleClock', +) + +def get_ni_names(): + name_dict = dict() + + # load all the static names; start with those that do not begin with NI_ + name_dict['PXI_Star'] = comedi_h.PXI_Star + name_dict['PXI_Clk10'] = comedi_h.PXI_Clk10 + + #load all macro values + for fun in ni_macros: + f = getattr(comedi_h, fun) + name_dict.update({ + '{}({})'.format(fun,i):f(i) for i in range(1 + f(-1) - f(0)) + }) + + #load everything else in ni_common_signal_names enum + name_dict.update({ + k:v for k,v in comedi_h.__dict__.items() + if k.startswith('NI_') and (not callable(v)) and + comedi_h.NI_COUNTER_NAMES_MAX < v < (comedi_h.NI_NAMES_BASE + comedi_h.NI_NUM_NAMES) + }) + + # now create reverse lookup (value -> name) + + val_dict = {v:k for k,v in name_dict.items()} + + return name_dict, val_dict + +name_to_value, value_to_name = get_ni_names() diff --git a/drivers/comedi/drivers/ni_stc.h b/drivers/comedi/drivers/ni_stc.h new file mode 100644 index 000000000..fbc0b753a --- /dev/null +++ b/drivers/comedi/drivers/ni_stc.h @@ -0,0 +1,1142 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Register descriptions for NI DAQ-STC chip + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998-9 David A. Schleef <ds@schleef.org> + */ + +/* + * References: + * DAQ-STC Technical Reference Manual + */ + +#ifndef _COMEDI_NI_STC_H +#define _COMEDI_NI_STC_H + +#include "ni_tio.h" +#include "ni_routes.h" + +/* + * Registers in the National Instruments DAQ-STC chip + */ + +#define NISTC_INTA_ACK_REG 2 +#define NISTC_INTA_ACK_G0_GATE BIT(15) +#define NISTC_INTA_ACK_G0_TC BIT(14) +#define NISTC_INTA_ACK_AI_ERR BIT(13) +#define NISTC_INTA_ACK_AI_STOP BIT(12) +#define NISTC_INTA_ACK_AI_START BIT(11) +#define NISTC_INTA_ACK_AI_START2 BIT(10) +#define NISTC_INTA_ACK_AI_START1 BIT(9) +#define NISTC_INTA_ACK_AI_SC_TC BIT(8) +#define NISTC_INTA_ACK_AI_SC_TC_ERR BIT(7) +#define NISTC_INTA_ACK_G0_TC_ERR BIT(6) +#define NISTC_INTA_ACK_G0_GATE_ERR BIT(5) +#define NISTC_INTA_ACK_AI_ALL (NISTC_INTA_ACK_AI_ERR | \ + NISTC_INTA_ACK_AI_STOP | \ + NISTC_INTA_ACK_AI_START | \ + NISTC_INTA_ACK_AI_START2 | \ + NISTC_INTA_ACK_AI_START1 | \ + NISTC_INTA_ACK_AI_SC_TC | \ + NISTC_INTA_ACK_AI_SC_TC_ERR) + +#define NISTC_INTB_ACK_REG 3 +#define NISTC_INTB_ACK_G1_GATE BIT(15) +#define NISTC_INTB_ACK_G1_TC BIT(14) +#define NISTC_INTB_ACK_AO_ERR BIT(13) +#define NISTC_INTB_ACK_AO_STOP BIT(12) +#define NISTC_INTB_ACK_AO_START BIT(11) +#define NISTC_INTB_ACK_AO_UPDATE BIT(10) +#define NISTC_INTB_ACK_AO_START1 BIT(9) +#define NISTC_INTB_ACK_AO_BC_TC BIT(8) +#define NISTC_INTB_ACK_AO_UC_TC BIT(7) +#define NISTC_INTB_ACK_AO_UI2_TC BIT(6) +#define NISTC_INTB_ACK_AO_UI2_TC_ERR BIT(5) +#define NISTC_INTB_ACK_AO_BC_TC_ERR BIT(4) +#define NISTC_INTB_ACK_AO_BC_TC_TRIG_ERR BIT(3) +#define NISTC_INTB_ACK_G1_TC_ERR BIT(2) +#define NISTC_INTB_ACK_G1_GATE_ERR BIT(1) +#define NISTC_INTB_ACK_AO_ALL (NISTC_INTB_ACK_AO_ERR | \ + NISTC_INTB_ACK_AO_STOP | \ + NISTC_INTB_ACK_AO_START | \ + NISTC_INTB_ACK_AO_UPDATE | \ + NISTC_INTB_ACK_AO_START1 | \ + NISTC_INTB_ACK_AO_BC_TC | \ + NISTC_INTB_ACK_AO_UC_TC | \ + NISTC_INTB_ACK_AO_BC_TC_ERR | \ + NISTC_INTB_ACK_AO_BC_TC_TRIG_ERR) + +#define NISTC_AI_CMD2_REG 4 +#define NISTC_AI_CMD2_END_ON_SC_TC BIT(15) +#define NISTC_AI_CMD2_END_ON_EOS BIT(14) +#define NISTC_AI_CMD2_START1_DISABLE BIT(11) +#define NISTC_AI_CMD2_SC_SAVE_TRACE BIT(10) +#define NISTC_AI_CMD2_SI_SW_ON_SC_TC BIT(9) +#define NISTC_AI_CMD2_SI_SW_ON_STOP BIT(8) +#define NISTC_AI_CMD2_SI_SW_ON_TC BIT(7) +#define NISTC_AI_CMD2_SC_SW_ON_TC BIT(4) +#define NISTC_AI_CMD2_STOP_PULSE BIT(3) +#define NISTC_AI_CMD2_START_PULSE BIT(2) +#define NISTC_AI_CMD2_START2_PULSE BIT(1) +#define NISTC_AI_CMD2_START1_PULSE BIT(0) + +#define NISTC_AO_CMD2_REG 5 +#define NISTC_AO_CMD2_END_ON_BC_TC(x) (((x) & 0x3) << 14) +#define NISTC_AO_CMD2_START_STOP_GATE_ENA BIT(13) +#define NISTC_AO_CMD2_UC_SAVE_TRACE BIT(12) +#define NISTC_AO_CMD2_BC_GATE_ENA BIT(11) +#define NISTC_AO_CMD2_BC_SAVE_TRACE BIT(10) +#define NISTC_AO_CMD2_UI_SW_ON_BC_TC BIT(9) +#define NISTC_AO_CMD2_UI_SW_ON_STOP BIT(8) +#define NISTC_AO_CMD2_UI_SW_ON_TC BIT(7) +#define NISTC_AO_CMD2_UC_SW_ON_BC_TC BIT(6) +#define NISTC_AO_CMD2_UC_SW_ON_TC BIT(5) +#define NISTC_AO_CMD2_BC_SW_ON_TC BIT(4) +#define NISTC_AO_CMD2_MUTE_B BIT(3) +#define NISTC_AO_CMD2_MUTE_A BIT(2) +#define NISTC_AO_CMD2_UPDATE2_PULSE BIT(1) +#define NISTC_AO_CMD2_START1_PULSE BIT(0) + +#define NISTC_G0_CMD_REG 6 +#define NISTC_G1_CMD_REG 7 + +#define NISTC_AI_CMD1_REG 8 +#define NISTC_AI_CMD1_ATRIG_RESET BIT(14) +#define NISTC_AI_CMD1_DISARM BIT(13) +#define NISTC_AI_CMD1_SI2_ARM BIT(12) +#define NISTC_AI_CMD1_SI2_LOAD BIT(11) +#define NISTC_AI_CMD1_SI_ARM BIT(10) +#define NISTC_AI_CMD1_SI_LOAD BIT(9) +#define NISTC_AI_CMD1_DIV_ARM BIT(8) +#define NISTC_AI_CMD1_DIV_LOAD BIT(7) +#define NISTC_AI_CMD1_SC_ARM BIT(6) +#define NISTC_AI_CMD1_SC_LOAD BIT(5) +#define NISTC_AI_CMD1_SCAN_IN_PROG_PULSE BIT(4) +#define NISTC_AI_CMD1_EXTMUX_CLK_PULSE BIT(3) +#define NISTC_AI_CMD1_LOCALMUX_CLK_PULSE BIT(2) +#define NISTC_AI_CMD1_SC_TC_PULSE BIT(1) +#define NISTC_AI_CMD1_CONVERT_PULSE BIT(0) + +#define NISTC_AO_CMD1_REG 9 +#define NISTC_AO_CMD1_ATRIG_RESET BIT(15) +#define NISTC_AO_CMD1_START_PULSE BIT(14) +#define NISTC_AO_CMD1_DISARM BIT(13) +#define NISTC_AO_CMD1_UI2_ARM_DISARM BIT(12) +#define NISTC_AO_CMD1_UI2_LOAD BIT(11) +#define NISTC_AO_CMD1_UI_ARM BIT(10) +#define NISTC_AO_CMD1_UI_LOAD BIT(9) +#define NISTC_AO_CMD1_UC_ARM BIT(8) +#define NISTC_AO_CMD1_UC_LOAD BIT(7) +#define NISTC_AO_CMD1_BC_ARM BIT(6) +#define NISTC_AO_CMD1_BC_LOAD BIT(5) +#define NISTC_AO_CMD1_DAC1_UPDATE_MODE BIT(4) +#define NISTC_AO_CMD1_LDAC1_SRC_SEL BIT(3) +#define NISTC_AO_CMD1_DAC0_UPDATE_MODE BIT(2) +#define NISTC_AO_CMD1_LDAC0_SRC_SEL BIT(1) +#define NISTC_AO_CMD1_UPDATE_PULSE BIT(0) + +#define NISTC_DIO_OUT_REG 10 +#define NISTC_DIO_OUT_SERIAL(x) (((x) & 0xff) << 8) +#define NISTC_DIO_OUT_SERIAL_MASK NISTC_DIO_OUT_SERIAL(0xff) +#define NISTC_DIO_OUT_PARALLEL(x) ((x) & 0xff) +#define NISTC_DIO_OUT_PARALLEL_MASK NISTC_DIO_OUT_PARALLEL(0xff) +#define NISTC_DIO_SDIN BIT(4) +#define NISTC_DIO_SDOUT BIT(0) + +#define NISTC_DIO_CTRL_REG 11 +#define NISTC_DIO_SDCLK BIT(11) +#define NISTC_DIO_CTRL_HW_SER_TIMEBASE BIT(10) +#define NISTC_DIO_CTRL_HW_SER_ENA BIT(9) +#define NISTC_DIO_CTRL_HW_SER_START BIT(8) +#define NISTC_DIO_CTRL_DIR(x) ((x) & 0xff) +#define NISTC_DIO_CTRL_DIR_MASK NISTC_DIO_CTRL_DIR(0xff) + +#define NISTC_AI_MODE1_REG 12 +#define NISTC_AI_MODE1_CONVERT_SRC(x) (((x) & 0x1f) << 11) +#define NISTC_AI_MODE1_SI_SRC(x) (((x) & 0x1f) << 6) +#define NISTC_AI_MODE1_CONVERT_POLARITY BIT(5) +#define NISTC_AI_MODE1_SI_POLARITY BIT(4) +#define NISTC_AI_MODE1_START_STOP BIT(3) +#define NISTC_AI_MODE1_RSVD BIT(2) +#define NISTC_AI_MODE1_CONTINUOUS BIT(1) +#define NISTC_AI_MODE1_TRIGGER_ONCE BIT(0) + +#define NISTC_AI_MODE2_REG 13 +#define NISTC_AI_MODE2_SC_GATE_ENA BIT(15) +#define NISTC_AI_MODE2_START_STOP_GATE_ENA BIT(14) +#define NISTC_AI_MODE2_PRE_TRIGGER BIT(13) +#define NISTC_AI_MODE2_EXTMUX_PRESENT BIT(12) +#define NISTC_AI_MODE2_SI2_INIT_LOAD_SRC BIT(9) +#define NISTC_AI_MODE2_SI2_RELOAD_MODE BIT(8) +#define NISTC_AI_MODE2_SI_INIT_LOAD_SRC BIT(7) +#define NISTC_AI_MODE2_SI_RELOAD_MODE(x) (((x) & 0x7) << 4) +#define NISTC_AI_MODE2_SI_WR_SWITCH BIT(3) +#define NISTC_AI_MODE2_SC_INIT_LOAD_SRC BIT(2) +#define NISTC_AI_MODE2_SC_RELOAD_MODE BIT(1) +#define NISTC_AI_MODE2_SC_WR_SWITCH BIT(0) + +#define NISTC_AI_SI_LOADA_REG 14 +#define NISTC_AI_SI_LOADB_REG 16 +#define NISTC_AI_SC_LOADA_REG 18 +#define NISTC_AI_SC_LOADB_REG 20 +#define NISTC_AI_SI2_LOADA_REG 23 +#define NISTC_AI_SI2_LOADB_REG 25 + +#define NISTC_G0_MODE_REG 26 +#define NISTC_G1_MODE_REG 27 +#define NISTC_G0_LOADA_REG 28 +#define NISTC_G0_LOADB_REG 30 +#define NISTC_G1_LOADA_REG 32 +#define NISTC_G1_LOADB_REG 34 +#define NISTC_G0_INPUT_SEL_REG 36 +#define NISTC_G1_INPUT_SEL_REG 37 + +#define NISTC_AO_MODE1_REG 38 +#define NISTC_AO_MODE1_UPDATE_SRC(x) (((x) & 0x1f) << 11) +#define NISTC_AO_MODE1_UPDATE_SRC_MASK NISTC_AO_MODE1_UPDATE_SRC(0x1f) +#define NISTC_AO_MODE1_UI_SRC(x) (((x) & 0x1f) << 6) +#define NISTC_AO_MODE1_UI_SRC_MASK NISTC_AO_MODE1_UI_SRC(0x1f) +#define NISTC_AO_MODE1_MULTI_CHAN BIT(5) +#define NISTC_AO_MODE1_UPDATE_SRC_POLARITY BIT(4) +#define NISTC_AO_MODE1_UI_SRC_POLARITY BIT(3) +#define NISTC_AO_MODE1_UC_SW_EVERY_TC BIT(2) +#define NISTC_AO_MODE1_CONTINUOUS BIT(1) +#define NISTC_AO_MODE1_TRIGGER_ONCE BIT(0) + +#define NISTC_AO_MODE2_REG 39 +#define NISTC_AO_MODE2_FIFO_MODE(x) (((x) & 0x3) << 14) +#define NISTC_AO_MODE2_FIFO_MODE_MASK NISTC_AO_MODE2_FIFO_MODE(3) +#define NISTC_AO_MODE2_FIFO_MODE_E NISTC_AO_MODE2_FIFO_MODE(0) +#define NISTC_AO_MODE2_FIFO_MODE_HF NISTC_AO_MODE2_FIFO_MODE(1) +#define NISTC_AO_MODE2_FIFO_MODE_F NISTC_AO_MODE2_FIFO_MODE(2) +#define NISTC_AO_MODE2_FIFO_MODE_HF_F NISTC_AO_MODE2_FIFO_MODE(3) +#define NISTC_AO_MODE2_FIFO_REXMIT_ENA BIT(13) +#define NISTC_AO_MODE2_START1_DISABLE BIT(12) +#define NISTC_AO_MODE2_UC_INIT_LOAD_SRC BIT(11) +#define NISTC_AO_MODE2_UC_WR_SWITCH BIT(10) +#define NISTC_AO_MODE2_UI2_INIT_LOAD_SRC BIT(9) +#define NISTC_AO_MODE2_UI2_RELOAD_MODE BIT(8) +#define NISTC_AO_MODE2_UI_INIT_LOAD_SRC BIT(7) +#define NISTC_AO_MODE2_UI_RELOAD_MODE(x) (((x) & 0x7) << 4) +#define NISTC_AO_MODE2_UI_WR_SWITCH BIT(3) +#define NISTC_AO_MODE2_BC_INIT_LOAD_SRC BIT(2) +#define NISTC_AO_MODE2_BC_RELOAD_MODE BIT(1) +#define NISTC_AO_MODE2_BC_WR_SWITCH BIT(0) + +#define NISTC_AO_UI_LOADA_REG 40 +#define NISTC_AO_UI_LOADB_REG 42 +#define NISTC_AO_BC_LOADA_REG 44 +#define NISTC_AO_BC_LOADB_REG 46 +#define NISTC_AO_UC_LOADA_REG 48 +#define NISTC_AO_UC_LOADB_REG 50 + +#define NISTC_CLK_FOUT_REG 56 +#define NISTC_CLK_FOUT_ENA BIT(15) +#define NISTC_CLK_FOUT_TIMEBASE_SEL BIT(14) +#define NISTC_CLK_FOUT_DIO_SER_OUT_DIV2 BIT(13) +#define NISTC_CLK_FOUT_SLOW_DIV2 BIT(12) +#define NISTC_CLK_FOUT_SLOW_TIMEBASE BIT(11) +#define NISTC_CLK_FOUT_G_SRC_DIV2 BIT(10) +#define NISTC_CLK_FOUT_TO_BOARD_DIV2 BIT(9) +#define NISTC_CLK_FOUT_TO_BOARD BIT(8) +#define NISTC_CLK_FOUT_AI_OUT_DIV2 BIT(7) +#define NISTC_CLK_FOUT_AI_SRC_DIV2 BIT(6) +#define NISTC_CLK_FOUT_AO_OUT_DIV2 BIT(5) +#define NISTC_CLK_FOUT_AO_SRC_DIV2 BIT(4) +#define NISTC_CLK_FOUT_DIVIDER(x) (((x) & 0xf) << 0) +#define NISTC_CLK_FOUT_TO_DIVIDER(x) (((x) >> 0) & 0xf) +#define NISTC_CLK_FOUT_DIVIDER_MASK NISTC_CLK_FOUT_DIVIDER(0xf) + +#define NISTC_IO_BIDIR_PIN_REG 57 + +#define NISTC_RTSI_TRIG_DIR_REG 58 +#define NISTC_RTSI_TRIG_OLD_CLK_CHAN 7 +#define NISTC_RTSI_TRIG_NUM_CHAN(_m) ((_m) ? 8 : 7) +#define NISTC_RTSI_TRIG_DIR(_c, _m) ((_m) ? BIT(8 + (_c)) : BIT(7 + (_c))) +#define NISTC_RTSI_TRIG_DIR_SUB_SEL1 BIT(2) /* only for M-Series */ +#define NISTC_RTSI_TRIG_DIR_SUB_SEL1_SHIFT 2 /* only for M-Series */ +#define NISTC_RTSI_TRIG_USE_CLK BIT(1) +#define NISTC_RTSI_TRIG_DRV_CLK BIT(0) + +#define NISTC_INT_CTRL_REG 59 +#define NISTC_INT_CTRL_INTB_ENA BIT(15) +#define NISTC_INT_CTRL_INTB_SEL(x) (((x) & 0x7) << 12) +#define NISTC_INT_CTRL_INTA_ENA BIT(11) +#define NISTC_INT_CTRL_INTA_SEL(x) (((x) & 0x7) << 8) +#define NISTC_INT_CTRL_PASSTHRU0_POL BIT(3) +#define NISTC_INT_CTRL_PASSTHRU1_POL BIT(2) +#define NISTC_INT_CTRL_3PIN_INT BIT(1) +#define NISTC_INT_CTRL_INT_POL BIT(0) + +#define NISTC_AI_OUT_CTRL_REG 60 +#define NISTC_AI_OUT_CTRL_START_SEL BIT(10) +#define NISTC_AI_OUT_CTRL_SCAN_IN_PROG_SEL(x) (((x) & 0x3) << 8) +#define NISTC_AI_OUT_CTRL_EXTMUX_CLK_SEL(x) (((x) & 0x3) << 6) +#define NISTC_AI_OUT_CTRL_LOCALMUX_CLK_SEL(x) (((x) & 0x3) << 4) +#define NISTC_AI_OUT_CTRL_SC_TC_SEL(x) (((x) & 0x3) << 2) +#define NISTC_AI_OUT_CTRL_CONVERT_SEL(x) (((x) & 0x3) << 0) +#define NISTC_AI_OUT_CTRL_CONVERT_HIGH_Z NISTC_AI_OUT_CTRL_CONVERT_SEL(0) +#define NISTC_AI_OUT_CTRL_CONVERT_GND NISTC_AI_OUT_CTRL_CONVERT_SEL(1) +#define NISTC_AI_OUT_CTRL_CONVERT_LOW NISTC_AI_OUT_CTRL_CONVERT_SEL(2) +#define NISTC_AI_OUT_CTRL_CONVERT_HIGH NISTC_AI_OUT_CTRL_CONVERT_SEL(3) + +#define NISTC_ATRIG_ETC_REG 61 +#define NISTC_ATRIG_ETC_GPFO_1_ENA BIT(15) +#define NISTC_ATRIG_ETC_GPFO_0_ENA BIT(14) +#define NISTC_ATRIG_ETC_GPFO_0_SEL(x) (((x) & 0x7) << 11) +#define NISTC_ATRIG_ETC_GPFO_0_SEL_TO_SRC(x) (((x) >> 11) & 0x7) +#define NISTC_ATRIG_ETC_GPFO_1_SEL BIT(7) +#define NISTC_ATRIG_ETC_GPFO_1_SEL_TO_SRC(x) (((x) >> 7) & 0x1) +#define NISTC_ATRIG_ETC_DRV BIT(4) +#define NISTC_ATRIG_ETC_ENA BIT(3) +#define NISTC_ATRIG_ETC_MODE(x) (((x) & 0x7) << 0) +#define NISTC_GPFO_0_G_OUT 0 /* input to GPFO_0_SEL for Ctr0Out */ +#define NISTC_GPFO_1_G_OUT 0 /* input to GPFO_1_SEL for Ctr1Out */ + +#define NISTC_AI_START_STOP_REG 62 +#define NISTC_AI_START_POLARITY BIT(15) +#define NISTC_AI_STOP_POLARITY BIT(14) +#define NISTC_AI_STOP_SYNC BIT(13) +#define NISTC_AI_STOP_EDGE BIT(12) +#define NISTC_AI_STOP_SEL(x) (((x) & 0x1f) << 7) +#define NISTC_AI_START_SYNC BIT(6) +#define NISTC_AI_START_EDGE BIT(5) +#define NISTC_AI_START_SEL(x) (((x) & 0x1f) << 0) + +#define NISTC_AI_TRIG_SEL_REG 63 +#define NISTC_AI_TRIG_START1_POLARITY BIT(15) +#define NISTC_AI_TRIG_START2_POLARITY BIT(14) +#define NISTC_AI_TRIG_START2_SYNC BIT(13) +#define NISTC_AI_TRIG_START2_EDGE BIT(12) +#define NISTC_AI_TRIG_START2_SEL(x) (((x) & 0x1f) << 7) +#define NISTC_AI_TRIG_START1_SYNC BIT(6) +#define NISTC_AI_TRIG_START1_EDGE BIT(5) +#define NISTC_AI_TRIG_START1_SEL(x) (((x) & 0x1f) << 0) + +#define NISTC_AI_DIV_LOADA_REG 64 + +#define NISTC_AO_START_SEL_REG 66 +#define NISTC_AO_START_UI2_SW_GATE BIT(15) +#define NISTC_AO_START_UI2_EXT_GATE_POL BIT(14) +#define NISTC_AO_START_POLARITY BIT(13) +#define NISTC_AO_START_AOFREQ_ENA BIT(12) +#define NISTC_AO_START_UI2_EXT_GATE_SEL(x) (((x) & 0x1f) << 7) +#define NISTC_AO_START_SYNC BIT(6) +#define NISTC_AO_START_EDGE BIT(5) +#define NISTC_AO_START_SEL(x) (((x) & 0x1f) << 0) + +#define NISTC_AO_TRIG_SEL_REG 67 +#define NISTC_AO_TRIG_UI2_EXT_GATE_ENA BIT(15) +#define NISTC_AO_TRIG_DELAYED_START1 BIT(14) +#define NISTC_AO_TRIG_START1_POLARITY BIT(13) +#define NISTC_AO_TRIG_UI2_SRC_POLARITY BIT(12) +#define NISTC_AO_TRIG_UI2_SRC_SEL(x) (((x) & 0x1f) << 7) +#define NISTC_AO_TRIG_START1_SYNC BIT(6) +#define NISTC_AO_TRIG_START1_EDGE BIT(5) +#define NISTC_AO_TRIG_START1_SEL(x) (((x) & 0x1f) << 0) +#define NISTC_AO_TRIG_START1_SEL_MASK NISTC_AO_TRIG_START1_SEL(0x1f) + +#define NISTC_G0_AUTOINC_REG 68 +#define NISTC_G1_AUTOINC_REG 69 + +#define NISTC_AO_MODE3_REG 70 +#define NISTC_AO_MODE3_UI2_SW_NEXT_TC BIT(13) +#define NISTC_AO_MODE3_UC_SW_EVERY_BC_TC BIT(12) +#define NISTC_AO_MODE3_TRIG_LEN BIT(11) +#define NISTC_AO_MODE3_STOP_ON_OVERRUN_ERR BIT(5) +#define NISTC_AO_MODE3_STOP_ON_BC_TC_TRIG_ERR BIT(4) +#define NISTC_AO_MODE3_STOP_ON_BC_TC_ERR BIT(3) +#define NISTC_AO_MODE3_NOT_AN_UPDATE BIT(2) +#define NISTC_AO_MODE3_SW_GATE BIT(1) +#define NISTC_AO_MODE3_LAST_GATE_DISABLE BIT(0) /* M-Series only */ + +#define NISTC_RESET_REG 72 +#define NISTC_RESET_SOFTWARE BIT(11) +#define NISTC_RESET_AO_CFG_END BIT(9) +#define NISTC_RESET_AI_CFG_END BIT(8) +#define NISTC_RESET_AO_CFG_START BIT(5) +#define NISTC_RESET_AI_CFG_START BIT(4) +#define NISTC_RESET_G1 BIT(3) +#define NISTC_RESET_G0 BIT(2) +#define NISTC_RESET_AO BIT(1) +#define NISTC_RESET_AI BIT(0) + +#define NISTC_INTA_ENA_REG 73 +#define NISTC_INTA2_ENA_REG 74 +#define NISTC_INTA_ENA_PASSTHRU0 BIT(9) +#define NISTC_INTA_ENA_G0_GATE BIT(8) +#define NISTC_INTA_ENA_AI_FIFO BIT(7) +#define NISTC_INTA_ENA_G0_TC BIT(6) +#define NISTC_INTA_ENA_AI_ERR BIT(5) +#define NISTC_INTA_ENA_AI_STOP BIT(4) +#define NISTC_INTA_ENA_AI_START BIT(3) +#define NISTC_INTA_ENA_AI_START2 BIT(2) +#define NISTC_INTA_ENA_AI_START1 BIT(1) +#define NISTC_INTA_ENA_AI_SC_TC BIT(0) +#define NISTC_INTA_ENA_AI_MASK (NISTC_INTA_ENA_AI_FIFO | \ + NISTC_INTA_ENA_AI_ERR | \ + NISTC_INTA_ENA_AI_STOP | \ + NISTC_INTA_ENA_AI_START | \ + NISTC_INTA_ENA_AI_START2 | \ + NISTC_INTA_ENA_AI_START1 | \ + NISTC_INTA_ENA_AI_SC_TC) + +#define NISTC_INTB_ENA_REG 75 +#define NISTC_INTB2_ENA_REG 76 +#define NISTC_INTB_ENA_PASSTHRU1 BIT(11) +#define NISTC_INTB_ENA_G1_GATE BIT(10) +#define NISTC_INTB_ENA_G1_TC BIT(9) +#define NISTC_INTB_ENA_AO_FIFO BIT(8) +#define NISTC_INTB_ENA_AO_UI2_TC BIT(7) +#define NISTC_INTB_ENA_AO_UC_TC BIT(6) +#define NISTC_INTB_ENA_AO_ERR BIT(5) +#define NISTC_INTB_ENA_AO_STOP BIT(4) +#define NISTC_INTB_ENA_AO_START BIT(3) +#define NISTC_INTB_ENA_AO_UPDATE BIT(2) +#define NISTC_INTB_ENA_AO_START1 BIT(1) +#define NISTC_INTB_ENA_AO_BC_TC BIT(0) + +#define NISTC_AI_PERSONAL_REG 77 +#define NISTC_AI_PERSONAL_SHIFTIN_PW BIT(15) +#define NISTC_AI_PERSONAL_EOC_POLARITY BIT(14) +#define NISTC_AI_PERSONAL_SOC_POLARITY BIT(13) +#define NISTC_AI_PERSONAL_SHIFTIN_POL BIT(12) +#define NISTC_AI_PERSONAL_CONVERT_TIMEBASE BIT(11) +#define NISTC_AI_PERSONAL_CONVERT_PW BIT(10) +#define NISTC_AI_PERSONAL_CONVERT_ORIG_PULSE BIT(9) +#define NISTC_AI_PERSONAL_FIFO_FLAGS_POL BIT(8) +#define NISTC_AI_PERSONAL_OVERRUN_MODE BIT(7) +#define NISTC_AI_PERSONAL_EXTMUX_CLK_PW BIT(6) +#define NISTC_AI_PERSONAL_LOCALMUX_CLK_PW BIT(5) +#define NISTC_AI_PERSONAL_AIFREQ_POL BIT(4) + +#define NISTC_AO_PERSONAL_REG 78 +#define NISTC_AO_PERSONAL_MULTI_DACS BIT(15) /* M-Series only */ +#define NISTC_AO_PERSONAL_NUM_DAC BIT(14) /* 1:single; 0:dual */ +#define NISTC_AO_PERSONAL_FAST_CPU BIT(13) /* M-Series reserved */ +#define NISTC_AO_PERSONAL_TMRDACWR_PW BIT(12) +#define NISTC_AO_PERSONAL_FIFO_FLAGS_POL BIT(11) /* M-Series reserved */ +#define NISTC_AO_PERSONAL_FIFO_ENA BIT(10) +#define NISTC_AO_PERSONAL_AOFREQ_POL BIT(9) /* M-Series reserved */ +#define NISTC_AO_PERSONAL_DMA_PIO_CTRL BIT(8) /* M-Series reserved */ +#define NISTC_AO_PERSONAL_UPDATE_ORIG_PULSE BIT(7) +#define NISTC_AO_PERSONAL_UPDATE_TIMEBASE BIT(6) +#define NISTC_AO_PERSONAL_UPDATE_PW BIT(5) +#define NISTC_AO_PERSONAL_BC_SRC_SEL BIT(4) +#define NISTC_AO_PERSONAL_INTERVAL_BUFFER_MODE BIT(3) + +#define NISTC_RTSI_TRIGA_OUT_REG 79 +#define NISTC_RTSI_TRIGB_OUT_REG 80 +#define NISTC_RTSI_TRIGB_SUB_SEL1 BIT(15) /* not for M-Series */ +#define NISTC_RTSI_TRIGB_SUB_SEL1_SHIFT 15 /* not for M-Series */ +#define NISTC_RTSI_TRIG(_c, _s) (((_s) & 0xf) << (((_c) % 4) * 4)) +#define NISTC_RTSI_TRIG_MASK(_c) NISTC_RTSI_TRIG((_c), 0xf) +#define NISTC_RTSI_TRIG_TO_SRC(_c, _b) (((_b) >> (((_c) % 4) * 4)) & 0xf) + +#define NISTC_RTSI_BOARD_REG 81 + +#define NISTC_CFG_MEM_CLR_REG 82 +#define NISTC_ADC_FIFO_CLR_REG 83 +#define NISTC_DAC_FIFO_CLR_REG 84 +#define NISTC_WR_STROBE3_REG 85 + +#define NISTC_AO_OUT_CTRL_REG 86 +#define NISTC_AO_OUT_CTRL_EXT_GATE_ENA BIT(15) +#define NISTC_AO_OUT_CTRL_EXT_GATE_SEL(x) (((x) & 0x1f) << 10) +#define NISTC_AO_OUT_CTRL_CHANS(x) (((x) & 0xf) << 6) +#define NISTC_AO_OUT_CTRL_UPDATE2_SEL(x) (((x) & 0x3) << 4) +#define NISTC_AO_OUT_CTRL_EXT_GATE_POL BIT(3) +#define NISTC_AO_OUT_CTRL_UPDATE2_TOGGLE BIT(2) +#define NISTC_AO_OUT_CTRL_UPDATE_SEL(x) (((x) & 0x3) << 0) +#define NISTC_AO_OUT_CTRL_UPDATE_SEL_HIGHZ NISTC_AO_OUT_CTRL_UPDATE_SEL(0) +#define NISTC_AO_OUT_CTRL_UPDATE_SEL_GND NISTC_AO_OUT_CTRL_UPDATE_SEL(1) +#define NISTC_AO_OUT_CTRL_UPDATE_SEL_LOW NISTC_AO_OUT_CTRL_UPDATE_SEL(2) +#define NISTC_AO_OUT_CTRL_UPDATE_SEL_HIGH NISTC_AO_OUT_CTRL_UPDATE_SEL(3) + +#define NISTC_AI_MODE3_REG 87 +#define NISTC_AI_MODE3_TRIG_LEN BIT(15) +#define NISTC_AI_MODE3_DELAY_START BIT(14) +#define NISTC_AI_MODE3_SOFTWARE_GATE BIT(13) +#define NISTC_AI_MODE3_SI_TRIG_DELAY BIT(12) +#define NISTC_AI_MODE3_SI2_SRC_SEL BIT(11) +#define NISTC_AI_MODE3_DELAYED_START2 BIT(10) +#define NISTC_AI_MODE3_DELAYED_START1 BIT(9) +#define NISTC_AI_MODE3_EXT_GATE_MODE BIT(8) +#define NISTC_AI_MODE3_FIFO_MODE(x) (((x) & 0x3) << 6) +#define NISTC_AI_MODE3_FIFO_MODE_NE NISTC_AI_MODE3_FIFO_MODE(0) +#define NISTC_AI_MODE3_FIFO_MODE_HF NISTC_AI_MODE3_FIFO_MODE(1) +#define NISTC_AI_MODE3_FIFO_MODE_F NISTC_AI_MODE3_FIFO_MODE(2) +#define NISTC_AI_MODE3_FIFO_MODE_HF_E NISTC_AI_MODE3_FIFO_MODE(3) +#define NISTC_AI_MODE3_EXT_GATE_POL BIT(5) +#define NISTC_AI_MODE3_EXT_GATE_SEL(x) (((x) & 0x1f) << 0) + +#define NISTC_AI_STATUS1_REG 2 +#define NISTC_AI_STATUS1_INTA BIT(15) +#define NISTC_AI_STATUS1_FIFO_F BIT(14) +#define NISTC_AI_STATUS1_FIFO_HF BIT(13) +#define NISTC_AI_STATUS1_FIFO_E BIT(12) +#define NISTC_AI_STATUS1_OVERRUN BIT(11) +#define NISTC_AI_STATUS1_OVERFLOW BIT(10) +#define NISTC_AI_STATUS1_SC_TC_ERR BIT(9) +#define NISTC_AI_STATUS1_OVER (NISTC_AI_STATUS1_OVERRUN | \ + NISTC_AI_STATUS1_OVERFLOW) +#define NISTC_AI_STATUS1_ERR (NISTC_AI_STATUS1_OVER | \ + NISTC_AI_STATUS1_SC_TC_ERR) +#define NISTC_AI_STATUS1_START2 BIT(8) +#define NISTC_AI_STATUS1_START1 BIT(7) +#define NISTC_AI_STATUS1_SC_TC BIT(6) +#define NISTC_AI_STATUS1_START BIT(5) +#define NISTC_AI_STATUS1_STOP BIT(4) +#define NISTC_AI_STATUS1_G0_TC BIT(3) +#define NISTC_AI_STATUS1_G0_GATE BIT(2) +#define NISTC_AI_STATUS1_FIFO_REQ BIT(1) +#define NISTC_AI_STATUS1_PASSTHRU0 BIT(0) + +#define NISTC_AO_STATUS1_REG 3 +#define NISTC_AO_STATUS1_INTB BIT(15) +#define NISTC_AO_STATUS1_FIFO_F BIT(14) +#define NISTC_AO_STATUS1_FIFO_HF BIT(13) +#define NISTC_AO_STATUS1_FIFO_E BIT(12) +#define NISTC_AO_STATUS1_BC_TC_ERR BIT(11) +#define NISTC_AO_STATUS1_START BIT(10) +#define NISTC_AO_STATUS1_OVERRUN BIT(9) +#define NISTC_AO_STATUS1_START1 BIT(8) +#define NISTC_AO_STATUS1_BC_TC BIT(7) +#define NISTC_AO_STATUS1_UC_TC BIT(6) +#define NISTC_AO_STATUS1_UPDATE BIT(5) +#define NISTC_AO_STATUS1_UI2_TC BIT(4) +#define NISTC_AO_STATUS1_G1_TC BIT(3) +#define NISTC_AO_STATUS1_G1_GATE BIT(2) +#define NISTC_AO_STATUS1_FIFO_REQ BIT(1) +#define NISTC_AO_STATUS1_PASSTHRU1 BIT(0) + +#define NISTC_G01_STATUS_REG 4 + +#define NISTC_AI_STATUS2_REG 5 + +#define NISTC_AO_STATUS2_REG 6 + +#define NISTC_DIO_IN_REG 7 + +#define NISTC_G0_HW_SAVE_REG 8 +#define NISTC_G1_HW_SAVE_REG 10 + +#define NISTC_G0_SAVE_REG 12 +#define NISTC_G1_SAVE_REG 14 + +#define NISTC_AO_UI_SAVE_REG 16 +#define NISTC_AO_BC_SAVE_REG 18 +#define NISTC_AO_UC_SAVE_REG 20 + +#define NISTC_STATUS1_REG 27 +#define NISTC_STATUS1_SERIO_IN_PROG BIT(12) + +#define NISTC_DIO_SERIAL_IN_REG 28 + +#define NISTC_STATUS2_REG 29 +#define NISTC_STATUS2_AO_TMRDACWRS_IN_PROGRESS BIT(5) + +#define NISTC_AI_SI_SAVE_REG 64 +#define NISTC_AI_SC_SAVE_REG 66 + +/* + * PCI E Series Registers + */ +#define NI_E_STC_WINDOW_ADDR_REG 0x00 /* rw16 */ +#define NI_E_STC_WINDOW_DATA_REG 0x02 /* rw16 */ + +#define NI_E_STATUS_REG 0x01 /* r8 */ +#define NI_E_STATUS_AI_FIFO_LOWER_NE BIT(3) +#define NI_E_STATUS_PROMOUT BIT(0) + +#define NI_E_DMA_AI_AO_SEL_REG 0x09 /* w8 */ +#define NI_E_DMA_AI_SEL(x) (((x) & 0xf) << 0) +#define NI_E_DMA_AI_SEL_MASK NI_E_DMA_AI_SEL(0xf) +#define NI_E_DMA_AO_SEL(x) (((x) & 0xf) << 4) +#define NI_E_DMA_AO_SEL_MASK NI_E_DMA_AO_SEL(0xf) + +#define NI_E_DMA_G0_G1_SEL_REG 0x0b /* w8 */ +#define NI_E_DMA_G0_G1_SEL(_g, _c) (((_c) & 0xf) << ((_g) * 4)) +#define NI_E_DMA_G0_G1_SEL_MASK(_g) NI_E_DMA_G0_G1_SEL((_g), 0xf) + +#define NI_E_SERIAL_CMD_REG 0x0d /* w8 */ +#define NI_E_SERIAL_CMD_DAC_LD(x) BIT(3 + (x)) +#define NI_E_SERIAL_CMD_EEPROM_CS BIT(2) +#define NI_E_SERIAL_CMD_SDATA BIT(1) +#define NI_E_SERIAL_CMD_SCLK BIT(0) + +#define NI_E_MISC_CMD_REG 0x0f /* w8 */ +#define NI_E_MISC_CMD_INTEXT_ATRIG(x) (((x) & 0x1) << 7) +#define NI_E_MISC_CMD_EXT_ATRIG NI_E_MISC_CMD_INTEXT_ATRIG(0) +#define NI_E_MISC_CMD_INT_ATRIG NI_E_MISC_CMD_INTEXT_ATRIG(1) + +#define NI_E_AI_CFG_LO_REG 0x10 /* w16 */ +#define NI_E_AI_CFG_LO_LAST_CHAN BIT(15) +#define NI_E_AI_CFG_LO_GEN_TRIG BIT(12) +#define NI_E_AI_CFG_LO_DITHER BIT(9) +#define NI_E_AI_CFG_LO_UNI BIT(8) +#define NI_E_AI_CFG_LO_GAIN(x) ((x) << 0) + +#define NI_E_AI_CFG_HI_REG 0x12 /* w16 */ +#define NI_E_AI_CFG_HI_TYPE(x) (((x) & 0x7) << 12) +#define NI_E_AI_CFG_HI_TYPE_DIFF NI_E_AI_CFG_HI_TYPE(1) +#define NI_E_AI_CFG_HI_TYPE_COMMON NI_E_AI_CFG_HI_TYPE(2) +#define NI_E_AI_CFG_HI_TYPE_GROUND NI_E_AI_CFG_HI_TYPE(3) +#define NI_E_AI_CFG_HI_AC_COUPLE BIT(11) +#define NI_E_AI_CFG_HI_CHAN(x) (((x) & 0x3f) << 0) + +#define NI_E_AO_CFG_REG 0x16 /* w16 */ +#define NI_E_AO_DACSEL(x) ((x) << 8) +#define NI_E_AO_GROUND_REF BIT(3) +#define NI_E_AO_EXT_REF BIT(2) +#define NI_E_AO_DEGLITCH BIT(1) +#define NI_E_AO_CFG_BIP BIT(0) + +#define NI_E_DAC_DIRECT_DATA_REG(x) (0x18 + ((x) * 2)) /* w16 */ + +#define NI_E_8255_BASE 0x19 /* rw8 */ + +#define NI_E_AI_FIFO_DATA_REG 0x1c /* r16 */ + +#define NI_E_AO_FIFO_DATA_REG 0x1e /* w16 */ + +/* + * 611x registers (these boards differ from the e-series) + */ +#define NI611X_MAGIC_REG 0x19 /* w8 (new) */ +#define NI611X_CALIB_CHAN_SEL_REG 0x1a /* w16 (new) */ +#define NI611X_AI_FIFO_DATA_REG 0x1c /* r32 (incompatible) */ +#define NI611X_AI_FIFO_OFFSET_LOAD_REG 0x05 /* r8 (new) */ +#define NI611X_AO_FIFO_DATA_REG 0x14 /* w32 (incompatible) */ +#define NI611X_CAL_GAIN_SEL_REG 0x05 /* w8 (new) */ + +#define NI611X_AO_WINDOW_ADDR_REG 0x18 +#define NI611X_AO_WINDOW_DATA_REG 0x1e + +/* + * 6143 registers + */ +#define NI6143_MAGIC_REG 0x19 /* w8 */ +#define NI6143_DMA_G0_G1_SEL_REG 0x0b /* w8 */ +#define NI6143_PIPELINE_DELAY_REG 0x1f /* w8 */ +#define NI6143_EOC_SET_REG 0x1d /* w8 */ +#define NI6143_DMA_AI_SEL_REG 0x09 /* w8 */ +#define NI6143_AI_FIFO_DATA_REG 0x8c /* r32 */ +#define NI6143_AI_FIFO_FLAG_REG 0x84 /* w32 */ +#define NI6143_AI_FIFO_CTRL_REG 0x88 /* w32 */ +#define NI6143_AI_FIFO_STATUS_REG 0x88 /* r32 */ +#define NI6143_AI_FIFO_DMA_THRESH_REG 0x90 /* w32 */ +#define NI6143_AI_FIFO_WORDS_AVAIL_REG 0x94 /* w32 */ + +#define NI6143_CALIB_CHAN_REG 0x42 /* w16 */ +#define NI6143_CALIB_CHAN_RELAY_ON BIT(15) +#define NI6143_CALIB_CHAN_RELAY_OFF BIT(14) +#define NI6143_CALIB_CHAN(x) (((x) & 0xf) << 0) +#define NI6143_CALIB_CHAN_GND_GND NI6143_CALIB_CHAN(0) /* Offset Cal */ +#define NI6143_CALIB_CHAN_2V5_GND NI6143_CALIB_CHAN(2) /* 2.5V ref */ +#define NI6143_CALIB_CHAN_PWM_GND NI6143_CALIB_CHAN(5) /* +-5V Self Cal */ +#define NI6143_CALIB_CHAN_2V5_PWM NI6143_CALIB_CHAN(10) /* PWM Cal */ +#define NI6143_CALIB_CHAN_PWM_PWM NI6143_CALIB_CHAN(13) /* CMRR */ +#define NI6143_CALIB_CHAN_GND_PWM NI6143_CALIB_CHAN(14) /* PWM Cal */ +#define NI6143_CALIB_LO_TIME_REG 0x20 /* w16 */ +#define NI6143_CALIB_HI_TIME_REG 0x22 /* w16 */ +#define NI6143_RELAY_COUNTER_LOAD_REG 0x4c /* w32 */ +#define NI6143_SIGNATURE_REG 0x50 /* w32 */ +#define NI6143_RELEASE_DATE_REG 0x54 /* w32 */ +#define NI6143_RELEASE_OLDEST_DATE_REG 0x58 /* w32 */ + +/* + * 671x, 611x windowed ao registers + */ +#define NI671X_DAC_DIRECT_DATA_REG(x) (0x00 + (x)) /* w16 */ +#define NI611X_AO_TIMED_REG 0x10 /* w16 */ +#define NI671X_AO_IMMEDIATE_REG 0x11 /* w16 */ +#define NI611X_AO_FIFO_OFFSET_LOAD_REG 0x13 /* w32 */ +#define NI67XX_AO_SP_UPDATES_REG 0x14 /* w16 */ +#define NI611X_AO_WAVEFORM_GEN_REG 0x15 /* w16 */ +#define NI611X_AO_MISC_REG 0x16 /* w16 */ +#define NI611X_AO_MISC_CLEAR_WG BIT(0) +#define NI67XX_AO_CAL_CHAN_SEL_REG 0x17 /* w16 */ +#define NI67XX_AO_CFG2_REG 0x18 /* w16 */ +#define NI67XX_CAL_CMD_REG 0x19 /* w16 */ +#define NI67XX_CAL_STATUS_REG 0x1a /* r8 */ +#define NI67XX_CAL_STATUS_BUSY BIT(0) +#define NI67XX_CAL_STATUS_OSC_DETECT BIT(1) +#define NI67XX_CAL_STATUS_OVERRANGE BIT(2) +#define NI67XX_CAL_DATA_REG 0x1b /* r16 */ +#define NI67XX_CAL_CFG_HI_REG 0x1c /* rw16 */ +#define NI67XX_CAL_CFG_LO_REG 0x1d /* rw16 */ + +#define CS5529_CMD_CB BIT(7) +#define CS5529_CMD_SINGLE_CONV BIT(6) +#define CS5529_CMD_CONT_CONV BIT(5) +#define CS5529_CMD_READ BIT(4) +#define CS5529_CMD_REG(x) (((x) & 0x7) << 1) +#define CS5529_CMD_REG_MASK CS5529_CMD_REG(7) +#define CS5529_CMD_PWR_SAVE BIT(0) + +#define CS5529_OFFSET_REG CS5529_CMD_REG(0) +#define CS5529_GAIN_REG CS5529_CMD_REG(1) +#define CS5529_CONV_DATA_REG CS5529_CMD_REG(3) +#define CS5529_SETUP_REG CS5529_CMD_REG(4) + +#define CS5529_CFG_REG CS5529_CMD_REG(2) +#define CS5529_CFG_AOUT(x) BIT(22 + (x)) +#define CS5529_CFG_DOUT(x) BIT(18 + (x)) +#define CS5529_CFG_LOW_PWR_MODE BIT(16) +#define CS5529_CFG_WORD_RATE(x) (((x) & 0x7) << 13) +#define CS5529_CFG_WORD_RATE_MASK CS5529_CFG_WORD_RATE(0x7) +#define CS5529_CFG_WORD_RATE_2180 CS5529_CFG_WORD_RATE(0) +#define CS5529_CFG_WORD_RATE_1092 CS5529_CFG_WORD_RATE(1) +#define CS5529_CFG_WORD_RATE_532 CS5529_CFG_WORD_RATE(2) +#define CS5529_CFG_WORD_RATE_388 CS5529_CFG_WORD_RATE(3) +#define CS5529_CFG_WORD_RATE_324 CS5529_CFG_WORD_RATE(4) +#define CS5529_CFG_WORD_RATE_17444 CS5529_CFG_WORD_RATE(5) +#define CS5529_CFG_WORD_RATE_8724 CS5529_CFG_WORD_RATE(6) +#define CS5529_CFG_WORD_RATE_4364 CS5529_CFG_WORD_RATE(7) +#define CS5529_CFG_UNIPOLAR BIT(12) +#define CS5529_CFG_RESET BIT(7) +#define CS5529_CFG_RESET_VALID BIT(6) +#define CS5529_CFG_PORT_FLAG BIT(5) +#define CS5529_CFG_PWR_SAVE_SEL BIT(4) +#define CS5529_CFG_DONE_FLAG BIT(3) +#define CS5529_CFG_CALIB(x) (((x) & 0x7) << 0) +#define CS5529_CFG_CALIB_NONE CS5529_CFG_CALIB(0) +#define CS5529_CFG_CALIB_OFFSET_SELF CS5529_CFG_CALIB(1) +#define CS5529_CFG_CALIB_GAIN_SELF CS5529_CFG_CALIB(2) +#define CS5529_CFG_CALIB_BOTH_SELF CS5529_CFG_CALIB(3) +#define CS5529_CFG_CALIB_OFFSET_SYS CS5529_CFG_CALIB(5) +#define CS5529_CFG_CALIB_GAIN_SYS CS5529_CFG_CALIB(6) + +/* + * M-Series specific registers not handled by the DAQ-STC and GPCT register + * remapping. + */ +#define NI_M_CDIO_DMA_SEL_REG 0x007 +#define NI_M_CDIO_DMA_SEL_CDO(x) (((x) & 0xf) << 4) +#define NI_M_CDIO_DMA_SEL_CDO_MASK NI_M_CDIO_DMA_SEL_CDO(0xf) +#define NI_M_CDIO_DMA_SEL_CDI(x) (((x) & 0xf) << 0) +#define NI_M_CDIO_DMA_SEL_CDI_MASK NI_M_CDIO_DMA_SEL_CDI(0xf) +#define NI_M_SCXI_STATUS_REG 0x007 +#define NI_M_AI_AO_SEL_REG 0x009 +#define NI_M_G0_G1_SEL_REG 0x00b +#define NI_M_MISC_CMD_REG 0x00f +#define NI_M_SCXI_SER_DO_REG 0x011 +#define NI_M_SCXI_CTRL_REG 0x013 +#define NI_M_SCXI_OUT_ENA_REG 0x015 +#define NI_M_AI_FIFO_DATA_REG 0x01c +#define NI_M_DIO_REG 0x024 +#define NI_M_DIO_DIR_REG 0x028 +#define NI_M_CAL_PWM_REG 0x040 +#define NI_M_CAL_PWM_HIGH_TIME(x) (((x) & 0xffff) << 16) +#define NI_M_CAL_PWM_LOW_TIME(x) (((x) & 0xffff) << 0) +#define NI_M_GEN_PWM_REG(x) (0x044 + ((x) * 2)) +#define NI_M_AI_CFG_FIFO_DATA_REG 0x05e +#define NI_M_AI_CFG_LAST_CHAN BIT(14) +#define NI_M_AI_CFG_DITHER BIT(13) +#define NI_M_AI_CFG_POLARITY BIT(12) +#define NI_M_AI_CFG_GAIN(x) (((x) & 0x7) << 9) +#define NI_M_AI_CFG_CHAN_TYPE(x) (((x) & 0x7) << 6) +#define NI_M_AI_CFG_CHAN_TYPE_MASK NI_M_AI_CFG_CHAN_TYPE(7) +#define NI_M_AI_CFG_CHAN_TYPE_CALIB NI_M_AI_CFG_CHAN_TYPE(0) +#define NI_M_AI_CFG_CHAN_TYPE_DIFF NI_M_AI_CFG_CHAN_TYPE(1) +#define NI_M_AI_CFG_CHAN_TYPE_COMMON NI_M_AI_CFG_CHAN_TYPE(2) +#define NI_M_AI_CFG_CHAN_TYPE_GROUND NI_M_AI_CFG_CHAN_TYPE(3) +#define NI_M_AI_CFG_CHAN_TYPE_AUX NI_M_AI_CFG_CHAN_TYPE(5) +#define NI_M_AI_CFG_CHAN_TYPE_GHOST NI_M_AI_CFG_CHAN_TYPE(7) +#define NI_M_AI_CFG_BANK_SEL(x) ((((x) & 0x40) << 4) | ((x) & 0x30)) +#define NI_M_AI_CFG_CHAN_SEL(x) (((x) & 0xf) << 0) +#define NI_M_INTC_ENA_REG 0x088 +#define NI_M_INTC_ENA BIT(0) +#define NI_M_INTC_STATUS_REG 0x088 +#define NI_M_INTC_STATUS BIT(0) +#define NI_M_ATRIG_CTRL_REG 0x08c +#define NI_M_AO_SER_INT_ENA_REG 0x0a0 +#define NI_M_AO_SER_INT_ACK_REG 0x0a1 +#define NI_M_AO_SER_INT_STATUS_REG 0x0a1 +#define NI_M_AO_CALIB_REG 0x0a3 +#define NI_M_AO_FIFO_DATA_REG 0x0a4 +#define NI_M_PFI_FILTER_REG 0x0b0 +#define NI_M_PFI_FILTER_SEL(_c, _f) (((_f) & 0x3) << ((_c) * 2)) +#define NI_M_PFI_FILTER_SEL_MASK(_c) NI_M_PFI_FILTER_SEL((_c), 0x3) +#define NI_M_RTSI_FILTER_REG 0x0b4 +#define NI_M_SCXI_LEGACY_COMPAT_REG 0x0bc +#define NI_M_DAC_DIRECT_DATA_REG(x) (0x0c0 + ((x) * 4)) +#define NI_M_AO_WAVEFORM_ORDER_REG(x) (0x0c2 + ((x) * 4)) +#define NI_M_AO_CFG_BANK_REG(x) (0x0c3 + ((x) * 4)) +#define NI_M_AO_CFG_BANK_BIPOLAR BIT(7) +#define NI_M_AO_CFG_BANK_UPDATE_TIMED BIT(6) +#define NI_M_AO_CFG_BANK_REF(x) (((x) & 0x7) << 3) +#define NI_M_AO_CFG_BANK_REF_MASK NI_M_AO_CFG_BANK_REF(7) +#define NI_M_AO_CFG_BANK_REF_INT_10V NI_M_AO_CFG_BANK_REF(0) +#define NI_M_AO_CFG_BANK_REF_INT_5V NI_M_AO_CFG_BANK_REF(1) +#define NI_M_AO_CFG_BANK_OFFSET(x) (((x) & 0x7) << 0) +#define NI_M_AO_CFG_BANK_OFFSET_MASK NI_M_AO_CFG_BANK_OFFSET(7) +#define NI_M_AO_CFG_BANK_OFFSET_0V NI_M_AO_CFG_BANK_OFFSET(0) +#define NI_M_AO_CFG_BANK_OFFSET_5V NI_M_AO_CFG_BANK_OFFSET(1) +#define NI_M_RTSI_SHARED_MUX_REG 0x1a2 +#define NI_M_CLK_FOUT2_REG 0x1c4 +#define NI_M_CLK_FOUT2_RTSI_10MHZ BIT(7) +#define NI_M_CLK_FOUT2_TIMEBASE3_PLL BIT(6) +#define NI_M_CLK_FOUT2_TIMEBASE1_PLL BIT(5) +#define NI_M_CLK_FOUT2_PLL_SRC(x) (((x) & 0x1f) << 0) +#define NI_M_CLK_FOUT2_PLL_SRC_MASK NI_M_CLK_FOUT2_PLL_SRC(0x1f) +#define NI_M_MAX_RTSI_CHAN 7 +#define NI_M_CLK_FOUT2_PLL_SRC_RTSI(x) (((x) == NI_M_MAX_RTSI_CHAN) \ + ? NI_M_CLK_FOUT2_PLL_SRC(0x1b) \ + : NI_M_CLK_FOUT2_PLL_SRC(0xb + (x))) +#define NI_M_CLK_FOUT2_PLL_SRC_STAR NI_M_CLK_FOUT2_PLL_SRC(0x14) +#define NI_M_CLK_FOUT2_PLL_SRC_PXI10 NI_M_CLK_FOUT2_PLL_SRC(0x1d) +#define NI_M_PLL_CTRL_REG 0x1c6 +#define NI_M_PLL_CTRL_VCO_MODE(x) (((x) & 0x3) << 13) +#define NI_M_PLL_CTRL_VCO_MODE_200_325MHZ NI_M_PLL_CTRL_VCO_MODE(0) +#define NI_M_PLL_CTRL_VCO_MODE_175_225MHZ NI_M_PLL_CTRL_VCO_MODE(1) +#define NI_M_PLL_CTRL_VCO_MODE_100_225MHZ NI_M_PLL_CTRL_VCO_MODE(2) +#define NI_M_PLL_CTRL_VCO_MODE_75_150MHZ NI_M_PLL_CTRL_VCO_MODE(3) +#define NI_M_PLL_CTRL_ENA BIT(12) +#define NI_M_PLL_MAX_DIVISOR 0x10 +#define NI_M_PLL_CTRL_DIVISOR(x) (((x) & 0xf) << 8) +#define NI_M_PLL_MAX_MULTIPLIER 0x100 +#define NI_M_PLL_CTRL_MULTIPLIER(x) (((x) & 0xff) << 0) +#define NI_M_PLL_STATUS_REG 0x1c8 +#define NI_M_PLL_STATUS_LOCKED BIT(0) +#define NI_M_PFI_OUT_SEL_REG(x) (0x1d0 + ((x) * 2)) +#define NI_M_PFI_CHAN(_c) (((_c) % 3) * 5) +#define NI_M_PFI_OUT_SEL(_c, _s) (((_s) & 0x1f) << NI_M_PFI_CHAN(_c)) +#define NI_M_PFI_OUT_SEL_MASK(_c) (0x1f << NI_M_PFI_CHAN(_c)) +#define NI_M_PFI_OUT_SEL_TO_SRC(_c, _b) (((_b) >> NI_M_PFI_CHAN(_c)) & 0x1f) +#define NI_M_PFI_DI_REG 0x1dc +#define NI_M_PFI_DO_REG 0x1de +#define NI_M_CFG_BYPASS_FIFO_REG 0x218 +#define NI_M_CFG_BYPASS_FIFO BIT(31) +#define NI_M_CFG_BYPASS_AI_POLARITY BIT(22) +#define NI_M_CFG_BYPASS_AI_DITHER BIT(21) +#define NI_M_CFG_BYPASS_AI_GAIN(x) (((x) & 0x7) << 18) +#define NI_M_CFG_BYPASS_AO_CAL(x) (((x) & 0xf) << 15) +#define NI_M_CFG_BYPASS_AO_CAL_MASK NI_M_CFG_BYPASS_AO_CAL(0xf) +#define NI_M_CFG_BYPASS_AI_MODE_MUX(x) (((x) & 0x3) << 13) +#define NI_M_CFG_BYPASS_AI_MODE_MUX_MASK NI_M_CFG_BYPASS_AI_MODE_MUX(3) +#define NI_M_CFG_BYPASS_AI_CAL_NEG(x) (((x) & 0x7) << 10) +#define NI_M_CFG_BYPASS_AI_CAL_NEG_MASK NI_M_CFG_BYPASS_AI_CAL_NEG(7) +#define NI_M_CFG_BYPASS_AI_CAL_POS(x) (((x) & 0x7) << 7) +#define NI_M_CFG_BYPASS_AI_CAL_POS_MASK NI_M_CFG_BYPASS_AI_CAL_POS(7) +#define NI_M_CFG_BYPASS_AI_CAL_MASK (NI_M_CFG_BYPASS_AI_CAL_POS_MASK | \ + NI_M_CFG_BYPASS_AI_CAL_NEG_MASK | \ + NI_M_CFG_BYPASS_AI_MODE_MUX_MASK | \ + NI_M_CFG_BYPASS_AO_CAL_MASK) +#define NI_M_CFG_BYPASS_AI_BANK(x) (((x) & 0xf) << 3) +#define NI_M_CFG_BYPASS_AI_BANK_MASK NI_M_CFG_BYPASS_AI_BANK(0xf) +#define NI_M_CFG_BYPASS_AI_CHAN(x) (((x) & 0x7) << 0) +#define NI_M_CFG_BYPASS_AI_CHAN_MASK NI_M_CFG_BYPASS_AI_CHAN(7) +#define NI_M_SCXI_DIO_ENA_REG 0x21c +#define NI_M_CDI_FIFO_DATA_REG 0x220 +#define NI_M_CDO_FIFO_DATA_REG 0x220 +#define NI_M_CDIO_STATUS_REG 0x224 +#define NI_M_CDIO_STATUS_CDI_OVERFLOW BIT(20) +#define NI_M_CDIO_STATUS_CDI_OVERRUN BIT(19) +#define NI_M_CDIO_STATUS_CDI_ERROR (NI_M_CDIO_STATUS_CDI_OVERFLOW | \ + NI_M_CDIO_STATUS_CDI_OVERRUN) +#define NI_M_CDIO_STATUS_CDI_FIFO_REQ BIT(18) +#define NI_M_CDIO_STATUS_CDI_FIFO_FULL BIT(17) +#define NI_M_CDIO_STATUS_CDI_FIFO_EMPTY BIT(16) +#define NI_M_CDIO_STATUS_CDO_UNDERFLOW BIT(4) +#define NI_M_CDIO_STATUS_CDO_OVERRUN BIT(3) +#define NI_M_CDIO_STATUS_CDO_ERROR (NI_M_CDIO_STATUS_CDO_UNDERFLOW | \ + NI_M_CDIO_STATUS_CDO_OVERRUN) +#define NI_M_CDIO_STATUS_CDO_FIFO_REQ BIT(2) +#define NI_M_CDIO_STATUS_CDO_FIFO_FULL BIT(1) +#define NI_M_CDIO_STATUS_CDO_FIFO_EMPTY BIT(0) +#define NI_M_CDIO_CMD_REG 0x224 +#define NI_M_CDI_CMD_SW_UPDATE BIT(20) +#define NI_M_CDO_CMD_SW_UPDATE BIT(19) +#define NI_M_CDO_CMD_F_E_INT_ENA_CLR BIT(17) +#define NI_M_CDO_CMD_F_E_INT_ENA_SET BIT(16) +#define NI_M_CDI_CMD_ERR_INT_CONFIRM BIT(15) +#define NI_M_CDO_CMD_ERR_INT_CONFIRM BIT(14) +#define NI_M_CDI_CMD_F_REQ_INT_ENA_CLR BIT(13) +#define NI_M_CDI_CMD_F_REQ_INT_ENA_SET BIT(12) +#define NI_M_CDO_CMD_F_REQ_INT_ENA_CLR BIT(11) +#define NI_M_CDO_CMD_F_REQ_INT_ENA_SET BIT(10) +#define NI_M_CDI_CMD_ERR_INT_ENA_CLR BIT(9) +#define NI_M_CDI_CMD_ERR_INT_ENA_SET BIT(8) +#define NI_M_CDO_CMD_ERR_INT_ENA_CLR BIT(7) +#define NI_M_CDO_CMD_ERR_INT_ENA_SET BIT(6) +#define NI_M_CDI_CMD_RESET BIT(5) +#define NI_M_CDO_CMD_RESET BIT(4) +#define NI_M_CDI_CMD_ARM BIT(3) +#define NI_M_CDI_CMD_DISARM BIT(2) +#define NI_M_CDO_CMD_ARM BIT(1) +#define NI_M_CDO_CMD_DISARM BIT(0) +#define NI_M_CDI_MODE_REG 0x228 +#define NI_M_CDI_MODE_DATA_LANE(x) (((x) & 0x3) << 12) +#define NI_M_CDI_MODE_DATA_LANE_MASK NI_M_CDI_MODE_DATA_LANE(3) +#define NI_M_CDI_MODE_DATA_LANE_0_15 NI_M_CDI_MODE_DATA_LANE(0) +#define NI_M_CDI_MODE_DATA_LANE_16_31 NI_M_CDI_MODE_DATA_LANE(1) +#define NI_M_CDI_MODE_DATA_LANE_0_7 NI_M_CDI_MODE_DATA_LANE(0) +#define NI_M_CDI_MODE_DATA_LANE_8_15 NI_M_CDI_MODE_DATA_LANE(1) +#define NI_M_CDI_MODE_DATA_LANE_16_23 NI_M_CDI_MODE_DATA_LANE(2) +#define NI_M_CDI_MODE_DATA_LANE_24_31 NI_M_CDI_MODE_DATA_LANE(3) +#define NI_M_CDI_MODE_FIFO_MODE BIT(11) +#define NI_M_CDI_MODE_POLARITY BIT(10) +#define NI_M_CDI_MODE_HALT_ON_ERROR BIT(9) +#define NI_M_CDI_MODE_SAMPLE_SRC(x) (((x) & 0x3f) << 0) +#define NI_M_CDI_MODE_SAMPLE_SRC_MASK NI_M_CDI_MODE_SAMPLE_SRC(0x3f) +#define NI_M_CDO_MODE_REG 0x22c +#define NI_M_CDO_MODE_DATA_LANE(x) (((x) & 0x3) << 12) +#define NI_M_CDO_MODE_DATA_LANE_MASK NI_M_CDO_MODE_DATA_LANE(3) +#define NI_M_CDO_MODE_DATA_LANE_0_15 NI_M_CDO_MODE_DATA_LANE(0) +#define NI_M_CDO_MODE_DATA_LANE_16_31 NI_M_CDO_MODE_DATA_LANE(1) +#define NI_M_CDO_MODE_DATA_LANE_0_7 NI_M_CDO_MODE_DATA_LANE(0) +#define NI_M_CDO_MODE_DATA_LANE_8_15 NI_M_CDO_MODE_DATA_LANE(1) +#define NI_M_CDO_MODE_DATA_LANE_16_23 NI_M_CDO_MODE_DATA_LANE(2) +#define NI_M_CDO_MODE_DATA_LANE_24_31 NI_M_CDO_MODE_DATA_LANE(3) +#define NI_M_CDO_MODE_FIFO_MODE BIT(11) +#define NI_M_CDO_MODE_POLARITY BIT(10) +#define NI_M_CDO_MODE_HALT_ON_ERROR BIT(9) +#define NI_M_CDO_MODE_RETRANSMIT BIT(8) +#define NI_M_CDO_MODE_SAMPLE_SRC(x) (((x) & 0x3f) << 0) +#define NI_M_CDO_MODE_SAMPLE_SRC_MASK NI_M_CDO_MODE_SAMPLE_SRC(0x3f) +#define NI_M_CDI_MASK_ENA_REG 0x230 +#define NI_M_CDO_MASK_ENA_REG 0x234 +#define NI_M_STATIC_AI_CTRL_REG(x) ((x) ? (0x260 + (x)) : 0x064) +#define NI_M_AO_REF_ATTENUATION_REG(x) (0x264 + (x)) +#define NI_M_AO_REF_ATTENUATION_X5 BIT(0) + +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 +}; + +struct ni_board_struct { + const char *name; + const char *alt_route_name; + int device_id; + int isapnp_id; + + int n_adchan; + unsigned int ai_maxdata; + + int ai_fifo_depth; + unsigned int alwaysdither:1; + int gainlkup; + int ai_speed; + + int n_aochan; + unsigned int ao_maxdata; + int ao_fifo_depth; + const struct comedi_lrange *ao_range_table; + unsigned int ao_speed; + + int reg_type; + unsigned int has_8255:1; + unsigned int has_32dio_chan:1; + unsigned int dio_speed; /* not for e-series */ + + enum caldac_enum caldac[3]; +}; + +#define MAX_N_CALDACS 34 +#define MAX_N_AO_CHAN 8 +#define NUM_GPCT 2 + +#define NUM_PFI_OUTPUT_SELECT_REGS 6 +#define NUM_RTSI_SHARED_MUXS (NI_RTSI_BRD(-1) - NI_RTSI_BRD(0) + 1) + +#define M_SERIES_EEPROM_SIZE 1024 + +struct ni_private { + unsigned short dio_output; + unsigned short dio_control; + int aimode; + unsigned int ai_calib_source; + unsigned int ai_calib_source_enabled; + /* protects access to windowed registers */ + spinlock_t window_lock; + /* protects interrupt/dma register access */ + spinlock_t soft_reg_copy_lock; + /* protects mite DMA channel request/release */ + spinlock_t mite_channel_lock; + + int changain_state; + unsigned int changain_spec; + + unsigned int caldac_maxdata_list[MAX_N_CALDACS]; + unsigned short caldacs[MAX_N_CALDACS]; + + unsigned short ai_cmd2; + + unsigned short ao_conf[MAX_N_AO_CHAN]; + unsigned short ao_mode1; + unsigned short ao_mode2; + unsigned short ao_mode3; + unsigned short ao_cmd1; + unsigned short ao_cmd2; + + struct ni_gpct_device *counter_dev; + unsigned short an_trig_etc_reg; + + unsigned int 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 int clock_ns; + unsigned int clock_source; + + unsigned short pwm_up_count; + unsigned short pwm_down_count; + + unsigned short ai_fifo_buffer[0x2000]; + u8 eeprom_buffer[M_SERIES_EEPROM_SIZE]; + + struct mite *mite; + struct mite_channel *ai_mite_chan; + struct mite_channel *ao_mite_chan; + struct mite_channel *cdo_mite_chan; + struct mite_ring *ai_mite_ring; + struct mite_ring *ao_mite_ring; + struct mite_ring *cdo_mite_ring; + struct mite_ring *gpct_mite_ring[NUM_GPCT]; + + /* ni_pcimio board type flags (based on the boardinfo reg_type) */ + unsigned int is_m_series:1; + unsigned int is_6xxx:1; + unsigned int is_611x:1; + unsigned int is_6143:1; + unsigned int is_622x:1; + unsigned int is_625x:1; + unsigned int is_628x:1; + unsigned int is_67xx:1; + unsigned int is_6711:1; + unsigned int is_6713:1; + + /* + * Boolean value of whether device needs to be armed. + * + * Currently, only NI AO devices are known to be needing arming, since + * the DAC registers must be preloaded before triggering. + * This variable should only be set true during a command operation + * (e.g ni_ao_cmd) and should then be set false by the arming + * function (e.g. ni_ao_arm). + * + * This variable helps to ensure that multiple DMA allocations are not + * possible. + */ + unsigned int ao_needs_arming:1; + + /* device signal route tables */ + struct ni_route_tables routing_tables; + + /* + * Number of clients (RTSI lines) for current RTSI MUX source. + * + * This allows resource management of RTSI board/shared mux lines by + * marking the RTSI line that is using a particular MUX. Currently, + * these lines are only automatically allocated based on source of the + * route requested. Furthermore, the only way that this auto-allocation + * and configuration works is via the globally-named ni signal/terminal + * names. + */ + u8 rtsi_shared_mux_usage[NUM_RTSI_SHARED_MUXS]; + + /* + * softcopy register for rtsi shared mux/board lines. + * For e-series, the bit layout of this register is + * (docs: mhddk/nieseries/ChipObjects/tSTC.{h,ipp}, + * DAQ-STC, Jan 1999, 340934B-01): + * bits 0:2 -- NI_RTSI_BRD(0) source selection + * bits 3:5 -- NI_RTSI_BRD(1) source selection + * bits 6:8 -- NI_RTSI_BRD(2) source selection + * bits 9:11 -- NI_RTSI_BRD(3) source selection + * bit 12 -- NI_RTSI_BRD(0) direction, 0:input, 1:output + * bit 13 -- NI_RTSI_BRD(1) direction, 0:input, 1:output + * bit 14 -- NI_RTSI_BRD(2) direction, 0:input, 1:output + * bit 15 -- NI_RTSI_BRD(3) direction, 0:input, 1:output + * According to DAQ-STC: + * RTSI Board Interface--Configured as an input, each bidirectional + * RTSI_BRD pin can drive any of the seven RTSI_TRIGGER pins. + * RTSI_BRD<0..1> can also be driven by AI STOP and RTSI_BRD<2..3> + * can also be driven by the AI START and SCAN_IN_PROG signals. + * These pins provide a mechanism for additional board-level signals + * to be sent on or received from the RTSI bus. + * Couple of comments: + * - Neither the DAQ-STC nor the MHDDK is clear on what the direction + * of the RTSI_BRD pins actually means. There does not appear to be + * any clear indication on what "output" would mean, since the point + * of the RTSI_BRD lines is to always drive one of the + * RTSI_TRIGGER<0..6> lines. + * - The DAQ-STC also indicates that the NI_RTSI_BRD lines can be + * driven by any of the RTSI_TRIGGER<0..6> lines. + * But, looking at valid device routes, as visually imported from + * NI-MAX, there appears to be only one family (so far) that has the + * ability to route a signal from one TRIGGER_LINE to another + * TRIGGER_LINE: the 653x family of DIO devices. + * + * For m-series, the bit layout of this register is + * (docs: mhddk/nimseries/ChipObjects/tMSeries.{h,ipp}): + * bits 0:3 -- NI_RTSI_BRD(0) source selection + * bits 4:7 -- NI_RTSI_BRD(1) source selection + * bits 8:11 -- NI_RTSI_BRD(2) source selection + * bits 12:15 -- NI_RTSI_BRD(3) source selection + * Note: The m-series does not have any option to change direction of + * NI_RTSI_BRD muxes. Furthermore, there are no register values that + * indicate the ability to have TRIGGER_LINES driving the output of + * the NI_RTSI_BRD muxes. + */ + u16 rtsi_shared_mux_reg; + + /* + * Number of clients (RTSI lines) for current RGOUT0 path. + * Stored in part of in RTSI_TRIG_DIR or RTSI_TRIGB registers + */ + u8 rgout0_usage; +}; + +static const struct comedi_lrange range_ni_E_ao_ext; + +#endif /* _COMEDI_NI_STC_H */ diff --git a/drivers/comedi/drivers/ni_tio.c b/drivers/comedi/drivers/ni_tio.c new file mode 100644 index 000000000..da6826d77 --- /dev/null +++ b/drivers/comedi/drivers/ni_tio.c @@ -0,0 +1,1842 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Support for NI general purpose counters + * + * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +/* + * Module: ni_tio + * Description: National Instruments general purpose counters + * Author: J.P. Mellor <jpmellor@rose-hulman.edu>, + * Herman.Bruyninckx@mech.kuleuven.ac.be, + * Wim.Meeussen@mech.kuleuven.ac.be, + * Klaas.Gadeyne@mech.kuleuven.ac.be, + * Frank Mori Hess <fmhess@users.sourceforge.net> + * Updated: Thu Nov 16 09:50:32 EST 2006 + * Status: works + * + * This module is not used directly by end-users. Rather, it + * is used by other drivers (for example ni_660x and ni_pcimio) + * to provide support for NI's general purpose counters. It was + * originally based on the counter code from ni_660x.c and + * ni_mio_common.c. + * + * References: + * DAQ 660x Register-Level Programmer Manual (NI 370505A-01) + * DAQ 6601/6602 User Manual (NI 322137B-01) + * 340934b.pdf DAQ-STC reference manual + * + * TODO: Support use of both banks X and Y + */ + +#include <linux/module.h> +#include <linux/slab.h> + +#include "ni_tio_internal.h" + +/* + * clock sources for ni e and m series boards, + * get bits with GI_SRC_SEL() + */ +#define NI_M_TIMEBASE_1_CLK 0x0 /* 20MHz */ +#define NI_M_PFI_CLK(x) (((x) < 10) ? (1 + (x)) : (0xb + (x))) +#define NI_M_RTSI_CLK(x) (((x) == 7) ? 0x1b : (0xb + (x))) +#define NI_M_TIMEBASE_2_CLK 0x12 /* 100KHz */ +#define NI_M_NEXT_TC_CLK 0x13 +#define NI_M_NEXT_GATE_CLK 0x14 /* Gi_Src_SubSelect=0 */ +#define NI_M_PXI_STAR_TRIGGER_CLK 0x14 /* Gi_Src_SubSelect=1 */ +#define NI_M_PXI10_CLK 0x1d +#define NI_M_TIMEBASE_3_CLK 0x1e /* 80MHz, Gi_Src_SubSelect=0 */ +#define NI_M_ANALOG_TRIGGER_OUT_CLK 0x1e /* Gi_Src_SubSelect=1 */ +#define NI_M_LOGIC_LOW_CLK 0x1f +#define NI_M_MAX_PFI_CHAN 15 +#define NI_M_MAX_RTSI_CHAN 7 + +/* + * clock sources for ni_660x boards, + * get bits with GI_SRC_SEL() + */ +#define NI_660X_TIMEBASE_1_CLK 0x0 /* 20MHz */ +#define NI_660X_SRC_PIN_I_CLK 0x1 +#define NI_660X_SRC_PIN_CLK(x) (0x2 + (x)) +#define NI_660X_NEXT_GATE_CLK 0xa +#define NI_660X_RTSI_CLK(x) (0xb + (x)) +#define NI_660X_TIMEBASE_2_CLK 0x12 /* 100KHz */ +#define NI_660X_NEXT_TC_CLK 0x13 +#define NI_660X_TIMEBASE_3_CLK 0x1e /* 80MHz */ +#define NI_660X_LOGIC_LOW_CLK 0x1f +#define NI_660X_MAX_SRC_PIN 7 +#define NI_660X_MAX_RTSI_CHAN 6 + +/* ni m series gate_select */ +#define NI_M_TIMESTAMP_MUX_GATE_SEL 0x0 +#define NI_M_PFI_GATE_SEL(x) (((x) < 10) ? (1 + (x)) : (0xb + (x))) +#define NI_M_RTSI_GATE_SEL(x) (((x) == 7) ? 0x1b : (0xb + (x))) +#define NI_M_AI_START2_GATE_SEL 0x12 +#define NI_M_PXI_STAR_TRIGGER_GATE_SEL 0x13 +#define NI_M_NEXT_OUT_GATE_SEL 0x14 +#define NI_M_AI_START1_GATE_SEL 0x1c +#define NI_M_NEXT_SRC_GATE_SEL 0x1d +#define NI_M_ANALOG_TRIG_OUT_GATE_SEL 0x1e +#define NI_M_LOGIC_LOW_GATE_SEL 0x1f + +/* ni_660x gate select */ +#define NI_660X_SRC_PIN_I_GATE_SEL 0x0 +#define NI_660X_GATE_PIN_I_GATE_SEL 0x1 +#define NI_660X_PIN_GATE_SEL(x) (0x2 + (x)) +#define NI_660X_NEXT_SRC_GATE_SEL 0xa +#define NI_660X_RTSI_GATE_SEL(x) (0xb + (x)) +#define NI_660X_NEXT_OUT_GATE_SEL 0x14 +#define NI_660X_LOGIC_LOW_GATE_SEL 0x1f +#define NI_660X_MAX_GATE_PIN 7 + +/* ni_660x second gate select */ +#define NI_660X_SRC_PIN_I_GATE2_SEL 0x0 +#define NI_660X_UD_PIN_I_GATE2_SEL 0x1 +#define NI_660X_UD_PIN_GATE2_SEL(x) (0x2 + (x)) +#define NI_660X_NEXT_SRC_GATE2_SEL 0xa +#define NI_660X_RTSI_GATE2_SEL(x) (0xb + (x)) +#define NI_660X_NEXT_OUT_GATE2_SEL 0x14 +#define NI_660X_SELECTED_GATE2_SEL 0x1e +#define NI_660X_LOGIC_LOW_GATE2_SEL 0x1f +#define NI_660X_MAX_UP_DOWN_PIN 7 + +static inline unsigned int GI_PRESCALE_X2(enum ni_gpct_variant variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + default: + return 0; + case ni_gpct_variant_m_series: + return GI_M_PRESCALE_X2; + case ni_gpct_variant_660x: + return GI_660X_PRESCALE_X2; + } +} + +static inline unsigned int GI_PRESCALE_X8(enum ni_gpct_variant variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + default: + return 0; + case ni_gpct_variant_m_series: + return GI_M_PRESCALE_X8; + case ni_gpct_variant_660x: + return GI_660X_PRESCALE_X8; + } +} + +static bool ni_tio_has_gate2_registers(const struct ni_gpct_device *counter_dev) +{ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + default: + return false; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + return true; + } +} + +/** + * ni_tio_write() - Write a TIO register using the driver provided callback. + * @counter: struct ni_gpct counter. + * @value: the value to write + * @reg: the register to write. + */ +void ni_tio_write(struct ni_gpct *counter, unsigned int value, + enum ni_gpct_register reg) +{ + if (reg < NITIO_NUM_REGS) + counter->counter_dev->write(counter, value, reg); +} +EXPORT_SYMBOL_GPL(ni_tio_write); + +/** + * ni_tio_read() - Read a TIO register using the driver provided callback. + * @counter: struct ni_gpct counter. + * @reg: the register to read. + */ +unsigned int ni_tio_read(struct ni_gpct *counter, enum ni_gpct_register reg) +{ + if (reg < NITIO_NUM_REGS) + return counter->counter_dev->read(counter, reg); + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_read); + +static void ni_tio_reset_count_and_disarm(struct ni_gpct *counter) +{ + unsigned int cidx = counter->counter_index; + + ni_tio_write(counter, GI_RESET(cidx), NITIO_RESET_REG(cidx)); +} + +static int ni_tio_clock_period_ps(const struct ni_gpct *counter, + unsigned int generic_clock_source, + u64 *period_ps) +{ + u64 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. + */ + *period_ps = counter->clock_period_ps; + return 0; + } + + 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: + return -EINVAL; + } + *period_ps = clock_period_ps; + return 0; +} + +static void ni_tio_set_bits_transient(struct ni_gpct *counter, + enum ni_gpct_register reg, + unsigned int mask, unsigned int value, + unsigned int transient) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int chip = counter->chip_index; + unsigned long flags; + + if (reg < NITIO_NUM_REGS && chip < counter_dev->num_chips) { + unsigned int *regs = counter_dev->regs[chip]; + + spin_lock_irqsave(&counter_dev->regs_lock, flags); + regs[reg] &= ~mask; + regs[reg] |= (value & mask); + ni_tio_write(counter, regs[reg] | transient, reg); + spin_unlock_irqrestore(&counter_dev->regs_lock, flags); + } +} + +/** + * ni_tio_set_bits() - Safely write a counter register. + * @counter: struct ni_gpct counter. + * @reg: the register to write. + * @mask: the bits to change. + * @value: the new bits value. + * + * Used to write to, and update the software copy, a register whose bits may + * be twiddled in interrupt context, or whose software copy may be read in + * interrupt context. + */ +void ni_tio_set_bits(struct ni_gpct *counter, enum ni_gpct_register reg, + unsigned int mask, unsigned int value) +{ + ni_tio_set_bits_transient(counter, reg, mask, value, 0x0); +} +EXPORT_SYMBOL_GPL(ni_tio_set_bits); + +/** + * ni_tio_get_soft_copy() - Safely read the software copy of a counter register. + * @counter: struct ni_gpct counter. + * @reg: the register to read. + * + * Used to get 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. + */ +unsigned int ni_tio_get_soft_copy(const struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int chip = counter->chip_index; + unsigned int value = 0; + unsigned long flags; + + if (reg < NITIO_NUM_REGS && chip < counter_dev->num_chips) { + spin_lock_irqsave(&counter_dev->regs_lock, flags); + value = counter_dev->regs[chip][reg]; + spin_unlock_irqrestore(&counter_dev->regs_lock, flags); + } + return value; +} +EXPORT_SYMBOL_GPL(ni_tio_get_soft_copy); + +static unsigned int ni_tio_clock_src_modifiers(const struct ni_gpct *counter) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int counting_mode_bits = + ni_tio_get_soft_copy(counter, NITIO_CNT_MODE_REG(cidx)); + unsigned int bits = 0; + + if (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) & + GI_SRC_POL_INVERT) + bits |= NI_GPCT_INVERT_CLOCK_SRC_BIT; + if (counting_mode_bits & GI_PRESCALE_X2(counter_dev->variant)) + bits |= NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS; + if (counting_mode_bits & GI_PRESCALE_X8(counter_dev->variant)) + bits |= NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS; + return bits; +} + +static int ni_m_series_clock_src_select(const struct ni_gpct *counter, + unsigned int *clk_src) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int chip = counter->chip_index; + unsigned int second_gate_reg = NITIO_GATE2_REG(cidx); + unsigned int clock_source = 0; + unsigned int src; + unsigned int i; + + src = GI_BITS_TO_SRC(ni_tio_get_soft_copy(counter, + NITIO_INPUT_SEL_REG(cidx))); + + switch (src) { + case NI_M_TIMEBASE_1_CLK: + clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS; + break; + case NI_M_TIMEBASE_2_CLK: + clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS; + break; + case NI_M_TIMEBASE_3_CLK: + if (counter_dev->regs[chip][second_gate_reg] & GI_SRC_SUBSEL) + clock_source = + NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS; + else + clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS; + break; + case NI_M_LOGIC_LOW_CLK: + clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS; + break; + case NI_M_NEXT_GATE_CLK: + if (counter_dev->regs[chip][second_gate_reg] & GI_SRC_SUBSEL) + clock_source = NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS; + else + clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS; + break; + case NI_M_PXI10_CLK: + clock_source = NI_GPCT_PXI10_CLOCK_SRC_BITS; + break; + case NI_M_NEXT_TC_CLK: + clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS; + break; + default: + for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) { + if (src == NI_M_RTSI_CLK(i)) { + clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= NI_M_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) { + if (src == NI_M_PFI_CLK(i)) { + clock_source = NI_GPCT_PFI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= NI_M_MAX_PFI_CHAN) + break; + return -EINVAL; + } + clock_source |= ni_tio_clock_src_modifiers(counter); + *clk_src = clock_source; + return 0; +} + +static int ni_660x_clock_src_select(const struct ni_gpct *counter, + unsigned int *clk_src) +{ + unsigned int clock_source = 0; + unsigned int cidx = counter->counter_index; + unsigned int src; + unsigned int i; + + src = GI_BITS_TO_SRC(ni_tio_get_soft_copy(counter, + NITIO_INPUT_SEL_REG(cidx))); + + switch (src) { + case NI_660X_TIMEBASE_1_CLK: + clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS; + break; + case NI_660X_TIMEBASE_2_CLK: + clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS; + break; + case NI_660X_TIMEBASE_3_CLK: + clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS; + break; + case NI_660X_LOGIC_LOW_CLK: + clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS; + break; + case NI_660X_SRC_PIN_I_CLK: + clock_source = NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS; + break; + case NI_660X_NEXT_GATE_CLK: + clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS; + break; + case NI_660X_NEXT_TC_CLK: + clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS; + break; + default: + for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { + if (src == NI_660X_RTSI_CLK(i)) { + clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= NI_660X_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_660X_MAX_SRC_PIN; ++i) { + if (src == NI_660X_SRC_PIN_CLK(i)) { + clock_source = + NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= NI_660X_MAX_SRC_PIN) + break; + return -EINVAL; + } + clock_source |= ni_tio_clock_src_modifiers(counter); + *clk_src = clock_source; + return 0; +} + +static int ni_tio_generic_clock_src_select(const struct ni_gpct *counter, + unsigned int *clk_src) +{ + switch (counter->counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + default: + return ni_m_series_clock_src_select(counter, clk_src); + case ni_gpct_variant_660x: + return ni_660x_clock_src_select(counter, clk_src); + } +} + +static void ni_tio_set_sync_mode(struct ni_gpct *counter) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + static const u64 min_normal_sync_period_ps = 25000; + unsigned int mask = 0; + unsigned int bits = 0; + unsigned int reg; + unsigned int mode; + unsigned int clk_src = 0; + u64 ps = 0; + int ret; + bool force_alt_sync; + + /* only m series and 660x variants have counting mode registers */ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + default: + return; + case ni_gpct_variant_m_series: + mask = GI_M_ALT_SYNC; + break; + case ni_gpct_variant_660x: + mask = GI_660X_ALT_SYNC; + break; + } + + reg = NITIO_CNT_MODE_REG(cidx); + mode = ni_tio_get_soft_copy(counter, reg); + switch (mode & GI_CNT_MODE_MASK) { + case GI_CNT_MODE_QUADX1: + case GI_CNT_MODE_QUADX2: + case GI_CNT_MODE_QUADX4: + case GI_CNT_MODE_SYNC_SRC: + force_alt_sync = true; + break; + default: + force_alt_sync = false; + break; + } + + ret = ni_tio_generic_clock_src_select(counter, &clk_src); + if (ret) + return; + ret = ni_tio_clock_period_ps(counter, clk_src, &ps); + if (ret) + return; + /* + * 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. + */ + if (force_alt_sync || (ps && ps < min_normal_sync_period_ps)) + bits = mask; + + ni_tio_set_bits(counter, reg, mask, bits); +} + +static int ni_tio_set_counter_mode(struct ni_gpct *counter, unsigned int mode) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int mode_reg_mask; + unsigned int mode_reg_values; + unsigned int input_select_bits = 0; + /* these bits map directly on to the mode register */ + static const unsigned int mode_reg_direct_mask = + NI_GPCT_GATE_ON_BOTH_EDGES_BIT | NI_GPCT_EDGE_GATE_MODE_MASK | + NI_GPCT_STOP_MODE_MASK | NI_GPCT_OUTPUT_MODE_MASK | + NI_GPCT_HARDWARE_DISARM_MASK | NI_GPCT_LOADING_ON_TC_BIT | + NI_GPCT_LOADING_ON_GATE_BIT | NI_GPCT_LOAD_B_SELECT_BIT; + + mode_reg_mask = mode_reg_direct_mask | GI_RELOAD_SRC_SWITCHING; + mode_reg_values = mode & mode_reg_direct_mask; + switch (mode & NI_GPCT_RELOAD_SOURCE_MASK) { + case NI_GPCT_RELOAD_SOURCE_FIXED_BITS: + break; + case NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS: + mode_reg_values |= GI_RELOAD_SRC_SWITCHING; + break; + case NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS: + input_select_bits |= GI_GATE_SEL_LOAD_SRC; + mode_reg_mask |= GI_GATING_MODE_MASK; + mode_reg_values |= GI_LEVEL_GATING; + break; + default: + break; + } + ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), + mode_reg_mask, mode_reg_values); + + if (ni_tio_counting_mode_registers_present(counter_dev)) { + unsigned int bits = 0; + + bits |= GI_CNT_MODE(mode >> NI_GPCT_COUNTING_MODE_SHIFT); + bits |= GI_INDEX_PHASE((mode >> NI_GPCT_INDEX_PHASE_BITSHIFT)); + if (mode & NI_GPCT_INDEX_ENABLE_BIT) + bits |= GI_INDEX_MODE; + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), + GI_CNT_MODE_MASK | GI_INDEX_PHASE_MASK | + GI_INDEX_MODE, bits); + ni_tio_set_sync_mode(counter); + } + + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_CNT_DIR_MASK, + GI_CNT_DIR(mode >> NI_GPCT_COUNTING_DIRECTION_SHIFT)); + + if (mode & NI_GPCT_OR_GATE_BIT) + input_select_bits |= GI_OR_GATE; + if (mode & NI_GPCT_INVERT_OUTPUT_BIT) + input_select_bits |= GI_OUTPUT_POL_INVERT; + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + GI_GATE_SEL_LOAD_SRC | GI_OR_GATE | + GI_OUTPUT_POL_INVERT, input_select_bits); + + return 0; +} + +int ni_tio_arm(struct ni_gpct *counter, bool arm, unsigned int start_trigger) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int transient_bits = 0; + + if (arm) { + unsigned int mask = 0; + unsigned int bits = 0; + + /* only m series and 660x have counting mode registers */ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + default: + break; + case ni_gpct_variant_m_series: + mask = GI_M_HW_ARM_SEL_MASK; + break; + case ni_gpct_variant_660x: + mask = GI_660X_HW_ARM_SEL_MASK; + break; + } + + switch (start_trigger) { + case NI_GPCT_ARM_IMMEDIATE: + transient_bits |= GI_ARM; + break; + case NI_GPCT_ARM_PAIRED_IMMEDIATE: + transient_bits |= GI_ARM | GI_ARM_COPY; + break; + default: + /* + * for m series and 660x, pass-through the least + * significant bits so we can figure out what select + * later + */ + if (mask && (start_trigger & NI_GPCT_ARM_UNKNOWN)) { + bits |= GI_HW_ARM_ENA | + (GI_HW_ARM_SEL(start_trigger) & mask); + } else { + return -EINVAL; + } + break; + } + + if (mask) + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), + GI_HW_ARM_ENA | mask, bits); + } else { + transient_bits |= GI_DISARM; + } + ni_tio_set_bits_transient(counter, NITIO_CMD_REG(cidx), + 0, 0, transient_bits); + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_arm); + +static int ni_660x_clk_src(unsigned int clock_source, unsigned int *bits) +{ + unsigned int clk_src = clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK; + unsigned int ni_660x_clock; + unsigned int i; + + switch (clk_src) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_TIMEBASE_1_CLK; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_TIMEBASE_2_CLK; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_TIMEBASE_3_CLK; + break; + case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_LOGIC_LOW_CLK; + break; + case NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_SRC_PIN_I_CLK; + break; + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_NEXT_GATE_CLK; + break; + case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS: + ni_660x_clock = NI_660X_NEXT_TC_CLK; + break; + default: + for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { + if (clk_src == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) { + ni_660x_clock = NI_660X_RTSI_CLK(i); + break; + } + } + if (i <= NI_660X_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_660X_MAX_SRC_PIN; ++i) { + if (clk_src == NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i)) { + ni_660x_clock = NI_660X_SRC_PIN_CLK(i); + break; + } + } + if (i <= NI_660X_MAX_SRC_PIN) + break; + return -EINVAL; + } + *bits = GI_SRC_SEL(ni_660x_clock); + return 0; +} + +static int ni_m_clk_src(unsigned int clock_source, unsigned int *bits) +{ + unsigned int clk_src = clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK; + unsigned int ni_m_series_clock; + unsigned int i; + + switch (clk_src) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_TIMEBASE_1_CLK; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_TIMEBASE_2_CLK; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_TIMEBASE_3_CLK; + break; + case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_LOGIC_LOW_CLK; + break; + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_NEXT_GATE_CLK; + break; + case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_NEXT_TC_CLK; + break; + case NI_GPCT_PXI10_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_PXI10_CLK; + break; + case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_PXI_STAR_TRIGGER_CLK; + break; + case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_ANALOG_TRIGGER_OUT_CLK; + break; + default: + for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) { + if (clk_src == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) { + ni_m_series_clock = NI_M_RTSI_CLK(i); + break; + } + } + if (i <= NI_M_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) { + if (clk_src == NI_GPCT_PFI_CLOCK_SRC_BITS(i)) { + ni_m_series_clock = NI_M_PFI_CLK(i); + break; + } + } + if (i <= NI_M_MAX_PFI_CHAN) + break; + return -EINVAL; + } + *bits = GI_SRC_SEL(ni_m_series_clock); + return 0; +}; + +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 int cidx = counter->counter_index; + unsigned int chip = counter->chip_index; + unsigned int 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[chip][second_gate_reg] &= ~GI_SRC_SUBSEL; + break; + /* Gi_Source_Subselect is one */ + case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS: + case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS: + counter_dev->regs[chip][second_gate_reg] |= GI_SRC_SUBSEL; + break; + /* Gi_Source_Subselect doesn't matter */ + default: + return; + } + ni_tio_write(counter, counter_dev->regs[chip][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 int cidx = counter->counter_index; + unsigned int bits = 0; + int ret; + + switch (counter_dev->variant) { + case ni_gpct_variant_660x: + ret = ni_660x_clk_src(clock_source, &bits); + break; + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + default: + ret = ni_m_clk_src(clock_source, &bits); + break; + } + if (ret) { + struct comedi_device *dev = counter_dev->dev; + + dev_err(dev->class_dev, "invalid clock source 0x%x\n", + clock_source); + return ret; + } + + if (clock_source & NI_GPCT_INVERT_CLOCK_SRC_BIT) + bits |= GI_SRC_POL_INVERT; + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), + GI_SRC_SEL_MASK | GI_SRC_POL_INVERT, bits); + ni_tio_set_source_subselect(counter, clock_source); + + if (ni_tio_counting_mode_registers_present(counter_dev)) { + bits = 0; + switch (clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK) { + case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS: + break; + case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS: + bits |= GI_PRESCALE_X2(counter_dev->variant); + break; + case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS: + bits |= GI_PRESCALE_X8(counter_dev->variant); + break; + default: + return -EINVAL; + } + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), + GI_PRESCALE_X2(counter_dev->variant) | + GI_PRESCALE_X8(counter_dev->variant), bits); + } + counter->clock_period_ps = period_ns * 1000; + ni_tio_set_sync_mode(counter); + return 0; +} + +static int ni_tio_get_clock_src(struct ni_gpct *counter, + unsigned int *clock_source, + unsigned int *period_ns) +{ + u64 temp64 = 0; + int ret; + + ret = ni_tio_generic_clock_src_select(counter, clock_source); + if (ret) + return ret; + ret = ni_tio_clock_period_ps(counter, *clock_source, &temp64); + if (ret) + return ret; + do_div(temp64, 1000); /* ps to ns */ + *period_ns = temp64; + return 0; +} + +static inline void ni_tio_set_gate_raw(struct ni_gpct *counter, + unsigned int gate_source) +{ + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(counter->counter_index), + GI_GATE_SEL_MASK, GI_GATE_SEL(gate_source)); +} + +static inline void ni_tio_set_gate2_raw(struct ni_gpct *counter, + unsigned int gate_source) +{ + ni_tio_set_bits(counter, NITIO_GATE2_REG(counter->counter_index), + GI_GATE2_SEL_MASK, GI_GATE2_SEL(gate_source)); +} + +/* Set the mode bits for gate. */ +static inline void ni_tio_set_gate_mode(struct ni_gpct *counter, + unsigned int src) +{ + unsigned int mode_bits = 0; + + if (CR_CHAN(src) & NI_GPCT_DISABLED_GATE_SELECT) { + /* + * Allowing bitwise comparison here to allow non-zero raw + * register value to be used for channel when disabling. + */ + mode_bits = GI_GATING_DISABLED; + } else { + if (src & CR_INVERT) + mode_bits |= GI_GATE_POL_INVERT; + if (src & CR_EDGE) + mode_bits |= GI_RISING_EDGE_GATING; + else + mode_bits |= GI_LEVEL_GATING; + } + ni_tio_set_bits(counter, NITIO_MODE_REG(counter->counter_index), + GI_GATE_POL_INVERT | GI_GATING_MODE_MASK, + mode_bits); +} + +/* + * Set the mode bits for gate2. + * + * Previously, the code this function represents did not actually write anything + * to the register. Rather, writing to this register was reserved for the code + * ni ni_tio_set_gate2_raw. + */ +static inline void ni_tio_set_gate2_mode(struct ni_gpct *counter, + unsigned int src) +{ + /* + * The GI_GATE2_MODE bit was previously set in the code that also sets + * the gate2 source. + * We'll set mode bits _after_ source bits now, and thus, this function + * will effectively enable the second gate after all bits are set. + */ + unsigned int mode_bits = GI_GATE2_MODE; + + if (CR_CHAN(src) & NI_GPCT_DISABLED_GATE_SELECT) + /* + * Allowing bitwise comparison here to allow non-zero raw + * register value to be used for channel when disabling. + */ + mode_bits = GI_GATING_DISABLED; + if (src & CR_INVERT) + mode_bits |= GI_GATE2_POL_INVERT; + + ni_tio_set_bits(counter, NITIO_GATE2_REG(counter->counter_index), + GI_GATE2_POL_INVERT | GI_GATE2_MODE, mode_bits); +} + +static int ni_660x_set_gate(struct ni_gpct *counter, unsigned int gate_source) +{ + unsigned int chan = CR_CHAN(gate_source); + unsigned int gate_sel; + unsigned int i; + + switch (chan) { + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + gate_sel = NI_660X_NEXT_SRC_GATE_SEL; + break; + case NI_GPCT_NEXT_OUT_GATE_SELECT: + case NI_GPCT_LOGIC_LOW_GATE_SELECT: + case NI_GPCT_SOURCE_PIN_i_GATE_SELECT: + case NI_GPCT_GATE_PIN_i_GATE_SELECT: + gate_sel = chan & 0x1f; + break; + default: + for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { + if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) { + gate_sel = chan & 0x1f; + break; + } + } + if (i <= NI_660X_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_660X_MAX_GATE_PIN; ++i) { + if (chan == NI_GPCT_GATE_PIN_GATE_SELECT(i)) { + gate_sel = chan & 0x1f; + break; + } + } + if (i <= NI_660X_MAX_GATE_PIN) + break; + return -EINVAL; + } + ni_tio_set_gate_raw(counter, gate_sel); + return 0; +} + +static int ni_m_set_gate(struct ni_gpct *counter, unsigned int gate_source) +{ + unsigned int chan = CR_CHAN(gate_source); + unsigned int gate_sel; + unsigned int i; + + switch (chan) { + case NI_GPCT_TIMESTAMP_MUX_GATE_SELECT: + case NI_GPCT_AI_START2_GATE_SELECT: + case NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT: + case NI_GPCT_NEXT_OUT_GATE_SELECT: + case NI_GPCT_AI_START1_GATE_SELECT: + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + case NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT: + case NI_GPCT_LOGIC_LOW_GATE_SELECT: + gate_sel = chan & 0x1f; + break; + default: + for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) { + if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) { + gate_sel = chan & 0x1f; + break; + } + } + if (i <= NI_M_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) { + if (chan == NI_GPCT_PFI_GATE_SELECT(i)) { + gate_sel = chan & 0x1f; + break; + } + } + if (i <= NI_M_MAX_PFI_CHAN) + break; + return -EINVAL; + } + ni_tio_set_gate_raw(counter, gate_sel); + return 0; +} + +static int ni_660x_set_gate2(struct ni_gpct *counter, unsigned int gate_source) +{ + unsigned int chan = CR_CHAN(gate_source); + unsigned int gate2_sel; + unsigned int i; + + switch (chan) { + case NI_GPCT_SOURCE_PIN_i_GATE_SELECT: + case NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT: + case NI_GPCT_SELECTED_GATE_GATE_SELECT: + case NI_GPCT_NEXT_OUT_GATE_SELECT: + case NI_GPCT_LOGIC_LOW_GATE_SELECT: + gate2_sel = chan & 0x1f; + break; + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + gate2_sel = NI_660X_NEXT_SRC_GATE2_SEL; + break; + default: + for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { + if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) { + gate2_sel = chan & 0x1f; + break; + } + } + if (i <= NI_660X_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_660X_MAX_UP_DOWN_PIN; ++i) { + if (chan == NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i)) { + gate2_sel = chan & 0x1f; + break; + } + } + if (i <= NI_660X_MAX_UP_DOWN_PIN) + break; + return -EINVAL; + } + ni_tio_set_gate2_raw(counter, gate2_sel); + return 0; +} + +static int ni_m_set_gate2(struct ni_gpct *counter, unsigned int gate_source) +{ + /* + * FIXME: We don't know what the m-series second gate codes are, + * so we'll just pass the bits through for now. + */ + ni_tio_set_gate2_raw(counter, gate_source); + return 0; +} + +int ni_tio_set_gate_src_raw(struct ni_gpct *counter, + unsigned int gate, unsigned int src) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + + switch (gate) { + case 0: + /* 1. start by disabling gate */ + ni_tio_set_gate_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); + /* 2. set the requested gate source */ + ni_tio_set_gate_raw(counter, src); + /* 3. reenable & set mode to starts things back up */ + ni_tio_set_gate_mode(counter, src); + break; + case 1: + if (!ni_tio_has_gate2_registers(counter_dev)) + return -EINVAL; + + /* 1. start by disabling gate */ + ni_tio_set_gate2_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); + /* 2. set the requested gate source */ + ni_tio_set_gate2_raw(counter, src); + /* 3. reenable & set mode to starts things back up */ + ni_tio_set_gate2_mode(counter, src); + break; + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_set_gate_src_raw); + +int ni_tio_set_gate_src(struct ni_gpct *counter, + unsigned int gate, unsigned int src) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + /* + * mask off disable flag. This high bit still passes CR_CHAN. + * Doing this allows one to both set the gate as disabled, but also + * change the route value of the gate. + */ + int chan = CR_CHAN(src) & (~NI_GPCT_DISABLED_GATE_SELECT); + int ret; + + switch (gate) { + case 0: + /* 1. start by disabling gate */ + ni_tio_set_gate_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); + /* 2. set the requested gate source */ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + ret = ni_m_set_gate(counter, chan); + break; + case ni_gpct_variant_660x: + ret = ni_660x_set_gate(counter, chan); + break; + default: + return -EINVAL; + } + if (ret) + return ret; + /* 3. reenable & set mode to starts things back up */ + ni_tio_set_gate_mode(counter, src); + break; + case 1: + if (!ni_tio_has_gate2_registers(counter_dev)) + return -EINVAL; + + /* 1. start by disabling gate */ + ni_tio_set_gate2_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); + /* 2. set the requested gate source */ + switch (counter_dev->variant) { + case ni_gpct_variant_m_series: + ret = ni_m_set_gate2(counter, chan); + break; + case ni_gpct_variant_660x: + ret = ni_660x_set_gate2(counter, chan); + break; + default: + return -EINVAL; + } + if (ret) + return ret; + /* 3. reenable & set mode to starts things back up */ + ni_tio_set_gate2_mode(counter, src); + break; + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_set_gate_src); + +static int ni_tio_set_other_src(struct ni_gpct *counter, unsigned int index, + unsigned int source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int chip = counter->chip_index; + unsigned int abz_reg, shift, mask; + + if (counter_dev->variant != ni_gpct_variant_m_series) + return -EINVAL; + + abz_reg = NITIO_ABZ_REG(cidx); + + /* allow for new device-global names */ + if (index == NI_GPCT_SOURCE_ENCODER_A || + (index >= NI_CtrA(0) && index <= NI_CtrA(-1))) { + shift = 10; + } else if (index == NI_GPCT_SOURCE_ENCODER_B || + (index >= NI_CtrB(0) && index <= NI_CtrB(-1))) { + shift = 5; + } else if (index == NI_GPCT_SOURCE_ENCODER_Z || + (index >= NI_CtrZ(0) && index <= NI_CtrZ(-1))) { + shift = 0; + } else { + return -EINVAL; + } + + mask = 0x1f << shift; + if (source > 0x1f) + source = 0x1f; /* Disable gate */ + + counter_dev->regs[chip][abz_reg] &= ~mask; + counter_dev->regs[chip][abz_reg] |= (source << shift) & mask; + ni_tio_write(counter, counter_dev->regs[chip][abz_reg], abz_reg); + return 0; +} + +static int ni_tio_get_other_src(struct ni_gpct *counter, unsigned int index, + unsigned int *source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int abz_reg, shift, mask; + + if (counter_dev->variant != ni_gpct_variant_m_series) + /* A,B,Z only valid for m-series */ + return -EINVAL; + + abz_reg = NITIO_ABZ_REG(cidx); + + /* allow for new device-global names */ + if (index == NI_GPCT_SOURCE_ENCODER_A || + (index >= NI_CtrA(0) && index <= NI_CtrA(-1))) { + shift = 10; + } else if (index == NI_GPCT_SOURCE_ENCODER_B || + (index >= NI_CtrB(0) && index <= NI_CtrB(-1))) { + shift = 5; + } else if (index == NI_GPCT_SOURCE_ENCODER_Z || + (index >= NI_CtrZ(0) && index <= NI_CtrZ(-1))) { + shift = 0; + } else { + return -EINVAL; + } + + mask = 0x1f; + + *source = (ni_tio_get_soft_copy(counter, abz_reg) >> shift) & mask; + return 0; +} + +static int ni_660x_gate_to_generic_gate(unsigned int gate, unsigned int *src) +{ + unsigned int source; + unsigned int i; + + switch (gate) { + case NI_660X_SRC_PIN_I_GATE_SEL: + source = NI_GPCT_SOURCE_PIN_i_GATE_SELECT; + break; + case NI_660X_GATE_PIN_I_GATE_SEL: + source = NI_GPCT_GATE_PIN_i_GATE_SELECT; + break; + case NI_660X_NEXT_SRC_GATE_SEL: + source = NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_660X_NEXT_OUT_GATE_SEL: + source = NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_660X_LOGIC_LOW_GATE_SEL: + source = NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { + if (gate == NI_660X_RTSI_GATE_SEL(i)) { + source = NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= NI_660X_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_660X_MAX_GATE_PIN; ++i) { + if (gate == NI_660X_PIN_GATE_SEL(i)) { + source = NI_GPCT_GATE_PIN_GATE_SELECT(i); + break; + } + } + if (i <= NI_660X_MAX_GATE_PIN) + break; + return -EINVAL; + } + *src = source; + return 0; +} + +static int ni_m_gate_to_generic_gate(unsigned int gate, unsigned int *src) +{ + unsigned int source; + unsigned int i; + + switch (gate) { + case NI_M_TIMESTAMP_MUX_GATE_SEL: + source = NI_GPCT_TIMESTAMP_MUX_GATE_SELECT; + break; + case NI_M_AI_START2_GATE_SEL: + source = NI_GPCT_AI_START2_GATE_SELECT; + break; + case NI_M_PXI_STAR_TRIGGER_GATE_SEL: + source = NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT; + break; + case NI_M_NEXT_OUT_GATE_SEL: + source = NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_M_AI_START1_GATE_SEL: + source = NI_GPCT_AI_START1_GATE_SELECT; + break; + case NI_M_NEXT_SRC_GATE_SEL: + source = NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_M_ANALOG_TRIG_OUT_GATE_SEL: + source = NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT; + break; + case NI_M_LOGIC_LOW_GATE_SEL: + source = NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) { + if (gate == NI_M_RTSI_GATE_SEL(i)) { + source = NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= NI_M_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) { + if (gate == NI_M_PFI_GATE_SEL(i)) { + source = NI_GPCT_PFI_GATE_SELECT(i); + break; + } + } + if (i <= NI_M_MAX_PFI_CHAN) + break; + return -EINVAL; + } + *src = source; + return 0; +} + +static int ni_660x_gate2_to_generic_gate(unsigned int gate, unsigned int *src) +{ + unsigned int source; + unsigned int i; + + switch (gate) { + case NI_660X_SRC_PIN_I_GATE2_SEL: + source = NI_GPCT_SOURCE_PIN_i_GATE_SELECT; + break; + case NI_660X_UD_PIN_I_GATE2_SEL: + source = NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT; + break; + case NI_660X_NEXT_SRC_GATE2_SEL: + source = NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_660X_NEXT_OUT_GATE2_SEL: + source = NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_660X_SELECTED_GATE2_SEL: + source = NI_GPCT_SELECTED_GATE_GATE_SELECT; + break; + case NI_660X_LOGIC_LOW_GATE2_SEL: + source = NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { + if (gate == NI_660X_RTSI_GATE2_SEL(i)) { + source = NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= NI_660X_MAX_RTSI_CHAN) + break; + for (i = 0; i <= NI_660X_MAX_UP_DOWN_PIN; ++i) { + if (gate == NI_660X_UD_PIN_GATE2_SEL(i)) { + source = NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i); + break; + } + } + if (i <= NI_660X_MAX_UP_DOWN_PIN) + break; + return -EINVAL; + } + *src = source; + return 0; +} + +static int ni_m_gate2_to_generic_gate(unsigned int gate, unsigned int *src) +{ + /* + * FIXME: the second gate sources for the m series are undocumented, + * so we just return the raw bits for now. + */ + *src = gate; + return 0; +} + +static inline unsigned int ni_tio_get_gate_mode(struct ni_gpct *counter) +{ + unsigned int mode = ni_tio_get_soft_copy(counter, + NITIO_MODE_REG(counter->counter_index)); + unsigned int ret = 0; + + if ((mode & GI_GATING_MODE_MASK) == GI_GATING_DISABLED) + ret |= NI_GPCT_DISABLED_GATE_SELECT; + if (mode & GI_GATE_POL_INVERT) + ret |= CR_INVERT; + if ((mode & GI_GATING_MODE_MASK) != GI_LEVEL_GATING) + ret |= CR_EDGE; + + return ret; +} + +static inline unsigned int ni_tio_get_gate2_mode(struct ni_gpct *counter) +{ + unsigned int mode = ni_tio_get_soft_copy(counter, + NITIO_GATE2_REG(counter->counter_index)); + unsigned int ret = 0; + + if (!(mode & GI_GATE2_MODE)) + ret |= NI_GPCT_DISABLED_GATE_SELECT; + if (mode & GI_GATE2_POL_INVERT) + ret |= CR_INVERT; + + return ret; +} + +static inline unsigned int ni_tio_get_gate_val(struct ni_gpct *counter) +{ + return GI_BITS_TO_GATE(ni_tio_get_soft_copy(counter, + NITIO_INPUT_SEL_REG(counter->counter_index))); +} + +static inline unsigned int ni_tio_get_gate2_val(struct ni_gpct *counter) +{ + return GI_BITS_TO_GATE2(ni_tio_get_soft_copy(counter, + NITIO_GATE2_REG(counter->counter_index))); +} + +static int ni_tio_get_gate_src(struct ni_gpct *counter, unsigned int gate_index, + unsigned int *gate_source) +{ + unsigned int gate; + int ret; + + switch (gate_index) { + case 0: + gate = ni_tio_get_gate_val(counter); + switch (counter->counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + default: + ret = ni_m_gate_to_generic_gate(gate, gate_source); + break; + case ni_gpct_variant_660x: + ret = ni_660x_gate_to_generic_gate(gate, gate_source); + break; + } + if (ret) + return ret; + *gate_source |= ni_tio_get_gate_mode(counter); + break; + case 1: + gate = ni_tio_get_gate2_val(counter); + switch (counter->counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + default: + ret = ni_m_gate2_to_generic_gate(gate, gate_source); + break; + case ni_gpct_variant_660x: + ret = ni_660x_gate2_to_generic_gate(gate, gate_source); + break; + } + if (ret) + return ret; + *gate_source |= ni_tio_get_gate2_mode(counter); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ni_tio_get_gate_src_raw(struct ni_gpct *counter, + unsigned int gate_index, + unsigned int *gate_source) +{ + switch (gate_index) { + case 0: + *gate_source = ni_tio_get_gate_mode(counter) + | ni_tio_get_gate_val(counter); + break; + case 1: + *gate_source = ni_tio_get_gate2_mode(counter) + | ni_tio_get_gate2_val(counter); + break; + default: + return -EINVAL; + } + return 0; +} + +int ni_tio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_gpct *counter = s->private; + unsigned int cidx = counter->counter_index; + unsigned int status; + int ret = 0; + + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + ret = ni_tio_set_counter_mode(counter, data[1]); + break; + case INSN_CONFIG_ARM: + ret = ni_tio_arm(counter, true, data[1]); + break; + case INSN_CONFIG_DISARM: + ret = ni_tio_arm(counter, false, 0); + break; + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = 0; + status = ni_tio_read(counter, NITIO_SHARED_STATUS_REG(cidx)); + if (status & GI_ARMED(cidx)) { + data[1] |= COMEDI_COUNTER_ARMED; + if (status & GI_COUNTING(cidx)) + data[1] |= COMEDI_COUNTER_COUNTING; + } + data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + ret = ni_tio_set_clock_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ret = ni_tio_get_clock_src(counter, &data[1], &data[2]); + break; + case INSN_CONFIG_SET_GATE_SRC: + ret = ni_tio_set_gate_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_GET_GATE_SRC: + ret = ni_tio_get_gate_src(counter, data[1], &data[2]); + break; + case INSN_CONFIG_SET_OTHER_SRC: + ret = ni_tio_set_other_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_RESET: + ni_tio_reset_count_and_disarm(counter); + break; + default: + return -EINVAL; + } + return ret ? ret : insn->n; +} +EXPORT_SYMBOL_GPL(ni_tio_insn_config); + +/* + * Retrieves the register value of the current source of the output selector for + * the given destination. + * + * If the terminal for the destination is not already configured as an output, + * this function returns -EINVAL as error. + * + * Return: the register value of the destination output selector; + * -EINVAL if terminal is not configured for output. + */ +int ni_tio_get_routing(struct ni_gpct_device *counter_dev, unsigned int dest) +{ + /* we need to know the actual counter below... */ + int ctr_index = (dest - NI_COUNTER_NAMES_BASE) % NI_MAX_COUNTERS; + struct ni_gpct *counter = &counter_dev->counters[ctr_index]; + int ret = 1; + unsigned int reg; + + if (dest >= NI_CtrA(0) && dest <= NI_CtrZ(-1)) { + ret = ni_tio_get_other_src(counter, dest, ®); + } else if (dest >= NI_CtrGate(0) && dest <= NI_CtrGate(-1)) { + ret = ni_tio_get_gate_src_raw(counter, 0, ®); + } else if (dest >= NI_CtrAux(0) && dest <= NI_CtrAux(-1)) { + ret = ni_tio_get_gate_src_raw(counter, 1, ®); + /* + * This case is not possible through this interface. A user must use + * INSN_CONFIG_SET_CLOCK_SRC instead. + * } else if (dest >= NI_CtrSource(0) && dest <= NI_CtrSource(-1)) { + * ret = ni_tio_set_clock_src(counter, ®, &period_ns); + */ + } + + if (ret) + return -EINVAL; + + return reg; +} +EXPORT_SYMBOL_GPL(ni_tio_get_routing); + +/** + * ni_tio_set_routing() - Sets the register value of the selector MUX for the given destination. + * @counter_dev: Pointer to general counter device. + * @dest: Device-global identifier of route destination. + * @reg: + * The first several bits of this value should store the desired + * value to write to the register. All other bits are for + * transmitting information that modify the mode of the particular + * destination/gate. These mode bits might include a bitwise or of + * CR_INVERT and CR_EDGE. Note that the calling function should + * have already validated the correctness of this value. + */ +int ni_tio_set_routing(struct ni_gpct_device *counter_dev, unsigned int dest, + unsigned int reg) +{ + /* we need to know the actual counter below... */ + int ctr_index = (dest - NI_COUNTER_NAMES_BASE) % NI_MAX_COUNTERS; + struct ni_gpct *counter = &counter_dev->counters[ctr_index]; + int ret; + + if (dest >= NI_CtrA(0) && dest <= NI_CtrZ(-1)) { + ret = ni_tio_set_other_src(counter, dest, reg); + } else if (dest >= NI_CtrGate(0) && dest <= NI_CtrGate(-1)) { + ret = ni_tio_set_gate_src_raw(counter, 0, reg); + } else if (dest >= NI_CtrAux(0) && dest <= NI_CtrAux(-1)) { + ret = ni_tio_set_gate_src_raw(counter, 1, reg); + /* + * This case is not possible through this interface. A user must use + * INSN_CONFIG_SET_CLOCK_SRC instead. + * } else if (dest >= NI_CtrSource(0) && dest <= NI_CtrSource(-1)) { + * ret = ni_tio_set_clock_src(counter, reg, period_ns); + */ + } else { + return -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(ni_tio_set_routing); + +/* + * Sets the given destination MUX to its default value or disable it. + * + * Return: 0 if successful; -EINVAL if terminal is unknown. + */ +int ni_tio_unset_routing(struct ni_gpct_device *counter_dev, unsigned int dest) +{ + if (dest >= NI_GATES_NAMES_BASE && dest <= NI_GATES_NAMES_MAX) + /* Disable gate (via mode bits) and set to default 0-value */ + return ni_tio_set_routing(counter_dev, dest, + NI_GPCT_DISABLED_GATE_SELECT); + /* + * This case is not possible through this interface. A user must use + * INSN_CONFIG_SET_CLOCK_SRC instead. + * if (dest >= NI_CtrSource(0) && dest <= NI_CtrSource(-1)) + * return ni_tio_set_clock_src(counter, reg, period_ns); + */ + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ni_tio_unset_routing); + +static unsigned int ni_tio_read_sw_save_reg(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + unsigned int cidx = counter->counter_index; + unsigned int val; + + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_SAVE_TRACE, 0); + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), + GI_SAVE_TRACE, GI_SAVE_TRACE); + + /* + * The count doesn't get latched until the next clock edge, so it is + * possible the count may change (once) while we are reading. Since + * the read of the SW_Save_Reg isn't atomic (apparently even when it's + * a 32 bit register according to 660x docs), we need to read twice + * and make sure the reading hasn't changed. If it has, a third read + * will be correct since the count value will definitely have latched + * by then. + */ + val = ni_tio_read(counter, NITIO_SW_SAVE_REG(cidx)); + if (val != ni_tio_read(counter, NITIO_SW_SAVE_REG(cidx))) + val = ni_tio_read(counter, NITIO_SW_SAVE_REG(cidx)); + + return val; +} + +int ni_tio_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_gpct *counter = s->private; + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int channel = CR_CHAN(insn->chanspec); + unsigned int cidx = counter->counter_index; + unsigned int chip = counter->chip_index; + int i; + + for (i = 0; i < insn->n; i++) { + switch (channel) { + case 0: + data[i] = ni_tio_read_sw_save_reg(dev, s); + break; + case 1: + data[i] = + counter_dev->regs[chip][NITIO_LOADA_REG(cidx)]; + break; + case 2: + data[i] = + counter_dev->regs[chip][NITIO_LOADB_REG(cidx)]; + break; + } + } + return insn->n; +} +EXPORT_SYMBOL_GPL(ni_tio_insn_read); + +static unsigned int ni_tio_next_load_register(struct ni_gpct *counter) +{ + unsigned int cidx = counter->counter_index; + unsigned int bits = ni_tio_read(counter, NITIO_SHARED_STATUS_REG(cidx)); + + return (bits & GI_NEXT_LOAD_SRC(cidx)) + ? NITIO_LOADB_REG(cidx) + : NITIO_LOADA_REG(cidx); +} + +int ni_tio_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_gpct *counter = s->private; + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int channel = CR_CHAN(insn->chanspec); + unsigned int cidx = counter->counter_index; + unsigned int chip = counter->chip_index; + unsigned int load_reg; + unsigned int load_val; + + if (insn->n < 1) + return 0; + load_val = data[insn->n - 1]; + 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); + ni_tio_write(counter, load_val, load_reg); + ni_tio_set_bits_transient(counter, NITIO_CMD_REG(cidx), + 0, 0, GI_LOAD); + /* restore load reg */ + ni_tio_write(counter, counter_dev->regs[chip][load_reg], + load_reg); + break; + case 1: + counter_dev->regs[chip][NITIO_LOADA_REG(cidx)] = load_val; + ni_tio_write(counter, load_val, NITIO_LOADA_REG(cidx)); + break; + case 2: + counter_dev->regs[chip][NITIO_LOADB_REG(cidx)] = load_val; + ni_tio_write(counter, load_val, NITIO_LOADB_REG(cidx)); + break; + default: + return -EINVAL; + } + return insn->n; +} +EXPORT_SYMBOL_GPL(ni_tio_insn_write); + +void ni_tio_init_counter(struct ni_gpct *counter) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int chip = counter->chip_index; + + ni_tio_reset_count_and_disarm(counter); + + /* initialize counter registers */ + counter_dev->regs[chip][NITIO_AUTO_INC_REG(cidx)] = 0x0; + ni_tio_write(counter, 0x0, NITIO_AUTO_INC_REG(cidx)); + + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), + ~0, GI_SYNC_GATE); + + ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), ~0, 0); + + counter_dev->regs[chip][NITIO_LOADA_REG(cidx)] = 0x0; + ni_tio_write(counter, 0x0, NITIO_LOADA_REG(cidx)); + + counter_dev->regs[chip][NITIO_LOADB_REG(cidx)] = 0x0; + ni_tio_write(counter, 0x0, NITIO_LOADB_REG(cidx)); + + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), ~0, 0); + + if (ni_tio_counting_mode_registers_present(counter_dev)) + ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), ~0, 0); + + if (ni_tio_has_gate2_registers(counter_dev)) { + counter_dev->regs[chip][NITIO_GATE2_REG(cidx)] = 0x0; + ni_tio_write(counter, 0x0, NITIO_GATE2_REG(cidx)); + } + + ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), ~0, 0x0); + + ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), ~0, 0x0); +} +EXPORT_SYMBOL_GPL(ni_tio_init_counter); + +struct ni_gpct_device * +ni_gpct_device_construct(struct comedi_device *dev, + void (*write)(struct ni_gpct *counter, + unsigned int value, + enum ni_gpct_register reg), + unsigned int (*read)(struct ni_gpct *counter, + enum ni_gpct_register reg), + enum ni_gpct_variant variant, + unsigned int num_counters, + unsigned int counters_per_chip, + const struct ni_route_tables *routing_tables) +{ + struct ni_gpct_device *counter_dev; + struct ni_gpct *counter; + unsigned int i; + + if (num_counters == 0 || counters_per_chip == 0) + return NULL; + + counter_dev = kzalloc(sizeof(*counter_dev), GFP_KERNEL); + if (!counter_dev) + return NULL; + + counter_dev->dev = dev; + counter_dev->write = write; + counter_dev->read = read; + counter_dev->variant = variant; + counter_dev->routing_tables = routing_tables; + + spin_lock_init(&counter_dev->regs_lock); + + counter_dev->num_counters = num_counters; + counter_dev->num_chips = DIV_ROUND_UP(num_counters, counters_per_chip); + + counter_dev->counters = kcalloc(num_counters, sizeof(*counter), + GFP_KERNEL); + counter_dev->regs = kcalloc(counter_dev->num_chips, + sizeof(*counter_dev->regs), GFP_KERNEL); + if (!counter_dev->regs || !counter_dev->counters) { + kfree(counter_dev->regs); + kfree(counter_dev->counters); + kfree(counter_dev); + return NULL; + } + + for (i = 0; i < num_counters; ++i) { + counter = &counter_dev->counters[i]; + counter->counter_dev = counter_dev; + counter->chip_index = i / counters_per_chip; + counter->counter_index = i % counters_per_chip; + spin_lock_init(&counter->lock); + } + + return counter_dev; +} +EXPORT_SYMBOL_GPL(ni_gpct_device_construct); + +void ni_gpct_device_destroy(struct ni_gpct_device *counter_dev) +{ + if (!counter_dev) + return; + kfree(counter_dev->regs); + kfree(counter_dev->counters); + kfree(counter_dev); +} +EXPORT_SYMBOL_GPL(ni_gpct_device_destroy); + +static int __init ni_tio_init_module(void) +{ + return 0; +} +module_init(ni_tio_init_module); + +static void __exit ni_tio_cleanup_module(void) +{ +} +module_exit(ni_tio_cleanup_module); + +MODULE_AUTHOR("Comedi <comedi@comedi.org>"); +MODULE_DESCRIPTION("Comedi support for NI general-purpose counters"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/ni_tio.h b/drivers/comedi/drivers/ni_tio.h new file mode 100644 index 000000000..9ae2221c3 --- /dev/null +++ b/drivers/comedi/drivers/ni_tio.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Header file for NI general purpose counter support code (ni_tio.c) + * + * COMEDI - Linux Control and Measurement Device Interface + */ + +#ifndef _COMEDI_NI_TIO_H +#define _COMEDI_NI_TIO_H + +#include <linux/comedi/comedidev.h> + +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 int counter_index; + unsigned int chip_index; + u64 clock_period_ps; /* clock period in picoseconds */ + struct mite_channel *mite_chan; + spinlock_t lock; /* protects 'mite_chan' */ +}; + +struct ni_gpct_device { + struct comedi_device *dev; + void (*write)(struct ni_gpct *counter, unsigned int value, + enum ni_gpct_register); + unsigned int (*read)(struct ni_gpct *counter, enum ni_gpct_register); + enum ni_gpct_variant variant; + struct ni_gpct *counters; + unsigned int num_counters; + unsigned int num_chips; + unsigned int (*regs)[NITIO_NUM_REGS]; /* [num_chips][NITIO_NUM_REGS] */ + spinlock_t regs_lock; /* protects 'regs' */ + const struct ni_route_tables *routing_tables; /* link to routes */ +}; + +struct ni_gpct_device * +ni_gpct_device_construct(struct comedi_device *dev, + void (*write)(struct ni_gpct *counter, + unsigned int value, + enum ni_gpct_register), + unsigned int (*read)(struct ni_gpct *counter, + enum ni_gpct_register), + enum ni_gpct_variant, + unsigned int num_counters, + unsigned int counters_per_chip, + const struct ni_route_tables *routing_tables); +void ni_gpct_device_destroy(struct ni_gpct_device *counter_dev); +void ni_tio_init_counter(struct ni_gpct *counter); +int ni_tio_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int ni_tio_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int ni_tio_insn_write(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int ni_tio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +int ni_tio_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +int ni_tio_cancel(struct ni_gpct *counter); +void ni_tio_handle_interrupt(struct ni_gpct *counter, + struct comedi_subdevice *s); +void ni_tio_set_mite_channel(struct ni_gpct *counter, + struct mite_channel *mite_chan); +void ni_tio_acknowledge(struct ni_gpct *counter); + +/* + * Retrieves the register value of the current source of the output selector for + * the given destination. + * + * If the terminal for the destination is not already configured as an output, + * this function returns -EINVAL as error. + * + * Return: the register value of the destination output selector; + * -EINVAL if terminal is not configured for output. + */ +int ni_tio_get_routing(struct ni_gpct_device *counter_dev, + unsigned int destination); + +/* + * Sets the register value of the selector MUX for the given destination. + * @counter_dev:Pointer to general counter device. + * @destination:Device-global identifier of route destination. + * @register_value: + * The first several bits of this value should store the desired + * value to write to the register. All other bits are for + * transmitting information that modify the mode of the particular + * destination/gate. These mode bits might include a bitwise or of + * CR_INVERT and CR_EDGE. Note that the calling function should + * have already validated the correctness of this value. + */ +int ni_tio_set_routing(struct ni_gpct_device *counter_dev, + unsigned int destination, unsigned int register_value); + +/* + * Sets the given destination MUX to its default value or disable it. + * + * Return: 0 if successful; -EINVAL if terminal is unknown. + */ +int ni_tio_unset_routing(struct ni_gpct_device *counter_dev, + unsigned int destination); + +#endif /* _COMEDI_NI_TIO_H */ diff --git a/drivers/comedi/drivers/ni_tio_internal.h b/drivers/comedi/drivers/ni_tio_internal.h new file mode 100644 index 000000000..20fcd6003 --- /dev/null +++ b/drivers/comedi/drivers/ni_tio_internal.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Header file for NI general purpose counter support code (ni_tio.c and + * ni_tiocmd.c) + * + * COMEDI - Linux Control and Measurement Device Interface + */ + +#ifndef _COMEDI_NI_TIO_INTERNAL_H +#define _COMEDI_NI_TIO_INTERNAL_H + +#include "ni_tio.h" + +#define NITIO_AUTO_INC_REG(x) (NITIO_G0_AUTO_INC + (x)) +#define GI_AUTO_INC_MASK 0xff +#define NITIO_CMD_REG(x) (NITIO_G0_CMD + (x)) +#define GI_ARM BIT(0) +#define GI_SAVE_TRACE BIT(1) +#define GI_LOAD BIT(2) +#define GI_DISARM BIT(4) +#define GI_CNT_DIR(x) (((x) & 0x3) << 5) +#define GI_CNT_DIR_MASK GI_CNT_DIR(3) +#define GI_WRITE_SWITCH BIT(7) +#define GI_SYNC_GATE BIT(8) +#define GI_LITTLE_BIG_ENDIAN BIT(9) +#define GI_BANK_SWITCH_START BIT(10) +#define GI_BANK_SWITCH_MODE BIT(11) +#define GI_BANK_SWITCH_ENABLE BIT(12) +#define GI_ARM_COPY BIT(13) +#define GI_SAVE_TRACE_COPY BIT(14) +#define GI_DISARM_COPY BIT(15) +#define NITIO_HW_SAVE_REG(x) (NITIO_G0_HW_SAVE + (x)) +#define NITIO_SW_SAVE_REG(x) (NITIO_G0_SW_SAVE + (x)) +#define NITIO_MODE_REG(x) (NITIO_G0_MODE + (x)) +#define GI_GATING_MODE(x) (((x) & 0x3) << 0) +#define GI_GATING_DISABLED GI_GATING_MODE(0) +#define GI_LEVEL_GATING GI_GATING_MODE(1) +#define GI_RISING_EDGE_GATING GI_GATING_MODE(2) +#define GI_FALLING_EDGE_GATING GI_GATING_MODE(3) +#define GI_GATING_MODE_MASK GI_GATING_MODE(3) +#define GI_GATE_ON_BOTH_EDGES BIT(2) +#define GI_EDGE_GATE_MODE(x) (((x) & 0x3) << 3) +#define GI_EDGE_GATE_STARTS_STOPS GI_EDGE_GATE_MODE(0) +#define GI_EDGE_GATE_STOPS_STARTS GI_EDGE_GATE_MODE(1) +#define GI_EDGE_GATE_STARTS GI_EDGE_GATE_MODE(2) +#define GI_EDGE_GATE_NO_STARTS_OR_STOPS GI_EDGE_GATE_MODE(3) +#define GI_EDGE_GATE_MODE_MASK GI_EDGE_GATE_MODE(3) +#define GI_STOP_MODE(x) (((x) & 0x3) << 5) +#define GI_STOP_ON_GATE GI_STOP_MODE(0) +#define GI_STOP_ON_GATE_OR_TC GI_STOP_MODE(1) +#define GI_STOP_ON_GATE_OR_SECOND_TC GI_STOP_MODE(2) +#define GI_STOP_MODE_MASK GI_STOP_MODE(3) +#define GI_LOAD_SRC_SEL BIT(7) +#define GI_OUTPUT_MODE(x) (((x) & 0x3) << 8) +#define GI_OUTPUT_TC_PULSE GI_OUTPUT_MODE(1) +#define GI_OUTPUT_TC_TOGGLE GI_OUTPUT_MODE(2) +#define GI_OUTPUT_TC_OR_GATE_TOGGLE GI_OUTPUT_MODE(3) +#define GI_OUTPUT_MODE_MASK GI_OUTPUT_MODE(3) +#define GI_COUNTING_ONCE(x) (((x) & 0x3) << 10) +#define GI_NO_HARDWARE_DISARM GI_COUNTING_ONCE(0) +#define GI_DISARM_AT_TC GI_COUNTING_ONCE(1) +#define GI_DISARM_AT_GATE GI_COUNTING_ONCE(2) +#define GI_DISARM_AT_TC_OR_GATE GI_COUNTING_ONCE(3) +#define GI_COUNTING_ONCE_MASK GI_COUNTING_ONCE(3) +#define GI_LOADING_ON_TC BIT(12) +#define GI_GATE_POL_INVERT BIT(13) +#define GI_LOADING_ON_GATE BIT(14) +#define GI_RELOAD_SRC_SWITCHING BIT(15) +#define NITIO_LOADA_REG(x) (NITIO_G0_LOADA + (x)) +#define NITIO_LOADB_REG(x) (NITIO_G0_LOADB + (x)) +#define NITIO_INPUT_SEL_REG(x) (NITIO_G0_INPUT_SEL + (x)) +#define GI_READ_ACKS_IRQ BIT(0) +#define GI_WRITE_ACKS_IRQ BIT(1) +#define GI_BITS_TO_SRC(x) (((x) >> 2) & 0x1f) +#define GI_SRC_SEL(x) (((x) & 0x1f) << 2) +#define GI_SRC_SEL_MASK GI_SRC_SEL(0x1f) +#define GI_BITS_TO_GATE(x) (((x) >> 7) & 0x1f) +#define GI_GATE_SEL(x) (((x) & 0x1f) << 7) +#define GI_GATE_SEL_MASK GI_GATE_SEL(0x1f) +#define GI_GATE_SEL_LOAD_SRC BIT(12) +#define GI_OR_GATE BIT(13) +#define GI_OUTPUT_POL_INVERT BIT(14) +#define GI_SRC_POL_INVERT BIT(15) +#define NITIO_CNT_MODE_REG(x) (NITIO_G0_CNT_MODE + (x)) +#define GI_CNT_MODE(x) (((x) & 0x7) << 0) +#define GI_CNT_MODE_NORMAL GI_CNT_MODE(0) +#define GI_CNT_MODE_QUADX1 GI_CNT_MODE(1) +#define GI_CNT_MODE_QUADX2 GI_CNT_MODE(2) +#define GI_CNT_MODE_QUADX4 GI_CNT_MODE(3) +#define GI_CNT_MODE_TWO_PULSE GI_CNT_MODE(4) +#define GI_CNT_MODE_SYNC_SRC GI_CNT_MODE(6) +#define GI_CNT_MODE_MASK GI_CNT_MODE(7) +#define GI_INDEX_MODE BIT(4) +#define GI_INDEX_PHASE(x) (((x) & 0x3) << 5) +#define GI_INDEX_PHASE_MASK GI_INDEX_PHASE(3) +#define GI_HW_ARM_ENA BIT(7) +#define GI_HW_ARM_SEL(x) ((x) << 8) +#define GI_660X_HW_ARM_SEL_MASK GI_HW_ARM_SEL(0x7) +#define GI_M_HW_ARM_SEL_MASK GI_HW_ARM_SEL(0x1f) +#define GI_660X_PRESCALE_X8 BIT(12) +#define GI_M_PRESCALE_X8 BIT(13) +#define GI_660X_ALT_SYNC BIT(13) +#define GI_M_ALT_SYNC BIT(14) +#define GI_660X_PRESCALE_X2 BIT(14) +#define GI_M_PRESCALE_X2 BIT(15) +#define NITIO_GATE2_REG(x) (NITIO_G0_GATE2 + (x)) +#define GI_GATE2_MODE BIT(0) +#define GI_BITS_TO_GATE2(x) (((x) >> 7) & 0x1f) +#define GI_GATE2_SEL(x) (((x) & 0x1f) << 7) +#define GI_GATE2_SEL_MASK GI_GATE2_SEL(0x1f) +#define GI_GATE2_POL_INVERT BIT(13) +#define GI_GATE2_SUBSEL BIT(14) +#define GI_SRC_SUBSEL BIT(15) +#define NITIO_SHARED_STATUS_REG(x) (NITIO_G01_STATUS + ((x) / 2)) +#define GI_SAVE(x) (((x) % 2) ? BIT(1) : BIT(0)) +#define GI_COUNTING(x) (((x) % 2) ? BIT(3) : BIT(2)) +#define GI_NEXT_LOAD_SRC(x) (((x) % 2) ? BIT(5) : BIT(4)) +#define GI_STALE_DATA(x) (((x) % 2) ? BIT(7) : BIT(6)) +#define GI_ARMED(x) (((x) % 2) ? BIT(9) : BIT(8)) +#define GI_NO_LOAD_BETWEEN_GATES(x) (((x) % 2) ? BIT(11) : BIT(10)) +#define GI_TC_ERROR(x) (((x) % 2) ? BIT(13) : BIT(12)) +#define GI_GATE_ERROR(x) (((x) % 2) ? BIT(15) : BIT(14)) +#define NITIO_RESET_REG(x) (NITIO_G01_RESET + ((x) / 2)) +#define GI_RESET(x) BIT(2 + ((x) % 2)) +#define NITIO_STATUS1_REG(x) (NITIO_G01_STATUS1 + ((x) / 2)) +#define NITIO_STATUS2_REG(x) (NITIO_G01_STATUS2 + ((x) / 2)) +#define GI_OUTPUT(x) (((x) % 2) ? BIT(1) : BIT(0)) +#define GI_HW_SAVE(x) (((x) % 2) ? BIT(13) : BIT(12)) +#define GI_PERMANENT_STALE(x) (((x) % 2) ? BIT(15) : BIT(14)) +#define NITIO_DMA_CFG_REG(x) (NITIO_G0_DMA_CFG + (x)) +#define GI_DMA_ENABLE BIT(0) +#define GI_DMA_WRITE BIT(1) +#define GI_DMA_INT_ENA BIT(2) +#define GI_DMA_RESET BIT(3) +#define GI_DMA_BANKSW_ERROR BIT(4) +#define NITIO_DMA_STATUS_REG(x) (NITIO_G0_DMA_STATUS + (x)) +#define GI_DMA_READBANK BIT(13) +#define GI_DRQ_ERROR BIT(14) +#define GI_DRQ_STATUS BIT(15) +#define NITIO_ABZ_REG(x) (NITIO_G0_ABZ + (x)) +#define NITIO_INT_ACK_REG(x) (NITIO_G0_INT_ACK + (x)) +#define GI_GATE_ERROR_CONFIRM(x) (((x) % 2) ? BIT(1) : BIT(5)) +#define GI_TC_ERROR_CONFIRM(x) (((x) % 2) ? BIT(2) : BIT(6)) +#define GI_TC_INTERRUPT_ACK BIT(14) +#define GI_GATE_INTERRUPT_ACK BIT(15) +#define NITIO_STATUS_REG(x) (NITIO_G0_STATUS + (x)) +#define GI_GATE_INTERRUPT BIT(2) +#define GI_TC BIT(3) +#define GI_INTERRUPT BIT(15) +#define NITIO_INT_ENA_REG(x) (NITIO_G0_INT_ENA + (x)) +#define GI_TC_INTERRUPT_ENABLE(x) (((x) % 2) ? BIT(9) : BIT(6)) +#define GI_GATE_INTERRUPT_ENABLE(x) (((x) % 2) ? BIT(10) : BIT(8)) + +void ni_tio_write(struct ni_gpct *counter, unsigned int value, + enum ni_gpct_register); +unsigned int ni_tio_read(struct ni_gpct *counter, enum ni_gpct_register); + +static inline bool +ni_tio_counting_mode_registers_present(const struct ni_gpct_device *counter_dev) +{ + /* m series and 660x variants have counting mode registers */ + return counter_dev->variant != ni_gpct_variant_e_series; +} + +void ni_tio_set_bits(struct ni_gpct *counter, enum ni_gpct_register reg, + unsigned int mask, unsigned int value); +unsigned int ni_tio_get_soft_copy(const struct ni_gpct *counter, + enum ni_gpct_register reg); + +int ni_tio_arm(struct ni_gpct *counter, bool arm, unsigned int start_trigger); +int ni_tio_set_gate_src(struct ni_gpct *counter, unsigned int gate, + unsigned int src); +int ni_tio_set_gate_src_raw(struct ni_gpct *counter, unsigned int gate, + unsigned int src); + +#endif /* _COMEDI_NI_TIO_INTERNAL_H */ diff --git a/drivers/comedi/drivers/ni_tiocmd.c b/drivers/comedi/drivers/ni_tiocmd.c new file mode 100644 index 000000000..ab6d9e826 --- /dev/null +++ b/drivers/comedi/drivers/ni_tiocmd.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Command support for NI general purpose counters + * + * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net> + */ + +/* + * Module: ni_tiocmd + * Description: National Instruments general purpose counters command support + * Author: J.P. Mellor <jpmellor@rose-hulman.edu>, + * Herman.Bruyninckx@mech.kuleuven.ac.be, + * Wim.Meeussen@mech.kuleuven.ac.be, + * Klaas.Gadeyne@mech.kuleuven.ac.be, + * Frank Mori Hess <fmhess@users.sourceforge.net> + * Updated: Fri, 11 Apr 2008 12:32:35 +0100 + * Status: works + * + * This module is not used directly by end-users. Rather, it + * is used by other drivers (for example ni_660x and ni_pcimio) + * to provide command support for NI's general purpose counters. + * It was originally split out of ni_tio.c to stop the 'ni_tio' + * module depending on the 'mite' module. + * + * References: + * DAQ 660x Register-Level Programmer Manual (NI 370505A-01) + * DAQ 6601/6602 User Manual (NI 322137B-01) + * 340934b.pdf DAQ-STC reference manual + * + * TODO: Support use of both banks X and Y + */ + +#include <linux/module.h> +#include "ni_tio_internal.h" +#include "mite.h" +#include "ni_routes.h" + +static void ni_tio_configure_dma(struct ni_gpct *counter, + bool enable, bool read) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned int cidx = counter->counter_index; + unsigned int mask; + unsigned int bits; + + mask = GI_READ_ACKS_IRQ | GI_WRITE_ACKS_IRQ; + bits = 0; + + if (enable) { + if (read) + bits |= GI_READ_ACKS_IRQ; + else + bits |= GI_WRITE_ACKS_IRQ; + } + ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), mask, bits); + + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + mask = GI_DMA_ENABLE | GI_DMA_INT_ENA | GI_DMA_WRITE; + bits = 0; + + if (enable) + bits |= GI_DMA_ENABLE | GI_DMA_INT_ENA; + if (!read) + bits |= GI_DMA_WRITE; + ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), mask, bits); + break; + } +} + +static int ni_tio_input_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct ni_gpct *counter = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + int ret = 0; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan) + mite_dma_arm(counter->mite_chan); + else + ret = -EIO; + spin_unlock_irqrestore(&counter->lock, flags); + if (ret < 0) + return ret; + ret = ni_tio_arm(counter, true, NI_GPCT_ARM_IMMEDIATE); + s->async->inttrig = NULL; + + return ret; +} + +static int ni_tio_input_cmd(struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + struct ni_gpct_device *counter_dev = counter->counter_dev; + const struct ni_route_tables *routing_tables = + counter_dev->routing_tables; + unsigned int cidx = counter->counter_index; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int ret = 0; + + /* write alloc the entire buffer */ + comedi_buf_write_alloc(s, async->prealloc_bufsz); + counter->mite_chan->dir = COMEDI_INPUT; + switch (counter_dev->variant) { + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + mite_prep_dma(counter->mite_chan, 32, 32); + break; + case ni_gpct_variant_e_series: + mite_prep_dma(counter->mite_chan, 16, 32); + break; + } + ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_SAVE_TRACE, 0); + ni_tio_configure_dma(counter, true, true); + + if (cmd->start_src == TRIG_INT) { + async->inttrig = &ni_tio_input_inttrig; + } else { /* TRIG_NOW || TRIG_EXT || TRIG_OTHER */ + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + + if (cmd->start_src == TRIG_NOW) + ret = ni_tio_arm(counter, true, NI_GPCT_ARM_IMMEDIATE); + else if (cmd->start_src == TRIG_EXT) { + int reg = CR_CHAN(cmd->start_arg); + + if (reg >= NI_NAMES_BASE) { + /* using a device-global name. lookup reg */ + reg = ni_get_reg_value(reg, + NI_CtrArmStartTrigger(cidx), + routing_tables); + /* mark this as a raw register value */ + reg |= NI_GPCT_HW_ARM; + } + ret = ni_tio_arm(counter, true, reg); + } + } + return ret; +} + +static int ni_tio_output_cmd(struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + + dev_err(counter->counter_dev->dev->class_dev, + "output commands not yet implemented.\n"); + return -ENOTSUPP; +} + +static int ni_tio_cmd_setup(struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + struct ni_gpct *counter = s->private; + unsigned int cidx = counter->counter_index; + const struct ni_route_tables *routing_tables = + counter->counter_dev->routing_tables; + int set_gate_source = 0; + unsigned int 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) { + if (CR_CHAN(gate_source) >= NI_NAMES_BASE) { + /* Lookup and use the real register values */ + int reg = ni_get_reg_value(CR_CHAN(gate_source), + NI_CtrGate(cidx), + routing_tables); + if (reg < 0) + return -EINVAL; + retval = ni_tio_set_gate_src_raw(counter, 0, reg); + } else { + /* + * This function must be used separately since it does + * not expect real register values and attempts to + * convert these to real register values. + */ + retval = ni_tio_set_gate_src(counter, 0, gate_source); + } + } + if (cmd->flags & CMDF_WAKE_EOS) { + ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), + GI_GATE_INTERRUPT_ENABLE(cidx), + GI_GATE_INTERRUPT_ENABLE(cidx)); + } + return retval; +} + +int ni_tio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&counter->lock, flags); + if (!counter->mite_chan) { + dev_err(counter->counter_dev->dev->class_dev, + "commands only supported with DMA. "); + dev_err(counter->counter_dev->dev->class_dev, + "Interrupt-driven commands not yet implemented.\n"); + retval = -EIO; + } else { + retval = ni_tio_cmd_setup(s); + if (retval == 0) { + if (cmd->flags & CMDF_WRITE) + retval = ni_tio_output_cmd(s); + else + retval = ni_tio_input_cmd(s); + } + } + spin_unlock_irqrestore(&counter->lock, flags); + return retval; +} +EXPORT_SYMBOL_GPL(ni_tio_cmd); + +int ni_tio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct ni_gpct *counter = s->private; + unsigned int cidx = counter->counter_index; + const struct ni_route_tables *routing_tables = + counter->counter_dev->routing_tables; + int err = 0; + unsigned int sources; + + /* Step 1 : check if triggers are trivially valid */ + + sources = TRIG_NOW | TRIG_INT | TRIG_OTHER; + if (ni_tio_counting_mode_registers_present(counter->counter_dev)) + sources |= TRIG_EXT; + err |= comedi_check_trigger_src(&cmd->start_src, sources); + + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_FOLLOW | TRIG_EXT | TRIG_OTHER); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_NOW | TRIG_EXT | TRIG_OTHER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + + /* Step 2b : and mutually compatible */ + + if (cmd->convert_src != TRIG_NOW && cmd->scan_begin_src != TRIG_FOLLOW) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + case TRIG_INT: + case TRIG_OTHER: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + /* start_arg is the start_trigger passed to ni_tio_arm() */ + /* + * This should be done, but we don't yet know the actual + * register values. These should be tested and then documented + * in the ni_route_values/ni_*.csv files, with indication of + * who/when/which/how these were tested. + * When at least a e/m/660x series have been tested, this code + * should be uncommented: + * + * err |= ni_check_trigger_arg(CR_CHAN(cmd->start_arg), + * NI_CtrArmStartTrigger(cidx), + * routing_tables); + */ + break; + } + + /* + * It seems that convention is to allow either scan_begin_arg or + * convert_arg to specify the Gate source, with scan_begin_arg taking + * precedence. + */ + if (cmd->scan_begin_src != TRIG_EXT) + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + else + err |= ni_check_trigger_arg(CR_CHAN(cmd->scan_begin_arg), + NI_CtrGate(cidx), routing_tables); + + if (cmd->convert_src != TRIG_EXT) + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + else + err |= ni_check_trigger_arg(CR_CHAN(cmd->convert_arg), + NI_CtrGate(cidx), routing_tables); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_cmdtest); + +int ni_tio_cancel(struct ni_gpct *counter) +{ + unsigned int cidx = counter->counter_index; + unsigned long flags; + + ni_tio_arm(counter, false, 0); + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan) + mite_dma_disarm(counter->mite_chan); + spin_unlock_irqrestore(&counter->lock, flags); + ni_tio_configure_dma(counter, false, false); + + ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), + GI_GATE_INTERRUPT_ENABLE(cidx), 0x0); + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_cancel); + +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: + case ni_gpct_variant_660x: + /* + * not sure if 660x really supports gate interrupts + * (the bits are not listed in register-level manual) + */ + return 1; + case ni_gpct_variant_e_series: + /* + * 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. + */ + spin_lock_irqsave(&counter->lock, flags); + { + if (!counter->mite_chan || + counter->mite_chan->dir != COMEDI_INPUT || + (mite_done(counter->mite_chan))) { + retval = 1; + } + } + spin_unlock_irqrestore(&counter->lock, flags); + break; + } + return retval; +} + +static void ni_tio_acknowledge_and_confirm(struct ni_gpct *counter, + int *gate_error, + int *tc_error, + int *perm_stale_data) +{ + unsigned int cidx = counter->counter_index; + const unsigned short gxx_status = ni_tio_read(counter, + NITIO_SHARED_STATUS_REG(cidx)); + const unsigned short gi_status = ni_tio_read(counter, + NITIO_STATUS_REG(cidx)); + unsigned int ack = 0; + + if (gate_error) + *gate_error = 0; + if (tc_error) + *tc_error = 0; + if (perm_stale_data) + *perm_stale_data = 0; + + if (gxx_status & GI_GATE_ERROR(cidx)) { + ack |= GI_GATE_ERROR_CONFIRM(cidx); + if (gate_error) { + /* + * 660x don't support automatic acknowledgment + * of gate interrupt via dma read/write + * and report bogus gate errors + */ + if (counter->counter_dev->variant != + ni_gpct_variant_660x) + *gate_error = 1; + } + } + if (gxx_status & GI_TC_ERROR(cidx)) { + ack |= GI_TC_ERROR_CONFIRM(cidx); + if (tc_error) + *tc_error = 1; + } + if (gi_status & GI_TC) + ack |= GI_TC_INTERRUPT_ACK; + if (gi_status & GI_GATE_INTERRUPT) { + if (should_ack_gate(counter)) + ack |= GI_GATE_INTERRUPT_ACK; + } + if (ack) + ni_tio_write(counter, ack, NITIO_INT_ACK_REG(cidx)); + if (ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx)) & + GI_LOADING_ON_GATE) { + if (ni_tio_read(counter, NITIO_STATUS2_REG(cidx)) & + GI_PERMANENT_STALE(cidx)) { + dev_info(counter->counter_dev->dev->class_dev, + "%s: Gi_Permanent_Stale_Data detected.\n", + __func__); + if (perm_stale_data) + *perm_stale_data = 1; + } + } +} + +void ni_tio_acknowledge(struct ni_gpct *counter) +{ + ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL); +} +EXPORT_SYMBOL_GPL(ni_tio_acknowledge); + +void ni_tio_handle_interrupt(struct ni_gpct *counter, + struct comedi_subdevice *s) +{ + unsigned int cidx = counter->counter_index; + 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); + 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 (ni_tio_read(counter, NITIO_DMA_STATUS_REG(cidx)) & + GI_DRQ_ERROR) { + dev_notice(counter->counter_dev->dev->class_dev, + "%s: Gi_DRQ_Error detected.\n", __func__); + s->async->events |= COMEDI_CB_OVERFLOW; + } + break; + case ni_gpct_variant_e_series: + break; + } + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan) + mite_ack_linkc(counter->mite_chan, s, true); + 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/comedi/drivers/ni_usb6501.c b/drivers/comedi/drivers/ni_usb6501.c new file mode 100644 index 000000000..0dd9edf7b --- /dev/null +++ b/drivers/comedi/drivers/ni_usb6501.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/ni_usb6501.c + * Comedi driver for National Instruments USB-6501 + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2014 Luca Ellero <luca.ellero@brickedbrain.com> + */ + +/* + * Driver: ni_usb6501 + * Description: National Instruments USB-6501 module + * Devices: [National Instruments] USB-6501 (ni_usb6501) + * Author: Luca Ellero <luca.ellero@brickedbrain.com> + * Updated: 8 Sep 2014 + * Status: works + * + * + * Configuration Options: + * none + */ + +/* + * NI-6501 - USB PROTOCOL DESCRIPTION + * + * Every command is composed by two USB packets: + * - request (out) + * - response (in) + * + * Every packet is at least 12 bytes long, here is the meaning of + * every field (all values are hex): + * + * byte 0 is always 00 + * byte 1 is always 01 + * byte 2 is always 00 + * byte 3 is the total packet length + * + * byte 4 is always 00 + * byte 5 is the total packet length - 4 + * byte 6 is always 01 + * byte 7 is the command + * + * byte 8 is 02 (request) or 00 (response) + * byte 9 is 00 (response) or 10 (port request) or 20 (counter request) + * byte 10 is always 00 + * byte 11 is 00 (request) or 02 (response) + * + * PORT PACKETS + * + * CMD: 0xE READ_PORT + * REQ: 00 01 00 10 00 0C 01 0E 02 10 00 00 00 03 <PORT> 00 + * RES: 00 01 00 10 00 0C 01 00 00 00 00 02 00 03 <BMAP> 00 + * + * CMD: 0xF WRITE_PORT + * REQ: 00 01 00 14 00 10 01 0F 02 10 00 00 00 03 <PORT> 00 03 <BMAP> 00 00 + * RES: 00 01 00 0C 00 08 01 00 00 00 00 02 + * + * CMD: 0x12 SET_PORT_DIR (0 = input, 1 = output) + * REQ: 00 01 00 18 00 14 01 12 02 10 00 00 + * 00 05 <PORT 0> <PORT 1> <PORT 2> 00 05 00 00 00 00 00 + * RES: 00 01 00 0C 00 08 01 00 00 00 00 02 + * + * COUNTER PACKETS + * + * CMD 0x9: START_COUNTER + * REQ: 00 01 00 0C 00 08 01 09 02 20 00 00 + * RES: 00 01 00 0C 00 08 01 00 00 00 00 02 + * + * CMD 0xC: STOP_COUNTER + * REQ: 00 01 00 0C 00 08 01 0C 02 20 00 00 + * RES: 00 01 00 0C 00 08 01 00 00 00 00 02 + * + * CMD 0xE: READ_COUNTER + * REQ: 00 01 00 0C 00 08 01 0E 02 20 00 00 + * RES: 00 01 00 10 00 0C 01 00 00 00 00 02 <u32 counter value, Big Endian> + * + * CMD 0xF: WRITE_COUNTER + * REQ: 00 01 00 10 00 0C 01 0F 02 20 00 00 <u32 counter value, Big Endian> + * RES: 00 01 00 0C 00 08 01 00 00 00 00 02 + * + * + * Please visit https://www.brickedbrain.com if you need + * additional information or have any questions. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/comedi/comedi_usb.h> + +#define NI6501_TIMEOUT 1000 + +/* Port request packets */ +static const u8 READ_PORT_REQUEST[] = {0x00, 0x01, 0x00, 0x10, + 0x00, 0x0C, 0x01, 0x0E, + 0x02, 0x10, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00}; + +static const u8 WRITE_PORT_REQUEST[] = {0x00, 0x01, 0x00, 0x14, + 0x00, 0x10, 0x01, 0x0F, + 0x02, 0x10, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00}; + +static const u8 SET_PORT_DIR_REQUEST[] = {0x00, 0x01, 0x00, 0x18, + 0x00, 0x14, 0x01, 0x12, + 0x02, 0x10, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00}; + +/* Counter request packets */ +static const u8 START_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C, + 0x00, 0x08, 0x01, 0x09, + 0x02, 0x20, 0x00, 0x00}; + +static const u8 STOP_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C, + 0x00, 0x08, 0x01, 0x0C, + 0x02, 0x20, 0x00, 0x00}; + +static const u8 READ_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C, + 0x00, 0x08, 0x01, 0x0E, + 0x02, 0x20, 0x00, 0x00}; + +static const u8 WRITE_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x10, + 0x00, 0x0C, 0x01, 0x0F, + 0x02, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + +/* Response packets */ +static const u8 GENERIC_RESPONSE[] = {0x00, 0x01, 0x00, 0x0C, + 0x00, 0x08, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x02}; + +static const u8 READ_PORT_RESPONSE[] = {0x00, 0x01, 0x00, 0x10, + 0x00, 0x0C, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x00}; + +static const u8 READ_COUNTER_RESPONSE[] = {0x00, 0x01, 0x00, 0x10, + 0x00, 0x0C, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00}; + +/* Largest supported packets */ +static const size_t TX_MAX_SIZE = sizeof(SET_PORT_DIR_REQUEST); +static const size_t RX_MAX_SIZE = sizeof(READ_PORT_RESPONSE); + +enum commands { + READ_PORT, + WRITE_PORT, + SET_PORT_DIR, + START_COUNTER, + STOP_COUNTER, + READ_COUNTER, + WRITE_COUNTER +}; + +struct ni6501_private { + struct usb_endpoint_descriptor *ep_rx; + struct usb_endpoint_descriptor *ep_tx; + struct mutex mut; + u8 *usb_rx_buf; + u8 *usb_tx_buf; +}; + +static int ni6501_port_command(struct comedi_device *dev, int command, + unsigned int val, u8 *bitmap) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct ni6501_private *devpriv = dev->private; + int request_size, response_size; + u8 *tx = devpriv->usb_tx_buf; + int ret; + + if (command != SET_PORT_DIR && !bitmap) + return -EINVAL; + + mutex_lock(&devpriv->mut); + + switch (command) { + case READ_PORT: + request_size = sizeof(READ_PORT_REQUEST); + response_size = sizeof(READ_PORT_RESPONSE); + memcpy(tx, READ_PORT_REQUEST, request_size); + tx[14] = val & 0xff; + break; + case WRITE_PORT: + request_size = sizeof(WRITE_PORT_REQUEST); + response_size = sizeof(GENERIC_RESPONSE); + memcpy(tx, WRITE_PORT_REQUEST, request_size); + tx[14] = val & 0xff; + tx[17] = *bitmap; + break; + case SET_PORT_DIR: + request_size = sizeof(SET_PORT_DIR_REQUEST); + response_size = sizeof(GENERIC_RESPONSE); + memcpy(tx, SET_PORT_DIR_REQUEST, request_size); + tx[14] = val & 0xff; + tx[15] = (val >> 8) & 0xff; + tx[16] = (val >> 16) & 0xff; + break; + default: + ret = -EINVAL; + goto end; + } + + ret = usb_bulk_msg(usb, + usb_sndbulkpipe(usb, + devpriv->ep_tx->bEndpointAddress), + devpriv->usb_tx_buf, + request_size, + NULL, + NI6501_TIMEOUT); + if (ret) + goto end; + + ret = usb_bulk_msg(usb, + usb_rcvbulkpipe(usb, + devpriv->ep_rx->bEndpointAddress), + devpriv->usb_rx_buf, + response_size, + NULL, + NI6501_TIMEOUT); + if (ret) + goto end; + + /* Check if results are valid */ + + if (command == READ_PORT) { + *bitmap = devpriv->usb_rx_buf[14]; + /* mask bitmap for comparing */ + devpriv->usb_rx_buf[14] = 0x00; + + if (memcmp(devpriv->usb_rx_buf, READ_PORT_RESPONSE, + sizeof(READ_PORT_RESPONSE))) { + ret = -EINVAL; + } + } else if (memcmp(devpriv->usb_rx_buf, GENERIC_RESPONSE, + sizeof(GENERIC_RESPONSE))) { + ret = -EINVAL; + } +end: + mutex_unlock(&devpriv->mut); + + return ret; +} + +static int ni6501_counter_command(struct comedi_device *dev, int command, + u32 *val) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct ni6501_private *devpriv = dev->private; + int request_size, response_size; + u8 *tx = devpriv->usb_tx_buf; + int ret; + + if ((command == READ_COUNTER || command == WRITE_COUNTER) && !val) + return -EINVAL; + + mutex_lock(&devpriv->mut); + + switch (command) { + case START_COUNTER: + request_size = sizeof(START_COUNTER_REQUEST); + response_size = sizeof(GENERIC_RESPONSE); + memcpy(tx, START_COUNTER_REQUEST, request_size); + break; + case STOP_COUNTER: + request_size = sizeof(STOP_COUNTER_REQUEST); + response_size = sizeof(GENERIC_RESPONSE); + memcpy(tx, STOP_COUNTER_REQUEST, request_size); + break; + case READ_COUNTER: + request_size = sizeof(READ_COUNTER_REQUEST); + response_size = sizeof(READ_COUNTER_RESPONSE); + memcpy(tx, READ_COUNTER_REQUEST, request_size); + break; + case WRITE_COUNTER: + request_size = sizeof(WRITE_COUNTER_REQUEST); + response_size = sizeof(GENERIC_RESPONSE); + memcpy(tx, WRITE_COUNTER_REQUEST, request_size); + /* Setup tx packet: bytes 12,13,14,15 hold the */ + /* u32 counter value (Big Endian) */ + *((__be32 *)&tx[12]) = cpu_to_be32(*val); + break; + default: + ret = -EINVAL; + goto end; + } + + ret = usb_bulk_msg(usb, + usb_sndbulkpipe(usb, + devpriv->ep_tx->bEndpointAddress), + devpriv->usb_tx_buf, + request_size, + NULL, + NI6501_TIMEOUT); + if (ret) + goto end; + + ret = usb_bulk_msg(usb, + usb_rcvbulkpipe(usb, + devpriv->ep_rx->bEndpointAddress), + devpriv->usb_rx_buf, + response_size, + NULL, + NI6501_TIMEOUT); + if (ret) + goto end; + + /* Check if results are valid */ + + if (command == READ_COUNTER) { + int i; + + /* Read counter value: bytes 12,13,14,15 of rx packet */ + /* hold the u32 counter value (Big Endian) */ + *val = be32_to_cpu(*((__be32 *)&devpriv->usb_rx_buf[12])); + + /* mask counter value for comparing */ + for (i = 12; i < sizeof(READ_COUNTER_RESPONSE); ++i) + devpriv->usb_rx_buf[i] = 0x00; + + if (memcmp(devpriv->usb_rx_buf, READ_COUNTER_RESPONSE, + sizeof(READ_COUNTER_RESPONSE))) { + ret = -EINVAL; + } + } else if (memcmp(devpriv->usb_rx_buf, GENERIC_RESPONSE, + sizeof(GENERIC_RESPONSE))) { + ret = -EINVAL; + } +end: + mutex_unlock(&devpriv->mut); + + return ret; +} + +static int ni6501_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + ret = ni6501_port_command(dev, SET_PORT_DIR, s->io_bits, NULL); + if (ret) + return ret; + + return insn->n; +} + +static int ni6501_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + int ret; + u8 port; + u8 bitmap; + + mask = comedi_dio_update_state(s, data); + + for (port = 0; port < 3; port++) { + if (mask & (0xFF << port * 8)) { + bitmap = (s->state >> port * 8) & 0xFF; + ret = ni6501_port_command(dev, WRITE_PORT, + port, &bitmap); + if (ret) + return ret; + } + } + + data[1] = 0; + + for (port = 0; port < 3; port++) { + ret = ni6501_port_command(dev, READ_PORT, port, &bitmap); + if (ret) + return ret; + data[1] |= bitmap << port * 8; + } + + return insn->n; +} + +static int ni6501_cnt_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + u32 val = 0; + + switch (data[0]) { + case INSN_CONFIG_ARM: + ret = ni6501_counter_command(dev, START_COUNTER, NULL); + break; + case INSN_CONFIG_DISARM: + ret = ni6501_counter_command(dev, STOP_COUNTER, NULL); + break; + case INSN_CONFIG_RESET: + ret = ni6501_counter_command(dev, STOP_COUNTER, NULL); + if (ret) + break; + ret = ni6501_counter_command(dev, WRITE_COUNTER, &val); + break; + default: + return -EINVAL; + } + + return ret ? ret : insn->n; +} + +static int ni6501_cnt_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + u32 val; + unsigned int i; + + for (i = 0; i < insn->n; i++) { + ret = ni6501_counter_command(dev, READ_COUNTER, &val); + if (ret) + return ret; + data[i] = val; + } + + return insn->n; +} + +static int ni6501_cnt_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + if (insn->n) { + u32 val = data[insn->n - 1]; + + ret = ni6501_counter_command(dev, WRITE_COUNTER, &val); + if (ret) + return ret; + } + + return insn->n; +} + +static int ni6501_alloc_usb_buffers(struct comedi_device *dev) +{ + struct ni6501_private *devpriv = dev->private; + size_t size; + + size = usb_endpoint_maxp(devpriv->ep_rx); + devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL); + if (!devpriv->usb_rx_buf) + return -ENOMEM; + + size = usb_endpoint_maxp(devpriv->ep_tx); + devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL); + if (!devpriv->usb_tx_buf) + return -ENOMEM; + + return 0; +} + +static int ni6501_find_endpoints(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct ni6501_private *devpriv = dev->private; + struct usb_host_interface *iface_desc = intf->cur_altsetting; + struct usb_endpoint_descriptor *ep_desc; + int i; + + if (iface_desc->desc.bNumEndpoints != 2) { + dev_err(dev->class_dev, "Wrong number of endpoints\n"); + return -ENODEV; + } + + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + ep_desc = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(ep_desc)) { + if (!devpriv->ep_rx) + devpriv->ep_rx = ep_desc; + continue; + } + + if (usb_endpoint_is_bulk_out(ep_desc)) { + if (!devpriv->ep_tx) + devpriv->ep_tx = ep_desc; + continue; + } + } + + if (!devpriv->ep_rx || !devpriv->ep_tx) + return -ENODEV; + + if (usb_endpoint_maxp(devpriv->ep_rx) < RX_MAX_SIZE) + return -ENODEV; + + if (usb_endpoint_maxp(devpriv->ep_tx) < TX_MAX_SIZE) + return -ENODEV; + + return 0; +} + +static int ni6501_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct ni6501_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + mutex_init(&devpriv->mut); + usb_set_intfdata(intf, devpriv); + + ret = ni6501_find_endpoints(dev); + if (ret) + return ret; + + ret = ni6501_alloc_usb_buffers(dev); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Digital Input/Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni6501_dio_insn_bits; + s->insn_config = ni6501_dio_insn_config; + + /* Counter subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; + s->n_chan = 1; + s->maxdata = 0xffffffff; + s->insn_read = ni6501_cnt_insn_read; + s->insn_write = ni6501_cnt_insn_write; + s->insn_config = ni6501_cnt_insn_config; + + return 0; +} + +static void ni6501_detach(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct ni6501_private *devpriv = dev->private; + + if (!devpriv) + return; + + mutex_destroy(&devpriv->mut); + + usb_set_intfdata(intf, NULL); + + kfree(devpriv->usb_rx_buf); + kfree(devpriv->usb_tx_buf); +} + +static struct comedi_driver ni6501_driver = { + .module = THIS_MODULE, + .driver_name = "ni6501", + .auto_attach = ni6501_auto_attach, + .detach = ni6501_detach, +}; + +static int ni6501_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return comedi_usb_auto_config(intf, &ni6501_driver, id->driver_info); +} + +static const struct usb_device_id ni6501_usb_table[] = { + { USB_DEVICE(0x3923, 0x718a) }, + { } +}; +MODULE_DEVICE_TABLE(usb, ni6501_usb_table); + +static struct usb_driver ni6501_usb_driver = { + .name = "ni6501", + .id_table = ni6501_usb_table, + .probe = ni6501_usb_probe, + .disconnect = comedi_usb_auto_unconfig, +}; +module_comedi_usb_driver(ni6501_driver, ni6501_usb_driver); + +MODULE_AUTHOR("Luca Ellero"); +MODULE_DESCRIPTION("Comedi driver for National Instruments USB-6501"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl711.c b/drivers/comedi/drivers/pcl711.c new file mode 100644 index 000000000..05172c553 --- /dev/null +++ b/drivers/comedi/drivers/pcl711.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: pcl711 + * Description: Advantech PCL-711 and 711b, ADLink ACL-8112 + * Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b), + * [ADLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg) + * Author: David A. Schleef <ds@schleef.org> + * Janne Jalkanen <jalkanen@cs.hut.fi> + * Eric Bunn <ebu@cs.hut.fi> + * Updated: + * Status: mostly complete + * + * Configuration Options: + * [0] - I/O port base + * [1] - IRQ, optional + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> + +/* + * I/O port register map + */ +#define PCL711_TIMER_BASE 0x00 +#define PCL711_AI_LSB_REG 0x04 +#define PCL711_AI_MSB_REG 0x05 +#define PCL711_AI_MSB_DRDY BIT(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 BIT(4) +#define PCL711_MUX_CS1 BIT(5) +#define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1) +#define PCL711_MODE_REG 0x0b +#define PCL711_MODE(x) (((x) & 0x7) << 0) +#define PCL711_MODE_DEFAULT PCL711_MODE(0) +#define PCL711_MODE_SOFTTRIG PCL711_MODE(1) +#define PCL711_MODE_EXT PCL711_MODE(2) +#define PCL711_MODE_EXT_IRQ PCL711_MODE(3) +#define PCL711_MODE_PACER PCL711_MODE(4) +#define PCL711_MODE_PACER_IRQ PCL711_MODE(6) +#define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4) +#define PCL711_SOFTTRIG_REG 0x0c +#define PCL711_SOFTTRIG (0 << 0) /* any value will work */ +#define PCL711_DO_LSB_REG 0x0d +#define PCL711_DO_MSB_REG 0x0e + +static const struct comedi_lrange range_pcl711b_ai = { + 5, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + BIP_RANGE(0.3125) + } +}; + +static const struct comedi_lrange range_acl8112hg_ai = { + 12, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_acl8112dg_ai = { + 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + BIP_RANGE(10) + } +}; + +struct pcl711_board { + const char *name; + int n_aichan; + int n_aochan; + int maxirq; + const struct comedi_lrange *ai_range_type; +}; + +static const struct pcl711_board boardtypes[] = { + { + .name = "pcl711", + .n_aichan = 8, + .n_aochan = 1, + .ai_range_type = &range_bipolar5, + }, { + .name = "pcl711b", + .n_aichan = 8, + .n_aochan = 1, + .maxirq = 7, + .ai_range_type = &range_pcl711b_ai, + }, { + .name = "acl8112hg", + .n_aichan = 16, + .n_aochan = 2, + .maxirq = 15, + .ai_range_type = &range_acl8112hg_ai, + }, { + .name = "acl8112dg", + .n_aichan = 16, + .n_aochan = 2, + .maxirq = 15, + .ai_range_type = &range_acl8112dg_ai, + }, +}; + +static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode) +{ + /* + * The pcl711b board uses bits in the mode register to select the + * interrupt. The other boards supported by this driver all use + * jumpers on the board. + * + * Enables the interrupt when needed on the pcl711b board. These + * bits do nothing on the other boards. + */ + if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ) + mode |= PCL711_MODE_IRQ(dev->irq); + + outb(mode, dev->iobase + PCL711_MODE_REG); +} + +static unsigned int pcl711_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8; + val |= inb(dev->iobase + PCL711_AI_LSB_REG); + + return val & s->maxdata; +} + +static int pcl711_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); + pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); + return 0; +} + +static irqreturn_t pcl711_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short data; + + if (!dev->attached) { + dev_err(dev->class_dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + + data = pcl711_ai_get_sample(dev, s); + + outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); + + comedi_buf_write_samples(s, &data, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static void pcl711_set_changain(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec) +{ + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + unsigned int mux = 0; + + outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG); + + if (s->n_chan > 8) { + /* Select the correct MPC508A chip */ + if (aref == AREF_DIFF) { + chan &= 0x7; + mux |= PCL711_MUX_DIFF; + } else { + if (chan < 8) + mux |= PCL711_MUX_CS0; + else + mux |= PCL711_MUX_CS1; + } + } + outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG); +} + +static int pcl711_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + PCL711_AI_MSB_REG); + if ((status & PCL711_AI_MSB_DRDY) == 0) + return 0; + return -EBUSY; +} + +static int pcl711_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + int i; + + pcl711_set_changain(dev, s, insn->chanspec); + + pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); + + for (i = 0; i < insn->n; i++) { + outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG); + + ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0); + if (ret) + return ret; + + data[i] = pcl711_ai_get_sample(dev, s); + } + + return insn->n; +} + +static int pcl711_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_EXT) { + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + } else { +#define MAX_SPEED 1000 + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + MAX_SPEED); + } + + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4 */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int arg = cmd->scan_begin_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + return 0; +} + +static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + pcl711_set_changain(dev, s, cmd->chanlist[0]); + + if (cmd->scan_begin_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); + pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ); + } else { + pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ); + } + + return 0; +} + +static void pcl711_ao_write(struct comedi_device *dev, + unsigned int chan, unsigned int val) +{ + outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan)); + outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan)); +} + +static int pcl711_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + pcl711_ao_write(dev, chan, val); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pcl711_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int val; + + val = inb(dev->iobase + PCL711_DI_LSB_REG); + val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8); + + data[1] = val; + + return insn->n; +} + +static int pcl711_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0x00ff) + outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG); + if (mask & 0xff00) + outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl711_board *board = dev->board_ptr; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + if (it->options[1] && it->options[1] <= board->maxirq) { + ret = request_irq(it->options[1], pcl711_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + dev->pacer = comedi_8254_init(dev->iobase + PCL711_TIMER_BASE, + I8254_OSC_BASE_2MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + if (board->n_aichan > 8) + s->subdev_flags |= SDF_DIFF; + s->n_chan = board->n_aichan; + s->maxdata = 0xfff; + s->range_table = board->ai_range_type; + s->insn_read = pcl711_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 1; + s->do_cmdtest = pcl711_ai_cmdtest; + s->do_cmd = pcl711_ai_cmd; + s->cancel = pcl711_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->n_aochan; + s->maxdata = 0xfff; + s->range_table = &range_bipolar5; + s->insn_write = pcl711_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl711_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl711_do_insn_bits; + + /* clear DAC */ + pcl711_ao_write(dev, 0, 0x0); + pcl711_ao_write(dev, 1, 0x0); + + return 0; +} + +static struct comedi_driver pcl711_driver = { + .driver_name = "pcl711", + .module = THIS_MODULE, + .attach = pcl711_attach, + .detach = comedi_legacy_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct pcl711_board), +}; +module_comedi_driver(pcl711_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl724.c b/drivers/comedi/drivers/pcl724.c new file mode 100644 index 000000000..948a0576c --- /dev/null +++ b/drivers/comedi/drivers/pcl724.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pcl724.c + * Comedi driver for 8255 based ISA and PC/104 DIO boards + * + * Michal Dobes <dobes@tesnet.cz> + */ + +/* + * Driver: pcl724 + * Description: Comedi driver for 8255 based ISA DIO boards + * Devices: [Advantech] PCL-724 (pcl724), PCL-722 (pcl722), PCL-731 (pcl731), + * [ADLink] ACL-7122 (acl7122), ACL-7124 (acl7124), PET-48DIO (pet48dio), + * [WinSystems] PCM-IO48 (pcmio48), + * [Diamond Systems] ONYX-MM-DIO (onyx-mm-dio) + * Author: Michal Dobes <dobes@tesnet.cz> + * Status: untested + * + * Configuration options: + * [0] - IO Base + * [1] - IRQ (not supported) + * [2] - number of DIO (pcl722 and acl7122 boards) + * 0, 144: 144 DIO configuration + * 1, 96: 96 DIO configuration + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +struct pcl724_board { + const char *name; + unsigned int io_range; + unsigned int can_have96:1; + unsigned int is_pet48:1; + int numofports; +}; + +static const struct pcl724_board boardtypes[] = { + { + .name = "pcl724", + .io_range = 0x04, + .numofports = 1, /* 24 DIO channels */ + }, { + .name = "pcl722", + .io_range = 0x20, + .can_have96 = 1, + .numofports = 6, /* 144 (or 96) DIO channels */ + }, { + .name = "pcl731", + .io_range = 0x08, + .numofports = 2, /* 48 DIO channels */ + }, { + .name = "acl7122", + .io_range = 0x20, + .can_have96 = 1, + .numofports = 6, /* 144 (or 96) DIO channels */ + }, { + .name = "acl7124", + .io_range = 0x04, + .numofports = 1, /* 24 DIO channels */ + }, { + .name = "pet48dio", + .io_range = 0x02, + .is_pet48 = 1, + .numofports = 2, /* 48 DIO channels */ + }, { + .name = "pcmio48", + .io_range = 0x08, + .numofports = 2, /* 48 DIO channels */ + }, { + .name = "onyx-mm-dio", + .io_range = 0x10, + .numofports = 2, /* 48 DIO channels */ + }, +}; + +static int pcl724_8255mapped_io(struct comedi_device *dev, + int dir, int port, int data, + unsigned long iobase) +{ + int movport = I8255_SIZE * (iobase >> 12); + + iobase &= 0x0fff; + + outb(port + movport, iobase); + if (dir) { + outb(data, iobase + 1); + return 0; + } + return inb(iobase + 1); +} + +static int pcl724_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pcl724_board *board = dev->board_ptr; + struct comedi_subdevice *s; + unsigned long iobase; + unsigned int iorange; + int n_subdevices; + int ret; + int i; + + iorange = board->io_range; + n_subdevices = board->numofports; + + /* Handle PCL-724 in 96 DIO configuration */ + if (board->can_have96 && + (it->options[2] == 1 || it->options[2] == 96)) { + iorange = 0x10; + n_subdevices = 4; + } + + ret = comedi_request_region(dev, it->options[0], iorange); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, n_subdevices); + if (ret) + return ret; + + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + if (board->is_pet48) { + iobase = dev->iobase + (i * 0x1000); + ret = subdev_8255_init(dev, s, pcl724_8255mapped_io, + iobase); + } else { + ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE); + } + if (ret) + return ret; + } + + return 0; +} + +static struct comedi_driver pcl724_driver = { + .driver_name = "pcl724", + .module = THIS_MODULE, + .attach = pcl724_attach, + .detach = comedi_legacy_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct pcl724_board), +}; +module_comedi_driver(pcl724_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for 8255 based ISA and PC/104 DIO boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl726.c b/drivers/comedi/drivers/pcl726.c new file mode 100644 index 000000000..0430630e6 --- /dev/null +++ b/drivers/comedi/drivers/pcl726.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: pcl726 + * Description: Advantech PCL-726 & compatibles + * Author: David A. Schleef <ds@schleef.org> + * Status: untested + * Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728), + * [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128) + * + * Configuration Options: + * [0] - IO Base + * [1] - IRQ (ACL-6126 only) + * [2] - D/A output range for channel 0 + * [3] - D/A output range for channel 1 + * + * Boards with > 2 analog output channels: + * [4] - D/A output range for channel 2 + * [5] - D/A output range for channel 3 + * [6] - D/A output range for channel 4 + * [7] - D/A output range for channel 5 + * + * Boards with > 6 analog output channels: + * [8] - D/A output range for channel 6 + * [9] - D/A output range for channel 7 + * [10] - D/A output range for channel 8 + * [11] - D/A output range for channel 9 + * [12] - D/A output range for channel 10 + * [13] - D/A output range for channel 11 + * + * For PCL-726 the D/A output ranges are: + * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: unknown + * + * For PCL-727: + * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: 4-20mA + * + * For PCL-728 and ACL-6128: + * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: 0-20mA + * + * For ACL-6126: + * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> + +#define PCL726_AO_MSB_REG(x) (0x00 + ((x) * 2)) +#define PCL726_AO_LSB_REG(x) (0x01 + ((x) * 2)) +#define PCL726_DO_MSB_REG 0x0c +#define PCL726_DO_LSB_REG 0x0d +#define PCL726_DI_MSB_REG 0x0e +#define PCL726_DI_LSB_REG 0x0f + +#define PCL727_DI_MSB_REG 0x00 +#define PCL727_DI_LSB_REG 0x01 +#define PCL727_DO_MSB_REG 0x18 +#define PCL727_DO_LSB_REG 0x19 + +static const struct comedi_lrange *const rangelist_726[] = { + &range_unipolar5, + &range_unipolar10, + &range_bipolar5, + &range_bipolar10, + &range_4_20mA, + &range_unknown +}; + +static const struct comedi_lrange *const rangelist_727[] = { + &range_unipolar5, + &range_unipolar10, + &range_bipolar5, + &range_4_20mA +}; + +static const struct comedi_lrange *const rangelist_728[] = { + &range_unipolar5, + &range_unipolar10, + &range_bipolar5, + &range_bipolar10, + &range_4_20mA, + &range_0_20mA +}; + +struct pcl726_board { + const char *name; + unsigned long io_len; + unsigned int irq_mask; + const struct comedi_lrange *const *ao_ranges; + int ao_num_ranges; + int ao_nchan; + unsigned int have_dio:1; + unsigned int is_pcl727:1; +}; + +static const struct pcl726_board pcl726_boards[] = { + { + .name = "pcl726", + .io_len = 0x10, + .ao_ranges = &rangelist_726[0], + .ao_num_ranges = ARRAY_SIZE(rangelist_726), + .ao_nchan = 6, + .have_dio = 1, + }, { + .name = "pcl727", + .io_len = 0x20, + .ao_ranges = &rangelist_727[0], + .ao_num_ranges = ARRAY_SIZE(rangelist_727), + .ao_nchan = 12, + .have_dio = 1, + .is_pcl727 = 1, + }, { + .name = "pcl728", + .io_len = 0x08, + .ao_num_ranges = ARRAY_SIZE(rangelist_728), + .ao_ranges = &rangelist_728[0], + .ao_nchan = 2, + }, { + .name = "acl6126", + .io_len = 0x10, + .irq_mask = 0x96e8, + .ao_num_ranges = ARRAY_SIZE(rangelist_726), + .ao_ranges = &rangelist_726[0], + .ao_nchan = 6, + .have_dio = 1, + }, { + .name = "acl6128", + .io_len = 0x08, + .ao_num_ranges = ARRAY_SIZE(rangelist_728), + .ao_ranges = &rangelist_728[0], + .ao_nchan = 2, + }, +}; + +struct pcl726_private { + const struct comedi_lrange *rangelist[12]; + unsigned int cmd_running:1; +}; + +static int pcl726_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + return insn->n; +} + +static int pcl726_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + /* Step 2b : and mutually compatible */ + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + + return 0; +} + +static int pcl726_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl726_private *devpriv = dev->private; + + devpriv->cmd_running = 1; + + return 0; +} + +static int pcl726_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl726_private *devpriv = dev->private; + + devpriv->cmd_running = 0; + + return 0; +} + +static irqreturn_t pcl726_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct pcl726_private *devpriv = dev->private; + + if (devpriv->cmd_running) { + unsigned short val = 0; + + pcl726_intr_cancel(dev, s); + + comedi_buf_write_samples(s, &val, 1); + comedi_handle_events(dev, s); + } + + return IRQ_HANDLED; +} + +static int pcl726_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + /* bipolar data to the DAC is two's complement */ + if (comedi_chan_range_is_bipolar(s, chan, range)) + val = comedi_offset_munge(s, val); + + /* order is important, MSB then LSB */ + outb((val >> 8) & 0xff, dev->iobase + PCL726_AO_MSB_REG(chan)); + outb(val & 0xff, dev->iobase + PCL726_AO_LSB_REG(chan)); + } + + return insn->n; +} + +static int pcl726_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct pcl726_board *board = dev->board_ptr; + unsigned int val; + + if (board->is_pcl727) { + val = inb(dev->iobase + PCL727_DI_LSB_REG); + val |= (inb(dev->iobase + PCL727_DI_MSB_REG) << 8); + } else { + val = inb(dev->iobase + PCL726_DI_LSB_REG); + val |= (inb(dev->iobase + PCL726_DI_MSB_REG) << 8); + } + + data[1] = val; + + return insn->n; +} + +static int pcl726_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct pcl726_board *board = dev->board_ptr; + unsigned long io = dev->iobase; + unsigned int mask; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (board->is_pcl727) { + if (mask & 0x00ff) + outb(s->state & 0xff, io + PCL727_DO_LSB_REG); + if (mask & 0xff00) + outb((s->state >> 8), io + PCL727_DO_MSB_REG); + } else { + if (mask & 0x00ff) + outb(s->state & 0xff, io + PCL726_DO_LSB_REG); + if (mask & 0xff00) + outb((s->state >> 8), io + PCL726_DO_MSB_REG); + } + } + + data[1] = s->state; + + return insn->n; +} + +static int pcl726_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pcl726_board *board = dev->board_ptr; + struct pcl726_private *devpriv; + struct comedi_subdevice *s; + int subdev; + int ret; + int i; + + ret = comedi_request_region(dev, it->options[0], board->io_len); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + /* + * Hook up the external trigger source interrupt only if the + * user config option is valid and the board supports interrupts. + */ + if (it->options[1] && (board->irq_mask & (1 << it->options[1]))) { + ret = request_irq(it->options[1], pcl726_interrupt, 0, + dev->board_name, dev); + if (ret == 0) { + /* External trigger source is from Pin-17 of CN3 */ + dev->irq = it->options[1]; + } + } + + /* setup the per-channel analog output range_table_list */ + for (i = 0; i < 12; i++) { + unsigned int opt = it->options[2 + i]; + + if (opt < board->ao_num_ranges && i < board->ao_nchan) + devpriv->rangelist[i] = board->ao_ranges[opt]; + else + devpriv->rangelist[i] = &range_unknown; + } + + subdev = board->have_dio ? 3 : 1; + if (dev->irq) + subdev++; + ret = comedi_alloc_subdevices(dev, subdev); + if (ret) + return ret; + + subdev = 0; + + /* Analog Output subdevice */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = board->ao_nchan; + s->maxdata = 0x0fff; + s->range_table_list = devpriv->rangelist; + s->insn_write = pcl726_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + if (board->have_dio) { + /* Digital Input subdevice */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->insn_bits = pcl726_di_insn_bits; + s->range_table = &range_digital; + + /* Digital Output subdevice */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->insn_bits = pcl726_do_insn_bits; + s->range_table = &range_digital; + } + + if (dev->irq) { + /* Digital 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Advantech PCL-726 & compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl730.c b/drivers/comedi/drivers/pcl730.c new file mode 100644 index 000000000..d2733cd53 --- /dev/null +++ b/drivers/comedi/drivers/pcl730.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * comedi/drivers/pcl730.c + * Driver for Advantech PCL-730 and clones + * José Luis Sánchez + */ + +/* + * Driver: pcl730 + * Description: Advantech PCL-730 (& compatibles) + * Devices: [Advantech] PCL-730 (pcl730), PCM-3730 (pcm3730), PCL-725 (pcl725), + * PCL-733 (pcl733), PCL-734 (pcl734), + * [ADLink] ACL-7130 (acl7130), ACL-7225b (acl7225b), + * [ICP] ISO-730 (iso730), P8R8-DIO (p8r8dio), P16R16-DIO (p16r16dio), + * [Diamond Systems] OPMM-1616-XT (opmm-1616-xt), PEARL-MM-P (pearl-mm-p), + * IR104-PBF (ir104-pbf), + * Author: José Luis Sánchez (jsanchezv@teleline.es) + * Status: untested + * + * Configuration options: + * [0] - I/O port base + * + * Interrupts are not supported. + * The ACL-7130 card has an 8254 timer/counter not supported by this driver. + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> + +/* + * Register map + * + * The register map varies slightly depending on the board type but + * all registers are 8-bit. + * + * The boardinfo 'io_range' is used to allow comedi to request the + * proper range required by the board. + * + * The comedi_subdevice 'private' data is used to pass the register + * offset to the (*insn_bits) functions to read/write the correct + * registers. + * + * The basic register mapping looks like this: + * + * BASE+0 Isolated outputs 0-7 (write) / inputs 0-7 (read) + * BASE+1 Isolated outputs 8-15 (write) / inputs 8-15 (read) + * BASE+2 TTL outputs 0-7 (write) / inputs 0-7 (read) + * BASE+3 TTL outputs 8-15 (write) / inputs 8-15 (read) + * + * The pcm3730 board does not have register BASE+1. + * + * The pcl725 and p8r8dio only have registers BASE+0 and BASE+1: + * + * BASE+0 Isolated outputs 0-7 (write) (read back on p8r8dio) + * BASE+1 Isolated inputs 0-7 (read) + * + * The acl7225b and p16r16dio boards have this register mapping: + * + * BASE+0 Isolated outputs 0-7 (write) (read back) + * BASE+1 Isolated outputs 8-15 (write) (read back) + * BASE+2 Isolated inputs 0-7 (read) + * BASE+3 Isolated inputs 8-15 (read) + * + * The pcl733 and pcl733 boards have this register mapping: + * + * BASE+0 Isolated outputs 0-7 (write) or inputs 0-7 (read) + * BASE+1 Isolated outputs 8-15 (write) or inputs 8-15 (read) + * BASE+2 Isolated outputs 16-23 (write) or inputs 16-23 (read) + * BASE+3 Isolated outputs 24-31 (write) or inputs 24-31 (read) + * + * The opmm-1616-xt board has this register mapping: + * + * BASE+0 Isolated outputs 0-7 (write) (read back) + * BASE+1 Isolated outputs 8-15 (write) (read back) + * BASE+2 Isolated inputs 0-7 (read) + * BASE+3 Isolated inputs 8-15 (read) + * + * These registers are not currently supported: + * + * BASE+2 Relay select register (write) + * BASE+3 Board reset control register (write) + * BASE+4 Interrupt control register (write) + * BASE+4 Change detect 7-0 status register (read) + * BASE+5 LED control register (write) + * BASE+5 Change detect 15-8 status register (read) + * + * The pearl-mm-p board has this register mapping: + * + * BASE+0 Isolated outputs 0-7 (write) + * BASE+1 Isolated outputs 8-15 (write) + * + * The ir104-pbf board has this register mapping: + * + * BASE+0 Isolated outputs 0-7 (write) (read back) + * BASE+1 Isolated outputs 8-15 (write) (read back) + * BASE+2 Isolated outputs 16-19 (write) (read back) + * BASE+4 Isolated inputs 0-7 (read) + * BASE+5 Isolated inputs 8-15 (read) + * BASE+6 Isolated inputs 16-19 (read) + */ + +struct pcl730_board { + const char *name; + unsigned int io_range; + unsigned is_pcl725:1; + unsigned is_acl7225b:1; + unsigned is_ir104:1; + unsigned has_readback:1; + unsigned has_ttl_io:1; + int n_subdevs; + int n_iso_out_chan; + int n_iso_in_chan; + int n_ttl_chan; +}; + +static const struct pcl730_board pcl730_boards[] = { + { + .name = "pcl730", + .io_range = 0x04, + .has_ttl_io = 1, + .n_subdevs = 4, + .n_iso_out_chan = 16, + .n_iso_in_chan = 16, + .n_ttl_chan = 16, + }, { + .name = "iso730", + .io_range = 0x04, + .n_subdevs = 4, + .n_iso_out_chan = 16, + .n_iso_in_chan = 16, + .n_ttl_chan = 16, + }, { + .name = "acl7130", + .io_range = 0x08, + .has_ttl_io = 1, + .n_subdevs = 4, + .n_iso_out_chan = 16, + .n_iso_in_chan = 16, + .n_ttl_chan = 16, + }, { + .name = "pcm3730", + .io_range = 0x04, + .has_ttl_io = 1, + .n_subdevs = 4, + .n_iso_out_chan = 8, + .n_iso_in_chan = 8, + .n_ttl_chan = 16, + }, { + .name = "pcl725", + .io_range = 0x02, + .is_pcl725 = 1, + .n_subdevs = 2, + .n_iso_out_chan = 8, + .n_iso_in_chan = 8, + }, { + .name = "p8r8dio", + .io_range = 0x02, + .is_pcl725 = 1, + .has_readback = 1, + .n_subdevs = 2, + .n_iso_out_chan = 8, + .n_iso_in_chan = 8, + }, { + .name = "acl7225b", + .io_range = 0x08, /* only 4 are used */ + .is_acl7225b = 1, + .has_readback = 1, + .n_subdevs = 2, + .n_iso_out_chan = 16, + .n_iso_in_chan = 16, + }, { + .name = "p16r16dio", + .io_range = 0x04, + .is_acl7225b = 1, + .has_readback = 1, + .n_subdevs = 2, + .n_iso_out_chan = 16, + .n_iso_in_chan = 16, + }, { + .name = "pcl733", + .io_range = 0x04, + .n_subdevs = 1, + .n_iso_in_chan = 32, + }, { + .name = "pcl734", + .io_range = 0x04, + .n_subdevs = 1, + .n_iso_out_chan = 32, + }, { + .name = "opmm-1616-xt", + .io_range = 0x10, + .is_acl7225b = 1, + .has_readback = 1, + .n_subdevs = 2, + .n_iso_out_chan = 16, + .n_iso_in_chan = 16, + }, { + .name = "pearl-mm-p", + .io_range = 0x02, + .n_subdevs = 1, + .n_iso_out_chan = 16, + }, { + .name = "ir104-pbf", + .io_range = 0x08, + .is_ir104 = 1, + .has_readback = 1, + .n_iso_out_chan = 20, + .n_iso_in_chan = 20, + }, +}; + +static int pcl730_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long reg = (unsigned long)s->private; + unsigned int mask; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0x00ff) + outb(s->state & 0xff, dev->iobase + reg); + if ((mask & 0xff00) && (s->n_chan > 8)) + outb((s->state >> 8) & 0xff, dev->iobase + reg + 1); + if ((mask & 0xff0000) && (s->n_chan > 16)) + outb((s->state >> 16) & 0xff, dev->iobase + reg + 2); + if ((mask & 0xff000000) && (s->n_chan > 24)) + outb((s->state >> 24) & 0xff, dev->iobase + reg + 3); + } + + data[1] = s->state; + + return insn->n; +} + +static unsigned int pcl730_get_bits(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long reg = (unsigned long)s->private; + unsigned int val; + + val = inb(dev->iobase + reg); + if (s->n_chan > 8) + val |= (inb(dev->iobase + reg + 1) << 8); + if (s->n_chan > 16) + val |= (inb(dev->iobase + reg + 2) << 16); + if (s->n_chan > 24) + val |= (inb(dev->iobase + reg + 3) << 24); + + return val; +} + +static int pcl730_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = pcl730_get_bits(dev, s); + + return insn->n; +} + +static int pcl730_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + const struct pcl730_board *board = dev->board_ptr; + struct comedi_subdevice *s; + int subdev; + int ret; + + ret = comedi_request_region(dev, it->options[0], board->io_range); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, board->n_subdevs); + if (ret) + return ret; + + subdev = 0; + + if (board->n_iso_out_chan) { + /* Isolated Digital Outputs */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->n_iso_out_chan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl730_do_insn_bits; + s->private = (void *)0; + + /* get the initial state if supported */ + if (board->has_readback) + s->state = pcl730_get_bits(dev, s); + } + + if (board->n_iso_in_chan) { + /* Isolated Digital Inputs */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = board->n_iso_in_chan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl730_di_insn_bits; + s->private = board->is_ir104 ? (void *)4 : + board->is_acl7225b ? (void *)2 : + board->is_pcl725 ? (void *)1 : (void *)0; + } + + if (board->has_ttl_io) { + /* TTL Digital Outputs */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->n_ttl_chan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl730_do_insn_bits; + s->private = (void *)2; + + /* TTL Digital Inputs */ + s = &dev->subdevices[subdev++]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = board->n_ttl_chan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl730_di_insn_bits; + s->private = (void *)2; + } + + return 0; +} + +static struct comedi_driver pcl730_driver = { + .driver_name = "pcl730", + .module = THIS_MODULE, + .attach = pcl730_attach, + .detach = comedi_legacy_detach, + .board_name = &pcl730_boards[0].name, + .num_names = ARRAY_SIZE(pcl730_boards), + .offset = sizeof(struct pcl730_board), +}; +module_comedi_driver(pcl730_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl812.c b/drivers/comedi/drivers/pcl812.c new file mode 100644 index 000000000..70dbc129f --- /dev/null +++ b/drivers/comedi/drivers/pcl812.c @@ -0,0 +1,1334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * comedi/drivers/pcl812.c + * + * Author: Michal Dobes <dobes@tesnet.cz> + * + * hardware driver for Advantech cards + * card: PCL-812, PCL-812PG, PCL-813, PCL-813B + * driver: pcl812, pcl812pg, pcl813, pcl813b + * and for ADlink cards + * card: ACL-8112DG, ACL-8112HG, ACL-8112PG, ACL-8113, ACL-8216 + * driver: acl8112dg, acl8112hg, acl8112pg, acl8113, acl8216 + * and for ICP DAS cards + * card: ISO-813, A-821PGH, A-821PGL, A-821PGL-NDA, A-822PGH, A-822PGL, + * driver: iso813, a821pgh, a-821pgl, a-821pglnda, a822pgh, a822pgl, + * card: A-823PGH, A-823PGL, A-826PG + * driver: a823pgh, a823pgl, a826pg + */ + +/* + * Driver: pcl812 + * Description: Advantech PCL-812/PG, PCL-813/B, + * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216, + * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG, + * ICP DAS ISO-813 + * Author: Michal Dobes <dobes@tesnet.cz> + * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg), + * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg), + * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216), + * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl), + * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl), + * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg) + * Updated: Mon, 06 Aug 2007 12:03:15 +0100 + * Status: works (I hope. My board fire up under my hands + * and I cann't test all features.) + * + * This driver supports insn and cmd interfaces. Some boards support only insn + * because their hardware don't allow more (PCL-813/B, ACL-8113, ISO-813). + * Data transfer over DMA is supported only when you measure only one + * channel, this is too hardware limitation of these boards. + * + * Options for PCL-812: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0=trigger source is internal 8253 with 2MHz clock + * 1=trigger source is external + * [4] - 0=A/D input range is +/-10V + * 1=A/D input range is +/-5V + * 2=A/D input range is +/-2.5V + * 3=A/D input range is +/-1.25V + * 4=A/D input range is +/-0.625V + * 5=A/D input range is +/-0.3125V + * [5] - 0=D/A outputs 0-5V (internal reference -5V) + * 1=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * + * Options for PCL-812PG, ACL-8112PG: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0=trigger source is internal 8253 with 2MHz clock + * 1=trigger source is external + * [4] - 0=A/D have max +/-5V input + * 1=A/D have max +/-10V input + * [5] - 0=D/A outputs 0-5V (internal reference -5V) + * 1=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * + * Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0=trigger source is internal 8253 with 2MHz clock + * 1=trigger source is external + * [4] - 0=A/D channels are S.E. + * 1=A/D channels are DIFF + * [5] - 0=D/A outputs 0-5V (internal reference -5V) + * 1=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * + * Options for A-821PGL/PGH: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - 0=A/D channels are S.E. + * 1=A/D channels are DIFF + * [3] - 0=D/A output 0-5V (internal reference -5V) + * 1=D/A output 0-10V (internal reference -10V) + * + * Options for A-821PGL-NDA: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - 0=A/D channels are S.E. + * 1=A/D channels are DIFF + * + * Options for PCL-813: + * [0] - IO Base + * + * Options for PCL-813B: + * [0] - IO Base + * [1] - 0= bipolar inputs + * 1= unipolar inputs + * + * Options for ACL-8113, ISO-813: + * [0] - IO Base + * [1] - 0= 10V bipolar inputs + * 1= 10V unipolar inputs + * 2= 20V bipolar inputs + * 3= 20V unipolar inputs + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/gfp.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> +#include <linux/comedi/comedi_isadma.h> + +/* + * 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 BIT(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 BIT(5) +#define PCL812_RANGE_REG 0x09 +#define PCL812_MUX_REG 0x0a +#define PCL812_MUX_CHAN(x) ((x) << 0) +#define PCL812_MUX_CS0 BIT(4) +#define PCL812_MUX_CS1 BIT(5) +#define PCL812_CTRL_REG 0x0b +#define PCL812_CTRL_TRIG(x) (((x) & 0x7) << 0) +#define PCL812_CTRL_DISABLE_TRIG PCL812_CTRL_TRIG(0) +#define PCL812_CTRL_SOFT_TRIG PCL812_CTRL_TRIG(1) +#define PCL812_CTRL_PACER_DMA_TRIG PCL812_CTRL_TRIG(2) +#define PCL812_CTRL_PACER_EOC_TRIG PCL812_CTRL_TRIG(6) +#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) + } +}; + +enum pcl812_boardtype { + BOARD_PCL812PG = 0, /* and ACL-8112PG */ + BOARD_PCL813B = 1, + BOARD_PCL812 = 2, + BOARD_PCL813 = 3, + BOARD_ISO813 = 5, + BOARD_ACL8113 = 6, + BOARD_ACL8112 = 7, /* ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH */ + BOARD_ACL8216 = 8, /* and ICP DAS A-826PG */ + BOARD_A821 = 9, /* PGH, PGL, PGL/NDA versions */ +}; + +struct pcl812_board { + const char *name; + enum pcl812_boardtype board_type; + int n_aichan; + int n_aochan; + unsigned int ai_ns_min; + const struct comedi_lrange *rangelist_ai; + unsigned int irq_bits; + 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 = BOARD_PCL812, + .n_aichan = 16, + .n_aochan = 2, + .ai_ns_min = 33000, + .rangelist_ai = &range_bipolar10, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "pcl812pg", + .board_type = BOARD_PCL812PG, + .n_aichan = 16, + .n_aochan = 2, + .ai_ns_min = 33000, + .rangelist_ai = &range_pcl812pg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "acl8112pg", + .board_type = BOARD_PCL812PG, + .n_aichan = 16, + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_pcl812pg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "acl8112dg", + .board_type = BOARD_ACL8112, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_acl8112dg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_mpc508_mux = 1, + .has_dio = 1, + }, { + .name = "acl8112hg", + .board_type = BOARD_ACL8112, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_acl8112hg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_mpc508_mux = 1, + .has_dio = 1, + }, { + .name = "a821pgl", + .board_type = BOARD_A821, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 1, + .ai_ns_min = 10000, + .rangelist_ai = &range_pcl813b_ai, + .irq_bits = 0x000c, + .has_dio = 1, + }, { + .name = "a821pglnda", + .board_type = BOARD_A821, + .n_aichan = 16, /* 8 differential */ + .ai_ns_min = 10000, + .rangelist_ai = &range_pcl813b_ai, + .irq_bits = 0x000c, + }, { + .name = "a821pgh", + .board_type = BOARD_A821, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 1, + .ai_ns_min = 10000, + .rangelist_ai = &range_a821pgh_ai, + .irq_bits = 0x000c, + .has_dio = 1, + }, { + .name = "a822pgl", + .board_type = BOARD_ACL8112, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_acl8112dg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "a822pgh", + .board_type = BOARD_ACL8112, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_acl8112hg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "a823pgl", + .board_type = BOARD_ACL8112, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 8000, + .rangelist_ai = &range_acl8112dg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "a823pgh", + .board_type = BOARD_ACL8112, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 8000, + .rangelist_ai = &range_acl8112hg_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_dio = 1, + }, { + .name = "pcl813", + .board_type = BOARD_PCL813, + .n_aichan = 32, + .rangelist_ai = &range_pcl813b_ai, + }, { + .name = "pcl813b", + .board_type = BOARD_PCL813B, + .n_aichan = 32, + .rangelist_ai = &range_pcl813b_ai, + }, { + .name = "acl8113", + .board_type = BOARD_ACL8113, + .n_aichan = 32, + .rangelist_ai = &range_acl8113_1_ai, + }, { + .name = "iso813", + .board_type = BOARD_ISO813, + .n_aichan = 32, + .rangelist_ai = &range_iso813_1_ai, + }, { + .name = "acl8216", + .board_type = BOARD_ACL8216, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_pcl813b2_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_16bit_ai = 1, + .has_mpc508_mux = 1, + .has_dio = 1, + }, { + .name = "a826pg", + .board_type = BOARD_ACL8216, + .n_aichan = 16, /* 8 differential */ + .n_aochan = 2, + .ai_ns_min = 10000, + .rangelist_ai = &range_pcl813b2_ai, + .irq_bits = 0xdcfc, + .has_dma = 1, + .has_16bit_ai = 1, + .has_dio = 1, + }, +}; + +struct pcl812_private { + struct comedi_isadma *dma; + unsigned char range_correction; /* =1 we must add 1 to range number */ + unsigned int last_ai_chanspec; + unsigned char mode_reg_int; /* stored INT number for some cards */ + unsigned int ai_poll_ptr; /* how many samples transfer poll */ + unsigned int max_812_ai_mode0_rangewait; /* settling time for gain */ + unsigned int use_diff:1; + unsigned int use_mpc508:1; + unsigned int use_ext_trg:1; + unsigned int ai_dma:1; + unsigned int ai_eos:1; +}; + +static void pcl812_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int unread_samples) +{ + struct pcl812_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int bytes; + unsigned int max_samples; + unsigned int nsamples; + + comedi_isadma_disable(dma->chan); + + /* if using EOS, adapt DMA buffer to one scan */ + bytes = devpriv->ai_eos ? comedi_bytes_per_scan(s) : desc->maxsize; + max_samples = comedi_bytes_to_samples(s, bytes); + + /* + * Determine dma size based on the buffer size plus the number of + * unread samples and the number of samples remaining in the command. + */ + nsamples = comedi_nsamples_left(s, max_samples + unread_samples); + if (nsamples > unread_samples) { + nsamples -= unread_samples; + desc->size = comedi_samples_to_bytes(s, nsamples); + comedi_isadma_program(desc); + } +} + +static void pcl812_ai_set_chan_range(struct comedi_device *dev, + unsigned int chanspec, char wait) +{ + struct pcl812_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int mux = 0; + + if (chanspec == devpriv->last_ai_chanspec) + return; + + devpriv->last_ai_chanspec = chanspec; + + if (devpriv->use_mpc508) { + if (devpriv->use_diff) { + mux |= PCL812_MUX_CS0 | PCL812_MUX_CS1; + } else { + if (chan < 8) + mux |= PCL812_MUX_CS0; + else + mux |= PCL812_MUX_CS1; + } + } + + outb(mux | PCL812_MUX_CHAN(chan), dev->iobase + PCL812_MUX_REG); + outb(range + devpriv->range_correction, dev->iobase + PCL812_RANGE_REG); + + if (wait) + /* + * XXX this depends on selected range and can be very long for + * some high gain ranges! + */ + udelay(devpriv->max_812_ai_mode0_rangewait); +} + +static void pcl812_ai_clear_eoc(struct comedi_device *dev) +{ + /* writing any value clears the interrupt request */ + outb(0, dev->iobase + PCL812_STATUS_REG); +} + +static void pcl812_ai_soft_trig(struct comedi_device *dev) +{ + /* writing any value triggers a software conversion */ + outb(255, dev->iobase + PCL812_SOFTTRIG_REG); +} + +static unsigned int pcl812_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + val = inb(dev->iobase + PCL812_AI_MSB_REG) << 8; + val |= inb(dev->iobase + PCL812_AI_LSB_REG); + + return val & s->maxdata; +} + +static int pcl812_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + if (s->maxdata > 0x0fff) { + status = inb(dev->iobase + PCL812_STATUS_REG); + if ((status & PCL812_STATUS_DRDY) == 0) + return 0; + } else { + status = inb(dev->iobase + PCL812_AI_MSB_REG); + if ((status & PCL812_AI_MSB_DRDY) == 0) + return 0; + } + return -EBUSY; +} + +static int pcl812_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct pcl812_board *board = dev->board_ptr; + struct pcl812_private *devpriv = dev->private; + int err = 0; + unsigned int flags; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + + if (devpriv->use_ext_trg) + flags = TRIG_EXT; + else + flags = TRIG_TIMER; + err |= comedi_check_trigger_src(&cmd->convert_src, flags); + + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ai_ns_min); + } else { /* TRIG_EXT */ + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + return 0; +} + +static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int ctrl = 0; + unsigned int i; + + pcl812_ai_set_chan_range(dev, cmd->chanlist[0], 1); + + if (dma) { /* check if we can use DMA transfer */ + devpriv->ai_dma = 1; + for (i = 1; i < cmd->chanlist_len; i++) + if (cmd->chanlist[0] != cmd->chanlist[i]) { + /* we cann't use DMA :-( */ + devpriv->ai_dma = 0; + break; + } + } else { + devpriv->ai_dma = 0; + } + + devpriv->ai_poll_ptr = 0; + + /* don't we want wake up every scan? */ + if (cmd->flags & CMDF_WAKE_EOS) { + devpriv->ai_eos = 1; + + /* DMA is useless for this situation */ + if (cmd->chanlist_len == 1) + devpriv->ai_dma = 0; + } + + if (devpriv->ai_dma) { + /* setup and enable dma for the first buffer */ + dma->cur_dma = 0; + pcl812_ai_setup_dma(dev, s, 0); + } + + switch (cmd->convert_src) { + case TRIG_TIMER: + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + break; + } + + if (devpriv->ai_dma) + ctrl |= PCL812_CTRL_PACER_DMA_TRIG; + else + ctrl |= PCL812_CTRL_PACER_EOC_TRIG; + outb(devpriv->mode_reg_int | ctrl, dev->iobase + PCL812_CTRL_REG); + + return 0; +} + +static bool pcl812_ai_next_chan(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + s->async->events |= COMEDI_CB_EOA; + return false; + } + + return true; +} + +static void pcl812_handle_eoc(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan = s->async->cur_chan; + unsigned int next_chan; + unsigned short val; + + if (pcl812_ai_eoc(dev, s, NULL, 0)) { + dev_dbg(dev->class_dev, "A/D cmd IRQ without DRDY!\n"); + s->async->events |= COMEDI_CB_ERROR; + return; + } + + val = pcl812_ai_get_sample(dev, s); + comedi_buf_write_samples(s, &val, 1); + + /* Set up next channel. Added by abbotti 2010-01-20, but untested. */ + next_chan = s->async->cur_chan; + if (cmd->chanlist[chan] != cmd->chanlist[next_chan]) + pcl812_ai_set_chan_range(dev, cmd->chanlist[next_chan], 0); + + pcl812_ai_next_chan(dev, s); +} + +static void transfer_from_dma_buf(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *ptr, + unsigned int bufptr, unsigned int len) +{ + unsigned int i; + unsigned short val; + + for (i = len; i; i--) { + val = ptr[bufptr++]; + comedi_buf_write_samples(s, &val, 1); + + if (!pcl812_ai_next_chan(dev, s)) + break; + } +} + +static void pcl812_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int nsamples; + int bufptr; + + nsamples = comedi_bytes_to_samples(s, desc->size) - + devpriv->ai_poll_ptr; + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + /* restart dma with the next buffer */ + dma->cur_dma = 1 - dma->cur_dma; + pcl812_ai_setup_dma(dev, s, nsamples); + + transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples); +} + +static irqreturn_t pcl812_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct pcl812_private *devpriv = dev->private; + + if (!dev->attached) { + pcl812_ai_clear_eoc(dev); + return IRQ_HANDLED; + } + + if (devpriv->ai_dma) + pcl812_handle_dma(dev, s); + else + pcl812_handle_eoc(dev, s); + + pcl812_ai_clear_eoc(dev); + + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc; + unsigned long flags; + unsigned int poll; + int ret; + + /* poll is valid only for DMA transfer */ + if (!devpriv->ai_dma) + return 0; + + spin_lock_irqsave(&dev->spinlock, flags); + + poll = comedi_isadma_poll(dma); + poll = comedi_bytes_to_samples(s, poll); + if (poll > devpriv->ai_poll_ptr) { + desc = &dma->desc[dma->cur_dma]; + transfer_from_dma_buf(dev, s, desc->virt_addr, + devpriv->ai_poll_ptr, + poll - devpriv->ai_poll_ptr); + /* new buffer position */ + devpriv->ai_poll_ptr = poll; + + ret = comedi_buf_n_bytes_ready(s); + } else { + /* no new samples */ + ret = 0; + } + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return ret; +} + +static int pcl812_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl812_private *devpriv = dev->private; + + if (devpriv->ai_dma) + comedi_isadma_disable(devpriv->dma->chan); + + outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG, + dev->iobase + PCL812_CTRL_REG); + comedi_8254_pacer_enable(dev->pacer, 1, 2, false); + pcl812_ai_clear_eoc(dev); + return 0; +} + +static int pcl812_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcl812_private *devpriv = dev->private; + int ret = 0; + int i; + + outb(devpriv->mode_reg_int | PCL812_CTRL_SOFT_TRIG, + dev->iobase + PCL812_CTRL_REG); + + pcl812_ai_set_chan_range(dev, insn->chanspec, 1); + + for (i = 0; i < insn->n; i++) { + pcl812_ai_clear_eoc(dev); + pcl812_ai_soft_trig(dev); + + ret = comedi_timeout(dev, s, insn, pcl812_ai_eoc, 0); + if (ret) + break; + + data[i] = pcl812_ai_get_sample(dev, s); + } + outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG, + dev->iobase + PCL812_CTRL_REG); + pcl812_ai_clear_eoc(dev); + + return ret ? ret : insn->n; +} + +static int pcl812_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outb(val & 0xff, dev->iobase + PCL812_AO_LSB_REG(chan)); + outb((val >> 8) & 0x0f, dev->iobase + PCL812_AO_MSB_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pcl812_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + PCL812_DI_LSB_REG) | + (inb(dev->iobase + PCL812_DI_MSB_REG) << 8); + + return insn->n; +} + +static int pcl812_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) { + outb(s->state & 0xff, dev->iobase + PCL812_DO_LSB_REG); + outb((s->state >> 8), dev->iobase + PCL812_DO_MSB_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static void pcl812_reset(struct comedi_device *dev) +{ + const struct pcl812_board *board = dev->board_ptr; + struct pcl812_private *devpriv = dev->private; + unsigned int chan; + + /* disable analog input trigger */ + outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG, + dev->iobase + PCL812_CTRL_REG); + pcl812_ai_clear_eoc(dev); + + /* + * Invalidate last_ai_chanspec then set analog input to + * known channel/range. + */ + devpriv->last_ai_chanspec = CR_PACK(16, 0, 0); + pcl812_ai_set_chan_range(dev, CR_PACK(0, 0, 0), 0); + + /* set analog output channels to 0V */ + for (chan = 0; chan < board->n_aochan; chan++) { + outb(0, dev->iobase + PCL812_AO_LSB_REG(chan)); + outb(0, dev->iobase + PCL812_AO_MSB_REG(chan)); + } + + /* set all digital outputs low */ + if (board->has_dio) { + outb(0, dev->iobase + PCL812_DO_MSB_REG); + outb(0, dev->iobase + PCL812_DO_LSB_REG); + } +} + +static void pcl812_set_ai_range_table(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_devconfig *it) +{ + const struct pcl812_board *board = dev->board_ptr; + struct pcl812_private *devpriv = dev->private; + + switch (board->board_type) { + case BOARD_PCL812PG: + if (it->options[4] == 1) + s->range_table = &range_pcl812pg2_ai; + else + s->range_table = board->rangelist_ai; + break; + case BOARD_PCL812: + 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 BOARD_PCL813B: + if (it->options[1] == 1) + s->range_table = &range_pcl813b2_ai; + else + s->range_table = board->rangelist_ai; + break; + case BOARD_ISO813: + 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 BOARD_ACL8113: + 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; + default: + s->range_table = board->rangelist_ai; + break; + } +} + +static void pcl812_alloc_dma(struct comedi_device *dev, unsigned int dma_chan) +{ + struct pcl812_private *devpriv = dev->private; + + /* only DMA channels 3 and 1 are valid */ + if (!(dma_chan == 3 || dma_chan == 1)) + return; + + /* DMA uses two 8K buffers */ + devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, + PAGE_SIZE * 2, COMEDI_ISADMA_READ); +} + +static void pcl812_free_dma(struct comedi_device *dev) +{ + struct pcl812_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} + +static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl812_board *board = dev->board_ptr; + struct pcl812_private *devpriv; + struct comedi_subdevice *s; + int n_subdevices; + int subdev; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + if (board->irq_bits) { + dev->pacer = comedi_8254_init(dev->iobase + PCL812_TIMER_BASE, + I8254_OSC_BASE_2MHZ, + I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + if ((1 << it->options[1]) & board->irq_bits) { + ret = request_irq(it->options[1], pcl812_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + } + + /* we need an IRQ to do DMA on channel 3 or 1 */ + if (dev->irq && board->has_dma) + pcl812_alloc_dma(dev, it->options[2]); + + /* differential analog inputs? */ + switch (board->board_type) { + case BOARD_A821: + if (it->options[2] == 1) + devpriv->use_diff = 1; + break; + case BOARD_ACL8112: + case BOARD_ACL8216: + if (it->options[4] == 1) + devpriv->use_diff = 1; + break; + default: + 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; + switch (board->board_type) { + case BOARD_A821: + if (it->options[3] == 1) + s->range_table = &range_unipolar10; + else + s->range_table = &range_unipolar5; + break; + case BOARD_PCL812: + case BOARD_ACL8112: + case BOARD_PCL812PG: + case BOARD_ACL8216: + switch (it->options[5]) { + case 1: + s->range_table = &range_unipolar10; + break; + case 2: + s->range_table = &range_unknown; + break; + default: + s->range_table = &range_unipolar5; + break; + } + break; + default: + s->range_table = &range_unipolar5; + break; + } + s->insn_write = pcl812_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + subdev++; + } + + if (board->has_dio) { + /* Digital Input subdevice */ + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl812_di_insn_bits; + subdev++; + + /* Digital Output subdevice */ + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl812_do_insn_bits; + subdev++; + } + + switch (board->board_type) { + case BOARD_ACL8216: + case BOARD_PCL812PG: + case BOARD_PCL812: + case BOARD_ACL8112: + devpriv->max_812_ai_mode0_rangewait = 1; + if (it->options[3] > 0) + /* we use external trigger */ + devpriv->use_ext_trg = 1; + break; + case BOARD_A821: + devpriv->max_812_ai_mode0_rangewait = 1; + devpriv->mode_reg_int = (dev->irq << 4) & 0xf0; + break; + case BOARD_PCL813B: + case BOARD_PCL813: + case BOARD_ISO813: + case BOARD_ACL8113: + /* maybe there must by greatest timeout */ + devpriv->max_812_ai_mode0_rangewait = 5; + break; + } + + pcl812_reset(dev); + + return 0; +} + +static void pcl812_detach(struct comedi_device *dev) +{ + pcl812_free_dma(dev); + comedi_legacy_detach(dev); +} + +static struct comedi_driver pcl812_driver = { + .driver_name = "pcl812", + .module = THIS_MODULE, + .attach = pcl812_attach, + .detach = pcl812_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct pcl812_board), +}; +module_comedi_driver(pcl812_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl816.c b/drivers/comedi/drivers/pcl816.c new file mode 100644 index 000000000..a5e5320be --- /dev/null +++ b/drivers/comedi/drivers/pcl816.c @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pcl816.c + * Comedi driver for Advantech PCL-816 cards + * + * Author: Juan Grigera <juan@grigera.com.ar> + * based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812 + */ + +/* + * Driver: pcl816 + * Description: Advantech PCL-816 cards, PCL-814 + * Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) + * Author: Juan Grigera <juan@grigera.com.ar> + * Status: works + * Updated: Tue, 2 Apr 2002 23:15:21 -0800 + * + * PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. + * Differences are at resolution (16 vs 12 bits). + * + * The driver support AI command mode, other subdevices not written. + * + * Analog output and digital input and output are not supported. + * + * Configuration Options: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0, 10=10MHz clock for 8254 + * 1= 1MHz clock for 8254 + */ + +#include <linux/module.h> +#include <linux/gfp.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> +#include <linux/comedi/comedi_isadma.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_SOFT_TRIG BIT(0) +#define PCL816_CTRL_PACER_TRIG BIT(1) +#define PCL816_CTRL_EXT_TRIG BIT(2) +#define PCL816_CTRL_POE BIT(3) +#define PCL816_CTRL_DMAEN BIT(4) +#define PCL816_CTRL_INTEN BIT(5) +#define PCL816_CTRL_DMASRC_SLOT(x) (((x) & 0x3) << 6) +#define PCL816_STATUS_REG 0x0d +#define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0) +#define PCL816_STATUS_INTSRC_SLOT(x) (((x) & 0x3) << 4) +#define PCL816_STATUS_INTSRC_DMA PCL816_STATUS_INTSRC_SLOT(3) +#define PCL816_STATUS_INTSRC_MASK PCL816_STATUS_INTSRC_SLOT(3) +#define PCL816_STATUS_INTACT BIT(6) +#define PCL816_STATUS_DRDY BIT(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 ai_chanlist; +}; + +static const struct pcl816_board boardtypes[] = { + { + .name = "pcl816", + .ai_maxdata = 0xffff, + .ai_chanlist = 1024, + }, { + .name = "pcl814b", + .ai_maxdata = 0x3fff, + .ai_chanlist = 1024, + }, +}; + +struct pcl816_private { + struct comedi_isadma *dma; + unsigned int ai_poll_ptr; /* how many sampes transfer poll */ + unsigned int ai_cmd_running:1; + unsigned int ai_cmd_canceled:1; +}; + +static void pcl816_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int unread_samples) +{ + struct pcl816_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize); + unsigned int nsamples; + + comedi_isadma_disable(dma->chan); + + /* + * Determine dma size based on the buffer maxsize plus the number of + * unread samples and the number of samples remaining in the command. + */ + nsamples = comedi_nsamples_left(s, max_samples + unread_samples); + if (nsamples > unread_samples) { + nsamples -= unread_samples; + desc->size = comedi_samples_to_bytes(s, nsamples); + comedi_isadma_program(desc); + } +} + +static void pcl816_ai_set_chan_range(struct comedi_device *dev, + unsigned int chan, + unsigned int range) +{ + outb(chan, dev->iobase + PCL816_MUX_REG); + outb(range, dev->iobase + PCL816_RANGE_REG); +} + +static void pcl816_ai_set_chan_scan(struct comedi_device *dev, + unsigned int first_chan, + unsigned int last_chan) +{ + outb(PCL816_MUX_SCAN(first_chan, last_chan), + dev->iobase + PCL816_MUX_REG); +} + +static void pcl816_ai_setup_chanlist(struct comedi_device *dev, + unsigned int *chanlist, + unsigned int seglen) +{ + unsigned int first_chan = CR_CHAN(chanlist[0]); + unsigned int last_chan; + unsigned int range; + unsigned int i; + + /* store range list to card */ + for (i = 0; i < seglen; i++) { + last_chan = CR_CHAN(chanlist[i]); + range = CR_RANGE(chanlist[i]); + + pcl816_ai_set_chan_range(dev, last_chan, range); + } + + udelay(1); + + pcl816_ai_set_chan_scan(dev, first_chan, last_chan); +} + +static void pcl816_ai_clear_eoc(struct comedi_device *dev) +{ + /* writing any value clears the interrupt request */ + outb(0, dev->iobase + PCL816_CLRINT_REG); +} + +static void pcl816_ai_soft_trig(struct comedi_device *dev) +{ + /* writing any value triggers a software conversion */ + outb(0, dev->iobase + PCL816_AI_LSB_REG); +} + +static unsigned int pcl816_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8; + val |= inb(dev->iobase + PCL816_AI_LSB_REG); + + return val & s->maxdata; +} + +static int pcl816_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + PCL816_STATUS_REG); + if ((status & PCL816_STATUS_DRDY) == 0) + return 0; + return -EBUSY; +} + +static bool pcl816_ai_next_chan(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + s->async->events |= COMEDI_CB_EOA; + return false; + } + + return true; +} + +static void transfer_from_dma_buf(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short *ptr, + unsigned int bufptr, unsigned int len) +{ + unsigned short val; + int i; + + for (i = 0; i < len; i++) { + val = ptr[bufptr++]; + comedi_buf_write_samples(s, &val, 1); + + if (!pcl816_ai_next_chan(dev, s)) + return; + } +} + +static irqreturn_t pcl816_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct pcl816_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int nsamples; + unsigned int bufptr; + + if (!dev->attached || !devpriv->ai_cmd_running) { + pcl816_ai_clear_eoc(dev); + return IRQ_HANDLED; + } + + if (devpriv->ai_cmd_canceled) { + devpriv->ai_cmd_canceled = 0; + pcl816_ai_clear_eoc(dev); + return IRQ_HANDLED; + } + + nsamples = comedi_bytes_to_samples(s, desc->size) - + devpriv->ai_poll_ptr; + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + /* restart dma with the next buffer */ + dma->cur_dma = 1 - dma->cur_dma; + pcl816_ai_setup_dma(dev, s, nsamples); + + transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples); + + pcl816_ai_clear_eoc(dev); + + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, + unsigned int chanlen) +{ + unsigned int chansegment[16]; + unsigned int i, nowmustbechan, seglen; + + /* correct channel and range number check itself comedi/range.c */ + if (chanlen < 1) { + dev_err(dev->class_dev, "range/channel list is empty!\n"); + return 0; + } + + if (chanlen > 1) { + /* first channel is every time ok */ + chansegment[0] = chanlist[0]; + for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { + /* we detect loop, this must by finish */ + if (chanlist[0] == chanlist[i]) + break; + nowmustbechan = + (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; + if (nowmustbechan != CR_CHAN(chanlist[i])) { + /* channel list isn't continuous :-( */ + dev_dbg(dev->class_dev, + "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", + i, CR_CHAN(chanlist[i]), nowmustbechan, + CR_CHAN(chanlist[0])); + return 0; + } + /* well, this is next correct channel in list */ + chansegment[i] = chanlist[i]; + } + + /* check whole chanlist */ + for (i = 0; i < chanlen; i++) { + if (chanlist[i] != chansegment[i % seglen]) { + dev_dbg(dev->class_dev, + "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", + i, CR_CHAN(chansegment[i]), + CR_RANGE(chansegment[i]), + CR_AREF(chansegment[i]), + CR_CHAN(chanlist[i % seglen]), + CR_RANGE(chanlist[i % seglen]), + CR_AREF(chansegment[i % seglen])); + return 0; /* chan/gain list is strange */ + } + } + } else { + seglen = 1; + } + + return seglen; /* we can serve this with MUX logic */ +} + +static int pcl816_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_EXT | TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); + else /* TRIG_EXT */ + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + if (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* step 5: complain about special chanlist considerations */ + + if (cmd->chanlist) { + if (!check_channel_list(dev, s, cmd->chanlist, + cmd->chanlist_len)) + return 5; /* incorrect channels list */ + } + + return 0; +} + +static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int ctrl; + unsigned int seglen; + + if (devpriv->ai_cmd_running) + return -EBUSY; + + seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); + if (seglen < 1) + return -EINVAL; + pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen); + udelay(1); + + devpriv->ai_cmd_running = 1; + devpriv->ai_poll_ptr = 0; + devpriv->ai_cmd_canceled = 0; + + /* setup and enable dma for the first buffer */ + dma->cur_dma = 0; + pcl816_ai_setup_dma(dev, s, 0); + + comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY); + comedi_8254_write(dev->pacer, 0, 0x0ff); + udelay(1); + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + + ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | + PCL816_CTRL_DMASRC_SLOT(0); + if (cmd->convert_src == TRIG_TIMER) + ctrl |= PCL816_CTRL_PACER_TRIG; + else /* TRIG_EXT */ + ctrl |= PCL816_CTRL_EXT_TRIG; + + outb(ctrl, dev->iobase + PCL816_CTRL_REG); + outb((dma->chan << 4) | dev->irq, + dev->iobase + PCL816_STATUS_REG); + + return 0; +} + +static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc; + unsigned long flags; + unsigned int poll; + int ret; + + spin_lock_irqsave(&dev->spinlock, flags); + + poll = comedi_isadma_poll(dma); + poll = comedi_bytes_to_samples(s, poll); + if (poll > devpriv->ai_poll_ptr) { + desc = &dma->desc[dma->cur_dma]; + transfer_from_dma_buf(dev, s, desc->virt_addr, + devpriv->ai_poll_ptr, + poll - devpriv->ai_poll_ptr); + /* new buffer position */ + devpriv->ai_poll_ptr = poll; + + comedi_handle_events(dev, s); + + ret = comedi_buf_n_bytes_ready(s); + } else { + /* no new samples */ + ret = 0; + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + return ret; +} + +static int pcl816_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + + if (!devpriv->ai_cmd_running) + return 0; + + outb(0, dev->iobase + PCL816_CTRL_REG); + pcl816_ai_clear_eoc(dev); + + comedi_8254_pacer_enable(dev->pacer, 1, 2, false); + + devpriv->ai_cmd_running = 0; + devpriv->ai_cmd_canceled = 1; + + return 0; +} + +static int pcl816_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int ret = 0; + int i; + + outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG); + + pcl816_ai_set_chan_range(dev, chan, range); + pcl816_ai_set_chan_scan(dev, chan, chan); + + for (i = 0; i < insn->n; i++) { + pcl816_ai_clear_eoc(dev); + pcl816_ai_soft_trig(dev); + + ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0); + if (ret) + break; + + data[i] = pcl816_ai_get_sample(dev, s); + } + outb(0, dev->iobase + PCL816_CTRL_REG); + pcl816_ai_clear_eoc(dev); + + return ret ? ret : insn->n; +} + +static int pcl816_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) | + (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8); + + return insn->n; +} + +static int pcl816_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) { + outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG); + outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static void pcl816_reset(struct comedi_device *dev) +{ + outb(0, dev->iobase + PCL816_CTRL_REG); + pcl816_ai_set_chan_range(dev, 0, 0); + pcl816_ai_clear_eoc(dev); + + /* set all digital outputs low */ + outb(0, dev->iobase + PCL816_DO_DI_LSB_REG); + outb(0, dev->iobase + PCL816_DO_DI_MSB_REG); +} + +static void pcl816_alloc_irq_and_dma(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pcl816_private *devpriv = dev->private; + unsigned int irq_num = it->options[1]; + unsigned int dma_chan = it->options[2]; + + /* only IRQs 2-7 and DMA channels 3 and 1 are valid */ + if (!(irq_num >= 2 && irq_num <= 7) || + !(dma_chan == 3 || dma_chan == 1)) + return; + + if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev)) + return; + + /* DMA uses two 16K buffers */ + devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, + PAGE_SIZE * 4, COMEDI_ISADMA_READ); + if (!devpriv->dma) + free_irq(irq_num, dev); + else + dev->irq = irq_num; +} + +static void pcl816_free_dma(struct comedi_device *dev) +{ + struct pcl816_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} + +static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl816_board *board = dev->board_ptr; + struct pcl816_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + /* an IRQ and DMA are required to support async commands */ + pcl816_alloc_irq_and_dma(dev, it); + + dev->pacer = comedi_8254_init(dev->iobase + PCL816_TIMER_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_CMD_READ | SDF_DIFF; + s->n_chan = 16; + s->maxdata = board->ai_maxdata; + s->range_table = &range_pcl816; + s->insn_read = pcl816_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = board->ai_chanlist; + s->do_cmdtest = pcl816_ai_cmdtest; + s->do_cmd = pcl816_ai_cmd; + s->poll = pcl816_ai_poll; + s->cancel = pcl816_ai_cancel; + } + + /* Piggyback Slot1 subdevice */ + s = &dev->subdevices[1]; + 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 = pcl816_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl816_do_insn_bits; + + pcl816_reset(dev); + + return 0; +} + +static void pcl816_detach(struct comedi_device *dev) +{ + if (dev->private) { + pcl816_ai_cancel(dev, dev->read_subdev); + pcl816_reset(dev); + } + pcl816_free_dma(dev); + comedi_legacy_detach(dev); +} + +static struct comedi_driver pcl816_driver = { + .driver_name = "pcl816", + .module = THIS_MODULE, + .attach = pcl816_attach, + .detach = pcl816_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct pcl816_board), +}; +module_comedi_driver(pcl816_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcl818.c b/drivers/comedi/drivers/pcl818.c new file mode 100644 index 000000000..29e503de8 --- /dev/null +++ b/drivers/comedi/drivers/pcl818.c @@ -0,0 +1,1135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * comedi/drivers/pcl818.c + * + * Driver: pcl818 + * Description: Advantech PCL-818 cards, PCL-718 + * Author: Michal Dobes <dobes@tesnet.cz> + * Devices: [Advantech] PCL-818L (pcl818l), PCL-818H (pcl818h), + * PCL-818HD (pcl818hd), PCL-818HG (pcl818hg), PCL-818 (pcl818), + * PCL-718 (pcl718) + * Status: works + * + * All cards have 16 SE/8 DIFF ADCs, one or two DACs, 16 DI and 16 DO. + * Differences are only at maximal sample speed, range list and FIFO + * support. + * The driver support AI mode 0, 1, 3 other subdevices (AO, DI, DO) support + * only mode 0. If DMA/FIFO/INT are disabled then AI support only mode 0. + * PCL-818HD and PCL-818HG support 1kword FIFO. Driver support this FIFO + * but this code is untested. + * A word or two about DMA. Driver support DMA operations at two ways: + * 1) DMA uses two buffers and after one is filled then is generated + * INT and DMA restart with second buffer. With this mode I'm unable run + * more that 80Ksamples/secs without data dropouts on K6/233. + * 2) DMA uses one buffer and run in autoinit mode and the data are + * from DMA buffer moved on the fly with 2kHz interrupts from RTC. + * This mode is used if the interrupt 8 is available for allocation. + * If not, then first DMA mode is used. With this I can run at + * full speed one card (100ksamples/secs) or two cards with + * 60ksamples/secs each (more is problem on account of ISA limitations). + * To use this mode you must have compiled kernel with disabled + * "Enhanced Real Time Clock Support". + * Maybe you can have problems if you use xntpd or similar. + * If you've data dropouts with DMA mode 2 then: + * a) disable IDE DMA + * b) switch text mode console to fb. + * + * Options for PCL-818L: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0, 10=10MHz clock for 8254 + * 1= 1MHz clock for 8254 + * [4] - 0, 5=A/D input -5V.. +5V + * 1, 10=A/D input -10V..+10V + * [5] - 0, 5=D/A output 0-5V (internal reference -5V) + * 1, 10=D/A output 0-10V (internal reference -10V) + * 2 =D/A output unknown (external reference) + * + * Options for PCL-818, PCL-818H: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0, 10=10MHz clock for 8254 + * 1= 1MHz clock for 8254 + * [4] - 0, 5=D/A output 0-5V (internal reference -5V) + * 1, 10=D/A output 0-10V (internal reference -10V) + * 2 =D/A output unknown (external reference) + * + * Options for PCL-818HD, PCL-818HG: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - DMA/FIFO (-1=use FIFO, 0=disable both FIFO and DMA, + * 1=use DMA ch 1, 3=use DMA ch 3) + * [3] - 0, 10=10MHz clock for 8254 + * 1= 1MHz clock for 8254 + * [4] - 0, 5=D/A output 0-5V (internal reference -5V) + * 1, 10=D/A output 0-10V (internal reference -10V) + * 2 =D/A output unknown (external reference) + * + * Options for PCL-718: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0, 10=10MHz clock for 8254 + * 1= 1MHz clock for 8254 + * [4] - 0=A/D Range is +/-10V + * 1= +/-5V + * 2= +/-2.5V + * 3= +/-1V + * 4= +/-0.5V + * 5= user defined bipolar + * 6= 0-10V + * 7= 0-5V + * 8= 0-2V + * 9= 0-1V + * 10= user defined unipolar + * [5] - 0, 5=D/A outputs 0-5V (internal reference -5V) + * 1, 10=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * [6] - 0, 60=max 60kHz A/D sampling + * 1,100=max 100kHz A/D sampling (PCL-718 with Option 001 installed) + * + */ + +#include <linux/module.h> +#include <linux/gfp.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8254.h> +#include <linux/comedi/comedi_isadma.h> + +/* + * 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 BIT(4) +#define PCL818_STATUS_MUX BIT(5) +#define PCL818_STATUS_UNI BIT(6) +#define PCL818_STATUS_EOC BIT(7) +#define PCL818_CTRL_REG 0x09 +#define PCL818_CTRL_TRIG(x) (((x) & 0x3) << 0) +#define PCL818_CTRL_DISABLE_TRIG PCL818_CTRL_TRIG(0) +#define PCL818_CTRL_SOFT_TRIG PCL818_CTRL_TRIG(1) +#define PCL818_CTRL_EXT_TRIG PCL818_CTRL_TRIG(2) +#define PCL818_CTRL_PACER_TRIG PCL818_CTRL_TRIG(3) +#define PCL818_CTRL_DMAE BIT(2) +#define PCL818_CTRL_IRQ(x) ((x) << 4) +#define PCL818_CTRL_INTE BIT(7) +#define PCL818_CNTENABLE_REG 0x0a +#define PCL818_CNTENABLE_PACER_TRIG0 BIT(0) +#define PCL818_CNTENABLE_CNT0_INT_CLK BIT(1) /* 0=ext clk */ +#define PCL818_DO_DI_MSB_REG 0x0b +#define PCL818_TIMER_BASE 0x0c + +/* W: fifo enable/disable */ +#define PCL818_FI_ENABLE 6 +/* W: fifo interrupt clear */ +#define PCL818_FI_INTCLR 20 +/* W: fifo interrupt clear */ +#define PCL818_FI_FLUSH 25 +/* R: fifo status */ +#define PCL818_FI_STATUS 25 +/* R: one record from FIFO */ +#define PCL818_FI_DATALO 23 +#define PCL818_FI_DATAHI 24 + +#define MAGIC_DMA_WORD 0x5a5a + +static const struct comedi_lrange range_pcl818h_ai = { + 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + BIP_RANGE(10) + } +}; + +static const struct comedi_lrange range_pcl818hg_ai = { + 10, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_pcl818l_l_ai = { + 4, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625) + } +}; + +static const struct comedi_lrange range_pcl818l_h_ai = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; + +static const struct comedi_lrange range718_bipolar1 = { + 1, { + BIP_RANGE(1) + } +}; + +static const struct comedi_lrange range718_bipolar0_5 = { + 1, { + BIP_RANGE(0.5) + } +}; + +static const struct comedi_lrange range718_unipolar2 = { + 1, { + UNI_RANGE(2) + } +}; + +static const struct comedi_lrange range718_unipolar1 = { + 1, { + BIP_RANGE(1) + } +}; + +struct pcl818_board { + const char *name; + unsigned int ns_min; + int n_aochan; + const struct comedi_lrange *ai_range_type; + unsigned int has_dma:1; + unsigned int has_fifo:1; + unsigned int is_818:1; +}; + +static const struct pcl818_board boardtypes[] = { + { + .name = "pcl818l", + .ns_min = 25000, + .n_aochan = 1, + .ai_range_type = &range_pcl818l_l_ai, + .has_dma = 1, + .is_818 = 1, + }, { + .name = "pcl818h", + .ns_min = 10000, + .n_aochan = 1, + .ai_range_type = &range_pcl818h_ai, + .has_dma = 1, + .is_818 = 1, + }, { + .name = "pcl818hd", + .ns_min = 10000, + .n_aochan = 1, + .ai_range_type = &range_pcl818h_ai, + .has_dma = 1, + .has_fifo = 1, + .is_818 = 1, + }, { + .name = "pcl818hg", + .ns_min = 10000, + .n_aochan = 1, + .ai_range_type = &range_pcl818hg_ai, + .has_dma = 1, + .has_fifo = 1, + .is_818 = 1, + }, { + .name = "pcl818", + .ns_min = 10000, + .n_aochan = 2, + .ai_range_type = &range_pcl818h_ai, + .has_dma = 1, + .is_818 = 1, + }, { + .name = "pcl718", + .ns_min = 16000, + .n_aochan = 2, + .ai_range_type = &range_unipolar5, + .has_dma = 1, + }, { + .name = "pcm3718", + .ns_min = 10000, + .ai_range_type = &range_pcl818h_ai, + .has_dma = 1, + .is_818 = 1, + }, +}; + +struct pcl818_private { + struct comedi_isadma *dma; + /* manimal allowed delay between samples (in us) for actual card */ + unsigned int ns_min; + /* MUX setting for actual AI operations */ + unsigned int act_chanlist[16]; + unsigned int act_chanlist_len; /* how long is actual MUX list */ + unsigned int act_chanlist_pos; /* actual position in MUX list */ + unsigned int usefifo:1; + unsigned int ai_cmd_running:1; + unsigned int ai_cmd_canceled:1; +}; + +static void pcl818_ai_setup_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int unread_samples) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize); + unsigned int nsamples; + + comedi_isadma_disable(dma->chan); + + /* + * Determine dma size based on the buffer maxsize plus the number of + * unread samples and the number of samples remaining in the command. + */ + nsamples = comedi_nsamples_left(s, max_samples + unread_samples); + if (nsamples > unread_samples) { + nsamples -= unread_samples; + desc->size = comedi_samples_to_bytes(s, nsamples); + comedi_isadma_program(desc); + } +} + +static void pcl818_ai_set_chan_range(struct comedi_device *dev, + unsigned int chan, + unsigned int range) +{ + outb(chan, dev->iobase + PCL818_MUX_REG); + outb(range, dev->iobase + PCL818_RANGE_REG); +} + +static void pcl818_ai_set_chan_scan(struct comedi_device *dev, + unsigned int first_chan, + unsigned int last_chan) +{ + outb(PCL818_MUX_SCAN(first_chan, last_chan), + dev->iobase + PCL818_MUX_REG); +} + +static void pcl818_ai_setup_chanlist(struct comedi_device *dev, + unsigned int *chanlist, + unsigned int seglen) +{ + struct pcl818_private *devpriv = dev->private; + unsigned int first_chan = CR_CHAN(chanlist[0]); + unsigned int last_chan; + unsigned int range; + int i; + + devpriv->act_chanlist_len = seglen; + devpriv->act_chanlist_pos = 0; + + /* store range list to card */ + for (i = 0; i < seglen; i++) { + last_chan = CR_CHAN(chanlist[i]); + range = CR_RANGE(chanlist[i]); + + devpriv->act_chanlist[i] = last_chan; + + pcl818_ai_set_chan_range(dev, last_chan, range); + } + + udelay(1); + + pcl818_ai_set_chan_scan(dev, first_chan, last_chan); +} + +static void pcl818_ai_clear_eoc(struct comedi_device *dev) +{ + /* writing any value clears the interrupt request */ + outb(0, dev->iobase + PCL818_STATUS_REG); +} + +static void pcl818_ai_soft_trig(struct comedi_device *dev) +{ + /* writing any value triggers a software conversion */ + outb(0, dev->iobase + PCL818_AI_LSB_REG); +} + +static unsigned int pcl818_ai_get_fifo_sample(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chan) +{ + unsigned int val; + + val = inb(dev->iobase + PCL818_FI_DATALO); + val |= (inb(dev->iobase + PCL818_FI_DATAHI) << 8); + + if (chan) + *chan = val & 0xf; + + return (val >> 4) & s->maxdata; +} + +static unsigned int pcl818_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chan) +{ + unsigned int val; + + val = inb(dev->iobase + PCL818_AI_MSB_REG) << 8; + val |= inb(dev->iobase + PCL818_AI_LSB_REG); + + if (chan) + *chan = val & 0xf; + + return (val >> 4) & s->maxdata; +} + +static int pcl818_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + PCL818_STATUS_REG); + if (status & PCL818_STATUS_INT) + return 0; + return -EBUSY; +} + +static bool pcl818_ai_write_sample(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, unsigned short val) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int expected_chan; + + expected_chan = devpriv->act_chanlist[devpriv->act_chanlist_pos]; + if (chan != expected_chan) { + dev_dbg(dev->class_dev, + "A/D mode1/3 %s - channel dropout %d!=%d !\n", + (devpriv->dma) ? "DMA" : + (devpriv->usefifo) ? "FIFO" : "IRQ", + chan, expected_chan); + s->async->events |= COMEDI_CB_ERROR; + return false; + } + + comedi_buf_write_samples(s, &val, 1); + + devpriv->act_chanlist_pos++; + if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len) + devpriv->act_chanlist_pos = 0; + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + s->async->events |= COMEDI_CB_EOA; + return false; + } + + return true; +} + +static void pcl818_handle_eoc(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int chan; + unsigned int val; + + if (pcl818_ai_eoc(dev, s, NULL, 0)) { + dev_err(dev->class_dev, "A/D mode1/3 IRQ without DRDY!\n"); + s->async->events |= COMEDI_CB_ERROR; + return; + } + + val = pcl818_ai_get_sample(dev, s, &chan); + pcl818_ai_write_sample(dev, s, chan, val); +} + +static void pcl818_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; + unsigned short *ptr = desc->virt_addr; + unsigned int nsamples = comedi_bytes_to_samples(s, desc->size); + unsigned int chan; + unsigned int val; + int i; + + /* restart dma with the next buffer */ + dma->cur_dma = 1 - dma->cur_dma; + pcl818_ai_setup_dma(dev, s, nsamples); + + for (i = 0; i < nsamples; i++) { + val = ptr[i]; + chan = val & 0xf; + val = (val >> 4) & s->maxdata; + if (!pcl818_ai_write_sample(dev, s, chan, val)) + break; + } +} + +static void pcl818_handle_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int status; + unsigned int chan; + unsigned int val; + int i, len; + + status = inb(dev->iobase + PCL818_FI_STATUS); + + if (status & 4) { + dev_err(dev->class_dev, "A/D mode1/3 FIFO overflow!\n"); + s->async->events |= COMEDI_CB_ERROR; + return; + } + + if (status & 1) { + dev_err(dev->class_dev, + "A/D mode1/3 FIFO interrupt without data!\n"); + s->async->events |= COMEDI_CB_ERROR; + return; + } + + if (status & 2) + len = 512; + else + len = 0; + + for (i = 0; i < len; i++) { + val = pcl818_ai_get_fifo_sample(dev, s, &chan); + if (!pcl818_ai_write_sample(dev, s, chan, val)) + break; + } +} + +static irqreturn_t pcl818_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pcl818_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + + if (!dev->attached || !devpriv->ai_cmd_running) { + pcl818_ai_clear_eoc(dev); + return IRQ_HANDLED; + } + + if (devpriv->ai_cmd_canceled) { + /* + * The cleanup from ai_cancel() has been delayed + * until now because the card doesn't seem to like + * being reprogrammed while a DMA transfer is in + * progress. + */ + s->async->scans_done = cmd->stop_arg; + s->cancel(dev, s); + return IRQ_HANDLED; + } + + if (devpriv->dma) + pcl818_handle_dma(dev, s); + else if (devpriv->usefifo) + pcl818_handle_fifo(dev, s); + else + pcl818_handle_eoc(dev, s); + + pcl818_ai_clear_eoc(dev); + + comedi_handle_events(dev, s); + return IRQ_HANDLED; +} + +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int chansegment[16]; + unsigned int i, nowmustbechan, seglen; + + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + dev_err(dev->class_dev, "range/channel list is empty!\n"); + return 0; + } + + if (n_chan > 1) { + /* first channel is every time ok */ + chansegment[0] = chanlist[0]; + /* build part of chanlist */ + for (i = 1, seglen = 1; i < n_chan; i++, seglen++) { + /* we detect loop, this must by finish */ + + if (chanlist[0] == chanlist[i]) + break; + nowmustbechan = + (CR_CHAN(chansegment[i - 1]) + 1) % s->n_chan; + if (nowmustbechan != CR_CHAN(chanlist[i])) { + /* channel list isn't continuous :-( */ + dev_dbg(dev->class_dev, + "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", + i, CR_CHAN(chanlist[i]), nowmustbechan, + CR_CHAN(chanlist[0])); + return 0; + } + /* well, this is next correct channel in list */ + chansegment[i] = chanlist[i]; + } + + /* check whole chanlist */ + for (i = 0; i < n_chan; i++) { + if (chanlist[i] != chansegment[i % seglen]) { + dev_dbg(dev->class_dev, + "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", + i, CR_CHAN(chansegment[i]), + CR_RANGE(chansegment[i]), + CR_AREF(chansegment[i]), + CR_CHAN(chanlist[i % seglen]), + CR_RANGE(chanlist[i % seglen]), + CR_AREF(chansegment[i % seglen])); + return 0; /* chan/gain list is strange */ + } + } + } else { + seglen = 1; + } + return seglen; +} + +static int check_single_ended(unsigned int port) +{ + if (inb(port + PCL818_STATUS_REG) & PCL818_STATUS_MUX) + return 1; + return 0; +} + +static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + const struct pcl818_board *board = dev->board_ptr; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + board->ns_min); + } else { /* TRIG_EXT */ + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* step 5: complain about special chanlist considerations */ + + if (cmd->chanlist) { + if (!check_channel_list(dev, s, cmd->chanlist, + cmd->chanlist_len)) + return 5; /* incorrect channels list */ + } + + return 0; +} + +static int pcl818_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int ctrl = 0; + unsigned int seglen; + + if (devpriv->ai_cmd_running) + return -EBUSY; + + seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); + if (seglen < 1) + return -EINVAL; + pcl818_ai_setup_chanlist(dev, cmd->chanlist, seglen); + + devpriv->ai_cmd_running = 1; + devpriv->ai_cmd_canceled = 0; + devpriv->act_chanlist_pos = 0; + + if (cmd->convert_src == TRIG_TIMER) + ctrl |= PCL818_CTRL_PACER_TRIG; + else + ctrl |= PCL818_CTRL_EXT_TRIG; + + outb(0, dev->iobase + PCL818_CNTENABLE_REG); + + if (dma) { + /* setup and enable dma for the first buffer */ + dma->cur_dma = 0; + pcl818_ai_setup_dma(dev, s, 0); + + ctrl |= PCL818_CTRL_INTE | PCL818_CTRL_IRQ(dev->irq) | + PCL818_CTRL_DMAE; + } else if (devpriv->usefifo) { + /* enable FIFO */ + outb(1, dev->iobase + PCL818_FI_ENABLE); + } else { + ctrl |= PCL818_CTRL_INTE | PCL818_CTRL_IRQ(dev->irq); + } + outb(ctrl, dev->iobase + PCL818_CTRL_REG); + + if (cmd->convert_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + } + + return 0; +} + +static int pcl818_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl818_private *devpriv = dev->private; + struct comedi_isadma *dma = devpriv->dma; + struct comedi_cmd *cmd = &s->async->cmd; + + if (!devpriv->ai_cmd_running) + return 0; + + if (dma) { + if (cmd->stop_src == TRIG_NONE || + (cmd->stop_src == TRIG_COUNT && + s->async->scans_done < cmd->stop_arg)) { + if (!devpriv->ai_cmd_canceled) { + /* + * Wait for running dma transfer to end, + * do cleanup in interrupt. + */ + devpriv->ai_cmd_canceled = 1; + return 0; + } + } + comedi_isadma_disable(dma->chan); + } + + outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG); + comedi_8254_pacer_enable(dev->pacer, 1, 2, false); + pcl818_ai_clear_eoc(dev); + + if (devpriv->usefifo) { /* FIFO shutdown */ + outb(0, dev->iobase + PCL818_FI_INTCLR); + outb(0, dev->iobase + PCL818_FI_FLUSH); + outb(0, dev->iobase + PCL818_FI_ENABLE); + } + devpriv->ai_cmd_running = 0; + devpriv->ai_cmd_canceled = 0; + + return 0; +} + +static int pcl818_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int ret = 0; + int i; + + outb(PCL818_CTRL_SOFT_TRIG, dev->iobase + PCL818_CTRL_REG); + + pcl818_ai_set_chan_range(dev, chan, range); + pcl818_ai_set_chan_scan(dev, chan, chan); + + for (i = 0; i < insn->n; i++) { + pcl818_ai_clear_eoc(dev); + pcl818_ai_soft_trig(dev); + + ret = comedi_timeout(dev, s, insn, pcl818_ai_eoc, 0); + if (ret) + break; + + data[i] = pcl818_ai_get_sample(dev, s, NULL); + } + pcl818_ai_clear_eoc(dev); + + return ret ? ret : insn->n; +} + +static int pcl818_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + int i; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outb((val & 0x000f) << 4, + dev->iobase + PCL818_AO_LSB_REG(chan)); + outb((val & 0x0ff0) >> 4, + dev->iobase + PCL818_AO_MSB_REG(chan)); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pcl818_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + PCL818_DO_DI_LSB_REG) | + (inb(dev->iobase + PCL818_DO_DI_MSB_REG) << 8); + + return insn->n; +} + +static int pcl818_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) { + outb(s->state & 0xff, dev->iobase + PCL818_DO_DI_LSB_REG); + outb((s->state >> 8), dev->iobase + PCL818_DO_DI_MSB_REG); + } + + data[1] = s->state; + + return insn->n; +} + +static void pcl818_reset(struct comedi_device *dev) +{ + const struct pcl818_board *board = dev->board_ptr; + unsigned int chan; + + /* flush and disable the FIFO */ + if (board->has_fifo) { + outb(0, dev->iobase + PCL818_FI_INTCLR); + outb(0, dev->iobase + PCL818_FI_FLUSH); + outb(0, dev->iobase + PCL818_FI_ENABLE); + } + + /* disable analog input trigger */ + outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG); + pcl818_ai_clear_eoc(dev); + + pcl818_ai_set_chan_range(dev, 0, 0); + + /* stop pacer */ + outb(0, dev->iobase + PCL818_CNTENABLE_REG); + + /* set analog output channels to 0V */ + for (chan = 0; chan < board->n_aochan; chan++) { + outb(0, dev->iobase + PCL818_AO_LSB_REG(chan)); + outb(0, dev->iobase + PCL818_AO_MSB_REG(chan)); + } + + /* set all digital outputs low */ + outb(0, dev->iobase + PCL818_DO_DI_MSB_REG); + outb(0, dev->iobase + PCL818_DO_DI_LSB_REG); +} + +static void pcl818_set_ai_range_table(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_devconfig *it) +{ + const struct pcl818_board *board = dev->board_ptr; + + /* default to the range table from the boardinfo */ + s->range_table = board->ai_range_type; + + /* now check the user config option based on the boardtype */ + if (board->is_818) { + if (it->options[4] == 1 || it->options[4] == 10) { + /* secondary range list jumper selectable */ + s->range_table = &range_pcl818l_h_ai; + } + } else { + switch (it->options[4]) { + case 0: + s->range_table = &range_bipolar10; + break; + case 1: + s->range_table = &range_bipolar5; + break; + case 2: + s->range_table = &range_bipolar2_5; + break; + case 3: + s->range_table = &range718_bipolar1; + break; + case 4: + s->range_table = &range718_bipolar0_5; + break; + case 6: + s->range_table = &range_unipolar10; + break; + case 7: + s->range_table = &range_unipolar5; + break; + case 8: + s->range_table = &range718_unipolar2; + break; + case 9: + s->range_table = &range718_unipolar1; + break; + default: + s->range_table = &range_unknown; + break; + } + } +} + +static void pcl818_alloc_dma(struct comedi_device *dev, unsigned int dma_chan) +{ + struct pcl818_private *devpriv = dev->private; + + /* only DMA channels 3 and 1 are valid */ + if (!(dma_chan == 3 || dma_chan == 1)) + return; + + /* DMA uses two 16K buffers */ + devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, + PAGE_SIZE * 4, COMEDI_ISADMA_READ); +} + +static void pcl818_free_dma(struct comedi_device *dev) +{ + struct pcl818_private *devpriv = dev->private; + + if (devpriv) + comedi_isadma_free(devpriv->dma); +} + +static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl818_board *board = dev->board_ptr; + struct pcl818_private *devpriv; + struct comedi_subdevice *s; + unsigned int osc_base; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], + board->has_fifo ? 0x20 : 0x10); + if (ret) + return ret; + + /* we can use IRQ 2-7 for async command support */ + if (it->options[1] >= 2 && it->options[1] <= 7) { + ret = request_irq(it->options[1], pcl818_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + /* should we use the FIFO? */ + if (dev->irq && board->has_fifo && it->options[2] == -1) + devpriv->usefifo = 1; + + /* we need an IRQ to do DMA on channel 3 or 1 */ + if (dev->irq && board->has_dma) + pcl818_alloc_dma(dev, it->options[2]); + + /* use 1MHz or 10MHz oscilator */ + if ((it->options[3] == 0) || (it->options[3] == 10)) + osc_base = I8254_OSC_BASE_10MHZ; + else + osc_base = I8254_OSC_BASE_1MHZ; + + dev->pacer = comedi_8254_init(dev->iobase + PCL818_TIMER_BASE, + osc_base, I8254_IO8, 0); + if (!dev->pacer) + return -ENOMEM; + + /* max sampling speed */ + devpriv->ns_min = board->ns_min; + if (!board->is_818) { + /* extended PCL718 to 100kHz DAC */ + if ((it->options[6] == 1) || (it->options[6] == 100)) + devpriv->ns_min = 10000; + } + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + if (check_single_ended(dev->iobase)) { + s->n_chan = 16; + s->subdev_flags |= SDF_COMMON | SDF_GROUND; + } else { + s->n_chan = 8; + s->subdev_flags |= SDF_DIFF; + } + s->maxdata = 0x0fff; + + pcl818_set_ai_range_table(dev, s, it); + + s->insn_read = pcl818_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = ai_cmdtest; + s->do_cmd = pcl818_ai_cmd; + s->cancel = pcl818_ai_cancel; + } + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + if (board->n_aochan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = board->n_aochan; + s->maxdata = 0x0fff; + s->range_table = &range_unipolar5; + if (board->is_818) { + if ((it->options[4] == 1) || (it->options[4] == 10)) + s->range_table = &range_unipolar10; + if (it->options[4] == 2) + s->range_table = &range_unknown; + } else { + if ((it->options[5] == 1) || (it->options[5] == 10)) + s->range_table = &range_unipolar10; + if (it->options[5] == 2) + s->range_table = &range_unknown; + } + s->insn_write = pcl818_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Digital Input subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl818_di_insn_bits; + + /* Digital Output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcl818_do_insn_bits; + + pcl818_reset(dev); + + return 0; +} + +static void pcl818_detach(struct comedi_device *dev) +{ + struct pcl818_private *devpriv = dev->private; + + if (devpriv) { + pcl818_ai_cancel(dev, dev->read_subdev); + pcl818_reset(dev); + } + pcl818_free_dma(dev); + comedi_legacy_detach(dev); +} + +static struct comedi_driver pcl818_driver = { + .driver_name = "pcl818", + .module = THIS_MODULE, + .attach = pcl818_attach, + .detach = pcl818_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct pcl818_board), +}; +module_comedi_driver(pcl818_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcm3724.c b/drivers/comedi/drivers/pcm3724.c new file mode 100644 index 000000000..ca8bef54d --- /dev/null +++ b/drivers/comedi/drivers/pcm3724.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pcm3724.c + * Comedi driver for Advantech PCM-3724 Digital I/O board + * + * Drew Csillag <drew_csillag@yahoo.com> + */ + +/* + * Driver: pcm3724 + * Description: Advantech PCM-3724 + * Devices: [Advantech] PCM-3724 (pcm3724) + * Author: Drew Csillag <drew_csillag@yahoo.com> + * 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 + * + * Configuration Options: + * [0] - I/O port base address + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedi_8255.h> + +/* + * Register I/O Map + * + * This board has two standard 8255 devices that provide six 8-bit DIO ports + * (48 channels total). Six 74HCT245 chips (one for each port) buffer the + * I/O lines to increase driving capability. Because the 74HCT245 is a + * bidirectional, tri-state line buffer, two additional I/O ports are used + * to control the direction of data and the enable of each port. + */ +#define PCM3724_8255_0_BASE 0x00 +#define PCM3724_8255_1_BASE 0x04 +#define PCM3724_DIO_DIR_REG 0x08 +#define PCM3724_DIO_DIR_C0_OUT BIT(0) +#define PCM3724_DIO_DIR_B0_OUT BIT(1) +#define PCM3724_DIO_DIR_A0_OUT BIT(2) +#define PCM3724_DIO_DIR_C1_OUT BIT(3) +#define PCM3724_DIO_DIR_B1_OUT BIT(4) +#define PCM3724_DIO_DIR_A1_OUT BIT(5) +#define PCM3724_GATE_CTRL_REG 0x09 +#define PCM3724_GATE_CTRL_C0_ENA BIT(0) +#define PCM3724_GATE_CTRL_B0_ENA BIT(1) +#define PCM3724_GATE_CTRL_A0_ENA BIT(2) +#define PCM3724_GATE_CTRL_C1_ENA BIT(3) +#define PCM3724_GATE_CTRL_B1_ENA BIT(4) +#define PCM3724_GATE_CTRL_A1_ENA BIT(5) + +/* used to track configured dios */ +struct priv_pcm3724 { + int dio_1; + int dio_2; +}; + +static int compute_buffer(int config, int devno, struct comedi_subdevice *s) +{ + /* 1 in io_bits indicates output */ + if (s->io_bits & 0x0000ff) { + if (devno == 0) + config |= PCM3724_DIO_DIR_A0_OUT; + else + config |= PCM3724_DIO_DIR_A1_OUT; + } + if (s->io_bits & 0x00ff00) { + if (devno == 0) + config |= PCM3724_DIO_DIR_B0_OUT; + else + config |= PCM3724_DIO_DIR_B1_OUT; + } + if (s->io_bits & 0xff0000) { + if (devno == 0) + config |= PCM3724_DIO_DIR_C0_OUT; + else + config |= PCM3724_DIO_DIR_C1_OUT; + } + return config; +} + +static void do_3724_config(struct comedi_device *dev, + struct comedi_subdevice *s, int chanspec) +{ + struct comedi_subdevice *s_dio1 = &dev->subdevices[0]; + struct comedi_subdevice *s_dio2 = &dev->subdevices[1]; + int config; + int buffer_config; + unsigned long port_8255_cfg; + + config = I8255_CTRL_CW; + + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= I8255_CTRL_A_IO; + + if (!(s->io_bits & 0x00ff00)) + config |= I8255_CTRL_B_IO; + + if (!(s->io_bits & 0xff0000)) + config |= I8255_CTRL_C_HI_IO | I8255_CTRL_C_LO_IO; + + buffer_config = compute_buffer(0, 0, s_dio1); + buffer_config = compute_buffer(buffer_config, 1, s_dio2); + + if (s == s_dio1) + port_8255_cfg = dev->iobase + I8255_CTRL_REG; + else + port_8255_cfg = dev->iobase + I8255_SIZE + I8255_CTRL_REG; + + outb(buffer_config, dev->iobase + PCM3724_DIO_DIR_REG); + + 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 |= PCM3724_GATE_CTRL_C0_ENA; + + if (priv->dio_1 & 0xff00) + gatecfg |= PCM3724_GATE_CTRL_B0_ENA; + + if (priv->dio_1 & 0xff) + gatecfg |= PCM3724_GATE_CTRL_A0_ENA; + + if (priv->dio_2 & 0xff0000) + gatecfg |= PCM3724_GATE_CTRL_C1_ENA; + + if (priv->dio_2 & 0xff00) + gatecfg |= PCM3724_GATE_CTRL_B1_ENA; + + if (priv->dio_2 & 0xff) + gatecfg |= PCM3724_GATE_CTRL_A1_ENA; + + outb(gatecfg, dev->iobase + PCM3724_GATE_CTRL_REG); +} + +/* overriding the 8255 insn config */ +static int subdev_3724_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x0000ff; + else if (chan < 16) + mask = 0x00ff00; + else if (chan < 20) + mask = 0x0f0000; + else + mask = 0xf00000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + do_3724_config(dev, s, insn->chanspec); + enable_chan(dev, s, insn->chanspec); + + return insn->n; +} + +static int pcm3724_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct priv_pcm3724 *priv; + struct comedi_subdevice *s; + int ret, i; + + priv = comedi_alloc_devpriv(dev, sizeof(*priv)); + if (!priv) + return -ENOMEM; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + for (i = 0; i < dev->n_subdevices; i++) { + s = &dev->subdevices[i]; + ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE); + if (ret) + return ret; + s->insn_config = subdev_3724_insn_config; + } + return 0; +} + +static struct comedi_driver pcm3724_driver = { + .driver_name = "pcm3724", + .module = THIS_MODULE, + .attach = pcm3724_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(pcm3724_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Advantech PCM-3724 Digital I/O board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcmad.c b/drivers/comedi/drivers/pcmad.c new file mode 100644 index 000000000..976eda438 --- /dev/null +++ b/drivers/comedi/drivers/pcmad.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: pcmad + * Description: Winsystems PCM-A/D12, PCM-A/D16 + * Devices: [Winsystems] PCM-A/D12 (pcmad12), PCM-A/D16 (pcmad16) + * Author: ds + * Status: untested + * + * This driver was written on a bet that I couldn't write a driver + * in less than 2 hours. I won the bet, but never got paid. =( + * + * Configuration options: + * [0] - I/O port base + * [1] - IRQ (unused) + * [2] - Analog input reference (must match jumpers) + * 0 = single-ended (16 channels) + * 1 = differential (8 channels) + * [3] - Analog input encoding (must match jumpers) + * 0 = straight binary (0-5V input range) + * 1 = two's complement (+-10V input range) + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> + +#define PCMAD_STATUS 0 +#define PCMAD_LSB 1 +#define PCMAD_MSB 2 +#define PCMAD_CONVERT 1 + +struct pcmad_board_struct { + const char *name; + unsigned int ai_maxdata; +}; + +static const struct pcmad_board_struct pcmad_boards[] = { + { + .name = "pcmad12", + .ai_maxdata = 0x0fff, + }, { + .name = "pcmad16", + .ai_maxdata = 0xffff, + }, +}; + +static int pcmad_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + PCMAD_STATUS); + if ((status & 0x3) == 0x3) + return 0; + return -EBUSY; +} + +static int pcmad_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int ret; + int i; + + for (i = 0; i < insn->n; i++) { + outb(chan, dev->iobase + PCMAD_CONVERT); + + ret = comedi_timeout(dev, s, insn, pcmad_ai_eoc, 0); + if (ret) + return ret; + + val = inb(dev->iobase + PCMAD_LSB) | + (inb(dev->iobase + PCMAD_MSB) << 8); + + /* data is shifted on the pcmad12, fix it */ + if (s->maxdata == 0x0fff) + val >>= 4; + + if (comedi_range_is_bipolar(s, range)) { + /* munge the two's complement value */ + val ^= ((s->maxdata + 1) >> 1); + } + + data[i] = val; + } + + return insn->n; +} + +static int pcmad_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcmad_board_struct *board = dev->board_ptr; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x04); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + if (it->options[1]) { + /* 8 differential channels */ + s->subdev_flags = SDF_READABLE | AREF_DIFF; + s->n_chan = 8; + } else { + /* 16 single-ended channels */ + s->subdev_flags = SDF_READABLE | AREF_GROUND; + s->n_chan = 16; + } + s->len_chanlist = 1; + s->maxdata = board->ai_maxdata; + s->range_table = it->options[2] ? &range_bipolar10 : &range_unipolar5; + s->insn_read = pcmad_ai_insn_read; + + return 0; +} + +static struct comedi_driver pcmad_driver = { + .driver_name = "pcmad", + .module = THIS_MODULE, + .attach = pcmad_attach, + .detach = comedi_legacy_detach, + .board_name = &pcmad_boards[0].name, + .num_names = ARRAY_SIZE(pcmad_boards), + .offset = sizeof(pcmad_boards[0]), +}; +module_comedi_driver(pcmad_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcmda12.c b/drivers/comedi/drivers/pcmda12.c new file mode 100644 index 000000000..611f13bed --- /dev/null +++ b/drivers/comedi/drivers/pcmda12.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* AI range is not configurable, it's set by jumpers on the board */ +static const struct comedi_lrange pcmda12_ranges = { + 3, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5) + } +}; + +struct pcmda12_private { + int simultaneous_xfer_mode; +}; + +static int pcmda12_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcmda12_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int val = s->readback[chan]; + unsigned long ioreg = dev->iobase + (chan * 2); + int i; + + for (i = 0; i < insn->n; ++i) { + val = data[i]; + outb(val & 0xff, ioreg); + outb((val >> 8) & 0xff, ioreg + 1); + + /* + * Initiate transfer if not in simultaneaous xfer + * mode by reading one of the AO registers. + */ + if (!devpriv->simultaneous_xfer_mode) + inb(ioreg); + } + s->readback[chan] = val; + + return insn->n; +} + +static int pcmda12_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pcmda12_private *devpriv = dev->private; + + /* + * Initiate simultaneaous xfer mode by reading one of the + * AO registers. All analog outputs will then be updated. + */ + if (devpriv->simultaneous_xfer_mode) + inb(dev->iobase); + + return comedi_readback_insn_read(dev, s, insn, data); +} + +static void pcmda12_ao_reset(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int i; + + for (i = 0; i < s->n_chan; ++i) { + outb(0, dev->iobase + (i * 2)); + outb(0, dev->iobase + (i * 2) + 1); + } + /* Initiate transfer by reading one of the AO registers. */ + inb(dev->iobase); +} + +static int pcmda12_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pcmda12_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->simultaneous_xfer_mode = it->options[1]; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 0x0fff; + s->range_table = &pcmda12_ranges; + s->insn_write = pcmda12_ao_insn_write; + s->insn_read = pcmda12_ao_insn_read; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + pcmda12_ao_reset(dev, s); + + return 0; +} + +static struct comedi_driver pcmda12_driver = { + .driver_name = "pcmda12", + .module = THIS_MODULE, + .attach = pcmda12_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(pcmda12_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcmmio.c b/drivers/comedi/drivers/pcmmio.c new file mode 100644 index 000000000..c2402239d --- /dev/null +++ b/drivers/comedi/drivers/pcmmio.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* + * Register I/O map + */ +#define PCMMIO_AI_LSB_REG 0x00 +#define PCMMIO_AI_MSB_REG 0x01 +#define PCMMIO_AI_CMD_REG 0x02 +#define PCMMIO_AI_CMD_SE BIT(7) +#define PCMMIO_AI_CMD_ODD_CHAN BIT(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 BIT(7) +#define PCMMIO_AI_STATUS_DATA_DMA_PEND BIT(6) +#define PCMMIO_AI_STATUS_CMD_DMA_PEND BIT(5) +#define PCMMIO_AI_STATUS_IRQ_PEND BIT(4) +#define PCMMIO_AI_STATUS_DATA_DRQ_ENA BIT(2) +#define PCMMIO_AI_STATUS_REG_SEL BIT(3) +#define PCMMIO_AI_STATUS_CMD_DRQ_ENA BIT(1) +#define PCMMIO_AI_STATUS_IRQ_ENA BIT(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 BIT(3) +#define PCMMIO_AI_RES_ENA_DIO_RES_ACCESS BIT(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 BIT(7) +#define PCMMIO_AO_STATUS_DATA_DMA_PEND BIT(6) +#define PCMMIO_AO_STATUS_CMD_DMA_PEND BIT(5) +#define PCMMIO_AO_STATUS_IRQ_PEND BIT(4) +#define PCMMIO_AO_STATUS_DATA_DRQ_ENA BIT(2) +#define PCMMIO_AO_STATUS_REG_SEL BIT(3) +#define PCMMIO_AO_STATUS_CMD_DRQ_ENA BIT(1) +#define PCMMIO_AO_STATUS_IRQ_ENA BIT(0) +#define PCMMIO_AO_RESOURCE_ENA_REG 0x0b +#define PCMMIO_AO_2ND_DAC_OFFSET 0x04 + +/* + * WinSystems WS16C48 + * + * Offset Page 0 Page 1 Page 2 Page 3 + * ------ ----------- ----------- ----------- ----------- + * 0x10 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O + * 0x11 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O + * 0x12 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O + * 0x13 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O + * 0x14 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O + * 0x15 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O + * 0x16 INT_PENDING INT_PENDING INT_PENDING INT_PENDING + * 0x17 Page/Lock Page/Lock Page/Lock Page/Lock + * 0x18 N/A POL_0 ENAB_0 INT_ID0 + * 0x19 N/A POL_1 ENAB_1 INT_ID1 + * 0x1a N/A POL_2 ENAB_2 INT_ID2 + */ +#define PCMMIO_PORT_REG(x) (0x10 + (x)) +#define PCMMIO_INT_PENDING_REG 0x16 +#define PCMMIO_PAGE_LOCK_REG 0x17 +#define PCMMIO_LOCK_PORT(x) ((1 << (x)) & 0x3f) +#define PCMMIO_PAGE(x) (((x) & 0x3) << 6) +#define PCMMIO_PAGE_MASK PCMUIO_PAGE(3) +#define PCMMIO_PAGE_POL 1 +#define PCMMIO_PAGE_ENAB 2 +#define PCMMIO_PAGE_INT_ID 3 +#define PCMMIO_PAGE_REG(x) (0x18 + (x)) + +static const struct comedi_lrange pcmmio_ai_ranges = { + 4, { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(10) + } +}; + +static const struct comedi_lrange pcmmio_ao_ranges = { + 6, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10), + BIP_RANGE(2.5), + RANGE(-2.5, 7.5) + } +}; + +struct pcmmio_private { + spinlock_t pagelock; /* protects the page registers */ + spinlock_t spinlock; /* protects the member variables */ + unsigned int enabled_mask; + unsigned int active:1; +}; + +static void pcmmio_dio_write(struct comedi_device *dev, unsigned int val, + int page, int port) +{ + struct pcmmio_private *devpriv = dev->private; + unsigned long iobase = dev->iobase; + unsigned long flags; + + spin_lock_irqsave(&devpriv->pagelock, flags); + if (page == 0) { + /* Port registers are valid for any page */ + outb(val & 0xff, iobase + PCMMIO_PORT_REG(port + 0)); + outb((val >> 8) & 0xff, iobase + PCMMIO_PORT_REG(port + 1)); + outb((val >> 16) & 0xff, iobase + PCMMIO_PORT_REG(port + 2)); + } else { + outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG); + outb(val & 0xff, iobase + PCMMIO_PAGE_REG(0)); + outb((val >> 8) & 0xff, iobase + PCMMIO_PAGE_REG(1)); + outb((val >> 16) & 0xff, iobase + PCMMIO_PAGE_REG(2)); + } + spin_unlock_irqrestore(&devpriv->pagelock, flags); +} + +static unsigned int pcmmio_dio_read(struct comedi_device *dev, + int page, int port) +{ + struct pcmmio_private *devpriv = dev->private; + unsigned long iobase = dev->iobase; + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(&devpriv->pagelock, flags); + if (page == 0) { + /* Port registers are valid for any page */ + val = inb(iobase + PCMMIO_PORT_REG(port + 0)); + val |= (inb(iobase + PCMMIO_PORT_REG(port + 1)) << 8); + val |= (inb(iobase + PCMMIO_PORT_REG(port + 2)) << 16); + } else { + outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG); + val = inb(iobase + PCMMIO_PAGE_REG(0)); + val |= (inb(iobase + PCMMIO_PAGE_REG(1)) << 8); + val |= (inb(iobase + PCMMIO_PAGE_REG(2)) << 16); + } + spin_unlock_irqrestore(&devpriv->pagelock, flags); + + return val; +} + +/* + * Each channel can be individually programmed for input or output. + * Writing a '0' to a channel causes the corresponding output pin + * to go to a high-z state (pulled high by an external 10K resistor). + * This allows it to be used as an input. When used in the input mode, + * a read reflects the inverted state of the I/O pin, such that a + * high on the pin will read as a '0' in the register. Writing a '1' + * to a bit position causes the pin to sink current (up to 12mA), + * effectively pulling it low. + */ +static int pcmmio_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */ + int port = s->index == 2 ? 0 : 3; + unsigned int chanmask = (1 << s->n_chan) - 1; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + /* + * Outputs are inverted, invert the state and + * update the channels. + * + * The s->io_bits mask makes sure the input channels + * are '0' so that the outputs pins stay in a high + * z-state. + */ + val = ~s->state & chanmask; + val &= s->io_bits; + pcmmio_dio_write(dev, val, 0, port); + } + + /* get inverted state of the channels from the port */ + val = pcmmio_dio_read(dev, 0, port); + + /* return the true state of the channels */ + data[1] = ~val & chanmask; + + return insn->n; +} + +static int pcmmio_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */ + int port = s->index == 2 ? 0 : 3; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + if (data[0] == INSN_CONFIG_DIO_INPUT) + pcmmio_dio_write(dev, s->io_bits, 0, port); + + return insn->n; +} + +static void pcmmio_reset(struct comedi_device *dev) +{ + /* Clear all the DIO port bits */ + pcmmio_dio_write(dev, 0, 0, 0); + pcmmio_dio_write(dev, 0, 0, 3); + + /* Clear all the paged registers */ + pcmmio_dio_write(dev, 0, PCMMIO_PAGE_POL, 0); + pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0); + pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0); +} + +/* devpriv->spinlock is already locked */ +static void pcmmio_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcmmio_private *devpriv = dev->private; + + devpriv->enabled_mask = 0; + devpriv->active = 0; + s->async->inttrig = NULL; + + /* disable all dio interrupts */ + pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0); +} + +static void pcmmio_handle_dio_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int triggered) +{ + struct pcmmio_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int val = 0; + unsigned long flags; + int i; + + spin_lock_irqsave(&devpriv->spinlock, flags); + + if (!devpriv->active) + goto done; + + if (!(triggered & devpriv->enabled_mask)) + goto done; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (triggered & (1 << chan)) + val |= (1 << i); + } + + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + +done: + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + comedi_handle_events(dev, s); +} + +static irqreturn_t interrupt_pcmmio(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + unsigned int triggered; + unsigned char int_pend; + + /* are there any interrupts pending */ + int_pend = inb(dev->iobase + PCMMIO_INT_PENDING_REG) & 0x07; + if (!int_pend) + return IRQ_NONE; + + /* get, and clear, the pending interrupts */ + triggered = pcmmio_dio_read(dev, PCMMIO_PAGE_INT_ID, 0); + pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0); + + pcmmio_handle_dio_intr(dev, s, triggered); + + return IRQ_HANDLED; +} + +/* devpriv->spinlock is already locked */ +static void pcmmio_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcmmio_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int bits = 0; + unsigned int pol_bits = 0; + int i; + + devpriv->enabled_mask = 0; + devpriv->active = 1; + if (cmd->chanlist) { + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chanspec = cmd->chanlist[i]; + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + + bits |= (1 << chan); + pol_bits |= (((aref || range) ? 1 : 0) << chan); + } + } + bits &= ((1 << s->n_chan) - 1); + devpriv->enabled_mask = bits; + + /* set polarity and enable interrupts */ + pcmmio_dio_write(dev, pol_bits, PCMMIO_PAGE_POL, 0); + pcmmio_dio_write(dev, bits, PCMMIO_PAGE_ENAB, 0); +} + +static int pcmmio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcmmio_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->spinlock, flags); + if (devpriv->active) + pcmmio_stop_intr(dev, s); + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + return 0; +} + +static int pcmmio_inttrig_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct pcmmio_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&devpriv->spinlock, flags); + s->async->inttrig = NULL; + if (devpriv->active) + pcmmio_start_intr(dev, s); + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + return 1; +} + +/* + * 'do_cmd' function for an 'INTERRUPT' subdevice. + */ +static int pcmmio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcmmio_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + spin_lock_irqsave(&devpriv->spinlock, flags); + devpriv->active = 1; + + /* Set up start of acquisition. */ + if (cmd->start_src == TRIG_INT) + s->async->inttrig = pcmmio_inttrig_start_intr; + else /* TRIG_NOW */ + pcmmio_start_intr(dev, s); + + spin_unlock_irqrestore(&devpriv->spinlock, flags); + + return 0; +} + +static int pcmmio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} + +static int pcmmio_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + PCMMIO_AI_STATUS_REG); + if (status & PCMMIO_AI_STATUS_DATA_READY) + return 0; + return -EBUSY; +} + +static int pcmmio_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long iobase = dev->iobase; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int aref = CR_AREF(insn->chanspec); + unsigned char cmd = 0; + unsigned int val; + int ret; + int i; + + /* + * The PCM-MIO uses two Linear Tech LTC1859CG 8-channel A/D converters. + * The devices use a full duplex serial interface which transmits and + * receives data simultaneously. An 8-bit command is shifted into the + * ADC interface to configure it for the next conversion. At the same + * time, the data from the previous conversion is shifted out of the + * device. Consequently, the conversion result is delayed by one + * conversion from the command word. + * + * Setup the cmd for the conversions then do a dummy conversion to + * flush the junk data. Then do each conversion requested by the + * comedi_insn. Note that the last conversion will leave junk data + * in ADC which will get flushed on the next comedi_insn. + */ + + if (chan > 7) { + chan -= 8; + iobase += PCMMIO_AI_2ND_ADC_OFFSET; + } + + if (aref == AREF_GROUND) + cmd |= PCMMIO_AI_CMD_SE; + if (chan % 2) + cmd |= PCMMIO_AI_CMD_ODD_CHAN; + cmd |= PCMMIO_AI_CMD_CHAN_SEL(chan / 2); + cmd |= PCMMIO_AI_CMD_RANGE(range); + + outb(cmd, iobase + PCMMIO_AI_CMD_REG); + + ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0); + if (ret) + return ret; + + val = inb(iobase + PCMMIO_AI_LSB_REG); + val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8; + + for (i = 0; i < insn->n; i++) { + outb(cmd, iobase + PCMMIO_AI_CMD_REG); + + ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0); + if (ret) + return ret; + + val = inb(iobase + PCMMIO_AI_LSB_REG); + val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8; + + /* bipolar data is two's complement */ + if (comedi_range_is_bipolar(s, range)) + val = comedi_offset_munge(s, val); + + data[i] = val; + } + + return insn->n; +} + +static int pcmmio_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + PCMMIO_AO_STATUS_REG); + if (status & PCMMIO_AO_STATUS_DATA_READY) + return 0; + return -EBUSY; +} + +static int pcmmio_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long iobase = dev->iobase; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned char cmd = 0; + int ret; + int i; + + /* + * The PCM-MIO has two Linear Tech LTC2704 DAC devices. Each device + * is a 4-channel converter with software-selectable output range. + */ + + if (chan > 3) { + cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan - 4); + iobase += PCMMIO_AO_2ND_DAC_OFFSET; + } else { + cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan); + } + + /* set the range for the channel */ + outb(PCMMIO_AO_LSB_SPAN(range), iobase + PCMMIO_AO_LSB_REG); + outb(0, iobase + PCMMIO_AO_MSB_REG); + outb(cmd | PCMMIO_AO_CMD_WR_SPAN_UPDATE, iobase + PCMMIO_AO_CMD_REG); + + ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + /* write the data to the channel */ + outb(val & 0xff, iobase + PCMMIO_AO_LSB_REG); + outb((val >> 8) & 0xff, iobase + PCMMIO_AO_MSB_REG); + outb(cmd | PCMMIO_AO_CMD_WR_CODE_UPDATE, + iobase + PCMMIO_AO_CMD_REG); + + ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0); + if (ret) + return ret; + + s->readback[chan] = val; + } + + return insn->n; +} + +static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pcmmio_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 32); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + spin_lock_init(&devpriv->pagelock); + spin_lock_init(&devpriv->spinlock); + + pcmmio_reset(dev); + + if (it->options[1]) { + ret = request_irq(it->options[1], interrupt_pcmmio, 0, + dev->board_name, dev); + if (ret == 0) { + dev->irq = it->options[1]; + + /* configure the interrupt routing on the board */ + outb(PCMMIO_AI_RES_ENA_DIO_RES_ACCESS, + dev->iobase + PCMMIO_AI_RES_ENA_REG); + outb(PCMMIO_RESOURCE_IRQ(dev->irq), + dev->iobase + PCMMIO_RESOURCE_REG); + } + } + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 16; + s->maxdata = 0xffff; + s->range_table = &pcmmio_ai_ranges; + s->insn_read = pcmmio_ai_insn_read; + + /* initialize the resource enable register by clearing it */ + outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS, + dev->iobase + PCMMIO_AI_RES_ENA_REG); + outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS, + dev->iobase + PCMMIO_AI_RES_ENA_REG + PCMMIO_AI_2ND_ADC_OFFSET); + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->maxdata = 0xffff; + s->range_table = &pcmmio_ao_ranges; + s->insn_write = pcmmio_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* initialize the resource enable register by clearing it */ + outb(0, dev->iobase + PCMMIO_AO_RESOURCE_ENA_REG); + outb(0, dev->iobase + PCMMIO_AO_2ND_DAC_OFFSET + + PCMMIO_AO_RESOURCE_ENA_REG); + + /* Digital I/O subdevice with interrupt support */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->len_chanlist = 1; + s->range_table = &range_digital; + s->insn_bits = pcmmio_dio_insn_bits; + s->insn_config = pcmmio_dio_insn_config; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | SDF_PACKED; + s->len_chanlist = s->n_chan; + s->cancel = pcmmio_cancel; + s->do_cmd = pcmmio_cmd; + s->do_cmdtest = pcmmio_cmdtest; + } + + /* Digital I/O subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcmmio_dio_insn_bits; + s->insn_config = pcmmio_dio_insn_config; + + return 0; +} + +static struct comedi_driver pcmmio_driver = { + .driver_name = "pcmmio", + .module = THIS_MODULE, + .attach = pcmmio_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(pcmmio_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Winsystems PCM-MIO PC/104 board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/pcmuio.c b/drivers/comedi/drivers/pcmuio.c new file mode 100644 index 000000000..33b24dbbb --- /dev/null +++ b/drivers/comedi/drivers/pcmuio.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: pcmuio + * Description: Winsystems PC-104 based 48/96-channel DIO boards. + * Devices: [Winsystems] PCM-UIO48A (pcmuio48), PCM-UIO96A (pcmuio96) + * Author: Calin Culianu <calin@ajvar.org> + * Updated: Fri, 13 Jan 2006 12:01:01 -0500 + * Status: works + * + * A driver for the relatively straightforward-to-program PCM-UIO48A and + * PCM-UIO96A boards from Winsystems. These boards use either one or two + * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This + * chip is interesting in that each I/O line is individually programmable + * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel + * basis). Also, each chip supports edge-triggered interrupts for the first + * 24 I/O lines. Of course, since the 96-channel version of the board has + * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since + * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection + * are done through jumpers on the board. You need to pass that information + * to this driver as the first and second comedi_config option, respectively. + * Note that the 48-channel version uses 16 bytes of IO memory and the 96- + * channel version uses 32-bytes (in case you are worried about conflicts). + * The 48-channel board is split into two 24-channel comedi subdevices. The + * 96-channel board is split into 4 24-channel DIO subdevices. + * + * Note that IRQ support has been added, but it is untested. + * + * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the + * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use + * comedi_commands with TRIG_NOW. Your callback will be called each time an + * edge is triggered, and the data values will be two sample_t's, which + * should be concatenated to form one 32-bit unsigned int. This value is + * the mask of channels that had edges detected from your channel list. Note + * that the bits positions in the mask correspond to positions in your + * chanlist when you specified the command and *not* channel id's! + * + * To set the polarity of the edge-detection interrupts pass a nonzero value + * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for + * both CR_RANGE and CR_AREF if you want edge-down polarity. + * + * In the 48-channel version: + * + * On subdev 0, the first 24 channels 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 <linux/comedi/comedidev.h> + +/* + * Register I/O map + * + * Offset Page 0 Page 1 Page 2 Page 3 + * ------ ----------- ----------- ----------- ----------- + * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O + * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O + * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O + * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O + * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O + * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O + * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING + * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock + * 0x08 N/A POL_0 ENAB_0 INT_ID0 + * 0x09 N/A POL_1 ENAB_1 INT_ID1 + * 0x0a N/A POL_2 ENAB_2 INT_ID2 + */ +#define PCMUIO_PORT_REG(x) (0x00 + (x)) +#define PCMUIO_INT_PENDING_REG 0x06 +#define PCMUIO_PAGE_LOCK_REG 0x07 +#define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f) +#define PCMUIO_PAGE(x) (((x) & 0x3) << 6) +#define PCMUIO_PAGE_MASK PCMUIO_PAGE(3) +#define PCMUIO_PAGE_POL 1 +#define PCMUIO_PAGE_ENAB 2 +#define PCMUIO_PAGE_INT_ID 3 +#define PCMUIO_PAGE_REG(x) (0x08 + (x)) + +#define PCMUIO_ASIC_IOSIZE 0x10 +#define PCMUIO_MAX_ASICS 2 + +struct pcmuio_board { + const char *name; + const int num_asics; +}; + +static const struct pcmuio_board pcmuio_boards[] = { + { + .name = "pcmuio48", + .num_asics = 1, + }, { + .name = "pcmuio96", + .num_asics = 2, + }, +}; + +struct pcmuio_asic { + spinlock_t pagelock; /* protects the page registers */ + spinlock_t spinlock; /* protects member variables */ + unsigned int enabled_mask; + unsigned int active:1; +}; + +struct pcmuio_private { + struct pcmuio_asic asics[PCMUIO_MAX_ASICS]; + unsigned int irq2; +}; + +static inline unsigned long pcmuio_asic_iobase(struct comedi_device *dev, + int asic) +{ + return dev->iobase + (asic * PCMUIO_ASIC_IOSIZE); +} + +static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice *s) +{ + /* + * subdevice 0 and 1 are handled by the first asic + * subdevice 2 and 3 are handled by the second asic + */ + return s->index / 2; +} + +static inline int pcmuio_subdevice_to_port(struct comedi_subdevice *s) +{ + /* + * subdevice 0 and 2 use port registers 0-2 + * subdevice 1 and 3 use port registers 3-5 + */ + return (s->index % 2) ? 3 : 0; +} + +static void pcmuio_write(struct comedi_device *dev, unsigned int val, + int asic, int page, int port) +{ + struct pcmuio_private *devpriv = dev->private; + struct pcmuio_asic *chip = &devpriv->asics[asic]; + unsigned long iobase = pcmuio_asic_iobase(dev, asic); + unsigned long flags; + + spin_lock_irqsave(&chip->pagelock, flags); + if (page == 0) { + /* Port registers are valid for any page */ + outb(val & 0xff, iobase + PCMUIO_PORT_REG(port + 0)); + outb((val >> 8) & 0xff, iobase + PCMUIO_PORT_REG(port + 1)); + outb((val >> 16) & 0xff, iobase + PCMUIO_PORT_REG(port + 2)); + } else { + outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG); + outb(val & 0xff, iobase + PCMUIO_PAGE_REG(0)); + outb((val >> 8) & 0xff, iobase + PCMUIO_PAGE_REG(1)); + outb((val >> 16) & 0xff, iobase + PCMUIO_PAGE_REG(2)); + } + spin_unlock_irqrestore(&chip->pagelock, flags); +} + +static unsigned int pcmuio_read(struct comedi_device *dev, + int asic, int page, int port) +{ + struct pcmuio_private *devpriv = dev->private; + struct pcmuio_asic *chip = &devpriv->asics[asic]; + unsigned long iobase = pcmuio_asic_iobase(dev, asic); + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(&chip->pagelock, flags); + if (page == 0) { + /* Port registers are valid for any page */ + val = inb(iobase + PCMUIO_PORT_REG(port + 0)); + val |= (inb(iobase + PCMUIO_PORT_REG(port + 1)) << 8); + val |= (inb(iobase + PCMUIO_PORT_REG(port + 2)) << 16); + } else { + outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG); + val = inb(iobase + PCMUIO_PAGE_REG(0)); + val |= (inb(iobase + PCMUIO_PAGE_REG(1)) << 8); + val |= (inb(iobase + PCMUIO_PAGE_REG(2)) << 16); + } + spin_unlock_irqrestore(&chip->pagelock, flags); + + return val; +} + +/* + * Each channel can be individually programmed for input or output. + * Writing a '0' to a channel causes the corresponding output pin + * to go to a high-z state (pulled high by an external 10K resistor). + * This allows it to be used as an input. When used in the input mode, + * a read reflects the inverted state of the I/O pin, such that a + * high on the pin will read as a '0' in the register. Writing a '1' + * to a bit position causes the pin to sink current (up to 12mA), + * effectively pulling it low. + */ +static int pcmuio_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int asic = pcmuio_subdevice_to_asic(s); + int port = pcmuio_subdevice_to_port(s); + unsigned int chanmask = (1 << s->n_chan) - 1; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + /* + * Outputs are inverted, invert the state and + * update the channels. + * + * The s->io_bits mask makes sure the input channels + * are '0' so that the outputs pins stay in a high + * z-state. + */ + val = ~s->state & chanmask; + val &= s->io_bits; + pcmuio_write(dev, val, asic, 0, port); + } + + /* get inverted state of the channels from the port */ + val = pcmuio_read(dev, asic, 0, port); + + /* return the true state of the channels */ + data[1] = ~val & chanmask; + + return insn->n; +} + +static int pcmuio_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int asic = pcmuio_subdevice_to_asic(s); + int port = pcmuio_subdevice_to_port(s); + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + if (data[0] == INSN_CONFIG_DIO_INPUT) + pcmuio_write(dev, s->io_bits, asic, 0, port); + + return insn->n; +} + +static void pcmuio_reset(struct comedi_device *dev) +{ + const struct pcmuio_board *board = dev->board_ptr; + int asic; + + for (asic = 0; asic < board->num_asics; ++asic) { + /* first, clear all the DIO port bits */ + pcmuio_write(dev, 0, asic, 0, 0); + pcmuio_write(dev, 0, asic, 0, 3); + + /* Next, clear all the paged registers for each page */ + pcmuio_write(dev, 0, asic, PCMUIO_PAGE_POL, 0); + pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0); + pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0); + } +} + +/* chip->spinlock is already locked */ +static void pcmuio_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcmuio_private *devpriv = dev->private; + int asic = pcmuio_subdevice_to_asic(s); + struct pcmuio_asic *chip = &devpriv->asics[asic]; + + chip->enabled_mask = 0; + chip->active = 0; + s->async->inttrig = NULL; + + /* disable all intrs for this subdev.. */ + pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0); +} + +static void pcmuio_handle_intr_subdev(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int triggered) +{ + struct pcmuio_private *devpriv = dev->private; + int asic = pcmuio_subdevice_to_asic(s); + struct pcmuio_asic *chip = &devpriv->asics[asic]; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int val = 0; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&chip->spinlock, flags); + + if (!chip->active) + goto done; + + if (!(triggered & chip->enabled_mask)) + goto done; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + if (triggered & (1 << chan)) + val |= (1 << i); + } + + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; + +done: + spin_unlock_irqrestore(&chip->spinlock, flags); + + comedi_handle_events(dev, s); +} + +static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic) +{ + /* there are could be two asics so we can't use dev->read_subdev */ + struct comedi_subdevice *s = &dev->subdevices[asic * 2]; + unsigned long iobase = pcmuio_asic_iobase(dev, asic); + unsigned int val; + + /* are there any interrupts pending */ + val = inb(iobase + PCMUIO_INT_PENDING_REG) & 0x07; + if (!val) + return 0; + + /* get, and clear, the pending interrupts */ + val = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, 0); + pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0); + + /* handle the pending interrupts */ + pcmuio_handle_intr_subdev(dev, s, val); + + return 1; +} + +static irqreturn_t pcmuio_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pcmuio_private *devpriv = dev->private; + int handled = 0; + + if (irq == dev->irq) + handled += pcmuio_handle_asic_interrupt(dev, 0); + if (irq == devpriv->irq2) + handled += pcmuio_handle_asic_interrupt(dev, 1); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +/* chip->spinlock is already locked */ +static void pcmuio_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcmuio_private *devpriv = dev->private; + int asic = pcmuio_subdevice_to_asic(s); + struct pcmuio_asic *chip = &devpriv->asics[asic]; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int bits = 0; + unsigned int pol_bits = 0; + int i; + + chip->enabled_mask = 0; + chip->active = 1; + if (cmd->chanlist) { + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chanspec = cmd->chanlist[i]; + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + + bits |= (1 << chan); + pol_bits |= ((aref || range) ? 1 : 0) << chan; + } + } + bits &= ((1 << s->n_chan) - 1); + chip->enabled_mask = bits; + + /* set pol and enab intrs for this subdev.. */ + pcmuio_write(dev, pol_bits, asic, PCMUIO_PAGE_POL, 0); + pcmuio_write(dev, bits, asic, PCMUIO_PAGE_ENAB, 0); +} + +static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcmuio_private *devpriv = dev->private; + int asic = pcmuio_subdevice_to_asic(s); + struct pcmuio_asic *chip = &devpriv->asics[asic]; + unsigned long flags; + + spin_lock_irqsave(&chip->spinlock, flags); + if (chip->active) + pcmuio_stop_intr(dev, s); + spin_unlock_irqrestore(&chip->spinlock, flags); + + return 0; +} + +static int pcmuio_inttrig_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct pcmuio_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int asic = pcmuio_subdevice_to_asic(s); + struct pcmuio_asic *chip = &devpriv->asics[asic]; + unsigned long flags; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&chip->spinlock, flags); + s->async->inttrig = NULL; + if (chip->active) + pcmuio_start_intr(dev, s); + + spin_unlock_irqrestore(&chip->spinlock, flags); + + return 1; +} + +/* + * 'do_cmd' function for an 'INTERRUPT' subdevice. + */ +static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcmuio_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int asic = pcmuio_subdevice_to_asic(s); + struct pcmuio_asic *chip = &devpriv->asics[asic]; + unsigned long flags; + + spin_lock_irqsave(&chip->spinlock, flags); + chip->active = 1; + + /* Set up start of acquisition. */ + if (cmd->start_src == TRIG_INT) + s->async->inttrig = pcmuio_inttrig_start_intr; + else /* TRIG_NOW */ + pcmuio_start_intr(dev, s); + + spin_unlock_irqrestore(&chip->spinlock, flags); + + return 0; +} + +static int pcmuio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} + +static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcmuio_board *board = dev->board_ptr; + struct comedi_subdevice *s; + struct pcmuio_private *devpriv; + int ret; + int i; + + ret = comedi_request_region(dev, it->options[0], + board->num_asics * PCMUIO_ASIC_IOSIZE); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + for (i = 0; i < PCMUIO_MAX_ASICS; ++i) { + struct pcmuio_asic *chip = &devpriv->asics[i]; + + spin_lock_init(&chip->pagelock); + spin_lock_init(&chip->spinlock); + } + + pcmuio_reset(dev); + + if (it->options[1]) { + /* request the irq for the 1st asic */ + ret = request_irq(it->options[1], pcmuio_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + dev->irq = it->options[1]; + } + + if (board->num_asics == 2) { + if (it->options[2] == dev->irq) { + /* the same irq (or none) is used by both asics */ + devpriv->irq2 = it->options[2]; + } else if (it->options[2]) { + /* request the irq for the 2nd asic */ + ret = request_irq(it->options[2], pcmuio_interrupt, 0, + dev->board_name, dev); + if (ret == 0) + devpriv->irq2 = it->options[2]; + } + } + + ret = comedi_alloc_subdevices(dev, board->num_asics * 2); + if (ret) + return ret; + + for (i = 0; i < dev->n_subdevices; ++i) { + s = &dev->subdevices[i]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pcmuio_dio_insn_bits; + s->insn_config = pcmuio_dio_insn_config; + + /* subdevices 0 and 2 can support interrupts */ + if ((i == 0 && dev->irq) || (i == 2 && devpriv->irq2)) { + /* setup the interrupt subdevice */ + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | + SDF_PACKED; + s->len_chanlist = s->n_chan; + s->cancel = pcmuio_cancel; + s->do_cmd = pcmuio_cmd; + s->do_cmdtest = pcmuio_cmdtest; + } + } + + return 0; +} + +static void pcmuio_detach(struct comedi_device *dev) +{ + struct pcmuio_private *devpriv = dev->private; + + if (devpriv) { + pcmuio_reset(dev); + + /* free the 2nd irq if used, the core will free the 1st one */ + if (devpriv->irq2 && devpriv->irq2 != dev->irq) + free_irq(devpriv->irq2, dev); + } + comedi_legacy_detach(dev); +} + +static struct comedi_driver pcmuio_driver = { + .driver_name = "pcmuio", + .module = THIS_MODULE, + .attach = pcmuio_attach, + .detach = pcmuio_detach, + .board_name = &pcmuio_boards[0].name, + .offset = sizeof(struct pcmuio_board), + .num_names = ARRAY_SIZE(pcmuio_boards), +}; +module_comedi_driver(pcmuio_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/plx9052.h b/drivers/comedi/drivers/plx9052.h new file mode 100644 index 000000000..e68a7afef --- /dev/null +++ b/drivers/comedi/drivers/plx9052.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Definitions for the PLX-9052 PCI interface chip + * + * Copyright (C) 2002 MEV Ltd. <https://www.mev.co.uk/> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +#ifndef _PLX9052_H_ +#define _PLX9052_H_ + +/* + * INTCSR - Interrupt Control/Status register + */ +#define PLX9052_INTCSR 0x4c +#define PLX9052_INTCSR_LI1ENAB BIT(0) /* LI1 enabled */ +#define PLX9052_INTCSR_LI1POL BIT(1) /* LI1 active high */ +#define PLX9052_INTCSR_LI1STAT BIT(2) /* LI1 active */ +#define PLX9052_INTCSR_LI2ENAB BIT(3) /* LI2 enabled */ +#define PLX9052_INTCSR_LI2POL BIT(4) /* LI2 active high */ +#define PLX9052_INTCSR_LI2STAT BIT(5) /* LI2 active */ +#define PLX9052_INTCSR_PCIENAB BIT(6) /* PCIINT enabled */ +#define PLX9052_INTCSR_SOFTINT BIT(7) /* generate soft int */ +#define PLX9052_INTCSR_LI1SEL BIT(8) /* LI1 edge */ +#define PLX9052_INTCSR_LI2SEL BIT(9) /* LI2 edge */ +#define PLX9052_INTCSR_LI1CLRINT BIT(10) /* LI1 clear int */ +#define PLX9052_INTCSR_LI2CLRINT BIT(11) /* LI2 clear int */ +#define PLX9052_INTCSR_ISAMODE BIT(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 BIT(0) /* UIO0 or WAITO# select */ +#define PLX9052_CNTRL_UIO0_DIR BIT(1) /* UIO0 direction */ +#define PLX9052_CNTRL_UIO0_DATA BIT(2) /* UIO0 data */ +#define PLX9052_CNTRL_LLOCKO BIT(3) /* UIO1 or LLOCKo# select */ +#define PLX9052_CNTRL_UIO1_DIR BIT(4) /* UIO1 direction */ +#define PLX9052_CNTRL_UIO1_DATA BIT(5) /* UIO1 data */ +#define PLX9052_CNTRL_CS2 BIT(6) /* UIO2 or CS2# select */ +#define PLX9052_CNTRL_UIO2_DIR BIT(7) /* UIO2 direction */ +#define PLX9052_CNTRL_UIO2_DATA BIT(8) /* UIO2 data */ +#define PLX9052_CNTRL_CS3 BIT(9) /* UIO3 or CS3# select */ +#define PLX9052_CNTRL_UIO3_DIR BIT(10) /* UIO3 direction */ +#define PLX9052_CNTRL_UIO3_DATA BIT(11) /* UIO3 data */ +#define PLX9052_CNTRL_PCIBAR(x) (((x) & 0x3) << 12) +#define PLX9052_CNTRL_PCIBAR01 PLX9052_CNTRL_PCIBAR(0) /* mem and IO */ +#define PLX9052_CNTRL_PCIBAR0 PLX9052_CNTRL_PCIBAR(1) /* mem only */ +#define PLX9052_CNTRL_PCIBAR1 PLX9052_CNTRL_PCIBAR(2) /* IO only */ +#define PLX9052_CNTRL_PCI2_1_FEATURES BIT(14) /* PCI v2.1 features enabled */ +#define PLX9052_CNTRL_PCI_R_W_FLUSH BIT(15) /* read w/write flush mode */ +#define PLX9052_CNTRL_PCI_R_NO_FLUSH BIT(16) /* read no flush mode */ +#define PLX9052_CNTRL_PCI_R_NO_WRITE BIT(17) /* read no write mode */ +#define PLX9052_CNTRL_PCI_W_RELEASE BIT(18) /* write release bus mode */ +#define PLX9052_CNTRL_RETRY_CLKS(x) (((x) & 0xf) << 19) /* retry clks */ +#define PLX9052_CNTRL_LOCK_ENAB BIT(23) /* slave LOCK# enable */ +#define PLX9052_CNTRL_EEPROM_MASK (0x1f << 24) /* EEPROM bits */ +#define PLX9052_CNTRL_EEPROM_CLK BIT(24) /* EEPROM clock */ +#define PLX9052_CNTRL_EEPROM_CS BIT(25) /* EEPROM chip select */ +#define PLX9052_CNTRL_EEPROM_DOUT BIT(26) /* EEPROM write bit */ +#define PLX9052_CNTRL_EEPROM_DIN BIT(27) /* EEPROM read bit */ +#define PLX9052_CNTRL_EEPROM_PRESENT BIT(28) /* EEPROM present */ +#define PLX9052_CNTRL_RELOAD_CFG BIT(29) /* reload configuration */ +#define PLX9052_CNTRL_PCI_RESET BIT(30) /* PCI adapter reset */ +#define PLX9052_CNTRL_MASK_REV BIT(31) /* mask revision */ + +#endif /* _PLX9052_H_ */ diff --git a/drivers/comedi/drivers/plx9080.h b/drivers/comedi/drivers/plx9080.h new file mode 100644 index 000000000..aa0eda5a8 --- /dev/null +++ b/drivers/comedi/drivers/plx9080.h @@ -0,0 +1,656 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * plx9080.h + * + * Copyright (C) 2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net> + * + ******************************************************************** + * + * Copyright (C) 1999 RG Studio s.c. + * Written by Krzysztof Halasa <khc@rgstudio.com.pl> + * + * Portions (C) SBE Inc., used by permission. + */ + +#ifndef __COMEDI_PLX9080_H +#define __COMEDI_PLX9080_H + +#include <linux/compiler.h> +#include <linux/types.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/io.h> + +/** + * struct plx_dma_desc - DMA descriptor format for PLX PCI 9080 + * @pci_start_addr: PCI Bus address for transfer (DMAPADR). + * @local_start_addr: Local Bus address for transfer (DMALADR). + * @transfer_size: Transfer size in bytes (max 8 MiB) (DMASIZ). + * @next: Address of next descriptor + flags (DMADPR). + * + * Describes the format of a scatter-gather DMA descriptor for the PLX + * PCI 9080. All members are raw, little-endian register values that + * will be transferred by the DMA engine from local or PCI memory into + * corresponding registers for the DMA channel. + * + * The DMA descriptors must be aligned on a 16-byte boundary. Bits 3:0 + * of @next contain flags describing the address space of the next + * descriptor (local or PCI), an "end of chain" marker, an "interrupt on + * terminal count" bit, and a data transfer direction. + */ +struct plx_dma_desc { + __le32 pci_start_addr; + __le32 local_start_addr; + __le32 transfer_size; + __le32 next; +}; + +/* + * Register Offsets and Bit Definitions + */ + +/* Local Address Space 0 Range Register */ +#define PLX_REG_LAS0RR 0x0000 +/* Local Address Space 1 Range Register */ +#define PLX_REG_LAS1RR 0x00f0 + +#define PLX_LASRR_IO BIT(0) /* Map to: 1=I/O, 0=Mem */ +#define PLX_LASRR_MLOC_ANY32 (BIT(1) * 0) /* Locate anywhere in 32 bit */ +#define PLX_LASRR_MLOC_LT1MB (BIT(1) * 1) /* Locate in 1st meg */ +#define PLX_LASRR_MLOC_ANY64 (BIT(1) * 2) /* Locate anywhere in 64 bit */ +#define PLX_LASRR_MLOC_MASK GENMASK(2, 1) /* Memory location bits */ +#define PLX_LASRR_PREFETCH BIT(3) /* Memory is prefetchable */ +/* bits that specify range for memory space decode bits */ +#define PLX_LASRR_MEM_MASK GENMASK(31, 4) +/* bits that specify range for i/o space decode bits */ +#define PLX_LASRR_IO_MASK GENMASK(31, 2) + +/* Local Address Space 0 Local Base Address (Remap) Register */ +#define PLX_REG_LAS0BA 0x0004 +/* Local Address Space 1 Local Base Address (Remap) Register */ +#define PLX_REG_LAS1BA 0x00f4 + +#define PLX_LASBA_EN BIT(0) /* Enable slave decode */ +/* bits that specify local base address for memory space */ +#define PLX_LASBA_MEM_MASK GENMASK(31, 4) +/* bits that specify local base address for i/o space */ +#define PLX_LASBA_IO_MASK GENMASK(31, 2) + +/* Mode/Arbitration Register */ +#define PLX_REG_MARBR 0x0008 +/* DMA Arbitration Register (alias of MARBR). */ +#define PLX_REG_DMAARB 0x00ac + +/* Local Bus Latency Timer */ +#define PLX_MARBR_LT(x) (BIT(0) * ((x) & 0xff)) +#define PLX_MARBR_LT_MASK GENMASK(7, 0) +#define PLX_MARBR_TO_LT(r) ((r) & PLX_MARBR_LT_MASK) +/* Local Bus Pause Timer */ +#define PLX_MARBR_PT(x) (BIT(8) * ((x) & 0xff)) +#define PLX_MARBR_PT_MASK GENMASK(15, 8) +#define PLX_MARBR_TO_PT(r) (((r) & PLX_MARBR_PT_MASK) >> 8) +/* Local Bus Latency Timer Enable */ +#define PLX_MARBR_LTEN BIT(16) +/* Local Bus Pause Timer Enable */ +#define PLX_MARBR_PTEN BIT(17) +/* Local Bus BREQ Enable */ +#define PLX_MARBR_BREQEN BIT(18) +/* DMA Channel Priority */ +#define PLX_MARBR_PRIO_ROT (BIT(19) * 0) /* Rotational priority */ +#define PLX_MARBR_PRIO_DMA0 (BIT(19) * 1) /* DMA channel 0 has priority */ +#define PLX_MARBR_PRIO_DMA1 (BIT(19) * 2) /* DMA channel 1 has priority */ +#define PLX_MARBR_PRIO_MASK GENMASK(20, 19) +/* Local Bus Direct Slave Give Up Bus Mode */ +#define PLX_MARBR_DSGUBM BIT(21) +/* Direct Slace LLOCKo# Enable */ +#define PLX_MARBR_DSLLOCKOEN BIT(22) +/* PCI Request Mode */ +#define PLX_MARBR_PCIREQM BIT(23) +/* PCI Specification v2.1 Mode */ +#define PLX_MARBR_PCIV21M BIT(24) +/* PCI Read No Write Mode */ +#define PLX_MARBR_PCIRNWM BIT(25) +/* PCI Read with Write Flush Mode */ +#define PLX_MARBR_PCIRWFM BIT(26) +/* Gate Local Bus Latency Timer with BREQ */ +#define PLX_MARBR_GLTBREQ BIT(27) +/* PCI Read No Flush Mode */ +#define PLX_MARBR_PCIRNFM BIT(28) +/* + * Make reads from PCI Configuration register 0 return Subsystem ID and + * Subsystem Vendor ID instead of Device ID and Vendor ID + */ +#define PLX_MARBR_SUBSYSIDS BIT(29) + +/* Big/Little Endian Descriptor Register */ +#define PLX_REG_BIGEND 0x000c + +/* Configuration Register Big Endian Mode */ +#define PLX_BIGEND_CONFIG BIT(0) +/* Direct Master Big Endian Mode */ +#define PLX_BIGEND_DM BIT(1) +/* Direct Slave Address Space 0 Big Endian Mode */ +#define PLX_BIGEND_DSAS0 BIT(2) +/* Direct Slave Expansion ROM Big Endian Mode */ +#define PLX_BIGEND_EROM BIT(3) +/* Big Endian Byte Lane Mode - use most significant byte lanes */ +#define PLX_BIGEND_BEBLM BIT(4) +/* Direct Slave Address Space 1 Big Endian Mode */ +#define PLX_BIGEND_DSAS1 BIT(5) +/* DMA Channel 1 Big Endian Mode */ +#define PLX_BIGEND_DMA1 BIT(6) +/* DMA Channel 0 Big Endian Mode */ +#define PLX_BIGEND_DMA0 BIT(7) +/* DMA Channel N Big Endian Mode (N <= 1) */ +#define PLX_BIGEND_DMA(n) ((n) ? PLX_BIGEND_DMA1 : PLX_BIGEND_DMA0) + +/* + * 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. + */ + +/* Expansion ROM Range Register */ +#define PLX_REG_EROMRR 0x0010 +/* Expansion ROM Local Base Address (Remap) Register */ +#define PLX_REG_EROMBA 0x0014 + +/* Local Address Space 0/Expansion ROM Bus Region Descriptor Register */ +#define PLX_REG_LBRD0 0x0018 +/* Local Address Space 1 Bus Region Descriptor Register */ +#define PLX_REG_LBRD1 0x00f8 + +/* Memory Space Local Bus Width */ +#define PLX_LBRD_MSWIDTH_8 (BIT(0) * 0) /* 8 bits wide */ +#define PLX_LBRD_MSWIDTH_16 (BIT(0) * 1) /* 16 bits wide */ +#define PLX_LBRD_MSWIDTH_32 (BIT(0) * 2) /* 32 bits wide */ +#define PLX_LBRD_MSWIDTH_32A (BIT(0) * 3) /* 32 bits wide */ +#define PLX_LBRD_MSWIDTH_MASK GENMASK(1, 0) +/* Memory Space Internal Wait States */ +#define PLX_LBRD_MSIWS(x) (BIT(2) * ((x) & 0xf)) +#define PLX_LBRD_MSIWS_MASK GENMASK(5, 2) +#define PLX_LBRD_TO_MSIWS(r) (((r) & PLS_LBRD_MSIWS_MASK) >> 2) +/* Memory Space Ready Input Enable */ +#define PLX_LBRD_MSREADYIEN BIT(6) +/* Memory Space BTERM# Input Enable */ +#define PLX_LBRD_MSBTERMIEN BIT(7) +/* Memory Space 0 Prefetch Disable (LBRD0 only) */ +#define PLX_LBRD0_MSPREDIS BIT(8) +/* Memory Space 1 Burst Enable (LBRD1 only) */ +#define PLX_LBRD1_MSBURSTEN BIT(8) +/* Expansion ROM Space Prefetch Disable (LBRD0 only) */ +#define PLX_LBRD0_EROMPREDIS BIT(9) +/* Memory Space 1 Prefetch Disable (LBRD1 only) */ +#define PLX_LBRD1_MSPREDIS BIT(9) +/* Read Prefetch Count Enable */ +#define PLX_LBRD_RPFCOUNTEN BIT(10) +/* Prefetch Counter */ +#define PLX_LBRD_PFCOUNT(x) (BIT(11) * ((x) & 0xf)) +#define PLX_LBRD_PFCOUNT_MASK GENMASK(14, 11) +#define PLX_LBRD_TO_PFCOUNT(r) (((r) & PLX_LBRD_PFCOUNT_MASK) >> 11) +/* Expansion ROM Space Local Bus Width (LBRD0 only) */ +#define PLX_LBRD0_EROMWIDTH_8 (BIT(16) * 0) /* 8 bits wide */ +#define PLX_LBRD0_EROMWIDTH_16 (BIT(16) * 1) /* 16 bits wide */ +#define PLX_LBRD0_EROMWIDTH_32 (BIT(16) * 2) /* 32 bits wide */ +#define PLX_LBRD0_EROMWIDTH_32A (BIT(16) * 3) /* 32 bits wide */ +#define PLX_LBRD0_EROMWIDTH_MASK GENMASK(17, 16) +/* Expansion ROM Space Internal Wait States (LBRD0 only) */ +#define PLX_LBRD0_EROMIWS(x) (BIT(18) * ((x) & 0xf)) +#define PLX_LBRD0_EROMIWS_MASK GENMASK(21, 18) +#define PLX_LBRD0_TO_EROMIWS(r) (((r) & PLX_LBRD0_EROMIWS_MASK) >> 18) +/* Expansion ROM Space Ready Input Enable (LBDR0 only) */ +#define PLX_LBRD0_EROMREADYIEN BIT(22) +/* Expansion ROM Space BTERM# Input Enable (LBRD0 only) */ +#define PLX_LBRD0_EROMBTERMIEN BIT(23) +/* Memory Space 0 Burst Enable (LBRD0 only) */ +#define PLX_LBRD0_MSBURSTEN BIT(24) +/* Extra Long Load From Serial EEPROM (LBRD0 only) */ +#define PLX_LBRD0_EELONGLOAD BIT(25) +/* Expansion ROM Space Burst Enable (LBRD0 only) */ +#define PLX_LBRD0_EROMBURSTEN BIT(26) +/* Direct Slave PCI Write Mode - assert TRDY# when FIFO full (LBRD0 only) */ +#define PLX_LBRD0_DSWMTRDY BIT(27) +/* PCI Target Retry Delay Clocks / 8 (LBRD0 only) */ +#define PLX_LBRD0_TRDELAY(x) (BIT(28) * ((x) & 0xF)) +#define PLX_LBRD0_TRDELAY_MASK GENMASK(31, 28) +#define PLX_LBRD0_TO_TRDELAY(r) (((r) & PLX_LBRD0_TRDELAY_MASK) >> 28) + +/* Local Range Register for Direct Master to PCI */ +#define PLX_REG_DMRR 0x001c + +/* Local Bus Base Address Register for Direct Master to PCI Memory */ +#define PLX_REG_DMLBAM 0x0020 + +/* Local Base Address Register for Direct Master to PCI IO/CFG */ +#define PLX_REG_DMLBAI 0x0024 + +/* PCI Base Address (Remap) Register for Direct Master to PCI Memory */ +#define PLX_REG_DMPBAM 0x0028 + +/* Direct Master Memory Access Enable */ +#define PLX_DMPBAM_MEMACCEN BIT(0) +/* Direct Master I/O Access Enable */ +#define PLX_DMPBAM_IOACCEN BIT(1) +/* LLOCK# Input Enable */ +#define PLX_DMPBAM_LLOCKIEN BIT(2) +/* Direct Master Read Prefetch Size Control (bits 12, 3) */ +#define PLX_DMPBAM_RPSIZE_CONT ((BIT(12) * 0) | (BIT(3) * 0)) +#define PLX_DMPBAM_RPSIZE_4 ((BIT(12) * 0) | (BIT(3) * 1)) +#define PLX_DMPBAM_RPSIZE_8 ((BIT(12) * 1) | (BIT(3) * 0)) +#define PLX_DMPBAM_RPSIZE_16 ((BIT(12) * 1) | (BIT(3) * 1)) +#define PLX_DMPBAM_RPSIZE_MASK (BIT(12) | BIT(3)) +/* Direct Master PCI Read Mode - deassert IRDY when FIFO full */ +#define PLX_DMPBAM_RMIRDY BIT(4) +/* Programmable Almost Full Level (bits 10, 8:5) */ +#define PLX_DMPBAM_PAFL(x) ((BIT(10) * !!((x) & 0x10)) | \ + (BIT(5) * ((x) & 0xf))) +#define PLX_DMPBAM_TO_PAFL(v) ((((BIT(10) & (v)) >> 1) | \ + (GENMASK(8, 5) & (v))) >> 5) +#define PLX_DMPBAM_PAFL_MASK (BIT(10) | GENMASK(8, 5)) +/* Write And Invalidate Mode */ +#define PLX_DMPBAM_WIM BIT(9) +/* Direct Master Prefetch Limit */ +#define PLX_DBPBAM_PFLIMIT BIT(11) +/* I/O Remap Select */ +#define PLX_DMPBAM_IOREMAPSEL BIT(13) +/* Direct Master Write Delay */ +#define PLX_DMPBAM_WDELAY_NONE (BIT(14) * 0) +#define PLX_DMPBAM_WDELAY_4 (BIT(14) * 1) +#define PLX_DMPBAM_WDELAY_8 (BIT(14) * 2) +#define PLX_DMPBAM_WDELAY_16 (BIT(14) * 3) +#define PLX_DMPBAM_WDELAY_MASK GENMASK(15, 14) +/* Remap of Local-to-PCI Space Into PCI Address Space */ +#define PLX_DMPBAM_REMAP_MASK GENMASK(31, 16) + +/* PCI Configuration Address Register for Direct Master to PCI IO/CFG */ +#define PLX_REG_DMCFGA 0x002c + +/* Congiguration Type */ +#define PLX_DMCFGA_TYPE0 (BIT(0) * 0) +#define PLX_DMCFGA_TYPE1 (BIT(0) * 1) +#define PLX_DMCFGA_TYPE_MASK GENMASK(1, 0) +/* Register Number */ +#define PLX_DMCFGA_REGNUM(x) (BIT(2) * ((x) & 0x3f)) +#define PLX_DMCFGA_REGNUM_MASK GENMASK(7, 2) +#define PLX_DMCFGA_TO_REGNUM(r) (((r) & PLX_DMCFGA_REGNUM_MASK) >> 2) +/* Function Number */ +#define PLX_DMCFGA_FUNCNUM(x) (BIT(8) * ((x) & 0x7)) +#define PLX_DMCFGA_FUNCNUM_MASK GENMASK(10, 8) +#define PLX_DMCFGA_TO_FUNCNUM(r) (((r) & PLX_DMCFGA_FUNCNUM_MASK) >> 8) +/* Device Number */ +#define PLX_DMCFGA_DEVNUM(x) (BIT(11) * ((x) & 0x1f)) +#define PLX_DMCFGA_DEVNUM_MASK GENMASK(15, 11) +#define PLX_DMCFGA_TO_DEVNUM(r) (((r) & PLX_DMCFGA_DEVNUM_MASK) >> 11) +/* Bus Number */ +#define PLX_DMCFGA_BUSNUM(x) (BIT(16) * ((x) & 0xff)) +#define PLX_DMCFGA_BUSNUM_MASK GENMASK(23, 16) +#define PLX_DMCFGA_TO_BUSNUM(r) (((r) & PLX_DMCFGA_BUSNUM_MASK) >> 16) +/* Configuration Enable */ +#define PLX_DMCFGA_CONFIGEN BIT(31) + +/* + * Mailbox Register N (N <= 7) + * + * Note that if the I2O feature is enabled (QSR[0] is set), Mailbox Register 0 + * is replaced by the Inbound Queue Port, and Mailbox Register 1 is replaced + * by the Outbound Queue Port. However, Mailbox Register 0 and 1 are always + * accessible at alternative offsets if the I2O feature is enabled. + */ +#define PLX_REG_MBOX(n) (0x0040 + (n) * 4) +#define PLX_REG_MBOX0 PLX_REG_MBOX(0) +#define PLX_REG_MBOX1 PLX_REG_MBOX(1) +#define PLX_REG_MBOX2 PLX_REG_MBOX(2) +#define PLX_REG_MBOX3 PLX_REG_MBOX(3) +#define PLX_REG_MBOX4 PLX_REG_MBOX(4) +#define PLX_REG_MBOX5 PLX_REG_MBOX(5) +#define PLX_REG_MBOX6 PLX_REG_MBOX(6) +#define PLX_REG_MBOX7 PLX_REG_MBOX(7) + +/* Alternative offsets for Mailbox Registers 0 and 1 (in case I2O is enabled) */ +#define PLX_REG_ALT_MBOX(n) ((n) < 2 ? 0x0078 + (n) * 4 : PLX_REG_MBOX(n)) +#define PLX_REG_ALT_MBOX0 PLX_REG_ALT_MBOX(0) +#define PLX_REG_ALT_MBOX1 PLX_REG_ALT_MBOX(1) + +/* PCI-to-Local Doorbell Register */ +#define PLX_REG_P2LDBELL 0x0060 + +/* Local-to-PCI Doorbell Register */ +#define PLX_REG_L2PDBELL 0x0064 + +/* Interrupt Control/Status Register */ +#define PLX_REG_INTCSR 0x0068 + +/* Enable Local Bus LSERR# when PCI Bus Target Abort or Master Abort occurs */ +#define PLX_INTCSR_LSEABORTEN BIT(0) +/* Enable Local Bus LSERR# when PCI parity error occurs */ +#define PLX_INTCSR_LSEPARITYEN BIT(1) +/* Generate PCI Bus SERR# when set to 1 */ +#define PLX_INTCSR_GENSERR BIT(2) +/* Mailbox Interrupt Enable (local bus interrupts on PCI write to MBOX0-3) */ +#define PLX_INTCSR_MBIEN BIT(3) +/* PCI Interrupt Enable */ +#define PLX_INTCSR_PIEN BIT(8) +/* PCI Doorbell Interrupt Enable */ +#define PLX_INTCSR_PDBIEN BIT(9) +/* PCI Abort Interrupt Enable */ +#define PLX_INTCSR_PABORTIEN BIT(10) +/* PCI Local Interrupt Enable */ +#define PLX_INTCSR_PLIEN BIT(11) +/* Retry Abort Enable (for diagnostic purposes only) */ +#define PLX_INTCSR_RAEN BIT(12) +/* PCI Doorbell Interrupt Active (read-only) */ +#define PLX_INTCSR_PDBIA BIT(13) +/* PCI Abort Interrupt Active (read-only) */ +#define PLX_INTCSR_PABORTIA BIT(14) +/* Local Interrupt (LINTi#) Active (read-only) */ +#define PLX_INTCSR_PLIA BIT(15) +/* Local Interrupt Output (LINTo#) Enable */ +#define PLX_INTCSR_LIOEN BIT(16) +/* Local Doorbell Interrupt Enable */ +#define PLX_INTCSR_LDBIEN BIT(17) +/* DMA Channel 0 Interrupt Enable */ +#define PLX_INTCSR_DMA0IEN BIT(18) +/* DMA Channel 1 Interrupt Enable */ +#define PLX_INTCSR_DMA1IEN BIT(19) +/* DMA Channel N Interrupt Enable (N <= 1) */ +#define PLX_INTCSR_DMAIEN(n) ((n) ? PLX_INTCSR_DMA1IEN : PLX_INTCSR_DMA0IEN) +/* Local Doorbell Interrupt Active (read-only) */ +#define PLX_INTCSR_LDBIA BIT(20) +/* DMA Channel 0 Interrupt Active (read-only) */ +#define PLX_INTCSR_DMA0IA BIT(21) +/* DMA Channel 1 Interrupt Active (read-only) */ +#define PLX_INTCSR_DMA1IA BIT(22) +/* DMA Channel N Interrupt Active (N <= 1) (read-only) */ +#define PLX_INTCSR_DMAIA(n) ((n) ? PLX_INTCSR_DMA1IA : PLX_INTCSR_DMA0IA) +/* BIST Interrupt Active (read-only) */ +#define PLX_INTCSR_BISTIA BIT(23) +/* Direct Master Not Bus Master During Master Or Target Abort (read-only) */ +#define PLX_INTCSR_ABNOTDM BIT(24) +/* DMA Channel 0 Not Bus Master During Master Or Target Abort (read-only) */ +#define PLX_INTCSR_ABNOTDMA0 BIT(25) +/* DMA Channel 1 Not Bus Master During Master Or Target Abort (read-only) */ +#define PLX_INTCSR_ABNOTDMA1 BIT(26) +/* DMA Channel N Not Bus Master During Master Or Target Abort (read-only) */ +#define PLX_INTCSR_ABNOTDMA(n) ((n) ? PLX_INTCSR_ABNOTDMA1 \ + : PLX_INTCSR_ABNOTDMA0) +/* Target Abort Not Generated After 256 Master Retries (read-only) */ +#define PLX_INTCSR_ABNOTRETRY BIT(27) +/* PCI Wrote Mailbox 0 (enabled if bit 3 set) (read-only) */ +#define PLX_INTCSR_MB0IA BIT(28) +/* PCI Wrote Mailbox 1 (enabled if bit 3 set) (read-only) */ +#define PLX_INTCSR_MB1IA BIT(29) +/* PCI Wrote Mailbox 2 (enabled if bit 3 set) (read-only) */ +#define PLX_INTCSR_MB2IA BIT(30) +/* PCI Wrote Mailbox 3 (enabled if bit 3 set) (read-only) */ +#define PLX_INTCSR_MB3IA BIT(31) +/* PCI Wrote Mailbox N (N <= 3) (enabled if bit 3 set) (read-only) */ +#define PLX_INTCSR_MBIA(n) BIT(28 + (n)) + +/* + * Serial EEPROM Control, PCI Command Codes, User I/O Control, + * Init Control Register + */ +#define PLX_REG_CNTRL 0x006c + +/* PCI Read Command Code For DMA */ +#define PLX_CNTRL_CCRDMA(x) (BIT(0) * ((x) & 0xf)) +#define PLX_CNTRL_CCRDMA_MASK GENMASK(3, 0) +#define PLX_CNTRL_TO_CCRDMA(r) ((r) & PLX_CNTRL_CCRDMA_MASK) +#define PLX_CNTRL_CCRDMA_NORMAL PLX_CNTRL_CCRDMA(14) /* value after reset */ +/* PCI Write Command Code For DMA 0 */ +#define PLX_CNTRL_CCWDMA(x) (BIT(4) * ((x) & 0xf)) +#define PLX_CNTRL_CCWDMA_MASK GENMASK(7, 4) +#define PLX_CNTRL_TO_CCWDMA(r) (((r) & PLX_CNTRL_CCWDMA_MASK) >> 4) +#define PLX_CNTRL_CCWDMA_NORMAL PLX_CNTRL_CCWDMA(7) /* value after reset */ +/* PCI Memory Read Command Code For Direct Master */ +#define PLX_CNTRL_CCRDM(x) (BIT(8) * ((x) & 0xf)) +#define PLX_CNTRL_CCRDM_MASK GENMASK(11, 8) +#define PLX_CNTRL_TO_CCRDM(r) (((r) & PLX_CNTRL_CCRDM_MASK) >> 8) +#define PLX_CNTRL_CCRDM_NORMAL PLX_CNTRL_CCRDM(6) /* value after reset */ +/* PCI Memory Write Command Code For Direct Master */ +#define PLX_CNTRL_CCWDM(x) (BIT(12) * ((x) & 0xf)) +#define PLX_CNTRL_CCWDM_MASK GENMASK(15, 12) +#define PLX_CNTRL_TO_CCWDM(r) (((r) & PLX_CNTRL_CCWDM_MASK) >> 12) +#define PLX_CNTRL_CCWDM_NORMAL PLX_CNTRL_CCWDM(7) /* value after reset */ +/* General Purpose Output (USERO) */ +#define PLX_CNTRL_USERO BIT(16) +/* General Purpose Input (USERI) (read-only) */ +#define PLX_CNTRL_USERI BIT(17) +/* Serial EEPROM Clock Output (EESK) */ +#define PLX_CNTRL_EESK BIT(24) +/* Serial EEPROM Chip Select Output (EECS) */ +#define PLX_CNTRL_EECS BIT(25) +/* Serial EEPROM Data Write Bit (EEDI (sic)) */ +#define PLX_CNTRL_EEWB BIT(26) +/* Serial EEPROM Data Read Bit (EEDO (sic)) (read-only) */ +#define PLX_CNTRL_EERB BIT(27) +/* Serial EEPROM Present (read-only) */ +#define PLX_CNTRL_EEPRESENT BIT(28) +/* Reload Configuration Registers from EEPROM */ +#define PLX_CNTRL_EERELOAD BIT(29) +/* PCI Adapter Software Reset (asserts LRESETo#) */ +#define PLX_CNTRL_RESET BIT(30) +/* Local Init Status (read-only) */ +#define PLX_CNTRL_INITDONE BIT(31) +/* + * Combined command code stuff for convenience. + */ +#define PLX_CNTRL_CC_MASK \ + (PLX_CNTRL_CCRDMA_MASK | PLX_CNTRL_CCWDMA_MASK | \ + PLX_CNTRL_CCRDM_MASK | PLX_CNTRL_CCWDM_MASK) +#define PLX_CNTRL_CC_NORMAL \ + (PLX_CNTRL_CCRDMA_NORMAL | PLX_CNTRL_CCWDMA_NORMAL | \ + PLX_CNTRL_CCRDM_NORMAL | PLX_CNTRL_CCWDM_NORMAL) /* val after reset */ + +/* PCI Permanent Configuration ID Register (hard-coded PLX vendor and device) */ +#define PLX_REG_PCIHIDR 0x0070 + +/* Hard-coded ID for PLX PCI 9080 */ +#define PLX_PCIHIDR_9080 0x908010b5 + +/* PCI Permanent Revision ID Register (hard-coded silicon revision) (8-bit). */ +#define PLX_REG_PCIHREV 0x0074 + +/* DMA Channel N Mode Register (N <= 1) */ +#define PLX_REG_DMAMODE(n) ((n) ? PLX_REG_DMAMODE1 : PLX_REG_DMAMODE0) +#define PLX_REG_DMAMODE0 0x0080 +#define PLX_REG_DMAMODE1 0x0094 + +/* Local Bus Width */ +#define PLX_DMAMODE_WIDTH_8 (BIT(0) * 0) /* 8 bits wide */ +#define PLX_DMAMODE_WIDTH_16 (BIT(0) * 1) /* 16 bits wide */ +#define PLX_DMAMODE_WIDTH_32 (BIT(0) * 2) /* 32 bits wide */ +#define PLX_DMAMODE_WIDTH_32A (BIT(0) * 3) /* 32 bits wide */ +#define PLX_DMAMODE_WIDTH_MASK GENMASK(1, 0) +/* Internal Wait States */ +#define PLX_DMAMODE_IWS(x) (BIT(2) * ((x) & 0xf)) +#define PLX_DMAMODE_IWS_MASK GENMASK(5, 2) +#define PLX_DMAMODE_TO_IWS(r) (((r) & PLX_DMAMODE_IWS_MASK) >> 2) +/* Ready Input Enable */ +#define PLX_DMAMODE_READYIEN BIT(6) +/* BTERM# Input Enable */ +#define PLX_DMAMODE_BTERMIEN BIT(7) +/* Local Burst Enable */ +#define PLX_DMAMODE_BURSTEN BIT(8) +/* Chaining Enable */ +#define PLX_DMAMODE_CHAINEN BIT(9) +/* Done Interrupt Enable */ +#define PLX_DMAMODE_DONEIEN BIT(10) +/* Hold Local Address Constant */ +#define PLX_DMAMODE_LACONST BIT(11) +/* Demand Mode */ +#define PLX_DMAMODE_DEMAND BIT(12) +/* Write And Invalidate Mode */ +#define PLX_DMAMODE_WINVALIDATE BIT(13) +/* DMA EOT Enable - enables EOT0# or EOT1# input pin */ +#define PLX_DMAMODE_EOTEN BIT(14) +/* DMA Stop Data Transfer Mode - 0:BLAST; 1:EOT asserted or DREQ deasserted */ +#define PLX_DMAMODE_STOP BIT(15) +/* DMA Clear Count Mode - count in descriptor cleared on completion */ +#define PLX_DMAMODE_CLRCOUNT BIT(16) +/* DMA Channel Interrupt Select - 0:local bus interrupt; 1:PCI interrupt */ +#define PLX_DMAMODE_INTRPCI BIT(17) + +/* DMA Channel N PCI Address Register (N <= 1) */ +#define PLX_REG_DMAPADR(n) ((n) ? PLX_REG_DMAPADR1 : PLX_REG_DMAPADR0) +#define PLX_REG_DMAPADR0 0x0084 +#define PLX_REG_DMAPADR1 0x0098 + +/* DMA Channel N Local Address Register (N <= 1) */ +#define PLX_REG_DMALADR(n) ((n) ? PLX_REG_DMALADR1 : PLX_REG_DMALADR0) +#define PLX_REG_DMALADR0 0x0088 +#define PLX_REG_DMALADR1 0x009c + +/* DMA Channel N Transfer Size (Bytes) Register (N <= 1) (first 23 bits) */ +#define PLX_REG_DMASIZ(n) ((n) ? PLX_REG_DMASIZ1 : PLX_REG_DMASIZ0) +#define PLX_REG_DMASIZ0 0x008c +#define PLX_REG_DMASIZ1 0x00a0 + +/* DMA Channel N Descriptor Pointer Register (N <= 1) */ +#define PLX_REG_DMADPR(n) ((n) ? PLX_REG_DMADPR1 : PLX_REG_DMADPR0) +#define PLX_REG_DMADPR0 0x0090 +#define PLX_REG_DMADPR1 0x00a4 + +/* Descriptor Located In PCI Address Space (not local address space) */ +#define PLX_DMADPR_DESCPCI BIT(0) +/* End Of Chain */ +#define PLX_DMADPR_CHAINEND BIT(1) +/* Interrupt After Terminal Count */ +#define PLX_DMADPR_TCINTR BIT(2) +/* Direction Of Transfer Local Bus To PCI (not PCI to local) */ +#define PLX_DMADPR_XFERL2P BIT(3) +/* Next Descriptor Address Bits 31:4 (16 byte boundary) */ +#define PLX_DMADPR_NEXT_MASK GENMASK(31, 4) + +/* DMA Channel N Command/Status Register (N <= 1) (8-bit) */ +#define PLX_REG_DMACSR(n) ((n) ? PLX_REG_DMACSR1 : PLX_REG_DMACSR0) +#define PLX_REG_DMACSR0 0x00a8 +#define PLX_REG_DMACSR1 0x00a9 + +/* Channel Enable */ +#define PLX_DMACSR_ENABLE BIT(0) +/* Channel Start - write 1 to start transfer (write-only) */ +#define PLX_DMACSR_START BIT(1) +/* Channel Abort - write 1 to abort transfer (write-only) */ +#define PLX_DMACSR_ABORT BIT(2) +/* Clear Interrupt - write 1 to clear DMA Channel Interrupt (write-only) */ +#define PLX_DMACSR_CLEARINTR BIT(3) +/* Channel Done - transfer complete/inactive (read-only) */ +#define PLX_DMACSR_DONE BIT(4) + +/* DMA Threshold Register */ +#define PLX_REG_DMATHR 0x00b0 + +/* + * DMA Threshold constraints: + * (C0PLAF + 1) + (C0PLAE + 1) <= 32 + * (C0LPAF + 1) + (C0LPAE + 1) <= 32 + * (C1PLAF + 1) + (C1PLAE + 1) <= 16 + * (C1LPAF + 1) + (C1LPAE + 1) <= 16 + */ + +/* DMA Channel 0 PCI-to-Local Almost Full (divided by 2, minus 1) */ +#define PLX_DMATHR_C0PLAF(x) (BIT(0) * ((x) & 0xf)) +#define PLX_DMATHR_C0PLAF_MASK GENMASK(3, 0) +#define PLX_DMATHR_TO_C0PLAF(r) ((r) & PLX_DMATHR_C0PLAF_MASK) +/* DMA Channel 0 Local-to-PCI Almost Empty (divided by 2, minus 1) */ +#define PLX_DMATHR_C0LPAE(x) (BIT(4) * ((x) & 0xf)) +#define PLX_DMATHR_C0LPAE_MASK GENMASK(7, 4) +#define PLX_DMATHR_TO_C0LPAE(r) (((r) & PLX_DMATHR_C0LPAE_MASK) >> 4) +/* DMA Channel 0 Local-to-PCI Almost Full (divided by 2, minus 1) */ +#define PLX_DMATHR_C0LPAF(x) (BIT(8) * ((x) & 0xf)) +#define PLX_DMATHR_C0LPAF_MASK GENMASK(11, 8) +#define PLX_DMATHR_TO_C0LPAF(r) (((r) & PLX_DMATHR_C0LPAF_MASK) >> 8) +/* DMA Channel 0 PCI-to-Local Almost Empty (divided by 2, minus 1) */ +#define PLX_DMATHR_C0PLAE(x) (BIT(12) * ((x) & 0xf)) +#define PLX_DMATHR_C0PLAE_MASK GENMASK(15, 12) +#define PLX_DMATHR_TO_C0PLAE(r) (((r) & PLX_DMATHR_C0PLAE_MASK) >> 12) +/* DMA Channel 1 PCI-to-Local Almost Full (divided by 2, minus 1) */ +#define PLX_DMATHR_C1PLAF(x) (BIT(16) * ((x) & 0xf)) +#define PLX_DMATHR_C1PLAF_MASK GENMASK(19, 16) +#define PLX_DMATHR_TO_C1PLAF(r) (((r) & PLX_DMATHR_C1PLAF_MASK) >> 16) +/* DMA Channel 1 Local-to-PCI Almost Empty (divided by 2, minus 1) */ +#define PLX_DMATHR_C1LPAE(x) (BIT(20) * ((x) & 0xf)) +#define PLX_DMATHR_C1LPAE_MASK GENMASK(23, 20) +#define PLX_DMATHR_TO_C1LPAE(r) (((r) & PLX_DMATHR_C1LPAE_MASK) >> 20) +/* DMA Channel 1 Local-to-PCI Almost Full (divided by 2, minus 1) */ +#define PLX_DMATHR_C1LPAF(x) (BIT(24) * ((x) & 0xf)) +#define PLX_DMATHR_C1LPAF_MASK GENMASK(27, 24) +#define PLX_DMATHR_TO_C1LPAF(r) (((r) & PLX_DMATHR_C1LPAF_MASK) >> 24) +/* DMA Channel 1 PCI-to-Local Almost Empty (divided by 2, minus 1) */ +#define PLX_DMATHR_C1PLAE(x) (BIT(28) * ((x) & 0xf)) +#define PLX_DMATHR_C1PLAE_MASK GENMASK(31, 28) +#define PLX_DMATHR_TO_C1PLAE(r) (((r) & PLX_DMATHR_C1PLAE_MASK) >> 28) + +/* + * Messaging Queue Registers OPLFIS, OPLFIM, IQP, OQP, MQCR, QBAR, IFHPR, + * IFTPR, IPHPR, IPTPR, OFHPR, OFTPR, OPHPR, OPTPR, and QSR have been omitted. + * They are used by the I2O feature. (IQP and OQP occupy the usual offsets of + * the MBOX0 and MBOX1 registers if the I2O feature is enabled, but MBOX0 and + * MBOX1 are accessible via alternative offsets. + */ + +/* Queue Status/Control Register */ +#define PLX_REG_QSR 0x00e8 + +/* Value of QSR after reset - disables I2O feature completely. */ +#define PLX_QSR_VALUE_AFTER_RESET 0x00000050 + +/* + * 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 + +/** + * plx9080_abort_dma - Abort a PLX PCI 9080 DMA transfer + * @iobase: Remapped base address of configuration registers. + * @channel: DMA channel number (0 or 1). + * + * Aborts the DMA transfer on the channel, which must have been enabled + * and started beforehand. + * + * Return: + * %0 on success. + * -%ETIMEDOUT if timed out waiting for abort to complete. + */ +static inline int plx9080_abort_dma(void __iomem *iobase, unsigned int channel) +{ + void __iomem *dma_cs_addr; + u8 dma_status; + const int timeout = 10000; + unsigned int i; + + dma_cs_addr = iobase + PLX_REG_DMACSR(channel); + + /* abort dma transfer if necessary */ + dma_status = readb(dma_cs_addr); + if ((dma_status & PLX_DMACSR_ENABLE) == 0) + return 0; + + /* wait to make sure done bit is zero */ + for (i = 0; (dma_status & PLX_DMACSR_DONE) && i < timeout; i++) { + udelay(1); + dma_status = readb(dma_cs_addr); + } + if (i == timeout) + return -ETIMEDOUT; + + /* disable and abort channel */ + writeb(PLX_DMACSR_ABORT, dma_cs_addr); + /* wait for dma done bit */ + dma_status = readb(dma_cs_addr); + for (i = 0; (dma_status & PLX_DMACSR_DONE) == 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/comedi/drivers/quatech_daqp_cs.c b/drivers/comedi/drivers/quatech_daqp_cs.c new file mode 100644 index 000000000..2a76c75c5 --- /dev/null +++ b/drivers/comedi/drivers/quatech_daqp_cs.c @@ -0,0 +1,841 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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> + * https://www.comedi.org/ + * + * 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 + * Devices: [Quatech] DAQP-208 (daqp), DAQP-308 + * Author: Brent Baccala <baccala@freesoft.org> + * Status: works + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pcmcia.h> + +/* + * Register I/O map + * + * The D/A and timer registers can be accessed with 16-bit or 8-bit I/O + * instructions. All other registers can only use 8-bit instructions. + * + * The FIFO and scanlist registers require two 8-bit instructions to + * access the 16-bit data. Data is transferred LSB then MSB. + */ +#define DAQP_AI_FIFO_REG 0x00 + +#define DAQP_SCANLIST_REG 0x01 +#define DAQP_SCANLIST_DIFFERENTIAL BIT(14) +#define DAQP_SCANLIST_GAIN(x) (((x) & 0x3) << 12) +#define DAQP_SCANLIST_CHANNEL(x) (((x) & 0xf) << 8) +#define DAQP_SCANLIST_START BIT(7) +#define DAQP_SCANLIST_EXT_GAIN(x) (((x) & 0x3) << 4) +#define DAQP_SCANLIST_EXT_CHANNEL(x) (((x) & 0xf) << 0) + +#define DAQP_CTRL_REG 0x02 +#define DAQP_CTRL_PACER_CLK(x) (((x) & 0x3) << 6) +#define DAQP_CTRL_PACER_CLK_EXT DAQP_CTRL_PACER_CLK(0) +#define DAQP_CTRL_PACER_CLK_5MHZ DAQP_CTRL_PACER_CLK(1) +#define DAQP_CTRL_PACER_CLK_1MHZ DAQP_CTRL_PACER_CLK(2) +#define DAQP_CTRL_PACER_CLK_100KHZ DAQP_CTRL_PACER_CLK(3) +#define DAQP_CTRL_EXPANSION BIT(5) +#define DAQP_CTRL_EOS_INT_ENA BIT(4) +#define DAQP_CTRL_FIFO_INT_ENA BIT(3) +#define DAQP_CTRL_TRIG_MODE BIT(2) /* 0=one-shot; 1=continuous */ +#define DAQP_CTRL_TRIG_SRC BIT(1) /* 0=internal; 1=external */ +#define DAQP_CTRL_TRIG_EDGE BIT(0) /* 0=rising; 1=falling */ + +#define DAQP_STATUS_REG 0x02 +#define DAQP_STATUS_IDLE BIT(7) +#define DAQP_STATUS_RUNNING BIT(6) +#define DAQP_STATUS_DATA_LOST BIT(5) +#define DAQP_STATUS_END_OF_SCAN BIT(4) +#define DAQP_STATUS_FIFO_THRESHOLD BIT(3) +#define DAQP_STATUS_FIFO_FULL BIT(2) +#define DAQP_STATUS_FIFO_NEARFULL BIT(1) +#define DAQP_STATUS_FIFO_EMPTY BIT(0) +/* these bits clear when the status register is read */ +#define DAQP_STATUS_EVENTS (DAQP_STATUS_DATA_LOST | \ + DAQP_STATUS_END_OF_SCAN | \ + DAQP_STATUS_FIFO_THRESHOLD) + +#define DAQP_DI_REG 0x03 +#define DAQP_DO_REG 0x03 + +#define DAQP_PACER_LOW_REG 0x04 +#define DAQP_PACER_MID_REG 0x05 +#define DAQP_PACER_HIGH_REG 0x06 + +#define DAQP_CMD_REG 0x07 +/* the monostable bits are self-clearing after the function is complete */ +#define DAQP_CMD_ARM BIT(7) /* monostable */ +#define DAQP_CMD_RSTF BIT(6) /* monostable */ +#define DAQP_CMD_RSTQ BIT(5) /* monostable */ +#define DAQP_CMD_STOP BIT(4) /* monostable */ +#define DAQP_CMD_LATCH BIT(3) /* monostable */ +#define DAQP_CMD_SCANRATE(x) (((x) & 0x3) << 1) +#define DAQP_CMD_SCANRATE_100KHZ DAQP_CMD_SCANRATE(0) +#define DAQP_CMD_SCANRATE_50KHZ DAQP_CMD_SCANRATE(1) +#define DAQP_CMD_SCANRATE_25KHZ DAQP_CMD_SCANRATE(2) +#define DAQP_CMD_FIFO_DATA BIT(0) + +#define DAQP_AO_REG 0x08 /* and 0x09 (16-bit) */ + +#define DAQP_TIMER_REG 0x0a /* and 0x0b (16-bit) */ + +#define DAQP_AUX_REG 0x0f +/* Auxiliary Control register bits (write) */ +#define DAQP_AUX_EXT_ANALOG_TRIG BIT(7) +#define DAQP_AUX_PRETRIG BIT(6) +#define DAQP_AUX_TIMER_INT_ENA BIT(5) +#define DAQP_AUX_TIMER_MODE(x) (((x) & 0x3) << 3) +#define DAQP_AUX_TIMER_MODE_RELOAD DAQP_AUX_TIMER_MODE(0) +#define DAQP_AUX_TIMER_MODE_PAUSE DAQP_AUX_TIMER_MODE(1) +#define DAQP_AUX_TIMER_MODE_GO DAQP_AUX_TIMER_MODE(2) +#define DAQP_AUX_TIMER_MODE_EXT DAQP_AUX_TIMER_MODE(3) +#define DAQP_AUX_TIMER_CLK_SRC_EXT BIT(2) +#define DAQP_AUX_DA_UPDATE(x) (((x) & 0x3) << 0) +#define DAQP_AUX_DA_UPDATE_DIRECT DAQP_AUX_DA_UPDATE(0) +#define DAQP_AUX_DA_UPDATE_OVERFLOW DAQP_AUX_DA_UPDATE(1) +#define DAQP_AUX_DA_UPDATE_EXTERNAL DAQP_AUX_DA_UPDATE(2) +#define DAQP_AUX_DA_UPDATE_PACER DAQP_AUX_DA_UPDATE(3) +/* Auxiliary Status register bits (read) */ +#define DAQP_AUX_RUNNING BIT(7) +#define DAQP_AUX_TRIGGERED BIT(6) +#define DAQP_AUX_DA_BUFFER BIT(5) +#define DAQP_AUX_TIMER_OVERFLOW BIT(4) +#define DAQP_AUX_CONVERSION BIT(3) +#define DAQP_AUX_DATA_LOST BIT(2) +#define DAQP_AUX_FIFO_NEARFULL BIT(1) +#define DAQP_AUX_FIFO_EMPTY BIT(0) + +#define DAQP_FIFO_SIZE 4096 + +#define DAQP_MAX_TIMER_SPEED 10000 /* 100 kHz in nanoseconds */ + +struct daqp_private { + unsigned int pacer_div; + int stop; +}; + +static const struct comedi_lrange range_daqp_ai = { + 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; + +static int daqp_clear_events(struct comedi_device *dev, int loops) +{ + unsigned int status; + + /* + * Reset any pending interrupts (my card has a tendency to require + * multiple reads on the status register to achieve this). + */ + while (--loops) { + status = inb(dev->iobase + DAQP_STATUS_REG); + if ((status & DAQP_STATUS_EVENTS) == 0) + return 0; + } + dev_err(dev->class_dev, "couldn't clear events in status register\n"); + return -EBUSY; +} + +static int daqp_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct daqp_private *devpriv = dev->private; + + if (devpriv->stop) + return -EIO; + + /* + * Stop any conversions, disable interrupts, and clear + * the status event flags. + */ + outb(DAQP_CMD_STOP, dev->iobase + DAQP_CMD_REG); + outb(0, dev->iobase + DAQP_CTRL_REG); + inb(dev->iobase + DAQP_STATUS_REG); + + return 0; +} + +static unsigned int daqp_ai_get_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int val; + + /* + * Get a two's complement sample from the FIFO and + * return the munged offset binary value. + */ + val = inb(dev->iobase + DAQP_AI_FIFO_REG); + val |= inb(dev->iobase + DAQP_AI_FIFO_REG) << 8; + return comedi_offset_munge(s, val); +} + +static irqreturn_t daqp_interrupt(int irq, void *dev_id) +{ + struct comedi_device *dev = dev_id; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + int loop_limit = 10000; + int status; + + if (!dev->attached) + return IRQ_NONE; + + status = inb(dev->iobase + DAQP_STATUS_REG); + if (!(status & DAQP_STATUS_EVENTS)) + return IRQ_NONE; + + while (!(status & DAQP_STATUS_FIFO_EMPTY)) { + unsigned short data; + + if (status & DAQP_STATUS_DATA_LOST) { + s->async->events |= COMEDI_CB_OVERFLOW; + dev_warn(dev->class_dev, "data lost\n"); + break; + } + + data = daqp_ai_get_sample(dev, s); + comedi_buf_write_samples(s, &data, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) { + s->async->events |= COMEDI_CB_EOA; + break; + } + + if ((loop_limit--) <= 0) + break; + + status = inb(dev->iobase + DAQP_STATUS_REG); + } + + if (loop_limit <= 0) { + dev_warn(dev->class_dev, + "loop_limit reached in %s()\n", __func__); + s->async->events |= COMEDI_CB_ERROR; + } + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static void daqp_ai_set_one_scanlist_entry(struct comedi_device *dev, + unsigned int chanspec, + int start) +{ + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + unsigned int val; + + val = DAQP_SCANLIST_CHANNEL(chan) | DAQP_SCANLIST_GAIN(range); + + if (aref == AREF_DIFF) + val |= DAQP_SCANLIST_DIFFERENTIAL; + + if (start) + val |= DAQP_SCANLIST_START; + + outb(val & 0xff, dev->iobase + DAQP_SCANLIST_REG); + outb((val >> 8) & 0xff, dev->iobase + DAQP_SCANLIST_REG); +} + +static int daqp_ai_eos(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + DAQP_AUX_REG); + if (status & DAQP_AUX_CONVERSION) + return 0; + return -EBUSY; +} + +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 ret = 0; + int i; + + if (devpriv->stop) + return -EIO; + + outb(0, dev->iobase + DAQP_AUX_REG); + + /* Reset scan list queue */ + outb(DAQP_CMD_RSTQ, dev->iobase + DAQP_CMD_REG); + + /* 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_CMD_RSTF, dev->iobase + DAQP_CMD_REG); + + /* Set trigger - one-shot, internal, no interrupts */ + outb(DAQP_CTRL_PACER_CLK_100KHZ, dev->iobase + DAQP_CTRL_REG); + + ret = daqp_clear_events(dev, 10000); + if (ret) + return ret; + + for (i = 0; i < insn->n; i++) { + /* Start conversion */ + outb(DAQP_CMD_ARM | DAQP_CMD_FIFO_DATA, + dev->iobase + DAQP_CMD_REG); + + ret = comedi_timeout(dev, s, insn, daqp_ai_eos, 0); + if (ret) + break; + + /* clear the status event flags */ + inb(dev->iobase + DAQP_STATUS_REG); + + data[i] = daqp_ai_get_sample(dev, s); + } + + /* stop any conversions and clear the status event flags */ + outb(DAQP_CMD_STOP, dev->iobase + DAQP_CMD_REG); + inb(dev->iobase + DAQP_STATUS_REG); + + return ret ? ret : insn->n; +} + +/* This function converts ns nanoseconds to a counter value suitable + * for programming the device. We always use the DAQP's 5 MHz clock, + * which with its 24-bit counter, allows values up to 84 seconds. + * Also, the function adjusts ns so that it cooresponds to the actual + * time that the device will use. + */ + +static int daqp_ns_to_timer(unsigned int *ns, unsigned int flags) +{ + int timer; + + timer = *ns / 200; + *ns = timer * 200; + + return timer; +} + +static void daqp_set_pacer(struct comedi_device *dev, unsigned int val) +{ + outb(val & 0xff, dev->iobase + DAQP_PACER_LOW_REG); + outb((val >> 8) & 0xff, dev->iobase + DAQP_PACER_MID_REG); + outb((val >> 16) & 0xff, dev->iobase + DAQP_PACER_HIGH_REG); +} + +static int daqp_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct daqp_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + /* the async command requires a pacer */ + if (cmd->scan_begin_src != TRIG_TIMER && cmd->convert_src != TRIG_TIMER) + err |= -EINVAL; + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->scan_begin_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + DAQP_MAX_TIMER_SPEED); + + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + DAQP_MAX_TIMER_SPEED); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* + * 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. + */ + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, + arg); + } + } + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + devpriv->pacer_div = daqp_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } else if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + devpriv->pacer_div = daqp_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (err) + return 4; + + return 0; +} + +static int 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 scanlist_start_on_every_entry; + int threshold; + int ret; + int i; + + if (devpriv->stop) + return -EIO; + + outb(0, dev->iobase + DAQP_AUX_REG); + + /* Reset scan list queue */ + outb(DAQP_CMD_RSTQ, dev->iobase + DAQP_CMD_REG); + + /* 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. + */ + daqp_set_pacer(dev, devpriv->pacer_div); + + if (cmd->convert_src == TRIG_TIMER) + scanlist_start_on_every_entry = 1; + else + scanlist_start_on_every_entry = 0; + + /* Program scan list */ + for (i = 0; i < cmd->chanlist_len; i++) { + int start = (i == 0 || scanlist_start_on_every_entry); + + daqp_ai_set_one_scanlist_entry(dev, cmd->chanlist[i], start); + } + + /* Now it's time to program the FIFO threshold, basically the + * number of samples the card will buffer before it interrupts + * the CPU. + * + * If we don't have a stop count, then use half the size of + * the FIFO (the manufacturer's recommendation). Consider + * that the FIFO can hold 2K samples (4K bytes). With the + * threshold set at half the FIFO size, we have a margin of + * error of 1024 samples. At the chip's maximum sample rate + * of 100,000 Hz, the CPU would have to delay interrupt + * service for a full 10 milliseconds in order to lose data + * here (as opposed to higher up in the kernel). I've never + * seen it happen. However, for slow sample rates it may + * buffer too much data and introduce too much delay for the + * user application. + * + * If we have a stop count, then things get more interesting. + * If the stop count is less than the FIFO size (actually + * three-quarters of the FIFO size - see below), we just use + * the stop count itself as the threshold, the card interrupts + * us when that many samples have been taken, and we kill the + * acquisition at that point and are done. If the stop count + * is larger than that, then we divide it by 2 until it's less + * than three quarters of the FIFO size (we always leave the + * top quarter of the FIFO as protection against sluggish CPU + * interrupt response) and use that as the threshold. So, if + * the stop count is 4000 samples, we divide by two twice to + * get 1000 samples, use that as the threshold, take four + * interrupts to get our 4000 samples and are done. + * + * The algorithm could be more clever. For example, if 81000 + * samples are requested, we could set the threshold to 1500 + * samples and take 54 interrupts to get 81000. But 54 isn't + * a power of two, so this algorithm won't find that option. + * Instead, it'll set the threshold at 1266 and take 64 + * interrupts to get 81024 samples, of which the last 24 will + * be discarded... but we won't get the last interrupt until + * they've been collected. To find the first option, the + * computer could look at the prime decomposition of the + * sample count (81000 = 3^4 * 5^3 * 2^3) and factor it into a + * threshold (1500 = 3 * 5^3 * 2^2) and an interrupt count (54 + * = 3^3 * 2). Hmmm... a one-line while loop or prime + * decomposition of integers... I'll leave it the way it is. + * + * I'll also note a mini-race condition before ignoring it in + * the code. Let's say we're taking 4000 samples, as before. + * After 1000 samples, we get an interrupt. But before that + * interrupt is completely serviced, another sample is taken + * and loaded into the FIFO. Since the interrupt handler + * empties the FIFO before returning, it will read 1001 samples. + * If that happens four times, we'll end up taking 4004 samples, + * not 4000. The interrupt handler will discard the extra four + * samples (by halting the acquisition with four samples still + * in the FIFO), but we will have to wait for them. + * + * In short, this code works pretty well, but for either of + * the two reasons noted, might end up waiting for a few more + * samples than actually requested. Shouldn't make too much + * of a difference. + */ + + /* Save away the number of conversions we should perform, and + * compute the FIFO threshold (in bytes, not samples - that's + * why we multiple devpriv->count by 2 = sizeof(sample)) + */ + + if (cmd->stop_src == TRIG_COUNT) { + unsigned long long nsamples; + unsigned long long nbytes; + + nsamples = (unsigned long long)cmd->stop_arg * + cmd->scan_end_arg; + nbytes = nsamples * comedi_bytes_per_sample(s); + while (nbytes > DAQP_FIFO_SIZE * 3 / 4) + nbytes /= 2; + threshold = nbytes; + } else { + threshold = DAQP_FIFO_SIZE / 2; + } + + /* Reset data FIFO (see page 28 of DAQP User's Manual) */ + + outb(DAQP_CMD_RSTF, dev->iobase + DAQP_CMD_REG); + + /* 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_AI_FIFO_REG); + outb(0x00, dev->iobase + DAQP_AI_FIFO_REG); + + outb((DAQP_FIFO_SIZE - threshold) & 0xff, + dev->iobase + DAQP_AI_FIFO_REG); + outb((DAQP_FIFO_SIZE - threshold) >> 8, dev->iobase + DAQP_AI_FIFO_REG); + + /* Set trigger - continuous, internal */ + outb(DAQP_CTRL_TRIG_MODE | DAQP_CTRL_PACER_CLK_5MHZ | + DAQP_CTRL_FIFO_INT_ENA, dev->iobase + DAQP_CTRL_REG); + + ret = daqp_clear_events(dev, 100); + if (ret) + return ret; + + /* Start conversion */ + outb(DAQP_CMD_ARM | DAQP_CMD_FIFO_DATA, dev->iobase + DAQP_CMD_REG); + + return 0; +} + +static int daqp_ao_empty(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inb(dev->iobase + DAQP_AUX_REG); + if ((status & DAQP_AUX_DA_BUFFER) == 0) + return 0; + return -EBUSY; +} + +static int daqp_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct daqp_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + if (devpriv->stop) + return -EIO; + + /* Make sure D/A update mode is direct update */ + outb(0, dev->iobase + DAQP_AUX_REG); + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + int ret; + + /* D/A transfer rate is about 8ms */ + ret = comedi_timeout(dev, s, insn, daqp_ao_empty, 0); + if (ret) + return ret; + + /* write the two's complement value to the channel */ + outw((chan << 12) | comedi_offset_munge(s, val), + dev->iobase + DAQP_AO_REG); + + s->readback[chan] = val; + } + + 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_DI_REG); + + 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_DO_REG); + + 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 == 0) + dev->irq = link->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_GROUND | SDF_DIFF; + s->n_chan = 8; + s->maxdata = 0xffff; + s->range_table = &range_daqp_ai; + s->insn_read = daqp_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = 2048; + s->do_cmdtest = daqp_ai_cmdtest; + s->do_cmd = daqp_ai_cmd; + s->cancel = daqp_ai_cancel; + } + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &range_bipolar5; + s->insn_write = daqp_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* + * Digital Input subdevice + * NOTE: The digital input lines are shared: + * + * Chan Normal Mode Expansion Mode + * ---- ----------------- ---------------------------- + * 0 DI0, ext. trigger Same as normal mode + * 1 DI1 External gain select, lo bit + * 2 DI2, ext. clock Same as normal mode + * 3 DI3 External gain select, hi bit + */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->insn_bits = daqp_di_insn_bits; + + /* + * Digital Output subdevice + * NOTE: The digital output lines share the same pins on the + * interface connector as the four external channel selection + * bits. If expansion mode is used the digital outputs do not + * work. + */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + 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/comedi/drivers/rtd520.c b/drivers/comedi/drivers/rtd520.c new file mode 100644 index 000000000..7e0ec1a2a --- /dev/null +++ b/drivers/comedi/drivers/rtd520.c @@ -0,0 +1,1364 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: rtd520 + * Description: Real Time Devices PCI4520/DM7520 + * Devices: [Real Time Devices] DM7520HR-1 (DM7520), DM7520HR-8, + * PCI4520 (PCI4520), PCI4520-8 + * Author: Dan Christian + * Status: Works. Only tested on DM7520-8. Not SMP safe. + * + * Configuration options: not applicable, uses PCI auto config + */ + +/* + * Created by Dan Christian, NASA Ames Research Center. + * + * The PCI4520 is a PCI card. The DM7520 is a PC/104-plus card. + * Both have: + * 8/16 12 bit ADC with FIFO and channel gain table + * 8 bits high speed digital out (for external MUX) (or 8 in or 8 out) + * 8 bits high speed digital in with FIFO and interrupt on change (or 8 IO) + * 2 12 bit DACs with FIFOs + * 2 bits output + * 2 bits input + * bus mastering DMA + * timers: ADC sample, pacer, burst, about, delay, DA1, DA2 + * sample counter + * 3 user timer/counters (8254) + * external interrupt + * + * The DM7520 has slightly fewer features (fewer gain steps). + * + * These boards can support external multiplexors and multi-board + * synchronization, but this driver doesn't support that. + * + * Board docs: http://www.rtdusa.com/PC104/DM/analog%20IO/dm7520.htm + * Data sheet: http://www.rtdusa.com/pdf/dm7520.pdf + * Example source: http://www.rtdusa.com/examples/dm/dm7520.zip + * Call them and ask for the register level manual. + * PCI chip: http://www.plxtech.com/products/io/pci9080 + * + * Notes: + * This board is memory mapped. There is some IO stuff, but it isn't needed. + * + * I use a pretty loose naming style within the driver (rtd_blah). + * All externally visible names should be rtd520_blah. + * I use camelCase for structures (and inside them). + * I may also use upper CamelCase for function names (old habit). + * + * This board is somewhat related to the RTD PCI4400 board. + * + * I borrowed heavily from the ni_mio_common, ni_atmio16d, mite, and + * das1800, since they have the best documented code. Driver cb_pcidas64.c + * uses the same DMA controller. + * + * As far as I can tell, the About interrupt doesn't work if Sample is + * also enabled. It turns out that About really isn't needed, since + * we always count down samples read. + */ + +/* + * driver status: + * + * Analog-In supports instruction and command mode. + * + * With DMA, you can sample at 1.15Mhz with 70% idle on a 400Mhz K6-2 + * (single channel, 64K read buffer). I get random system lockups when + * using DMA with ALI-15xx based systems. I haven't been able to test + * any other chipsets. The lockups happen soon after the start of an + * acquistion, not in the middle of a long run. + * + * Without DMA, you can do 620Khz sampling with 20% idle on a 400Mhz K6-2 + * (with a 256K read buffer). + * + * Digital-IO and Analog-Out only support instruction mode. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedi_pci.h> +#include <linux/comedi/comedi_8254.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 BIT(0) /* DAC1 FIFO not empty */ +#define FS_DAC1_HEMPTY BIT(1) /* DAC1 FIFO half empty */ +#define FS_DAC1_NOT_FULL BIT(2) /* DAC1 FIFO not full */ +#define FS_DAC2_NOT_EMPTY BIT(4) /* DAC2 FIFO not empty */ +#define FS_DAC2_HEMPTY BIT(5) /* DAC2 FIFO half empty */ +#define FS_DAC2_NOT_FULL BIT(6) /* DAC2 FIFO not full */ +#define FS_ADC_NOT_EMPTY BIT(8) /* ADC FIFO not empty */ +#define FS_ADC_HEMPTY BIT(9) /* ADC FIFO half empty */ +#define FS_ADC_NOT_FULL BIT(10) /* ADC FIFO not full */ +#define FS_DIN_NOT_EMPTY BIT(12) /* DIN FIFO not empty */ +#define FS_DIN_HEMPTY BIT(13) /* DIN FIFO half empty */ +#define FS_DIN_NOT_FULL BIT(14) /* DIN FIFO not full */ +#define LAS0_UPDATE_DAC(x) (0x0014 + ((x) * 0x4)) /* D/Ax 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 BIT(0) /* ADC FIFO Write */ +#define IRQM_CGT_RESET BIT(1) /* Reset CGT */ +#define IRQM_CGT_PAUSE BIT(3) /* Pause CGT */ +#define IRQM_ADC_ABOUT_CNT BIT(4) /* About Counter out */ +#define IRQM_ADC_DELAY_CNT BIT(5) /* Delay Counter out */ +#define IRQM_ADC_SAMPLE_CNT BIT(6) /* ADC Sample Counter */ +#define IRQM_DAC1_UCNT BIT(7) /* DAC1 Update Counter */ +#define IRQM_DAC2_UCNT BIT(8) /* DAC2 Update Counter */ +#define IRQM_UTC1 BIT(9) /* User TC1 out */ +#define IRQM_UTC1_INV BIT(10) /* User TC1 out, inverted */ +#define IRQM_UTC2 BIT(11) /* User TC2 out */ +#define IRQM_DIGITAL_IT BIT(12) /* Digital Interrupt */ +#define IRQM_EXTERNAL_IT BIT(13) /* External Interrupt */ +#define IRQM_ETRIG_RISING BIT(14) /* Ext Trigger rising-edge */ +#define IRQM_ETRIG_FALLING BIT(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_8254_TIMER_BASE 0x0060 /* 8254 timer/counter base */ +#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_DAC_CTRL(x) (0x0150 + ((x) * 0x14)) /* D/Ax type/range */ +#define LAS0_DAC_SRC(x) (0x0154 + ((x) * 0x14)) /* D/Ax update source */ +#define LAS0_DAC_CYCLE(x) (0x0158 + ((x) * 0x14)) /* D/Ax cycle mode */ +#define LAS0_DAC_RESET(x) (0x015c + ((x) * 0x14)) /* D/Ax FIFO reset */ +#define LAS0_DAC_FIFO_CLEAR(x) (0x0160 + ((x) * 0x14)) /* D/Ax 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_8254_CLK_SEL(x) (0x01ac + ((x) * 0x8)) /* 8254 clock select */ +#define LAS0_8254_GATE_SEL(x) (0x01b0 + ((x) * 0x8)) /* 8254 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_DAC_FIFO(x) (0x0008 + ((x) * 0x4)) /* D/Ax 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 rtd520_boards[] = { + [BOARD_DM7520] = { + .name = "DM7520", + .range_bip10 = 6, + .range_uni10 = 12, + .ai_range = &rtd_ai_7520_range, + }, + [BOARD_PCI4520] = { + .name = "PCI4520", + .range_bip10 = 8, + .range_uni10 = 16, + .ai_range = &rtd_ai_4520_range, + }, +}; + +struct rtd_private { + /* memory mapped board structures */ + void __iomem *las1; + void __iomem *lcfg; + + long ai_count; /* total transfer size (samples) */ + int xfer_count; /* # to transfer data. 0->1/2FIFO */ + int flags; /* flag event modes */ + unsigned int fifosz; + + /* 8254 Timer/Counter gate and clock sources */ + unsigned char timer_gate_src[3]; + unsigned char timer_clk_src[3]; +}; + +/* bit defines for "flags" */ +#define SEND_EOS 0x01 /* send End Of Scan events */ +#define DMA0_ACTIVE 0x02 /* DMA0 is active */ +#define DMA1_ACTIVE 0x04 /* DMA1 is active */ + +/* + * Given a desired period and the clock period (both in ns), return the + * proper counter value (divider-1). Sets the original period to be the + * true value. + * Note: you have to check if the value is larger than the counter range! + */ +static int rtd_ns_to_timer_base(unsigned int *nanosec, + unsigned int flags, int base) +{ + int divider; + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + divider = DIV_ROUND_CLOSEST(*nanosec, base); + break; + case CMDF_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case CMDF_ROUND_UP: + divider = DIV_ROUND_UP(*nanosec, base); + break; + } + if (divider < 2) + divider = 2; /* min is divide by 2 */ + + /* + * Note: we don't check for max, because different timers + * have different ranges + */ + + *nanosec = base * divider; + return divider - 1; /* countdown is divisor+1 */ +} + +/* + * Given a desired period (in ns), return the proper counter value + * (divider-1) for the internal clock. Sets the original period to + * be the true value. + */ +static int rtd_ns_to_timer(unsigned int *ns, unsigned int flags) +{ + return rtd_ns_to_timer_base(ns, flags, RTD_CLOCK_BASE); +} + +/* Convert a single comedi channel-gain entry to a RTD520 table entry */ +static unsigned short rtd_convert_chan_gain(struct comedi_device *dev, + unsigned int chanspec, int index) +{ + const struct rtd_boardinfo *board = dev->board_ptr; + unsigned int chan = CR_CHAN(chanspec); + unsigned int range = CR_RANGE(chanspec); + unsigned int aref = CR_AREF(chanspec); + unsigned short r = 0; + + r |= chan & 0xf; + + /* Note: we also setup the channel list bipolar flag array */ + if (range < board->range_bip10) { + /* +-5 range */ + r |= 0x000; + r |= (range & 0x7) << 4; + } else if (range < board->range_uni10) { + /* +-10 range */ + r |= 0x100; + r |= ((range - board->range_bip10) & 0x7) << 4; + } else { + /* +10 range */ + r |= 0x200; + r |= ((range - board->range_uni10) & 0x7) << 4; + } + + switch (aref) { + case AREF_GROUND: /* on-board ground */ + break; + + case AREF_COMMON: + r |= 0x80; /* ref external analog common */ + break; + + case AREF_DIFF: + r |= 0x400; /* differential inputs */ + break; + + case AREF_OTHER: /* ??? */ + break; + } + return r; +} + +/* Setup the channel-gain table from a comedi list */ +static void rtd_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, unsigned int *list) +{ + if (n_chan > 1) { /* setup channel gain table */ + int ii; + + writel(0, dev->mmio + LAS0_CGT_CLEAR); + writel(1, dev->mmio + LAS0_CGT_ENABLE); + for (ii = 0; ii < n_chan; ii++) { + writel(rtd_convert_chan_gain(dev, list[ii], ii), + dev->mmio + LAS0_CGT_WRITE); + } + } else { /* just use the channel gain latch */ + writel(0, dev->mmio + LAS0_CGT_ENABLE); + writel(rtd_convert_chan_gain(dev, list[0], 0), + dev->mmio + LAS0_CGL_WRITE); + } +} + +/* + * Determine fifo size by doing adc conversions until the fifo half + * empty status flag clears. + */ +static int rtd520_probe_fifo_depth(struct comedi_device *dev) +{ + unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND); + unsigned int i; + static const unsigned int limit = 0x2000; + unsigned int fifo_size = 0; + + writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR); + rtd_load_channelgain_list(dev, 1, &chanspec); + /* ADC conversion trigger source: SOFTWARE */ + writel(0, dev->mmio + LAS0_ADC_CONVERSION); + /* convert samples */ + for (i = 0; i < limit; ++i) { + unsigned int fifo_status; + /* trigger conversion */ + writew(0, dev->mmio + LAS0_ADC); + usleep_range(1, 1000); + fifo_status = readl(dev->mmio + LAS0_ADC); + if ((fifo_status & FS_ADC_HEMPTY) == 0) { + fifo_size = 2 * i; + break; + } + } + if (i == limit) { + dev_info(dev->class_dev, "failed to probe fifo size.\n"); + return -EIO; + } + writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR); + if (fifo_size != 0x400 && fifo_size != 0x2000) { + dev_info(dev->class_dev, + "unexpected fifo size of %i, expected 1024 or 8192.\n", + fifo_size); + return -EIO; + } + return fifo_size; +} + +static int rtd_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readl(dev->mmio + LAS0_ADC); + if (status & FS_ADC_NOT_EMPTY) + return 0; + return -EBUSY; +} + +static int rtd_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + struct rtd_private *devpriv = dev->private; + unsigned int range = CR_RANGE(insn->chanspec); + int ret; + int n; + + /* clear any old fifo data */ + writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR); + + /* write channel to multiplexer and clear channel gain table */ + rtd_load_channelgain_list(dev, 1, &insn->chanspec); + + /* ADC conversion trigger source: SOFTWARE */ + writel(0, dev->mmio + LAS0_ADC_CONVERSION); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + unsigned short d; + /* trigger conversion */ + writew(0, dev->mmio + LAS0_ADC); + + ret = comedi_timeout(dev, s, insn, rtd_ai_eoc, 0); + if (ret) + return ret; + + /* read data */ + d = readw(devpriv->las1 + LAS1_ADC_FIFO); + d >>= 3; /* low 3 bits are marker lines */ + + /* convert bipolar data to comedi unsigned data */ + if (comedi_range_is_bipolar(s, range)) + d = comedi_offset_munge(s, d); + + data[n] = d & s->maxdata; + } + + /* return the number of samples read/written */ + return n; +} + +static int ai_read_n(struct comedi_device *dev, struct comedi_subdevice *s, + int count) +{ + struct rtd_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int ii; + + for (ii = 0; ii < count; ii++) { + unsigned int range = CR_RANGE(cmd->chanlist[async->cur_chan]); + unsigned short d; + + if (devpriv->ai_count == 0) { /* done */ + d = readw(devpriv->las1 + LAS1_ADC_FIFO); + continue; + } + + d = readw(devpriv->las1 + LAS1_ADC_FIFO); + d >>= 3; /* low 3 bits are marker lines */ + + /* convert bipolar data to comedi unsigned data */ + if (comedi_range_is_bipolar(s, range)) + d = comedi_offset_munge(s, d); + d &= s->maxdata; + + if (!comedi_buf_write_samples(s, &d, 1)) + return -1; + + if (devpriv->ai_count > 0) /* < 0, means read forever */ + devpriv->ai_count--; + } + return 0; +} + +static irqreturn_t rtd_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct rtd_private *devpriv = dev->private; + u32 overrun; + u16 status; + u16 fifo_status; + + if (!dev->attached) + return IRQ_NONE; + + fifo_status = readl(dev->mmio + LAS0_ADC); + /* check for FIFO full, this automatically halts the ADC! */ + if (!(fifo_status & FS_ADC_NOT_FULL)) /* 0 -> full */ + goto xfer_abort; + + status = readw(dev->mmio + LAS0_IT); + /* if interrupt was not caused by our board, or handled above */ + if (status == 0) + 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 (devpriv->ai_count == 0) + goto xfer_done; + } else if (devpriv->xfer_count > 0) { + if (fifo_status & FS_ADC_NOT_EMPTY) { + /* FIFO not empty */ + if (ai_read_n(dev, s, devpriv->xfer_count) < 0) + goto xfer_abort; + + if (devpriv->ai_count == 0) + goto xfer_done; + } + } + } + + overrun = readl(dev->mmio + LAS0_OVERRUN) & 0xffff; + if (overrun) + goto xfer_abort; + + /* clear the interrupt */ + writew(status, dev->mmio + LAS0_CLEAR); + readw(dev->mmio + LAS0_CLEAR); + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; + +xfer_abort: + s->async->events |= COMEDI_CB_ERROR; + +xfer_done: + s->async->events |= COMEDI_CB_EOA; + + /* clear the interrupt */ + status = readw(dev->mmio + LAS0_IT); + writew(status, dev->mmio + LAS0_CLEAR); + readw(dev->mmio + LAS0_CLEAR); + + fifo_status = readl(dev->mmio + LAS0_ADC); + overrun = readl(dev->mmio + LAS0_OVERRUN) & 0xffff; + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int rtd_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Note: these are time periods, not actual rates */ + if (cmd->chanlist_len == 1) { /* no scanning */ + if (comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + RTD_MAX_SPEED_1)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + CMDF_ROUND_UP); + err |= -EINVAL; + } + if (comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + RTD_MIN_SPEED_1)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + CMDF_ROUND_DOWN); + err |= -EINVAL; + } + } else { + if (comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + RTD_MAX_SPEED)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + CMDF_ROUND_UP); + err |= -EINVAL; + } + if (comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + RTD_MIN_SPEED)) { + rtd_ns_to_timer(&cmd->scan_begin_arg, + CMDF_ROUND_DOWN); + err |= -EINVAL; + } + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + } + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->chanlist_len == 1) { /* no scanning */ + if (comedi_check_trigger_arg_min(&cmd->convert_arg, + RTD_MAX_SPEED_1)) { + rtd_ns_to_timer(&cmd->convert_arg, + CMDF_ROUND_UP); + err |= -EINVAL; + } + if (comedi_check_trigger_arg_max(&cmd->convert_arg, + RTD_MIN_SPEED_1)) { + rtd_ns_to_timer(&cmd->convert_arg, + CMDF_ROUND_DOWN); + err |= -EINVAL; + } + } else { + if (comedi_check_trigger_arg_min(&cmd->convert_arg, + RTD_MAX_SPEED)) { + rtd_ns_to_timer(&cmd->convert_arg, + CMDF_ROUND_UP); + err |= -EINVAL; + } + if (comedi_check_trigger_arg_max(&cmd->convert_arg, + RTD_MIN_SPEED)) { + rtd_ns_to_timer(&cmd->convert_arg, + CMDF_ROUND_DOWN); + err |= -EINVAL; + } + } + } else { + /* external trigger */ + /* see above */ + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, 9); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + rtd_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + rtd_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, arg); + } + } + + if (err) + return 4; + + return 0; +} + +static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct rtd_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int timer; + + /* stop anything currently running */ + /* pacer stop source: SOFTWARE */ + writel(0, dev->mmio + LAS0_PACER_STOP); + writel(0, dev->mmio + LAS0_PACER); /* stop pacer */ + writel(0, dev->mmio + LAS0_ADC_CONVERSION); + writew(0, dev->mmio + LAS0_IT); + writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR); + writel(0, dev->mmio + LAS0_OVERRUN); + + /* start configuration */ + /* load channel list and reset CGT */ + rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); + + /* setup the common case and override if needed */ + if (cmd->chanlist_len > 1) { + /* pacer start source: SOFTWARE */ + writel(0, dev->mmio + LAS0_PACER_START); + /* burst trigger source: PACER */ + writel(1, dev->mmio + LAS0_BURST_START); + /* ADC conversion trigger source: BURST */ + writel(2, dev->mmio + LAS0_ADC_CONVERSION); + } else { /* single channel */ + /* pacer start source: SOFTWARE */ + writel(0, dev->mmio + LAS0_PACER_START); + /* ADC conversion trigger source: PACER */ + writel(1, dev->mmio + LAS0_ADC_CONVERSION); + } + writel((devpriv->fifosz / 2 - 1) & 0xffff, dev->mmio + LAS0_ACNT); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* scan_begin_arg is in nanoseconds */ + /* find out how many samples to wait before transferring */ + if (cmd->flags & CMDF_WAKE_EOS) { + /* + * this may generate un-sustainable interrupt rates + * the application is responsible for doing the + * right thing + */ + devpriv->xfer_count = cmd->chanlist_len; + devpriv->flags |= SEND_EOS; + } else { + /* arrange to transfer data periodically */ + devpriv->xfer_count = + (TRANS_TARGET_PERIOD * cmd->chanlist_len) / + cmd->scan_begin_arg; + if (devpriv->xfer_count < cmd->chanlist_len) { + /* transfer after each scan (and avoid 0) */ + devpriv->xfer_count = cmd->chanlist_len; + } else { /* make a multiple of scan length */ + devpriv->xfer_count = + DIV_ROUND_UP(devpriv->xfer_count, + cmd->chanlist_len); + devpriv->xfer_count *= cmd->chanlist_len; + } + devpriv->flags |= SEND_EOS; + } + if (devpriv->xfer_count >= (devpriv->fifosz / 2)) { + /* out of counter range, use 1/2 fifo instead */ + devpriv->xfer_count = 0; + devpriv->flags &= ~SEND_EOS; + } else { + /* interrupt for each transfer */ + writel((devpriv->xfer_count - 1) & 0xffff, + dev->mmio + LAS0_ACNT); + } + } else { /* unknown timing, just use 1/2 FIFO */ + devpriv->xfer_count = 0; + devpriv->flags &= ~SEND_EOS; + } + /* pacer clock source: INTERNAL 8MHz */ + writel(1, dev->mmio + LAS0_PACER_SELECT); + /* just interrupt, don't stop */ + writel(1, dev->mmio + LAS0_ACNT_STOP_ENABLE); + + /* BUG??? these look like enumerated values, but they are bit fields */ + + /* First, setup when to stop */ + switch (cmd->stop_src) { + case TRIG_COUNT: /* stop after N scans */ + devpriv->ai_count = cmd->stop_arg * cmd->chanlist_len; + if ((devpriv->xfer_count > 0) && + (devpriv->xfer_count > devpriv->ai_count)) { + devpriv->xfer_count = devpriv->ai_count; + } + break; + + case TRIG_NONE: /* stop when cancel is called */ + devpriv->ai_count = -1; /* read forever */ + break; + } + + /* Scan timing */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: /* periodic scanning */ + timer = rtd_ns_to_timer(&cmd->scan_begin_arg, + CMDF_ROUND_NEAREST); + /* set PACER clock */ + writel(timer & 0xffffff, dev->mmio + LAS0_PCLK); + + break; + + case TRIG_EXT: + /* pacer start source: EXTERNAL */ + writel(1, dev->mmio + LAS0_PACER_START); + break; + } + + /* Sample timing within a scan */ + switch (cmd->convert_src) { + case TRIG_TIMER: /* periodic */ + if (cmd->chanlist_len > 1) { + /* only needed for multi-channel */ + timer = rtd_ns_to_timer(&cmd->convert_arg, + CMDF_ROUND_NEAREST); + /* setup BURST clock */ + writel(timer & 0x3ff, dev->mmio + LAS0_BCLK); + } + + break; + + case TRIG_EXT: /* external */ + /* burst trigger source: EXTERNAL */ + writel(2, dev->mmio + LAS0_BURST_START); + break; + } + /* end configuration */ + + /* + * This doesn't seem to work. There is no way to clear an interrupt + * that the priority controller has queued! + */ + writew(~0, dev->mmio + LAS0_CLEAR); + readw(dev->mmio + LAS0_CLEAR); + + /* TODO: allow multiple interrupt sources */ + /* transfer every N samples */ + writew(IRQM_ADC_ABOUT_CNT, dev->mmio + LAS0_IT); + + /* BUG: start_src is ASSUMED to be TRIG_NOW */ + /* BUG? it seems like things are running before the "start" */ + readl(dev->mmio + LAS0_PACER); /* start pacer */ + return 0; +} + +static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct rtd_private *devpriv = dev->private; + + /* pacer stop source: SOFTWARE */ + writel(0, dev->mmio + LAS0_PACER_STOP); + writel(0, dev->mmio + LAS0_PACER); /* stop pacer */ + writel(0, dev->mmio + LAS0_ADC_CONVERSION); + writew(0, dev->mmio + LAS0_IT); + devpriv->ai_count = 0; /* stop and don't transfer any more */ + writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR); + return 0; +} + +static int rtd_ao_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int bit = (chan == 0) ? FS_DAC1_NOT_EMPTY : FS_DAC2_NOT_EMPTY; + unsigned int status; + + status = readl(dev->mmio + LAS0_ADC); + if (status & bit) + return 0; + return -EBUSY; +} + +static int rtd_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct rtd_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + int ret; + int i; + + /* Configure the output range (table index matches the range values) */ + writew(range & 7, dev->mmio + LAS0_DAC_CTRL(chan)); + + for (i = 0; i < insn->n; ++i) { + unsigned int val = data[i]; + + /* bipolar uses 2's complement values with an extended sign */ + if (comedi_range_is_bipolar(s, range)) { + val = comedi_offset_munge(s, val); + val |= (val & ((s->maxdata + 1) >> 1)) << 1; + } + + /* shift the 12-bit data (+ sign) to match the register */ + val <<= 3; + + writew(val, devpriv->las1 + LAS1_DAC_FIFO(chan)); + writew(0, dev->mmio + LAS0_UPDATE_DAC(chan)); + + ret = comedi_timeout(dev, s, insn, rtd_ao_eoc, 0); + if (ret) + return ret; + + s->readback[chan] = data[i]; + } + + return insn->n; +} + +static int rtd_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + writew(s->state & 0xff, dev->mmio + LAS0_DIO0); + + data[1] = readw(dev->mmio + LAS0_DIO0) & 0xff; + + return insn->n; +} + +static int rtd_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + /* TODO support digital match interrupts and strobes */ + + /* set direction */ + writew(0x01, dev->mmio + LAS0_DIO_STATUS); + writew(s->io_bits & 0xff, dev->mmio + LAS0_DIO0_CTRL); + + /* clear interrupts */ + writew(0x00, dev->mmio + LAS0_DIO_STATUS); + + /* port1 can only be all input or all output */ + + /* there are also 2 user input lines and 2 user output lines */ + + return insn->n; +} + +static int rtd_counter_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct rtd_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int max_src; + unsigned int src; + + switch (data[0]) { + case INSN_CONFIG_SET_GATE_SRC: + /* + * 8254 Timer/Counter gate sources: + * + * 0 = Not gated, free running (reset state) + * 1 = Gated, off + * 2 = Ext. TC Gate 1 + * 3 = Ext. TC Gate 2 + * 4 = Previous TC out (chan 1 and 2 only) + */ + src = data[2]; + max_src = (chan == 0) ? 3 : 4; + if (src > max_src) + return -EINVAL; + + devpriv->timer_gate_src[chan] = src; + writeb(src, dev->mmio + LAS0_8254_GATE_SEL(chan)); + break; + case INSN_CONFIG_GET_GATE_SRC: + data[2] = devpriv->timer_gate_src[chan]; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + /* + * 8254 Timer/Counter clock sources: + * + * 0 = 8 MHz (reset state) + * 1 = Ext. TC Clock 1 + * 2 = Ext. TX Clock 2 + * 3 = Ext. Pacer Clock + * 4 = Previous TC out (chan 1 and 2 only) + * 5 = High-Speed Digital Input Sampling signal (chan 1 only) + */ + src = data[1]; + switch (chan) { + case 0: + max_src = 3; + break; + case 1: + max_src = 5; + break; + case 2: + max_src = 4; + break; + default: + return -EINVAL; + } + if (src > max_src) + return -EINVAL; + + devpriv->timer_clk_src[chan] = src; + writeb(src, dev->mmio + LAS0_8254_CLK_SEL(chan)); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + src = devpriv->timer_clk_src[chan]; + data[1] = devpriv->timer_clk_src[chan]; + data[2] = (src == 0) ? RTD_CLOCK_BASE : 0; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static void rtd_reset(struct comedi_device *dev) +{ + struct rtd_private *devpriv = dev->private; + + writel(0, dev->mmio + LAS0_BOARD_RESET); + usleep_range(100, 1000); /* needed? */ + writel(0, devpriv->lcfg + PLX_REG_INTCSR); + writew(0, dev->mmio + LAS0_IT); + writew(~0, dev->mmio + LAS0_CLEAR); + readw(dev->mmio + LAS0_CLEAR); +} + +/* + * initialize board, per RTD spec + * also, initialize shadow registers + */ +static void rtd_init_board(struct comedi_device *dev) +{ + rtd_reset(dev); + + writel(0, dev->mmio + LAS0_OVERRUN); + writel(0, dev->mmio + LAS0_CGT_CLEAR); + writel(0, dev->mmio + LAS0_ADC_FIFO_CLEAR); + writel(0, dev->mmio + LAS0_DAC_RESET(0)); + writel(0, dev->mmio + LAS0_DAC_RESET(1)); + /* clear digital IO fifo */ + writew(0, dev->mmio + LAS0_DIO_STATUS); + /* 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(rtd520_boards)) + board = &rtd520_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 2); + devpriv->las1 = pci_ioremap_bar(pcidev, 3); + devpriv->lcfg = pci_ioremap_bar(pcidev, 0); + if (!dev->mmio || !devpriv->las1 || !devpriv->lcfg) + return -ENOMEM; + + rtd_pci_latency_quirk(dev, pcidev); + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, rtd_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF; + s->n_chan = 16; + s->maxdata = 0x0fff; + s->range_table = board->ai_range; + s->len_chanlist = RTD_MAX_CHANLIST; + s->insn_read = rtd_ai_rinsn; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmd = rtd_ai_cmd; + s->do_cmdtest = rtd_ai_cmdtest; + s->cancel = rtd_ai_cancel; + } + + s = &dev->subdevices[1]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table = &rtd_ao_range; + s->insn_write = rtd_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[2]; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_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; + + /* 8254 Timer/Counter subdevice */ + s = &dev->subdevices[3]; + dev->pacer = comedi_8254_mm_init(dev->mmio + LAS0_8254_TIMER_BASE, + RTD_CLOCK_BASE, I8254_IO8, 2); + if (!dev->pacer) + return -ENOMEM; + + comedi_8254_subdevice_init(s, dev->pacer); + dev->pacer->insn_config = rtd_counter_insn_config; + + rtd_init_board(dev); + + ret = rtd520_probe_fifo_depth(dev); + if (ret < 0) + return ret; + devpriv->fifosz = ret; + + if (dev->irq) + writel(PLX_INTCSR_PIEN | PLX_INTCSR_PLIEN, + devpriv->lcfg + PLX_REG_INTCSR); + + return 0; +} + +static void rtd_detach(struct comedi_device *dev) +{ + struct rtd_private *devpriv = dev->private; + + if (devpriv) { + /* Shut down any board ops by resetting it */ + if (dev->mmio && devpriv->lcfg) + rtd_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->mmio) + iounmap(dev->mmio); + if (devpriv->las1) + iounmap(devpriv->las1); + if (devpriv->lcfg) + iounmap(devpriv->lcfg); + } + comedi_pci_disable(dev); +} + +static struct comedi_driver rtd520_driver = { + .driver_name = "rtd520", + .module = THIS_MODULE, + .auto_attach = rtd_auto_attach, + .detach = rtd_detach, +}; + +static int rtd520_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &rtd520_driver, id->driver_data); +} + +static const struct pci_device_id rtd520_pci_table[] = { + { PCI_VDEVICE(RTD, 0x7520), BOARD_DM7520 }, + { PCI_VDEVICE(RTD, 0x4520), BOARD_PCI4520 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, rtd520_pci_table); + +static struct pci_driver rtd520_pci_driver = { + .name = "rtd520", + .id_table = rtd520_pci_table, + .probe = rtd520_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(rtd520_driver, rtd520_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/rti800.c b/drivers/comedi/drivers/rti800.c new file mode 100644 index 000000000..1b02e47bd --- /dev/null +++ b/drivers/comedi/drivers/rti800.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: rti800 + * Description: Analog Devices RTI-800/815 + * Devices: [Analog Devices] RTI-800 (rti800), RTI-815 (rti815) + * Author: David A. Schleef <ds@schleef.org> + * Status: unknown + * Updated: Fri, 05 Sep 2008 14:50:44 +0100 + * + * Configuration options: + * [0] - I/O port base address + * [1] - IRQ (not supported / unused) + * [2] - A/D mux/reference (number of channels) + * 0 = differential + * 1 = pseudodifferential (common) + * 2 = single-ended + * [3] - A/D range + * 0 = [-10,10] + * 1 = [-5,5] + * 2 = [0,10] + * [4] - A/D encoding + * 0 = two's complement + * 1 = straight binary + * [5] - DAC 0 range + * 0 = [-10,10] + * 1 = [0,10] + * [6] - DAC 0 encoding + * 0 = two's complement + * 1 = straight binary + * [7] - DAC 1 range (same as DAC 0) + * [8] - DAC 1 encoding (same as DAC 0) + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/comedi/comedidev.h> + +/* + * Register map + */ +#define RTI800_CSR 0x00 +#define RTI800_CSR_BUSY BIT(7) +#define RTI800_CSR_DONE BIT(6) +#define RTI800_CSR_OVERRUN BIT(5) +#define RTI800_CSR_TCR BIT(4) +#define RTI800_CSR_DMA_ENAB BIT(3) +#define RTI800_CSR_INTR_TC BIT(2) +#define RTI800_CSR_INTR_EC BIT(1) +#define RTI800_CSR_INTR_OVRN BIT(0) +#define RTI800_MUXGAIN 0x01 +#define RTI800_CONVERT 0x02 +#define RTI800_ADCLO 0x03 +#define RTI800_ADCHI 0x04 +#define RTI800_DAC0LO 0x05 +#define RTI800_DAC0HI 0x06 +#define RTI800_DAC1LO 0x07 +#define RTI800_DAC1HI 0x08 +#define RTI800_CLRFLAGS 0x09 +#define RTI800_DI 0x0a +#define RTI800_DO 0x0b +#define RTI800_9513A_DATA 0x0c +#define RTI800_9513A_CNTRL 0x0d +#define RTI800_9513A_STATUS 0x0d + +static const struct comedi_lrange range_rti800_ai_10_bipolar = { + 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.02) + } +}; + +static const struct comedi_lrange range_rti800_ai_5_bipolar = { + 4, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_rti800_ai_unipolar = { + 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.02) + } +}; + +static const struct comedi_lrange *const rti800_ai_ranges[] = { + &range_rti800_ai_10_bipolar, + &range_rti800_ai_5_bipolar, + &range_rti800_ai_unipolar, +}; + +static const struct comedi_lrange *const rti800_ao_ranges[] = { + &range_bipolar10, + &range_unipolar10, +}; + +struct rti800_board { + const char *name; + int has_ao; +}; + +static const struct rti800_board rti800_boardtypes[] = { + { + .name = "rti800", + }, { + .name = "rti815", + .has_ao = 1, + }, +}; + +struct rti800_private { + bool adc_2comp; + bool dac_2comp[2]; + const struct comedi_lrange *ao_range_type_list[2]; + unsigned char muxgain_bits; +}; + +static int rti800_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned char status; + + status = inb(dev->iobase + RTI800_CSR); + if (status & RTI800_CSR_OVERRUN) { + outb(0, dev->iobase + RTI800_CLRFLAGS); + return -EOVERFLOW; + } + if (status & RTI800_CSR_DONE) + return 0; + return -EBUSY; +} + +static int rti800_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct rti800_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int gain = CR_RANGE(insn->chanspec); + unsigned char muxgain_bits; + int ret; + int i; + + inb(dev->iobase + RTI800_ADCHI); + outb(0, dev->iobase + RTI800_CLRFLAGS); + + muxgain_bits = chan | (gain << 5); + if (muxgain_bits != devpriv->muxgain_bits) { + devpriv->muxgain_bits = muxgain_bits; + outb(devpriv->muxgain_bits, dev->iobase + RTI800_MUXGAIN); + /* + * Without a delay here, the RTI_CSR_OVERRUN bit + * gets set, and you will have an error. + */ + if (insn->n > 0) { + int delay = (gain == 0) ? 10 : + (gain == 1) ? 20 : + (gain == 2) ? 40 : 80; + + udelay(delay); + } + } + + for (i = 0; i < insn->n; i++) { + unsigned int val; + + outb(0, dev->iobase + RTI800_CONVERT); + + ret = comedi_timeout(dev, s, insn, rti800_ai_eoc, 0); + if (ret) + return ret; + + val = inb(dev->iobase + RTI800_ADCLO); + val |= (inb(dev->iobase + RTI800_ADCHI) & 0xf) << 8; + + if (devpriv->adc_2comp) + val = comedi_offset_munge(s, val); + + data[i] = val; + } + + return insn->n; +} + +static int rti800_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct rti800_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int reg_lo = chan ? RTI800_DAC1LO : RTI800_DAC0LO; + int reg_hi = chan ? RTI800_DAC1HI : RTI800_DAC0HI; + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + if (devpriv->dac_2comp[chan]) + val = comedi_offset_munge(s, val); + + outb(val & 0xff, dev->iobase + reg_lo); + outb((val >> 8) & 0xff, dev->iobase + reg_hi); + } + + return insn->n; +} + +static int rti800_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inb(dev->iobase + RTI800_DI); + return insn->n; +} + +static int rti800_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) { + /* Outputs are inverted... */ + outb(s->state ^ 0xff, dev->iobase + RTI800_DO); + } + + data[1] = s->state; + + return insn->n; +} + +static int rti800_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct rti800_board *board = dev->board_ptr; + struct rti800_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x10); + if (ret) + return ret; + + outb(0, dev->iobase + RTI800_CSR); + inb(dev->iobase + RTI800_ADCHI); + outb(0, dev->iobase + RTI800_CLRFLAGS); + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + devpriv->adc_2comp = (it->options[4] == 0); + devpriv->dac_2comp[0] = (it->options[6] == 0); + devpriv->dac_2comp[1] = (it->options[8] == 0); + /* invalid, forces the MUXGAIN register to be set when first used */ + devpriv->muxgain_bits = 0xff; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = (it->options[2] ? 16 : 8); + s->insn_read = rti800_ai_insn_read; + s->maxdata = 0x0fff; + s->range_table = (it->options[3] < ARRAY_SIZE(rti800_ai_ranges)) + ? rti800_ai_ranges[it->options[3]] + : &range_unknown; + + s = &dev->subdevices[1]; + if (board->has_ao) { + /* ao subdevice (only on rti815) */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0x0fff; + s->range_table_list = devpriv->ao_range_type_list; + devpriv->ao_range_type_list[0] = + (it->options[5] < ARRAY_SIZE(rti800_ao_ranges)) + ? rti800_ao_ranges[it->options[5]] + : &range_unknown; + devpriv->ao_range_type_list[1] = + (it->options[7] < ARRAY_SIZE(rti800_ao_ranges)) + ? rti800_ao_ranges[it->options[7]] + : &range_unknown; + s->insn_write = rti800_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = &dev->subdevices[2]; + /* di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->insn_bits = rti800_di_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + + s = &dev->subdevices[3]; + /* do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->insn_bits = rti800_do_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + + /* + * There is also an Am9513 timer on these boards. This subdevice + * is not currently supported. + */ + + return 0; +} + +static struct comedi_driver rti800_driver = { + .driver_name = "rti800", + .module = THIS_MODULE, + .attach = rti800_attach, + .detach = comedi_legacy_detach, + .num_names = ARRAY_SIZE(rti800_boardtypes), + .board_name = &rti800_boardtypes[0].name, + .offset = sizeof(struct rti800_board), +}; +module_comedi_driver(rti800_driver); + +MODULE_DESCRIPTION("Comedi: RTI-800 Multifunction Analog/Digital board"); +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/rti802.c b/drivers/comedi/drivers/rti802.c new file mode 100644 index 000000000..d66762a22 --- /dev/null +++ b/drivers/comedi/drivers/rti802.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/comedidev.h> + +/* + * Register I/O map + */ +#define RTI802_SELECT 0x00 +#define RTI802_DATALOW 0x01 +#define RTI802_DATAHIGH 0x02 + +struct rti802_private { + enum { + dac_2comp, dac_straight + } dac_coding[8]; + const struct comedi_lrange *range_type_list[8]; +}; + +static int rti802_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct rti802_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + outb(chan, dev->iobase + RTI802_SELECT); + + for (i = 0; i < insn->n; i++) { + unsigned int val = data[i]; + + s->readback[chan] = val; + + /* munge offset binary to two's complement if needed */ + if (devpriv->dac_coding[chan] == dac_2comp) + val = comedi_offset_munge(s, val); + + outb(val & 0xff, dev->iobase + RTI802_DATALOW); + outb((val >> 8) & 0xff, dev->iobase + RTI802_DATAHIGH); + } + + return insn->n; +} + +static int rti802_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct rti802_private *devpriv; + struct comedi_subdevice *s; + int i; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x04); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + /* Analog Output subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 0xfff; + s->n_chan = 8; + s->insn_write = rti802_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s->range_table_list = devpriv->range_type_list; + for (i = 0; i < 8; i++) { + devpriv->dac_coding[i] = (it->options[3 + 2 * i]) + ? (dac_straight) : (dac_2comp); + devpriv->range_type_list[i] = (it->options[2 + 2 * i]) + ? &range_unipolar10 : &range_bipolar10; + } + + return 0; +} + +static struct comedi_driver rti802_driver = { + .driver_name = "rti802", + .module = THIS_MODULE, + .attach = rti802_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(rti802_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Analog Devices RTI-802 board"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/s526.c b/drivers/comedi/drivers/s526.c new file mode 100644 index 000000000..9245c679a --- /dev/null +++ b/drivers/comedi/drivers/s526.c @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * s526.c + * Sensoray s526 Comedi driver + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + */ + +/* + * 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: + * [0] - I/O port base address + */ + +#include <linux/module.h> +#include <linux/comedi/comedidev.h> + +/* + * Register I/O map + */ +#define S526_TIMER_REG 0x00 +#define S526_TIMER_LOAD(x) (((x) & 0xff) << 8) +#define S526_TIMER_MODE ((x) << 1) +#define S526_TIMER_MANUAL S526_TIMER_MODE(0) +#define S526_TIMER_AUTO S526_TIMER_MODE(1) +#define S526_TIMER_RESTART BIT(0) +#define S526_WDOG_REG 0x02 +#define S526_WDOG_INVERTED BIT(4) +#define S526_WDOG_ENA BIT(3) +#define S526_WDOG_INTERVAL(x) (((x) & 0x7) << 0) +#define S526_AO_CTRL_REG 0x04 +#define S526_AO_CTRL_RESET BIT(3) +#define S526_AO_CTRL_CHAN(x) (((x) & 0x3) << 1) +#define S526_AO_CTRL_START BIT(0) +#define S526_AI_CTRL_REG 0x06 +#define S526_AI_CTRL_DELAY BIT(15) +#define S526_AI_CTRL_CONV(x) (1 << (5 + ((x) & 0x9))) +#define S526_AI_CTRL_READ(x) (((x) & 0xf) << 1) +#define S526_AI_CTRL_START BIT(0) +#define S526_AO_REG 0x08 +#define S526_AI_REG 0x08 +#define S526_DIO_CTRL_REG 0x0a +#define S526_DIO_CTRL_DIO3_NEG BIT(15) /* irq on DIO3 neg/pos edge */ +#define S526_DIO_CTRL_DIO2_NEG BIT(14) /* irq on DIO2 neg/pos edge */ +#define S526_DIO_CTRL_DIO1_NEG BIT(13) /* irq on DIO1 neg/pos edge */ +#define S526_DIO_CTRL_DIO0_NEG BIT(12) /* irq on DIO0 neg/pos edge */ +#define S526_DIO_CTRL_GRP2_OUT BIT(11) +#define S526_DIO_CTRL_GRP1_OUT BIT(10) +#define S526_DIO_CTRL_GRP2_NEG BIT(8) /* irq on DIO[4-7] neg/pos edge */ +#define S526_INT_ENA_REG 0x0c +#define S526_INT_STATUS_REG 0x0e +#define S526_INT_DIO(x) BIT(8 + ((x) & 0x7)) +#define S526_INT_EEPROM BIT(7) /* status only */ +#define S526_INT_CNTR(x) BIT(3 + (3 - ((x) & 0x3))) +#define S526_INT_AI BIT(2) +#define S526_INT_AO BIT(1) +#define S526_INT_TIMER BIT(0) +#define S526_MISC_REG 0x10 +#define S526_MISC_LED_OFF BIT(0) +#define S526_GPCT_LSB_REG(x) (0x12 + ((x) * 8)) +#define S526_GPCT_MSB_REG(x) (0x14 + ((x) * 8)) +#define S526_GPCT_MODE_REG(x) (0x16 + ((x) * 8)) +#define S526_GPCT_MODE_COUT_SRC(x) ((x) << 0) +#define S526_GPCT_MODE_COUT_SRC_MASK S526_GPCT_MODE_COUT_SRC(0x1) +#define S526_GPCT_MODE_COUT_SRC_RCAP S526_GPCT_MODE_COUT_SRC(0) +#define S526_GPCT_MODE_COUT_SRC_RTGL S526_GPCT_MODE_COUT_SRC(1) +#define S526_GPCT_MODE_COUT_POL(x) ((x) << 1) +#define S526_GPCT_MODE_COUT_POL_MASK S526_GPCT_MODE_COUT_POL(0x1) +#define S526_GPCT_MODE_COUT_POL_NORM S526_GPCT_MODE_COUT_POL(0) +#define S526_GPCT_MODE_COUT_POL_INV S526_GPCT_MODE_COUT_POL(1) +#define S526_GPCT_MODE_AUTOLOAD(x) ((x) << 2) +#define S526_GPCT_MODE_AUTOLOAD_MASK S526_GPCT_MODE_AUTOLOAD(0x7) +#define S526_GPCT_MODE_AUTOLOAD_NONE S526_GPCT_MODE_AUTOLOAD(0) +/* these 3 bits can be OR'ed */ +#define S526_GPCT_MODE_AUTOLOAD_RO S526_GPCT_MODE_AUTOLOAD(0x1) +#define S526_GPCT_MODE_AUTOLOAD_IXFALL S526_GPCT_MODE_AUTOLOAD(0x2) +#define S526_GPCT_MODE_AUTOLOAD_IXRISE S526_GPCT_MODE_AUTOLOAD(0x4) +#define S526_GPCT_MODE_HWCTEN_SRC(x) ((x) << 5) +#define S526_GPCT_MODE_HWCTEN_SRC_MASK S526_GPCT_MODE_HWCTEN_SRC(0x3) +#define S526_GPCT_MODE_HWCTEN_SRC_CEN S526_GPCT_MODE_HWCTEN_SRC(0) +#define S526_GPCT_MODE_HWCTEN_SRC_IX S526_GPCT_MODE_HWCTEN_SRC(1) +#define S526_GPCT_MODE_HWCTEN_SRC_IXRF S526_GPCT_MODE_HWCTEN_SRC(2) +#define S526_GPCT_MODE_HWCTEN_SRC_NRCAP S526_GPCT_MODE_HWCTEN_SRC(3) +#define S526_GPCT_MODE_CTEN_CTRL(x) ((x) << 7) +#define S526_GPCT_MODE_CTEN_CTRL_MASK S526_GPCT_MODE_CTEN_CTRL(0x3) +#define S526_GPCT_MODE_CTEN_CTRL_DIS S526_GPCT_MODE_CTEN_CTRL(0) +#define S526_GPCT_MODE_CTEN_CTRL_ENA S526_GPCT_MODE_CTEN_CTRL(1) +#define S526_GPCT_MODE_CTEN_CTRL_HW S526_GPCT_MODE_CTEN_CTRL(2) +#define S526_GPCT_MODE_CTEN_CTRL_INVHW S526_GPCT_MODE_CTEN_CTRL(3) +#define S526_GPCT_MODE_CLK_SRC(x) ((x) << 9) +#define S526_GPCT_MODE_CLK_SRC_MASK S526_GPCT_MODE_CLK_SRC(0x3) +/* if count direction control set to quadrature */ +#define S526_GPCT_MODE_CLK_SRC_QUADX1 S526_GPCT_MODE_CLK_SRC(0) +#define S526_GPCT_MODE_CLK_SRC_QUADX2 S526_GPCT_MODE_CLK_SRC(1) +#define S526_GPCT_MODE_CLK_SRC_QUADX4 S526_GPCT_MODE_CLK_SRC(2) +#define S526_GPCT_MODE_CLK_SRC_QUADX4_ S526_GPCT_MODE_CLK_SRC(3) +/* if count direction control set to software control */ +#define S526_GPCT_MODE_CLK_SRC_ARISE S526_GPCT_MODE_CLK_SRC(0) +#define S526_GPCT_MODE_CLK_SRC_AFALL S526_GPCT_MODE_CLK_SRC(1) +#define S526_GPCT_MODE_CLK_SRC_INT S526_GPCT_MODE_CLK_SRC(2) +#define S526_GPCT_MODE_CLK_SRC_INTHALF S526_GPCT_MODE_CLK_SRC(3) +#define S526_GPCT_MODE_CT_DIR(x) ((x) << 11) +#define S526_GPCT_MODE_CT_DIR_MASK S526_GPCT_MODE_CT_DIR(0x1) +/* if count direction control set to software control */ +#define S526_GPCT_MODE_CT_DIR_UP S526_GPCT_MODE_CT_DIR(0) +#define S526_GPCT_MODE_CT_DIR_DOWN S526_GPCT_MODE_CT_DIR(1) +#define S526_GPCT_MODE_CTDIR_CTRL(x) ((x) << 12) +#define S526_GPCT_MODE_CTDIR_CTRL_MASK S526_GPCT_MODE_CTDIR_CTRL(0x1) +#define S526_GPCT_MODE_CTDIR_CTRL_QUAD S526_GPCT_MODE_CTDIR_CTRL(0) +#define S526_GPCT_MODE_CTDIR_CTRL_SOFT S526_GPCT_MODE_CTDIR_CTRL(1) +#define S526_GPCT_MODE_LATCH_CTRL(x) ((x) << 13) +#define S526_GPCT_MODE_LATCH_CTRL_MASK S526_GPCT_MODE_LATCH_CTRL(0x1) +#define S526_GPCT_MODE_LATCH_CTRL_READ S526_GPCT_MODE_LATCH_CTRL(0) +#define S526_GPCT_MODE_LATCH_CTRL_EVENT S526_GPCT_MODE_LATCH_CTRL(1) +#define S526_GPCT_MODE_PR_SELECT(x) ((x) << 14) +#define S526_GPCT_MODE_PR_SELECT_MASK S526_GPCT_MODE_PR_SELECT(0x1) +#define S526_GPCT_MODE_PR_SELECT_PR0 S526_GPCT_MODE_PR_SELECT(0) +#define S526_GPCT_MODE_PR_SELECT_PR1 S526_GPCT_MODE_PR_SELECT(1) +/* Control/Status - R = readable, W = writeable, C = write 1 to clear */ +#define S526_GPCT_CTRL_REG(x) (0x18 + ((x) * 8)) +#define S526_GPCT_CTRL_EV_STATUS(x) ((x) << 0) /* RC */ +#define S526_GPCT_CTRL_EV_STATUS_MASK S526_GPCT_EV_STATUS(0xf) +#define S526_GPCT_CTRL_EV_STATUS_NONE S526_GPCT_EV_STATUS(0) +/* these 4 bits can be OR'ed */ +#define S526_GPCT_CTRL_EV_STATUS_ECAP S526_GPCT_EV_STATUS(0x1) +#define S526_GPCT_CTRL_EV_STATUS_ICAPN S526_GPCT_EV_STATUS(0x2) +#define S526_GPCT_CTRL_EV_STATUS_ICAPP S526_GPCT_EV_STATUS(0x4) +#define S526_GPCT_CTRL_EV_STATUS_RCAP S526_GPCT_EV_STATUS(0x8) +#define S526_GPCT_CTRL_COUT_STATUS BIT(4) /* R */ +#define S526_GPCT_CTRL_INDEX_STATUS BIT(5) /* R */ +#define S525_GPCT_CTRL_INTEN(x) ((x) << 6) /* W */ +#define S525_GPCT_CTRL_INTEN_MASK S526_GPCT_CTRL_INTEN(0xf) +#define S525_GPCT_CTRL_INTEN_NONE S526_GPCT_CTRL_INTEN(0) +/* these 4 bits can be OR'ed */ +#define S525_GPCT_CTRL_INTEN_ERROR S526_GPCT_CTRL_INTEN(0x1) +#define S525_GPCT_CTRL_INTEN_IXFALL S526_GPCT_CTRL_INTEN(0x2) +#define S525_GPCT_CTRL_INTEN_IXRISE S526_GPCT_CTRL_INTEN(0x4) +#define S525_GPCT_CTRL_INTEN_RO S526_GPCT_CTRL_INTEN(0x8) +#define S525_GPCT_CTRL_LATCH_SEL(x) ((x) << 10) /* W */ +#define S525_GPCT_CTRL_LATCH_SEL_MASK S526_GPCT_CTRL_LATCH_SEL(0x7) +#define S525_GPCT_CTRL_LATCH_SEL_NONE S526_GPCT_CTRL_LATCH_SEL(0) +/* these 3 bits can be OR'ed */ +#define S525_GPCT_CTRL_LATCH_SEL_IXFALL S526_GPCT_CTRL_LATCH_SEL(0x1) +#define S525_GPCT_CTRL_LATCH_SEL_IXRISE S526_GPCT_CTRL_LATCH_SEL(0x2) +#define S525_GPCT_CTRL_LATCH_SEL_ITIMER S526_GPCT_CTRL_LATCH_SEL(0x4) +#define S525_GPCT_CTRL_CT_ARM BIT(13) /* W */ +#define S525_GPCT_CTRL_CT_LOAD BIT(14) /* W */ +#define S526_GPCT_CTRL_CT_RESET BIT(15) /* W */ +#define S526_EEPROM_DATA_REG 0x32 +#define S526_EEPROM_CTRL_REG 0x34 +#define S526_EEPROM_CTRL_ADDR(x) (((x) & 0x3f) << 3) +#define S526_EEPROM_CTRL(x) (((x) & 0x3) << 1) +#define S526_EEPROM_CTRL_READ S526_EEPROM_CTRL(2) +#define S526_EEPROM_CTRL_START BIT(0) + +struct s526_private { + unsigned int gpct_config[4]; + unsigned short ai_ctrl; +}; + +static void s526_gpct_write(struct comedi_device *dev, + unsigned int chan, unsigned int val) +{ + /* write high word then low word */ + outw((val >> 16) & 0xffff, dev->iobase + S526_GPCT_MSB_REG(chan)); + outw(val & 0xffff, dev->iobase + S526_GPCT_LSB_REG(chan)); +} + +static unsigned int s526_gpct_read(struct comedi_device *dev, + unsigned int chan) +{ + unsigned int val; + + /* read the low word then high word */ + val = inw(dev->iobase + S526_GPCT_LSB_REG(chan)) & 0xffff; + val |= (inw(dev->iobase + S526_GPCT_MSB_REG(chan)) & 0xff) << 16; + + return val; +} + +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); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = s526_gpct_read(dev, chan); + + 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 int val; + + /* + * 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 1 + /* Set Counter Mode Register */ + val = data[1] & 0xffff; + outw(val, dev->iobase + S526_GPCT_MODE_REG(chan)); + + /* Reset the counter if it is software preload */ + if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) == + S526_GPCT_MODE_AUTOLOAD_NONE) { + /* Reset the counter */ + outw(S526_GPCT_CTRL_CT_RESET, + dev->iobase + S526_GPCT_CTRL_REG(chan)); + /* + * Load the counter from PR0 + * outw(S526_GPCT_CTRL_CT_LOAD, + * dev->iobase + S526_GPCT_CTRL_REG(chan)); + */ + } +#else + val = S526_GPCT_MODE_CTDIR_CTRL_QUAD; + + /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */ + if (data[1] == GPCT_X2) + val |= S526_GPCT_MODE_CLK_SRC_QUADX2; + else if (data[1] == GPCT_X4) + val |= S526_GPCT_MODE_CLK_SRC_QUADX4; + else + val |= S526_GPCT_MODE_CLK_SRC_QUADX1; + + /* 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^ */ + val |= S526_GPCT_MODE_AUTOLOAD_IXRISE; + } + + /* Set Counter Mode Register */ + val = data[1] & 0xffff; + outw(val, dev->iobase + S526_GPCT_MODE_REG(chan)); + + /* Load the pre-load register */ + s526_gpct_write(dev, chan, data[2]); + + /* Write the Counter Control Register */ + if (data[3]) + outw(data[3] & 0xffff, + dev->iobase + S526_GPCT_CTRL_REG(chan)); + + /* Reset the counter if it is software preload */ + if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) == + S526_GPCT_MODE_AUTOLOAD_NONE) { + /* Reset the counter */ + outw(S526_GPCT_CTRL_CT_RESET, + dev->iobase + S526_GPCT_CTRL_REG(chan)); + /* Load the counter from PR0 */ + outw(S526_GPCT_CTRL_CT_LOAD, + dev->iobase + S526_GPCT_CTRL_REG(chan)); + } +#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 */ + val = data[1] & 0xffff; + /* Select PR0 */ + val &= ~S526_GPCT_MODE_PR_SELECT_MASK; + val |= S526_GPCT_MODE_PR_SELECT_PR0; + outw(val, dev->iobase + S526_GPCT_MODE_REG(chan)); + + /* Load the pre-load register 0 */ + s526_gpct_write(dev, chan, data[2]); + + /* Set Counter Mode Register */ + val = data[1] & 0xffff; + /* Select PR1 */ + val &= ~S526_GPCT_MODE_PR_SELECT_MASK; + val |= S526_GPCT_MODE_PR_SELECT_PR1; + outw(val, dev->iobase + S526_GPCT_MODE_REG(chan)); + + /* Load the pre-load register 1 */ + s526_gpct_write(dev, chan, data[3]); + + /* Write the Counter Control Register */ + if (data[4]) { + val = data[4] & 0xffff; + outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan)); + } + 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 */ + val = data[1] & 0xffff; + /* Select PR0 */ + val &= ~S526_GPCT_MODE_PR_SELECT_MASK; + val |= S526_GPCT_MODE_PR_SELECT_PR0; + outw(val, dev->iobase + S526_GPCT_MODE_REG(chan)); + + /* Load the pre-load register 0 */ + s526_gpct_write(dev, chan, data[2]); + + /* Set Counter Mode Register */ + val = data[1] & 0xffff; + /* Select PR1 */ + val &= ~S526_GPCT_MODE_PR_SELECT_MASK; + val |= S526_GPCT_MODE_PR_SELECT_PR1; + outw(val, dev->iobase + S526_GPCT_MODE_REG(chan)); + + /* Load the pre-load register 1 */ + s526_gpct_write(dev, chan, data[3]); + + /* Write the Counter Control Register */ + if (data[4]) { + val = data[4] & 0xffff; + outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan)); + } + break; + + default: + return -EINVAL; + } + + return insn->n; +} + +static int s526_gpct_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct s526_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + + inw(dev->iobase + S526_GPCT_MODE_REG(chan)); /* Is this 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; + /* to write the PULSE_WIDTH */ + fallthrough; + case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: + case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: + s526_gpct_write(dev, chan, data[0]); + break; + + default: + return -EINVAL; + } + + return insn->n; +} + +static int s526_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + S526_INT_STATUS_REG); + if (status & context) { + /* we got our eoc event, clear it */ + outw(context, dev->iobase + S526_INT_STATUS_REG); + return 0; + } + return -EBUSY; +} + +static int s526_ai_insn_read(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 int ctrl; + unsigned int val; + int ret; + int i; + + ctrl = S526_AI_CTRL_CONV(chan) | S526_AI_CTRL_READ(chan) | + S526_AI_CTRL_START; + if (ctrl != devpriv->ai_ctrl) { + /* + * The multiplexor needs to change, enable the 15us + * delay for the first sample. + */ + devpriv->ai_ctrl = ctrl; + ctrl |= S526_AI_CTRL_DELAY; + } + + for (i = 0; i < insn->n; i++) { + /* trigger conversion */ + outw(ctrl, dev->iobase + S526_AI_CTRL_REG); + ctrl &= ~S526_AI_CTRL_DELAY; + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AI); + if (ret) + return ret; + + val = inw(dev->iobase + S526_AI_REG); + data[i] = comedi_offset_munge(s, val); + } + + return insn->n; +} + +static int s526_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int ctrl = S526_AO_CTRL_CHAN(chan); + unsigned int val = s->readback[chan]; + int ret; + int i; + + outw(ctrl, dev->iobase + S526_AO_CTRL_REG); + ctrl |= S526_AO_CTRL_START; + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + S526_AO_REG); + outw(ctrl, dev->iobase + S526_AO_CTRL_REG); + + /* wait for conversion to end */ + ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AO); + if (ret) + return ret; + } + s->readback[chan] = val; + + return insn->n; +} + +static int s526_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + S526_DIO_CTRL_REG); + + data[1] = inw(dev->iobase + S526_DIO_CTRL_REG) & 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; + + /* + * Digital I/O can be configured as inputs or outputs in + * groups of 4; DIO group 1 (DIO0-3) and DIO group 2 (DIO4-7). + */ + 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) + s->state |= S526_DIO_CTRL_GRP1_OUT; + else + s->state &= ~S526_DIO_CTRL_GRP1_OUT; + if (s->io_bits & 0xf0) + s->state |= S526_DIO_CTRL_GRP2_OUT; + else + s->state &= ~S526_DIO_CTRL_GRP2_OUT; + + outw(s->state, dev->iobase + S526_DIO_CTRL_REG); + + return insn->n; +} + +static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct s526_private *devpriv; + struct comedi_subdevice *s; + int ret; + + ret = comedi_request_region(dev, it->options[0], 0x40); + if (ret) + return ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + return ret; + + /* General-Purpose Counter/Timer (GPCT) */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; + s->n_chan = 4; + s->maxdata = 0x00ffffff; + s->insn_read = s526_gpct_rinsn; + s->insn_config = s526_gpct_insn_config; + s->insn_write = s526_gpct_winsn; + + /* + * Analog Input subdevice + * channels 0 to 7 are the regular differential inputs + * channel 8 is "reference 0" (+10V) + * channel 9 is "reference 1" (0V) + */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF; + s->n_chan = 10; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->len_chanlist = 16; + s->insn_read = s526_ai_insn_read; + + /* Analog Output subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = s526_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O subdevice */ + s = &dev->subdevices[3]; + 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/s626.c b/drivers/comedi/drivers/s626.c new file mode 100644 index 000000000..0e5f9a9a7 --- /dev/null +++ b/drivers/comedi/drivers/s626.c @@ -0,0 +1,2604 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +/* + * Driver: s626 + * Description: Sensoray 626 driver + * Devices: [Sensoray] 626 (s626) + * Authors: Gianluca Palli <gpalli@deis.unibo.it>, + * Updated: Fri, 15 Feb 2008 10:28:42 +0000 + * Status: experimental + + * Configuration options: not applicable, uses PCI auto config + + * INSN_CONFIG instructions: + * analog input: + * none + * + * analog output: + * none + * + * digital channel: + * s626 has 3 dio subdevices (2,3 and 4) each with 16 i/o channels + * supported configuration options: + * INSN_CONFIG_DIO_QUERY + * COMEDI_INPUT + * COMEDI_OUTPUT + * + * encoder: + * Every channel must be configured before reading. + * + * Example code + * + * insn.insn=INSN_CONFIG; //configuration instruction + * insn.n=1; //number of operation (must be 1) + * insn.data=&initialvalue; //initial value loaded into encoder + * //during configuration + * insn.subdev=5; //encoder subdevice + * insn.chanspec=CR_PACK(encoder_channel,0,AREF_OTHER); //encoder_channel + * //to configure + * + * comedi_do_insn(cf,&insn); //executing configuration + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/comedi/comedi_pci.h> + +#include "s626.h" + +struct s626_buffer_dma { + dma_addr_t physical_base; + void *logical_base; +}; + +/** + * struct s626_private - Working data for s626 driver. + * @ai_cmd_running: non-zero if ai_cmd is running. + * @ai_sample_timer: time between samples in units of the timer. + * @ai_convert_count: conversion counter. + * @ai_convert_timer: time between conversion in units of the timer. + * @counter_int_enabs: counter interrupt enable mask for MISC2 register. + * @adc_items: number of items in ADC poll list. + * @rps_buf: DMA buffer used to hold ADC (RPS1) program. + * @ana_buf: DMA buffer used to receive ADC data and hold DAC data. + * @dac_wbuf: pointer to logical adrs of DMA buffer used to hold DAC data. + * @dacpol: image of DAC polarity register. + * @trim_setpoint: images of TrimDAC setpoints. + * @i2c_adrs: I2C device address for onboard EEPROM (board rev dependent) + */ +struct s626_private { + u8 ai_cmd_running; + unsigned int ai_sample_timer; + int ai_convert_count; + unsigned int ai_convert_timer; + u16 counter_int_enabs; + u8 adc_items; + struct s626_buffer_dma rps_buf; + struct s626_buffer_dma ana_buf; + u32 *dac_wbuf; + u16 dacpol; + u8 trim_setpoint[12]; + u32 i2c_adrs; +}; + +/* Counter overflow/index event flag masks for RDMISC2. */ +#define S626_INDXMASK(C) (1 << (((C) > 2) ? ((C) * 2 - 1) : ((C) * 2 + 4))) +#define S626_OVERMASK(C) (1 << (((C) > 2) ? ((C) * 2 + 5) : ((C) * 2 + 10))) + +/* + * Enable/disable a function or test status bit(s) that are accessed + * through Main Control Registers 1 or 2. + */ +static void s626_mc_enable(struct comedi_device *dev, + unsigned int cmd, unsigned int reg) +{ + unsigned int val = (cmd << 16) | cmd; + + writel(val, dev->mmio + reg); +} + +static void s626_mc_disable(struct comedi_device *dev, + unsigned int cmd, unsigned int reg) +{ + writel(cmd << 16, dev->mmio + reg); +} + +static bool s626_mc_test(struct comedi_device *dev, + unsigned int cmd, unsigned int reg) +{ + unsigned int val; + + val = readl(dev->mmio + reg); + + return (val & cmd) ? true : false; +} + +#define S626_BUGFIX_STREG(REGADRS) ((REGADRS) - 4) + +/* Write a time slot control record to TSL2. */ +#define S626_VECTPORT(VECTNUM) (S626_P_TSL2 + ((VECTNUM) << 2)) + +static const struct comedi_lrange s626_range_table = { + 2, { + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +/* + * Execute a DEBI transfer. This must be called from within a critical section. + */ +static void s626_debi_transfer(struct comedi_device *dev) +{ + static const int timeout = 10000; + int i; + + /* Initiate upload of shadow RAM to DEBI control register */ + s626_mc_enable(dev, S626_MC2_UPLD_DEBI, S626_P_MC2); + + /* + * Wait for completion of upload from shadow RAM to + * DEBI control register. + */ + for (i = 0; i < timeout; i++) { + if (s626_mc_test(dev, S626_MC2_UPLD_DEBI, S626_P_MC2)) + break; + udelay(1); + } + if (i == timeout) + dev_err(dev->class_dev, + "Timeout while uploading to DEBI control register\n"); + + /* Wait until DEBI transfer is done */ + for (i = 0; i < timeout; i++) { + if (!(readl(dev->mmio + S626_P_PSR) & S626_PSR_DEBI_S)) + break; + udelay(1); + } + if (i == timeout) + dev_err(dev->class_dev, "DEBI transfer timeout\n"); +} + +/* + * Read a value from a gate array register. + */ +static u16 s626_debi_read(struct comedi_device *dev, u16 addr) +{ + /* Set up DEBI control register value in shadow RAM */ + writel(S626_DEBI_CMD_RDWORD | addr, dev->mmio + S626_P_DEBICMD); + + /* Execute the DEBI transfer. */ + s626_debi_transfer(dev); + + return readl(dev->mmio + S626_P_DEBIAD); +} + +/* + * Write a value to a gate array register. + */ +static void s626_debi_write(struct comedi_device *dev, u16 addr, + u16 wdata) +{ + /* Set up DEBI control register value in shadow RAM */ + writel(S626_DEBI_CMD_WRWORD | addr, dev->mmio + S626_P_DEBICMD); + writel(wdata, dev->mmio + S626_P_DEBIAD); + + /* Execute the DEBI transfer. */ + s626_debi_transfer(dev); +} + +/* + * Replace the specified bits in a gate array register. Imports: mask + * specifies bits that are to be preserved, wdata is new value to be + * or'd with the masked original. + */ +static void s626_debi_replace(struct comedi_device *dev, unsigned int addr, + unsigned int mask, unsigned int wdata) +{ + unsigned int val; + + addr &= 0xffff; + writel(S626_DEBI_CMD_RDWORD | addr, dev->mmio + S626_P_DEBICMD); + s626_debi_transfer(dev); + + writel(S626_DEBI_CMD_WRWORD | addr, dev->mmio + S626_P_DEBICMD); + val = readl(dev->mmio + S626_P_DEBIAD); + val &= mask; + val |= wdata; + writel(val & 0xffff, dev->mmio + S626_P_DEBIAD); + s626_debi_transfer(dev); +} + +/* ************** EEPROM ACCESS FUNCTIONS ************** */ + +static int s626_i2c_handshake_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + bool status; + + status = s626_mc_test(dev, S626_MC2_UPLD_IIC, S626_P_MC2); + if (status) + return 0; + return -EBUSY; +} + +static int s626_i2c_handshake(struct comedi_device *dev, u32 val) +{ + unsigned int ctrl; + int ret; + + /* Write I2C command to I2C Transfer Control shadow register */ + writel(val, dev->mmio + S626_P_I2CCTRL); + + /* + * Upload I2C shadow registers into working registers and + * wait for upload confirmation. + */ + s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2); + ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0); + if (ret) + return ret; + + /* Wait until I2C bus transfer is finished or an error occurs */ + do { + ctrl = readl(dev->mmio + S626_P_I2CCTRL); + } while ((ctrl & (S626_I2C_BUSY | S626_I2C_ERR)) == S626_I2C_BUSY); + + /* Return non-zero if I2C error occurred */ + return ctrl & S626_I2C_ERR; +} + +/* Read u8 from EEPROM. */ +static u8 s626_i2c_read(struct comedi_device *dev, u8 addr) +{ + struct s626_private *devpriv = dev->private; + + /* + * Send EEPROM target address: + * Byte2 = I2C command: write to I2C EEPROM device. + * Byte1 = EEPROM internal target address. + * Byte0 = Not sent. + */ + if (s626_i2c_handshake(dev, S626_I2C_B2(S626_I2C_ATTRSTART, + devpriv->i2c_adrs) | + S626_I2C_B1(S626_I2C_ATTRSTOP, addr) | + S626_I2C_B0(S626_I2C_ATTRNOP, 0))) + /* Abort function and declare error if handshake failed. */ + return 0; + + /* + * Execute EEPROM read: + * Byte2 = I2C command: read from I2C EEPROM device. + * Byte1 receives uint8_t from EEPROM. + * Byte0 = Not sent. + */ + if (s626_i2c_handshake(dev, S626_I2C_B2(S626_I2C_ATTRSTART, + (devpriv->i2c_adrs | 1)) | + S626_I2C_B1(S626_I2C_ATTRSTOP, 0) | + S626_I2C_B0(S626_I2C_ATTRNOP, 0))) + /* Abort function and declare error if handshake failed. */ + return 0; + + return (readl(dev->mmio + S626_P_I2CCTRL) >> 16) & 0xff; +} + +/* *********** DAC FUNCTIONS *********** */ + +/* TrimDac LogicalChan-to-PhysicalChan mapping table. */ +static const u8 s626_trimchan[] = { 10, 9, 8, 3, 2, 7, 6, 1, 0, 5, 4 }; + +/* TrimDac LogicalChan-to-EepromAdrs mapping table. */ +static const u8 s626_trimadrs[] = { + 0x40, 0x41, 0x42, 0x50, 0x51, 0x52, 0x53, 0x60, 0x61, 0x62, 0x63 +}; + +enum { + s626_send_dac_wait_not_mc1_a2out, + s626_send_dac_wait_ssr_af2_out, + s626_send_dac_wait_fb_buffer2_msb_00, + s626_send_dac_wait_fb_buffer2_msb_ff +}; + +static int s626_send_dac_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + switch (context) { + case s626_send_dac_wait_not_mc1_a2out: + status = readl(dev->mmio + S626_P_MC1); + if (!(status & S626_MC1_A2OUT)) + return 0; + break; + case s626_send_dac_wait_ssr_af2_out: + status = readl(dev->mmio + S626_P_SSR); + if (status & S626_SSR_AF2_OUT) + return 0; + break; + case s626_send_dac_wait_fb_buffer2_msb_00: + status = readl(dev->mmio + S626_P_FB_BUFFER2); + if (!(status & 0xff000000)) + return 0; + break; + case s626_send_dac_wait_fb_buffer2_msb_ff: + status = readl(dev->mmio + S626_P_FB_BUFFER2); + if (status & 0xff000000) + return 0; + break; + default: + return -EINVAL; + } + return -EBUSY; +} + +/* + * Private helper function: Transmit serial data to DAC via Audio + * channel 2. Assumes: (1) TSL2 slot records initialized, and (2) + * dacpol contains valid target image. + */ +static int s626_send_dac(struct comedi_device *dev, u32 val) +{ + struct s626_private *devpriv = dev->private; + int ret; + + /* START THE SERIAL CLOCK RUNNING ------------- */ + + /* + * Assert DAC polarity control and enable gating of DAC serial clock + * and audio bit stream signals. At this point in time we must be + * assured of being in time slot 0. If we are not in slot 0, the + * serial clock and audio stream signals will be disabled; this is + * because the following s626_debi_write statement (which enables + * signals to be passed through the gate array) would execute before + * the trailing edge of WS1/WS3 (which turns off the signals), thus + * causing the signals to be inactive during the DAC write. + */ + s626_debi_write(dev, S626_LP_DACPOL, devpriv->dacpol); + + /* TRANSFER OUTPUT DWORD VALUE INTO A2'S OUTPUT FIFO ---------------- */ + + /* Copy DAC setpoint value to DAC's output DMA buffer. */ + /* writel(val, dev->mmio + (uint32_t)devpriv->dac_wbuf); */ + *devpriv->dac_wbuf = val; + + /* + * Enable the output DMA transfer. This will cause the DMAC to copy + * the DAC's data value to A2's output FIFO. The DMA transfer will + * then immediately terminate because the protection address is + * reached upon transfer of the first DWORD value. + */ + s626_mc_enable(dev, S626_MC1_A2OUT, S626_P_MC1); + + /* While the DMA transfer is executing ... */ + + /* + * Reset Audio2 output FIFO's underflow flag (along with any + * other FIFO underflow/overflow flags). When set, this flag + * will indicate that we have emerged from slot 0. + */ + writel(S626_ISR_AFOU, dev->mmio + S626_P_ISR); + + /* + * Wait for the DMA transfer to finish so that there will be data + * available in the FIFO when time slot 1 tries to transfer a DWORD + * from the FIFO to the output buffer register. We test for DMA + * Done by polling the DMAC enable flag; this flag is automatically + * cleared when the transfer has finished. + */ + ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc, + s626_send_dac_wait_not_mc1_a2out); + if (ret) { + dev_err(dev->class_dev, "DMA transfer timeout\n"); + return ret; + } + + /* START THE OUTPUT STREAM TO THE TARGET DAC -------------------- */ + + /* + * FIFO data is now available, so we enable execution of time slots + * 1 and higher by clearing the EOS flag in slot 0. Note that SD3 + * will be shifted in and stored in FB_BUFFER2 for end-of-slot-list + * detection. + */ + writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2, + dev->mmio + S626_VECTPORT(0)); + + /* + * Wait for slot 1 to execute to ensure that the Packet will be + * transmitted. This is detected by polling the Audio2 output FIFO + * underflow flag, which will be set when slot 1 execution has + * finished transferring the DAC's data DWORD from the output FIFO + * to the output buffer register. + */ + ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc, + s626_send_dac_wait_ssr_af2_out); + if (ret) { + dev_err(dev->class_dev, + "TSL timeout waiting for slot 1 to execute\n"); + return ret; + } + + /* + * Set up to trap execution at slot 0 when the TSL sequencer cycles + * back to slot 0 after executing the EOS in slot 5. Also, + * simultaneously shift out and in the 0x00 that is ALWAYS the value + * stored in the last byte to be shifted out of the FIFO's DWORD + * buffer register. + */ + writel(S626_XSD2 | S626_XFIFO_2 | S626_RSD2 | S626_SIB_A2 | S626_EOS, + dev->mmio + S626_VECTPORT(0)); + + /* WAIT FOR THE TRANSACTION TO FINISH ----------------------- */ + + /* + * Wait for the TSL to finish executing all time slots before + * exiting this function. We must do this so that the next DAC + * write doesn't start, thereby enabling clock/chip select signals: + * + * 1. Before the TSL sequence cycles back to slot 0, which disables + * the clock/cs signal gating and traps slot // list execution. + * we have not yet finished slot 5 then the clock/cs signals are + * still gated and we have not finished transmitting the stream. + * + * 2. While slots 2-5 are executing due to a late slot 0 trap. In + * this case, the slot sequence is currently repeating, but with + * clock/cs signals disabled. We must wait for slot 0 to trap + * execution before setting up the next DAC setpoint DMA transfer + * and enabling the clock/cs signals. To detect the end of slot 5, + * we test for the FB_BUFFER2 MSB contents to be equal to 0xFF. If + * the TSL has not yet finished executing slot 5 ... + */ + if (readl(dev->mmio + S626_P_FB_BUFFER2) & 0xff000000) { + /* + * The trap was set on time and we are still executing somewhere + * in slots 2-5, so we now wait for slot 0 to execute and trap + * TSL execution. This is detected when FB_BUFFER2 MSB changes + * from 0xFF to 0x00, which slot 0 causes to happen by shifting + * out/in on SD2 the 0x00 that is always referenced by slot 5. + */ + ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc, + s626_send_dac_wait_fb_buffer2_msb_00); + if (ret) { + dev_err(dev->class_dev, + "TSL timeout waiting for slot 0 to execute\n"); + return ret; + } + } + /* + * Either (1) we were too late setting the slot 0 trap; the TSL + * sequencer restarted slot 0 before we could set the EOS trap flag, + * or (2) we were not late and execution is now trapped at slot 0. + * In either case, we must now change slot 0 so that it will store + * value 0xFF (instead of 0x00) to FB_BUFFER2 next time it executes. + * In order to do this, we reprogram slot 0 so that it will shift in + * SD3, which is driven only by a pull-up resistor. + */ + writel(S626_RSD3 | S626_SIB_A2 | S626_EOS, + dev->mmio + S626_VECTPORT(0)); + + /* + * Wait for slot 0 to execute, at which time the TSL is setup for + * the next DAC write. This is detected when FB_BUFFER2 MSB changes + * from 0x00 to 0xFF. + */ + ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc, + s626_send_dac_wait_fb_buffer2_msb_ff); + if (ret) { + dev_err(dev->class_dev, + "TSL timeout waiting for slot 0 to execute\n"); + return ret; + } + return 0; +} + +/* + * Private helper function: Write setpoint to an application DAC channel. + */ +static int s626_set_dac(struct comedi_device *dev, + u16 chan, int16_t dacdata) +{ + struct s626_private *devpriv = dev->private; + u16 signmask; + u32 ws_image; + u32 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 ((u16)dacdata > 0x1FFF) + dacdata = 0x1FFF; + + /* + * Set up TSL2 records (aka "vectors") for DAC update. Vectors V2 + * and V3 transmit the setpoint to the target DAC. V4 and V5 send + * data to a non-existent TrimDac channel just to keep the clock + * running after sending data to the target DAC. This is necessary + * to eliminate the clock glitch that would otherwise occur at the + * end of the target DAC's serial data stream. When the sequence + * restarts at V0 (after executing V5), the gate array automatically + * disables gating for the DAC clock and all DAC chip selects. + */ + + /* Choose DAC chip select to be asserted */ + ws_image = (chan & 2) ? S626_WS1 : S626_WS2; + /* Slot 2: Transmit high data byte to target DAC */ + writel(S626_XSD2 | S626_XFIFO_1 | ws_image, + dev->mmio + S626_VECTPORT(2)); + /* Slot 3: Transmit low data byte to target DAC */ + writel(S626_XSD2 | S626_XFIFO_0 | ws_image, + dev->mmio + S626_VECTPORT(3)); + /* Slot 4: Transmit to non-existent TrimDac channel to keep clock */ + writel(S626_XSD2 | S626_XFIFO_3 | S626_WS3, + dev->mmio + S626_VECTPORT(4)); + /* Slot 5: running after writing target DAC's low data byte */ + writel(S626_XSD2 | S626_XFIFO_2 | S626_WS3 | S626_EOS, + dev->mmio + S626_VECTPORT(5)); + + /* + * Construct and transmit target DAC's serial packet: + * (A10D DDDD), (DDDD DDDD), (0x0F), (0x00) where A is chan<0>, + * and D<12:0> is the DAC setpoint. Append a WORD value (that writes + * to a non-existent TrimDac channel) that serves to keep the clock + * running after the packet has been sent to the target DAC. + */ + val = 0x0F000000; /* Continue clock after target DAC data + * (write to non-existent trimdac). + */ + val |= 0x00004000; /* Address the two main dual-DAC devices + * (TSL's chip select enables target device). + */ + val |= ((u32)(chan & 1) << 15); /* Address the DAC channel + * within the device. + */ + val |= (u32)dacdata; /* Include DAC setpoint data. */ + return s626_send_dac(dev, val); +} + +static int s626_write_trim_dac(struct comedi_device *dev, + u8 logical_chan, u8 dac_data) +{ + struct s626_private *devpriv = dev->private; + u32 chan; + + /* + * Save the new setpoint in case the application needs to read it back + * later. + */ + devpriv->trim_setpoint[logical_chan] = dac_data; + + /* Map logical channel number to physical channel number. */ + chan = s626_trimchan[logical_chan]; + + /* + * Set up TSL2 records for TrimDac write operation. All slots shift + * 0xFF in from pulled-up SD3 so that the end of the slot sequence + * can be detected. + */ + + /* Slot 2: Send high uint8_t to target TrimDac */ + writel(S626_XSD2 | S626_XFIFO_1 | S626_WS3, + dev->mmio + S626_VECTPORT(2)); + /* Slot 3: Send low uint8_t to target TrimDac */ + writel(S626_XSD2 | S626_XFIFO_0 | S626_WS3, + dev->mmio + S626_VECTPORT(3)); + /* Slot 4: Send NOP high uint8_t to DAC0 to keep clock running */ + writel(S626_XSD2 | S626_XFIFO_3 | S626_WS1, + dev->mmio + S626_VECTPORT(4)); + /* Slot 5: Send NOP low uint8_t to DAC0 */ + writel(S626_XSD2 | S626_XFIFO_2 | S626_WS1 | S626_EOS, + dev->mmio + S626_VECTPORT(5)); + + /* + * Construct and transmit target DAC's serial packet: + * (0000 AAAA), (DDDD DDDD), (0x00), (0x00) where A<3:0> is the + * DAC channel's address, and D<7:0> is the DAC setpoint. Append a + * WORD value (that writes a channel 0 NOP command to a non-existent + * main DAC channel) that serves to keep the clock running after the + * packet has been sent to the target DAC. + */ + + /* + * Address the DAC channel within the trimdac device. + * Include DAC setpoint data. + */ + return s626_send_dac(dev, (chan << 8) | dac_data); +} + +static int s626_load_trim_dacs(struct comedi_device *dev) +{ + u8 i; + int ret; + + /* Copy TrimDac setpoint values from EEPROM to TrimDacs. */ + for (i = 0; i < ARRAY_SIZE(s626_trimchan); i++) { + ret = s626_write_trim_dac(dev, i, + s626_i2c_read(dev, s626_trimadrs[i])); + if (ret) + return ret; + } + return 0; +} + +/* ****** COUNTER FUNCTIONS ******* */ + +/* + * All counter functions address a specific counter by means of the + * "Counter" argument, which is a logical counter number. The Counter + * argument may have any of the following legal values: 0=0A, 1=1A, + * 2=2A, 3=0B, 4=1B, 5=2B. + */ + +/* + * Return/set a counter pair's latch trigger source. 0: On read + * access, 1: A index latches A, 2: B index latches B, 3: A overflow + * latches B. + */ +static void s626_set_latch_source(struct comedi_device *dev, + unsigned int chan, u16 value) +{ + s626_debi_replace(dev, S626_LP_CRB(chan), + ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_LATCHSRC), + S626_SET_CRB_LATCHSRC(value)); +} + +/* + * Write value into counter preload register. + */ +static void s626_preload(struct comedi_device *dev, + unsigned int chan, u32 value) +{ + s626_debi_write(dev, S626_LP_CNTR(chan), value); + s626_debi_write(dev, S626_LP_CNTR(chan) + 2, value >> 16); +} + +/* ****** PRIVATE COUNTER FUNCTIONS ****** */ + +/* + * Reset a counter's index and overflow event capture flags. + */ +static void s626_reset_cap_flags(struct comedi_device *dev, + unsigned int chan) +{ + u16 set; + + set = S626_SET_CRB_INTRESETCMD(1); + if (chan < 3) + set |= S626_SET_CRB_INTRESET_A(1); + else + set |= S626_SET_CRB_INTRESET_B(1); + + s626_debi_replace(dev, S626_LP_CRB(chan), ~S626_CRBMSK_INTCTRL, set); +} + +/* + * Set the operating mode for the specified counter. The setup + * parameter is treated as a COUNTER_SETUP data type. The following + * parameters are programmable (all other parms are ignored): ClkMult, + * ClkPol, ClkEnab, IndexSrc, IndexPol, LoadSrc. + */ +static void s626_set_mode_a(struct comedi_device *dev, + unsigned int chan, u16 setup, + u16 disable_int_src) +{ + struct s626_private *devpriv = dev->private; + u16 cra; + u16 crb; + unsigned int cntsrc, clkmult, clkpol; + + /* Initialize CRA and CRB images. */ + /* Preload trigger is passed through. */ + cra = S626_SET_CRA_LOADSRC_A(S626_GET_STD_LOADSRC(setup)); + /* IndexSrc is passed through. */ + cra |= S626_SET_CRA_INDXSRC_A(S626_GET_STD_INDXSRC(setup)); + + /* Reset any pending CounterA event captures. */ + crb = S626_SET_CRB_INTRESETCMD(1) | S626_SET_CRB_INTRESET_A(1); + /* Clock enable is passed through. */ + crb |= S626_SET_CRB_CLKENAB_A(S626_GET_STD_CLKENAB(setup)); + + /* Force IntSrc to Disabled if disable_int_src is asserted. */ + if (!disable_int_src) + cra |= S626_SET_CRA_INTSRC_A(S626_GET_STD_INTSRC(setup)); + + /* Populate all mode-dependent attributes of CRA & CRB images. */ + clkpol = S626_GET_STD_CLKPOL(setup); + switch (S626_GET_STD_ENCMODE(setup)) { + case S626_ENCMODE_EXTENDER: /* Extender Mode: */ + /* Force to Timer mode (Extender valid only for B counters). */ + /* Fall through to case S626_ENCMODE_TIMER: */ + case S626_ENCMODE_TIMER: /* Timer Mode: */ + /* CntSrcA<1> selects system clock */ + cntsrc = S626_CNTSRC_SYSCLK; + /* Count direction (CntSrcA<0>) obtained from ClkPol. */ + cntsrc |= clkpol; + /* ClkPolA behaves as always-on clock enable. */ + clkpol = 1; + /* ClkMult must be 1x. */ + clkmult = S626_CLKMULT_1X; + break; + default: /* Counter Mode: */ + /* Select ENC_C and ENC_D as clock/direction inputs. */ + cntsrc = S626_CNTSRC_ENCODER; + /* Clock polarity is passed through. */ + /* Force multiplier to x1 if not legal, else pass through. */ + clkmult = S626_GET_STD_CLKMULT(setup); + if (clkmult == S626_CLKMULT_SPECIAL) + clkmult = S626_CLKMULT_1X; + break; + } + cra |= S626_SET_CRA_CNTSRC_A(cntsrc) | S626_SET_CRA_CLKPOL_A(clkpol) | + S626_SET_CRA_CLKMULT_A(clkmult); + + /* + * Force positive index polarity if IndxSrc is software-driven only, + * otherwise pass it through. + */ + if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT) + cra |= S626_SET_CRA_INDXPOL_A(S626_GET_STD_INDXPOL(setup)); + + /* + * If IntSrc has been forced to Disabled, update the MISC2 interrupt + * enable mask to indicate the counter interrupt is disabled. + */ + if (disable_int_src) + devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) | + S626_INDXMASK(chan)); + + /* + * While retaining CounterB and LatchSrc configurations, program the + * new counter operating mode. + */ + s626_debi_replace(dev, S626_LP_CRA(chan), + S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B, cra); + s626_debi_replace(dev, S626_LP_CRB(chan), + ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A), crb); +} + +static void s626_set_mode_b(struct comedi_device *dev, + unsigned int chan, u16 setup, + u16 disable_int_src) +{ + struct s626_private *devpriv = dev->private; + u16 cra; + u16 crb; + unsigned int cntsrc, clkmult, clkpol; + + /* Initialize CRA and CRB images. */ + /* IndexSrc is passed through. */ + cra = S626_SET_CRA_INDXSRC_B(S626_GET_STD_INDXSRC(setup)); + + /* Reset event captures and disable interrupts. */ + crb = S626_SET_CRB_INTRESETCMD(1) | S626_SET_CRB_INTRESET_B(1); + /* Clock enable is passed through. */ + crb |= S626_SET_CRB_CLKENAB_B(S626_GET_STD_CLKENAB(setup)); + /* Preload trigger source is passed through. */ + crb |= S626_SET_CRB_LOADSRC_B(S626_GET_STD_LOADSRC(setup)); + + /* Force IntSrc to Disabled if disable_int_src is asserted. */ + if (!disable_int_src) + crb |= S626_SET_CRB_INTSRC_B(S626_GET_STD_INTSRC(setup)); + + /* Populate all mode-dependent attributes of CRA & CRB images. */ + clkpol = S626_GET_STD_CLKPOL(setup); + switch (S626_GET_STD_ENCMODE(setup)) { + case S626_ENCMODE_TIMER: /* Timer Mode: */ + /* CntSrcB<1> selects system clock */ + cntsrc = S626_CNTSRC_SYSCLK; + /* with direction (CntSrcB<0>) obtained from ClkPol. */ + cntsrc |= clkpol; + /* ClkPolB behaves as always-on clock enable. */ + clkpol = 1; + /* ClkMultB must be 1x. */ + clkmult = S626_CLKMULT_1X; + break; + case S626_ENCMODE_EXTENDER: /* Extender Mode: */ + /* CntSrcB source is OverflowA (same as "timer") */ + cntsrc = S626_CNTSRC_SYSCLK; + /* with direction obtained from ClkPol. */ + cntsrc |= clkpol; + /* ClkPolB controls IndexB -- always set to active. */ + clkpol = 1; + /* ClkMultB selects OverflowA as the clock source. */ + clkmult = S626_CLKMULT_SPECIAL; + break; + default: /* Counter Mode: */ + /* Select ENC_C and ENC_D as clock/direction inputs. */ + cntsrc = S626_CNTSRC_ENCODER; + /* ClkPol is passed through. */ + /* Force ClkMult to x1 if not legal, otherwise pass through. */ + clkmult = S626_GET_STD_CLKMULT(setup); + if (clkmult == S626_CLKMULT_SPECIAL) + clkmult = S626_CLKMULT_1X; + break; + } + cra |= S626_SET_CRA_CNTSRC_B(cntsrc); + crb |= S626_SET_CRB_CLKPOL_B(clkpol) | S626_SET_CRB_CLKMULT_B(clkmult); + + /* + * Force positive index polarity if IndxSrc is software-driven only, + * otherwise pass it through. + */ + if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT) + crb |= S626_SET_CRB_INDXPOL_B(S626_GET_STD_INDXPOL(setup)); + + /* + * If IntSrc has been forced to Disabled, update the MISC2 interrupt + * enable mask to indicate the counter interrupt is disabled. + */ + if (disable_int_src) + devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) | + S626_INDXMASK(chan)); + + /* + * While retaining CounterA and LatchSrc configurations, program the + * new counter operating mode. + */ + s626_debi_replace(dev, S626_LP_CRA(chan), + ~(S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B), cra); + s626_debi_replace(dev, S626_LP_CRB(chan), + S626_CRBMSK_CLKENAB_A | S626_CRBMSK_LATCHSRC, crb); +} + +static void s626_set_mode(struct comedi_device *dev, + unsigned int chan, + u16 setup, u16 disable_int_src) +{ + if (chan < 3) + s626_set_mode_a(dev, chan, setup, disable_int_src); + else + s626_set_mode_b(dev, chan, setup, disable_int_src); +} + +/* + * Return/set a counter's enable. enab: 0=always enabled, 1=enabled by index. + */ +static void s626_set_enable(struct comedi_device *dev, + unsigned int chan, u16 enab) +{ + unsigned int mask = S626_CRBMSK_INTCTRL; + unsigned int set; + + if (chan < 3) { + mask |= S626_CRBMSK_CLKENAB_A; + set = S626_SET_CRB_CLKENAB_A(enab); + } else { + mask |= S626_CRBMSK_CLKENAB_B; + set = S626_SET_CRB_CLKENAB_B(enab); + } + s626_debi_replace(dev, S626_LP_CRB(chan), ~mask, set); +} + +/* + * Return/set the event that will trigger transfer of the preload + * register into the counter. 0=ThisCntr_Index, 1=ThisCntr_Overflow, + * 2=OverflowA (B counters only), 3=disabled. + */ +static void s626_set_load_trig(struct comedi_device *dev, + unsigned int chan, u16 trig) +{ + u16 reg; + u16 mask; + u16 set; + + if (chan < 3) { + reg = S626_LP_CRA(chan); + mask = S626_CRAMSK_LOADSRC_A; + set = S626_SET_CRA_LOADSRC_A(trig); + } else { + reg = S626_LP_CRB(chan); + mask = S626_CRBMSK_LOADSRC_B | S626_CRBMSK_INTCTRL; + set = S626_SET_CRB_LOADSRC_B(trig); + } + s626_debi_replace(dev, reg, ~mask, set); +} + +/* + * Return/set counter interrupt source and clear any captured + * index/overflow events. int_source: 0=Disabled, 1=OverflowOnly, + * 2=IndexOnly, 3=IndexAndOverflow. + */ +static void s626_set_int_src(struct comedi_device *dev, + unsigned int chan, u16 int_source) +{ + struct s626_private *devpriv = dev->private; + u16 cra_reg = S626_LP_CRA(chan); + u16 crb_reg = S626_LP_CRB(chan); + + if (chan < 3) { + /* Reset any pending counter overflow or index captures */ + s626_debi_replace(dev, crb_reg, ~S626_CRBMSK_INTCTRL, + S626_SET_CRB_INTRESETCMD(1) | + S626_SET_CRB_INTRESET_A(1)); + + /* Program counter interrupt source */ + s626_debi_replace(dev, cra_reg, ~S626_CRAMSK_INTSRC_A, + S626_SET_CRA_INTSRC_A(int_source)); + } else { + u16 crb; + + /* Cache writeable CRB register image */ + crb = s626_debi_read(dev, crb_reg); + crb &= ~S626_CRBMSK_INTCTRL; + + /* Reset any pending counter overflow or index captures */ + s626_debi_write(dev, crb_reg, + crb | S626_SET_CRB_INTRESETCMD(1) | + S626_SET_CRB_INTRESET_B(1)); + + /* Program counter interrupt source */ + s626_debi_write(dev, crb_reg, + (crb & ~S626_CRBMSK_INTSRC_B) | + S626_SET_CRB_INTSRC_B(int_source)); + } + + /* Update MISC2 interrupt enable mask. */ + devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) | + S626_INDXMASK(chan)); + switch (int_source) { + case 0: + default: + break; + case 1: + devpriv->counter_int_enabs |= S626_OVERMASK(chan); + break; + case 2: + devpriv->counter_int_enabs |= S626_INDXMASK(chan); + break; + case 3: + devpriv->counter_int_enabs |= (S626_OVERMASK(chan) | + S626_INDXMASK(chan)); + break; + } +} + +/* + * Generate an index pulse. + */ +static void s626_pulse_index(struct comedi_device *dev, + unsigned int chan) +{ + if (chan < 3) { + u16 cra; + + cra = s626_debi_read(dev, S626_LP_CRA(chan)); + + /* Pulse index */ + s626_debi_write(dev, S626_LP_CRA(chan), + (cra ^ S626_CRAMSK_INDXPOL_A)); + s626_debi_write(dev, S626_LP_CRA(chan), cra); + } else { + u16 crb; + + crb = s626_debi_read(dev, S626_LP_CRB(chan)); + crb &= ~S626_CRBMSK_INTCTRL; + + /* Pulse index */ + s626_debi_write(dev, S626_LP_CRB(chan), + (crb ^ S626_CRBMSK_INDXPOL_B)); + s626_debi_write(dev, S626_LP_CRB(chan), crb); + } +} + +static unsigned int s626_ai_reg_to_uint(unsigned int data) +{ + return ((data >> 18) & 0x3fff) ^ 0x2000; +} + +static int s626_dio_set_irq(struct comedi_device *dev, unsigned int chan) +{ + unsigned int group = chan / 16; + unsigned int mask = 1 << (chan - (16 * group)); + unsigned int status; + + /* set channel to capture positive edge */ + status = s626_debi_read(dev, S626_LP_RDEDGSEL(group)); + s626_debi_write(dev, S626_LP_WREDGSEL(group), mask | status); + + /* enable interrupt on selected channel */ + status = s626_debi_read(dev, S626_LP_RDINTSEL(group)); + s626_debi_write(dev, S626_LP_WRINTSEL(group), mask | status); + + /* enable edge capture write command */ + s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_EDCAP); + + /* enable edge capture on selected channel */ + status = s626_debi_read(dev, S626_LP_RDCAPSEL(group)); + s626_debi_write(dev, S626_LP_WRCAPSEL(group), mask | status); + + return 0; +} + +static int s626_dio_reset_irq(struct comedi_device *dev, unsigned int group, + unsigned int mask) +{ + /* disable edge capture write command */ + s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP); + + /* enable edge capture on selected channel */ + s626_debi_write(dev, S626_LP_WRCAPSEL(group), mask); + + return 0; +} + +static int s626_dio_clear_irq(struct comedi_device *dev) +{ + unsigned int group; + + /* disable edge capture write command */ + s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP); + + /* clear all dio pending events and interrupt */ + for (group = 0; group < S626_DIO_BANKS; group++) + s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff); + + return 0; +} + +static void s626_handle_dio_interrupt(struct comedi_device *dev, + u16 irqbit, u8 group) +{ + struct s626_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + + s626_dio_reset_irq(dev, group, irqbit); + + if (devpriv->ai_cmd_running) { + /* check if interrupt is an ai acquisition start trigger */ + if ((irqbit >> (cmd->start_arg - (16 * group))) == 1 && + cmd->start_src == TRIG_EXT) { + /* Start executing the RPS program */ + s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1); + + if (cmd->scan_begin_src == TRIG_EXT) + s626_dio_set_irq(dev, cmd->scan_begin_arg); + } + if ((irqbit >> (cmd->scan_begin_arg - (16 * group))) == 1 && + cmd->scan_begin_src == TRIG_EXT) { + /* Trigger ADC scan loop start */ + s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2); + + if (cmd->convert_src == TRIG_EXT) { + devpriv->ai_convert_count = cmd->chanlist_len; + + s626_dio_set_irq(dev, cmd->convert_arg); + } + + if (cmd->convert_src == TRIG_TIMER) { + devpriv->ai_convert_count = cmd->chanlist_len; + s626_set_enable(dev, 5, S626_CLKENAB_ALWAYS); + } + } + if ((irqbit >> (cmd->convert_arg - (16 * group))) == 1 && + cmd->convert_src == TRIG_EXT) { + /* Trigger ADC scan loop start */ + s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2); + + devpriv->ai_convert_count--; + if (devpriv->ai_convert_count > 0) + s626_dio_set_irq(dev, cmd->convert_arg); + } + } +} + +static void s626_check_dio_interrupts(struct comedi_device *dev) +{ + u16 irqbit; + u8 group; + + for (group = 0; group < S626_DIO_BANKS; group++) { + /* read interrupt type */ + irqbit = s626_debi_read(dev, S626_LP_RDCAPFLG(group)); + + /* check if interrupt is generated from dio channels */ + if (irqbit) { + s626_handle_dio_interrupt(dev, irqbit, group); + return; + } + } +} + +static void s626_check_counter_interrupts(struct comedi_device *dev) +{ + struct s626_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u16 irqbit; + + /* read interrupt type */ + irqbit = s626_debi_read(dev, S626_LP_RDMISC2); + + /* check interrupt on counters */ + if (irqbit & S626_IRQ_COINT1A) { + /* clear interrupt capture flag */ + s626_reset_cap_flags(dev, 0); + } + if (irqbit & S626_IRQ_COINT2A) { + /* clear interrupt capture flag */ + s626_reset_cap_flags(dev, 1); + } + if (irqbit & S626_IRQ_COINT3A) { + /* clear interrupt capture flag */ + s626_reset_cap_flags(dev, 2); + } + if (irqbit & S626_IRQ_COINT1B) { + /* clear interrupt capture flag */ + s626_reset_cap_flags(dev, 3); + } + if (irqbit & S626_IRQ_COINT2B) { + /* clear interrupt capture flag */ + s626_reset_cap_flags(dev, 4); + + if (devpriv->ai_convert_count > 0) { + devpriv->ai_convert_count--; + if (devpriv->ai_convert_count == 0) + s626_set_enable(dev, 4, S626_CLKENAB_INDEX); + + if (cmd->convert_src == TRIG_TIMER) { + /* Trigger ADC scan loop start */ + s626_mc_enable(dev, S626_MC2_ADC_RPS, + S626_P_MC2); + } + } + } + if (irqbit & S626_IRQ_COINT3B) { + /* clear interrupt capture flag */ + s626_reset_cap_flags(dev, 5); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Trigger ADC scan loop start */ + s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2); + } + + if (cmd->convert_src == TRIG_TIMER) { + devpriv->ai_convert_count = cmd->chanlist_len; + s626_set_enable(dev, 4, S626_CLKENAB_ALWAYS); + } + } +} + +static bool s626_handle_eos_interrupt(struct comedi_device *dev) +{ + struct s626_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + /* + * Init ptr to DMA buffer that holds new ADC data. We skip the + * first uint16_t in the buffer because it contains junk data + * from the final ADC of the previous poll list scan. + */ + u32 *readaddr = (u32 *)devpriv->ana_buf.logical_base + 1; + int i; + + /* get the data and hand it over to comedi */ + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned short tempdata; + + /* + * Convert ADC data to 16-bit integer values and copy + * to application buffer. + */ + tempdata = s626_ai_reg_to_uint(*readaddr); + readaddr++; + + comedi_buf_write_samples(s, &tempdata, 1); + } + + if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + + if (async->events & COMEDI_CB_CANCEL_MASK) + devpriv->ai_cmd_running = 0; + + if (devpriv->ai_cmd_running && cmd->scan_begin_src == TRIG_EXT) + s626_dio_set_irq(dev, cmd->scan_begin_arg); + + comedi_handle_events(dev, s); + + return !devpriv->ai_cmd_running; +} + +static irqreturn_t s626_irq_handler(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned long flags; + u32 irqtype, irqstatus; + + if (!dev->attached) + return IRQ_NONE; + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&dev->spinlock, flags); + + /* save interrupt enable register state */ + irqstatus = readl(dev->mmio + S626_P_IER); + + /* read interrupt type */ + irqtype = readl(dev->mmio + S626_P_ISR); + + /* disable master interrupt */ + writel(0, dev->mmio + S626_P_IER); + + /* clear interrupt */ + writel(irqtype, dev->mmio + S626_P_ISR); + + switch (irqtype) { + case S626_IRQ_RPS1: /* end_of_scan occurs */ + if (s626_handle_eos_interrupt(dev)) + irqstatus = 0; + break; + case S626_IRQ_GPIO3: /* check dio and counter interrupt */ + /* s626_dio_clear_irq(dev); */ + s626_check_dio_interrupts(dev); + s626_check_counter_interrupts(dev); + break; + } + + /* enable interrupt */ + writel(irqstatus, dev->mmio + S626_P_IER); + + spin_unlock_irqrestore(&dev->spinlock, flags); + return IRQ_HANDLED; +} + +/* + * This function builds the RPS program for hardware driven acquisition. + */ +static void s626_reset_adc(struct comedi_device *dev, u8 *ppl) +{ + struct s626_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + u32 *rps; + u32 jmp_adrs; + u16 i; + u16 n; + u32 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 = (u32 *)devpriv->rps_buf.logical_base; + + /* Initialize RPS instruction pointer */ + writel((u32)devpriv->rps_buf.physical_base, + dev->mmio + S626_P_RPSADDR1); + + /* Construct RPS program in rps_buf DMA buffer */ + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Wait for Start trigger. */ + *rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC; + *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC; + } + + /* + * SAA7146 BUG WORKAROUND Do a dummy DEBI Write. This is necessary + * because the first RPS DEBI Write following a non-RPS DEBI write + * seems to always fail. If we don't do this dummy write, the ADC + * gain might not be set to the value required for the first slot in + * the poll list; the ADC gain would instead remain unchanged from + * the previously programmed value. + */ + /* Write DEBI Write command and address to shadow RAM. */ + *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2); + *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL; + *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2); + /* Write DEBI immediate data to shadow RAM: */ + *rps++ = S626_GSEL_BIPOLAR5V; /* arbitrary immediate data value. */ + *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI; + /* Reset "shadow RAM uploaded" flag. */ + /* Invoke shadow RAM upload. */ + *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI; + /* Wait for shadow upload to finish. */ + *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI; + + /* + * Digitize all slots in the poll list. This is implemented as a + * for loop to limit the slot count to 16 in case the application + * forgot to set the S626_EOPL flag in the final slot. + */ + for (devpriv->adc_items = 0; devpriv->adc_items < 16; + devpriv->adc_items++) { + /* + * Convert application's poll list item to private board class + * format. Each app poll list item is an uint8_t with form + * (EOPL,x,x,RANGE,CHAN<3:0>), where RANGE code indicates 0 = + * +-10V, 1 = +-5V, and EOPL = End of Poll List marker. + */ + local_ppl = (*ppl << 8) | (*ppl & 0x10 ? S626_GSEL_BIPOLAR5V : + S626_GSEL_BIPOLAR10V); + + /* Switch ADC analog gain. */ + /* Write DEBI command and address to shadow RAM. */ + *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2); + *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL; + /* Write DEBI immediate data to shadow RAM. */ + *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2); + *rps++ = local_ppl; + /* Reset "shadow RAM uploaded" flag. */ + *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI; + /* Invoke shadow RAM upload. */ + *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI; + /* Wait for shadow upload to finish. */ + *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI; + /* Select ADC analog input channel. */ + *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2); + /* Write DEBI command and address to shadow RAM. */ + *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_ISEL; + *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2); + /* Write DEBI immediate data to shadow RAM. */ + *rps++ = local_ppl; + /* Reset "shadow RAM uploaded" flag. */ + *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI; + /* Invoke shadow RAM upload. */ + *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI; + /* Wait for shadow upload to finish. */ + *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI; + + /* + * Delay at least 10 microseconds for analog input settling. + * Instead of padding with NOPs, we use S626_RPS_JUMP + * instructions here; this allows us to produce a longer delay + * than is possible with NOPs because each S626_RPS_JUMP + * flushes the RPS' instruction prefetch pipeline. + */ + jmp_adrs = + (u32)devpriv->rps_buf.physical_base + + (u32)((unsigned long)rps - + (unsigned long)devpriv->rps_buf.logical_base); + for (i = 0; i < (10 * S626_RPSCLK_PER_US / 2); i++) { + jmp_adrs += 8; /* Repeat to implement time delay: */ + /* Jump to next RPS instruction. */ + *rps++ = S626_RPS_JUMP; + *rps++ = jmp_adrs; + } + + if (cmd->convert_src != TRIG_NOW) { + /* Wait for Start trigger. */ + *rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC; + *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC; + } + /* Start ADC by pulsing GPIO1. */ + /* Begin ADC Start pulse. */ + *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2); + *rps++ = S626_GPIO_BASE | S626_GPIO1_LO; + *rps++ = S626_RPS_NOP; + /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */ + /* End ADC Start pulse. */ + *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2); + *rps++ = S626_GPIO_BASE | S626_GPIO1_HI; + /* + * Wait for ADC to complete (GPIO2 is asserted high when ADC not + * busy) and for data from previous conversion to shift into FB + * BUFFER 1 register. + */ + /* Wait for ADC done. */ + *rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2; + + /* Transfer ADC data from FB BUFFER 1 register to DMA buffer. */ + *rps++ = S626_RPS_STREG | + (S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2); + *rps++ = (u32)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++ = (u32)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++ = (u32)devpriv->rps_buf.physical_base; + + /* End of RPS program build */ +} + +static int s626_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = readl(dev->mmio + S626_P_PSR); + if (status & S626_PSR_GPIO2) + return 0; + return -EBUSY; +} + +static int s626_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + u16 chan = CR_CHAN(insn->chanspec); + u16 range = CR_RANGE(insn->chanspec); + u16 adc_spec = 0; + u32 gpio_image; + u32 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. */ + usleep_range(10, 20); + + /* Start ADC by pulsing GPIO1 low */ + gpio_image = readl(dev->mmio + S626_P_GPIO); + /* Assert ADC Start command */ + writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + /* and stretch it out */ + writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + /* Negate ADC Start command */ + writel(gpio_image | S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + + /* + * Wait for ADC to complete (GPIO2 is asserted high when + * ADC not busy) and for data from previous conversion to + * shift into FB BUFFER 1 register. + */ + + /* Wait for ADC done */ + ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0); + if (ret) + return ret; + + /* Fetch ADC data */ + if (n != 0) { + tmp = readl(dev->mmio + S626_P_FB_BUFFER1); + data[n - 1] = s626_ai_reg_to_uint(tmp); + } + + /* + * Allow the ADC to stabilize for 4 microseconds before + * starting the next (final) conversion. This delay is + * necessary to allow sufficient time between last + * conversion finished and the start of the next + * conversion. Without this delay, the last conversion's + * data value is sometimes set to the previous + * conversion's data value. + */ + udelay(4); + } + + /* + * Start a dummy conversion to cause the data from the + * previous conversion to be shifted in. + */ + gpio_image = readl(dev->mmio + S626_P_GPIO); + /* Assert ADC Start command */ + writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + /* and stretch it out */ + writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + /* Negate ADC Start command */ + writel(gpio_image | S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + + /* Wait for the data to arrive in FB BUFFER 1 register. */ + + /* Wait for ADC done */ + ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0); + if (ret) + return ret; + + /* Fetch ADC data from audio interface's input shift register. */ + + /* Fetch ADC data */ + if (n != 0) { + tmp = readl(dev->mmio + S626_P_FB_BUFFER1); + data[n - 1] = s626_ai_reg_to_uint(tmp); + } + + return n; +} + +static int s626_ai_load_polllist(u8 *ppl, struct comedi_cmd *cmd) +{ + int n; + + for (n = 0; n < cmd->chanlist_len; n++) { + if (CR_RANGE(cmd->chanlist[n]) == 0) + ppl[n] = CR_CHAN(cmd->chanlist[n]) | S626_RANGE_5V; + else + ppl[n] = CR_CHAN(cmd->chanlist[n]) | S626_RANGE_10V; + } + if (n != 0) + ppl[n - 1] |= S626_EOPL; + + return n; +} + +static int s626_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + /* Start executing the RPS program */ + s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1); + + s->async->inttrig = NULL; + + return 1; +} + +/* + * This function doesn't require a particular form, this is just what + * happens to be used in some of the drivers. It should convert ns + * nanoseconds to a counter value suitable for programming the device. + * Also, it should adjust ns so that it cooresponds to the actual time + * that the device will use. + */ +static int s626_ns_to_timer(unsigned int *nanosec, unsigned int flags) +{ + int divider, base; + + base = 500; /* 2MHz internal clock */ + + switch (flags & CMDF_ROUND_MASK) { + case CMDF_ROUND_NEAREST: + default: + divider = DIV_ROUND_CLOSEST(*nanosec, base); + break; + case CMDF_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case CMDF_ROUND_UP: + divider = DIV_ROUND_UP(*nanosec, base); + break; + } + + *nanosec = base * divider; + return divider - 1; +} + +static void s626_timer_load(struct comedi_device *dev, + unsigned int chan, int tick) +{ + u16 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); + u16 value_latchsrc = S626_LATCHSRC_A_INDXA; + /* uint16_t enab = S626_CLKENAB_ALWAYS; */ + + s626_set_mode(dev, chan, setup, false); + + /* Set the preload register */ + s626_preload(dev, chan, tick); + + /* + * Software index pulse forces the preload register to load + * into the counter + */ + s626_set_load_trig(dev, chan, 0); + s626_pulse_index(dev, chan); + + /* set reload on counter overflow */ + s626_set_load_trig(dev, chan, 1); + + /* set interrupt on overflow */ + s626_set_int_src(dev, chan, S626_INTSRC_OVER); + + s626_set_latch_source(dev, chan, value_latchsrc); + /* s626_set_enable(dev, chan, (uint16_t)(enab != 0)); */ +} + +/* TO COMPLETE */ +static int s626_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct s626_private *devpriv = dev->private; + u8 ppl[16]; + struct comedi_cmd *cmd = &s->async->cmd; + int tick; + + if (devpriv->ai_cmd_running) { + dev_err(dev->class_dev, + "%s: Another ai_cmd is running\n", __func__); + return -EBUSY; + } + /* disable interrupt */ + writel(0, dev->mmio + S626_P_IER); + + /* clear interrupt request */ + writel(S626_IRQ_RPS1 | S626_IRQ_GPIO3, dev->mmio + S626_P_ISR); + + /* clear any pending interrupt */ + s626_dio_clear_irq(dev); + /* s626_enc_clear_irq(dev); */ + + /* reset ai_cmd_running flag */ + devpriv->ai_cmd_running = 0; + + s626_ai_load_polllist(ppl, cmd); + devpriv->ai_cmd_running = 1; + devpriv->ai_convert_count = 0; + + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: + break; + case TRIG_TIMER: + /* + * set a counter to generate adc trigger at scan_begin_arg + * interval + */ + tick = s626_ns_to_timer(&cmd->scan_begin_arg, cmd->flags); + + /* load timer value and enable interrupt */ + s626_timer_load(dev, 5, tick); + s626_set_enable(dev, 5, S626_CLKENAB_ALWAYS); + break; + case TRIG_EXT: + /* set the digital line and interrupt for scan trigger */ + if (cmd->start_src != TRIG_EXT) + s626_dio_set_irq(dev, cmd->scan_begin_arg); + break; + } + + switch (cmd->convert_src) { + case TRIG_NOW: + break; + case TRIG_TIMER: + /* + * set a counter to generate adc trigger at convert_arg + * interval + */ + tick = s626_ns_to_timer(&cmd->convert_arg, cmd->flags); + + /* load timer value and enable interrupt */ + s626_timer_load(dev, 4, tick); + s626_set_enable(dev, 4, S626_CLKENAB_INDEX); + break; + case TRIG_EXT: + /* set the digital line and interrupt for convert trigger */ + if (cmd->scan_begin_src != TRIG_EXT && + cmd->start_src == TRIG_EXT) + s626_dio_set_irq(dev, cmd->convert_arg); + break; + } + + s626_reset_adc(dev, ppl); + + switch (cmd->start_src) { + case TRIG_NOW: + /* Trigger ADC scan loop start */ + /* s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2); */ + + /* Start executing the RPS program */ + s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1); + s->async->inttrig = NULL; + break; + case TRIG_EXT: + /* configure DIO channel for acquisition trigger */ + s626_dio_set_irq(dev, cmd->start_arg); + s->async->inttrig = NULL; + break; + case TRIG_INT: + s->async->inttrig = s626_ai_inttrig; + break; + } + + /* enable interrupt */ + writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1, dev->mmio + S626_P_IER); + + return 0; +} + +static int s626_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_INT | TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, + TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT | TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); + err |= comedi_check_trigger_is_unique(cmd->convert_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + switch (cmd->start_src) { + case TRIG_NOW: + case TRIG_INT: + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + break; + case TRIG_EXT: + err |= comedi_check_trigger_arg_max(&cmd->start_arg, 39); + break; + } + + if (cmd->scan_begin_src == TRIG_EXT) + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 39); + if (cmd->convert_src == TRIG_EXT) + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, 39); + +#define S626_MAX_SPEED 200000 /* in nanoseconds */ +#define S626_MIN_SPEED 2000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + S626_MAX_SPEED); + err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, + S626_MIN_SPEED); + } else { + /* + * external trigger + * should be level/edge, hi/lo specification here + * should specify multiple external triggers + * err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + */ + } + if (cmd->convert_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, + S626_MAX_SPEED); + err |= comedi_check_trigger_arg_max(&cmd->convert_arg, + S626_MIN_SPEED); + } else { + /* + * external trigger - see above + * err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 9); + */ + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->scan_begin_arg; + s626_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + s626_ns_to_timer(&arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + + if (cmd->scan_begin_src == TRIG_TIMER) { + arg = cmd->convert_arg * cmd->scan_end_arg; + err |= comedi_check_trigger_arg_min( + &cmd->scan_begin_arg, arg); + } + } + + if (err) + return 4; + + return 0; +} + +static int s626_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct s626_private *devpriv = dev->private; + + /* Stop RPS program in case it is currently running */ + s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1); + + /* disable master interrupt */ + writel(0, dev->mmio + S626_P_IER); + + devpriv->ai_cmd_running = 0; + + return 0; +} + +static int s626_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + s16 dacdata = (s16)data[i]; + int ret; + + dacdata -= (0x1fff); + + ret = s626_set_dac(dev, chan, dacdata); + if (ret) + return ret; + + s->readback[chan] = data[i]; + } + + return insn->n; +} + +/* *************** DIGITAL I/O FUNCTIONS *************** */ + +/* + * All DIO functions address a group of DIO channels by means of + * "group" argument. group may be 0, 1 or 2, which correspond to DIO + * ports A, B and C, respectively. + */ + +static void s626_dio_init(struct comedi_device *dev) +{ + u16 group; + + /* Prepare to treat writes to WRCapSel as capture disables. */ + s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP); + + /* For each group of sixteen channels ... */ + for (group = 0; group < S626_DIO_BANKS; group++) { + /* Disable all interrupts */ + s626_debi_write(dev, S626_LP_WRINTSEL(group), 0); + /* Disable all event captures */ + s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff); + /* Init all DIOs to default edge polarity */ + s626_debi_write(dev, S626_LP_WREDGSEL(group), 0); + /* Program all outputs to inactive state */ + s626_debi_write(dev, S626_LP_WRDOUT(group), 0); + } +} + +static int s626_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long group = (unsigned long)s->private; + + if (comedi_dio_update_state(s, data)) + s626_debi_write(dev, S626_LP_WRDOUT(group), s->state); + + data[1] = s626_debi_read(dev, S626_LP_RDDIN(group)); + + return insn->n; +} + +static int s626_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned long group = (unsigned long)s->private; + int ret; + + ret = comedi_dio_insn_config(dev, s, insn, data, 0); + if (ret) + return ret; + + s626_debi_write(dev, S626_LP_WRDOUT(group), s->io_bits); + + return insn->n; +} + +/* + * Now this function initializes the value of the counter (data[0]) + * and set the subdevice. To complete with trigger and interrupt + * configuration. + * + * FIXME: data[0] is supposed to be an INSN_CONFIG_xxx constant indicating + * what is being configured, but this function appears to be using data[0] + * as a variable. + */ +static int s626_enc_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + u16 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 */ + u16 value_latchsrc = S626_LATCHSRC_AB_READ; + u16 enab = S626_CLKENAB_ALWAYS; + + /* (data==NULL) ? (Preloadvalue=0) : (Preloadvalue=data[0]); */ + + s626_set_mode(dev, chan, setup, true); + s626_preload(dev, chan, data[0]); + s626_pulse_index(dev, chan); + s626_set_latch_source(dev, chan, value_latchsrc); + s626_set_enable(dev, chan, (enab != 0)); + + return insn->n; +} + +static int s626_enc_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + u16 cntr_latch_reg = S626_LP_CNTR(chan); + int i; + + for (i = 0; i < insn->n; i++) { + unsigned int val; + + /* + * Read the counter's output latch LSW/MSW. + * Latches on LSW read. + */ + val = s626_debi_read(dev, cntr_latch_reg); + val |= (s626_debi_read(dev, cntr_latch_reg + 2) << 16); + data[i] = val; + } + + return insn->n; +} + +static int s626_enc_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* Set the preload register */ + s626_preload(dev, chan, data[0]); + + /* + * Software index pulse forces the preload register to load + * into the counter + */ + s626_set_load_trig(dev, chan, 0); + s626_pulse_index(dev, chan); + s626_set_load_trig(dev, chan, 2); + + return 1; +} + +static void s626_write_misc2(struct comedi_device *dev, u16 new_image) +{ + s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_WENABLE); + s626_debi_write(dev, S626_LP_WRMISC2, new_image); + s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_WDISABLE); +} + +static void s626_counters_init(struct comedi_device *dev) +{ + int chan; + u16 setup = + /* Preload upon index. */ + S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) | + /* Disable hardware index. */ + S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) | + /* Operating mode is counter. */ + S626_SET_STD_ENCMODE(S626_ENCMODE_COUNTER) | + /* Active high clock. */ + S626_SET_STD_CLKPOL(S626_CLKPOL_POS) | + /* Clock multiplier is 1x. */ + S626_SET_STD_CLKMULT(S626_CLKMULT_1X) | + /* Enabled by index */ + S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX); + + /* + * Disable all counter interrupts and clear any captured counter events. + */ + for (chan = 0; chan < S626_ENCODER_CHANNELS; chan++) { + s626_set_mode(dev, chan, setup, true); + s626_set_int_src(dev, chan, 0); + s626_reset_cap_flags(dev, chan); + s626_set_enable(dev, chan, S626_CLKENAB_ALWAYS); + } +} + +static int s626_allocate_dma_buffers(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct s626_private *devpriv = dev->private; + void *addr; + dma_addr_t appdma; + + addr = dma_alloc_coherent(&pcidev->dev, S626_DMABUF_SIZE, &appdma, + GFP_KERNEL); + if (!addr) + return -ENOMEM; + devpriv->ana_buf.logical_base = addr; + devpriv->ana_buf.physical_base = appdma; + + addr = dma_alloc_coherent(&pcidev->dev, S626_DMABUF_SIZE, &appdma, + GFP_KERNEL); + if (!addr) + return -ENOMEM; + devpriv->rps_buf.logical_base = addr; + devpriv->rps_buf.physical_base = appdma; + + return 0; +} + +static void s626_free_dma_buffers(struct comedi_device *dev) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct s626_private *devpriv = dev->private; + + if (!devpriv) + return; + + if (devpriv->rps_buf.logical_base) + dma_free_coherent(&pcidev->dev, S626_DMABUF_SIZE, + devpriv->rps_buf.logical_base, + devpriv->rps_buf.physical_base); + if (devpriv->ana_buf.logical_base) + dma_free_coherent(&pcidev->dev, S626_DMABUF_SIZE, + devpriv->ana_buf.logical_base, + devpriv->ana_buf.physical_base); +} + +static int s626_initialize(struct comedi_device *dev) +{ + struct s626_private *devpriv = dev->private; + dma_addr_t phys_buf; + u16 chan; + int i; + int ret; + + /* Enable DEBI and audio pins, enable I2C interface */ + s626_mc_enable(dev, S626_MC1_DEBI | S626_MC1_AUDIO | S626_MC1_I2C, + S626_P_MC1); + + /* + * Configure DEBI operating mode + * + * Local bus is 16 bits wide + * Declare DEBI transfer timeout interval + * Set up byte lane steering + * Intel-compatible local bus (DEBI never times out) + */ + writel(S626_DEBI_CFG_SLAVE16 | + (S626_DEBI_TOUT << S626_DEBI_CFG_TOUT_BIT) | S626_DEBI_SWAP | + S626_DEBI_CFG_INTEL, dev->mmio + S626_P_DEBICFG); + + /* Disable MMU paging */ + writel(S626_DEBI_PAGE_DISABLE, dev->mmio + S626_P_DEBIPAGE); + + /* Init GPIO so that ADC Start* is negated */ + writel(S626_GPIO_BASE | S626_GPIO1_HI, dev->mmio + S626_P_GPIO); + + /* I2C device address for onboard eeprom (revb) */ + devpriv->i2c_adrs = 0xA0; + + /* + * Issue an I2C ABORT command to halt any I2C + * operation in progress and reset BUSY flag. + */ + writel(S626_I2C_CLKSEL | S626_I2C_ABORT, + dev->mmio + S626_P_I2CSTAT); + s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2); + ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0); + if (ret) + return ret; + + /* + * Per SAA7146 data sheet, write to STATUS + * reg twice to reset all I2C error flags. + */ + for (i = 0; i < 2; i++) { + writel(S626_I2C_CLKSEL, dev->mmio + S626_P_I2CSTAT); + s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2); + ret = comedi_timeout(dev, NULL, + NULL, s626_i2c_handshake_eoc, 0); + if (ret) + return ret; + } + + /* + * Init audio interface functional attributes: set DAC/ADC + * serial clock rates, invert DAC serial clock so that + * DAC data setup times are satisfied, enable DAC serial + * clock out. + */ + writel(S626_ACON2_INIT, dev->mmio + S626_P_ACON2); + + /* + * Set up TSL1 slot list, which is used to control the + * accumulation of ADC data: S626_RSD1 = shift data in on SD1. + * S626_SIB_A1 = store data uint8_t at next available location + * in FB BUFFER1 register. + */ + writel(S626_RSD1 | S626_SIB_A1, dev->mmio + S626_P_TSL1); + writel(S626_RSD1 | S626_SIB_A1 | S626_EOS, + dev->mmio + S626_P_TSL1 + 4); + + /* Enable TSL1 slot list so that it executes all the time */ + writel(S626_ACON1_ADCSTART, dev->mmio + S626_P_ACON1); + + /* + * Initialize RPS registers used for ADC + */ + + /* Physical start of RPS program */ + writel((u32)devpriv->rps_buf.physical_base, + dev->mmio + S626_P_RPSADDR1); + /* RPS program performs no explicit mem writes */ + writel(0, dev->mmio + S626_P_RPSPAGE1); + /* Disable RPS timeouts */ + writel(0, dev->mmio + S626_P_RPS1_TOUT); + +#if 0 + /* + * SAA7146 BUG WORKAROUND + * + * Initialize SAA7146 ADC interface to a known state by + * invoking ADCs until FB BUFFER 1 register shows that it + * is correctly receiving ADC data. This is necessary + * because the SAA7146 ADC interface does not start up in + * a defined state after a PCI reset. + */ + { + struct comedi_subdevice *s = dev->read_subdev; + u8 poll_list; + u16 adc_data; + u16 start_val; + u16 index; + unsigned int data[16]; + + /* Create a simple polling list for analog input channel 0 */ + poll_list = S626_EOPL; + s626_reset_adc(dev, &poll_list); + + /* Get initial ADC value */ + s626_ai_rinsn(dev, s, NULL, data); + start_val = data[0]; + + /* + * VERSION 2.01 CHANGE: TIMEOUT ADDED TO PREVENT HANGED + * EXECUTION. + * + * Invoke ADCs until the new ADC value differs from the initial + * value or a timeout occurs. The timeout protects against the + * possibility that the driver is restarting and the ADC data is + * a fixed value resulting from the applied ADC analog input + * being unusually quiet or at the rail. + */ + for (index = 0; index < 500; index++) { + s626_ai_rinsn(dev, s, NULL, data); + adc_data = data[0]; + if (adc_data != start_val) + break; + } + } +#endif /* SAA7146 BUG WORKAROUND */ + + /* + * Initialize the DAC interface + */ + + /* + * Init Audio2's output DMAC attributes: + * burst length = 1 DWORD + * threshold = 1 DWORD. + */ + writel(0, dev->mmio + S626_P_PCI_BT_A); + + /* + * Init Audio2's output DMA physical addresses. The protection + * address is set to 1 DWORD past the base address so that a + * single DWORD will be transferred each time a DMA transfer is + * enabled. + */ + phys_buf = devpriv->ana_buf.physical_base + + (S626_DAC_WDMABUF_OS * sizeof(u32)); + writel((u32)phys_buf, dev->mmio + S626_P_BASEA2_OUT); + writel((u32)(phys_buf + sizeof(u32)), + dev->mmio + S626_P_PROTA2_OUT); + + /* + * Cache Audio2's output DMA buffer logical address. This is + * where DAC data is buffered for A2 output DMA transfers. + */ + devpriv->dac_wbuf = (u32 *)devpriv->ana_buf.logical_base + + S626_DAC_WDMABUF_OS; + + /* + * Audio2's output channels does not use paging. The + * protection violation handling bit is set so that the + * DMAC will automatically halt and its PCI address pointer + * will be reset when the protection address is reached. + */ + writel(8, dev->mmio + S626_P_PAGEA2_OUT); + + /* + * Initialize time slot list 2 (TSL2), which is used to control + * the clock generation for and serialization of data to be sent + * to the DAC devices. Slot 0 is a NOP that is used to trap TSL + * execution; this permits other slots to be safely modified + * without first turning off the TSL sequencer (which is + * apparently impossible to do). Also, SD3 (which is driven by a + * pull-up resistor) is shifted in and stored to the MSB of + * FB_BUFFER2 to be used as evidence that the slot sequence has + * not yet finished executing. + */ + + /* Slot 0: Trap TSL execution, shift 0xFF into FB_BUFFER2 */ + writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2 | S626_EOS, + dev->mmio + S626_VECTPORT(0)); + + /* + * Initialize slot 1, which is constant. Slot 1 causes a + * DWORD to be transferred from audio channel 2's output FIFO + * to the FIFO's output buffer so that it can be serialized + * and sent to the DAC during subsequent slots. All remaining + * slots are dynamically populated as required by the target + * DAC device. + */ + + /* Slot 1: Fetch DWORD from Audio2's output FIFO */ + writel(S626_LF_A2, dev->mmio + S626_VECTPORT(1)); + + /* Start DAC's audio interface (TSL2) running */ + writel(S626_ACON1_DACSTART, dev->mmio + S626_P_ACON1); + + /* + * Init Trim DACs to calibrated values. Do it twice because the + * SAA7146 audio channel does not always reset properly and + * sometimes causes the first few TrimDAC writes to malfunction. + */ + s626_load_trim_dacs(dev); + ret = s626_load_trim_dacs(dev); + if (ret) + return ret; + + /* + * Manually init all gate array hardware in case this is a soft + * reset (we have no way of determining whether this is a warm + * or cold start). This is necessary because the gate array will + * reset only in response to a PCI hard reset; there is no soft + * reset function. + */ + + /* + * Init all DAC outputs to 0V and init all DAC setpoint and + * polarity images. + */ + for (chan = 0; chan < S626_DAC_CHANNELS; chan++) { + ret = s626_set_dac(dev, chan, 0); + if (ret) + return ret; + } + + /* Init counters */ + s626_counters_init(dev); + + /* + * Without modifying the state of the Battery Backup enab, disable + * the watchdog timer, set DIO channels 0-5 to operate in the + * standard DIO (vs. counter overflow) mode, disable the battery + * charger, and reset the watchdog interval selector to zero. + */ + s626_write_misc2(dev, (s626_debi_read(dev, S626_LP_RDMISC2) & + S626_MISC2_BATT_ENABLE)); + + /* Initialize the digital I/O subsystem */ + s626_dio_init(dev); + + return 0; +} + +static int s626_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct s626_private *devpriv; + struct comedi_subdevice *s; + int ret; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->mmio = pci_ioremap_bar(pcidev, 0); + if (!dev->mmio) + return -ENOMEM; + + /* disable master interrupt */ + writel(0, dev->mmio + S626_P_IER); + + /* soft reset */ + writel(S626_MC1_SOFT_RESET, dev->mmio + S626_P_MC1); + + /* DMA FIXME DMA// */ + + ret = s626_allocate_dma_buffers(dev); + if (ret) + return ret; + + if (pcidev->irq) { + ret = request_irq(pcidev->irq, s626_irq_handler, IRQF_SHARED, + dev->board_name, dev); + + if (ret == 0) + dev->irq = pcidev->irq; + } + + ret = comedi_alloc_subdevices(dev, 6); + if (ret) + return ret; + + s = &dev->subdevices[0]; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF; + s->n_chan = S626_ADC_CHANNELS; + s->maxdata = 0x3fff; + s->range_table = &s626_range_table; + s->len_chanlist = S626_ADC_CHANNELS; + s->insn_read = s626_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmd = s626_ai_cmd; + s->do_cmdtest = s626_ai_cmdtest; + s->cancel = s626_ai_cancel; + } + + s = &dev->subdevices[1]; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = S626_DAC_CHANNELS; + s->maxdata = 0x3fff; + s->range_table = &range_bipolar10; + s->insn_write = s626_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + s = &dev->subdevices[2]; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = (void *)0; /* DIO group 0 */ + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = &dev->subdevices[3]; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = (void *)1; /* DIO group 1 */ + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = &dev->subdevices[4]; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = (void *)2; /* DIO group 2 */ + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = &dev->subdevices[5]; + /* encoder (counter) subdevice */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL; + s->n_chan = S626_ENCODER_CHANNELS; + s->maxdata = 0xffffff; + s->range_table = &range_unknown; + s->insn_config = s626_enc_insn_config; + s->insn_read = s626_enc_insn_read; + s->insn_write = s626_enc_insn_write; + + return s626_initialize(dev); +} + +static void s626_detach(struct comedi_device *dev) +{ + struct s626_private *devpriv = dev->private; + + if (devpriv) { + /* stop ai_command */ + devpriv->ai_cmd_running = 0; + + if (dev->mmio) { + /* interrupt mask */ + /* Disable master interrupt */ + writel(0, dev->mmio + S626_P_IER); + /* Clear board's IRQ status flag */ + writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1, + dev->mmio + S626_P_ISR); + + /* Disable the watchdog timer and battery charger. */ + s626_write_misc2(dev, 0); + + /* Close all interfaces on 7146 device */ + writel(S626_MC1_SHUTDOWN, dev->mmio + S626_P_MC1); + writel(S626_ACON1_BASE, dev->mmio + S626_P_ACON1); + } + } + comedi_pci_detach(dev); + s626_free_dma_buffers(dev); +} + +static struct comedi_driver s626_driver = { + .driver_name = "s626", + .module = THIS_MODULE, + .auto_attach = s626_auto_attach, + .detach = s626_detach, +}; + +static int s626_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &s626_driver, id->driver_data); +} + +/* + * For devices with vendor:device id == 0x1131:0x7146 you must specify + * also subvendor:subdevice ids, because otherwise it will conflict with + * Philips SAA7146 media/dvb based cards. + */ +static const struct pci_device_id s626_pci_table[] = { + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, + 0x6000, 0x0272) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, s626_pci_table); + +static struct pci_driver s626_pci_driver = { + .name = "s626", + .id_table = s626_pci_table, + .probe = s626_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(s626_driver, s626_pci_driver); + +MODULE_AUTHOR("Gianluca Palli <gpalli@deis.unibo.it>"); +MODULE_DESCRIPTION("Sensoray 626 Comedi driver module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/s626.h b/drivers/comedi/drivers/s626.h new file mode 100644 index 000000000..749252b1d --- /dev/null +++ b/drivers/comedi/drivers/s626.h @@ -0,0 +1,869 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#ifndef S626_H_INCLUDED +#define S626_H_INCLUDED + +#define S626_DMABUF_SIZE 4096 /* 4k pages */ + +#define S626_ADC_CHANNELS 16 +#define S626_DAC_CHANNELS 4 +#define S626_ENCODER_CHANNELS 6 +#define S626_DIO_CHANNELS 48 +#define S626_DIO_BANKS 3 /* Number of DIO groups. */ +#define S626_DIO_EXTCHANS 40 /* + * Number of extended-capability + * DIO channels. + */ + +#define S626_NUM_TRIMDACS 12 /* Number of valid TrimDAC channels. */ + +/* PCI bus interface types. */ +#define S626_INTEL 1 /* Intel bus type. */ +#define S626_MOTOROLA 2 /* Motorola bus type. */ + +#define S626_PLATFORM S626_INTEL /* *** SELECT PLATFORM TYPE *** */ + +#define S626_RANGE_5V 0x10 /* +/-5V range */ +#define S626_RANGE_10V 0x00 /* +/-10V range */ + +#define S626_EOPL 0x80 /* End of ADC poll list marker. */ +#define S626_GSEL_BIPOLAR5V 0x00F0 /* S626_LP_GSEL setting 5V bipolar. */ +#define S626_GSEL_BIPOLAR10V 0x00A0 /* S626_LP_GSEL setting 10V bipolar. */ + +/* Error codes that must be visible to this base class. */ +#define S626_ERR_ILLEGAL_PARM 0x00010000 /* + * Illegal function parameter + * value was specified. + */ +#define S626_ERR_I2C 0x00020000 /* I2C error. */ +#define S626_ERR_COUNTERSETUP 0x00200000 /* + * Illegal setup specified for + * counter channel. + */ +#define S626_ERR_DEBI_TIMEOUT 0x00400000 /* DEBI transfer timed out. */ + +/* + * Organization (physical order) and size (in DWORDs) of logical DMA buffers + * contained by ANA_DMABUF. + */ +#define S626_ADC_DMABUF_DWORDS 40 /* + * ADC DMA buffer must hold 16 samples, + * plus pre/post garbage samples. + */ +#define S626_DAC_WDMABUF_DWORDS 1 /* + * DAC output DMA buffer holds a single + * sample. + */ + +/* All remaining space in 4KB DMA buffer is available for the RPS1 program. */ + +/* Address offsets, in DWORDS, from base of DMA buffer. */ +#define S626_DAC_WDMABUF_OS S626_ADC_DMABUF_DWORDS + +/* Interrupt enable bit in ISR and IER. */ +#define S626_IRQ_GPIO3 0x00000040 /* IRQ enable for GPIO3. */ +#define S626_IRQ_RPS1 0x10000000 +#define S626_ISR_AFOU 0x00000800 +/* Audio fifo under/overflow detected. */ + +#define S626_IRQ_COINT1A 0x0400 /* counter 1A overflow interrupt mask */ +#define S626_IRQ_COINT1B 0x0800 /* counter 1B overflow interrupt mask */ +#define S626_IRQ_COINT2A 0x1000 /* counter 2A overflow interrupt mask */ +#define S626_IRQ_COINT2B 0x2000 /* counter 2B overflow interrupt mask */ +#define S626_IRQ_COINT3A 0x4000 /* counter 3A overflow interrupt mask */ +#define S626_IRQ_COINT3B 0x8000 /* counter 3B overflow interrupt mask */ + +/* RPS command codes. */ +#define S626_RPS_CLRSIGNAL 0x00000000 /* CLEAR SIGNAL */ +#define S626_RPS_SETSIGNAL 0x10000000 /* SET SIGNAL */ +#define S626_RPS_NOP 0x00000000 /* NOP */ +#define S626_RPS_PAUSE 0x20000000 /* PAUSE */ +#define S626_RPS_UPLOAD 0x40000000 /* UPLOAD */ +#define S626_RPS_JUMP 0x80000000 /* JUMP */ +#define S626_RPS_LDREG 0x90000100 /* LDREG (1 uint32_t only) */ +#define S626_RPS_STREG 0xA0000100 /* STREG (1 uint32_t only) */ +#define S626_RPS_STOP 0x50000000 /* STOP */ +#define S626_RPS_IRQ 0x60000000 /* IRQ */ + +#define S626_RPS_LOGICAL_OR 0x08000000 /* Logical OR conditionals. */ +#define S626_RPS_INVERT 0x04000000 /* + * Test for negated + * semaphores. + */ +#define S626_RPS_DEBI 0x00000002 /* DEBI done */ + +#define S626_RPS_SIG0 0x00200000 /* + * RPS semaphore 0 + * (used by ADC). + */ +#define S626_RPS_SIG1 0x00400000 /* + * RPS semaphore 1 + * (used by DAC). + */ +#define S626_RPS_SIG2 0x00800000 /* + * RPS semaphore 2 + * (not used). + */ +#define S626_RPS_GPIO2 0x00080000 /* RPS GPIO2 */ +#define S626_RPS_GPIO3 0x00100000 /* RPS GPIO3 */ + +#define S626_RPS_SIGADC S626_RPS_SIG0 /* + * Trigger/status for + * ADC's RPS program. + */ +#define S626_RPS_SIGDAC S626_RPS_SIG1 /* + * Trigger/status for + * DAC's RPS program. + */ + +/* RPS clock parameters. */ +#define S626_RPSCLK_SCALAR 8 /* + * This is apparent ratio of + * PCI/RPS clks (undocumented!!). + */ +#define S626_RPSCLK_PER_US (33 / S626_RPSCLK_SCALAR) + /* + * Number of RPS clocks in one + * microsecond. + */ + +/* Event counter source addresses. */ +#define S626_SBA_RPS_A0 0x27 /* Time of RPS0 busy, in PCI clocks. */ + +/* GPIO constants. */ +#define S626_GPIO_BASE 0x10004000 /* + * GPIO 0,2,3 = inputs, + * GPIO3 = IRQ; GPIO1 = out. + */ +#define S626_GPIO1_LO 0x00000000 /* GPIO1 set to LOW. */ +#define S626_GPIO1_HI 0x00001000 /* GPIO1 set to HIGH. */ + +/* Primary Status Register (PSR) constants. */ +#define S626_PSR_DEBI_E 0x00040000 /* DEBI event flag. */ +#define S626_PSR_DEBI_S 0x00080000 /* DEBI status flag. */ +#define S626_PSR_A2_IN 0x00008000 /* + * Audio output DMA2 protection + * address reached. + */ +#define S626_PSR_AFOU 0x00000800 /* + * Audio FIFO under/overflow + * detected. + */ +#define S626_PSR_GPIO2 0x00000020 /* + * GPIO2 input pin: 0=AdcBusy, + * 1=AdcIdle. + */ +#define S626_PSR_EC0S 0x00000001 /* + * Event counter 0 threshold + * reached. + */ + +/* Secondary Status Register (SSR) constants. */ +#define S626_SSR_AF2_OUT 0x00000200 /* + * Audio 2 output FIFO + * under/overflow detected. + */ + +/* Master Control Register 1 (MC1) constants. */ +#define S626_MC1_SOFT_RESET 0x80000000 /* Invoke 7146 soft reset. */ +#define S626_MC1_SHUTDOWN 0x3FFF0000 /* + * Shut down all MC1-controlled + * enables. + */ + +#define S626_MC1_ERPS1 0x2000 /* Enab/disable RPS task 1. */ +#define S626_MC1_ERPS0 0x1000 /* Enab/disable RPS task 0. */ +#define S626_MC1_DEBI 0x0800 /* Enab/disable DEBI pins. */ +#define S626_MC1_AUDIO 0x0200 /* Enab/disable audio port pins. */ +#define S626_MC1_I2C 0x0100 /* Enab/disable I2C interface. */ +#define S626_MC1_A2OUT 0x0008 /* Enab/disable transfer on A2 out. */ +#define S626_MC1_A2IN 0x0004 /* Enab/disable transfer on A2 in. */ +#define S626_MC1_A1IN 0x0001 /* Enab/disable transfer on A1 in. */ + +/* Master Control Register 2 (MC2) constants. */ +#define S626_MC2_UPLD_DEBI 0x0002 /* Upload DEBI. */ +#define S626_MC2_UPLD_IIC 0x0001 /* Upload I2C. */ +#define S626_MC2_RPSSIG2 0x2000 /* RPS signal 2 (not used). */ +#define S626_MC2_RPSSIG1 0x1000 /* RPS signal 1 (DAC RPS busy). */ +#define S626_MC2_RPSSIG0 0x0800 /* RPS signal 0 (ADC RPS busy). */ + +#define S626_MC2_ADC_RPS S626_MC2_RPSSIG0 /* ADC RPS busy. */ +#define S626_MC2_DAC_RPS S626_MC2_RPSSIG1 /* DAC RPS busy. */ + +/* PCI BUS (SAA7146) REGISTER ADDRESS OFFSETS */ +#define S626_P_PCI_BT_A 0x004C /* Audio DMA burst/threshold control. */ +#define S626_P_DEBICFG 0x007C /* DEBI configuration. */ +#define S626_P_DEBICMD 0x0080 /* DEBI command. */ +#define S626_P_DEBIPAGE 0x0084 /* DEBI page. */ +#define S626_P_DEBIAD 0x0088 /* DEBI target address. */ +#define S626_P_I2CCTRL 0x008C /* I2C control. */ +#define S626_P_I2CSTAT 0x0090 /* I2C status. */ +#define S626_P_BASEA2_IN 0x00AC /* + * Audio input 2 base physical DMAbuf + * address. + */ +#define S626_P_PROTA2_IN 0x00B0 /* + * Audio input 2 physical DMAbuf + * protection address. + */ +#define S626_P_PAGEA2_IN 0x00B4 /* Audio input 2 paging attributes. */ +#define S626_P_BASEA2_OUT 0x00B8 /* + * Audio output 2 base physical DMAbuf + * address. + */ +#define S626_P_PROTA2_OUT 0x00BC /* + * Audio output 2 physical DMAbuf + * protection address. + */ +#define S626_P_PAGEA2_OUT 0x00C0 /* Audio output 2 paging attributes. */ +#define S626_P_RPSPAGE0 0x00C4 /* RPS0 page. */ +#define S626_P_RPSPAGE1 0x00C8 /* RPS1 page. */ +#define S626_P_RPS0_TOUT 0x00D4 /* RPS0 time-out. */ +#define S626_P_RPS1_TOUT 0x00D8 /* RPS1 time-out. */ +#define S626_P_IER 0x00DC /* Interrupt enable. */ +#define S626_P_GPIO 0x00E0 /* General-purpose I/O. */ +#define S626_P_EC1SSR 0x00E4 /* Event counter set 1 source select. */ +#define S626_P_ECT1R 0x00EC /* Event counter threshold set 1. */ +#define S626_P_ACON1 0x00F4 /* Audio control 1. */ +#define S626_P_ACON2 0x00F8 /* Audio control 2. */ +#define S626_P_MC1 0x00FC /* Master control 1. */ +#define S626_P_MC2 0x0100 /* Master control 2. */ +#define S626_P_RPSADDR0 0x0104 /* RPS0 instruction pointer. */ +#define S626_P_RPSADDR1 0x0108 /* RPS1 instruction pointer. */ +#define S626_P_ISR 0x010C /* Interrupt status. */ +#define S626_P_PSR 0x0110 /* Primary status. */ +#define S626_P_SSR 0x0114 /* Secondary status. */ +#define S626_P_EC1R 0x0118 /* Event counter set 1. */ +#define S626_P_ADP4 0x0138 /* + * Logical audio DMA pointer of audio + * input FIFO A2_IN. + */ +#define S626_P_FB_BUFFER1 0x0144 /* Audio feedback buffer 1. */ +#define S626_P_FB_BUFFER2 0x0148 /* Audio feedback buffer 2. */ +#define S626_P_TSL1 0x0180 /* Audio time slot list 1. */ +#define S626_P_TSL2 0x01C0 /* Audio time slot list 2. */ + +/* LOCAL BUS (GATE ARRAY) REGISTER ADDRESS OFFSETS */ +/* Analog I/O registers: */ +#define S626_LP_DACPOL 0x0082 /* Write DAC polarity. */ +#define S626_LP_GSEL 0x0084 /* Write ADC gain. */ +#define S626_LP_ISEL 0x0086 /* Write ADC channel select. */ + +/* Digital I/O registers */ +#define S626_LP_RDDIN(x) (0x0040 + (x) * 0x10) /* R: digital input */ +#define S626_LP_WRINTSEL(x) (0x0042 + (x) * 0x10) /* W: int enable */ +#define S626_LP_WREDGSEL(x) (0x0044 + (x) * 0x10) /* W: edge selection */ +#define S626_LP_WRCAPSEL(x) (0x0046 + (x) * 0x10) /* W: capture enable */ +#define S626_LP_RDCAPFLG(x) (0x0048 + (x) * 0x10) /* R: edges captured */ +#define S626_LP_WRDOUT(x) (0x0048 + (x) * 0x10) /* W: digital output */ +#define S626_LP_RDINTSEL(x) (0x004a + (x) * 0x10) /* R: int enable */ +#define S626_LP_RDEDGSEL(x) (0x004c + (x) * 0x10) /* R: edge selection */ +#define S626_LP_RDCAPSEL(x) (0x004e + (x) * 0x10) /* R: capture enable */ + +/* Counter registers (read/write): 0A 1A 2A 0B 1B 2B */ +#define S626_LP_CRA(x) (0x0000 + (((x) % 3) * 0x4)) +#define S626_LP_CRB(x) (0x0002 + (((x) % 3) * 0x4)) + +/* Counter PreLoad (write) and Latch (read) Registers: 0A 1A 2A 0B 1B 2B */ +#define S626_LP_CNTR(x) (0x000c + (((x) < 3) ? 0x0 : 0x4) + \ + (((x) % 3) * 0x8)) + +/* Miscellaneous Registers (read/write): */ +#define S626_LP_MISC1 0x0088 /* Read/write Misc1. */ +#define S626_LP_WRMISC2 0x0090 /* Write Misc2. */ +#define S626_LP_RDMISC2 0x0082 /* Read Misc2. */ + +/* Bit masks for MISC1 register that are the same for reads and writes. */ +#define S626_MISC1_WENABLE 0x8000 /* + * enab writes to MISC2 (except Clear + * Watchdog bit). + */ +#define S626_MISC1_WDISABLE 0x0000 /* Disable writes to MISC2. */ +#define S626_MISC1_EDCAP 0x1000 /* + * Enable edge capture on DIO chans + * specified by S626_LP_WRCAPSELx. + */ +#define S626_MISC1_NOEDCAP 0x0000 /* + * Disable edge capture on specified + * DIO chans. + */ + +/* Bit masks for MISC1 register reads. */ +#define S626_RDMISC1_WDTIMEOUT 0x4000 /* Watchdog timer timed out. */ + +/* Bit masks for MISC2 register writes. */ +#define S626_WRMISC2_WDCLEAR 0x8000 /* Reset watchdog timer to zero. */ +#define S626_WRMISC2_CHARGE_ENABLE 0x4000 /* Enable battery trickle charging. */ + +/* Bit masks for MISC2 register that are the same for reads and writes. */ +#define S626_MISC2_BATT_ENABLE 0x0008 /* Backup battery enable. */ +#define S626_MISC2_WDENABLE 0x0004 /* Watchdog timer enable. */ +#define S626_MISC2_WDPERIOD_MASK 0x0003 /* Watchdog interval select mask. */ + +/* Bit masks for ACON1 register. */ +#define S626_A2_RUN 0x40000000 /* Run A2 based on TSL2. */ +#define S626_A1_RUN 0x20000000 /* Run A1 based on TSL1. */ +#define S626_A1_SWAP 0x00200000 /* Use big-endian for A1. */ +#define S626_A2_SWAP 0x00100000 /* Use big-endian for A2. */ +#define S626_WS_MODES 0x00019999 /* + * WS0 = TSL1 trigger input, + * WS1-WS4 = CS* outputs. + */ + +#if (S626_PLATFORM == S626_INTEL) /* + * Base ACON1 config: always run + * A1 based on TSL1. + */ +#define S626_ACON1_BASE (S626_WS_MODES | S626_A1_RUN) +#elif S626_PLATFORM == S626_MOTOROLA +#define S626_ACON1_BASE \ + (S626_WS_MODES | S626_A1_RUN | S626_A1_SWAP | S626_A2_SWAP) +#endif + +#define S626_ACON1_ADCSTART S626_ACON1_BASE /* + * Start ADC: run A1 + * based on TSL1. + */ +#define S626_ACON1_DACSTART (S626_ACON1_BASE | S626_A2_RUN) +/* Start transmit to DAC: run A2 based on TSL2. */ +#define S626_ACON1_DACSTOP S626_ACON1_BASE /* Halt A2. */ + +/* Bit masks for ACON2 register. */ +#define S626_A1_CLKSRC_BCLK1 0x00000000 /* A1 bit rate = BCLK1 (ADC). */ +#define S626_A2_CLKSRC_X1 0x00800000 /* + * A2 bit rate = ACLK/1 + * (DACs). + */ +#define S626_A2_CLKSRC_X2 0x00C00000 /* + * A2 bit rate = ACLK/2 + * (DACs). + */ +#define S626_A2_CLKSRC_X4 0x01400000 /* + * A2 bit rate = ACLK/4 + * (DACs). + */ +#define S626_INVERT_BCLK2 0x00100000 /* Invert BCLK2 (DACs). */ +#define S626_BCLK2_OE 0x00040000 /* Enable BCLK2 (DACs). */ +#define S626_ACON2_XORMASK 0x000C0000 /* + * XOR mask for ACON2 + * active-low bits. + */ + +#define S626_ACON2_INIT (S626_ACON2_XORMASK ^ \ + (S626_A1_CLKSRC_BCLK1 | S626_A2_CLKSRC_X2 | \ + S626_INVERT_BCLK2 | S626_BCLK2_OE)) + +/* Bit masks for timeslot records. */ +#define S626_WS1 0x40000000 /* WS output to assert. */ +#define S626_WS2 0x20000000 +#define S626_WS3 0x10000000 +#define S626_WS4 0x08000000 +#define S626_RSD1 0x01000000 /* Shift A1 data in on SD1. */ +#define S626_SDW_A1 0x00800000 /* + * Store rcv'd char at next char + * slot of DWORD1 buffer. + */ +#define S626_SIB_A1 0x00400000 /* + * Store rcv'd char at next + * char slot of FB1 buffer. + */ +#define S626_SF_A1 0x00200000 /* + * Write unsigned long + * buffer to input FIFO. + */ + +/* Select parallel-to-serial converter's data source: */ +#define S626_XFIFO_0 0x00000000 /* Data fifo byte 0. */ +#define S626_XFIFO_1 0x00000010 /* Data fifo byte 1. */ +#define S626_XFIFO_2 0x00000020 /* Data fifo byte 2. */ +#define S626_XFIFO_3 0x00000030 /* Data fifo byte 3. */ +#define S626_XFB0 0x00000040 /* FB_BUFFER byte 0. */ +#define S626_XFB1 0x00000050 /* FB_BUFFER byte 1. */ +#define S626_XFB2 0x00000060 /* FB_BUFFER byte 2. */ +#define S626_XFB3 0x00000070 /* FB_BUFFER byte 3. */ +#define S626_SIB_A2 0x00000200 /* + * Store next dword from A2's + * input shifter to FB2 + * buffer. + */ +#define S626_SF_A2 0x00000100 /* + * Store next dword from A2's + * input shifter to its input + * fifo. + */ +#define S626_LF_A2 0x00000080 /* + * Load next dword from A2's + * output fifo into its + * output dword buffer. + */ +#define S626_XSD2 0x00000008 /* Shift data out on SD2. */ +#define S626_RSD3 0x00001800 /* Shift data in on SD3. */ +#define S626_RSD2 0x00001000 /* Shift data in on SD2. */ +#define S626_LOW_A2 0x00000002 /* + * Drive last SD low for 7 clks, + * then tri-state. + */ +#define S626_EOS 0x00000001 /* End of superframe. */ + +/* I2C configuration constants. */ +#define S626_I2C_CLKSEL 0x0400 /* + * I2C bit rate = + * PCIclk/480 = 68.75 KHz. + */ +#define S626_I2C_BITRATE 68.75 /* + * I2C bus data bit rate + * (determined by + * S626_I2C_CLKSEL) in KHz. + */ +#define S626_I2C_WRTIME 15.0 /* + * Worst case time, in msec, + * for EEPROM internal write + * op. + */ + +/* I2C manifest constants. */ + +/* Max retries to wait for EEPROM write. */ +#define S626_I2C_RETRIES (S626_I2C_WRTIME * S626_I2C_BITRATE / 9.0) +#define S626_I2C_ERR 0x0002 /* I2C control/status flag ERROR. */ +#define S626_I2C_BUSY 0x0001 /* I2C control/status flag BUSY. */ +#define S626_I2C_ABORT 0x0080 /* I2C status flag ABORT. */ +#define S626_I2C_ATTRSTART 0x3 /* I2C attribute START. */ +#define S626_I2C_ATTRCONT 0x2 /* I2C attribute CONT. */ +#define S626_I2C_ATTRSTOP 0x1 /* I2C attribute STOP. */ +#define S626_I2C_ATTRNOP 0x0 /* I2C attribute NOP. */ + +/* Code macros used for constructing I2C command bytes. */ +#define S626_I2C_B2(ATTR, VAL) (((ATTR) << 6) | ((VAL) << 24)) +#define S626_I2C_B1(ATTR, VAL) (((ATTR) << 4) | ((VAL) << 16)) +#define S626_I2C_B0(ATTR, VAL) (((ATTR) << 2) | ((VAL) << 8)) + +/* DEBI command constants. */ +#define S626_DEBI_CMD_SIZE16 (2 << 17) /* + * Transfer size is always + * 2 bytes. + */ +#define S626_DEBI_CMD_READ 0x00010000 /* Read operation. */ +#define S626_DEBI_CMD_WRITE 0x00000000 /* Write operation. */ + +/* Read immediate 2 bytes. */ +#define S626_DEBI_CMD_RDWORD (S626_DEBI_CMD_READ | S626_DEBI_CMD_SIZE16) + +/* Write immediate 2 bytes. */ +#define S626_DEBI_CMD_WRWORD (S626_DEBI_CMD_WRITE | S626_DEBI_CMD_SIZE16) + +/* DEBI configuration constants. */ +#define S626_DEBI_CFG_XIRQ_EN 0x80000000 /* + * Enable external interrupt + * on GPIO3. + */ +#define S626_DEBI_CFG_XRESUME 0x40000000 /* Resume block */ + /* + * Transfer when XIRQ + * deasserted. + */ +#define S626_DEBI_CFG_TOQ 0x03C00000 /* Timeout (15 PCI cycles). */ +#define S626_DEBI_CFG_FAST 0x10000000 /* Fast mode enable. */ + +/* 4-bit field that specifies DEBI timeout value in PCI clock cycles: */ +#define S626_DEBI_CFG_TOUT_BIT 22 /* + * Finish DEBI cycle after this many + * clocks. + */ + +/* 2-bit field that specifies Endian byte lane steering: */ +#define S626_DEBI_CFG_SWAP_NONE 0x00000000 /* + * Straight - don't swap any + * bytes (Intel). + */ +#define S626_DEBI_CFG_SWAP_2 0x00100000 /* 2-byte swap (Motorola). */ +#define S626_DEBI_CFG_SWAP_4 0x00200000 /* 4-byte swap. */ +#define S626_DEBI_CFG_SLAVE16 0x00080000 /* + * Slave is able to serve + * 16-bit cycles. + */ +#define S626_DEBI_CFG_INC 0x00040000 /* + * Enable address increment + * for block transfers. + */ +#define S626_DEBI_CFG_INTEL 0x00020000 /* Intel style local bus. */ +#define S626_DEBI_CFG_TIMEROFF 0x00010000 /* Disable timer. */ + +#if S626_PLATFORM == S626_INTEL + +#define S626_DEBI_TOUT 7 /* + * Wait 7 PCI clocks (212 ns) before + * polling RDY. + */ + +/* Intel byte lane steering (pass through all byte lanes). */ +#define S626_DEBI_SWAP S626_DEBI_CFG_SWAP_NONE + +#elif S626_PLATFORM == S626_MOTOROLA + +#define S626_DEBI_TOUT 15 /* + * Wait 15 PCI clocks (454 ns) maximum + * before timing out. + */ + +/* Motorola byte lane steering. */ +#define S626_DEBI_SWAP S626_DEBI_CFG_SWAP_2 + +#endif + +/* DEBI page table constants. */ +#define S626_DEBI_PAGE_DISABLE 0x00000000 /* Paging disable. */ + +/* ******* EXTRA FROM OTHER SENSORAY * .h ******* */ + +/* LoadSrc values: */ +#define S626_LOADSRC_INDX 0 /* Preload core in response to Index. */ +#define S626_LOADSRC_OVER 1 /* + * Preload core in response to + * Overflow. + */ +#define S626_LOADSRCB_OVERA 2 /* + * Preload B core in response to + * A Overflow. + */ +#define S626_LOADSRC_NONE 3 /* Never preload core. */ + +/* IntSrc values: */ +#define S626_INTSRC_NONE 0 /* Interrupts disabled. */ +#define S626_INTSRC_OVER 1 /* Interrupt on Overflow. */ +#define S626_INTSRC_INDX 2 /* Interrupt on Index. */ +#define S626_INTSRC_BOTH 3 /* Interrupt on Index or Overflow. */ + +/* LatchSrc values: */ +#define S626_LATCHSRC_AB_READ 0 /* Latch on read. */ +#define S626_LATCHSRC_A_INDXA 1 /* Latch A on A Index. */ +#define S626_LATCHSRC_B_INDXB 2 /* Latch B on B Index. */ +#define S626_LATCHSRC_B_OVERA 3 /* Latch B on A Overflow. */ + +/* IndxSrc values: */ +#define S626_INDXSRC_ENCODER 0 /* Encoder. */ +#define S626_INDXSRC_DIGIN 1 /* Digital inputs. */ +#define S626_INDXSRC_SOFT 2 /* S/w controlled by IndxPol bit. */ +#define S626_INDXSRC_DISABLED 3 /* Index disabled. */ + +/* IndxPol values: */ +#define S626_INDXPOL_POS 0 /* Index input is active high. */ +#define S626_INDXPOL_NEG 1 /* Index input is active low. */ + +/* Logical encoder mode values: */ +#define S626_ENCMODE_COUNTER 0 /* Counter mode. */ +#define S626_ENCMODE_TIMER 2 /* Timer mode. */ +#define S626_ENCMODE_EXTENDER 3 /* Extender mode. */ + +/* Physical CntSrc values (for Counter A source and Counter B source): */ +#define S626_CNTSRC_ENCODER 0 /* Encoder */ +#define S626_CNTSRC_DIGIN 1 /* Digital inputs */ +#define S626_CNTSRC_SYSCLK 2 /* System clock up */ +#define S626_CNTSRC_SYSCLK_DOWN 3 /* System clock down */ + +/* ClkPol values: */ +#define S626_CLKPOL_POS 0 /* + * Counter/Extender clock is + * active high. + */ +#define S626_CLKPOL_NEG 1 /* + * Counter/Extender clock is + * active low. + */ +#define S626_CNTDIR_UP 0 /* Timer counts up. */ +#define S626_CNTDIR_DOWN 1 /* Timer counts down. */ + +/* ClkEnab values: */ +#define S626_CLKENAB_ALWAYS 0 /* Clock always enabled. */ +#define S626_CLKENAB_INDEX 1 /* Clock is enabled by index. */ + +/* ClkMult values: */ +#define S626_CLKMULT_4X 0 /* 4x clock multiplier. */ +#define S626_CLKMULT_2X 1 /* 2x clock multiplier. */ +#define S626_CLKMULT_1X 2 /* 1x clock multiplier. */ +#define S626_CLKMULT_SPECIAL 3 /* Special clock multiplier value. */ + +/* Sanity-check limits for parameters. */ + +#define S626_NUM_COUNTERS 6 /* + * Maximum valid counter + * logical channel number. + */ +#define S626_NUM_INTSOURCES 4 +#define S626_NUM_LATCHSOURCES 4 +#define S626_NUM_CLKMULTS 4 +#define S626_NUM_CLKSOURCES 4 +#define S626_NUM_CLKPOLS 2 +#define S626_NUM_INDEXPOLS 2 +#define S626_NUM_INDEXSOURCES 2 +#define S626_NUM_LOADTRIGS 4 + +/* General macros for manipulating bitfields: */ +#define S626_MAKE(x, w, p) (((x) & ((1 << (w)) - 1)) << (p)) +#define S626_UNMAKE(v, w, p) (((v) >> (p)) & ((1 << (w)) - 1)) + +/* Bit field positions in CRA: */ +#define S626_CRABIT_INDXSRC_B 14 /* B index source. */ +#define S626_CRABIT_CNTSRC_B 12 /* B counter source. */ +#define S626_CRABIT_INDXPOL_A 11 /* A index polarity. */ +#define S626_CRABIT_LOADSRC_A 9 /* A preload trigger. */ +#define S626_CRABIT_CLKMULT_A 7 /* A clock multiplier. */ +#define S626_CRABIT_INTSRC_A 5 /* A interrupt source. */ +#define S626_CRABIT_CLKPOL_A 4 /* A clock polarity. */ +#define S626_CRABIT_INDXSRC_A 2 /* A index source. */ +#define S626_CRABIT_CNTSRC_A 0 /* A counter source. */ + +/* Bit field widths in CRA: */ +#define S626_CRAWID_INDXSRC_B 2 +#define S626_CRAWID_CNTSRC_B 2 +#define S626_CRAWID_INDXPOL_A 1 +#define S626_CRAWID_LOADSRC_A 2 +#define S626_CRAWID_CLKMULT_A 2 +#define S626_CRAWID_INTSRC_A 2 +#define S626_CRAWID_CLKPOL_A 1 +#define S626_CRAWID_INDXSRC_A 2 +#define S626_CRAWID_CNTSRC_A 2 + +/* Bit field masks for CRA: */ +#define S626_CRAMSK_INDXSRC_B S626_SET_CRA_INDXSRC_B(~0) +#define S626_CRAMSK_CNTSRC_B S626_SET_CRA_CNTSRC_B(~0) +#define S626_CRAMSK_INDXPOL_A S626_SET_CRA_INDXPOL_A(~0) +#define S626_CRAMSK_LOADSRC_A S626_SET_CRA_LOADSRC_A(~0) +#define S626_CRAMSK_CLKMULT_A S626_SET_CRA_CLKMULT_A(~0) +#define S626_CRAMSK_INTSRC_A S626_SET_CRA_INTSRC_A(~0) +#define S626_CRAMSK_CLKPOL_A S626_SET_CRA_CLKPOL_A(~0) +#define S626_CRAMSK_INDXSRC_A S626_SET_CRA_INDXSRC_A(~0) +#define S626_CRAMSK_CNTSRC_A S626_SET_CRA_CNTSRC_A(~0) + +/* Construct parts of the CRA value: */ +#define S626_SET_CRA_INDXSRC_B(x) \ + S626_MAKE((x), S626_CRAWID_INDXSRC_B, S626_CRABIT_INDXSRC_B) +#define S626_SET_CRA_CNTSRC_B(x) \ + S626_MAKE((x), S626_CRAWID_CNTSRC_B, S626_CRABIT_CNTSRC_B) +#define S626_SET_CRA_INDXPOL_A(x) \ + S626_MAKE((x), S626_CRAWID_INDXPOL_A, S626_CRABIT_INDXPOL_A) +#define S626_SET_CRA_LOADSRC_A(x) \ + S626_MAKE((x), S626_CRAWID_LOADSRC_A, S626_CRABIT_LOADSRC_A) +#define S626_SET_CRA_CLKMULT_A(x) \ + S626_MAKE((x), S626_CRAWID_CLKMULT_A, S626_CRABIT_CLKMULT_A) +#define S626_SET_CRA_INTSRC_A(x) \ + S626_MAKE((x), S626_CRAWID_INTSRC_A, S626_CRABIT_INTSRC_A) +#define S626_SET_CRA_CLKPOL_A(x) \ + S626_MAKE((x), S626_CRAWID_CLKPOL_A, S626_CRABIT_CLKPOL_A) +#define S626_SET_CRA_INDXSRC_A(x) \ + S626_MAKE((x), S626_CRAWID_INDXSRC_A, S626_CRABIT_INDXSRC_A) +#define S626_SET_CRA_CNTSRC_A(x) \ + S626_MAKE((x), S626_CRAWID_CNTSRC_A, S626_CRABIT_CNTSRC_A) + +/* Extract parts of the CRA value: */ +#define S626_GET_CRA_INDXSRC_B(v) \ + S626_UNMAKE((v), S626_CRAWID_INDXSRC_B, S626_CRABIT_INDXSRC_B) +#define S626_GET_CRA_CNTSRC_B(v) \ + S626_UNMAKE((v), S626_CRAWID_CNTSRC_B, S626_CRABIT_CNTSRC_B) +#define S626_GET_CRA_INDXPOL_A(v) \ + S626_UNMAKE((v), S626_CRAWID_INDXPOL_A, S626_CRABIT_INDXPOL_A) +#define S626_GET_CRA_LOADSRC_A(v) \ + S626_UNMAKE((v), S626_CRAWID_LOADSRC_A, S626_CRABIT_LOADSRC_A) +#define S626_GET_CRA_CLKMULT_A(v) \ + S626_UNMAKE((v), S626_CRAWID_CLKMULT_A, S626_CRABIT_CLKMULT_A) +#define S626_GET_CRA_INTSRC_A(v) \ + S626_UNMAKE((v), S626_CRAWID_INTSRC_A, S626_CRABIT_INTSRC_A) +#define S626_GET_CRA_CLKPOL_A(v) \ + S626_UNMAKE((v), S626_CRAWID_CLKPOL_A, S626_CRABIT_CLKPOL_A) +#define S626_GET_CRA_INDXSRC_A(v) \ + S626_UNMAKE((v), S626_CRAWID_INDXSRC_A, S626_CRABIT_INDXSRC_A) +#define S626_GET_CRA_CNTSRC_A(v) \ + S626_UNMAKE((v), S626_CRAWID_CNTSRC_A, S626_CRABIT_CNTSRC_A) + +/* Bit field positions in CRB: */ +#define S626_CRBBIT_INTRESETCMD 15 /* (w) Interrupt reset command. */ +#define S626_CRBBIT_CNTDIR_B 15 /* (r) B counter direction. */ +#define S626_CRBBIT_INTRESET_B 14 /* (w) B interrupt reset enable. */ +#define S626_CRBBIT_OVERDO_A 14 /* (r) A overflow routed to dig. out. */ +#define S626_CRBBIT_INTRESET_A 13 /* (w) A interrupt reset enable. */ +#define S626_CRBBIT_OVERDO_B 13 /* (r) B overflow routed to dig. out. */ +#define S626_CRBBIT_CLKENAB_A 12 /* A clock enable. */ +#define S626_CRBBIT_INTSRC_B 10 /* B interrupt source. */ +#define S626_CRBBIT_LATCHSRC 8 /* A/B latch source. */ +#define S626_CRBBIT_LOADSRC_B 6 /* B preload trigger. */ +#define S626_CRBBIT_CLEAR_B 7 /* B cleared when A overflows. */ +#define S626_CRBBIT_CLKMULT_B 3 /* B clock multiplier. */ +#define S626_CRBBIT_CLKENAB_B 2 /* B clock enable. */ +#define S626_CRBBIT_INDXPOL_B 1 /* B index polarity. */ +#define S626_CRBBIT_CLKPOL_B 0 /* B clock polarity. */ + +/* Bit field widths in CRB: */ +#define S626_CRBWID_INTRESETCMD 1 +#define S626_CRBWID_CNTDIR_B 1 +#define S626_CRBWID_INTRESET_B 1 +#define S626_CRBWID_OVERDO_A 1 +#define S626_CRBWID_INTRESET_A 1 +#define S626_CRBWID_OVERDO_B 1 +#define S626_CRBWID_CLKENAB_A 1 +#define S626_CRBWID_INTSRC_B 2 +#define S626_CRBWID_LATCHSRC 2 +#define S626_CRBWID_LOADSRC_B 2 +#define S626_CRBWID_CLEAR_B 1 +#define S626_CRBWID_CLKMULT_B 2 +#define S626_CRBWID_CLKENAB_B 1 +#define S626_CRBWID_INDXPOL_B 1 +#define S626_CRBWID_CLKPOL_B 1 + +/* Bit field masks for CRB: */ +#define S626_CRBMSK_INTRESETCMD S626_SET_CRB_INTRESETCMD(~0) /* (w) */ +#define S626_CRBMSK_CNTDIR_B S626_CRBMSK_INTRESETCMD /* (r) */ +#define S626_CRBMSK_INTRESET_B S626_SET_CRB_INTRESET_B(~0) /* (w) */ +#define S626_CRBMSK_OVERDO_A S626_CRBMSK_INTRESET_B /* (r) */ +#define S626_CRBMSK_INTRESET_A S626_SET_CRB_INTRESET_A(~0) /* (w) */ +#define S626_CRBMSK_OVERDO_B S626_CRBMSK_INTRESET_A /* (r) */ +#define S626_CRBMSK_CLKENAB_A S626_SET_CRB_CLKENAB_A(~0) +#define S626_CRBMSK_INTSRC_B S626_SET_CRB_INTSRC_B(~0) +#define S626_CRBMSK_LATCHSRC S626_SET_CRB_LATCHSRC(~0) +#define S626_CRBMSK_LOADSRC_B S626_SET_CRB_LOADSRC_B(~0) +#define S626_CRBMSK_CLEAR_B S626_SET_CRB_CLEAR_B(~0) +#define S626_CRBMSK_CLKMULT_B S626_SET_CRB_CLKMULT_B(~0) +#define S626_CRBMSK_CLKENAB_B S626_SET_CRB_CLKENAB_B(~0) +#define S626_CRBMSK_INDXPOL_B S626_SET_CRB_INDXPOL_B(~0) +#define S626_CRBMSK_CLKPOL_B S626_SET_CRB_CLKPOL_B(~0) + +/* Interrupt reset control bits. */ +#define S626_CRBMSK_INTCTRL (S626_CRBMSK_INTRESETCMD | \ + S626_CRBMSK_INTRESET_A | \ + S626_CRBMSK_INTRESET_B) + +/* Construct parts of the CRB value: */ +#define S626_SET_CRB_INTRESETCMD(x) \ + S626_MAKE((x), S626_CRBWID_INTRESETCMD, S626_CRBBIT_INTRESETCMD) +#define S626_SET_CRB_INTRESET_B(x) \ + S626_MAKE((x), S626_CRBWID_INTRESET_B, S626_CRBBIT_INTRESET_B) +#define S626_SET_CRB_INTRESET_A(x) \ + S626_MAKE((x), S626_CRBWID_INTRESET_A, S626_CRBBIT_INTRESET_A) +#define S626_SET_CRB_CLKENAB_A(x) \ + S626_MAKE((x), S626_CRBWID_CLKENAB_A, S626_CRBBIT_CLKENAB_A) +#define S626_SET_CRB_INTSRC_B(x) \ + S626_MAKE((x), S626_CRBWID_INTSRC_B, S626_CRBBIT_INTSRC_B) +#define S626_SET_CRB_LATCHSRC(x) \ + S626_MAKE((x), S626_CRBWID_LATCHSRC, S626_CRBBIT_LATCHSRC) +#define S626_SET_CRB_LOADSRC_B(x) \ + S626_MAKE((x), S626_CRBWID_LOADSRC_B, S626_CRBBIT_LOADSRC_B) +#define S626_SET_CRB_CLEAR_B(x) \ + S626_MAKE((x), S626_CRBWID_CLEAR_B, S626_CRBBIT_CLEAR_B) +#define S626_SET_CRB_CLKMULT_B(x) \ + S626_MAKE((x), S626_CRBWID_CLKMULT_B, S626_CRBBIT_CLKMULT_B) +#define S626_SET_CRB_CLKENAB_B(x) \ + S626_MAKE((x), S626_CRBWID_CLKENAB_B, S626_CRBBIT_CLKENAB_B) +#define S626_SET_CRB_INDXPOL_B(x) \ + S626_MAKE((x), S626_CRBWID_INDXPOL_B, S626_CRBBIT_INDXPOL_B) +#define S626_SET_CRB_CLKPOL_B(x) \ + S626_MAKE((x), S626_CRBWID_CLKPOL_B, S626_CRBBIT_CLKPOL_B) + +/* Extract parts of the CRB value: */ +#define S626_GET_CRB_CNTDIR_B(v) \ + S626_UNMAKE((v), S626_CRBWID_CNTDIR_B, S626_CRBBIT_CNTDIR_B) +#define S626_GET_CRB_OVERDO_A(v) \ + S626_UNMAKE((v), S626_CRBWID_OVERDO_A, S626_CRBBIT_OVERDO_A) +#define S626_GET_CRB_OVERDO_B(v) \ + S626_UNMAKE((v), S626_CRBWID_OVERDO_B, S626_CRBBIT_OVERDO_B) +#define S626_GET_CRB_CLKENAB_A(v) \ + S626_UNMAKE((v), S626_CRBWID_CLKENAB_A, S626_CRBBIT_CLKENAB_A) +#define S626_GET_CRB_INTSRC_B(v) \ + S626_UNMAKE((v), S626_CRBWID_INTSRC_B, S626_CRBBIT_INTSRC_B) +#define S626_GET_CRB_LATCHSRC(v) \ + S626_UNMAKE((v), S626_CRBWID_LATCHSRC, S626_CRBBIT_LATCHSRC) +#define S626_GET_CRB_LOADSRC_B(v) \ + S626_UNMAKE((v), S626_CRBWID_LOADSRC_B, S626_CRBBIT_LOADSRC_B) +#define S626_GET_CRB_CLEAR_B(v) \ + S626_UNMAKE((v), S626_CRBWID_CLEAR_B, S626_CRBBIT_CLEAR_B) +#define S626_GET_CRB_CLKMULT_B(v) \ + S626_UNMAKE((v), S626_CRBWID_CLKMULT_B, S626_CRBBIT_CLKMULT_B) +#define S626_GET_CRB_CLKENAB_B(v) \ + S626_UNMAKE((v), S626_CRBWID_CLKENAB_B, S626_CRBBIT_CLKENAB_B) +#define S626_GET_CRB_INDXPOL_B(v) \ + S626_UNMAKE((v), S626_CRBWID_INDXPOL_B, S626_CRBBIT_INDXPOL_B) +#define S626_GET_CRB_CLKPOL_B(v) \ + S626_UNMAKE((v), S626_CRBWID_CLKPOL_B, S626_CRBBIT_CLKPOL_B) + +/* Bit field positions for standardized SETUP structure: */ +#define S626_STDBIT_INTSRC 13 +#define S626_STDBIT_LATCHSRC 11 +#define S626_STDBIT_LOADSRC 9 +#define S626_STDBIT_INDXSRC 7 +#define S626_STDBIT_INDXPOL 6 +#define S626_STDBIT_ENCMODE 4 +#define S626_STDBIT_CLKPOL 3 +#define S626_STDBIT_CLKMULT 1 +#define S626_STDBIT_CLKENAB 0 + +/* Bit field widths for standardized SETUP structure: */ +#define S626_STDWID_INTSRC 2 +#define S626_STDWID_LATCHSRC 2 +#define S626_STDWID_LOADSRC 2 +#define S626_STDWID_INDXSRC 2 +#define S626_STDWID_INDXPOL 1 +#define S626_STDWID_ENCMODE 2 +#define S626_STDWID_CLKPOL 1 +#define S626_STDWID_CLKMULT 2 +#define S626_STDWID_CLKENAB 1 + +/* Bit field masks for standardized SETUP structure: */ +#define S626_STDMSK_INTSRC S626_SET_STD_INTSRC(~0) +#define S626_STDMSK_LATCHSRC S626_SET_STD_LATCHSRC(~0) +#define S626_STDMSK_LOADSRC S626_SET_STD_LOADSRC(~0) +#define S626_STDMSK_INDXSRC S626_SET_STD_INDXSRC(~0) +#define S626_STDMSK_INDXPOL S626_SET_STD_INDXPOL(~0) +#define S626_STDMSK_ENCMODE S626_SET_STD_ENCMODE(~0) +#define S626_STDMSK_CLKPOL S626_SET_STD_CLKPOL(~0) +#define S626_STDMSK_CLKMULT S626_SET_STD_CLKMULT(~0) +#define S626_STDMSK_CLKENAB S626_SET_STD_CLKENAB(~0) + +/* Construct parts of standardized SETUP structure: */ +#define S626_SET_STD_INTSRC(x) \ + S626_MAKE((x), S626_STDWID_INTSRC, S626_STDBIT_INTSRC) +#define S626_SET_STD_LATCHSRC(x) \ + S626_MAKE((x), S626_STDWID_LATCHSRC, S626_STDBIT_LATCHSRC) +#define S626_SET_STD_LOADSRC(x) \ + S626_MAKE((x), S626_STDWID_LOADSRC, S626_STDBIT_LOADSRC) +#define S626_SET_STD_INDXSRC(x) \ + S626_MAKE((x), S626_STDWID_INDXSRC, S626_STDBIT_INDXSRC) +#define S626_SET_STD_INDXPOL(x) \ + S626_MAKE((x), S626_STDWID_INDXPOL, S626_STDBIT_INDXPOL) +#define S626_SET_STD_ENCMODE(x) \ + S626_MAKE((x), S626_STDWID_ENCMODE, S626_STDBIT_ENCMODE) +#define S626_SET_STD_CLKPOL(x) \ + S626_MAKE((x), S626_STDWID_CLKPOL, S626_STDBIT_CLKPOL) +#define S626_SET_STD_CLKMULT(x) \ + S626_MAKE((x), S626_STDWID_CLKMULT, S626_STDBIT_CLKMULT) +#define S626_SET_STD_CLKENAB(x) \ + S626_MAKE((x), S626_STDWID_CLKENAB, S626_STDBIT_CLKENAB) + +/* Extract parts of standardized SETUP structure: */ +#define S626_GET_STD_INTSRC(v) \ + S626_UNMAKE((v), S626_STDWID_INTSRC, S626_STDBIT_INTSRC) +#define S626_GET_STD_LATCHSRC(v) \ + S626_UNMAKE((v), S626_STDWID_LATCHSRC, S626_STDBIT_LATCHSRC) +#define S626_GET_STD_LOADSRC(v) \ + S626_UNMAKE((v), S626_STDWID_LOADSRC, S626_STDBIT_LOADSRC) +#define S626_GET_STD_INDXSRC(v) \ + S626_UNMAKE((v), S626_STDWID_INDXSRC, S626_STDBIT_INDXSRC) +#define S626_GET_STD_INDXPOL(v) \ + S626_UNMAKE((v), S626_STDWID_INDXPOL, S626_STDBIT_INDXPOL) +#define S626_GET_STD_ENCMODE(v) \ + S626_UNMAKE((v), S626_STDWID_ENCMODE, S626_STDBIT_ENCMODE) +#define S626_GET_STD_CLKPOL(v) \ + S626_UNMAKE((v), S626_STDWID_CLKPOL, S626_STDBIT_CLKPOL) +#define S626_GET_STD_CLKMULT(v) \ + S626_UNMAKE((v), S626_STDWID_CLKMULT, S626_STDBIT_CLKMULT) +#define S626_GET_STD_CLKENAB(v) \ + S626_UNMAKE((v), S626_STDWID_CLKENAB, S626_STDBIT_CLKENAB) + +#endif diff --git a/drivers/comedi/drivers/ssv_dnp.c b/drivers/comedi/drivers/ssv_dnp.c new file mode 100644 index 000000000..813bd0853 --- /dev/null +++ b/drivers/comedi/drivers/ssv_dnp.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * 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 <linux/comedi/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; + + /* + * We use 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. + */ + + 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; + + /* 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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/tests/Makefile b/drivers/comedi/drivers/tests/Makefile new file mode 100644 index 000000000..5ff7cdc32 --- /dev/null +++ b/drivers/comedi/drivers/tests/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for comedi drivers unit tests +# +ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG + +obj-$(CONFIG_COMEDI_TESTS_EXAMPLE) += comedi_example_test.o +obj-$(CONFIG_COMEDI_TESTS_NI_ROUTES) += ni_routes_test.o +CFLAGS_ni_routes_test.o := -DDEBUG diff --git a/drivers/comedi/drivers/tests/comedi_example_test.c b/drivers/comedi/drivers/tests/comedi_example_test.c new file mode 100644 index 000000000..81d074bcd --- /dev/null +++ b/drivers/comedi/drivers/tests/comedi_example_test.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/tests/comedi_example_test.c + * Example set of unit tests. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "unittest.h" + +/* *** BEGIN fake board data *** */ +struct comedi_device { + const char *board_name; + int item; +}; + +static struct comedi_device dev = { + .board_name = "fake_device", +}; + +/* *** END fake board data *** */ + +/* *** BEGIN fake data init *** */ +static void init_fake(void) +{ + dev.item = 10; +} + +/* *** END fake data init *** */ + +static void test0(void) +{ + init_fake(); + unittest(dev.item != 11, "negative result\n"); + unittest(dev.item == 10, "positive result\n"); +} + +/* **** BEGIN simple module entry/exit functions **** */ +static int __init unittest_enter(void) +{ + static const unittest_fptr unit_tests[] = { + test0, + NULL, + }; + + exec_unittests("example", unit_tests); + return 0; +} + +static void __exit unittest_exit(void) { } + +module_init(unittest_enter); +module_exit(unittest_exit); + +MODULE_AUTHOR("Spencer Olson <olsonse@umich.edu>"); +MODULE_DESCRIPTION("Comedi unit-tests example"); +MODULE_LICENSE("GPL"); +/* **** END simple module entry/exit functions **** */ diff --git a/drivers/comedi/drivers/tests/ni_routes_test.c b/drivers/comedi/drivers/tests/ni_routes_test.c new file mode 100644 index 000000000..652362486 --- /dev/null +++ b/drivers/comedi/drivers/tests/ni_routes_test.c @@ -0,0 +1,610 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/drivers/tests/ni_routes_test.c + * Unit tests for NI routes (ni_routes.c module). + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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 "../ni_stc.h" +#include "../ni_routes.h" +#include "unittest.h" + +#define RVI(table, src, dest) ((table)[(dest) * NI_NUM_NAMES + (src)]) +#define O(x) ((x) + NI_NAMES_BASE) +#define B(x) ((x) - NI_NAMES_BASE) +#define V(x) ((x) | 0x80) + +/* *** BEGIN fake board data *** */ +static const char *pci_6070e = "pci-6070e"; +static const char *pci_6220 = "pci-6220"; +static const char *pci_fake = "pci-fake"; + +static const char *ni_eseries = "ni_eseries"; +static const char *ni_mseries = "ni_mseries"; + +static struct ni_board_struct board = { + .name = NULL, +}; + +static struct ni_private private = { + .is_m_series = 0, +}; + +static const int bad_dest = O(8), dest0 = O(0), desti = O(5); +static const int ith_dest_index = 2; +static const int no_val_dest = O(7), no_val_index = 4; + +/* These have to be defs to be used in init code below */ +#define rgout0_src0 (O(100)) +#define rgout0_src1 (O(101)) +#define brd0_src0 (O(110)) +#define brd0_src1 (O(111)) +#define brd1_src0 (O(120)) +#define brd1_src1 (O(121)) +#define brd2_src0 (O(130)) +#define brd2_src1 (O(131)) +#define brd3_src0 (O(140)) +#define brd3_src1 (O(141)) + +/* I1 and I2 should not call O(...). Mostly here to shut checkpatch.pl up */ +#define I1(x1) \ + ((int[]){ \ + (x1), 0 \ + }) +#define I2(x1, x2) \ + ((int[]){ \ + (x1), (x2), 0 \ + }) +#define I3(x1, x2, x3) \ + ((int[]){ \ + (x1), (x2), (x3), 0 \ + }) + +/* O9 is build to call O(...) for each arg */ +#define O9(x1, x2, x3, x4, x5, x6, x7, x8, x9) \ + ((int[]){ \ + O(x1), O(x2), O(x3), O(x4), O(x5), O(x6), O(x7), O(x8), O(x9), \ + 0 \ + }) + +static struct ni_device_routes DR = { + .device = "testdev", + .routes = (struct ni_route_set[]){ + {.dest = O(0), .src = O9(/**/1, 2, 3, 4, 5, 6, 7, 8, 9)}, + {.dest = O(1), .src = O9(0, /**/2, 3, 4, 5, 6, 7, 8, 9)}, + /* ith route_set */ + {.dest = O(5), .src = O9(0, 1, 2, 3, 4,/**/ 6, 7, 8, 9)}, + {.dest = O(6), .src = O9(0, 1, 2, 3, 4, 5,/**/ 7, 8, 9)}, + /* next one will not have valid reg values */ + {.dest = O(7), .src = O9(0, 1, 2, 3, 4, 5, 6,/**/ 8, 9)}, + {.dest = O(9), .src = O9(0, 1, 2, 3, 4, 5, 6, 7, 8/**/)}, + + /* indirect routes done through muxes */ + {.dest = TRIGGER_LINE(0), .src = I1(rgout0_src0)}, + {.dest = TRIGGER_LINE(1), .src = I3(rgout0_src0, + brd3_src0, + brd3_src1)}, + {.dest = TRIGGER_LINE(2), .src = I3(rgout0_src1, + brd2_src0, + brd2_src1)}, + {.dest = TRIGGER_LINE(3), .src = I3(rgout0_src1, + brd1_src0, + brd1_src1)}, + {.dest = TRIGGER_LINE(4), .src = I2(brd0_src0, + brd0_src1)}, + {.dest = 0}, + }, +}; + +#undef I1 +#undef I2 +#undef O9 + +#define RV9(x1, x2, x3, x4, x5, x6, x7, x8, x9) \ + [x1] = V(x1), [x2] = V(x2), [x3] = V(x3), [x4] = V(x4), \ + [x5] = V(x5), [x6] = V(x6), [x7] = V(x7), [x8] = V(x8), \ + [x9] = V(x9), + +/* This table is indexed as RV[destination][source] */ +static const u8 RV[NI_NUM_NAMES][NI_NUM_NAMES] = { + [0] = {RV9(/**/1, 2, 3, 4, 5, 6, 7, 8, 9)}, + [1] = {RV9(0,/**/ 2, 3, 4, 5, 6, 7, 8, 9)}, + [2] = {RV9(0, 1,/**/3, 4, 5, 6, 7, 8, 9)}, + [3] = {RV9(0, 1, 2,/**/4, 5, 6, 7, 8, 9)}, + [4] = {RV9(0, 1, 2, 3,/**/5, 6, 7, 8, 9)}, + [5] = {RV9(0, 1, 2, 3, 4,/**/6, 7, 8, 9)}, + [6] = {RV9(0, 1, 2, 3, 4, 5,/**/7, 8, 9)}, + /* [7] is intentionaly left absent to test invalid routes */ + [8] = {RV9(0, 1, 2, 3, 4, 5, 6, 7,/**/9)}, + [9] = {RV9(0, 1, 2, 3, 4, 5, 6, 7, 8/**/)}, + /* some tests for needing extra muxes */ + [B(NI_RGOUT0)] = {[B(rgout0_src0)] = V(0), + [B(rgout0_src1)] = V(1)}, + [B(NI_RTSI_BRD(0))] = {[B(brd0_src0)] = V(0), + [B(brd0_src1)] = V(1)}, + [B(NI_RTSI_BRD(1))] = {[B(brd1_src0)] = V(0), + [B(brd1_src1)] = V(1)}, + [B(NI_RTSI_BRD(2))] = {[B(brd2_src0)] = V(0), + [B(brd2_src1)] = V(1)}, + [B(NI_RTSI_BRD(3))] = {[B(brd3_src0)] = V(0), + [B(brd3_src1)] = V(1)}, +}; + +#undef RV9 + +/* *** END fake board data *** */ + +/* *** BEGIN board data initializers *** */ +static void init_private(void) +{ + memset(&private, 0, sizeof(struct ni_private)); +} + +static void init_pci_6070e(void) +{ + board.name = pci_6070e; + init_private(); + private.is_m_series = 0; +} + +static void init_pci_6220(void) +{ + board.name = pci_6220; + init_private(); + private.is_m_series = 1; +} + +static void init_pci_fake(void) +{ + board.name = pci_fake; + init_private(); + private.routing_tables.route_values = &RV[0][0]; + private.routing_tables.valid_routes = &DR; +} + +/* *** END board data initializers *** */ + +/* Tests that route_sets are in order of the signal destination. */ +static bool route_set_dests_in_order(const struct ni_device_routes *devroutes) +{ + int i; + int last = NI_NAMES_BASE - 1; + + for (i = 0; i < devroutes->n_route_sets; ++i) { + if (last >= devroutes->routes[i].dest) + return false; + last = devroutes->routes[i].dest; + } + return true; +} + +/* Tests that all route_set->src are in order of the signal source. */ +static bool route_set_sources_in_order(const struct ni_device_routes *devroutes) +{ + int i; + + for (i = 0; i < devroutes->n_route_sets; ++i) { + int j; + int last = NI_NAMES_BASE - 1; + + for (j = 0; j < devroutes->routes[i].n_src; ++j) { + if (last >= devroutes->routes[i].src[j]) + return false; + last = devroutes->routes[i].src[j]; + } + } + return true; +} + +static void test_ni_assign_device_routes(void) +{ + const struct ni_device_routes *devroutes; + const u8 *table, *oldtable; + + init_pci_6070e(); + ni_assign_device_routes(ni_eseries, pci_6070e, NULL, + &private.routing_tables); + devroutes = private.routing_tables.valid_routes; + table = private.routing_tables.route_values; + + unittest(strncmp(devroutes->device, pci_6070e, 10) == 0, + "find device pci-6070e\n"); + unittest(devroutes->n_route_sets == 37, + "number of pci-6070e route_sets == 37\n"); + unittest(devroutes->routes->dest == NI_PFI(0), + "first pci-6070e route_set is for NI_PFI(0)\n"); + unittest(devroutes->routes->n_src == 1, + "first pci-6070e route_set length == 1\n"); + unittest(devroutes->routes->src[0] == NI_AI_StartTrigger, + "first pci-6070e route_set src. == NI_AI_StartTrigger\n"); + unittest(devroutes->routes[10].dest == TRIGGER_LINE(0), + "10th pci-6070e route_set is for TRIGGER_LINE(0)\n"); + unittest(devroutes->routes[10].n_src == 10, + "10th pci-6070e route_set length == 10\n"); + unittest(devroutes->routes[10].src[0] == NI_CtrSource(0), + "10th pci-6070e route_set src. == NI_CtrSource(0)\n"); + unittest(route_set_dests_in_order(devroutes), + "all pci-6070e route_sets in order of signal destination\n"); + unittest(route_set_sources_in_order(devroutes), + "all pci-6070e route_set->src's in order of signal source\n"); + + unittest(RVI(table, B(PXI_Star), B(NI_AI_SampleClock)) == V(17) && + RVI(table, B(NI_10MHzRefClock), B(TRIGGER_LINE(0))) == 0 && + RVI(table, B(NI_AI_ConvertClock), B(NI_PFI(0))) == 0 && + RVI(table, B(NI_AI_ConvertClock), B(NI_PFI(2))) == V(NI_PFI_OUTPUT_AI_CONVERT), + "pci-6070e finds e-series route_values table\n"); + + oldtable = table; + init_pci_6220(); + ni_assign_device_routes(ni_mseries, pci_6220, NULL, + &private.routing_tables); + devroutes = private.routing_tables.valid_routes; + table = private.routing_tables.route_values; + + unittest(strncmp(devroutes->device, pci_6220, 10) == 0, + "find device pci-6220\n"); + unittest(oldtable != table, "pci-6220 find other route_values table\n"); + + unittest(RVI(table, B(PXI_Star), B(NI_AI_SampleClock)) == V(20) && + RVI(table, B(NI_10MHzRefClock), B(TRIGGER_LINE(0))) == V(12) && + RVI(table, B(NI_AI_ConvertClock), B(NI_PFI(0))) == V(3) && + RVI(table, B(NI_AI_ConvertClock), B(NI_PFI(2))) == V(3), + "pci-6220 finds m-series route_values table\n"); +} + +static void test_ni_sort_device_routes(void) +{ + /* We begin by sorting the device routes for use in later tests */ + ni_sort_device_routes(&DR); + /* now we test that sorting. */ + unittest(route_set_dests_in_order(&DR), + "all route_sets of fake data in order of sig. destination\n"); + unittest(route_set_sources_in_order(&DR), + "all route_set->src's of fake data in order of sig. source\n"); +} + +static void test_ni_find_route_set(void) +{ + unittest(!ni_find_route_set(bad_dest, &DR), + "check for nonexistent route_set\n"); + unittest(ni_find_route_set(dest0, &DR) == &DR.routes[0], + "find first route_set\n"); + unittest(ni_find_route_set(desti, &DR) == &DR.routes[ith_dest_index], + "find ith route_set\n"); + unittest(ni_find_route_set(no_val_dest, &DR) == + &DR.routes[no_val_index], + "find no_val route_set in spite of missing values\n"); + unittest(ni_find_route_set(DR.routes[DR.n_route_sets - 1].dest, &DR) == + &DR.routes[DR.n_route_sets - 1], + "find last route_set\n"); +} + +static void test_ni_route_set_has_source(void) +{ + unittest(!ni_route_set_has_source(&DR.routes[0], O(0)), + "check for bad source\n"); + unittest(ni_route_set_has_source(&DR.routes[0], O(1)), + "find first source\n"); + unittest(ni_route_set_has_source(&DR.routes[0], O(5)), + "find fifth source\n"); + unittest(ni_route_set_has_source(&DR.routes[0], O(9)), + "find last source\n"); +} + +static void test_ni_route_to_register(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(ni_route_to_register(O(0), O(0), T) < 0, + "check for bad route 0-->0\n"); + unittest(ni_route_to_register(O(1), O(0), T) == 1, + "validate first destination\n"); + unittest(ni_route_to_register(O(6), O(5), T) == 6, + "validate middle destination\n"); + unittest(ni_route_to_register(O(8), O(9), T) == 8, + "validate last destination\n"); + + /* choice of trigger line in the following is somewhat random */ + unittest(ni_route_to_register(rgout0_src0, TRIGGER_LINE(0), T) == 0, + "validate indirect route through rgout0 to TRIGGER_LINE(0)\n"); + unittest(ni_route_to_register(rgout0_src0, TRIGGER_LINE(1), T) == 0, + "validate indirect route through rgout0 to TRIGGER_LINE(1)\n"); + unittest(ni_route_to_register(rgout0_src1, TRIGGER_LINE(2), T) == 1, + "validate indirect route through rgout0 to TRIGGER_LINE(2)\n"); + unittest(ni_route_to_register(rgout0_src1, TRIGGER_LINE(3), T) == 1, + "validate indirect route through rgout0 to TRIGGER_LINE(3)\n"); + + unittest(ni_route_to_register(brd0_src0, TRIGGER_LINE(4), T) == + BIT(6), + "validate indirect route through brd0 to TRIGGER_LINE(4)\n"); + unittest(ni_route_to_register(brd0_src1, TRIGGER_LINE(4), T) == + BIT(6), + "validate indirect route through brd0 to TRIGGER_LINE(4)\n"); + unittest(ni_route_to_register(brd1_src0, TRIGGER_LINE(3), T) == + BIT(6), + "validate indirect route through brd1 to TRIGGER_LINE(3)\n"); + unittest(ni_route_to_register(brd1_src1, TRIGGER_LINE(3), T) == + BIT(6), + "validate indirect route through brd1 to TRIGGER_LINE(3)\n"); + unittest(ni_route_to_register(brd2_src0, TRIGGER_LINE(2), T) == + BIT(6), + "validate indirect route through brd2 to TRIGGER_LINE(2)\n"); + unittest(ni_route_to_register(brd2_src1, TRIGGER_LINE(2), T) == + BIT(6), + "validate indirect route through brd2 to TRIGGER_LINE(2)\n"); + unittest(ni_route_to_register(brd3_src0, TRIGGER_LINE(1), T) == + BIT(6), + "validate indirect route through brd3 to TRIGGER_LINE(1)\n"); + unittest(ni_route_to_register(brd3_src1, TRIGGER_LINE(1), T) == + BIT(6), + "validate indirect route through brd3 to TRIGGER_LINE(1)\n"); +} + +static void test_ni_lookup_route_register(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(ni_lookup_route_register(O(0), O(0), T) == -EINVAL, + "check for bad route 0-->0\n"); + unittest(ni_lookup_route_register(O(1), O(0), T) == 1, + "validate first destination\n"); + unittest(ni_lookup_route_register(O(6), O(5), T) == 6, + "validate middle destination\n"); + unittest(ni_lookup_route_register(O(8), O(9), T) == 8, + "validate last destination\n"); + unittest(ni_lookup_route_register(O(10), O(9), T) == -EINVAL, + "lookup invalid destination\n"); + + unittest(ni_lookup_route_register(rgout0_src0, TRIGGER_LINE(0), T) == + -EINVAL, + "rgout0_src0: no direct lookup of indirect route\n"); + unittest(ni_lookup_route_register(rgout0_src0, NI_RGOUT0, T) == 0, + "rgout0_src0: lookup indirect route register\n"); + unittest(ni_lookup_route_register(rgout0_src1, TRIGGER_LINE(2), T) == + -EINVAL, + "rgout0_src1: no direct lookup of indirect route\n"); + unittest(ni_lookup_route_register(rgout0_src1, NI_RGOUT0, T) == 1, + "rgout0_src1: lookup indirect route register\n"); + + unittest(ni_lookup_route_register(brd0_src0, TRIGGER_LINE(4), T) == + -EINVAL, + "brd0_src0: no direct lookup of indirect route\n"); + unittest(ni_lookup_route_register(brd0_src0, NI_RTSI_BRD(0), T) == 0, + "brd0_src0: lookup indirect route register\n"); + unittest(ni_lookup_route_register(brd0_src1, TRIGGER_LINE(4), T) == + -EINVAL, + "brd0_src1: no direct lookup of indirect route\n"); + unittest(ni_lookup_route_register(brd0_src1, NI_RTSI_BRD(0), T) == 1, + "brd0_src1: lookup indirect route register\n"); +} + +static void test_route_is_valid(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(!route_is_valid(O(0), O(0), T), + "check for bad route 0-->0\n"); + unittest(route_is_valid(O(0), O(1), T), + "validate first destination\n"); + unittest(route_is_valid(O(5), O(6), T), + "validate middle destination\n"); + unittest(route_is_valid(O(8), O(9), T), + "validate last destination\n"); +} + +static void test_ni_is_cmd_dest(void) +{ + init_pci_fake(); + unittest(ni_is_cmd_dest(NI_AI_SampleClock), + "check that AI/SampleClock is cmd destination\n"); + unittest(ni_is_cmd_dest(NI_AI_StartTrigger), + "check that AI/StartTrigger is cmd destination\n"); + unittest(ni_is_cmd_dest(NI_AI_ConvertClock), + "check that AI/ConvertClock is cmd destination\n"); + unittest(ni_is_cmd_dest(NI_AO_SampleClock), + "check that AO/SampleClock is cmd destination\n"); + unittest(ni_is_cmd_dest(NI_DO_SampleClock), + "check that DO/SampleClock is cmd destination\n"); + unittest(!ni_is_cmd_dest(NI_AO_SampleClockTimebase), + "check that AO/SampleClockTimebase _not_ cmd destination\n"); +} + +static void test_channel_is_pfi(void) +{ + init_pci_fake(); + unittest(channel_is_pfi(NI_PFI(0)), "check First pfi channel\n"); + unittest(channel_is_pfi(NI_PFI(10)), "check 10th pfi channel\n"); + unittest(channel_is_pfi(NI_PFI(-1)), "check last pfi channel\n"); + unittest(!channel_is_pfi(NI_PFI(-1) + 1), + "check first non pfi channel\n"); +} + +static void test_channel_is_rtsi(void) +{ + init_pci_fake(); + unittest(channel_is_rtsi(TRIGGER_LINE(0)), + "check First rtsi channel\n"); + unittest(channel_is_rtsi(TRIGGER_LINE(3)), + "check 3rd rtsi channel\n"); + unittest(channel_is_rtsi(TRIGGER_LINE(-1)), + "check last rtsi channel\n"); + unittest(!channel_is_rtsi(TRIGGER_LINE(-1) + 1), + "check first non rtsi channel\n"); +} + +static void test_ni_count_valid_routes(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(ni_count_valid_routes(T) == 57, "count all valid routes\n"); +} + +static void test_ni_get_valid_routes(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + unsigned int pair_data[2]; + + init_pci_fake(); + unittest(ni_get_valid_routes(T, 0, NULL) == 57, + "count all valid routes through ni_get_valid_routes\n"); + + unittest(ni_get_valid_routes(T, 1, pair_data) == 1, + "copied first valid route from ni_get_valid_routes\n"); + unittest(pair_data[0] == O(1), + "source of first valid pair from ni_get_valid_routes\n"); + unittest(pair_data[1] == O(0), + "destination of first valid pair from ni_get_valid_routes\n"); +} + +static void test_ni_find_route_source(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(ni_find_route_source(4, O(4), T) == -EINVAL, + "check for bad source 4-->4\n"); + unittest(ni_find_route_source(0, O(1), T) == O(0), + "find first source\n"); + unittest(ni_find_route_source(4, O(6), T) == O(4), + "find middle source\n"); + unittest(ni_find_route_source(9, O(8), T) == O(9), + "find last source"); + unittest(ni_find_route_source(8, O(9), T) == O(8), + "find invalid source (without checking device routes)\n"); +} + +static void test_route_register_is_valid(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(!route_register_is_valid(4, O(4), T), + "check for bad source 4-->4\n"); + unittest(route_register_is_valid(0, O(1), T), + "find first source\n"); + unittest(route_register_is_valid(4, O(6), T), + "find middle source\n"); + unittest(route_register_is_valid(9, O(8), T), + "find last source"); +} + +static void test_ni_check_trigger_arg(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(ni_check_trigger_arg(0, O(0), T) == -EINVAL, + "check bad direct trigger arg for first reg->dest\n"); + unittest(ni_check_trigger_arg(0, O(1), T) == 0, + "check direct trigger arg for first reg->dest\n"); + unittest(ni_check_trigger_arg(4, O(6), T) == 0, + "check direct trigger arg for middle reg->dest\n"); + unittest(ni_check_trigger_arg(9, O(8), T) == 0, + "check direct trigger arg for last reg->dest\n"); + + unittest(ni_check_trigger_arg_roffs(-1, O(0), T, 1) == -EINVAL, + "check bad direct trigger arg for first reg->dest w/offs\n"); + unittest(ni_check_trigger_arg_roffs(0, O(1), T, 0) == 0, + "check direct trigger arg for first reg->dest w/offs\n"); + unittest(ni_check_trigger_arg_roffs(3, O(6), T, 1) == 0, + "check direct trigger arg for middle reg->dest w/offs\n"); + unittest(ni_check_trigger_arg_roffs(7, O(8), T, 2) == 0, + "check direct trigger arg for last reg->dest w/offs\n"); + + unittest(ni_check_trigger_arg(O(0), O(0), T) == -EINVAL, + "check bad trigger arg for first src->dest\n"); + unittest(ni_check_trigger_arg(O(0), O(1), T) == 0, + "check trigger arg for first src->dest\n"); + unittest(ni_check_trigger_arg(O(5), O(6), T) == 0, + "check trigger arg for middle src->dest\n"); + unittest(ni_check_trigger_arg(O(8), O(9), T) == 0, + "check trigger arg for last src->dest\n"); +} + +static void test_ni_get_reg_value(void) +{ + const struct ni_route_tables *T = &private.routing_tables; + + init_pci_fake(); + unittest(ni_get_reg_value(0, O(0), T) == -1, + "check bad direct trigger arg for first reg->dest\n"); + unittest(ni_get_reg_value(0, O(1), T) == 0, + "check direct trigger arg for first reg->dest\n"); + unittest(ni_get_reg_value(4, O(6), T) == 4, + "check direct trigger arg for middle reg->dest\n"); + unittest(ni_get_reg_value(9, O(8), T) == 9, + "check direct trigger arg for last reg->dest\n"); + + unittest(ni_get_reg_value_roffs(-1, O(0), T, 1) == -1, + "check bad direct trigger arg for first reg->dest w/offs\n"); + unittest(ni_get_reg_value_roffs(0, O(1), T, 0) == 0, + "check direct trigger arg for first reg->dest w/offs\n"); + unittest(ni_get_reg_value_roffs(3, O(6), T, 1) == 4, + "check direct trigger arg for middle reg->dest w/offs\n"); + unittest(ni_get_reg_value_roffs(7, O(8), T, 2) == 9, + "check direct trigger arg for last reg->dest w/offs\n"); + + unittest(ni_get_reg_value(O(0), O(0), T) == -1, + "check bad trigger arg for first src->dest\n"); + unittest(ni_get_reg_value(O(0), O(1), T) == 0, + "check trigger arg for first src->dest\n"); + unittest(ni_get_reg_value(O(5), O(6), T) == 5, + "check trigger arg for middle src->dest\n"); + unittest(ni_get_reg_value(O(8), O(9), T) == 8, + "check trigger arg for last src->dest\n"); +} + +/* **** BEGIN simple module entry/exit functions **** */ +static int __init ni_routes_unittest(void) +{ + static const unittest_fptr unit_tests[] = { + test_ni_assign_device_routes, + test_ni_sort_device_routes, + test_ni_find_route_set, + test_ni_route_set_has_source, + test_ni_route_to_register, + test_ni_lookup_route_register, + test_route_is_valid, + test_ni_is_cmd_dest, + test_channel_is_pfi, + test_channel_is_rtsi, + test_ni_count_valid_routes, + test_ni_get_valid_routes, + test_ni_find_route_source, + test_route_register_is_valid, + test_ni_check_trigger_arg, + test_ni_get_reg_value, + NULL, + }; + + exec_unittests("ni_routes", unit_tests); + return 0; +} + +static void __exit ni_routes_unittest_exit(void) { } + +module_init(ni_routes_unittest); +module_exit(ni_routes_unittest_exit); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi unit-tests for ni_routes module"); +MODULE_LICENSE("GPL"); +/* **** END simple module entry/exit functions **** */ diff --git a/drivers/comedi/drivers/tests/unittest.h b/drivers/comedi/drivers/tests/unittest.h new file mode 100644 index 000000000..b0b34e058 --- /dev/null +++ b/drivers/comedi/drivers/tests/unittest.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * comedi/drivers/tests/unittest.h + * Simple framework for unittests for comedi drivers. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2016 Spencer E. Olson <olsonse@umich.edu> + * based of parts of drivers/of/unittest.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT 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_DRIVERS_TESTS_UNITTEST_H +#define _COMEDI_DRIVERS_TESTS_UNITTEST_H + +static struct unittest_results { + int passed; + int failed; +} unittest_results; + +typedef void (*unittest_fptr)(void); + +#define unittest(result, fmt, ...) ({ \ + bool failed = !(result); \ + if (failed) { \ + ++unittest_results.failed; \ + pr_err("FAIL %s():%i " fmt, __func__, __LINE__, \ + ##__VA_ARGS__); \ + } else { \ + ++unittest_results.passed; \ + pr_debug("pass %s():%i " fmt, __func__, __LINE__, \ + ##__VA_ARGS__); \ + } \ + failed; \ +}) + +/** + * Execute an array of unit tests. + * @name: Name of set of unit tests--will be shown at INFO log level. + * @unit_tests: A null-terminated list of unit tests to execute. + */ +static inline void exec_unittests(const char *name, + const unittest_fptr *unit_tests) +{ + pr_info("begin comedi:\"%s\" unittests\n", name); + + for (; (*unit_tests) != NULL; ++unit_tests) + (*unit_tests)(); + + pr_info("end of comedi:\"%s\" unittests - %i passed, %i failed\n", name, + unittest_results.passed, unittest_results.failed); +} + +#endif /* _COMEDI_DRIVERS_TESTS_UNITTEST_H */ diff --git a/drivers/comedi/drivers/usbdux.c b/drivers/comedi/drivers/usbdux.c new file mode 100644 index 000000000..92d514b3c --- /dev/null +++ b/drivers/comedi/drivers/usbdux.c @@ -0,0 +1,1728 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * usbdux.c + * Copyright (C) 2003-2014 Bernd Porr, mail@berndporr.me.uk + */ + +/* + * Driver: usbdux + * Description: University of Stirling USB DAQ & INCITE Technology Limited + * Devices: [ITL] USB-DUX (usbdux) + * Author: Bernd Porr <mail@berndporr.me.uk> + * Updated: 10 Oct 2014 + * Status: Stable + * + * Connection scheme for the counter at the digital port: + * 0=/CLK0, 1=UP/DOWN0, 2=RESET0, 4=/CLK1, 5=UP/DOWN1, 6=RESET1. + * The sampling rate of the counter is approximately 500Hz. + * + * Note that under USB2.0 the length of the channel list determines + * the max sampling rate. If you sample only one channel you get 8kHz + * sampling rate. If you sample two channels you get 4kHz and so on. + */ + +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Bernd Porr + * + * + * Revision history: + * 0.94: D/A output should work now with any channel list combinations + * 0.95: .owner commented out for kernel vers below 2.4.19 + * sanity checks in ai/ao_cmd + * 0.96: trying to get it working with 2.6, moved all memory alloc to comedi's + * attach final USB IDs + * moved memory allocation completely to the corresponding comedi + * functions firmware upload is by fxload and no longer by comedi (due to + * enumeration) + * 0.97: USB IDs received, adjusted table + * 0.98: SMP, locking, memory alloc: moved all usb memory alloc + * to the usb subsystem and moved all comedi related memory + * alloc to comedi. + * | kernel | registration | usbdux-usb | usbdux-comedi | comedi | + * 0.99: USB 2.0: changed protocol to isochronous transfer + * IRQ transfer is too buggy and too risky in 2.0 + * for the high speed ISO transfer is now a working version + * available + * 0.99b: Increased the iso transfer buffer for high sp.to 10 buffers. Some VIA + * chipsets miss out IRQs. Deeper buffering is needed. + * 1.00: full USB 2.0 support for the A/D converter. Now: max 8kHz sampling + * rate. + * Firmware vers 1.00 is needed for this. + * Two 16 bit up/down/reset counter with a sampling rate of 1kHz + * And loads of cleaning up, in particular streamlining the + * bulk transfers. + * 1.1: moved EP4 transfers to EP1 to make space for a PWM output on EP4 + * 1.2: added PWM support via EP4 + * 2.0: PWM seems to be stable and is not interfering with the other functions + * 2.1: changed PWM API + * 2.2: added firmware kernel request to fix an udev problem + * 2.3: corrected a bug in bulk timeouts which were far too short + * 2.4: fixed a bug which causes the driver to hang when it ran out of data. + * Thanks to Jan-Matthias Braun and Ian to spot the bug and fix it. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> +#include <linux/comedi/comedi_usb.h> + +/* constants for firmware upload and download */ +#define USBDUX_FIRMWARE "usbdux_firmware.bin" +#define USBDUX_FIRMWARE_MAX_LEN 0x2000 +#define USBDUX_FIRMWARE_CMD 0xa0 +#define VENDOR_DIR_IN 0xc0 +#define VENDOR_DIR_OUT 0x40 +#define USBDUX_CPU_CS 0xe600 + +/* usbdux bulk transfer commands */ +#define USBDUX_CMD_MULT_AI 0 +#define USBDUX_CMD_AO 1 +#define USBDUX_CMD_DIO_CFG 2 +#define USBDUX_CMD_DIO_BITS 3 +#define USBDUX_CMD_SINGLE_AI 4 +#define USBDUX_CMD_TIMER_RD 5 +#define USBDUX_CMD_TIMER_WR 6 +#define USBDUX_CMD_PWM_ON 7 +#define USBDUX_CMD_PWM_OFF 8 + +/* timeout for the USB-transfer in ms */ +#define BULK_TIMEOUT 1000 + +/* 300Hz max frequ under PWM */ +#define MIN_PWM_PERIOD ((long)(1E9 / 300)) + +/* Default PWM frequency */ +#define PWM_DEFAULT_PERIOD ((long)(1E9 / 100)) + +/* Size of one A/D value */ +#define SIZEADIN ((sizeof(u16))) + +/* + * 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(u8) + sizeof(u16))) + +/* + * 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 */ + u8 pwm_delay; + /* size of the PWM buffer which holds the bit pattern */ + int pwm_buf_sz; + /* input buffer for the ISO-transfer */ + __le16 *in_buf; + /* input buffer for single insn */ + __le16 *insn_buf; + + unsigned int high_speed:1; + unsigned int ai_cmd_running:1; + unsigned int ao_cmd_running:1; + unsigned int pwm_cmd_running:1; + + /* time between samples in units of the timer */ + unsigned int ai_timer; + unsigned int ao_timer; + /* counter between aquisitions */ + unsigned int ai_counter; + unsigned int ao_counter; + /* interval in frames/uframes */ + unsigned int ai_interval; + /* commands */ + u8 *dux_commands; + struct mutex mut; +}; + +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 */ + mutex_lock(&devpriv->mut); + /* unlink only if the urb really has been submitted */ + usbdux_ai_stop(dev, devpriv->ai_cmd_running); + mutex_unlock(&devpriv->mut); + + return 0; +} + +static void usbduxsub_ai_handle_urb(struct comedi_device *dev, + struct comedi_subdevice *s, + struct urb *urb) +{ + struct usbdux_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int ret; + int i; + + devpriv->ai_counter--; + if (devpriv->ai_counter == 0) { + devpriv->ai_counter = devpriv->ai_timer; + + /* get the data from the USB bus and hand it over to comedi */ + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int range = CR_RANGE(cmd->chanlist[i]); + u16 val = le16_to_cpu(devpriv->in_buf[i]); + + /* bipolar data is two's-complement */ + if (comedi_range_is_bipolar(s, range)) + val = comedi_offset_munge(s, val); + + /* transfer data */ + if (!comedi_buf_write_samples(s, &val, 1)) + return; + } + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + } + + /* if command is still running, resubmit urb */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + urb->dev = comedi_to_usb_dev(dev); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(dev->class_dev, + "urb resubmit failed in int-context! err=%d\n", + ret); + if (ret == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler!\n"); + async->events |= COMEDI_CB_ERROR; + } + } +} + +static void usbduxsub_ai_isoc_irq(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct usbdux_private *devpriv = dev->private; + + /* exit if not running a command, do not resubmit urb */ + if (!devpriv->ai_cmd_running) + return; + + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF); + usbduxsub_ai_handle_urb(dev, s, urb); + break; + + case -EILSEQ: + /* + * error in the ISOchronous data + * we don't copy the data into the transfer buffer + * and recycle the last data byte + */ + dev_dbg(dev->class_dev, "CRC error in ISO IN stream\n"); + usbduxsub_ai_handle_urb(dev, s, urb); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + async->events |= COMEDI_CB_ERROR; + break; + + default: + /* a real error */ + dev_err(dev->class_dev, + "Non-zero urb status received in ai intr context: %d\n", + urb->status); + async->events |= COMEDI_CB_ERROR; + break; + } + + /* + * comedi_handle_events() cannot be used in this driver. The (*cancel) + * operation would unlink the urb. + */ + if (async->events & COMEDI_CB_CANCEL_MASK) + usbdux_ai_stop(dev, 0); + + comedi_event(dev, s); +} + +static void usbdux_ao_stop(struct comedi_device *dev, int do_unlink) +{ + struct usbdux_private *devpriv = dev->private; + + if (do_unlink && devpriv->ao_urbs) + usbdux_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs); + + devpriv->ao_cmd_running = 0; +} + +static int usbdux_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbdux_private *devpriv = dev->private; + + /* prevent other CPUs from submitting a command just now */ + mutex_lock(&devpriv->mut); + /* unlink only if it is really running */ + usbdux_ao_stop(dev, devpriv->ao_cmd_running); + mutex_unlock(&devpriv->mut); + + return 0; +} + +static void usbduxsub_ao_handle_urb(struct comedi_device *dev, + struct comedi_subdevice *s, + struct urb *urb) +{ + struct usbdux_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u8 *datap; + int ret; + int i; + + devpriv->ao_counter--; + if (devpriv->ao_counter == 0) { + devpriv->ao_counter = devpriv->ao_timer; + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + return; + } + + /* transmit data to the USB bus */ + datap = urb->transfer_buffer; + *datap++ = cmd->chanlist_len; + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned short val; + + if (!comedi_buf_read_samples(s, &val, 1)) { + dev_err(dev->class_dev, "buffer underflow\n"); + async->events |= COMEDI_CB_OVERFLOW; + return; + } + + /* pointer to the DA */ + *datap++ = val & 0xff; + *datap++ = (val >> 8) & 0xff; + *datap++ = chan << 6; + s->readback[chan] = val; + } + } + + /* if command is still running, resubmit urb for BULK transfer */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + urb->transfer_buffer_length = SIZEOUTBUF; + urb->dev = comedi_to_usb_dev(dev); + urb->status = 0; + if (devpriv->high_speed) + urb->interval = 8; /* uframes */ + else + urb->interval = 1; /* frames */ + urb->number_of_packets = 1; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = SIZEOUTBUF; + urb->iso_frame_desc[0].status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(dev->class_dev, + "ao urb resubm failed in int-cont. ret=%d", + ret); + if (ret == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handling!\n"); + async->events |= COMEDI_CB_ERROR; + } + } +} + +static void usbduxsub_ao_isoc_irq(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_async *async = s->async; + struct usbdux_private *devpriv = dev->private; + + /* exit if not running a command, do not resubmit urb */ + if (!devpriv->ao_cmd_running) + return; + + switch (urb->status) { + case 0: + usbduxsub_ao_handle_urb(dev, s, urb); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + async->events |= COMEDI_CB_ERROR; + break; + + default: + /* a real error */ + dev_err(dev->class_dev, + "Non-zero urb status received in ao intr context: %d\n", + urb->status); + async->events |= COMEDI_CB_ERROR; + break; + } + + /* + * comedi_handle_events() cannot be used in this driver. The (*cancel) + * operation would unlink the urb. + */ + if (async->events & COMEDI_CB_CANCEL_MASK) + usbdux_ao_stop(dev, 0); + + comedi_event(dev, s); +} + +static int usbdux_submit_urbs(struct comedi_device *dev, + struct urb **urbs, int num_urbs, + int input_urb) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbdux_private *devpriv = dev->private; + struct urb *urb; + int ret; + int i; + + /* Submit all URBs and start the transfer on the bus */ + for (i = 0; i < num_urbs; i++) { + urb = urbs[i]; + + /* in case of a resubmission after an unlink... */ + if (input_urb) + urb->interval = devpriv->ai_interval; + urb->context = dev; + urb->dev = usb; + urb->status = 0; + urb->transfer_flags = URB_ISO_ASAP; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + return ret; + } + return 0; +} + +static int usbdux_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + struct usbdux_private *devpriv = dev->private; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* full speed does 1kHz scans every USB frame */ + unsigned int arg = 1000000; + unsigned int min_arg = arg; + + if (devpriv->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. + */ + int i = 1; + + /* find a power of 2 for the number of channels */ + while (i < cmd->chanlist_len) + i = i * 2; + + arg /= 8; + min_arg = arg * i; + } + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + min_arg); + /* calc the real sampling rate with the rounding errors */ + arg = (cmd->scan_begin_arg / arg) * arg; + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + return 0; +} + +/* + * creates the ADC command for the MAX1271 + * range is the range value from comedi + */ +static u8 create_adc_command(unsigned int chan, unsigned int range) +{ + u8 p = (range <= 1); + u8 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; + + mutex_lock(&devpriv->mut); + + 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: + mutex_unlock(&devpriv->mut); + 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 */ + mutex_lock(&devpriv->mut); + + if (devpriv->ai_cmd_running) + goto ai_cmd_exit; + + devpriv->dux_commands[1] = len; + for (i = 0; i < len; ++i) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + devpriv->dux_commands[i + 2] = create_adc_command(chan, range); + } + + ret = send_dux_commands(dev, USBDUX_CMD_MULT_AI); + if (ret < 0) + goto ai_cmd_exit; + + if (devpriv->high_speed) { + /* + * every channel gets a time window of 125us. Thus, if we + * sample all 8 channels we need 1ms. If we sample only one + * channel we need only 125us + */ + devpriv->ai_interval = 1; + /* find a power of 2 for the interval */ + while (devpriv->ai_interval < len) + devpriv->ai_interval *= 2; + + devpriv->ai_timer = cmd->scan_begin_arg / + (125000 * devpriv->ai_interval); + } else { + /* interval always 1ms */ + devpriv->ai_interval = 1; + devpriv->ai_timer = cmd->scan_begin_arg / 1000000; + } + if (devpriv->ai_timer < 1) { + ret = -EINVAL; + goto ai_cmd_exit; + } + + devpriv->ai_counter = devpriv->ai_timer; + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + devpriv->ai_cmd_running = 1; + ret = usbdux_submit_urbs(dev, devpriv->ai_urbs, + devpriv->n_ai_urbs, 1); + if (ret < 0) { + devpriv->ai_cmd_running = 0; + /* fixme: unlink here?? */ + goto ai_cmd_exit; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* don't enable the acquision operation */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ai_inttrig; + } + +ai_cmd_exit: + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + 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 = comedi_offset_munge(s, val); + + data[i] = val; + } + +ai_read_exit: + mutex_unlock(&devpriv->mut); + + return ret ? ret : insn->n; +} + +static int usbdux_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usbdux_private *devpriv = dev->private; + int ret; + + mutex_lock(&devpriv->mut); + ret = comedi_readback_insn_read(dev, s, insn, data); + mutex_unlock(&devpriv->mut); + + return ret; +} + +static int usbdux_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usbdux_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + __le16 *p = (__le16 *)&devpriv->dux_commands[2]; + int ret = -EBUSY; + int i; + + mutex_lock(&devpriv->mut); + + 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++) { + unsigned int val = data[i]; + + /* one 16 bit value */ + *p = cpu_to_le16(val); + + ret = send_dux_commands(dev, USBDUX_CMD_AO); + if (ret < 0) + goto ao_write_exit; + + s->readback[chan] = val; + } + +ao_write_exit: + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + 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: + mutex_unlock(&devpriv->mut); + return ret; +} + +static int usbdux_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int flags; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + + if (0) { /* (devpriv->high_speed) */ + /* the sampling rate is set by the coversion rate */ + flags = TRIG_FOLLOW; + } else { + /* start a new scan (output at once) with a timer */ + flags = TRIG_TIMER; + } + err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags); + + if (0) { /* (devpriv->high_speed) */ + /* + * in usb-2.0 only one conversion it transmitted + * but with 8kHz/n + */ + flags = TRIG_TIMER; + } else { + /* + * all conversion events happen simultaneously with + * a rate of 1kHz/n + */ + flags = TRIG_NOW; + } + err |= comedi_check_trigger_src(&cmd->convert_src, flags); + + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->scan_begin_src == TRIG_TIMER) { + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + 1000000); + } + + /* not used now, is for later use */ + if (cmd->convert_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 125000); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + return 0; +} + +static int usbdux_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct usbdux_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + int ret = -EBUSY; + + mutex_lock(&devpriv->mut); + + if (devpriv->ao_cmd_running) + goto ao_cmd_exit; + + /* we count in steps of 1ms (125us) */ + /* 125us mode not used yet */ + if (0) { /* (devpriv->high_speed) */ + /* 125us */ + /* timing of the conversion itself: every 125 us */ + devpriv->ao_timer = cmd->convert_arg / 125000; + } else { + /* 1ms */ + /* timing of the scan: we get all channels at once */ + devpriv->ao_timer = cmd->scan_begin_arg / 1000000; + if (devpriv->ao_timer < 1) { + ret = -EINVAL; + goto ao_cmd_exit; + } + } + + devpriv->ao_counter = devpriv->ao_timer; + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + devpriv->ao_cmd_running = 1; + ret = usbdux_submit_urbs(dev, devpriv->ao_urbs, + devpriv->n_ao_urbs, 0); + if (ret < 0) { + devpriv->ao_cmd_running = 0; + /* fixme: unlink here?? */ + goto ao_cmd_exit; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* submit the urbs later */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ao_inttrig; + } + +ao_cmd_exit: + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + 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: + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + 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: + mutex_unlock(&devpriv->mut); + + return ret ? ret : insn->n; +} + +static int usbdux_counter_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usbdux_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + __le16 *p = (__le16 *)&devpriv->dux_commands[2]; + int ret = 0; + int i; + + mutex_lock(&devpriv->mut); + + 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; + } + + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + /* unlink only if it is really running */ + usbdux_pwm_stop(dev, devpriv->pwm_cmd_running); + ret = send_dux_commands(dev, USBDUX_CMD_PWM_OFF); + mutex_unlock(&devpriv->mut); + + 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; + + if (period < MIN_PWM_PERIOD) + return -EAGAIN; + + fx2delay = (period / (6 * 512 * 1000 / 33)) - 6; + if (fx2delay > 255) + return -EAGAIN; + + devpriv->pwm_delay = fx2delay; + devpriv->pwm_period = period; + + return 0; +} + +static int usbdux_pwm_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbdux_private *devpriv = dev->private; + int ret = 0; + + mutex_lock(&devpriv->mut); + + 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: + mutex_unlock(&devpriv->mut); + + 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); + u8 *buf; + u8 *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; + + mutex_init(&devpriv->mut); + + usb_set_intfdata(intf, devpriv); + + devpriv->high_speed = (usb->speed == USB_SPEED_HIGH); + if (devpriv->high_speed) { + devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH; + devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH; + devpriv->pwm_buf_sz = 512; + } else { + devpriv->n_ai_urbs = NUMOFINBUFFERSFULL; + devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL; + } + + ret = usbdux_alloc_usb_buffers(dev); + if (ret) + return ret; + + /* setting to alternate setting 3: enabling iso ep and bulk ep. */ + ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber, + 3); + if (ret < 0) { + dev_err(dev->class_dev, + "could not set alternate setting 3 in high speed\n"); + return ret; + } + + ret = comedi_load_firmware(dev, &usb->dev, USBDUX_FIRMWARE, + usbdux_firmware_upload, 0); + if (ret < 0) + return ret; + + ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 5 : 4); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + s->n_chan = 8; + s->maxdata = 0x0fff; + s->len_chanlist = 8; + s->range_table = &range_usbdux_ai_range; + s->insn_read = usbdux_ai_insn_read; + s->do_cmdtest = usbdux_ai_cmdtest; + s->do_cmd = usbdux_ai_cmd; + s->cancel = usbdux_ai_cancel; + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + dev->write_subdev = s; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = 4; + s->maxdata = 0x0fff; + s->len_chanlist = s->n_chan; + s->range_table = &range_usbdux_ao_range; + s->do_cmdtest = usbdux_ao_cmdtest; + s->do_cmd = usbdux_ao_cmd; + s->cancel = usbdux_ao_cancel; + s->insn_read = usbdux_ao_insn_read; + s->insn_write = usbdux_ao_insn_write; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = usbdux_dio_insn_bits; + s->insn_config = usbdux_dio_insn_config; + + /* Counter subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->insn_read = usbdux_counter_read; + s->insn_write = usbdux_counter_write; + s->insn_config = usbdux_counter_config; + + if (devpriv->high_speed) { + /* PWM subdevice */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE; + s->n_chan = 8; + s->maxdata = devpriv->pwm_buf_sz; + s->insn_write = usbdux_pwm_write; + s->insn_config = usbdux_pwm_config; + + usbdux_pwm_period(dev, s, PWM_DEFAULT_PERIOD); + } + + return 0; +} + +static void usbdux_detach(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct usbdux_private *devpriv = dev->private; + + usb_set_intfdata(intf, NULL); + + if (!devpriv) + return; + + mutex_lock(&devpriv->mut); + + /* force unlink all urbs */ + usbdux_pwm_stop(dev, 1); + usbdux_ao_stop(dev, 1); + usbdux_ai_stop(dev, 1); + + usbdux_free_usb_buffers(dev); + + mutex_unlock(&devpriv->mut); + + mutex_destroy(&devpriv->mut); +} + +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/comedi/drivers/usbduxfast.c b/drivers/comedi/drivers/usbduxfast.c new file mode 100644 index 000000000..39faae0ec --- /dev/null +++ b/drivers/comedi/drivers/usbduxfast.c @@ -0,0 +1,1039 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2004-2019 Bernd Porr, mail@berndporr.me.uk + */ + +/* + * Driver: usbduxfast + * Description: University of Stirling USB DAQ & INCITE Technology Limited + * Devices: [ITL] USB-DUX-FAST (usbduxfast) + * Author: Bernd Porr <mail@berndporr.me.uk> + * Updated: 16 Nov 2019 + * Status: stable + */ + +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Bernd Porr + * + * + * Revision history: + * 1.0: Fixed a rounding error in usbduxfast_ai_cmdtest + * 0.9: Dropping the first data packet which seems to be from the last transfer. + * Buffer overflows in the FX2 are handed over to comedi. + * 0.92: Dropping now 4 packets. The quad buffer has to be emptied. + * Added insn command basically for testing. Sample rate is + * 1MHz/16ch=62.5kHz + * 0.99: Ian Abbott pointed out a bug which has been corrected. Thanks! + * 0.99a: added external trigger. + * 1.00: added firmware kernel request to the driver which fixed + * udev coldplug problem + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> +#include <linux/comedi/comedi_usb.h> + +/* + * timeout for the USB-transfer + */ +#define EZTIMEOUT 30 + +/* + * constants for "firmware" upload and download + */ +#define FIRMWARE "usbduxfast_firmware.bin" +#define FIRMWARE_MAX_LEN 0x2000 +#define USBDUXFASTSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +/* + * internal addresses of the 8051 processor + */ +#define USBDUXFASTSUB_CPUCS 0xE600 + +/* + * max length 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(s16)) + +/* + * 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 */ + u8 *duxbuf; + s8 *inbuf; + short int ai_cmd_running; /* asynchronous command is running */ + int ignore; /* counter which ignores the first buffers */ + struct mutex mut; +}; + +/* + * 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, + u8 len, u8 op, u8 out, u8 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; + + mutex_lock(&devpriv->mut); + ret = usbduxfast_ai_stop(dev, 1); + mutex_unlock(&devpriv->mut); + + return ret; +} + +static void usbduxfast_ai_handle_urb(struct comedi_device *dev, + struct comedi_subdevice *s, + struct urb *urb) +{ + struct usbduxfast_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int ret; + + if (devpriv->ignore) { + devpriv->ignore--; + } else { + unsigned int nsamples; + + nsamples = comedi_bytes_to_samples(s, urb->actual_length); + nsamples = comedi_nsamples_left(s, nsamples); + comedi_buf_write_samples(s, urb->transfer_buffer, nsamples); + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + } + + /* if command is still running, resubmit urb for BULK transfer */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + urb->dev = comedi_to_usb_dev(dev); + urb->status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(dev->class_dev, "urb resubm failed: %d", ret); + async->events |= COMEDI_CB_ERROR; + } + } +} + +static void usbduxfast_ai_interrupt(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct usbduxfast_private *devpriv = dev->private; + + /* exit if not running a command, do not resubmit urb */ + if (!devpriv->ai_cmd_running) + return; + + switch (urb->status) { + case 0: + usbduxfast_ai_handle_urb(dev, s, urb); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + async->events |= COMEDI_CB_ERROR; + break; + + default: + /* a real error */ + dev_err(dev->class_dev, + "non-zero urb status received in ai intr context: %d\n", + urb->status); + async->events |= COMEDI_CB_ERROR; + break; + } + + /* + * comedi_handle_events() cannot be used in this driver. The (*cancel) + * operation would unlink the urb. + */ + if (async->events & COMEDI_CB_CANCEL_MASK) + usbduxfast_ai_stop(dev, 0); + + comedi_event(dev, s); +} + +static int usbduxfast_submit_urb(struct comedi_device *dev) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbduxfast_private *devpriv = dev->private; + int ret; + + 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_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int gain0 = CR_RANGE(cmd->chanlist[0]); + int i; + + if (cmd->chanlist_len > 3 && cmd->chanlist_len != 16) { + dev_err(dev->class_dev, "unsupported combination of channels\n"); + return -EINVAL; + } + + for (i = 0; i < cmd->chanlist_len; ++i) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int gain = CR_RANGE(cmd->chanlist[i]); + + if (chan != i) { + dev_err(dev->class_dev, + "channels are not consecutive\n"); + return -EINVAL; + } + if (gain != gain0 && cmd->chanlist_len > 3) { + dev_err(dev->class_dev, + "gain must be the same for all channels\n"); + return -EINVAL; + } + } + return 0; +} + +static int usbduxfast_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int err2 = 0; + unsigned int steps; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, + TRIG_NOW | TRIG_EXT | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (!cmd->chanlist_len) + err |= -EINVAL; + + /* external start trigger is only valid for 1 or 16 channels */ + if (cmd->start_src == TRIG_EXT && + cmd->chanlist_len != 1 && cmd->chanlist_len != 16) + err |= -EINVAL; + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + /* + * Validate the conversion timing: + * for 1 channel the timing in 30MHz "steps" is: + * steps <= MAX_SAMPLING_PERIOD + * for all other chanlist_len it is: + * MIN_SAMPLING_PERIOD <= steps <= MAX_SAMPLING_PERIOD + */ + steps = (cmd->convert_arg * 30) / 1000; + if (cmd->chanlist_len != 1) + err2 |= comedi_check_trigger_arg_min(&steps, + MIN_SAMPLING_PERIOD); + else + err2 |= comedi_check_trigger_arg_min(&steps, 1); + err2 |= comedi_check_trigger_arg_max(&steps, MAX_SAMPLING_PERIOD); + if (err2) { + err |= err2; + arg = (steps * 1000) / 30; + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + /* Step 5: check channel list if it exists */ + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= usbduxfast_ai_check_chanlist(dev, s, cmd); + if (err) + return 5; + + 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 (trig_num != cmd->start_arg) + return -EINVAL; + + mutex_lock(&devpriv->mut); + + 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; + mutex_unlock(&devpriv->mut); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(dev->class_dev, "ai is already running\n"); + } + mutex_unlock(&devpriv->mut); + 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 rngmask = 0xff; + int j, ret; + long steps, steps_tmp; + + mutex_lock(&devpriv->mut); + if (devpriv->ai_cmd_running) { + ret = -EBUSY; + goto cmd_exit; + } + + /* + * ignore the first buffers from the device if there + * is an error condition + */ + devpriv->ignore = PACKETS_TO_IGNORE; + + steps = (cmd->convert_arg * 30) / 1000; + + switch (cmd->chanlist_len) { + case 1: + /* + * one channel + */ + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* + * for external trigger: looping in this state until + * the RDY0 pin becomes zero + */ + + /* we loop here until ready has been set */ + if (cmd->start_src == TRIG_EXT) { + /* branch back to state 0 */ + /* deceision state w/o data */ + /* RDY0 = 0 */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x01, rngmask, 0x00); + } else { /* we just proceed to state 1 */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x00, rngmask, 0x00); + } + + if (steps < MIN_SAMPLING_PERIOD) { + /* for fast single channel aqu without mux */ + if (steps <= 1) { + /* + * we just stay here at state 1 and rexecute + * the same state this gives us 30MHz sampling + * rate + */ + + /* branch back to state 1 */ + /* deceision state with data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 1, + 0x89, 0x03, rngmask, 0xff); + } else { + /* + * we loop through two states: data and delay + * max rate is 15MHz + */ + /* data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 1, steps - 1, + 0x02, rngmask, 0x00); + + /* branch back to state 1 */ + /* deceision state w/o data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 2, + 0x09, 0x01, rngmask, 0xff); + } + } else { + /* + * we loop through 3 states: 2x delay and 1x data + * this gives a min sampling rate of 60kHz + */ + + /* we have 1 state with duration 1 */ + steps = steps - 1; + + /* do the first part of the delay */ + usbduxfast_cmd_data(dev, 1, + steps / 2, 0x00, rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 2, steps - steps / 2, + 0x00, rngmask, 0x00); + + /* get the data and branch back */ + + /* branch back to state 1 */ + /* deceision state w data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 3, + 0x09, 0x03, rngmask, 0xff); + } + break; + + case 2: + /* + * two channels + * commit data to the FIFO + */ + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* data */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00); + + /* we have 1 state with duration 1: state 0 */ + steps_tmp = steps - 1; + + if (CR_RANGE(cmd->chanlist[1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the first part of the delay */ + /* count */ + usbduxfast_cmd_data(dev, 1, steps_tmp / 2, + 0x00, 0xfe & rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 2, steps_tmp - steps_tmp / 2, + 0x00, rngmask, 0x00); + + /* data */ + usbduxfast_cmd_data(dev, 3, 0x01, 0x02, rngmask, 0x00); + + /* + * we have 2 states with duration 1: step 6 and + * the IDLE state + */ + steps_tmp = steps - 2; + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the first part of the delay */ + /* reset */ + usbduxfast_cmd_data(dev, 4, steps_tmp / 2, + 0x00, (0xff - 0x02) & rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2, + 0x00, rngmask, 0x00); + + usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00); + break; + + case 3: + /* + * three channels + */ + for (j = 0; j < 1; j++) { + int index = j * 2; + + if (CR_RANGE(cmd->chanlist[j]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + /* + * commit data to the FIFO and do the first part + * of the delay + */ + /* data */ + /* no change */ + usbduxfast_cmd_data(dev, index, steps / 2, + 0x02, rngmask, 0x00); + + if (CR_RANGE(cmd->chanlist[j + 1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the second part of the delay */ + /* no data */ + /* count */ + usbduxfast_cmd_data(dev, index + 1, steps - steps / 2, + 0x00, 0xfe & rngmask, 0x00); + } + + /* 2 steps with duration 1: the idele step and step 6: */ + steps_tmp = steps - 2; + + /* commit data to the FIFO and do the first part of the delay */ + /* data */ + usbduxfast_cmd_data(dev, 4, steps_tmp / 2, + 0x02, rngmask, 0x00); + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the second part of the delay */ + /* no data */ + /* reset */ + usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2, + 0x00, (0xff - 0x02) & rngmask, 0x00); + + usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00); + break; + + case 16: + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + if (cmd->start_src == TRIG_EXT) { + /* + * we loop here until ready has been set + */ + + /* branch back to state 0 */ + /* deceision state w/o data */ + /* reset */ + /* RDY0 = 0 */ + usbduxfast_cmd_data(dev, 0, 0x01, 0x01, + (0xff - 0x02) & rngmask, 0x00); + } else { + /* + * we just proceed to state 1 + */ + + /* 30us reset pulse */ + /* reset */ + usbduxfast_cmd_data(dev, 0, 0xff, 0x00, + (0xff - 0x02) & rngmask, 0x00); + } + + /* commit data to the FIFO */ + /* data */ + usbduxfast_cmd_data(dev, 1, 0x01, 0x02, rngmask, 0x00); + + /* we have 2 states with duration 1 */ + steps = steps - 2; + + /* do the first part of the delay */ + usbduxfast_cmd_data(dev, 2, steps / 2, + 0x00, 0xfe & rngmask, 0x00); + + /* and the second part */ + usbduxfast_cmd_data(dev, 3, steps - steps / 2, + 0x00, rngmask, 0x00); + + /* branch back to state 1 */ + /* deceision state w/o data */ + /* doesn't matter */ + usbduxfast_cmd_data(dev, 4, 0x09, 0x01, rngmask, 0xff); + + break; + } + + /* 0 means that the AD commands are sent */ + ret = usbduxfast_send_cmd(dev, SENDADCOMMANDS); + if (ret < 0) + goto cmd_exit; + + 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?? */ + goto cmd_exit; + } + s->async->inttrig = NULL; + } else { /* TRIG_INT */ + s->async->inttrig = usbduxfast_ai_inttrig; + } + +cmd_exit: + mutex_unlock(&devpriv->mut); + + return ret; +} + +/* + * 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); + u8 rngmask = range ? (0xff - 0x04) : 0xff; + int i, j, n, actual_length; + int ret; + + mutex_lock(&devpriv->mut); + + if (devpriv->ai_cmd_running) { + dev_err(dev->class_dev, + "ai_insn_read not possible, async cmd is running\n"); + mutex_unlock(&devpriv->mut); + 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) { + mutex_unlock(&devpriv->mut); + 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"); + mutex_unlock(&devpriv->mut); + 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); + mutex_unlock(&devpriv->mut); + return ret; + } + n = actual_length / sizeof(u16); + if ((n % 16) != 0) { + dev_err(dev->class_dev, "insn data packet corrupted\n"); + mutex_unlock(&devpriv->mut); + return -EINVAL; + } + for (j = chan; (j < n) && (i < insn->n); j = j + 16) { + data[i] = ((u16 *)(devpriv->inbuf))[j]; + i++; + } + } + + mutex_unlock(&devpriv->mut); + + return insn->n; +} + +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); + u8 *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; + struct comedi_subdevice *s; + 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; + + mutex_init(&devpriv->mut); + 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) + 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; + + ret = comedi_alloc_subdevices(dev, 1); + 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 = 16; + s->maxdata = 0x1000; /* 12-bit + 1 overflow bit */ + s->range_table = &range_usbduxfast_ai_range; + s->insn_read = usbduxfast_ai_insn_read; + s->len_chanlist = s->n_chan; + s->do_cmdtest = usbduxfast_ai_cmdtest; + s->do_cmd = usbduxfast_ai_cmd; + s->cancel = usbduxfast_ai_cancel; + + return 0; +} + +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; + + mutex_lock(&devpriv->mut); + + usb_set_intfdata(intf, NULL); + + if (devpriv->urb) { + /* waits until a running transfer is over */ + usb_kill_urb(devpriv->urb); + + kfree(devpriv->inbuf); + usb_free_urb(devpriv->urb); + } + + kfree(devpriv->duxbuf); + + mutex_unlock(&devpriv->mut); + + mutex_destroy(&devpriv->mut); +} + +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/comedi/drivers/usbduxsigma.c b/drivers/comedi/drivers/usbduxsigma.c new file mode 100644 index 000000000..2aaeaf44f --- /dev/null +++ b/drivers/comedi/drivers/usbduxsigma.c @@ -0,0 +1,1615 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * usbduxsigma.c + * Copyright (C) 2011-2015 Bernd Porr, mail@berndporr.me.uk + */ + +/* + * Driver: usbduxsigma + * Description: University of Stirling USB DAQ & INCITE Technology Limited + * Devices: [ITL] USB-DUX-SIGMA (usbduxsigma) + * Author: Bernd Porr <mail@berndporr.me.uk> + * Updated: 20 July 2015 + * Status: stable + */ + +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Note: the raw data from the A/D converter is 24 bit big endian + * anything else is little endian to/from the dux board + * + * + * Revision history: + * 0.1: initial version + * 0.2: all basic functions implemented, digital I/O only for one port + * 0.3: proper vendor ID and driver name + * 0.4: fixed D/A voltage range + * 0.5: various bug fixes, health check at startup + * 0.6: corrected wrong input range + * 0.7: rewrite code that urb->interval is always 1 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> +#include <asm/unaligned.h> +#include <linux/comedi/comedi_usb.h> + +/* timeout for the USB-transfer in ms*/ +#define BULK_TIMEOUT 1000 + +/* constants for "firmware" upload and download */ +#define FIRMWARE "usbduxsigma_firmware.bin" +#define FIRMWARE_MAX_LEN 0x4000 +#define USBDUXSUB_FIRMWARE 0xa0 +#define VENDOR_DIR_IN 0xc0 +#define VENDOR_DIR_OUT 0x40 + +/* internal addresses of the 8051 processor */ +#define USBDUXSUB_CPUCS 0xE600 + +/* 300Hz max frequ under PWM */ +#define MIN_PWM_PERIOD ((long)(1E9 / 300)) + +/* Default PWM frequency */ +#define PWM_DEFAULT_PERIOD ((long)(1E9 / 100)) + +/* Number of channels (16 AD and offset)*/ +#define NUMCHANNELS 16 + +/* Size of one A/D value */ +#define SIZEADIN ((sizeof(u32))) + +/* + * 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(u8) + 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 9 +#define USBDUXSIGMA_DA_CMD 1 +#define USBDUXSIGMA_DIO_CFG_CMD 2 +#define USBDUXSIGMA_DIO_BITS_CMD 3 +#define USBDUXSIGMA_SINGLE_AD_CMD 4 +#define USBDUXSIGMA_PWM_ON_CMD 7 +#define USBDUXSIGMA_PWM_OFF_CMD 8 + +static const struct comedi_lrange usbduxsigma_ai_range = { + 1, { + BIP_RANGE(2.5 * 0x800000 / 0x780000 / 2.0) + } +}; + +struct usbduxsigma_private { + /* actual number of in-buffers */ + int n_ai_urbs; + /* actual number of out-buffers */ + int n_ao_urbs; + /* ISO-transfer handling: buffers */ + struct urb **ai_urbs; + struct urb **ao_urbs; + /* pwm-transfer handling */ + struct urb *pwm_urb; + /* PWM period */ + unsigned int pwm_period; + /* PWM internal delay for the GPIF in the FX2 */ + u8 pwm_delay; + /* size of the PWM buffer which holds the bit pattern */ + int pwm_buf_sz; + /* input buffer for the ISO-transfer */ + __be32 *in_buf; + /* input buffer for single insn */ + u8 *insn_buf; + + unsigned high_speed:1; + unsigned ai_cmd_running:1; + unsigned ao_cmd_running:1; + unsigned pwm_cmd_running:1; + + /* time between samples in units of the timer */ + unsigned int ai_timer; + unsigned int ao_timer; + /* counter between acquisitions */ + unsigned int ai_counter; + unsigned int ao_counter; + /* interval in frames/uframes */ + unsigned int ai_interval; + /* commands */ + u8 *dux_commands; + struct mutex mut; +}; + +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; + + mutex_lock(&devpriv->mut); + /* unlink only if it is really running */ + usbduxsigma_ai_stop(dev, devpriv->ai_cmd_running); + mutex_unlock(&devpriv->mut); + + return 0; +} + +static void usbduxsigma_ai_handle_urb(struct comedi_device *dev, + struct comedi_subdevice *s, + struct urb *urb) +{ + struct usbduxsigma_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u32 val; + int ret; + int i; + + if ((urb->actual_length > 0) && (urb->status != -EXDEV)) { + devpriv->ai_counter--; + if (devpriv->ai_counter == 0) { + devpriv->ai_counter = devpriv->ai_timer; + + /* + * Get the data from the USB bus and hand it over + * to comedi. Note, first byte is the DIO state. + */ + for (i = 0; i < cmd->chanlist_len; i++) { + val = be32_to_cpu(devpriv->in_buf[i + 1]); + val &= 0x00ffffff; /* strip status byte */ + val = comedi_offset_munge(s, val); + if (!comedi_buf_write_samples(s, &val, 1)) + return; + } + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + } + } + + /* if command is still running, resubmit urb */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + urb->dev = comedi_to_usb_dev(dev); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(dev->class_dev, "urb resubmit failed (%d)\n", + ret); + if (ret == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler\n"); + async->events |= COMEDI_CB_ERROR; + } + } +} + +static void usbduxsigma_ai_urb_complete(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct usbduxsigma_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + + /* exit if not running a command, do not resubmit urb */ + if (!devpriv->ai_cmd_running) + return; + + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(devpriv->in_buf, urb->transfer_buffer, SIZEINBUF); + usbduxsigma_ai_handle_urb(dev, s, urb); + break; + + case -EILSEQ: + /* + * error in the ISOchronous data + * we don't copy the data into the transfer buffer + * and recycle the last data byte + */ + dev_dbg(dev->class_dev, "CRC error in ISO IN stream\n"); + usbduxsigma_ai_handle_urb(dev, s, urb); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + async->events |= COMEDI_CB_ERROR; + break; + + default: + /* a real error */ + dev_err(dev->class_dev, "non-zero urb status (%d)\n", + urb->status); + async->events |= COMEDI_CB_ERROR; + break; + } + + /* + * comedi_handle_events() cannot be used in this driver. The (*cancel) + * operation would unlink the urb. + */ + if (async->events & COMEDI_CB_CANCEL_MASK) + usbduxsigma_ai_stop(dev, 0); + + comedi_event(dev, s); +} + +static void usbduxsigma_ao_stop(struct comedi_device *dev, int do_unlink) +{ + struct usbduxsigma_private *devpriv = dev->private; + + if (do_unlink && devpriv->ao_urbs) + usbduxsigma_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs); + + devpriv->ao_cmd_running = 0; +} + +static int usbduxsigma_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsigma_private *devpriv = dev->private; + + mutex_lock(&devpriv->mut); + /* unlink only if it is really running */ + usbduxsigma_ao_stop(dev, devpriv->ao_cmd_running); + mutex_unlock(&devpriv->mut); + + return 0; +} + +static void usbduxsigma_ao_handle_urb(struct comedi_device *dev, + struct comedi_subdevice *s, + struct urb *urb) +{ + struct usbduxsigma_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + u8 *datap; + int ret; + int i; + + devpriv->ao_counter--; + if (devpriv->ao_counter == 0) { + devpriv->ao_counter = devpriv->ao_timer; + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + return; + } + + /* transmit data to the USB bus */ + datap = urb->transfer_buffer; + *datap++ = cmd->chanlist_len; + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned short val; + + if (!comedi_buf_read_samples(s, &val, 1)) { + dev_err(dev->class_dev, "buffer underflow\n"); + async->events |= COMEDI_CB_OVERFLOW; + return; + } + + *datap++ = val; + *datap++ = chan; + s->readback[chan] = val; + } + } + + /* if command is still running, resubmit urb */ + if (!(async->events & COMEDI_CB_CANCEL_MASK)) { + urb->transfer_buffer_length = SIZEOUTBUF; + urb->dev = comedi_to_usb_dev(dev); + urb->status = 0; + urb->interval = 1; /* (u)frames */ + urb->number_of_packets = 1; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = SIZEOUTBUF; + urb->iso_frame_desc[0].status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(dev->class_dev, "urb resubmit failed (%d)\n", + ret); + if (ret == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler\n"); + async->events |= COMEDI_CB_ERROR; + } + } +} + +static void usbduxsigma_ao_urb_complete(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct usbduxsigma_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_async *async = s->async; + + /* exit if not running a command, do not resubmit urb */ + if (!devpriv->ao_cmd_running) + return; + + switch (urb->status) { + case 0: + usbduxsigma_ao_handle_urb(dev, s, urb); + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + async->events |= COMEDI_CB_ERROR; + break; + + default: + /* a real error */ + dev_err(dev->class_dev, "non-zero urb status (%d)\n", + urb->status); + async->events |= COMEDI_CB_ERROR; + break; + } + + /* + * comedi_handle_events() cannot be used in this driver. The (*cancel) + * operation would unlink the urb. + */ + if (async->events & COMEDI_CB_CANCEL_MASK) + usbduxsigma_ao_stop(dev, 0); + + comedi_event(dev, s); +} + +static int usbduxsigma_submit_urbs(struct comedi_device *dev, + struct urb **urbs, int num_urbs, + int input_urb) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct 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 = 1; + 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); + unsigned int tmp; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + if (high_speed) { + /* + * In high speed mode microframes are possible. + * However, during one microframe we can roughly + * sample two channels. Thus, the more channels + * are in the channel list the more time we need. + */ + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + (125000 * interval)); + } else { + /* full speed */ + /* 1kHz scans every USB frame */ + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, + 1000000); + } + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + tmp = rounddown(cmd->scan_begin_arg, high_speed ? 125000 : 1000000); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, tmp); + + 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, + u8 *muxsg0, u8 *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; + + mutex_lock(&devpriv->mut); + 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; + mutex_unlock(&devpriv->mut); + return ret; + } + s->async->inttrig = NULL; + } + mutex_unlock(&devpriv->mut); + + 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; + u8 muxsg0 = 0; + u8 muxsg1 = 0; + u8 sysred = 0; + int ret; + int i; + + mutex_lock(&devpriv->mut); + + if (devpriv->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 + */ + unsigned int interval = usbduxsigma_chans_to_interval(len); + + 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; + } + + for (i = 0; i < len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + + create_adc_command(chan, &muxsg0, &muxsg1); + } + + devpriv->dux_commands[1] = devpriv->ai_interval; + devpriv->dux_commands[2] = len; /* num channels per time step */ + devpriv->dux_commands[3] = 0x12; /* CONFIG0 */ + devpriv->dux_commands[4] = 0x03; /* CONFIG1: 23kHz sample, delay 0us */ + devpriv->dux_commands[5] = 0x00; /* CONFIG3: diff. channels off */ + devpriv->dux_commands[6] = muxsg0; + devpriv->dux_commands[7] = muxsg1; + devpriv->dux_commands[8] = sysred; + + ret = usbbuxsigma_send_cmd(dev, USBBUXSIGMA_AD_CMD); + if (ret < 0) { + mutex_unlock(&devpriv->mut); + 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; + mutex_unlock(&devpriv->mut); + return ret; + } + s->async->inttrig = NULL; + } else { /* TRIG_INT */ + s->async->inttrig = usbduxsigma_ai_inttrig; + } + + mutex_unlock(&devpriv->mut); + + 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); + u8 muxsg0 = 0; + u8 muxsg1 = 0; + u8 sysred = 0; + int ret; + int i; + + mutex_lock(&devpriv->mut); + if (devpriv->ai_cmd_running) { + mutex_unlock(&devpriv->mut); + 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) { + mutex_unlock(&devpriv->mut); + return ret; + } + + for (i = 0; i < insn->n; i++) { + u32 val; + + ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD); + if (ret < 0) { + mutex_unlock(&devpriv->mut); + return ret; + } + + /* 32 bits big endian from the A/D converter */ + val = be32_to_cpu(get_unaligned((__be32 + *)(devpriv->insn_buf + 1))); + val &= 0x00ffffff; /* strip status byte */ + data[i] = comedi_offset_munge(s, val); + } + mutex_unlock(&devpriv->mut); + + return insn->n; +} + +static int usbduxsigma_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usbduxsigma_private *devpriv = dev->private; + int ret; + + mutex_lock(&devpriv->mut); + ret = comedi_readback_insn_read(dev, s, insn, data); + mutex_unlock(&devpriv->mut); + + return ret; +} + +static int usbduxsigma_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usbduxsigma_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int ret; + int i; + + mutex_lock(&devpriv->mut); + if (devpriv->ao_cmd_running) { + mutex_unlock(&devpriv->mut); + 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) { + mutex_unlock(&devpriv->mut); + return ret; + } + s->readback[chan] = data[i]; + } + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + 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; + mutex_unlock(&devpriv->mut); + return ret; + } + s->async->inttrig = NULL; + } + mutex_unlock(&devpriv->mut); + + 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; + unsigned int tmp; + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + + /* + * For now, always use "scan" timing with all channels updated at once + * (cmd->scan_begin_src == TRIG_TIMER, cmd->convert_src == TRIG_NOW). + * + * In a future version, "convert" timing with channels updated + * indivually may be supported in high speed mode + * (cmd->scan_begin_src == TRIG_FOLLOW, cmd->convert_src == TRIG_TIMER). + */ + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) { + mutex_unlock(&devpriv->mut); + return 1; + } + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + + err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 1000000); + + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + tmp = rounddown(cmd->scan_begin_arg, 1000000); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, tmp); + + 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; + + mutex_lock(&devpriv->mut); + + /* + * For now, only "scan" timing is supported. A future version may + * support "convert" timing in high speed mode. + * + * Timing of the scan: every 1ms all channels updated at once. + */ + devpriv->ao_timer = cmd->scan_begin_arg / 1000000; + + 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; + mutex_unlock(&devpriv->mut); + return ret; + } + s->async->inttrig = NULL; + } else { /* TRIG_INT */ + s->async->inttrig = usbduxsigma_ao_inttrig; + } + + mutex_unlock(&devpriv->mut); + + 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; + + mutex_lock(&devpriv->mut); + + 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: + mutex_unlock(&devpriv->mut); + + return ret; +} + +static void usbduxsigma_pwm_stop(struct comedi_device *dev, int do_unlink) +{ + struct usbduxsigma_private *devpriv = dev->private; + + if (do_unlink) { + if (devpriv->pwm_urb) + usb_kill_urb(devpriv->pwm_urb); + } + + devpriv->pwm_cmd_running = 0; +} + +static int usbduxsigma_pwm_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsigma_private *devpriv = dev->private; + + /* unlink only if it is really running */ + usbduxsigma_pwm_stop(dev, devpriv->pwm_cmd_running); + + return usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_OFF_CMD); +} + +static void usbduxsigma_pwm_urb_complete(struct urb *urb) +{ + struct comedi_device *dev = urb->context; + struct usbduxsigma_private *devpriv = dev->private; + int ret; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (devpriv->pwm_cmd_running) + usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */ + return; + + default: + /* a real error */ + if (devpriv->pwm_cmd_running) { + dev_err(dev->class_dev, "non-zero urb status (%d)\n", + urb->status); + usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */ + } + return; + } + + if (!devpriv->pwm_cmd_running) + return; + + urb->transfer_buffer_length = devpriv->pwm_buf_sz; + urb->dev = comedi_to_usb_dev(dev); + urb->status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(dev->class_dev, "urb resubmit failed (%d)\n", ret); + if (ret == -EL2NSYNC) + dev_err(dev->class_dev, + "buggy USB host controller or bug in IRQ handler\n"); + usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */ + } +} + +static int usbduxsigma_submit_pwm_urb(struct comedi_device *dev) +{ + struct usb_device *usb = comedi_to_usb_dev(dev); + struct usbduxsigma_private *devpriv = dev->private; + struct urb *urb = devpriv->pwm_urb; + + /* in case of a resubmission after an unlink... */ + usb_fill_bulk_urb(urb, usb, usb_sndbulkpipe(usb, 4), + urb->transfer_buffer, devpriv->pwm_buf_sz, + usbduxsigma_pwm_urb_complete, dev); + + return usb_submit_urb(urb, GFP_ATOMIC); +} + +static int usbduxsigma_pwm_period(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int period) +{ + struct usbduxsigma_private *devpriv = dev->private; + int fx2delay; + + if (period < MIN_PWM_PERIOD) + return -EAGAIN; + + fx2delay = (period / (6 * 512 * 1000 / 33)) - 6; + if (fx2delay > 255) + return -EAGAIN; + + devpriv->pwm_delay = fx2delay; + devpriv->pwm_period = period; + return 0; +} + +static int usbduxsigma_pwm_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsigma_private *devpriv = dev->private; + int ret; + + if (devpriv->pwm_cmd_running) + return 0; + + devpriv->dux_commands[1] = devpriv->pwm_delay; + ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_ON_CMD); + if (ret < 0) + return ret; + + memset(devpriv->pwm_urb->transfer_buffer, 0, devpriv->pwm_buf_sz); + + devpriv->pwm_cmd_running = 1; + ret = usbduxsigma_submit_pwm_urb(dev); + if (ret < 0) { + devpriv->pwm_cmd_running = 0; + return ret; + } + + return 0; +} + +static void usbduxsigma_pwm_pattern(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, + unsigned int value, + unsigned int sign) +{ + struct usbduxsigma_private *devpriv = dev->private; + char pwm_mask = (1 << chan); /* DIO bit for the PWM data */ + char sgn_mask = (16 << chan); /* DIO bit for the sign */ + char *buf = (char *)(devpriv->pwm_urb->transfer_buffer); + int szbuf = devpriv->pwm_buf_sz; + int i; + + for (i = 0; i < szbuf; i++) { + char c = *buf; + + c &= ~pwm_mask; + if (i < value) + c |= pwm_mask; + if (!sign) + c &= ~sgn_mask; + else + c |= sgn_mask; + *buf++ = c; + } +} + +static int usbduxsigma_pwm_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + + /* + * It doesn't make sense to support more than one value here + * because it would just overwrite the PWM buffer. + */ + if (insn->n != 1) + return -EINVAL; + + /* + * The sign is set via a special INSN only, this gives us 8 bits + * for normal operation, sign is 0 by default. + */ + usbduxsigma_pwm_pattern(dev, s, chan, data[0], 0); + + return insn->n; +} + +static int usbduxsigma_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct usbduxsigma_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case INSN_CONFIG_ARM: + /* + * if not zero the PWM is limited to a certain time which is + * not supported here + */ + if (data[1] != 0) + return -EINVAL; + return usbduxsigma_pwm_start(dev, s); + case INSN_CONFIG_DISARM: + return usbduxsigma_pwm_cancel(dev, s); + case INSN_CONFIG_GET_PWM_STATUS: + data[1] = devpriv->pwm_cmd_running; + return 0; + case INSN_CONFIG_PWM_SET_PERIOD: + return usbduxsigma_pwm_period(dev, s, data[1]); + case INSN_CONFIG_PWM_GET_PERIOD: + data[1] = devpriv->pwm_period; + return 0; + case INSN_CONFIG_PWM_SET_H_BRIDGE: + /* + * data[1] = value + * data[2] = sign (for a relay) + */ + usbduxsigma_pwm_pattern(dev, s, chan, data[1], (data[2] != 0)); + return 0; + case INSN_CONFIG_PWM_GET_H_BRIDGE: + /* values are not kept in this driver, nothing to return */ + return -EINVAL; + } + return -EINVAL; +} + +static int usbduxsigma_getstatusinfo(struct comedi_device *dev, int chan) +{ + struct comedi_subdevice *s = dev->read_subdev; + struct usbduxsigma_private *devpriv = dev->private; + u8 sysred; + u32 val; + int ret; + + switch (chan) { + default: + case 0: + sysred = 0; /* ADC zero */ + break; + case 1: + sysred = 1; /* ADC offset */ + break; + case 2: + sysred = 4; /* VCC */ + break; + case 3: + sysred = 8; /* temperature */ + break; + case 4: + sysred = 16; /* gain */ + break; + case 5: + sysred = 32; /* ref */ + break; + } + + devpriv->dux_commands[1] = 0x12; /* CONFIG0 */ + devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */ + devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */ + devpriv->dux_commands[4] = 0; + devpriv->dux_commands[5] = 0; + devpriv->dux_commands[6] = sysred; + ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD); + if (ret < 0) + return ret; + + ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD); + if (ret < 0) + return ret; + + /* 32 bits big endian from the A/D converter */ + val = be32_to_cpu(get_unaligned((__be32 *)(devpriv->insn_buf + 1))); + val &= 0x00ffffff; /* strip status byte */ + + return (int)comedi_offset_munge(s, 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); + u8 *buf; + u8 *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; + urb->interval = 1; /* (u)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; + + mutex_init(&devpriv->mut); + + usb_set_intfdata(intf, devpriv); + + devpriv->high_speed = (usb->speed == USB_SPEED_HIGH); + if (devpriv->high_speed) { + devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH; + devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH; + devpriv->pwm_buf_sz = 512; + } else { + devpriv->n_ai_urbs = NUMOFINBUFFERSFULL; + devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL; + } + + ret = usbduxsigma_alloc_usb_buffers(dev); + if (ret) + return ret; + + /* setting to alternate setting 3: enabling iso ep and bulk ep. */ + ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber, + 3); + if (ret < 0) { + dev_err(dev->class_dev, + "could not set alternate setting 3 in high speed\n"); + return ret; + } + + ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE, + usbduxsigma_firmware_upload, 0); + if (ret) + return ret; + + ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 4 : 3); + if (ret) + return ret; + + /* Analog Input subdevice */ + s = &dev->subdevices[0]; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ | SDF_LSAMPL; + s->n_chan = NUMCHANNELS; + s->len_chanlist = NUMCHANNELS; + s->maxdata = 0x00ffffff; + s->range_table = &usbduxsigma_ai_range; + s->insn_read = usbduxsigma_ai_insn_read; + s->do_cmdtest = usbduxsigma_ai_cmdtest; + s->do_cmd = usbduxsigma_ai_cmd; + s->cancel = usbduxsigma_ai_cancel; + + /* Analog Output subdevice */ + s = &dev->subdevices[1]; + dev->write_subdev = s; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = 4; + s->len_chanlist = s->n_chan; + s->maxdata = 0x00ff; + s->range_table = &range_unipolar2_5; + s->insn_write = usbduxsigma_ao_insn_write; + s->insn_read = usbduxsigma_ao_insn_read; + s->do_cmdtest = usbduxsigma_ao_cmdtest; + s->do_cmd = usbduxsigma_ao_cmd; + s->cancel = usbduxsigma_ao_cancel; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* Digital I/O subdevice */ + s = &dev->subdevices[2]; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = usbduxsigma_dio_insn_bits; + s->insn_config = usbduxsigma_dio_insn_config; + + if (devpriv->high_speed) { + /* Timer / pwm subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE; + s->n_chan = 8; + s->maxdata = devpriv->pwm_buf_sz; + s->insn_write = usbduxsigma_pwm_write; + s->insn_config = usbduxsigma_pwm_config; + + usbduxsigma_pwm_period(dev, s, PWM_DEFAULT_PERIOD); + } + + offset = usbduxsigma_getstatusinfo(dev, 0); + if (offset < 0) { + dev_err(dev->class_dev, + "Communication to USBDUXSIGMA failed! Check firmware and cabling.\n"); + return offset; + } + + dev_info(dev->class_dev, "ADC_zero = %x\n", offset); + + return 0; +} + +static void usbduxsigma_detach(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct usbduxsigma_private *devpriv = dev->private; + + usb_set_intfdata(intf, NULL); + + if (!devpriv) + return; + + mutex_lock(&devpriv->mut); + + /* force unlink all urbs */ + usbduxsigma_ai_stop(dev, 1); + usbduxsigma_ao_stop(dev, 1); + usbduxsigma_pwm_stop(dev, 1); + + usbduxsigma_free_usb_buffers(dev); + + mutex_unlock(&devpriv->mut); + + mutex_destroy(&devpriv->mut); +} + +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, mail@berndporr.me.uk"); +MODULE_DESCRIPTION("Stirling/ITL USB-DUX SIGMA -- mail@berndporr.me.uk"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FIRMWARE); diff --git a/drivers/comedi/drivers/vmk80xx.c b/drivers/comedi/drivers/vmk80xx.c new file mode 100644 index 000000000..4536ed43f --- /dev/null +++ b/drivers/comedi/drivers/vmk80xx.c @@ -0,0 +1,881 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +/* + * Driver: vmk80xx + * Description: Velleman USB Board Low-Level Driver + * Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140), + * VM110 (K8055/VM110), VM140 (K8061/VM140) + * Author: Manuel Gebele <forensixs@gmx.de> + * Updated: Sun, 10 May 2009 11:14:59 +0200 + * Status: works + * + * Supports: + * - analog input + * - analog output + * - digital input + * - digital output + * - counter + * - pwm + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/uaccess.h> +#include <linux/comedi/comedi_usb.h> + +enum { + DEVICE_VMK8055, + DEVICE_VMK8061 +}; + +#define VMK8055_DI_REG 0x00 +#define VMK8055_DO_REG 0x01 +#define VMK8055_AO1_REG 0x02 +#define VMK8055_AO2_REG 0x03 +#define VMK8055_AI1_REG 0x02 +#define VMK8055_AI2_REG 0x03 +#define VMK8055_CNT1_REG 0x04 +#define VMK8055_CNT2_REG 0x06 + +#define VMK8061_CH_REG 0x01 +#define VMK8061_DI_REG 0x01 +#define VMK8061_DO_REG 0x01 +#define VMK8061_PWM_REG1 0x01 +#define VMK8061_PWM_REG2 0x02 +#define VMK8061_CNT_REG 0x02 +#define VMK8061_AO_REG 0x02 +#define VMK8061_AI_REG1 0x02 +#define VMK8061_AI_REG2 0x03 + +#define VMK8055_CMD_RST 0x00 +#define VMK8055_CMD_DEB1_TIME 0x01 +#define VMK8055_CMD_DEB2_TIME 0x02 +#define VMK8055_CMD_RST_CNT1 0x03 +#define VMK8055_CMD_RST_CNT2 0x04 +#define VMK8055_CMD_WRT_AD 0x05 + +#define VMK8061_CMD_RD_AI 0x00 +#define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */ +#define VMK8061_CMD_SET_AO 0x02 +#define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */ +#define VMK8061_CMD_OUT_PWM 0x04 +#define VMK8061_CMD_RD_DI 0x05 +#define VMK8061_CMD_DO 0x06 /* !non-active! */ +#define VMK8061_CMD_CLR_DO 0x07 +#define VMK8061_CMD_SET_DO 0x08 +#define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */ +#define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */ +#define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */ +#define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */ +#define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */ +#define VMK8061_CMD_RD_DO 0x0e +#define VMK8061_CMD_RD_AO 0x0f +#define VMK8061_CMD_RD_PWM 0x10 + +#define IC3_VERSION BIT(0) +#define IC6_VERSION BIT(1) + +#define MIN_BUF_SIZE 64 +#define PACKET_TIMEOUT 10000 /* ms */ + +enum vmk80xx_model { + VMK8055_MODEL, + VMK8061_MODEL +}; + +static const struct comedi_lrange vmk8061_range = { + 2, { + UNI_RANGE(5), + UNI_RANGE(10) + } +}; + +struct vmk80xx_board { + const char *name; + enum vmk80xx_model model; + const struct comedi_lrange *range; + int ai_nchans; + unsigned int ai_maxdata; + int ao_nchans; + int di_nchans; + unsigned int cnt_maxdata; + int pwm_nchans; + unsigned int pwm_maxdata; +}; + +static const struct vmk80xx_board vmk80xx_boardinfo[] = { + [DEVICE_VMK8055] = { + .name = "K8055 (VM110)", + .model = VMK8055_MODEL, + .range = &range_unipolar5, + .ai_nchans = 2, + .ai_maxdata = 0x00ff, + .ao_nchans = 2, + .di_nchans = 6, + .cnt_maxdata = 0xffff, + }, + [DEVICE_VMK8061] = { + .name = "K8061 (VM140)", + .model = VMK8061_MODEL, + .range = &vmk8061_range, + .ai_nchans = 8, + .ai_maxdata = 0x03ff, + .ao_nchans = 8, + .di_nchans = 8, + .cnt_maxdata = 0, /* unknown, device is not writeable */ + .pwm_nchans = 1, + .pwm_maxdata = 0x03ff, + }, +}; + +struct vmk80xx_private { + struct usb_endpoint_descriptor *ep_rx; + struct usb_endpoint_descriptor *ep_tx; + struct semaphore limit_sem; + unsigned char *usb_rx_buf; + unsigned char *usb_tx_buf; + enum vmk80xx_model model; +}; + +static void vmk80xx_do_bulk_msg(struct comedi_device *dev) +{ + struct vmk80xx_private *devpriv = dev->private; + struct usb_device *usb = comedi_to_usb_dev(dev); + __u8 tx_addr; + __u8 rx_addr; + unsigned int tx_pipe; + unsigned int rx_pipe; + size_t tx_size; + size_t rx_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); + tx_size = usb_endpoint_maxp(devpriv->ep_tx); + rx_size = usb_endpoint_maxp(devpriv->ep_rx); + + usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, tx_size, NULL, + PACKET_TIMEOUT); + + usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, rx_size, NULL, + PACKET_TIMEOUT); +} + +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, + usb_endpoint_maxp(ep), NULL, + PACKET_TIMEOUT); +} + +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, + usb_endpoint_maxp(ep), NULL, + PACKET_TIMEOUT); +} + +static int vmk80xx_reset_device(struct comedi_device *dev) +{ + struct vmk80xx_private *devpriv = dev->private; + size_t size; + int retval; + + size = usb_endpoint_maxp(devpriv->ep_tx); + memset(devpriv->usb_tx_buf, 0, size); + retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST); + if (retval) + return retval; + /* set outputs to known state as we cannot read them */ + return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD); +} + +static int vmk80xx_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + int chan; + int reg[2]; + int n; + + down(&devpriv->limit_sem); + chan = CR_CHAN(insn->chanspec); + + switch (devpriv->model) { + case VMK8055_MODEL: + if (!chan) + reg[0] = VMK8055_AI1_REG; + else + reg[0] = VMK8055_AI2_REG; + break; + case VMK8061_MODEL: + default: + reg[0] = VMK8061_AI_REG1; + reg[1] = VMK8061_AI_REG2; + devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI; + devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; + break; + } + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + if (devpriv->model == VMK8055_MODEL) { + data[n] = devpriv->usb_rx_buf[reg[0]]; + continue; + } + + /* VMK8061_MODEL */ + data[n] = devpriv->usb_rx_buf[reg[0]] + 256 * + devpriv->usb_rx_buf[reg[1]]; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + int chan; + int cmd; + int reg; + int n; + + down(&devpriv->limit_sem); + chan = CR_CHAN(insn->chanspec); + + switch (devpriv->model) { + case VMK8055_MODEL: + cmd = VMK8055_CMD_WRT_AD; + if (!chan) + reg = VMK8055_AO1_REG; + else + reg = VMK8055_AO2_REG; + break; + default: /* NOTE: avoid compiler warnings */ + cmd = VMK8061_CMD_SET_AO; + reg = VMK8061_AO_REG; + devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; + break; + } + + for (n = 0; n < insn->n; n++) { + devpriv->usb_tx_buf[reg] = data[n]; + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + int chan; + int reg; + int n; + + down(&devpriv->limit_sem); + chan = CR_CHAN(insn->chanspec); + + reg = VMK8061_AO_REG - 1; + + devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO; + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + data[n] = devpriv->usb_rx_buf[reg + chan]; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + unsigned char *rx_buf; + int reg; + int retval; + + down(&devpriv->limit_sem); + + rx_buf = devpriv->usb_rx_buf; + + if (devpriv->model == VMK8061_MODEL) { + reg = VMK8061_DI_REG; + devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI; + } else { + reg = VMK8055_DI_REG; + } + + retval = vmk80xx_read_packet(dev); + + if (!retval) { + if (devpriv->model == VMK8055_MODEL) + data[1] = (((rx_buf[reg] >> 4) & 0x03) | + ((rx_buf[reg] << 2) & 0x04) | + ((rx_buf[reg] >> 3) & 0x18)); + else + data[1] = rx_buf[reg]; + + retval = 2; + } + + up(&devpriv->limit_sem); + + return retval; +} + +static int vmk80xx_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + unsigned char *rx_buf = devpriv->usb_rx_buf; + unsigned char *tx_buf = devpriv->usb_tx_buf; + int reg, cmd; + int ret = 0; + + if (devpriv->model == VMK8061_MODEL) { + reg = VMK8061_DO_REG; + cmd = VMK8061_CMD_DO; + } else { /* VMK8055_MODEL */ + reg = VMK8055_DO_REG; + cmd = VMK8055_CMD_WRT_AD; + } + + down(&devpriv->limit_sem); + + if (comedi_dio_update_state(s, data)) { + tx_buf[reg] = s->state; + ret = vmk80xx_write_packet(dev, cmd); + if (ret) + goto out; + } + + if (devpriv->model == VMK8061_MODEL) { + tx_buf[0] = VMK8061_CMD_RD_DO; + ret = vmk80xx_read_packet(dev); + if (ret) + goto out; + data[1] = rx_buf[reg]; + } else { + data[1] = s->state; + } + +out: + up(&devpriv->limit_sem); + + return ret ? ret : insn->n; +} + +static int vmk80xx_cnt_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + int chan; + int reg[2]; + int n; + + down(&devpriv->limit_sem); + chan = CR_CHAN(insn->chanspec); + + switch (devpriv->model) { + case VMK8055_MODEL: + if (!chan) + reg[0] = VMK8055_CNT1_REG; + else + reg[0] = VMK8055_CNT2_REG; + break; + case VMK8061_MODEL: + default: + reg[0] = VMK8061_CNT_REG; + reg[1] = VMK8061_CNT_REG; + devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; + break; + } + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + if (devpriv->model == VMK8055_MODEL) + data[n] = devpriv->usb_rx_buf[reg[0]]; + else /* VMK8061_MODEL */ + data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1] + + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2]; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_cnt_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + int cmd; + int reg; + int ret; + + down(&devpriv->limit_sem); + switch (data[0]) { + case INSN_CONFIG_RESET: + if (devpriv->model == VMK8055_MODEL) { + if (!chan) { + cmd = VMK8055_CMD_RST_CNT1; + reg = VMK8055_CNT1_REG; + } else { + cmd = VMK8055_CMD_RST_CNT2; + reg = VMK8055_CNT2_REG; + } + devpriv->usb_tx_buf[reg] = 0x00; + } else { + cmd = VMK8061_CMD_RST_CNT; + } + ret = vmk80xx_write_packet(dev, cmd); + break; + default: + ret = -EINVAL; + break; + } + up(&devpriv->limit_sem); + + return ret ? ret : insn->n; +} + +static int vmk80xx_cnt_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + unsigned long debtime; + unsigned long val; + int chan; + int cmd; + int n; + + down(&devpriv->limit_sem); + chan = CR_CHAN(insn->chanspec); + + if (!chan) + cmd = VMK8055_CMD_DEB1_TIME; + else + cmd = VMK8055_CMD_DEB2_TIME; + + for (n = 0; n < insn->n; n++) { + debtime = data[n]; + if (debtime == 0) + debtime = 1; + + /* TODO: Prevent overflows */ + if (debtime > 7450) + debtime = 7450; + + val = int_sqrt(debtime * 1000 / 115); + if (((val + 1) * val) < debtime * 1000 / 115) + val += 1; + + devpriv->usb_tx_buf[6 + chan] = val; + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_pwm_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + unsigned char *tx_buf; + unsigned char *rx_buf; + int reg[2]; + int n; + + down(&devpriv->limit_sem); + + tx_buf = devpriv->usb_tx_buf; + rx_buf = devpriv->usb_rx_buf; + + reg[0] = VMK8061_PWM_REG1; + reg[1] = VMK8061_PWM_REG2; + + tx_buf[0] = VMK8061_CMD_RD_PWM; + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]]; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_pwm_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct vmk80xx_private *devpriv = dev->private; + unsigned char *tx_buf; + int reg[2]; + int cmd; + int n; + + down(&devpriv->limit_sem); + + tx_buf = devpriv->usb_tx_buf; + + reg[0] = VMK8061_PWM_REG1; + reg[1] = VMK8061_PWM_REG2; + + cmd = VMK8061_CMD_OUT_PWM; + + /* + * The followin piece of code was translated from the inline + * assembler code in the DLL source code. + * + * asm + * mov eax, k ; k is the value (data[n]) + * and al, 03h ; al are the lower 8 bits of eax + * mov lo, al ; lo is the low part (tx_buf[reg[0]]) + * mov eax, k + * shr eax, 2 ; right shift eax register by 2 + * mov hi, al ; hi is the high part (tx_buf[reg[1]]) + * end; + */ + for (n = 0; n < insn->n; n++) { + tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03); + tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff; + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&devpriv->limit_sem); + + return n; +} + +static int vmk80xx_find_usb_endpoints(struct comedi_device *dev) +{ + struct vmk80xx_private *devpriv = dev->private; + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct usb_host_interface *iface_desc = intf->cur_altsetting; + struct usb_endpoint_descriptor *ep_desc; + int i; + + if (iface_desc->desc.bNumEndpoints != 2) + return -ENODEV; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + ep_desc = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep_desc) || + usb_endpoint_is_bulk_in(ep_desc)) { + if (!devpriv->ep_rx) + devpriv->ep_rx = ep_desc; + continue; + } + + if (usb_endpoint_is_int_out(ep_desc) || + usb_endpoint_is_bulk_out(ep_desc)) { + if (!devpriv->ep_tx) + devpriv->ep_tx = ep_desc; + continue; + } + } + + if (!devpriv->ep_rx || !devpriv->ep_tx) + return -ENODEV; + + if (!usb_endpoint_maxp(devpriv->ep_rx) || !usb_endpoint_maxp(devpriv->ep_tx)) + return -EINVAL; + + return 0; +} + +static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev) +{ + struct vmk80xx_private *devpriv = dev->private; + size_t size; + + size = max(usb_endpoint_maxp(devpriv->ep_rx), MIN_BUF_SIZE); + devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL); + if (!devpriv->usb_rx_buf) + return -ENOMEM; + + size = max(usb_endpoint_maxp(devpriv->ep_tx), MIN_BUF_SIZE); + devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL); + if (!devpriv->usb_tx_buf) + return -ENOMEM; + + return 0; +} + +static int vmk80xx_init_subdevices(struct comedi_device *dev) +{ + const struct vmk80xx_board *board = dev->board_ptr; + struct vmk80xx_private *devpriv = dev->private; + struct comedi_subdevice *s; + int n_subd; + int ret; + + down(&devpriv->limit_sem); + + if (devpriv->model == VMK8055_MODEL) + n_subd = 5; + else + n_subd = 6; + ret = comedi_alloc_subdevices(dev, n_subd); + if (ret) { + up(&devpriv->limit_sem); + return ret; + } + + /* Analog input subdevice */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = board->ai_nchans; + s->maxdata = board->ai_maxdata; + s->range_table = board->range; + s->insn_read = vmk80xx_ai_insn_read; + + /* Analog output subdevice */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = board->ao_nchans; + s->maxdata = 0x00ff; + s->range_table = board->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 = board->di_nchans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = vmk80xx_di_insn_bits; + + /* Digital output subdevice */ + s = &dev->subdevices[3]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = vmk80xx_do_insn_bits; + + /* Counter subdevice */ + s = &dev->subdevices[4]; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE; + s->n_chan = 2; + s->maxdata = board->cnt_maxdata; + s->insn_read = vmk80xx_cnt_insn_read; + s->insn_config = vmk80xx_cnt_insn_config; + if (devpriv->model == VMK8055_MODEL) { + s->subdev_flags |= SDF_WRITABLE; + s->insn_write = vmk80xx_cnt_insn_write; + } + + /* PWM subdevice */ + if (devpriv->model == VMK8061_MODEL) { + s = &dev->subdevices[5]; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = board->pwm_nchans; + s->maxdata = board->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 *board = NULL; + struct vmk80xx_private *devpriv; + int ret; + + if (context < ARRAY_SIZE(vmk80xx_boardinfo)) + board = &vmk80xx_boardinfo[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; + + devpriv->model = board->model; + + sema_init(&devpriv->limit_sem, 8); + + ret = vmk80xx_find_usb_endpoints(dev); + if (ret) + return ret; + + ret = vmk80xx_alloc_usb_buffers(dev); + if (ret) + return ret; + + usb_set_intfdata(intf, devpriv); + + if (devpriv->model == VMK8055_MODEL) + vmk80xx_reset_device(dev); + + return vmk80xx_init_subdevices(dev); +} + +static void vmk80xx_detach(struct comedi_device *dev) +{ + struct usb_interface *intf = comedi_to_usb_interface(dev); + struct vmk80xx_private *devpriv = dev->private; + + if (!devpriv) + return; + + down(&devpriv->limit_sem); + + usb_set_intfdata(intf, NULL); + + kfree(devpriv->usb_rx_buf); + kfree(devpriv->usb_tx_buf); + + up(&devpriv->limit_sem); +} + +static struct comedi_driver vmk80xx_driver = { + .module = THIS_MODULE, + .driver_name = "vmk80xx", + .auto_attach = vmk80xx_auto_attach, + .detach = vmk80xx_detach, +}; + +static int vmk80xx_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info); +} + +static const struct usb_device_id vmk80xx_usb_id_table[] = { + { USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 }, + { USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 }, + { USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 }, + { USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 }, + { USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 }, + { USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 }, + { } +}; +MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table); + +static struct usb_driver vmk80xx_usb_driver = { + .name = "vmk80xx", + .id_table = vmk80xx_usb_id_table, + .probe = vmk80xx_usb_probe, + .disconnect = comedi_usb_auto_unconfig, +}; +module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver); + +MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>"); +MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/comedi/drivers/z8536.h b/drivers/comedi/drivers/z8536.h new file mode 100644 index 000000000..3ef5f9e79 --- /dev/null +++ b/drivers/comedi/drivers/z8536.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Z8536 CIO Internal registers + */ + +#ifndef _Z8536_H +#define _Z8536_H + +/* Master Interrupt Control register */ +#define Z8536_INT_CTRL_REG 0x00 +#define Z8536_INT_CTRL_MIE BIT(7) /* Master Interrupt Enable */ +#define Z8536_INT_CTRL_DLC BIT(6) /* Disable Lower Chain */ +#define Z8536_INT_CTRL_NV BIT(5) /* No Vector */ +#define Z8536_INT_CTRL_PA_VIS BIT(4) /* Port A Vect Inc Status */ +#define Z8536_INT_CTRL_PB_VIS BIT(3) /* Port B Vect Inc Status */ +#define Z8536_INT_CTRL_VT_VIS BIT(2) /* C/T Vect Inc Status */ +#define Z8536_INT_CTRL_RJA BIT(1) /* Right Justified Addresses */ +#define Z8536_INT_CTRL_RESET BIT(0) /* Reset */ + +/* Master Configuration Control register */ +#define Z8536_CFG_CTRL_REG 0x01 +#define Z8536_CFG_CTRL_PBE BIT(7) /* Port B Enable */ +#define Z8536_CFG_CTRL_CT1E BIT(6) /* C/T 1 Enable */ +#define Z8536_CFG_CTRL_CT2E BIT(5) /* C/T 2 Enable */ +#define Z8536_CFG_CTRL_PCE_CT3E BIT(4) /* Port C & C/T 3 Enable */ +#define Z8536_CFG_CTRL_PLC BIT(3) /* Port A/B Link Control */ +#define Z8536_CFG_CTRL_PAE BIT(2) /* Port A Enable */ +#define Z8536_CFG_CTRL_LC(x) (((x) & 0x3) << 0) /* Link Control */ +#define Z8536_CFG_CTRL_LC_INDEP Z8536_CFG_CTRL_LC(0)/* Independent */ +#define Z8536_CFG_CTRL_LC_GATE Z8536_CFG_CTRL_LC(1)/* 1 Gates 2 */ +#define Z8536_CFG_CTRL_LC_TRIG Z8536_CFG_CTRL_LC(2)/* 1 Triggers 2 */ +#define Z8536_CFG_CTRL_LC_CLK Z8536_CFG_CTRL_LC(3)/* 1 Clocks 2 */ +#define Z8536_CFG_CTRL_LC_MASK Z8536_CFG_CTRL_LC(3) + +/* Interrupt Vector registers */ +#define Z8536_PA_INT_VECT_REG 0x02 +#define Z8536_PB_INT_VECT_REG 0x03 +#define Z8536_CT_INT_VECT_REG 0x04 +#define Z8536_CURR_INT_VECT_REG 0x1f + +/* Port A/B & Counter/Timer 1/2/3 Command and Status registers */ +#define Z8536_PA_CMDSTAT_REG 0x08 +#define Z8536_PB_CMDSTAT_REG 0x09 +#define Z8536_CT1_CMDSTAT_REG 0x0a +#define Z8536_CT2_CMDSTAT_REG 0x0b +#define Z8536_CT3_CMDSTAT_REG 0x0c +#define Z8536_CT_CMDSTAT_REG(x) (0x0a + (x)) +#define Z8536_CMD(x) (((x) & 0x7) << 5) +#define Z8536_CMD_NULL Z8536_CMD(0) /* Null Code */ +#define Z8536_CMD_CLR_IP_IUS Z8536_CMD(1) /* Clear IP & IUS */ +#define Z8536_CMD_SET_IUS Z8536_CMD(2) /* Set IUS */ +#define Z8536_CMD_CLR_IUS Z8536_CMD(3) /* Clear IUS */ +#define Z8536_CMD_SET_IP Z8536_CMD(4) /* Set IP */ +#define Z8536_CMD_CLR_IP Z8536_CMD(5) /* Clear IP */ +#define Z8536_CMD_SET_IE Z8536_CMD(6) /* Set IE */ +#define Z8536_CMD_CLR_IE Z8536_CMD(7) /* Clear IE */ +#define Z8536_CMD_MASK Z8536_CMD(7) + +#define Z8536_STAT_IUS BIT(7) /* Interrupt Under Service */ +#define Z8536_STAT_IE BIT(6) /* Interrupt Enable */ +#define Z8536_STAT_IP BIT(5) /* Interrupt Pending */ +#define Z8536_STAT_ERR BIT(4) /* Interrupt Error */ +#define Z8536_STAT_IE_IP (Z8536_STAT_IE | Z8536_STAT_IP) + +#define Z8536_PAB_STAT_ORE BIT(3) /* Output Register Empty */ +#define Z8536_PAB_STAT_IRF BIT(2) /* Input Register Full */ +#define Z8536_PAB_STAT_PMF BIT(1) /* Pattern Match Flag */ +#define Z8536_PAB_CMDSTAT_IOE BIT(0) /* Interrupt On Error */ + +#define Z8536_CT_CMD_RCC BIT(3) /* Read Counter Control */ +#define Z8536_CT_CMDSTAT_GCB BIT(2) /* Gate Command Bit */ +#define Z8536_CT_CMD_TCB BIT(1) /* Trigger Command Bit */ +#define Z8536_CT_STAT_CIP BIT(0) /* Count In Progress */ + +/* Port Data registers */ +#define Z8536_PA_DATA_REG 0x0d +#define Z8536_PB_DATA_REG 0x0e +#define Z8536_PC_DATA_REG 0x0f + +/* Counter/Timer 1/2/3 Current Count registers */ +#define Z8536_CT1_VAL_MSB_REG 0x10 +#define Z8536_CT1_VAL_LSB_REG 0x11 +#define Z8536_CT2_VAL_MSB_REG 0x12 +#define Z8536_CT2_VAL_LSB_REG 0x13 +#define Z8536_CT3_VAL_MSB_REG 0x14 +#define Z8536_CT3_VAL_LSB_REG 0x15 +#define Z8536_CT_VAL_MSB_REG(x) (0x10 + ((x) * 2)) +#define Z8536_CT_VAL_LSB_REG(x) (0x11 + ((x) * 2)) + +/* Counter/Timer 1/2/3 Time Constant registers */ +#define Z8536_CT1_RELOAD_MSB_REG 0x16 +#define Z8536_CT1_RELOAD_LSB_REG 0x17 +#define Z8536_CT2_RELOAD_MSB_REG 0x18 +#define Z8536_CT2_RELOAD_LSB_REG 0x19 +#define Z8536_CT3_RELOAD_MSB_REG 0x1a +#define Z8536_CT3_RELOAD_LSB_REG 0x1b +#define Z8536_CT_RELOAD_MSB_REG(x) (0x16 + ((x) * 2)) +#define Z8536_CT_RELOAD_LSB_REG(x) (0x17 + ((x) * 2)) + +/* Counter/Timer 1/2/3 Mode Specification registers */ +#define Z8536_CT1_MODE_REG 0x1c +#define Z8536_CT2_MODE_REG 0x1d +#define Z8536_CT3_MODE_REG 0x1e +#define Z8536_CT_MODE_REG(x) (0x1c + (x)) +#define Z8536_CT_MODE_CSC BIT(7) /* Continuous/Single Cycle */ +#define Z8536_CT_MODE_EOE BIT(6) /* External Output Enable */ +#define Z8536_CT_MODE_ECE BIT(5) /* External Count Enable */ +#define Z8536_CT_MODE_ETE BIT(4) /* External Trigger Enable */ +#define Z8536_CT_MODE_EGE BIT(3) /* External Gate Enable */ +#define Z8536_CT_MODE_REB BIT(2) /* Retrigger Enable Bit */ +#define Z8536_CT_MODE_DCS(x) (((x) & 0x3) << 0) /* Duty Cycle */ +#define Z8536_CT_MODE_DCS_PULSE Z8536_CT_MODE_DCS(0) /* Pulse */ +#define Z8536_CT_MODE_DCS_ONESHOT Z8536_CT_MODE_DCS(1) /* One-Shot */ +#define Z8536_CT_MODE_DCS_SQRWAVE Z8536_CT_MODE_DCS(2) /* Square Wave */ +#define Z8536_CT_MODE_DCS_DO_NOT_USE Z8536_CT_MODE_DCS(3) /* Do Not Use */ +#define Z8536_CT_MODE_DCS_MASK Z8536_CT_MODE_DCS(3) + +/* Port A/B Mode Specification registers */ +#define Z8536_PA_MODE_REG 0x20 +#define Z8536_PB_MODE_REG 0x28 +#define Z8536_PAB_MODE_PTS(x) (((x) & 0x3) << 6) /* Port type */ +#define Z8536_PAB_MODE_PTS_BIT Z8536_PAB_MODE_PTS(0 << 6)/* Bit */ +#define Z8536_PAB_MODE_PTS_INPUT Z8536_PAB_MODE_PTS(1 << 6)/* Input */ +#define Z8536_PAB_MODE_PTS_OUTPUT Z8536_PAB_MODE_PTS(2 << 6)/* Output */ +#define Z8536_PAB_MODE_PTS_BIDIR Z8536_PAB_MODE_PTS(3 << 6)/* Bidir */ +#define Z8536_PAB_MODE_PTS_MASK Z8536_PAB_MODE_PTS(3 << 6) +#define Z8536_PAB_MODE_ITB BIT(5) /* Interrupt on Two Bytes */ +#define Z8536_PAB_MODE_SB BIT(4) /* Single Buffered mode */ +#define Z8536_PAB_MODE_IMO BIT(3) /* Interrupt on Match Only */ +#define Z8536_PAB_MODE_PMS(x) (((x) & 0x3) << 1) /* Pattern Mode */ +#define Z8536_PAB_MODE_PMS_DISABLE Z8536_PAB_MODE_PMS(0)/* Disabled */ +#define Z8536_PAB_MODE_PMS_AND Z8536_PAB_MODE_PMS(1)/* "AND" */ +#define Z8536_PAB_MODE_PMS_OR Z8536_PAB_MODE_PMS(2)/* "OR" */ +#define Z8536_PAB_MODE_PMS_OR_PEV Z8536_PAB_MODE_PMS(3)/* "OR-Priority" */ +#define Z8536_PAB_MODE_PMS_MASK Z8536_PAB_MODE_PMS(3) +#define Z8536_PAB_MODE_LPM BIT(0) /* Latch on Pattern Match */ +#define Z8536_PAB_MODE_DTE BIT(0) /* Deskew Timer Enabled */ + +/* Port A/B Handshake Specification registers */ +#define Z8536_PA_HANDSHAKE_REG 0x21 +#define Z8536_PB_HANDSHAKE_REG 0x29 +#define Z8536_PAB_HANDSHAKE_HST(x) (((x) & 0x3) << 6) /* Handshake Type */ +#define Z8536_PAB_HANDSHAKE_HST_INTER Z8536_PAB_HANDSHAKE_HST(0)/*Interlock*/ +#define Z8536_PAB_HANDSHAKE_HST_STROBED Z8536_PAB_HANDSHAKE_HST(1)/* Strobed */ +#define Z8536_PAB_HANDSHAKE_HST_PULSED Z8536_PAB_HANDSHAKE_HST(2)/* Pulsed */ +#define Z8536_PAB_HANDSHAKE_HST_3WIRE Z8536_PAB_HANDSHAKE_HST(3)/* 3-Wire */ +#define Z8536_PAB_HANDSHAKE_HST_MASK Z8536_PAB_HANDSHAKE_HST(3) +#define Z8536_PAB_HANDSHAKE_RWS(x) (((x) & 0x7) << 3) /* Req/Wait */ +#define Z8536_PAB_HANDSHAKE_RWS_DISABLE Z8536_PAB_HANDSHAKE_RWS(0)/* Disabled */ +#define Z8536_PAB_HANDSHAKE_RWS_OUTWAIT Z8536_PAB_HANDSHAKE_RWS(1)/* Out Wait */ +#define Z8536_PAB_HANDSHAKE_RWS_INWAIT Z8536_PAB_HANDSHAKE_RWS(3)/* In Wait */ +#define Z8536_PAB_HANDSHAKE_RWS_SPREQ Z8536_PAB_HANDSHAKE_RWS(4)/* Special */ +#define Z8536_PAB_HANDSHAKE_RWS_OUTREQ Z8536_PAB_HANDSHAKE_RWS(5)/* Out Req */ +#define Z8536_PAB_HANDSHAKE_RWS_INREQ Z8536_PAB_HANDSHAKE_RWS(7)/* In Req */ +#define Z8536_PAB_HANDSHAKE_RWS_MASK Z8536_PAB_HANDSHAKE_RWS(7) +#define Z8536_PAB_HANDSHAKE_DESKEW(x) ((x) << 0)/* Deskew Time */ +#define Z8536_PAB_HANDSHAKE_DESKEW_MASK (3 << 0)/* Deskew Time mask */ + +/* + * Port A/B/C Data Path Polarity registers + * + * 0 = Non-Inverting + * 1 = Inverting + */ +#define Z8536_PA_DPP_REG 0x22 +#define Z8536_PB_DPP_REG 0x2a +#define Z8536_PC_DPP_REG 0x05 + +/* + * Port A/B/C Data Direction registers + * + * 0 = Output bit + * 1 = Input bit + */ +#define Z8536_PA_DD_REG 0x23 +#define Z8536_PB_DD_REG 0x2b +#define Z8536_PC_DD_REG 0x06 + +/* + * Port A/B/C Special I/O Control registers + * + * 0 = Normal Input or Output + * 1 = Output with open drain or Input with 1's catcher + */ +#define Z8536_PA_SIO_REG 0x24 +#define Z8536_PB_SIO_REG 0x2c +#define Z8536_PC_SIO_REG 0x07 + +/* + * Port A/B Pattern Polarity/Transition/Mask registers + * + * PM PT PP Pattern Specification + * -- -- -- ------------------------------------- + * 0 0 x Bit masked off + * 0 1 x Any transition + * 1 0 0 Zero (low-level) + * 1 0 1 One (high-level) + * 1 1 0 One-to-zero transition (falling-edge) + * 1 1 1 Zero-to-one transition (rising-edge) + */ +#define Z8536_PA_PP_REG 0x25 +#define Z8536_PB_PP_REG 0x2d + +#define Z8536_PA_PT_REG 0x26 +#define Z8536_PB_PT_REG 0x2e + +#define Z8536_PA_PM_REG 0x27 +#define Z8536_PB_PM_REG 0x2f + +#endif /* _Z8536_H */ diff --git a/drivers/comedi/kcomedilib/Makefile b/drivers/comedi/kcomedilib/Makefile new file mode 100644 index 000000000..8031142a1 --- /dev/null +++ b/drivers/comedi/kcomedilib/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG + +obj-$(CONFIG_COMEDI_KCOMEDILIB) += kcomedilib.o + +kcomedilib-objs := kcomedilib_main.o diff --git a/drivers/comedi/kcomedilib/kcomedilib_main.c b/drivers/comedi/kcomedilib/kcomedilib_main.c new file mode 100644 index 000000000..43fbe1a63 --- /dev/null +++ b/drivers/comedi/kcomedilib/kcomedilib_main.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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> + */ + +#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 <linux/comedi.h> +#include <linux/comedi/comedidev.h> +#include <linux/comedi/comedilib.h> + +MODULE_AUTHOR("David Schleef <ds@schleef.org>"); +MODULE_DESCRIPTION("Comedi kernel library"); +MODULE_LICENSE("GPL"); + +struct comedi_device *comedi_open(const char *filename) +{ + struct comedi_device *dev, *retval = NULL; + unsigned int minor; + + if (strncmp(filename, "/dev/comedi", 11) != 0) + return NULL; + + if (kstrtouint(filename + 11, 0, &minor)) + return NULL; + + if (minor >= COMEDI_NUM_BOARD_MINORS) + return NULL; + + dev = comedi_dev_get_from_minor(minor); + if (!dev) + return NULL; + + down_read(&dev->attach_lock); + if (dev->attached) + retval = dev; + else + retval = NULL; + up_read(&dev->attach_lock); + + if (!retval) + comedi_dev_put(dev); + + return retval; +} +EXPORT_SYMBOL_GPL(comedi_open); + +int comedi_close(struct comedi_device *dev) +{ + comedi_dev_put(dev); + return 0; +} +EXPORT_SYMBOL_GPL(comedi_close); + +static int comedi_do_insn(struct comedi_device *dev, + struct comedi_insn *insn, + unsigned int *data) +{ + struct comedi_subdevice *s; + int ret; + + mutex_lock(&dev->mutex); + + if (!dev->attached) { + ret = -EINVAL; + goto error; + } + + /* a subdevice instruction */ + if (insn->subdev >= dev->n_subdevices) { + ret = -EINVAL; + goto error; + } + s = &dev->subdevices[insn->subdev]; + + if (s->type == COMEDI_SUBD_UNUSED) { + dev_err(dev->class_dev, + "%d not usable subdevice\n", insn->subdev); + ret = -EIO; + goto error; + } + + /* XXX check lock */ + + ret = comedi_check_chanlist(s, 1, &insn->chanspec); + if (ret < 0) { + dev_err(dev->class_dev, "bad chanspec\n"); + ret = -EINVAL; + goto error; + } + + if (s->busy) { + ret = -EBUSY; + goto error; + } + s->busy = dev; + + switch (insn->insn) { + case INSN_BITS: + ret = s->insn_bits(dev, s, insn, data); + break; + case INSN_CONFIG: + /* XXX should check instruction length */ + ret = s->insn_config(dev, s, insn, data); + break; + default: + ret = -EINVAL; + break; + } + + s->busy = NULL; +error: + + mutex_unlock(&dev->mutex); + return ret; +} + +int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev, + unsigned int chan, unsigned int *io) +{ + struct comedi_insn insn; + unsigned int data[2]; + int ret; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_CONFIG; + insn.n = 2; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, 0, 0); + data[0] = INSN_CONFIG_DIO_QUERY; + data[1] = 0; + ret = comedi_do_insn(dev, &insn, data); + if (ret >= 0) + *io = data[1]; + return ret; +} +EXPORT_SYMBOL_GPL(comedi_dio_get_config); + +int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, + unsigned int chan, unsigned int io) +{ + struct comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_CONFIG; + insn.n = 1; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, 0, 0); + + return comedi_do_insn(dev, &insn, &io); +} +EXPORT_SYMBOL_GPL(comedi_dio_config); + +int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev, + unsigned int mask, unsigned int *bits, + unsigned int base_channel) +{ + struct comedi_insn insn; + unsigned int data[2]; + unsigned int n_chan; + unsigned int shift; + int ret; + + base_channel = CR_CHAN(base_channel); + n_chan = comedi_get_n_channels(dev, subdev); + if (base_channel >= n_chan) + return -EINVAL; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_BITS; + insn.chanspec = base_channel; + insn.n = 2; + insn.subdev = subdev; + + data[0] = mask; + data[1] = *bits; + + /* + * Most drivers ignore the base channel in insn->chanspec. + * Fix this here if the subdevice has <= 32 channels. + */ + if (n_chan <= 32) { + shift = base_channel; + if (shift) { + insn.chanspec = 0; + data[0] <<= shift; + data[1] <<= shift; + } + } else { + shift = 0; + } + + ret = comedi_do_insn(dev, &insn, data); + *bits = data[1] >> shift; + return ret; +} +EXPORT_SYMBOL_GPL(comedi_dio_bitfield2); + +int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, + unsigned int subd) +{ + struct comedi_subdevice *s; + int ret = -ENODEV; + + down_read(&dev->attach_lock); + if (dev->attached) + for (; subd < dev->n_subdevices; subd++) { + s = &dev->subdevices[subd]; + if (s->type == type) { + ret = subd; + break; + } + } + up_read(&dev->attach_lock); + return ret; +} +EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type); + +int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice) +{ + int n; + + down_read(&dev->attach_lock); + if (!dev->attached || subdevice >= dev->n_subdevices) + n = 0; + else + n = dev->subdevices[subdevice].n_chan; + up_read(&dev->attach_lock); + + return n; +} +EXPORT_SYMBOL_GPL(comedi_get_n_channels); + +static int __init kcomedilib_module_init(void) +{ + return 0; +} + +static void __exit kcomedilib_module_exit(void) +{ +} + +module_init(kcomedilib_module_init); +module_exit(kcomedilib_module_exit); diff --git a/drivers/comedi/proc.c b/drivers/comedi/proc.c new file mode 100644 index 000000000..2e4496633 --- /dev/null +++ b/drivers/comedi/proc.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * /proc interface for comedi + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998 David A. Schleef <ds@schleef.org> + */ + +/* + * This is some serious bloatware. + * + * Taken from Dave A.'s PCL-711 driver, 'cuz I thought it + * was cool. + */ + +#include <linux/comedi/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; +} + +void __init comedi_proc_init(void) +{ + if (!proc_create_single("comedi", 0444, NULL, comedi_read)) + pr_warn("comedi: unable to create proc entry\n"); +} + +void comedi_proc_cleanup(void) +{ + remove_proc_entry("comedi", NULL); +} diff --git a/drivers/comedi/range.c b/drivers/comedi/range.c new file mode 100644 index 000000000..8f43cf88d --- /dev/null +++ b/drivers/comedi/range.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * comedi/range.c + * comedi routines for voltage ranges + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> + */ + +#include <linux/uaccess.h> +#include <linux/comedi/comedidev.h> +#include "comedi_internal.h" + +const struct comedi_lrange range_bipolar10 = { 1, {BIP_RANGE(10)} }; +EXPORT_SYMBOL_GPL(range_bipolar10); +const struct comedi_lrange range_bipolar5 = { 1, {BIP_RANGE(5)} }; +EXPORT_SYMBOL_GPL(range_bipolar5); +const struct comedi_lrange range_bipolar2_5 = { 1, {BIP_RANGE(2.5)} }; +EXPORT_SYMBOL_GPL(range_bipolar2_5); +const struct comedi_lrange range_unipolar10 = { 1, {UNI_RANGE(10)} }; +EXPORT_SYMBOL_GPL(range_unipolar10); +const struct comedi_lrange range_unipolar5 = { 1, {UNI_RANGE(5)} }; +EXPORT_SYMBOL_GPL(range_unipolar5); +const struct comedi_lrange range_unipolar2_5 = { 1, {UNI_RANGE(2.5)} }; +EXPORT_SYMBOL_GPL(range_unipolar2_5); +const struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} }; +EXPORT_SYMBOL_GPL(range_0_20mA); +const struct comedi_lrange range_4_20mA = { 1, {RANGE_mA(4, 20)} }; +EXPORT_SYMBOL_GPL(range_4_20mA); +const struct comedi_lrange range_0_32mA = { 1, {RANGE_mA(0, 32)} }; +EXPORT_SYMBOL_GPL(range_0_32mA); +const struct comedi_lrange range_unknown = { 1, {{0, 1000000, UNIT_none} } }; +EXPORT_SYMBOL_GPL(range_unknown); + +/* + * COMEDI_RANGEINFO ioctl + * range information + * + * arg: + * pointer to comedi_rangeinfo structure + * + * reads: + * comedi_rangeinfo structure + * + * writes: + * array of comedi_krange structures to rangeinfo->range_ptr pointer + */ +int do_rangeinfo_ioctl(struct comedi_device *dev, + struct comedi_rangeinfo *it) +{ + int subd, chan; + const struct comedi_lrange *lr; + struct comedi_subdevice *s; + + subd = (it->range_type >> 24) & 0xf; + chan = (it->range_type >> 16) & 0xff; + + if (!dev->attached) + return -EINVAL; + if (subd >= dev->n_subdevices) + return -EINVAL; + s = &dev->subdevices[subd]; + if (s->range_table) { + lr = s->range_table; + } else if (s->range_table_list) { + if (chan >= s->n_chan) + return -EINVAL; + lr = s->range_table_list[chan]; + } else { + return -EINVAL; + } + + if (RANGE_LENGTH(it->range_type) != lr->length) { + dev_dbg(dev->class_dev, + "wrong length %d should be %d (0x%08x)\n", + RANGE_LENGTH(it->range_type), + lr->length, it->range_type); + return -EINVAL; + } + + if (copy_to_user(it->range_ptr, lr->range, + sizeof(struct comedi_krange) * lr->length)) + return -EFAULT; + + return 0; +} + +/** + * comedi_check_chanlist() - Validate each element in a chanlist. + * @s: comedi_subdevice struct + * @n: number of elements in the chanlist + * @chanlist: the chanlist to validate + * + * Each element consists of a channel number, a range index, an analog + * reference type and some flags, all packed into an unsigned int. + * + * This checks that the channel number and range index are supported by + * the comedi subdevice. It does not check whether the analog reference + * type and the flags are supported. Drivers that care should check those + * themselves. + * + * Return: %0 if all @chanlist elements are valid (success), + * %-EINVAL if one or more elements are invalid. + */ +int comedi_check_chanlist(struct comedi_subdevice *s, int n, + unsigned int *chanlist) +{ + struct comedi_device *dev = s->device; + unsigned int chanspec; + int chan, range_len, i; + + for (i = 0; i < n; i++) { + chanspec = chanlist[i]; + chan = CR_CHAN(chanspec); + if (s->range_table) + range_len = s->range_table->length; + else if (s->range_table_list && chan < s->n_chan) + range_len = s->range_table_list[chan]->length; + else + range_len = 0; + if (chan >= s->n_chan || + CR_RANGE(chanspec) >= range_len) { + dev_warn(dev->class_dev, + "bad chanlist[%d]=0x%08x chan=%d range length=%d\n", + i, chanspec, chan, range_len); + return -EINVAL; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(comedi_check_chanlist); |