VisionFive2 Linux kernel

StarFive Tech Linux Kernel for VisionFive (JH7110) boards (mirror)

More than 9999 Commits   32 Branches   54 Tags
author: Greg Kroah-Hartman <gregkh@linuxfoundation.org> 2021-04-14 10:58:10 +0200 committer: Greg Kroah-Hartman <gregkh@linuxfoundation.org> 2021-04-15 09:26:25 +0200 commit: 8ffdff6a8cfbdc174a3a390b6f825a277b5bb895 parent: 027ffa10b80be722c284067f7eee431ced1db208
Commit Summary:
staging: comedi: move out of staging directory
Diffstat:
215 files changed, 118755 insertions, 0 deletions
diff --git a/drivers/comedi/Kconfig b/drivers/comedi/Kconfig
new file mode 100644
index 000000000000..3cb61fa2c5c3
--- /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 000000000000..072ed83a5a6a
--- /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 000000000000..f733c017f181
--- /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.h b/drivers/comedi/comedi.h
new file mode 100644
index 000000000000..b5d00a006dbb
--- /dev/null
+++ b/drivers/comedi/comedi.h
@@ -0,0 +1,1528 @@
+/* SPDX-License-Identifier: LGPL-2.0+ */
+/*
+ * comedi.h
+ * header file for COMEDI user API
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _COMEDI_H
+#define _COMEDI_H
+
+#define COMEDI_MAJORVERSION	0
+#define COMEDI_MINORVERSION	7
+#define COMEDI_MICROVERSION	76
+#define VERSION	"0.7.76"
+
+/* comedi's major device number */
+#define COMEDI_MAJOR 98
+
+/*
+ * maximum number of minor devices.  This can be increased, although
+ * kernel structures are currently statically allocated, thus you
+ * don't want this to be much more than you actually use.
+ */
+#define COMEDI_NDEVICES 16
+
+/* number of config options in the config structure */
+#define COMEDI_NDEVCONFOPTS 32
+
+/*
+ * NOTE: 'comedi_config --init-data' is deprecated
+ *
+ * The following indexes in the config options were used by
+ * comedi_config to pass firmware blobs from user space to the
+ * comedi drivers. The request_firmware() hotplug interface is
+ * now used by all comedi drivers instead.
+ */
+
+/* length of nth chunk of firmware data -*/
+#define COMEDI_DEVCONF_AUX_DATA3_LENGTH		25
+#define COMEDI_DEVCONF_AUX_DATA2_LENGTH		26
+#define COMEDI_DEVCONF_AUX_DATA1_LENGTH		27
+#define COMEDI_DEVCONF_AUX_DATA0_LENGTH		28
+/* most significant 32 bits of pointer address (if needed) */
+#define COMEDI_DEVCONF_AUX_DATA_HI		29
+/* least significant 32 bits of pointer address */
+#define COMEDI_DEVCONF_AUX_DATA_LO		30
+#define COMEDI_DEVCONF_AUX_DATA_LENGTH		31	/* total data length */
+
+/* max length of device and driver names */
+#define COMEDI_NAMELEN 20
+
+/* packs and unpacks a channel/range number */
+
+#define CR_PACK(chan, rng, aref)					\
+	((((aref) & 0x3) << 24) | (((rng) & 0xff) << 16) | (chan))
+#define CR_PACK_FLAGS(chan, range, aref, flags)				\
+	(CR_PACK(chan, range, aref) | ((flags) & CR_FLAGS_MASK))
+
+#define CR_CHAN(a)	((a) & 0xffff)
+#define CR_RANGE(a)	(((a) >> 16) & 0xff)
+#define CR_AREF(a)	(((a) >> 24) & 0x03)
+
+#define CR_FLAGS_MASK	0xfc000000
+#define CR_ALT_FILTER	0x04000000
+#define CR_DITHER	CR_ALT_FILTER
+#define CR_DEGLITCH	CR_ALT_FILTER
+#define CR_ALT_SOURCE	0x08000000
+#define CR_EDGE		0x40000000
+#define CR_INVERT	0x80000000
+
+#define AREF_GROUND	0x00	/* analog ref = analog ground */
+#define AREF_COMMON	0x01	/* analog ref = analog common */
+#define AREF_DIFF	0x02	/* analog ref = differential */
+#define AREF_OTHER	0x03	/* analog ref = other (undefined) */
+
+/* counters -- these are arbitrary values */
+#define GPCT_RESET		0x0001
+#define GPCT_SET_SOURCE		0x0002
+#define GPCT_SET_GATE		0x0004
+#define GPCT_SET_DIRECTION	0x0008
+#define GPCT_SET_OPERATION	0x0010
+#define GPCT_ARM		0x0020
+#define GPCT_DISARM		0x0040
+#define GPCT_GET_INT_CLK_FRQ	0x0080
+
+#define GPCT_INT_CLOCK		0x0001
+#define GPCT_EXT_PIN		0x0002
+#define GPCT_NO_GATE		0x0004
+#define GPCT_UP			0x0008
+#define GPCT_DOWN		0x0010
+#define GPCT_HWUD		0x0020
+#define GPCT_SIMPLE_EVENT	0x0040
+#define GPCT_SINGLE_PERIOD	0x0080
+#define GPCT_SINGLE_PW		0x0100
+#define GPCT_CONT_PULSE_OUT	0x0200
+#define GPCT_SINGLE_PULSE_OUT	0x0400
+
+/* instructions */
+
+#define INSN_MASK_WRITE		0x8000000
+#define INSN_MASK_READ		0x4000000
+#define INSN_MASK_SPECIAL	0x2000000
+
+#define INSN_READ		(0 | INSN_MASK_READ)
+#define INSN_WRITE		(1 | INSN_MASK_WRITE)
+#define INSN_BITS		(2 | INSN_MASK_READ | INSN_MASK_WRITE)
+#define INSN_CONFIG		(3 | INSN_MASK_READ | INSN_MASK_WRITE)
+#define INSN_DEVICE_CONFIG	(INSN_CONFIG | INSN_MASK_SPECIAL)
+#define INSN_GTOD		(4 | INSN_MASK_READ | INSN_MASK_SPECIAL)
+#define INSN_WAIT		(5 | INSN_MASK_WRITE | INSN_MASK_SPECIAL)
+#define INSN_INTTRIG		(6 | INSN_MASK_WRITE | INSN_MASK_SPECIAL)
+
+/* command flags */
+/* These flags are used in comedi_cmd structures */
+
+#define CMDF_BOGUS		0x00000001	/* do the motions */
+
+/* try to use a real-time interrupt while performing command */
+#define CMDF_PRIORITY		0x00000008
+
+/* wake up on end-of-scan events */
+#define CMDF_WAKE_EOS		0x00000020
+
+#define CMDF_WRITE		0x00000040
+
+#define CMDF_RAWDATA		0x00000080
+
+/* timer rounding definitions */
+#define CMDF_ROUND_MASK		0x00030000
+#define CMDF_ROUND_NEAREST	0x00000000
+#define CMDF_ROUND_DOWN		0x00010000
+#define CMDF_ROUND_UP		0x00020000
+#define CMDF_ROUND_UP_NEXT	0x00030000
+
+#define COMEDI_EV_START		0x00040000
+#define COMEDI_EV_SCAN_BEGIN	0x00080000
+#define COMEDI_EV_CONVERT	0x00100000
+#define COMEDI_EV_SCAN_END	0x00200000
+#define COMEDI_EV_STOP		0x00400000
+
+/* compatibility definitions */
+#define TRIG_BOGUS		CMDF_BOGUS
+#define TRIG_RT			CMDF_PRIORITY
+#define TRIG_WAKE_EOS		CMDF_WAKE_EOS
+#define TRIG_WRITE		CMDF_WRITE
+#define TRIG_ROUND_MASK		CMDF_ROUND_MASK
+#define TRIG_ROUND_NEAREST	CMDF_ROUND_NEAREST
+#define TRIG_ROUND_DOWN		CMDF_ROUND_DOWN
+#define TRIG_ROUND_UP		CMDF_ROUND_UP
+#define TRIG_ROUND_UP_NEXT	CMDF_ROUND_UP_NEXT
+
+/* trigger sources */
+
+#define TRIG_ANY	0xffffffff
+#define TRIG_INVALID	0x00000000
+
+#define TRIG_NONE	0x00000001 /* never trigger */
+#define TRIG_NOW	0x00000002 /* trigger now + N ns */
+#define TRIG_FOLLOW	0x00000004 /* trigger on next lower level trig */
+#define TRIG_TIME	0x00000008 /* trigger at time N ns */
+#define TRIG_TIMER	0x00000010 /* trigger at rate N ns */
+#define TRIG_COUNT	0x00000020 /* trigger when count reaches N */
+#define TRIG_EXT	0x00000040 /* trigger on external signal N */
+#define TRIG_INT	0x00000080 /* trigger on comedi-internal signal N */
+#define TRIG_OTHER	0x00000100 /* driver defined */
+
+/* subdevice flags */
+
+#define SDF_BUSY	0x0001	/* device is busy */
+#define SDF_BUSY_OWNER	0x0002	/* device is busy with your job */
+#define SDF_LOCKED	0x0004	/* subdevice is locked */
+#define SDF_LOCK_OWNER	0x0008	/* you own lock */
+#define SDF_MAXDATA	0x0010	/* maxdata depends on channel */
+#define SDF_FLAGS	0x0020	/* flags depend on channel */
+#define SDF_RANGETYPE	0x0040	/* range type depends on channel */
+#define SDF_PWM_COUNTER 0x0080	/* PWM can automatically switch off */
+#define SDF_PWM_HBRIDGE 0x0100	/* PWM is signed (H-bridge) */
+#define SDF_CMD		0x1000	/* can do commands (deprecated) */
+#define SDF_SOFT_CALIBRATED	0x2000 /* subdevice uses software calibration */
+#define SDF_CMD_WRITE		0x4000 /* can do output commands */
+#define SDF_CMD_READ		0x8000 /* can do input commands */
+
+/* subdevice can be read (e.g. analog input) */
+#define SDF_READABLE	0x00010000
+/* subdevice can be written (e.g. analog output) */
+#define SDF_WRITABLE	0x00020000
+#define SDF_WRITEABLE	SDF_WRITABLE	/* spelling error in API */
+/* subdevice does not have externally visible lines */
+#define SDF_INTERNAL	0x00040000
+#define SDF_GROUND	0x00100000	/* can do aref=ground */
+#define SDF_COMMON	0x00200000	/* can do aref=common */
+#define SDF_DIFF	0x00400000	/* can do aref=diff */
+#define SDF_OTHER	0x00800000	/* can do aref=other */
+#define SDF_DITHER	0x01000000	/* can do dithering */
+#define SDF_DEGLITCH	0x02000000	/* can do deglitching */
+#define SDF_MMAP	0x04000000	/* can do mmap() */
+#define SDF_RUNNING	0x08000000	/* subdevice is acquiring data */
+#define SDF_LSAMPL	0x10000000	/* subdevice uses 32-bit samples */
+#define SDF_PACKED	0x20000000	/* subdevice can do packed DIO */
+
+/* subdevice types */
+
+/**
+ * enum comedi_subdevice_type - COMEDI subdevice types
+ * @COMEDI_SUBD_UNUSED:		Unused subdevice.
+ * @COMEDI_SUBD_AI:		Analog input.
+ * @COMEDI_SUBD_AO:		Analog output.
+ * @COMEDI_SUBD_DI:		Digital input.
+ * @COMEDI_SUBD_DO:		Digital output.
+ * @COMEDI_SUBD_DIO:		Digital input/output.
+ * @COMEDI_SUBD_COUNTER:	Counter.
+ * @COMEDI_SUBD_TIMER:		Timer.
+ * @COMEDI_SUBD_MEMORY:		Memory, EEPROM, DPRAM.
+ * @COMEDI_SUBD_CALIB:		Calibration DACs.
+ * @COMEDI_SUBD_PROC:		Processor, DSP.
+ * @COMEDI_SUBD_SERIAL:		Serial I/O.
+ * @COMEDI_SUBD_PWM:		Pulse-Width Modulation output.
+ */
+enum comedi_subdevice_type {
+	COMEDI_SUBD_UNUSED,
+	COMEDI_SUBD_AI,
+	COMEDI_SUBD_AO,
+	COMEDI_SUBD_DI,
+	COMEDI_SUBD_DO,
+	COMEDI_SUBD_DIO,
+	COMEDI_SUBD_COUNTER,
+	COMEDI_SUBD_TIMER,
+	COMEDI_SUBD_MEMORY,
+	COMEDI_SUBD_CALIB,
+	COMEDI_SUBD_PROC,
+	COMEDI_SUBD_SERIAL,
+	COMEDI_SUBD_PWM
+};
+
+/* configuration instructions */
+
+/**
+ * enum comedi_io_direction - COMEDI I/O directions
+ * @COMEDI_INPUT:	Input.
+ * @COMEDI_OUTPUT:	Output.
+ * @COMEDI_OPENDRAIN:	Open-drain (or open-collector) output.
+ *
+ * These are used by the %INSN_CONFIG_DIO_QUERY configuration instruction to
+ * report a direction.  They may also be used in other places where a direction
+ * needs to be specified.
+ */
+enum comedi_io_direction {
+	COMEDI_INPUT = 0,
+	COMEDI_OUTPUT = 1,
+	COMEDI_OPENDRAIN = 2
+};
+
+/**
+ * enum configuration_ids - COMEDI configuration instruction codes
+ * @INSN_CONFIG_DIO_INPUT:	Configure digital I/O as input.
+ * @INSN_CONFIG_DIO_OUTPUT:	Configure digital I/O as output.
+ * @INSN_CONFIG_DIO_OPENDRAIN:	Configure digital I/O as open-drain (or open
+ *				collector) output.
+ * @INSN_CONFIG_ANALOG_TRIG:	Configure analog trigger.
+ * @INSN_CONFIG_ALT_SOURCE:	Configure alternate input source.
+ * @INSN_CONFIG_DIGITAL_TRIG:	Configure digital trigger.
+ * @INSN_CONFIG_BLOCK_SIZE:	Configure block size for DMA transfers.
+ * @INSN_CONFIG_TIMER_1:	Configure divisor for external clock.
+ * @INSN_CONFIG_FILTER:		Configure a filter.
+ * @INSN_CONFIG_CHANGE_NOTIFY:	Configure change notification for digital
+ *				inputs.  (New drivers should use
+ *				%INSN_CONFIG_DIGITAL_TRIG instead.)
+ * @INSN_CONFIG_SERIAL_CLOCK:	Configure clock for serial I/O.
+ * @INSN_CONFIG_BIDIRECTIONAL_DATA: Send and receive byte over serial I/O.
+ * @INSN_CONFIG_DIO_QUERY:	Query direction of digital I/O channel.
+ * @INSN_CONFIG_PWM_OUTPUT:	Configure pulse-width modulator output.
+ * @INSN_CONFIG_GET_PWM_OUTPUT:	Get pulse-width modulator output configuration.
+ * @INSN_CONFIG_ARM:		Arm a subdevice or channel.
+ * @INSN_CONFIG_DISARM:		Disarm a subdevice or channel.
+ * @INSN_CONFIG_GET_COUNTER_STATUS: Get counter status.
+ * @INSN_CONFIG_RESET:		Reset a subdevice or channel.
+ * @INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: Configure counter/timer as
+ *				single pulse generator.
+ * @INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: Configure counter/timer as
+ *				pulse train generator.
+ * @INSN_CONFIG_GPCT_QUADRATURE_ENCODER: Configure counter as a quadrature
+ *				encoder.
+ * @INSN_CONFIG_SET_GATE_SRC:	Set counter/timer gate source.
+ * @INSN_CONFIG_GET_GATE_SRC:	Get counter/timer gate source.
+ * @INSN_CONFIG_SET_CLOCK_SRC:	Set counter/timer master clock source.
+ * @INSN_CONFIG_GET_CLOCK_SRC:	Get counter/timer master clock source.
+ * @INSN_CONFIG_SET_OTHER_SRC:	Set counter/timer "other" source.
+ * @INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: Get size (in bytes) of subdevice's
+ *				on-board FIFOs used during streaming
+ *				input/output.
+ * @INSN_CONFIG_SET_COUNTER_MODE: Set counter/timer mode.
+ * @INSN_CONFIG_8254_SET_MODE:	(Deprecated) Same as
+ *				%INSN_CONFIG_SET_COUNTER_MODE.
+ * @INSN_CONFIG_8254_READ_STATUS: Read status of 8254 counter channel.
+ * @INSN_CONFIG_SET_ROUTING:	Set routing for a channel.
+ * @INSN_CONFIG_GET_ROUTING:	Get routing for a channel.
+ * @INSN_CONFIG_PWM_SET_PERIOD: Set PWM period in nanoseconds.
+ * @INSN_CONFIG_PWM_GET_PERIOD: Get PWM period in nanoseconds.
+ * @INSN_CONFIG_GET_PWM_STATUS: Get PWM status.
+ * @INSN_CONFIG_PWM_SET_H_BRIDGE: Set PWM H bridge duty cycle and polarity for
+ *				a relay simultaneously.
+ * @INSN_CONFIG_PWM_GET_H_BRIDGE: Get PWM H bridge duty cycle and polarity.
+ * @INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS: Get the hardware timing restraints,
+ *				regardless of trigger sources.
+ */
+enum configuration_ids {
+	INSN_CONFIG_DIO_INPUT = COMEDI_INPUT,
+	INSN_CONFIG_DIO_OUTPUT = COMEDI_OUTPUT,
+	INSN_CONFIG_DIO_OPENDRAIN = COMEDI_OPENDRAIN,
+	INSN_CONFIG_ANALOG_TRIG = 16,
+/*	INSN_CONFIG_WAVEFORM = 17, */
+/*	INSN_CONFIG_TRIG = 18, */
+/*	INSN_CONFIG_COUNTER = 19, */
+	INSN_CONFIG_ALT_SOURCE = 20,
+	INSN_CONFIG_DIGITAL_TRIG = 21,
+	INSN_CONFIG_BLOCK_SIZE = 22,
+	INSN_CONFIG_TIMER_1 = 23,
+	INSN_CONFIG_FILTER = 24,
+	INSN_CONFIG_CHANGE_NOTIFY = 25,
+
+	INSN_CONFIG_SERIAL_CLOCK = 26,	/*ALPHA*/
+	INSN_CONFIG_BIDIRECTIONAL_DATA = 27,
+	INSN_CONFIG_DIO_QUERY = 28,
+	INSN_CONFIG_PWM_OUTPUT = 29,
+	INSN_CONFIG_GET_PWM_OUTPUT = 30,
+	INSN_CONFIG_ARM = 31,
+	INSN_CONFIG_DISARM = 32,
+	INSN_CONFIG_GET_COUNTER_STATUS = 33,
+	INSN_CONFIG_RESET = 34,
+	INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR = 1001,
+	INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR = 1002,
+	INSN_CONFIG_GPCT_QUADRATURE_ENCODER = 1003,
+	INSN_CONFIG_SET_GATE_SRC = 2001,
+	INSN_CONFIG_GET_GATE_SRC = 2002,
+	INSN_CONFIG_SET_CLOCK_SRC = 2003,
+	INSN_CONFIG_GET_CLOCK_SRC = 2004,
+	INSN_CONFIG_SET_OTHER_SRC = 2005,
+	INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE = 2006,
+	INSN_CONFIG_SET_COUNTER_MODE = 4097,
+	INSN_CONFIG_8254_SET_MODE = INSN_CONFIG_SET_COUNTER_MODE,
+	INSN_CONFIG_8254_READ_STATUS = 4098,
+	INSN_CONFIG_SET_ROUTING = 4099,
+	INSN_CONFIG_GET_ROUTING = 4109,
+	INSN_CONFIG_PWM_SET_PERIOD = 5000,
+	INSN_CONFIG_PWM_GET_PERIOD = 5001,
+	INSN_CONFIG_GET_PWM_STATUS = 5002,
+	INSN_CONFIG_PWM_SET_H_BRIDGE = 5003,
+	INSN_CONFIG_PWM_GET_H_BRIDGE = 5004,
+	INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS = 5005,
+};
+
+/**
+ * enum device_configuration_ids - COMEDI configuration instruction codes global
+ * to an entire device.
+ * @INSN_DEVICE_CONFIG_TEST_ROUTE:	Validate the possibility of a
+ *					globally-named route
+ * @INSN_DEVICE_CONFIG_CONNECT_ROUTE:	Connect a globally-named route
+ * @INSN_DEVICE_CONFIG_DISCONNECT_ROUTE:Disconnect a globally-named route
+ * @INSN_DEVICE_CONFIG_GET_ROUTES:	Get a list of all globally-named routes
+ *					that are valid for a particular device.
+ */
+enum device_config_route_ids {
+	INSN_DEVICE_CONFIG_TEST_ROUTE = 0,
+	INSN_DEVICE_CONFIG_CONNECT_ROUTE = 1,
+	INSN_DEVICE_CONFIG_DISCONNECT_ROUTE = 2,
+	INSN_DEVICE_CONFIG_GET_ROUTES = 3,
+};
+
+/**
+ * enum comedi_digital_trig_op - operations for configuring a digital trigger
+ * @COMEDI_DIGITAL_TRIG_DISABLE:	Return digital trigger to its default,
+ *					inactive, unconfigured state.
+ * @COMEDI_DIGITAL_TRIG_ENABLE_EDGES:	Set rising and/or falling edge inputs
+ *					that each can fire the trigger.
+ * @COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:	Set a combination of high and/or low
+ *					level inputs that can fire the trigger.
+ *
+ * These are used with the %INSN_CONFIG_DIGITAL_TRIG configuration instruction.
+ * The data for the configuration instruction is as follows...
+ *
+ *   data[%0] = %INSN_CONFIG_DIGITAL_TRIG
+ *
+ *   data[%1] = trigger ID
+ *
+ *   data[%2] = configuration operation
+ *
+ *   data[%3] = configuration parameter 1
+ *
+ *   data[%4] = configuration parameter 2
+ *
+ *   data[%5] = configuration parameter 3
+ *
+ * The trigger ID (data[%1]) is used to differentiate multiple digital triggers
+ * belonging to the same subdevice.  The configuration operation (data[%2]) is
+ * one of the enum comedi_digital_trig_op values.  The configuration
+ * parameters (data[%3], data[%4], and data[%5]) depend on the operation; they
+ * are not used with %COMEDI_DIGITAL_TRIG_DISABLE.
+ *
+ * For %COMEDI_DIGITAL_TRIG_ENABLE_EDGES and %COMEDI_DIGITAL_TRIG_ENABLE_LEVELS,
+ * configuration parameter 1 (data[%3]) contains a "left-shift" value that
+ * specifies the input corresponding to bit 0 of configuration parameters 2
+ * and 3.  This is useful if the trigger has more than 32 inputs.
+ *
+ * For %COMEDI_DIGITAL_TRIG_ENABLE_EDGES, configuration parameter 2 (data[%4])
+ * specifies which of up to 32 inputs have rising-edge sensitivity, and
+ * configuration parameter 3 (data[%5]) specifies which of up to 32 inputs
+ * have falling-edge sensitivity that can fire the trigger.
+ *
+ * For %COMEDI_DIGITAL_TRIG_ENABLE_LEVELS, configuration parameter 2 (data[%4])
+ * specifies which of up to 32 inputs must be at a high level, and
+ * configuration parameter 3 (data[%5]) specifies which of up to 32 inputs
+ * must be at a low level for the trigger to fire.
+ *
+ * Some sequences of %INSN_CONFIG_DIGITAL_TRIG instructions may have a (partly)
+ * accumulative effect, depending on the low-level driver.  This is useful
+ * when setting up a trigger that has more than 32 inputs, or has a combination
+ * of edge- and level-triggered inputs.
+ */
+enum comedi_digital_trig_op {
+	COMEDI_DIGITAL_TRIG_DISABLE = 0,
+	COMEDI_DIGITAL_TRIG_ENABLE_EDGES = 1,
+	COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = 2
+};
+
+/**
+ * enum comedi_support_level - support level for a COMEDI feature
+ * @COMEDI_UNKNOWN_SUPPORT:	Unspecified support for feature.
+ * @COMEDI_SUPPORTED:		Feature is supported.
+ * @COMEDI_UNSUPPORTED:		Feature is unsupported.
+ */
+enum comedi_support_level {
+	COMEDI_UNKNOWN_SUPPORT = 0,
+	COMEDI_SUPPORTED,
+	COMEDI_UNSUPPORTED
+};
+
+/**
+ * enum comedi_counter_status_flags - counter status bits
+ * @COMEDI_COUNTER_ARMED:		Counter is armed.
+ * @COMEDI_COUNTER_COUNTING:		Counter is counting.
+ * @COMEDI_COUNTER_TERMINAL_COUNT:	Counter reached terminal count.
+ *
+ * These bitwise values are used by the %INSN_CONFIG_GET_COUNTER_STATUS
+ * configuration instruction to report the status of a counter.
+ */
+enum comedi_counter_status_flags {
+	COMEDI_COUNTER_ARMED = 0x1,
+	COMEDI_COUNTER_COUNTING = 0x2,
+	COMEDI_COUNTER_TERMINAL_COUNT = 0x4,
+};
+
+/* ioctls */
+
+#define CIO 'd'
+#define COMEDI_DEVCONFIG _IOW(CIO, 0, struct comedi_devconfig)
+#define COMEDI_DEVINFO _IOR(CIO, 1, struct comedi_devinfo)
+#define COMEDI_SUBDINFO _IOR(CIO, 2, struct comedi_subdinfo)
+#define COMEDI_CHANINFO _IOR(CIO, 3, struct comedi_chaninfo)
+/* _IOWR(CIO, 4, ...) is reserved */
+#define COMEDI_LOCK _IO(CIO, 5)
+#define COMEDI_UNLOCK _IO(CIO, 6)
+#define COMEDI_CANCEL _IO(CIO, 7)
+#define COMEDI_RANGEINFO _IOR(CIO, 8, struct comedi_rangeinfo)
+#define COMEDI_CMD _IOR(CIO, 9, struct comedi_cmd)
+#define COMEDI_CMDTEST _IOR(CIO, 10, struct comedi_cmd)
+#define COMEDI_INSNLIST _IOR(CIO, 11, struct comedi_insnlist)
+#define COMEDI_INSN _IOR(CIO, 12, struct comedi_insn)
+#define COMEDI_BUFCONFIG _IOR(CIO, 13, struct comedi_bufconfig)
+#define COMEDI_BUFINFO _IOWR(CIO, 14, struct comedi_bufinfo)
+#define COMEDI_POLL _IO(CIO, 15)
+#define COMEDI_SETRSUBD _IO(CIO, 16)
+#define COMEDI_SETWSUBD _IO(CIO, 17)
+
+/* structures */
+
+/**
+ * struct comedi_insn - COMEDI instruction
+ * @insn:	COMEDI instruction type (%INSN_xxx).
+ * @n:		Length of @data[].
+ * @data:	Pointer to data array operated on by the instruction.
+ * @subdev:	Subdevice index.
+ * @chanspec:	A packed "chanspec" value consisting of channel number,
+ *		analog range index, analog reference type, and flags.
+ * @unused:	Reserved for future use.
+ *
+ * This is used with the %COMEDI_INSN ioctl, and indirectly with the
+ * %COMEDI_INSNLIST ioctl.
+ */
+struct comedi_insn {
+	unsigned int insn;
+	unsigned int n;
+	unsigned int __user *data;
+	unsigned int subdev;
+	unsigned int chanspec;
+	unsigned int unused[3];
+};
+
+/**
+ * struct comedi_insnlist - list of COMEDI instructions
+ * @n_insns:	Number of COMEDI instructions.
+ * @insns:	Pointer to array COMEDI instructions.
+ *
+ * This is used with the %COMEDI_INSNLIST ioctl.
+ */
+struct comedi_insnlist {
+	unsigned int n_insns;
+	struct comedi_insn __user *insns;
+};
+
+/**
+ * struct comedi_cmd - COMEDI asynchronous acquisition command details
+ * @subdev:		Subdevice index.
+ * @flags:		Command flags (%CMDF_xxx).
+ * @start_src:		"Start acquisition" trigger source (%TRIG_xxx).
+ * @start_arg:		"Start acquisition" trigger argument.
+ * @scan_begin_src:	"Scan begin" trigger source.
+ * @scan_begin_arg:	"Scan begin" trigger argument.
+ * @convert_src:	"Convert" trigger source.
+ * @convert_arg:	"Convert" trigger argument.
+ * @scan_end_src:	"Scan end" trigger source.
+ * @scan_end_arg:	"Scan end" trigger argument.
+ * @stop_src:		"Stop acquisition" trigger source.
+ * @stop_arg:		"Stop acquisition" trigger argument.
+ * @chanlist:		Pointer to array of "chanspec" values, containing a
+ *			sequence of channel numbers packed with analog range
+ *			index, etc.
+ * @chanlist_len:	Number of channels in sequence.
+ * @data:		Pointer to miscellaneous set-up data (not used).
+ * @data_len:		Length of miscellaneous set-up data.
+ *
+ * This is used with the %COMEDI_CMD or %COMEDI_CMDTEST ioctl to set-up
+ * or validate an asynchronous acquisition command.  The ioctl may modify
+ * the &struct comedi_cmd and copy it back to the caller.
+ *
+ * Optional command @flags values that can be ORed together...
+ *
+ * %CMDF_BOGUS - makes %COMEDI_CMD ioctl return error %EAGAIN instead of
+ * starting the command.
+ *
+ * %CMDF_PRIORITY - requests "hard real-time" processing (which is not
+ * supported in this version of COMEDI).
+ *
+ * %CMDF_WAKE_EOS - requests the command makes data available for reading
+ * after every "scan" period.
+ *
+ * %CMDF_WRITE - marks the command as being in the "write" (to device)
+ * direction.  This does not need to be specified by the caller unless the
+ * subdevice supports commands in either direction.
+ *
+ * %CMDF_RAWDATA - prevents the command from "munging" the data between the
+ * COMEDI sample format and the raw hardware sample format.
+ *
+ * %CMDF_ROUND_NEAREST - requests timing periods to be rounded to nearest
+ * supported values.
+ *
+ * %CMDF_ROUND_DOWN - requests timing periods to be rounded down to supported
+ * values (frequencies rounded up).
+ *
+ * %CMDF_ROUND_UP - requests timing periods to be rounded up to supported
+ * values (frequencies rounded down).
+ *
+ * Trigger source values for @start_src, @scan_begin_src, @convert_src,
+ * @scan_end_src, and @stop_src...
+ *
+ * %TRIG_ANY - "all ones" value used to test which trigger sources are
+ * supported.
+ *
+ * %TRIG_INVALID - "all zeroes" value used to indicate that all requested
+ * trigger sources are invalid.
+ *
+ * %TRIG_NONE - never trigger (often used as a @stop_src value).
+ *
+ * %TRIG_NOW - trigger after '_arg' nanoseconds.
+ *
+ * %TRIG_FOLLOW - trigger follows another event.
+ *
+ * %TRIG_TIMER - trigger every '_arg' nanoseconds.
+ *
+ * %TRIG_COUNT - trigger when count '_arg' is reached.
+ *
+ * %TRIG_EXT - trigger on external signal specified by '_arg'.
+ *
+ * %TRIG_INT - trigger on internal, software trigger specified by '_arg'.
+ *
+ * %TRIG_OTHER - trigger on other, driver-defined signal specified by '_arg'.
+ */
+struct comedi_cmd {
+	unsigned int subdev;
+	unsigned int flags;
+
+	unsigned int start_src;
+	unsigned int start_arg;
+
+	unsigned int scan_begin_src;
+	unsigned int scan_begin_arg;
+
+	unsigned int convert_src;
+	unsigned int convert_arg;
+
+	unsigned int scan_end_src;
+	unsigned int scan_end_arg;
+
+	unsigned int stop_src;
+	unsigned int stop_arg;
+
+	unsigned int *chanlist;
+	unsigned int chanlist_len;
+
+	short __user *data;
+	unsigned int data_len;
+};
+
+/**
+ * struct comedi_chaninfo - used to retrieve per-channel information
+ * @subdev:		Subdevice index.
+ * @maxdata_list:	Optional pointer to per-channel maximum data values.
+ * @flaglist:		Optional pointer to per-channel flags.
+ * @rangelist:		Optional pointer to per-channel range types.
+ * @unused:		Reserved for future use.
+ *
+ * This is used with the %COMEDI_CHANINFO ioctl to get per-channel information
+ * for the subdevice.  Use of this requires knowledge of the number of channels
+ * and subdevice flags obtained using the %COMEDI_SUBDINFO ioctl.
+ *
+ * The @maxdata_list member must be %NULL unless the %SDF_MAXDATA subdevice
+ * flag is set.  The @flaglist member must be %NULL unless the %SDF_FLAGS
+ * subdevice flag is set.  The @rangelist member must be %NULL unless the
+ * %SDF_RANGETYPE subdevice flag is set.  Otherwise, the arrays they point to
+ * must be at least as long as the number of channels.
+ */
+struct comedi_chaninfo {
+	unsigned int subdev;
+	unsigned int __user *maxdata_list;
+	unsigned int __user *flaglist;
+	unsigned int __user *rangelist;
+	unsigned int unused[4];
+};
+
+/**
+ * struct comedi_rangeinfo - used to retrieve the range table for a channel
+ * @range_type:		Encodes subdevice index (bits 27:24), channel index
+ *			(bits 23:16) and range table length (bits 15:0).
+ * @range_ptr:		Pointer to array of @struct comedi_krange to be filled
+ *			in with the range table for the channel or subdevice.
+ *
+ * This is used with the %COMEDI_RANGEINFO ioctl to retrieve the range table
+ * for a specific channel (if the subdevice has the %SDF_RANGETYPE flag set to
+ * indicate that the range table depends on the channel), or for the subdevice
+ * as a whole (if the %SDF_RANGETYPE flag is clear, indicating the range table
+ * is shared by all channels).
+ *
+ * The @range_type value is an input to the ioctl and comes from a previous
+ * use of the %COMEDI_SUBDINFO ioctl (if the %SDF_RANGETYPE flag is clear),
+ * or the %COMEDI_CHANINFO ioctl (if the %SDF_RANGETYPE flag is set).
+ */
+struct comedi_rangeinfo {
+	unsigned int range_type;
+	void __user *range_ptr;
+};
+
+/**
+ * struct comedi_krange - describes a range in a range table
+ * @min:	Minimum value in millionths (1e-6) of a unit.
+ * @max:	Maximum value in millionths (1e-6) of a unit.
+ * @flags:	Indicates the units (in bits 7:0) OR'ed with optional flags.
+ *
+ * A range table is associated with a single channel, or with all channels in a
+ * subdevice, and a list of one or more ranges.  A %struct comedi_krange
+ * describes the physical range of units for one of those ranges.  Sample
+ * values in COMEDI are unsigned from %0 up to some 'maxdata' value.  The
+ * mapping from sample values to physical units is assumed to be nomimally
+ * linear (for the purpose of describing the range), with sample value %0
+ * mapping to @min, and the 'maxdata' sample value mapping to @max.
+ *
+ * The currently defined units are %UNIT_volt (%0), %UNIT_mA (%1), and
+ * %UNIT_none (%2).  The @min and @max values are the physical range multiplied
+ * by 1e6, so a @max value of %1000000 (with %UNIT_volt) represents a maximal
+ * value of 1 volt.
+ *
+ * The only defined flag value is %RF_EXTERNAL (%0x100), indicating that the
+ * range needs to be multiplied by an external reference.
+ */
+struct comedi_krange {
+	int min;
+	int max;
+	unsigned int flags;
+};
+
+/**
+ * struct comedi_subdinfo - used to retrieve information about a subdevice
+ * @type:		Type of subdevice from &enum comedi_subdevice_type.
+ * @n_chan:		Number of channels the subdevice supports.
+ * @subd_flags:		A mixture of static and dynamic flags describing
+ *			aspects of the subdevice and its current state.
+ * @timer_type:		Timer type.  Always set to %5 ("nanosecond timer").
+ * @len_chanlist:	Maximum length of a channel list if the subdevice
+ *			supports asynchronous acquisition commands.
+ * @maxdata:		Maximum sample value for all channels if the
+ *			%SDF_MAXDATA subdevice flag is clear.
+ * @flags:		Channel flags for all channels if the %SDF_FLAGS
+ *			subdevice flag is clear.
+ * @range_type:		The range type for all channels if the %SDF_RANGETYPE
+ *			subdevice flag is clear.  Encodes the subdevice index
+ *			(bits 27:24), a dummy channel index %0 (bits 23:16),
+ *			and the range table length (bits 15:0).
+ * @settling_time_0:	Not used.
+ * @insn_bits_support:	Set to %COMEDI_SUPPORTED if the subdevice supports the
+ *			%INSN_BITS instruction, or to %COMEDI_UNSUPPORTED if it
+ *			does not.
+ * @unused:		Reserved for future use.
+ *
+ * This is used with the %COMEDI_SUBDINFO ioctl which copies an array of
+ * &struct comedi_subdinfo back to user space, with one element per subdevice.
+ * Use of this requires knowledge of the number of subdevices obtained from
+ * the %COMEDI_DEVINFO ioctl.
+ *
+ * These are the @subd_flags values that may be ORed together...
+ *
+ * %SDF_BUSY - the subdevice is busy processing an asynchronous command or a
+ * synchronous instruction.
+ *
+ * %SDF_BUSY_OWNER - the subdevice is busy processing an asynchronous
+ * acquisition command started on the current file object (the file object
+ * issuing the %COMEDI_SUBDINFO ioctl).
+ *
+ * %SDF_LOCKED - the subdevice is locked by a %COMEDI_LOCK ioctl.
+ *
+ * %SDF_LOCK_OWNER - the subdevice is locked by a %COMEDI_LOCK ioctl from the
+ * current file object.
+ *
+ * %SDF_MAXDATA - maximum sample values are channel-specific.
+ *
+ * %SDF_FLAGS - channel flags are channel-specific.
+ *
+ * %SDF_RANGETYPE - range types are channel-specific.
+ *
+ * %SDF_PWM_COUNTER - PWM can switch off automatically.
+ *
+ * %SDF_PWM_HBRIDGE - or PWM is signed (H-bridge).
+ *
+ * %SDF_CMD - the subdevice supports asynchronous commands.
+ *
+ * %SDF_SOFT_CALIBRATED - the subdevice uses software calibration.
+ *
+ * %SDF_CMD_WRITE - the subdevice supports asynchronous commands in the output
+ * ("write") direction.
+ *
+ * %SDF_CMD_READ - the subdevice supports asynchronous commands in the input
+ * ("read") direction.
+ *
+ * %SDF_READABLE - the subdevice is readable (e.g. analog input).
+ *
+ * %SDF_WRITABLE (aliased as %SDF_WRITEABLE) - the subdevice is writable (e.g.
+ * analog output).
+ *
+ * %SDF_INTERNAL - the subdevice has no externally visible lines.
+ *
+ * %SDF_GROUND - the subdevice can use ground as an analog reference.
+ *
+ * %SDF_COMMON - the subdevice can use a common analog reference.
+ *
+ * %SDF_DIFF - the subdevice can use differential inputs (or outputs).
+ *
+ * %SDF_OTHER - the subdevice can use some other analog reference.
+ *
+ * %SDF_DITHER - the subdevice can do dithering.
+ *
+ * %SDF_DEGLITCH - the subdevice can do deglitching.
+ *
+ * %SDF_MMAP - this is never set.
+ *
+ * %SDF_RUNNING - an asynchronous command is still running.
+ *
+ * %SDF_LSAMPL - the subdevice uses "long" (32-bit) samples (for asynchronous
+ * command data).
+ *
+ * %SDF_PACKED - the subdevice packs several DIO samples into a single sample
+ * (for asynchronous command data).
+ *
+ * No "channel flags" (@flags) values are currently defined.
+ */
+struct comedi_subdinfo {
+	unsigned int type;
+	unsigned int n_chan;
+	unsigned int subd_flags;
+	unsigned int timer_type;
+	unsigned int len_chanlist;
+	unsigned int maxdata;
+	unsigned int flags;
+	unsigned int range_type;
+	unsigned int settling_time_0;
+	unsigned int insn_bits_support;
+	unsigned int unused[8];
+};
+
+/**
+ * struct comedi_devinfo - used to retrieve information about a COMEDI device
+ * @version_code:	COMEDI version code.
+ * @n_subdevs:		Number of subdevices the device has.
+ * @driver_name:	Null-terminated COMEDI driver name.
+ * @board_name:		Null-terminated COMEDI board name.
+ * @read_subdevice:	Index of the current "read" subdevice (%-1 if none).
+ * @write_subdevice:	Index of the current "write" subdevice (%-1 if none).
+ * @unused:		Reserved for future use.
+ *
+ * This is used with the %COMEDI_DEVINFO ioctl to get basic information about
+ * the device.
+ */
+struct comedi_devinfo {
+	unsigned int version_code;
+	unsigned int n_subdevs;
+	char driver_name[COMEDI_NAMELEN];
+	char board_name[COMEDI_NAMELEN];
+	int read_subdevice;
+	int write_subdevice;
+	int unused[30];
+};
+
+/**
+ * struct comedi_devconfig - used to configure a legacy COMEDI device
+ * @board_name:		Null-terminated string specifying the type of board
+ *			to configure.
+ * @options:		An array of integer configuration options.
+ *
+ * This is used with the %COMEDI_DEVCONFIG ioctl to configure a "legacy" COMEDI
+ * device, such as an ISA card.  Not all COMEDI drivers support this.  Those
+ * that do either expect the specified board name to match one of a list of
+ * names registered with the COMEDI core, or expect the specified board name
+ * to match the COMEDI driver name itself.  The configuration options are
+ * handled in a driver-specific manner.
+ */
+struct comedi_devconfig {
+	char board_name[COMEDI_NAMELEN];
+	int options[COMEDI_NDEVCONFOPTS];
+};
+
+/**
+ * struct comedi_bufconfig - used to set or get buffer size for a subdevice
+ * @subdevice:		Subdevice index.
+ * @flags:		Not used.
+ * @maximum_size:	Maximum allowed buffer size.
+ * @size:		Buffer size.
+ * @unused:		Reserved for future use.
+ *
+ * This is used with the %COMEDI_BUFCONFIG ioctl to get or configure the
+ * maximum buffer size and current buffer size for a COMEDI subdevice that
+ * supports asynchronous commands.  If the subdevice does not support
+ * asynchronous commands, @maximum_size and @size are ignored and set to 0.
+ *
+ * On ioctl input, non-zero values of @maximum_size and @size specify a
+ * new maximum size and new current size (in bytes), respectively.  These
+ * will by rounded up to a multiple of %PAGE_SIZE.  Specifying a new maximum
+ * size requires admin capabilities.
+ *
+ * On ioctl output, @maximum_size and @size and set to the current maximum
+ * buffer size and current buffer size, respectively.
+ */
+struct comedi_bufconfig {
+	unsigned int subdevice;
+	unsigned int flags;
+
+	unsigned int maximum_size;
+	unsigned int size;
+
+	unsigned int unused[4];
+};
+
+/**
+ * struct comedi_bufinfo - used to manipulate buffer position for a subdevice
+ * @subdevice:		Subdevice index.
+ * @bytes_read:		Specify amount to advance read position for an
+ *			asynchronous command in the input ("read") direction.
+ * @buf_write_ptr:	Current write position (index) within the buffer.
+ * @buf_read_ptr:	Current read position (index) within the buffer.
+ * @buf_write_count:	Total amount written, modulo 2^32.
+ * @buf_read_count:	Total amount read, modulo 2^32.
+ * @bytes_written:	Specify amount to advance write position for an
+ *			asynchronous command in the output ("write") direction.
+ * @unused:		Reserved for future use.
+ *
+ * This is used with the %COMEDI_BUFINFO ioctl to optionally advance the
+ * current read or write position in an asynchronous acquisition data buffer,
+ * and to get the current read and write positions in the buffer.
+ */
+struct comedi_bufinfo {
+	unsigned int subdevice;
+	unsigned int bytes_read;
+
+	unsigned int buf_write_ptr;
+	unsigned int buf_read_ptr;
+	unsigned int buf_write_count;
+	unsigned int buf_read_count;
+
+	unsigned int bytes_written;
+
+	unsigned int unused[4];
+};
+
+/* range stuff */
+
+#define __RANGE(a, b)	((((a) & 0xffff) << 16) | ((b) & 0xffff))
+
+#define RANGE_OFFSET(a)		(((a) >> 16) & 0xffff)
+#define RANGE_LENGTH(b)		((b) & 0xffff)
+
+#define RF_UNIT(flags)		((flags) & 0xff)
+#define RF_EXTERNAL		0x100
+
+#define UNIT_volt		0
+#define UNIT_mA			1
+#define UNIT_none		2
+
+#define COMEDI_MIN_SPEED	0xffffffffu
+
+/**********************************************************/
+/* everything after this line is ALPHA */
+/**********************************************************/
+
+/*
+ * 8254 specific configuration.
+ *
+ * It supports two config commands:
+ *
+ * 0 ID: INSN_CONFIG_SET_COUNTER_MODE
+ * 1 8254 Mode
+ * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
+ * OR'ed with:
+ * I8254_BCD, I8254_BINARY
+ *
+ * 0 ID: INSN_CONFIG_8254_READ_STATUS
+ * 1 <-- Status byte returned here.
+ * B7 = Output
+ * B6 = NULL Count
+ * B5 - B0 Current mode.
+ */
+
+enum i8254_mode {
+	I8254_MODE0 = (0 << 1),	/* Interrupt on terminal count */
+	I8254_MODE1 = (1 << 1),	/* Hardware retriggerable one-shot */
+	I8254_MODE2 = (2 << 1),	/* Rate generator */
+	I8254_MODE3 = (3 << 1),	/* Square wave mode */
+	I8254_MODE4 = (4 << 1),	/* Software triggered strobe */
+	/* Hardware triggered strobe (retriggerable) */
+	I8254_MODE5 = (5 << 1),
+	/* Use binary-coded decimal instead of binary (pretty useless) */
+	I8254_BCD = 1,
+	I8254_BINARY = 0
+};
+
+/* *** BEGIN GLOBALLY-NAMED NI TERMINALS/SIGNALS *** */
+
+/*
+ * Common National Instruments Terminal/Signal names.
+ * Some of these have no NI_ prefix as they are useful for non-NI hardware, such
+ * as those that utilize the PXI/RTSI trigger lines.
+ *
+ * NOTE ABOUT THE CHOICE OF NAMES HERE AND THE CAMELSCRIPT:
+ *   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!
+ *
+ *   More detail:
+ *   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 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.
+ *
+ *   Similar, albeit less, confusion plagued NI's previous version of their own
+ *   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.
+ *
+ *   The names below mirror the names chosen and well documented by NI.  These
+ *   names are exposed to the user via the comedilib user library.  By keeping
+ *   the names below, in spite of the use of CamelScript, maintenance will be
+ *   greatly eased and confusion for users _and_ comedi developers will be
+ *   greatly reduced.
+ */
+
+/*
+ * Base of abstracted NI names.
+ * The first 16 bits of *_arg are reserved for channel selection.
+ * Since we only actually need the first 4 or 5 bits for all register values on
+ * NI select registers anyways, we'll identify all values >= (1<<15) as being an
+ * abstracted NI signal/terminal name.
+ * These values are also used/returned by INSN_DEVICE_CONFIG_TEST_ROUTE,
+ * INSN_DEVICE_CONFIG_CONNECT_ROUTE, INSN_DEVICE_CONFIG_DISCONNECT_ROUTE,
+ * and INSN_DEVICE_CONFIG_GET_ROUTES.
+ */
+#define NI_NAMES_BASE	0x8000u
+
+#define _TERM_N(base, n, x)	((base) + ((x) & ((n) - 1)))
+
+/*
+ * not necessarily all allowed 64 PFIs are valid--certainly not for all devices
+ */
+#define NI_PFI(x)		_TERM_N(NI_NAMES_BASE, 64, x)
+/* 8 trigger lines by standard, Some devices cannot talk to all eight. */
+#define TRIGGER_LINE(x)		_TERM_N(NI_PFI(-1) + 1, 8, x)
+/* 4 RTSI shared MUXes to route signals to/from TRIGGER_LINES on NI hardware */
+#define NI_RTSI_BRD(x)		_TERM_N(TRIGGER_LINE(-1) + 1, 4, x)
+
+/* *** Counter/timer names : 8 counters max *** */
+#define NI_MAX_COUNTERS		8
+#define NI_COUNTER_NAMES_BASE	(NI_RTSI_BRD(-1)  + 1)
+#define NI_CtrSource(x)	      _TERM_N(NI_COUNTER_NAMES_BASE, NI_MAX_COUNTERS, x)
+/* Gate, Aux, A,B,Z are all treated, at times as gates */
+#define NI_GATES_NAMES_BASE	(NI_CtrSource(-1) + 1)
+#define NI_CtrGate(x)		_TERM_N(NI_GATES_NAMES_BASE, NI_MAX_COUNTERS, x)
+#define NI_CtrAux(x)		_TERM_N(NI_CtrGate(-1)  + 1, NI_MAX_COUNTERS, x)
+#define NI_CtrA(x)		_TERM_N(NI_CtrAux(-1)   + 1, NI_MAX_COUNTERS, x)
+#define NI_CtrB(x)		_TERM_N(NI_CtrA(-1)     + 1, NI_MAX_COUNTERS, x)
+#define NI_CtrZ(x)		_TERM_N(NI_CtrB(-1)     + 1, NI_MAX_COUNTERS, x)
+#define NI_GATES_NAMES_MAX	NI_CtrZ(-1)
+#define NI_CtrArmStartTrigger(x) _TERM_N(NI_CtrZ(-1)    + 1, NI_MAX_COUNTERS, x)
+#define NI_CtrInternalOutput(x) \
+		      _TERM_N(NI_CtrArmStartTrigger(-1) + 1, NI_MAX_COUNTERS, x)
+/** external pin(s) labeled conveniently as Ctr<i>Out. */
+#define NI_CtrOut(x)   _TERM_N(NI_CtrInternalOutput(-1) + 1, NI_MAX_COUNTERS, x)
+/** For Buffered sampling of ctr -- x series capability. */
+#define NI_CtrSampleClock(x)	_TERM_N(NI_CtrOut(-1)   + 1, NI_MAX_COUNTERS, x)
+#define NI_COUNTER_NAMES_MAX	NI_CtrSampleClock(-1)
+
+enum ni_common_signal_names {
+	/* PXI_Star: this is a non-NI-specific signal */
+	PXI_Star = NI_COUNTER_NAMES_MAX + 1,
+	PXI_Clk10,
+	PXIe_Clk100,
+	NI_AI_SampleClock,
+	NI_AI_SampleClockTimebase,
+	NI_AI_StartTrigger,
+	NI_AI_ReferenceTrigger,
+	NI_AI_ConvertClock,
+	NI_AI_ConvertClockTimebase,
+	NI_AI_PauseTrigger,
+	NI_AI_HoldCompleteEvent,
+	NI_AI_HoldComplete,
+	NI_AI_ExternalMUXClock,
+	NI_AI_STOP, /* pulse signal that occurs when a update is finished(?) */
+	NI_AO_SampleClock,
+	NI_AO_SampleClockTimebase,
+	NI_AO_StartTrigger,
+	NI_AO_PauseTrigger,
+	NI_DI_SampleClock,
+	NI_DI_SampleClockTimebase,
+	NI_DI_StartTrigger,
+	NI_DI_ReferenceTrigger,
+	NI_DI_PauseTrigger,
+	NI_DI_InputBufferFull,
+	NI_DI_ReadyForStartEvent,
+	NI_DI_ReadyForTransferEventBurst,
+	NI_DI_ReadyForTransferEventPipelined,
+	NI_DO_SampleClock,
+	NI_DO_SampleClockTimebase,
+	NI_DO_StartTrigger,
+	NI_DO_PauseTrigger,
+	NI_DO_OutputBufferFull,
+	NI_DO_DataActiveEvent,
+	NI_DO_ReadyForStartEvent,
+	NI_DO_ReadyForTransferEvent,
+	NI_MasterTimebase,
+	NI_20MHzTimebase,
+	NI_80MHzTimebase,
+	NI_100MHzTimebase,
+	NI_200MHzTimebase,
+	NI_100kHzTimebase,
+	NI_10MHzRefClock,
+	NI_FrequencyOutput,
+	NI_ChangeDetectionEvent,
+	NI_AnalogComparisonEvent,
+	NI_WatchdogExpiredEvent,
+	NI_WatchdogExpirationTrigger,
+	NI_SCXI_Trig1,
+	NI_LogicLow,
+	NI_LogicHigh,
+	NI_ExternalStrobe,
+	NI_PFI_DO,
+	NI_CaseGround,
+	/* special internal signal used as variable source for RTSI bus: */
+	NI_RGOUT0,
+
+	/* just a name to make the next more convenient, regardless of above */
+	_NI_NAMES_MAX_PLUS_1,
+	NI_NUM_NAMES = _NI_NAMES_MAX_PLUS_1 - NI_NAMES_BASE,
+};
+
+/* *** END GLOBALLY-NAMED NI TERMINALS/SIGNALS *** */
+
+#define NI_USUAL_PFI_SELECT(x)	(((x) < 10) ? (0x1 + (x)) : (0xb + (x)))
+#define NI_USUAL_RTSI_SELECT(x)	(((x) < 7) ? (0xb + (x)) : 0x1b)
+
+/*
+ * mode bits for NI general-purpose counters, set with
+ * INSN_CONFIG_SET_COUNTER_MODE
+ */
+#define NI_GPCT_COUNTING_MODE_SHIFT 16
+#define NI_GPCT_INDEX_PHASE_BITSHIFT 20
+#define NI_GPCT_COUNTING_DIRECTION_SHIFT 24
+enum ni_gpct_mode_bits {
+	NI_GPCT_GATE_ON_BOTH_EDGES_BIT = 0x4,
+	NI_GPCT_EDGE_GATE_MODE_MASK = 0x18,
+	NI_GPCT_EDGE_GATE_STARTS_STOPS_BITS = 0x0,
+	NI_GPCT_EDGE_GATE_STOPS_STARTS_BITS = 0x8,
+	NI_GPCT_EDGE_GATE_STARTS_BITS = 0x10,
+	NI_GPCT_EDGE_GATE_NO_STARTS_NO_STOPS_BITS = 0x18,
+	NI_GPCT_STOP_MODE_MASK = 0x60,
+	NI_GPCT_STOP_ON_GATE_BITS = 0x00,
+	NI_GPCT_STOP_ON_GATE_OR_TC_BITS = 0x20,
+	NI_GPCT_STOP_ON_GATE_OR_SECOND_TC_BITS = 0x40,
+	NI_GPCT_LOAD_B_SELECT_BIT = 0x80,
+	NI_GPCT_OUTPUT_MODE_MASK = 0x300,
+	NI_GPCT_OUTPUT_TC_PULSE_BITS = 0x100,
+	NI_GPCT_OUTPUT_TC_TOGGLE_BITS = 0x200,
+	NI_GPCT_OUTPUT_TC_OR_GATE_TOGGLE_BITS = 0x300,
+	NI_GPCT_HARDWARE_DISARM_MASK = 0xc00,
+	NI_GPCT_NO_HARDWARE_DISARM_BITS = 0x000,
+	NI_GPCT_DISARM_AT_TC_BITS = 0x400,
+	NI_GPCT_DISARM_AT_GATE_BITS = 0x800,
+	NI_GPCT_DISARM_AT_TC_OR_GATE_BITS = 0xc00,
+	NI_GPCT_LOADING_ON_TC_BIT = 0x1000,
+	NI_GPCT_LOADING_ON_GATE_BIT = 0x4000,
+	NI_GPCT_COUNTING_MODE_MASK = 0x7 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_COUNTING_MODE_NORMAL_BITS =
+		0x0 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_COUNTING_MODE_QUADRATURE_X1_BITS =
+		0x1 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_COUNTING_MODE_QUADRATURE_X2_BITS =
+		0x2 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_COUNTING_MODE_QUADRATURE_X4_BITS =
+		0x3 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_COUNTING_MODE_TWO_PULSE_BITS =
+		0x4 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_COUNTING_MODE_SYNC_SOURCE_BITS =
+		0x6 << NI_GPCT_COUNTING_MODE_SHIFT,
+	NI_GPCT_INDEX_PHASE_MASK = 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+	NI_GPCT_INDEX_PHASE_LOW_A_LOW_B_BITS =
+		0x0 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+	NI_GPCT_INDEX_PHASE_LOW_A_HIGH_B_BITS =
+		0x1 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+	NI_GPCT_INDEX_PHASE_HIGH_A_LOW_B_BITS =
+		0x2 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+	NI_GPCT_INDEX_PHASE_HIGH_A_HIGH_B_BITS =
+		0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT,
+	NI_GPCT_INDEX_ENABLE_BIT = 0x400000,
+	NI_GPCT_COUNTING_DIRECTION_MASK =
+		0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+	NI_GPCT_COUNTING_DIRECTION_DOWN_BITS =
+		0x00 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+	NI_GPCT_COUNTING_DIRECTION_UP_BITS =
+		0x1 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+	NI_GPCT_COUNTING_DIRECTION_HW_UP_DOWN_BITS =
+		0x2 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+	NI_GPCT_COUNTING_DIRECTION_HW_GATE_BITS =
+		0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT,
+	NI_GPCT_RELOAD_SOURCE_MASK = 0xc000000,
+	NI_GPCT_RELOAD_SOURCE_FIXED_BITS = 0x0,
+	NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS = 0x4000000,
+	NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS = 0x8000000,
+	NI_GPCT_OR_GATE_BIT = 0x10000000,
+	NI_GPCT_INVERT_OUTPUT_BIT = 0x20000000
+};
+
+/*
+ * Bits for setting a clock source with
+ * INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters.
+ */
+enum ni_gpct_clock_source_bits {
+	NI_GPCT_CLOCK_SRC_SELECT_MASK = 0x3f,
+	NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS = 0x0,
+	NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS = 0x1,
+	NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS = 0x2,
+	NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS = 0x3,
+	NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS = 0x4,
+	NI_GPCT_NEXT_TC_CLOCK_SRC_BITS = 0x5,
+	/* NI 660x-specific */
+	NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS = 0x6,
+	NI_GPCT_PXI10_CLOCK_SRC_BITS = 0x7,
+	NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS = 0x8,
+	NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS = 0x9,
+	NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK = 0x30000000,
+	NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS = 0x0,
+	/* divide source by 2 */
+	NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS = 0x10000000,
+	/* divide source by 8 */
+	NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS = 0x20000000,
+	NI_GPCT_INVERT_CLOCK_SRC_BIT = 0x80000000
+};
+
+/* NI 660x-specific */
+#define NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(x)	(0x10 + (x))
+
+#define NI_GPCT_RTSI_CLOCK_SRC_BITS(x)		(0x18 + (x))
+
+/* no pfi on NI 660x */
+#define NI_GPCT_PFI_CLOCK_SRC_BITS(x)		(0x20 + (x))
+
+/*
+ * Possibilities for setting a gate source with
+ * INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters.
+ * May be bitwise-or'd with CR_EDGE or CR_INVERT.
+ */
+enum ni_gpct_gate_select {
+	/* m-series gates */
+	NI_GPCT_TIMESTAMP_MUX_GATE_SELECT = 0x0,
+	NI_GPCT_AI_START2_GATE_SELECT = 0x12,
+	NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT = 0x13,
+	NI_GPCT_NEXT_OUT_GATE_SELECT = 0x14,
+	NI_GPCT_AI_START1_GATE_SELECT = 0x1c,
+	NI_GPCT_NEXT_SOURCE_GATE_SELECT = 0x1d,
+	NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT = 0x1e,
+	NI_GPCT_LOGIC_LOW_GATE_SELECT = 0x1f,
+	/* more gates for 660x */
+	NI_GPCT_SOURCE_PIN_i_GATE_SELECT = 0x100,
+	NI_GPCT_GATE_PIN_i_GATE_SELECT = 0x101,
+	/* more gates for 660x "second gate" */
+	NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT = 0x201,
+	NI_GPCT_SELECTED_GATE_GATE_SELECT = 0x21e,
+	/*
+	 * m-series "second gate" sources are unknown,
+	 * we should add them here with an offset of 0x300 when
+	 * known.
+	 */
+	NI_GPCT_DISABLED_GATE_SELECT = 0x8000,
+};
+
+#define NI_GPCT_GATE_PIN_GATE_SELECT(x)		(0x102 + (x))
+#define NI_GPCT_RTSI_GATE_SELECT(x)		NI_USUAL_RTSI_SELECT(x)
+#define NI_GPCT_PFI_GATE_SELECT(x)		NI_USUAL_PFI_SELECT(x)
+#define NI_GPCT_UP_DOWN_PIN_GATE_SELECT(x)	(0x202 + (x))
+
+/*
+ * Possibilities for setting a source with
+ * INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters.
+ */
+enum ni_gpct_other_index {
+	NI_GPCT_SOURCE_ENCODER_A,
+	NI_GPCT_SOURCE_ENCODER_B,
+	NI_GPCT_SOURCE_ENCODER_Z
+};
+
+enum ni_gpct_other_select {
+	/* m-series gates */
+	/* Still unknown, probably only need NI_GPCT_PFI_OTHER_SELECT */
+	NI_GPCT_DISABLED_OTHER_SELECT = 0x8000,
+};
+
+#define NI_GPCT_PFI_OTHER_SELECT(x)	NI_USUAL_PFI_SELECT(x)
+
+/*
+ * start sources for ni general-purpose counters for use with
+ * INSN_CONFIG_ARM
+ */
+enum ni_gpct_arm_source {
+	NI_GPCT_ARM_IMMEDIATE = 0x0,
+	/*
+	 * Start both the counter and the adjacent paired counter simultaneously
+	 */
+	NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1,
+	/*
+	 * If the NI_GPCT_HW_ARM bit is set, we will pass the least significant
+	 * bits (3 bits for 660x or 5 bits for m-series) through to the
+	 * hardware. To select a hardware trigger, pass the appropriate select
+	 * bit, e.g.,
+	 * NI_GPCT_HW_ARM | NI_GPCT_AI_START1_GATE_SELECT or
+	 * NI_GPCT_HW_ARM | NI_GPCT_PFI_GATE_SELECT(pfi_number)
+	 */
+	NI_GPCT_HW_ARM = 0x1000,
+	NI_GPCT_ARM_UNKNOWN = NI_GPCT_HW_ARM,	/* for backward compatibility */
+};
+
+/* digital filtering options for ni 660x for use with INSN_CONFIG_FILTER. */
+enum ni_gpct_filter_select {
+	NI_GPCT_FILTER_OFF = 0x0,
+	NI_GPCT_FILTER_TIMEBASE_3_SYNC = 0x1,
+	NI_GPCT_FILTER_100x_TIMEBASE_1 = 0x2,
+	NI_GPCT_FILTER_20x_TIMEBASE_1 = 0x3,
+	NI_GPCT_FILTER_10x_TIMEBASE_1 = 0x4,
+	NI_GPCT_FILTER_2x_TIMEBASE_1 = 0x5,
+	NI_GPCT_FILTER_2x_TIMEBASE_3 = 0x6
+};
+
+/*
+ * PFI digital filtering options for ni m-series for use with
+ * INSN_CONFIG_FILTER.
+ */
+enum ni_pfi_filter_select {
+	NI_PFI_FILTER_OFF = 0x0,
+	NI_PFI_FILTER_125ns = 0x1,
+	NI_PFI_FILTER_6425ns = 0x2,
+	NI_PFI_FILTER_2550us = 0x3
+};
+
+/* master clock sources for ni mio boards and INSN_CONFIG_SET_CLOCK_SRC */
+enum ni_mio_clock_source {
+	NI_MIO_INTERNAL_CLOCK = 0,
+	/*
+	 * Doesn't work for m-series, use NI_MIO_PLL_RTSI_CLOCK()
+	 * the NI_MIO_PLL_* sources are m-series only
+	 */
+	NI_MIO_RTSI_CLOCK = 1,
+	NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK = 2,
+	NI_MIO_PLL_PXI10_CLOCK = 3,
+	NI_MIO_PLL_RTSI0_CLOCK = 4
+};
+
+#define NI_MIO_PLL_RTSI_CLOCK(x)	(NI_MIO_PLL_RTSI0_CLOCK + (x))
+
+/*
+ * Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING.
+ * The numbers assigned are not arbitrary, they correspond to the bits required
+ * to program the board.
+ */
+enum ni_rtsi_routing {
+	NI_RTSI_OUTPUT_ADR_START1 = 0,
+	NI_RTSI_OUTPUT_ADR_START2 = 1,
+	NI_RTSI_OUTPUT_SCLKG = 2,
+	NI_RTSI_OUTPUT_DACUPDN = 3,
+	NI_RTSI_OUTPUT_DA_START1 = 4,
+	NI_RTSI_OUTPUT_G_SRC0 = 5,
+	NI_RTSI_OUTPUT_G_GATE0 = 6,
+	NI_RTSI_OUTPUT_RGOUT0 = 7,
+	NI_RTSI_OUTPUT_RTSI_BRD_0 = 8,
+	/* Pre-m-series always have RTSI clock on line 7 */
+	NI_RTSI_OUTPUT_RTSI_OSC = 12
+};
+
+#define NI_RTSI_OUTPUT_RTSI_BRD(x)	(NI_RTSI_OUTPUT_RTSI_BRD_0 + (x))
+
+/*
+ * Signals which can be routed to an NI PFI pin on an m-series board with
+ * INSN_CONFIG_SET_ROUTING.  These numbers are also returned by
+ * INSN_CONFIG_GET_ROUTING on pre-m-series boards, even though their routing
+ * cannot be changed.  The numbers assigned are not arbitrary, they correspond
+ * to the bits required to program the board.
+ */
+enum ni_pfi_routing {
+	NI_PFI_OUTPUT_PFI_DEFAULT = 0,
+	NI_PFI_OUTPUT_AI_START1 = 1,
+	NI_PFI_OUTPUT_AI_START2 = 2,
+	NI_PFI_OUTPUT_AI_CONVERT = 3,
+	NI_PFI_OUTPUT_G_SRC1 = 4,
+	NI_PFI_OUTPUT_G_GATE1 = 5,
+	NI_PFI_OUTPUT_AO_UPDATE_N = 6,
+	NI_PFI_OUTPUT_AO_START1 = 7,
+	NI_PFI_OUTPUT_AI_START_PULSE = 8,
+	NI_PFI_OUTPUT_G_SRC0 = 9,
+	NI_PFI_OUTPUT_G_GATE0 = 10,
+	NI_PFI_OUTPUT_EXT_STROBE = 11,
+	NI_PFI_OUTPUT_AI_EXT_MUX_CLK = 12,
+	NI_PFI_OUTPUT_GOUT0 = 13,
+	NI_PFI_OUTPUT_GOUT1 = 14,
+	NI_PFI_OUTPUT_FREQ_OUT = 15,
+	NI_PFI_OUTPUT_PFI_DO = 16,
+	NI_PFI_OUTPUT_I_ATRIG = 17,
+	NI_PFI_OUTPUT_RTSI0 = 18,
+	NI_PFI_OUTPUT_PXI_STAR_TRIGGER_IN = 26,
+	NI_PFI_OUTPUT_SCXI_TRIG1 = 27,
+	NI_PFI_OUTPUT_DIO_CHANGE_DETECT_RTSI = 28,
+	NI_PFI_OUTPUT_CDI_SAMPLE = 29,
+	NI_PFI_OUTPUT_CDO_UPDATE = 30
+};
+
+#define NI_PFI_OUTPUT_RTSI(x)		(NI_PFI_OUTPUT_RTSI0 + (x))
+
+/*
+ * Signals which can be routed to output on a NI PFI pin on a 660x board
+ * with INSN_CONFIG_SET_ROUTING.  The numbers assigned are
+ * not arbitrary, they correspond to the bits required
+ * to program the board.  Lines 0 to 7 can only be set to
+ * NI_660X_PFI_OUTPUT_DIO.  Lines 32 to 39 can only be set to
+ * NI_660X_PFI_OUTPUT_COUNTER.
+ */
+enum ni_660x_pfi_routing {
+	NI_660X_PFI_OUTPUT_COUNTER = 1,	/* counter */
+	NI_660X_PFI_OUTPUT_DIO = 2,	/* static digital output */
+};
+
+/*
+ * NI External Trigger lines.  These values are not arbitrary, but are related
+ * to the bits required to program the board (offset by 1 for historical
+ * reasons).
+ */
+#define NI_EXT_PFI(x)			(NI_USUAL_PFI_SELECT(x) - 1)
+#define NI_EXT_RTSI(x)			(NI_USUAL_RTSI_SELECT(x) - 1)
+
+/*
+ * Clock sources for CDIO subdevice on NI m-series boards.  Used as the
+ * scan_begin_arg for a comedi_command. These sources may also be bitwise-or'd
+ * with CR_INVERT to change polarity.
+ */
+enum ni_m_series_cdio_scan_begin_src {
+	NI_CDIO_SCAN_BEGIN_SRC_GROUND = 0,
+	NI_CDIO_SCAN_BEGIN_SRC_AI_START = 18,
+	NI_CDIO_SCAN_BEGIN_SRC_AI_CONVERT = 19,
+	NI_CDIO_SCAN_BEGIN_SRC_PXI_STAR_TRIGGER = 20,
+	NI_CDIO_SCAN_BEGIN_SRC_G0_OUT = 28,
+	NI_CDIO_SCAN_BEGIN_SRC_G1_OUT = 29,
+	NI_CDIO_SCAN_BEGIN_SRC_ANALOG_TRIGGER = 30,
+	NI_CDIO_SCAN_BEGIN_SRC_AO_UPDATE = 31,
+	NI_CDIO_SCAN_BEGIN_SRC_FREQ_OUT = 32,
+	NI_CDIO_SCAN_BEGIN_SRC_DIO_CHANGE_DETECT_IRQ = 33
+};
+
+#define NI_CDIO_SCAN_BEGIN_SRC_PFI(x)	NI_USUAL_PFI_SELECT(x)
+#define NI_CDIO_SCAN_BEGIN_SRC_RTSI(x)	NI_USUAL_RTSI_SELECT(x)
+
+/*
+ * scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command on NI
+ * boards.  These scan begin sources can also be bitwise-or'd with CR_INVERT to
+ * change polarity.
+ */
+#define NI_AO_SCAN_BEGIN_SRC_PFI(x)	NI_USUAL_PFI_SELECT(x)
+#define NI_AO_SCAN_BEGIN_SRC_RTSI(x)	NI_USUAL_RTSI_SELECT(x)
+
+/*
+ * Bits for setting a clock source with
+ * INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice.
+ */
+enum ni_freq_out_clock_source_bits {
+	NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC,	/* 10 MHz */
+	NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC	/* 100 KHz */
+};
+
+/*
+ * Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver).
+ */
+enum amplc_dio_clock_source {
+	/*
+	 * Per channel external clock
+	 * input/output pin (pin is only an
+	 * input when clock source set to this value,
+	 * otherwise it is an output)
+	 */
+	AMPLC_DIO_CLK_CLKN,
+	AMPLC_DIO_CLK_10MHZ,	/* 10 MHz internal clock */
+	AMPLC_DIO_CLK_1MHZ,	/* 1 MHz internal clock */
+	AMPLC_DIO_CLK_100KHZ,	/* 100 kHz internal clock */
+	AMPLC_DIO_CLK_10KHZ,	/* 10 kHz internal clock */
+	AMPLC_DIO_CLK_1KHZ,	/* 1 kHz internal clock */
+	/*
+	 * Output of preceding counter channel
+	 * (for channel 0, preceding counter
+	 * channel is channel 2 on preceding
+	 * counter subdevice, for first counter
+	 * subdevice, preceding counter
+	 * subdevice is the last counter
+	 * subdevice)
+	 */
+	AMPLC_DIO_CLK_OUTNM1,
+	AMPLC_DIO_CLK_EXT,	/* per chip external input pin */
+	/* the following are "enhanced" clock sources for PCIe models */
+	AMPLC_DIO_CLK_VCC,	/* clock input HIGH */
+	AMPLC_DIO_CLK_GND,	/* clock input LOW */
+	AMPLC_DIO_CLK_PAT_PRESENT, /* "pattern present" signal */
+	AMPLC_DIO_CLK_20MHZ	/* 20 MHz internal clock */
+};
+
+/*
+ * Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * timer subdevice on some Amplicon DIO PCIe boards (amplc_dio200 driver).
+ */
+enum amplc_dio_ts_clock_src {
+	AMPLC_DIO_TS_CLK_1GHZ,	/* 1 ns period with 20 ns granularity */
+	AMPLC_DIO_TS_CLK_1MHZ,	/* 1 us period */
+	AMPLC_DIO_TS_CLK_1KHZ	/* 1 ms period */
+};
+
+/*
+ * Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for
+ * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver).
+ */
+enum amplc_dio_gate_source {
+	AMPLC_DIO_GAT_VCC,	/* internal high logic level */
+	AMPLC_DIO_GAT_GND,	/* internal low logic level */
+	AMPLC_DIO_GAT_GATN,	/* per channel external gate input */
+	/*
+	 * negated output of counter channel minus 2
+	 * (for channels 0 or 1, channel minus 2 is channel 1 or 2 on
+	 * the preceding counter subdevice, for the first counter subdevice
+	 * the preceding counter subdevice is the last counter subdevice)
+	 */
+	AMPLC_DIO_GAT_NOUTNM2,
+	AMPLC_DIO_GAT_RESERVED4,
+	AMPLC_DIO_GAT_RESERVED5,
+	AMPLC_DIO_GAT_RESERVED6,
+	AMPLC_DIO_GAT_RESERVED7,
+	/* the following are "enhanced" gate sources for PCIe models */
+	AMPLC_DIO_GAT_NGATN = 6, /* negated per channel gate input */
+	/* non-negated output of counter channel minus 2 */
+	AMPLC_DIO_GAT_OUTNM2,
+	AMPLC_DIO_GAT_PAT_PRESENT, /* "pattern present" signal */
+	AMPLC_DIO_GAT_PAT_OCCURRED, /* "pattern occurred" latched */
+	AMPLC_DIO_GAT_PAT_GONE,	/* "pattern gone away" latched */
+	AMPLC_DIO_GAT_NPAT_PRESENT, /* negated "pattern present" */
+	AMPLC_DIO_GAT_NPAT_OCCURRED, /* negated "pattern occurred" */
+	AMPLC_DIO_GAT_NPAT_GONE	/* negated "pattern gone away" */
+};
+
+/*
+ * Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * the counter subdevice on the Kolter Electronic PCI-Counter board
+ * (ke_counter driver).
+ */
+enum ke_counter_clock_source {
+	KE_CLK_20MHZ,	/* internal 20MHz (default) */
+	KE_CLK_4MHZ,	/* internal 4MHz (option) */
+	KE_CLK_EXT	/* external clock on pin 21 of D-Sub */
+};
+
+#endif /* _COMEDI_H */
diff --git a/drivers/comedi/comedi_buf.c b/drivers/comedi/comedi_buf.c
new file mode 100644
index 000000000000..06bfc859ab31
--- /dev/null
+++ b/drivers/comedi/comedi_buf.c
@@ -0,0 +1,692 @@
+// 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 "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 000000000000..df77b6bf5c64
--- /dev/null
+++ b/drivers/comedi/comedi_fops.c
@@ -0,0 +1,3436 @@
+// 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 "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 snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t max_read_buffer_kb_store(struct device *csdev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size;
+	int err;
+
+	err = kstrtouint(buf, 10, &size);
+	if (err)
+		return err;
+	if (size > (UINT_MAX / 1024))
+		return -EINVAL;
+	size *= 1024;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_read_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+		s->async->max_bufsize = size;
+	else
+		err = -EINVAL;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return err ? err : count;
+}
+static DEVICE_ATTR_RW(max_read_buffer_kb);
+
+static ssize_t read_buffer_kb_show(struct device *csdev,
+				   struct device_attribute *attr, char *buf)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size = 0;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_read_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+		size = s->async->prealloc_bufsz / 1024;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t read_buffer_kb_store(struct device *csdev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size;
+	int err;
+
+	err = kstrtouint(buf, 10, &size);
+	if (err)
+		return err;
+	if (size > (UINT_MAX / 1024))
+		return -EINVAL;
+	size *= 1024;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_read_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+		err = resize_async_buffer(dev, s, size);
+	else
+		err = -EINVAL;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return err ? err : count;
+}
+static DEVICE_ATTR_RW(read_buffer_kb);
+
+static ssize_t max_write_buffer_kb_show(struct device *csdev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size = 0;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_write_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+		size = s->async->max_bufsize / 1024;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t max_write_buffer_kb_store(struct device *csdev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size;
+	int err;
+
+	err = kstrtouint(buf, 10, &size);
+	if (err)
+		return err;
+	if (size > (UINT_MAX / 1024))
+		return -EINVAL;
+	size *= 1024;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_write_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+		s->async->max_bufsize = size;
+	else
+		err = -EINVAL;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return err ? err : count;
+}
+static DEVICE_ATTR_RW(max_write_buffer_kb);
+
+static ssize_t write_buffer_kb_show(struct device *csdev,
+				    struct device_attribute *attr, char *buf)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size = 0;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_write_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+		size = s->async->prealloc_bufsz / 1024;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t write_buffer_kb_store(struct device *csdev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	unsigned int minor = MINOR(csdev->devt);
+	struct comedi_device *dev;
+	struct comedi_subdevice *s;
+	unsigned int size;
+	int err;
+
+	err = kstrtouint(buf, 10, &size);
+	if (err)
+		return err;
+	if (size > (UINT_MAX / 1024))
+		return -EINVAL;
+	size *= 1024;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->mutex);
+	s = comedi_write_subdevice(dev, minor);
+	if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+		err = resize_async_buffer(dev, s, size);
+	else
+		err = -EINVAL;
+	mutex_unlock(&dev->mutex);
+
+	comedi_dev_put(dev);
+	return err ? err : count;
+}
+static DEVICE_ATTR_RW(write_buffer_kb);
+
+static struct attribute *comedi_dev_attrs[] = {
+	&dev_attr_max_read_buffer_kb.attr,
+	&dev_attr_read_buffer_kb.attr,
+	&dev_attr_max_write_buffer_kb.attr,
+	&dev_attr_write_buffer_kb.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(comedi_dev);
+
+static void __comedi_clear_subdevice_runflags(struct comedi_subdevice *s,
+					      unsigned 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, &copy, 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, &copy, 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, &copy, 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, &copy, 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);
+	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 000000000000..9b3631a654c8
--- /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 000000000000..54739af7eb71
--- /dev/null
+++ b/drivers/comedi/comedi_pci.c
@@ -0,0 +1,228 @@
+// 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 "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_pci.h b/drivers/comedi/comedi_pci.h
new file mode 100644
index 000000000000..4e069440cbdc
--- /dev/null
+++ b/drivers/comedi/comedi_pci.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * comedi_pci.h
+ * header file for Comedi PCI drivers
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _COMEDI_PCI_H
+#define _COMEDI_PCI_H
+
+#include <linux/pci.h>
+
+#include "comedidev.h"
+
+/*
+ * PCI Vendor IDs not in <linux/pci_ids.h>
+ */
+#define PCI_VENDOR_ID_KOLTER		0x1001
+#define PCI_VENDOR_ID_ICP		0x104c
+#define PCI_VENDOR_ID_DT		0x1116
+#define PCI_VENDOR_ID_IOTECH		0x1616
+#define PCI_VENDOR_ID_CONTEC		0x1221
+#define PCI_VENDOR_ID_RTD		0x1435
+#define PCI_VENDOR_ID_HUMUSOFT		0x186c
+
+struct pci_dev *comedi_to_pci_dev(struct comedi_device *dev);
+
+int comedi_pci_enable(struct comedi_device *dev);
+void comedi_pci_disable(struct comedi_device *dev);
+void comedi_pci_detach(struct comedi_device *dev);
+
+int comedi_pci_auto_config(struct pci_dev *pcidev, struct comedi_driver *driver,
+			   unsigned long context);
+void comedi_pci_auto_unconfig(struct pci_dev *pcidev);
+
+int comedi_pci_driver_register(struct comedi_driver *comedi_driver,
+			       struct pci_driver *pci_driver);
+void comedi_pci_driver_unregister(struct comedi_driver *comedi_driver,
+				  struct pci_driver *pci_driver);
+
+/**
+ * module_comedi_pci_driver() - Helper macro for registering a comedi PCI driver
+ * @__comedi_driver: comedi_driver struct
+ * @__pci_driver: pci_driver struct
+ *
+ * Helper macro for comedi PCI drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_pci_driver(__comedi_driver, __pci_driver) \
+	module_driver(__comedi_driver, comedi_pci_driver_register, \
+			comedi_pci_driver_unregister, &(__pci_driver))
+
+#endif /* _COMEDI_PCI_H */
diff --git a/drivers/comedi/comedi_pcmcia.c b/drivers/comedi/comedi_pcmcia.c
new file mode 100644
index 000000000000..bb273bb202e6
--- /dev/null
+++ b/drivers/comedi/comedi_pcmcia.c
@@ -0,0 +1,209 @@
+// 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 "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_pcmcia.h b/drivers/comedi/comedi_pcmcia.h
new file mode 100644
index 000000000000..f2f6e779645b
--- /dev/null
+++ b/drivers/comedi/comedi_pcmcia.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * comedi_pcmcia.h
+ * header file for Comedi PCMCIA drivers
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _COMEDI_PCMCIA_H
+#define _COMEDI_PCMCIA_H
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "comedidev.h"
+
+struct pcmcia_device *comedi_to_pcmcia_dev(struct comedi_device *dev);
+
+int comedi_pcmcia_enable(struct comedi_device *dev,
+			 int (*conf_check)(struct pcmcia_device *p_dev,
+					   void *priv_data));
+void comedi_pcmcia_disable(struct comedi_device *dev);
+
+int comedi_pcmcia_auto_config(struct pcmcia_device *link,
+			      struct comedi_driver *driver);
+void comedi_pcmcia_auto_unconfig(struct pcmcia_device *link);
+
+int comedi_pcmcia_driver_register(struct comedi_driver *comedi_driver,
+				  struct pcmcia_driver *pcmcia_driver);
+void comedi_pcmcia_driver_unregister(struct comedi_driver *comedi_driver,
+				     struct pcmcia_driver *pcmcia_driver);
+
+/**
+ * module_comedi_pcmcia_driver() - Helper macro for registering a comedi
+ * PCMCIA driver
+ * @__comedi_driver: comedi_driver struct
+ * @__pcmcia_driver: pcmcia_driver struct
+ *
+ * Helper macro for comedi PCMCIA drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_pcmcia_driver(__comedi_driver, __pcmcia_driver) \
+	module_driver(__comedi_driver, comedi_pcmcia_driver_register, \
+			comedi_pcmcia_driver_unregister, &(__pcmcia_driver))
+
+#endif /* _COMEDI_PCMCIA_H */
diff --git a/drivers/comedi/comedi_usb.c b/drivers/comedi/comedi_usb.c
new file mode 100644
index 000000000000..eea8ebf32ed0
--- /dev/null
+++ b/drivers/comedi/comedi_usb.c
@@ -0,0 +1,151 @@
+// 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 "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/comedi_usb.h b/drivers/comedi/comedi_usb.h
new file mode 100644
index 000000000000..601e29d3891c
--- /dev/null
+++ b/drivers/comedi/comedi_usb.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* comedi_usb.h
+ * header file for USB Comedi drivers
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _COMEDI_USB_H
+#define _COMEDI_USB_H
+
+#include <linux/usb.h>
+
+#include "comedidev.h"
+
+struct usb_interface *comedi_to_usb_interface(struct comedi_device *dev);
+struct usb_device *comedi_to_usb_dev(struct comedi_device *dev);
+
+int comedi_usb_auto_config(struct usb_interface *intf,
+			   struct comedi_driver *driver, unsigned long context);
+void comedi_usb_auto_unconfig(struct usb_interface *intf);
+
+int comedi_usb_driver_register(struct comedi_driver *comedi_driver,
+			       struct usb_driver *usb_driver);
+void comedi_usb_driver_unregister(struct comedi_driver *comedi_driver,
+				  struct usb_driver *usb_driver);
+
+/**
+ * module_comedi_usb_driver() - Helper macro for registering a comedi USB driver
+ * @__comedi_driver: comedi_driver struct
+ * @__usb_driver: usb_driver struct
+ *
+ * Helper macro for comedi USB drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_usb_driver(__comedi_driver, __usb_driver) \
+	module_driver(__comedi_driver, comedi_usb_driver_register, \
+			comedi_usb_driver_unregister, &(__usb_driver))
+
+#endif /* _COMEDI_USB_H */
diff --git a/drivers/comedi/comedidev.h b/drivers/comedi/comedidev.h
new file mode 100644
index 000000000000..0e1b95ef9a4d
--- /dev/null
+++ b/drivers/comedi/comedidev.h
@@ -0,0 +1,1054 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * comedidev.h
+ * header file for kernel-only structures, variables, and constants
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _COMEDIDEV_H
+#define _COMEDIDEV_H
+
+#include <linux/dma-mapping.h>
+#include <linux/mutex.h>
+#include <linux/spinlock_types.h>
+#include <linux/rwsem.h>
+#include <linux/kref.h>
+
+#include "comedi.h"
+
+#define COMEDI_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
+#define COMEDI_VERSION_CODE COMEDI_VERSION(COMEDI_MAJORVERSION, \
+	COMEDI_MINORVERSION, COMEDI_MICROVERSION)
+#define COMEDI_RELEASE VERSION
+
+#define COMEDI_NUM_BOARD_MINORS 0x30
+
+/**
+ * struct comedi_subdevice - Working data for a COMEDI subdevice
+ * @device: COMEDI device to which this subdevice belongs.  (Initialized by
+ *	comedi_alloc_subdevices().)
+ * @index: Index of this subdevice within device's array of subdevices.
+ *	(Initialized by comedi_alloc_subdevices().)
+ * @type: Type of subdevice from &enum comedi_subdevice_type.  (Initialized by
+ *	the low-level driver.)
+ * @n_chan: Number of channels the subdevice supports.  (Initialized by the
+ *	low-level driver.)
+ * @subdev_flags: Various "SDF" flags indicating aspects of the subdevice to
+ *	the COMEDI core and user application.  (Initialized by the low-level
+ *	driver.)
+ * @len_chanlist: Maximum length of a channel list if the subdevice supports
+ *	asynchronous acquisition commands.  (Optionally initialized by the
+ *	low-level driver, or changed from 0 to 1 during post-configuration.)
+ * @private: Private data pointer which is either set by the low-level driver
+ *	itself, or by a call to comedi_alloc_spriv() which allocates storage.
+ *	In the latter case, the storage is automatically freed after the
+ *	low-level driver's "detach" handler is called for the device.
+ *	(Initialized by the low-level driver.)
+ * @async: Pointer to &struct comedi_async id the subdevice supports
+ *	asynchronous acquisition commands.  (Allocated and initialized during
+ *	post-configuration if needed.)
+ * @lock: Pointer to a file object that performed a %COMEDI_LOCK ioctl on the
+ *	subdevice.  (Initially NULL.)
+ * @busy: Pointer to a file object that is performing an asynchronous
+ *	acquisition command on the subdevice.  (Initially NULL.)
+ * @runflags: Internal flags for use by COMEDI core, mostly indicating whether
+ *	an asynchronous acquisition command is running.
+ * @spin_lock: Generic spin-lock for use by the COMEDI core and the low-level
+ *	driver.  (Initialized by comedi_alloc_subdevices().)
+ * @io_bits: Bit-mask indicating the channel directions for a DIO subdevice
+ *	with no more than 32 channels.  A '1' at a bit position indicates the
+ *	corresponding channel is configured as an output.  (Initialized by the
+ *	low-level driver for a DIO subdevice.  Forced to all-outputs during
+ *	post-configuration for a digital output subdevice.)
+ * @maxdata: If non-zero, this is the maximum raw data value of each channel.
+ *	If zero, the maximum data value is channel-specific.  (Initialized by
+ *	the low-level driver.)
+ * @maxdata_list: If the maximum data value is channel-specific, this points
+ *	to an array of maximum data values indexed by channel index.
+ *	(Initialized by the low-level driver.)
+ * @range_table: If non-NULL, this points to a COMEDI range table for the
+ *	subdevice.  If NULL, the range table is channel-specific.  (Initialized
+ *	by the low-level driver, will be set to an "invalid" range table during
+ *	post-configuration if @range_table and @range_table_list are both
+ *	NULL.)
+ * @range_table_list: If the COMEDI range table is channel-specific, this
+ *	points to an array of pointers to COMEDI range tables indexed by
+ *	channel number.  (Initialized by the low-level driver.)
+ * @chanlist: Not used.
+ * @insn_read: Optional pointer to a handler for the %INSN_READ instruction.
+ *	(Initialized by the low-level driver, or set to a default handler
+ *	during post-configuration.)
+ * @insn_write: Optional pointer to a handler for the %INSN_WRITE instruction.
+ *	(Initialized by the low-level driver, or set to a default handler
+ *	during post-configuration.)
+ * @insn_bits: Optional pointer to a handler for the %INSN_BITS instruction
+ *	for a digital input, digital output or digital input/output subdevice.
+ *	(Initialized by the low-level driver, or set to a default handler
+ *	during post-configuration.)
+ * @insn_config: Optional pointer to a handler for the %INSN_CONFIG
+ *	instruction.  (Initialized by the low-level driver, or set to a default
+ *	handler during post-configuration.)
+ * @do_cmd: If the subdevice supports asynchronous acquisition commands, this
+ *	points to a handler to set it up in hardware.  (Initialized by the
+ *	low-level driver.)
+ * @do_cmdtest: If the subdevice supports asynchronous acquisition commands,
+ *	this points to a handler used to check and possibly tweak a prospective
+ *	acquisition command without setting it up in hardware.  (Initialized by
+ *	the low-level driver.)
+ * @poll: If the subdevice supports asynchronous acquisition commands, this
+ *	is an optional pointer to a handler for the %COMEDI_POLL ioctl which
+ *	instructs the low-level driver to synchronize buffers.  (Initialized by
+ *	the low-level driver if needed.)
+ * @cancel: If the subdevice supports asynchronous acquisition commands, this
+ *	points to a handler used to terminate a running command.  (Initialized
+ *	by the low-level driver.)
+ * @buf_change: If the subdevice supports asynchronous acquisition commands,
+ *	this is an optional pointer to a handler that is called when the data
+ *	buffer for handling asynchronous commands is allocated or reallocated.
+ *	(Initialized by the low-level driver if needed.)
+ * @munge: If the subdevice supports asynchronous acquisition commands and
+ *	uses DMA to transfer data from the hardware to the acquisition buffer,
+ *	this points to a function used to "munge" the data values from the
+ *	hardware into the format expected by COMEDI.  (Initialized by the
+ *	low-level driver if needed.)
+ * @async_dma_dir: If the subdevice supports asynchronous acquisition commands
+ *	and uses DMA to transfer data from the hardware to the acquisition
+ *	buffer, this sets the DMA direction for the buffer. (initialized to
+ *	%DMA_NONE by comedi_alloc_subdevices() and changed by the low-level
+ *	driver if necessary.)
+ * @state: Handy bit-mask indicating the output states for a DIO or digital
+ *	output subdevice with no more than 32 channels. (Initialized by the
+ *	low-level driver.)
+ * @class_dev: If the subdevice supports asynchronous acquisition commands,
+ *	this points to a sysfs comediX_subdY device where X is the minor device
+ *	number of the COMEDI device and Y is the subdevice number.  The minor
+ *	device number for the sysfs device is allocated dynamically in the
+ *	range 48 to 255.  This is used to allow the COMEDI device to be opened
+ *	with a different default read or write subdevice.  (Allocated during
+ *	post-configuration if needed.)
+ * @minor: If @class_dev is set, this is its dynamically allocated minor
+ *	device number.  (Set during post-configuration if necessary.)
+ * @readback: Optional pointer to memory allocated by
+ *	comedi_alloc_subdev_readback() used to hold the values written to
+ *	analog output channels so they can be read back.  The storage is
+ *	automatically freed after the low-level driver's "detach" handler is
+ *	called for the device.  (Initialized by the low-level driver.)
+ *
+ * This is the main control structure for a COMEDI subdevice.  If the subdevice
+ * supports asynchronous acquisition commands, additional information is stored
+ * in the &struct comedi_async pointed to by @async.
+ *
+ * Most of the subdevice is initialized by the low-level driver's "attach" or
+ * "auto_attach" handlers but parts of it are initialized by
+ * comedi_alloc_subdevices(), and other parts are initialized during
+ * post-configuration on return from that handler.
+ *
+ * A low-level driver that sets @insn_bits for a digital input, digital output,
+ * or DIO subdevice may leave @insn_read and @insn_write uninitialized, in
+ * which case they will be set to a default handler during post-configuration
+ * that uses @insn_bits to emulate the %INSN_READ and %INSN_WRITE instructions.
+ */
+struct comedi_subdevice {
+	struct comedi_device *device;
+	int index;
+	int type;
+	int n_chan;
+	int subdev_flags;
+	int len_chanlist;	/* maximum length of channel/gain list */
+
+	void *private;
+
+	struct comedi_async *async;
+
+	void *lock;
+	void *busy;
+	unsigned int runflags;
+	spinlock_t spin_lock;	/* generic spin-lock for COMEDI and drivers */
+
+	unsigned int io_bits;
+
+	unsigned int maxdata;	/* if maxdata==0, use list */
+	const unsigned int *maxdata_list;	/* list is channel specific */
+
+	const struct comedi_lrange *range_table;
+	const struct comedi_lrange *const *range_table_list;
+
+	unsigned int *chanlist;	/* driver-owned chanlist (not used) */
+
+	int (*insn_read)(struct comedi_device *dev, struct comedi_subdevice *s,
+			 struct comedi_insn *insn, unsigned int *data);
+	int (*insn_write)(struct comedi_device *dev, struct comedi_subdevice *s,
+			  struct comedi_insn *insn, unsigned int *data);
+	int (*insn_bits)(struct comedi_device *dev, struct comedi_subdevice *s,
+			 struct comedi_insn *insn, unsigned int *data);
+	int (*insn_config)(struct comedi_device *dev,
+			   struct comedi_subdevice *s,
+			   struct comedi_insn *insn,
+			   unsigned int *data);
+
+	int (*do_cmd)(struct comedi_device *dev, struct comedi_subdevice *s);
+	int (*do_cmdtest)(struct comedi_device *dev,
+			  struct comedi_subdevice *s,
+			  struct comedi_cmd *cmd);
+	int (*poll)(struct comedi_device *dev, struct comedi_subdevice *s);
+	int (*cancel)(struct comedi_device *dev, struct comedi_subdevice *s);
+
+	/* called when the buffer changes */
+	int (*buf_change)(struct comedi_device *dev,
+			  struct comedi_subdevice *s);
+
+	void (*munge)(struct comedi_device *dev, struct comedi_subdevice *s,
+		      void *data, unsigned int num_bytes,
+		      unsigned int start_chan_index);
+	enum dma_data_direction async_dma_dir;
+
+	unsigned int state;
+
+	struct device *class_dev;
+	int minor;
+
+	unsigned int *readback;
+};
+
+/**
+ * struct comedi_buf_page - Describe a page of a COMEDI buffer
+ * @virt_addr: Kernel address of page.
+ * @dma_addr: DMA address of page if in DMA coherent memory.
+ */
+struct comedi_buf_page {
+	void *virt_addr;
+	dma_addr_t dma_addr;
+};
+
+/**
+ * struct comedi_buf_map - Describe pages in a COMEDI buffer
+ * @dma_hw_dev: Low-level hardware &struct device pointer copied from the
+ *	COMEDI device's hw_dev member.
+ * @page_list: Pointer to array of &struct comedi_buf_page, one for each
+ *	page in the buffer.
+ * @n_pages: Number of pages in the buffer.
+ * @dma_dir: DMA direction used to allocate pages of DMA coherent memory,
+ *	or %DMA_NONE if pages allocated from regular memory.
+ * @refcount: &struct kref reference counter used to free the buffer.
+ *
+ * A COMEDI data buffer is allocated as individual pages, either in
+ * conventional memory or DMA coherent memory, depending on the attached,
+ * low-level hardware device.  (The buffer pages also get mapped into the
+ * kernel's contiguous virtual address space pointed to by the 'prealloc_buf'
+ * member of &struct comedi_async.)
+ *
+ * The buffer is normally freed when the COMEDI device is detached from the
+ * low-level driver (which may happen due to device removal), but if it happens
+ * to be mmapped at the time, the pages cannot be freed until the buffer has
+ * been munmapped.  That is what the reference counter is for.  (The virtual
+ * address space pointed by 'prealloc_buf' is freed when the COMEDI device is
+ * detached.)
+ */
+struct comedi_buf_map {
+	struct device *dma_hw_dev;
+	struct comedi_buf_page *page_list;
+	unsigned int n_pages;
+	enum dma_data_direction dma_dir;
+	struct kref refcount;
+};
+
+/**
+ * struct comedi_async - Control data for asynchronous COMEDI commands
+ * @prealloc_buf: Kernel virtual address of allocated acquisition buffer.
+ * @prealloc_bufsz: Buffer size (in bytes).
+ * @buf_map: Map of buffer pages.
+ * @max_bufsize: Maximum allowed buffer size (in bytes).
+ * @buf_write_count: "Write completed" count (in bytes, modulo 2**32).
+ * @buf_write_alloc_count: "Allocated for writing" count (in bytes,
+ *	modulo 2**32).
+ * @buf_read_count: "Read completed" count (in bytes, modulo 2**32).
+ * @buf_read_alloc_count: "Allocated for reading" count (in bytes,
+ *	modulo 2**32).
+ * @buf_write_ptr: Buffer position for writer.
+ * @buf_read_ptr: Buffer position for reader.
+ * @cur_chan: Current position in chanlist for scan (for those drivers that
+ *	use it).
+ * @scans_done: The number of scans completed.
+ * @scan_progress: Amount received or sent for current scan (in bytes).
+ * @munge_chan: Current position in chanlist for "munging".
+ * @munge_count: "Munge" count (in bytes, modulo 2**32).
+ * @munge_ptr: Buffer position for "munging".
+ * @events: Bit-vector of events that have occurred.
+ * @cmd: Details of comedi command in progress.
+ * @wait_head: Task wait queue for file reader or writer.
+ * @cb_mask: Bit-vector of events that should wake waiting tasks.
+ * @inttrig: Software trigger function for command, or NULL.
+ *
+ * Note about the ..._count and ..._ptr members:
+ *
+ * Think of the _Count values being integers of unlimited size, indexing
+ * into a buffer of infinite length (though only an advancing portion
+ * of the buffer of fixed length prealloc_bufsz is accessible at any
+ * time).  Then:
+ *
+ *   Buf_Read_Count <= Buf_Read_Alloc_Count <= Munge_Count <=
+ *   Buf_Write_Count <= Buf_Write_Alloc_Count <=
+ *   (Buf_Read_Count + prealloc_bufsz)
+ *
+ * (Those aren't the actual members, apart from prealloc_bufsz.) When the
+ * buffer is reset, those _Count values start at 0 and only increase in value,
+ * maintaining the above inequalities until the next time the buffer is
+ * reset.  The buffer is divided into the following regions by the inequalities:
+ *
+ *   [0, Buf_Read_Count):
+ *     old region no longer accessible
+ *
+ *   [Buf_Read_Count, Buf_Read_Alloc_Count):
+ *     filled and munged region allocated for reading but not yet read
+ *
+ *   [Buf_Read_Alloc_Count, Munge_Count):
+ *     filled and munged region not yet allocated for reading
+ *
+ *   [Munge_Count, Buf_Write_Count):
+ *     filled region not yet munged
+ *
+ *   [Buf_Write_Count, Buf_Write_Alloc_Count):
+ *     unfilled region allocated for writing but not yet written
+ *
+ *   [Buf_Write_Alloc_Count, Buf_Read_Count + prealloc_bufsz):
+ *     unfilled region not yet allocated for writing
+ *
+ *   [Buf_Read_Count + prealloc_bufsz, infinity):
+ *     unfilled region not yet accessible
+ *
+ * Data needs to be written into the buffer before it can be read out,
+ * and may need to be converted (or "munged") between the two
+ * operations.  Extra unfilled buffer space may need to allocated for
+ * writing (advancing Buf_Write_Alloc_Count) before new data is written.
+ * After writing new data, the newly filled space needs to be released
+ * (advancing Buf_Write_Count).  This also results in the new data being
+ * "munged" (advancing Munge_Count).  Before data is read out of the
+ * buffer, extra space may need to be allocated for reading (advancing
+ * Buf_Read_Alloc_Count).  After the data has been read out, the space
+ * needs to be released (advancing Buf_Read_Count).
+ *
+ * The actual members, buf_read_count, buf_read_alloc_count,
+ * munge_count, buf_write_count, and buf_write_alloc_count take the
+ * value of the corresponding capitalized _Count values modulo 2^32
+ * (UINT_MAX+1).  Subtracting a "higher" _count value from a "lower"
+ * _count value gives the same answer as subtracting a "higher" _Count
+ * value from a lower _Count value because prealloc_bufsz < UINT_MAX+1.
+ * The modulo operation is done implicitly.
+ *
+ * The buf_read_ptr, munge_ptr, and buf_write_ptr members take the value
+ * of the corresponding capitalized _Count values modulo prealloc_bufsz.
+ * These correspond to byte indices in the physical buffer.  The modulo
+ * operation is done by subtracting prealloc_bufsz when the value
+ * exceeds prealloc_bufsz (assuming prealloc_bufsz plus the increment is
+ * less than or equal to UINT_MAX).
+ */
+struct comedi_async {
+	void *prealloc_buf;
+	unsigned int prealloc_bufsz;
+	struct comedi_buf_map *buf_map;
+	unsigned int max_bufsize;
+	unsigned int buf_write_count;
+	unsigned int buf_write_alloc_count;
+	unsigned int buf_read_count;
+	unsigned int buf_read_alloc_count;
+	unsigned int buf_write_ptr;
+	unsigned int buf_read_ptr;
+	unsigned int cur_chan;
+	unsigned int scans_done;
+	unsigned int scan_progress;
+	unsigned int munge_chan;
+	unsigned int munge_count;
+	unsigned int munge_ptr;
+	unsigned int events;
+	struct comedi_cmd cmd;
+	wait_queue_head_t wait_head;
+	unsigned int cb_mask;
+	int (*inttrig)(struct comedi_device *dev, struct comedi_subdevice *s,
+		       unsigned int x);
+};
+
+/**
+ * enum comedi_cb - &struct comedi_async callback "events"
+ * @COMEDI_CB_EOS:		end-of-scan
+ * @COMEDI_CB_EOA:		end-of-acquisition/output
+ * @COMEDI_CB_BLOCK:		data has arrived, wakes up read() / write()
+ * @COMEDI_CB_EOBUF:		DEPRECATED: end of buffer
+ * @COMEDI_CB_ERROR:		card error during acquisition
+ * @COMEDI_CB_OVERFLOW:		buffer overflow/underflow
+ * @COMEDI_CB_ERROR_MASK:	events that indicate an error has occurred
+ * @COMEDI_CB_CANCEL_MASK:	events that will cancel an async command
+ */
+enum comedi_cb {
+	COMEDI_CB_EOS		= BIT(0),
+	COMEDI_CB_EOA		= BIT(1),
+	COMEDI_CB_BLOCK		= BIT(2),
+	COMEDI_CB_EOBUF		= BIT(3),
+	COMEDI_CB_ERROR		= BIT(4),
+	COMEDI_CB_OVERFLOW	= BIT(5),
+	/* masks */
+	COMEDI_CB_ERROR_MASK	= (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW),
+	COMEDI_CB_CANCEL_MASK	= (COMEDI_CB_EOA | COMEDI_CB_ERROR_MASK)
+};
+
+/**
+ * struct comedi_driver - COMEDI driver registration
+ * @driver_name: Name of driver.
+ * @module: Owning module.
+ * @attach: The optional "attach" handler for manually configured COMEDI
+ *	devices.
+ * @detach: The "detach" handler for deconfiguring COMEDI devices.
+ * @auto_attach: The optional "auto_attach" handler for automatically
+ *	configured COMEDI devices.
+ * @num_names: Optional number of "board names" supported.
+ * @board_name: Optional pointer to a pointer to a board name.  The pointer
+ *	to a board name is embedded in an element of a driver-defined array
+ *	of static, read-only board type information.
+ * @offset: Optional size of each element of the driver-defined array of
+ *	static, read-only board type information, i.e. the offset between each
+ *	pointer to a board name.
+ *
+ * This is used with comedi_driver_register() and comedi_driver_unregister() to
+ * register and unregister a low-level COMEDI driver with the COMEDI core.
+ *
+ * If @num_names is non-zero, @board_name should be non-NULL, and @offset
+ * should be at least sizeof(*board_name).  These are used by the handler for
+ * the %COMEDI_DEVCONFIG ioctl to match a hardware device and its driver by
+ * board name.  If @num_names is zero, the %COMEDI_DEVCONFIG ioctl matches a
+ * hardware device and its driver by driver name.  This is only useful if the
+ * @attach handler is set.  If @num_names is non-zero, the driver's @attach
+ * handler will be called with the COMEDI device structure's board_ptr member
+ * pointing to the matched pointer to a board name within the driver's private
+ * array of static, read-only board type information.
+ *
+ * The @detach handler has two roles.  If a COMEDI device was successfully
+ * configured by the @attach or @auto_attach handler, it is called when the
+ * device is being deconfigured (by the %COMEDI_DEVCONFIG ioctl, or due to
+ * unloading of the driver, or due to device removal).  It is also called when
+ * the @attach or @auto_attach handler returns an error.  Therefore, the
+ * @attach or @auto_attach handlers can defer clean-up on error until the
+ * @detach handler is called.  If the @attach or @auto_attach handlers free
+ * any resources themselves, they must prevent the @detach handler from
+ * freeing the same resources.  The @detach handler must not assume that all
+ * resources requested by the @attach or @auto_attach handler were
+ * successfully allocated.
+ */
+struct comedi_driver {
+	/* private: */
+	struct comedi_driver *next;	/* Next in list of COMEDI drivers. */
+	/* public: */
+	const char *driver_name;
+	struct module *module;
+	int (*attach)(struct comedi_device *dev, struct comedi_devconfig *it);
+	void (*detach)(struct comedi_device *dev);
+	int (*auto_attach)(struct comedi_device *dev, unsigned long context);
+	unsigned int num_names;
+	const char *const *board_name;
+	int offset;
+};
+
+/**
+ * struct comedi_device - Working data for a COMEDI device
+ * @use_count: Number of open file objects.
+ * @driver: Low-level COMEDI driver attached to this COMEDI device.
+ * @pacer: Optional pointer to a dynamically allocated acquisition pacer
+ *	control.  It is freed automatically after the COMEDI device is
+ *	detached from the low-level driver.
+ * @private: Optional pointer to private data allocated by the low-level
+ *	driver.  It is freed automatically after the COMEDI device is
+ *	detached from the low-level driver.
+ * @class_dev: Sysfs comediX device.
+ * @minor: Minor device number of COMEDI char device (0-47).
+ * @detach_count: Counter incremented every time the COMEDI device is detached.
+ *	Used for checking a previous attachment is still valid.
+ * @hw_dev: Optional pointer to the low-level hardware &struct device.  It is
+ *	required for automatically configured COMEDI devices and optional for
+ *	COMEDI devices configured by the %COMEDI_DEVCONFIG ioctl, although
+ *	the bus-specific COMEDI functions only work if it is set correctly.
+ *	It is also passed to dma_alloc_coherent() for COMEDI subdevices that
+ *	have their 'async_dma_dir' member set to something other than
+ *	%DMA_NONE.
+ * @board_name: Pointer to a COMEDI board name or a COMEDI driver name.  When
+ *	the low-level driver's "attach" handler is called by the handler for
+ *	the %COMEDI_DEVCONFIG ioctl, it either points to a matched board name
+ *	string if the 'num_names' member of the &struct comedi_driver is
+ *	non-zero, otherwise it points to the low-level driver name string.
+ *	When the low-lever driver's "auto_attach" handler is called for an
+ *	automatically configured COMEDI device, it points to the low-level
+ *	driver name string.  The low-level driver is free to change it in its
+ *	"attach" or "auto_attach" handler if it wishes.
+ * @board_ptr: Optional pointer to private, read-only board type information in
+ *	the low-level driver.  If the 'num_names' member of the &struct
+ *	comedi_driver is non-zero, the handler for the %COMEDI_DEVCONFIG ioctl
+ *	will point it to a pointer to a matched board name string within the
+ *	driver's private array of static, read-only board type information when
+ *	calling the driver's "attach" handler.  The low-level driver is free to
+ *	change it.
+ * @attached: Flag indicating that the COMEDI device is attached to a low-level
+ *	driver.
+ * @ioenabled: Flag used to indicate that a PCI device has been enabled and
+ *	its regions requested.
+ * @spinlock: Generic spin-lock for use by the low-level driver.
+ * @mutex: Generic mutex for use by the COMEDI core module.
+ * @attach_lock: &struct rw_semaphore used to guard against the COMEDI device
+ *	being detached while an operation is in progress.  The down_write()
+ *	operation is only allowed while @mutex is held and is used when
+ *	changing @attached and @detach_count and calling the low-level driver's
+ *	"detach" handler.  The down_read() operation is generally used without
+ *	holding @mutex.
+ * @refcount: &struct kref reference counter for freeing COMEDI device.
+ * @n_subdevices: Number of COMEDI subdevices allocated by the low-level
+ *	driver for this device.
+ * @subdevices: Dynamically allocated array of COMEDI subdevices.
+ * @mmio: Optional pointer to a remapped MMIO region set by the low-level
+ *	driver.
+ * @iobase: Optional base of an I/O port region requested by the low-level
+ *	driver.
+ * @iolen: Length of I/O port region requested at @iobase.
+ * @irq: Optional IRQ number requested by the low-level driver.
+ * @read_subdev: Optional pointer to a default COMEDI subdevice operated on by
+ *	the read() file operation.  Set by the low-level driver.
+ * @write_subdev: Optional pointer to a default COMEDI subdevice operated on by
+ *	the write() file operation.  Set by the low-level driver.
+ * @async_queue: Storage for fasync_helper().
+ * @open: Optional pointer to a function set by the low-level driver to be
+ *	called when @use_count changes from 0 to 1.
+ * @close: Optional pointer to a function set by the low-level driver to be
+ *	called when @use_count changed from 1 to 0.
+ * @insn_device_config: Optional pointer to a handler for all sub-instructions
+ *	except %INSN_DEVICE_CONFIG_GET_ROUTES of the %INSN_DEVICE_CONFIG
+ *	instruction.  If this is not initialized by the low-level driver, a
+ *	default handler will be set during post-configuration.
+ * @get_valid_routes: Optional pointer to a handler for the
+ *	%INSN_DEVICE_CONFIG_GET_ROUTES sub-instruction of the
+ *	%INSN_DEVICE_CONFIG instruction set.  If this is not initialized by the
+ *	low-level driver, a default handler that copies zero routes back to the
+ *	user will be used.
+ *
+ * This is the main control data structure for a COMEDI device (as far as the
+ * COMEDI core is concerned).  There are two groups of COMEDI devices -
+ * "legacy" devices that are configured by the handler for the
+ * %COMEDI_DEVCONFIG ioctl, and automatically configured devices resulting
+ * from a call to comedi_auto_config() as a result of a bus driver probe in
+ * a low-level COMEDI driver.  The "legacy" COMEDI devices are allocated
+ * during module initialization if the "comedi_num_legacy_minors" module
+ * parameter is non-zero and use minor device numbers from 0 to
+ * comedi_num_legacy_minors minus one.  The automatically configured COMEDI
+ * devices are allocated on demand and use minor device numbers from
+ * comedi_num_legacy_minors to 47.
+ */
+struct comedi_device {
+	int use_count;
+	struct comedi_driver *driver;
+	struct comedi_8254 *pacer;
+	void *private;
+
+	struct device *class_dev;
+	int minor;
+	unsigned int detach_count;
+	struct device *hw_dev;
+
+	const char *board_name;
+	const void *board_ptr;
+	unsigned int attached:1;
+	unsigned int ioenabled:1;
+	spinlock_t spinlock;	/* generic spin-lock for low-level driver */
+	struct mutex mutex;	/* generic mutex for COMEDI core */
+	struct rw_semaphore attach_lock;
+	struct kref refcount;
+
+	int n_subdevices;
+	struct comedi_subdevice *subdevices;
+
+	/* dumb */
+	void __iomem *mmio;
+	unsigned long iobase;
+	unsigned long iolen;
+	unsigned int irq;
+
+	struct comedi_subdevice *read_subdev;
+	struct comedi_subdevice *write_subdev;
+
+	struct fasync_struct *async_queue;
+
+	int (*open)(struct comedi_device *dev);
+	void (*close)(struct comedi_device *dev);
+	int (*insn_device_config)(struct comedi_device *dev,
+				  struct comedi_insn *insn, unsigned int *data);
+	unsigned int (*get_valid_routes)(struct comedi_device *dev,
+					 unsigned int n_pairs,
+					 unsigned int *pair_data);
+};
+
+/*
+ * function prototypes
+ */
+
+void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s);
+
+struct comedi_device *comedi_dev_get_from_minor(unsigned int minor);
+int comedi_dev_put(struct comedi_device *dev);
+
+bool comedi_is_subdevice_running(struct comedi_subdevice *s);
+
+void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size);
+void comedi_set_spriv_auto_free(struct comedi_subdevice *s);
+
+int comedi_check_chanlist(struct comedi_subdevice *s,
+			  int n,
+			  unsigned int *chanlist);
+
+/* range stuff */
+
+#define RANGE(a, b)		{(a) * 1e6, (b) * 1e6, 0}
+#define RANGE_ext(a, b)		{(a) * 1e6, (b) * 1e6, RF_EXTERNAL}
+#define RANGE_mA(a, b)		{(a) * 1e6, (b) * 1e6, UNIT_mA}
+#define RANGE_unitless(a, b)	{(a) * 1e6, (b) * 1e6, 0}
+#define BIP_RANGE(a)		{-(a) * 1e6, (a) * 1e6, 0}
+#define UNI_RANGE(a)		{0, (a) * 1e6, 0}
+
+extern const struct comedi_lrange range_bipolar10;
+extern const struct comedi_lrange range_bipolar5;
+extern const struct comedi_lrange range_bipolar2_5;
+extern const struct comedi_lrange range_unipolar10;
+extern const struct comedi_lrange range_unipolar5;
+extern const struct comedi_lrange range_unipolar2_5;
+extern const struct comedi_lrange range_0_20mA;
+extern const struct comedi_lrange range_4_20mA;
+extern const struct comedi_lrange range_0_32mA;
+extern const struct comedi_lrange range_unknown;
+
+#define range_digital		range_unipolar5
+
+/**
+ * struct comedi_lrange - Describes a COMEDI range table
+ * @length: Number of entries in the range table.
+ * @range: Array of &struct comedi_krange, one for each range.
+ *
+ * Each element of @range[] describes the minimum and maximum physical range
+ * and the type of units.  Typically, the type of unit is %UNIT_volt
+ * (i.e. volts) and the minimum and maximum are in millionths of a volt.
+ * There may also be a flag that indicates the minimum and maximum are merely
+ * scale factors for an unknown, external reference.
+ */
+struct comedi_lrange {
+	int length;
+	struct comedi_krange range[];
+};
+
+/**
+ * comedi_range_is_bipolar() - Test if subdevice range is bipolar
+ * @s: COMEDI subdevice.
+ * @range: Index of range within a range table.
+ *
+ * Tests whether a range is bipolar by checking whether its minimum value
+ * is negative.
+ *
+ * Assumes @range is valid.  Does not work for subdevices using a
+ * channel-specific range table list.
+ *
+ * Return:
+ *	%true if the range is bipolar.
+ *	%false if the range is unipolar.
+ */
+static inline bool comedi_range_is_bipolar(struct comedi_subdevice *s,
+					   unsigned int range)
+{
+	return s->range_table->range[range].min < 0;
+}
+
+/**
+ * comedi_range_is_unipolar() - Test if subdevice range is unipolar
+ * @s: COMEDI subdevice.
+ * @range: Index of range within a range table.
+ *
+ * Tests whether a range is unipolar by checking whether its minimum value
+ * is at least 0.
+ *
+ * Assumes @range is valid.  Does not work for subdevices using a
+ * channel-specific range table list.
+ *
+ * Return:
+ *	%true if the range is unipolar.
+ *	%false if the range is bipolar.
+ */
+static inline bool comedi_range_is_unipolar(struct comedi_subdevice *s,
+					    unsigned int range)
+{
+	return s->range_table->range[range].min >= 0;
+}
+
+/**
+ * comedi_range_is_external() - Test if subdevice range is external
+ * @s: COMEDI subdevice.
+ * @range: Index of range within a range table.
+ *
+ * Tests whether a range is externally reference by checking whether its
+ * %RF_EXTERNAL flag is set.
+ *
+ * Assumes @range is valid.  Does not work for subdevices using a
+ * channel-specific range table list.
+ *
+ * Return:
+ *	%true if the range is external.
+ *	%false if the range is internal.
+ */
+static inline bool comedi_range_is_external(struct comedi_subdevice *s,
+					    unsigned int range)
+{
+	return !!(s->range_table->range[range].flags & RF_EXTERNAL);
+}
+
+/**
+ * comedi_chan_range_is_bipolar() - Test if channel-specific range is bipolar
+ * @s: COMEDI subdevice.
+ * @chan: The channel number.
+ * @range: Index of range within a range table.
+ *
+ * Tests whether a range is bipolar by checking whether its minimum value
+ * is negative.
+ *
+ * Assumes @chan and @range are valid.  Only works for subdevices with a
+ * channel-specific range table list.
+ *
+ * Return:
+ *	%true if the range is bipolar.
+ *	%false if the range is unipolar.
+ */
+static inline bool comedi_chan_range_is_bipolar(struct comedi_subdevice *s,
+						unsigned int chan,
+						unsigned int range)
+{
+	return s->range_table_list[chan]->range[range].min < 0;
+}
+
+/**
+ * comedi_chan_range_is_unipolar() - Test if channel-specific range is unipolar
+ * @s: COMEDI subdevice.
+ * @chan: The channel number.
+ * @range: Index of range within a range table.
+ *
+ * Tests whether a range is unipolar by checking whether its minimum value
+ * is at least 0.
+ *
+ * Assumes @chan and @range are valid.  Only works for subdevices with a
+ * channel-specific range table list.
+ *
+ * Return:
+ *	%true if the range is unipolar.
+ *	%false if the range is bipolar.
+ */
+static inline bool comedi_chan_range_is_unipolar(struct comedi_subdevice *s,
+						 unsigned int chan,
+						 unsigned int range)
+{
+	return s->range_table_list[chan]->range[range].min >= 0;
+}
+
+/**
+ * comedi_chan_range_is_external() - Test if channel-specific range is external
+ * @s: COMEDI subdevice.
+ * @chan: The channel number.
+ * @range: Index of range within a range table.
+ *
+ * Tests whether a range is externally reference by checking whether its
+ * %RF_EXTERNAL flag is set.
+ *
+ * Assumes @chan and @range are valid.  Only works for subdevices with a
+ * channel-specific range table list.
+ *
+ * Return:
+ *	%true if the range is bipolar.
+ *	%false if the range is unipolar.
+ */
+static inline bool comedi_chan_range_is_external(struct comedi_subdevice *s,
+						 unsigned int chan,
+						 unsigned int range)
+{
+	return !!(s->range_table_list[chan]->range[range].flags & RF_EXTERNAL);
+}
+
+/**
+ * comedi_offset_munge() - Convert between offset binary and 2's complement
+ * @s: COMEDI subdevice.
+ * @val: Value to be converted.
+ *
+ * Toggles the highest bit of a sample value to toggle between offset binary
+ * and 2's complement.  Assumes that @s->maxdata is a power of 2 minus 1.
+ *
+ * Return: The converted value.
+ */
+static inline unsigned int comedi_offset_munge(struct comedi_subdevice *s,
+					       unsigned int val)
+{
+	return val ^ s->maxdata ^ (s->maxdata >> 1);
+}
+
+/**
+ * comedi_bytes_per_sample() - Determine subdevice sample size
+ * @s: COMEDI subdevice.
+ *
+ * The sample size will be 4 (sizeof int) or 2 (sizeof short) depending on
+ * whether the %SDF_LSAMPL subdevice flag is set or not.
+ *
+ * Return: The subdevice sample size.
+ */
+static inline unsigned int comedi_bytes_per_sample(struct comedi_subdevice *s)
+{
+	return s->subdev_flags & SDF_LSAMPL ? sizeof(int) : sizeof(short);
+}
+
+/**
+ * comedi_sample_shift() - Determine log2 of subdevice sample size
+ * @s: COMEDI subdevice.
+ *
+ * The sample size will be 4 (sizeof int) or 2 (sizeof short) depending on
+ * whether the %SDF_LSAMPL subdevice flag is set or not.  The log2 of the
+ * sample size will be 2 or 1 and can be used as the right operand of a
+ * bit-shift operator to multiply or divide something by the sample size.
+ *
+ * Return: log2 of the subdevice sample size.
+ */
+static inline unsigned int comedi_sample_shift(struct comedi_subdevice *s)
+{
+	return s->subdev_flags & SDF_LSAMPL ? 2 : 1;
+}
+
+/**
+ * comedi_bytes_to_samples() - Convert a number of bytes to a number of samples
+ * @s: COMEDI subdevice.
+ * @nbytes: Number of bytes
+ *
+ * Return: The number of bytes divided by the subdevice sample size.
+ */
+static inline unsigned int comedi_bytes_to_samples(struct comedi_subdevice *s,
+						   unsigned int nbytes)
+{
+	return nbytes >> comedi_sample_shift(s);
+}
+
+/**
+ * comedi_samples_to_bytes() - Convert a number of samples to a number of bytes
+ * @s: COMEDI subdevice.
+ * @nsamples: Number of samples.
+ *
+ * Return: The number of samples multiplied by the subdevice sample size.
+ * (Does not check for arithmetic overflow.)
+ */
+static inline unsigned int comedi_samples_to_bytes(struct comedi_subdevice *s,
+						   unsigned int nsamples)
+{
+	return nsamples << comedi_sample_shift(s);
+}
+
+/**
+ * comedi_check_trigger_src() - Trivially validate a comedi_cmd trigger source
+ * @src: Pointer to the trigger source to validate.
+ * @flags: Bitmask of valid %TRIG_* for the trigger.
+ *
+ * This is used in "step 1" of the do_cmdtest functions of comedi drivers
+ * to validate the comedi_cmd triggers. The mask of the @src against the
+ * @flags allows the userspace comedilib to pass all the comedi_cmd
+ * triggers as %TRIG_ANY and get back a bitmask of the valid trigger sources.
+ *
+ * Return:
+ *	0 if trigger sources in *@src are all supported.
+ *	-EINVAL if any trigger source in *@src is unsupported.
+ */
+static inline int comedi_check_trigger_src(unsigned int *src,
+					   unsigned int flags)
+{
+	unsigned int orig_src = *src;
+
+	*src = orig_src & flags;
+	if (*src == TRIG_INVALID || *src != orig_src)
+		return -EINVAL;
+	return 0;
+}
+
+/**
+ * comedi_check_trigger_is_unique() - Make sure a trigger source is unique
+ * @src: The trigger source to check.
+ *
+ * Return:
+ *	0 if no more than one trigger source is set.
+ *	-EINVAL if more than one trigger source is set.
+ */
+static inline int comedi_check_trigger_is_unique(unsigned int src)
+{
+	/* this test is true if more than one _src bit is set */
+	if ((src & (src - 1)) != 0)
+		return -EINVAL;
+	return 0;
+}
+
+/**
+ * comedi_check_trigger_arg_is() - Trivially validate a trigger argument
+ * @arg: Pointer to the trigger arg to validate.
+ * @val: The value the argument should be.
+ *
+ * Forces *@arg to be @val.
+ *
+ * Return:
+ *	0 if *@arg was already @val.
+ *	-EINVAL if *@arg differed from @val.
+ */
+static inline int comedi_check_trigger_arg_is(unsigned int *arg,
+					      unsigned int val)
+{
+	if (*arg != val) {
+		*arg = val;
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * comedi_check_trigger_arg_min() - Trivially validate a trigger argument min
+ * @arg: Pointer to the trigger arg to validate.
+ * @val: The minimum value the argument should be.
+ *
+ * Forces *@arg to be at least @val, setting it to @val if necessary.
+ *
+ * Return:
+ *	0 if *@arg was already at least @val.
+ *	-EINVAL if *@arg was less than @val.
+ */
+static inline int comedi_check_trigger_arg_min(unsigned int *arg,
+					       unsigned int val)
+{
+	if (*arg < val) {
+		*arg = val;
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * comedi_check_trigger_arg_max() - Trivially validate a trigger argument max
+ * @arg: Pointer to the trigger arg to validate.
+ * @val: The maximum value the argument should be.
+ *
+ * Forces *@arg to be no more than @val, setting it to @val if necessary.
+ *
+ * Return:
+ *	0 if*@arg was already no more than @val.
+ *	-EINVAL if *@arg was greater than @val.
+ */
+static inline int comedi_check_trigger_arg_max(unsigned int *arg,
+					       unsigned int val)
+{
+	if (*arg > val) {
+		*arg = val;
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Must set dev->hw_dev if you wish to dma directly into comedi's buffer.
+ * Also useful for retrieving a previously configured hardware device of
+ * known bus type.  Set automatically for auto-configured devices.
+ * Automatically set to NULL when detaching hardware device.
+ */
+int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev);
+
+/**
+ * comedi_buf_n_bytes_ready - Determine amount of unread data in buffer
+ * @s: COMEDI subdevice.
+ *
+ * Determines the number of bytes of unread data in the asynchronous
+ * acquisition data buffer for a subdevice.  The data in question might not
+ * have been fully "munged" yet.
+ *
+ * Returns: The amount of unread data in bytes.
+ */
+static inline unsigned int comedi_buf_n_bytes_ready(struct comedi_subdevice *s)
+{
+	return s->async->buf_write_count - s->async->buf_read_count;
+}
+
+unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, unsigned int n);
+unsigned int comedi_buf_write_free(struct comedi_subdevice *s, unsigned int n);
+
+unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s);
+unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s, unsigned int n);
+unsigned int comedi_buf_read_free(struct comedi_subdevice *s, unsigned int n);
+
+unsigned int comedi_buf_write_samples(struct comedi_subdevice *s,
+				      const void *data, unsigned int nsamples);
+unsigned int comedi_buf_read_samples(struct comedi_subdevice *s,
+				     void *data, unsigned int nsamples);
+
+/* drivers.c - general comedi driver functions */
+
+#define COMEDI_TIMEOUT_MS	1000
+
+int comedi_timeout(struct comedi_device *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 int comedi_handle_events(struct comedi_device *dev,
+				  struct comedi_subdevice *s);
+
+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 comedi_dio_update_state(struct comedi_subdevice *s,
+				     unsigned int *data);
+unsigned int comedi_bytes_per_scan_cmd(struct comedi_subdevice *s,
+				       struct comedi_cmd *cmd);
+unsigned int comedi_bytes_per_scan(struct comedi_subdevice *s);
+unsigned int comedi_nscans_left(struct comedi_subdevice *s,
+				unsigned int nscans);
+unsigned int comedi_nsamples_left(struct comedi_subdevice *s,
+				  unsigned int nsamples);
+void comedi_inc_scan_progress(struct comedi_subdevice *s,
+			      unsigned int num_bytes);
+
+void *comedi_alloc_devpriv(struct comedi_device *dev, size_t size);
+int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices);
+int comedi_alloc_subdev_readback(struct comedi_subdevice *s);
+
+int comedi_readback_insn_read(struct comedi_device *dev,
+			      struct comedi_subdevice *s,
+			      struct comedi_insn *insn, unsigned int *data);
+
+int comedi_load_firmware(struct comedi_device *dev, struct device *hw_dev,
+			 const char *name,
+			 int (*cb)(struct comedi_device *dev,
+				   const u8 *data, size_t size,
+				   unsigned long context),
+			 unsigned long context);
+
+int __comedi_request_region(struct comedi_device *dev,
+			    unsigned long start, unsigned long len);
+int comedi_request_region(struct comedi_device *dev,
+			  unsigned long start, unsigned long len);
+void comedi_legacy_detach(struct comedi_device *dev);
+
+int comedi_auto_config(struct device *hardware_device,
+		       struct comedi_driver *driver, unsigned long context);
+void comedi_auto_unconfig(struct device *hardware_device);
+
+int comedi_driver_register(struct comedi_driver *driver);
+void comedi_driver_unregister(struct comedi_driver *driver);
+
+/**
+ * module_comedi_driver() - Helper macro for registering a comedi driver
+ * @__comedi_driver: comedi_driver struct
+ *
+ * Helper macro for comedi drivers which do not do anything special in module
+ * init/exit. This eliminates a lot of boilerplate. Each module may only use
+ * this macro once, and calling it replaces module_init() and module_exit().
+ */
+#define module_comedi_driver(__comedi_driver) \
+	module_driver(__comedi_driver, comedi_driver_register, \
+			comedi_driver_unregister)
+
+#endif /* _COMEDIDEV_H */
diff --git a/drivers/comedi/comedilib.h b/drivers/comedi/comedilib.h
new file mode 100644
index 000000000000..0223c9cd9215
--- /dev/null
+++ b/drivers/comedi/comedilib.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * comedilib.h
+ * Header file for kcomedilib
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _LINUX_COMEDILIB_H
+#define _LINUX_COMEDILIB_H
+
+struct comedi_device *comedi_open(const char *path);
+int comedi_close(struct comedi_device *dev);
+int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
+			  unsigned int chan, unsigned int *io);
+int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
+		      unsigned int chan, unsigned int io);
+int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
+			 unsigned int mask, unsigned int *bits,
+			 unsigned int base_channel);
+int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
+				  unsigned int subd);
+int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice);
+
+#endif
diff --git a/drivers/comedi/drivers.c b/drivers/comedi/drivers.c
new file mode 100644
index 000000000000..750a6ff3c03c
--- /dev/null
+++ b/drivers/comedi/drivers.c
@@ -0,0 +1,1184 @@
+// 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 "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 ret < 0 ? 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 000000000000..e23335c75867
--- /dev/null
+++ b/drivers/comedi/drivers/8255.c
@@ -0,0 +1,125 @@
+// 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 "../comedidev.h"
+
+#include "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.h b/drivers/comedi/drivers/8255.h
new file mode 100644
index 000000000000..ceae3ca52e60
--- /dev/null
+++ b/drivers/comedi/drivers/8255.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * module/8255.h
+ * Header file for 8255
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _8255_H
+#define _8255_H
+
+#define I8255_SIZE		0x04
+
+#define I8255_DATA_A_REG	0x00
+#define I8255_DATA_B_REG	0x01
+#define I8255_DATA_C_REG	0x02
+#define I8255_CTRL_REG		0x03
+#define I8255_CTRL_C_LO_IO	BIT(0)
+#define I8255_CTRL_B_IO		BIT(1)
+#define I8255_CTRL_B_MODE	BIT(2)
+#define I8255_CTRL_C_HI_IO	BIT(3)
+#define I8255_CTRL_A_IO		BIT(4)
+#define I8255_CTRL_A_MODE(x)	((x) << 5)
+#define I8255_CTRL_CW		BIT(7)
+
+struct comedi_device;
+struct comedi_subdevice;
+
+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);
+
+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);
+
+unsigned long subdev_8255_regbase(struct comedi_subdevice *s);
+
+#endif
diff --git a/drivers/comedi/drivers/8255_pci.c b/drivers/comedi/drivers/8255_pci.c
new file mode 100644
index 000000000000..5a810f0e532a
--- /dev/null
+++ b/drivers/comedi/drivers/8255_pci.c
@@ -0,0 +1,295 @@
+// 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 "../comedi_pci.h"
+
+#include "8255.h"
+
+enum pci_8255_boardid {
+	BOARD_ADLINK_PCI7224,
+	BOARD_ADLINK_PCI7248,
+	BOARD_ADLINK_PCI7296,
+	BOARD_CB_PCIDIO24,
+	BOARD_CB_PCIDIO24H,
+	BOARD_CB_PCIDIO48H_OLD,
+	BOARD_CB_PCIDIO48H_NEW,
+	BOARD_CB_PCIDIO96H,
+	BOARD_NI_PCIDIO96,
+	BOARD_NI_PCIDIO96B,
+	BOARD_NI_PXI6508,
+	BOARD_NI_PCI6503,
+	BOARD_NI_PCI6503B,
+	BOARD_NI_PCI6503X,
+	BOARD_NI_PXI_6503,
+};
+
+struct pci_8255_boardinfo {
+	const char *name;
+	int dio_badr;
+	int n_8255;
+	unsigned int has_mite:1;
+};
+
+static const struct pci_8255_boardinfo pci_8255_boards[] = {
+	[BOARD_ADLINK_PCI7224] = {
+		.name		= "adl_pci-7224",
+		.dio_badr	= 2,
+		.n_8255		= 1,
+	},
+	[BOARD_ADLINK_PCI7248] = {
+		.name		= "adl_pci-7248",
+		.dio_badr	= 2,
+		.n_8255		= 2,
+	},
+	[BOARD_ADLINK_PCI7296] = {
+		.name		= "adl_pci-7296",
+		.dio_badr	= 2,
+		.n_8255		= 4,
+	},
+	[BOARD_CB_PCIDIO24] = {
+		.name		= "cb_pci-dio24",
+		.dio_badr	= 2,
+		.n_8255		= 1,
+	},
+	[BOARD_CB_PCIDIO24H] = {
+		.name		= "cb_pci-dio24h",
+		.dio_badr	= 2,
+		.n_8255		= 1,
+	},
+	[BOARD_CB_PCIDIO48H_OLD] = {
+		.name		= "cb_pci-dio48h",
+		.dio_badr	= 1,
+		.n_8255		= 2,
+	},
+	[BOARD_CB_PCIDIO48H_NEW] = {
+		.name		= "cb_pci-dio48h",
+		.dio_badr	= 2,
+		.n_8255		= 2,
+	},
+	[BOARD_CB_PCIDIO96H] = {
+		.name		= "cb_pci-dio96h",
+		.dio_badr	= 2,
+		.n_8255		= 4,
+	},
+	[BOARD_NI_PCIDIO96] = {
+		.name		= "ni_pci-dio-96",
+		.dio_badr	= 1,
+		.n_8255		= 4,
+		.has_mite	= 1,
+	},
+	[BOARD_NI_PCIDIO96B] = {
+		.name		= "ni_pci-dio-96b",
+		.dio_badr	= 1,
+		.n_8255		= 4,
+		.has_mite	= 1,
+	},
+	[BOARD_NI_PXI6508] = {
+		.name		= "ni_pxi-6508",
+		.dio_badr	= 1,
+		.n_8255		= 4,
+		.has_mite	= 1,
+	},
+	[BOARD_NI_PCI6503] = {
+		.name		= "ni_pci-6503",
+		.dio_badr	= 1,
+		.n_8255		= 1,
+		.has_mite	= 1,
+	},
+	[BOARD_NI_PCI6503B] = {
+		.name		= "ni_pci-6503b",
+		.dio_badr	= 1,
+		.n_8255		= 1,
+		.has_mite	= 1,
+	},
+	[BOARD_NI_PCI6503X] = {
+		.name		= "ni_pci-6503x",
+		.dio_badr	= 1,
+		.n_8255		= 1,
+		.has_mite	= 1,
+	},
+	[BOARD_NI_PXI_6503] = {
+		.name		= "ni_pxi-6503",
+		.dio_badr	= 1,
+		.n_8255		= 1,
+		.has_mite	= 1,
+	},
+};
+
+/* ripped from mite.h and mite_setup2() to avoid mite dependency */
+#define MITE_IODWBSR	0xc0	/* IO Device Window Base Size Register */
+#define WENAB		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 000000000000..b24ac00cab73
--- /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 000000000000..81a246fbcc01
--- /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 "../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 000000000000..b04c15dcfb57
--- /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 "../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 000000000000..274ec9fb030c
--- /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 "../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 000000000000..06fc7ed96200
--- /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 "../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 000000000000..c306aa41df97
--- /dev/null
+++ b/drivers/comedi/drivers/addi_apci_16xx.c
@@ -0,0 +1,178 @@
+// 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 "../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 000000000000..e9a2b37a4ae0
--- /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 "../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 000000000000..4c5aee784bd9
--- /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 "../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 000000000000..1ed3b33d1a30
--- /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 "../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 000000000000..f0c9642f3f1a
--- /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 "../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 000000000000..a90d59377e18
--- /dev/null
+++ b/drivers/comedi/drivers/addi_apci_3xxx.c
@@ -0,0 +1,961 @@
+// 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 "../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 000000000000..2b44d3a04484
--- /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 000000000000..69b323fb869f
--- /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 "../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 000000000000..7523084a0742
--- /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 000000000000..9ae4cc523dd4
--- /dev/null
+++ b/drivers/comedi/drivers/adl_pci6208.c
@@ -0,0 +1,201 @@
+// 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 "../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 000000000000..8fc45638ff59
--- /dev/null
+++ b/drivers/comedi/drivers/adl_pci7x3x.c
@@ -0,0 +1,542 @@
+// 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 "../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 000000000000..d5e1bda81557
--- /dev/null
+++ b/drivers/comedi/drivers/adl_pci8164.c
@@ -0,0 +1,154 @@
+// 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 "../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 000000000000..a062c5ab20e9
--- /dev/null
+++ b/drivers/comedi/drivers/adl_pci9111.c
@@ -0,0 +1,747 @@
+// 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 "../comedi_pci.h"
+
+#include "plx9052.h"
+#include "comedi_8254.h"
+
+#define PCI9111_FIFO_HALF_SIZE	512
+
+#define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS	10000
+
+#define PCI9111_RANGE_SETTING_DELAY		10
+#define PCI9111_AI_INSTANT_READ_UDELAY_US	2
+
+/*
+ * IO address map and bit defines
+ */
+#define PCI9111_AI_FIFO_REG		0x00
+#define PCI9111_AO_REG			0x00
+#define PCI9111_DIO_REG			0x02
+#define PCI9111_EDIO_REG		0x04
+#define PCI9111_AI_CHANNEL_REG		0x06
+#define PCI9111_AI_RANGE_STAT_REG	0x08
+#define PCI9111_AI_STAT_AD_BUSY		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 000000000000..cda3a4267dca
--- /dev/null
+++ b/drivers/comedi/drivers/adl_pci9118.c
@@ -0,0 +1,1736 @@
+// 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 "../comedi_pci.h"
+
+#include "amcc_s5933.h"
+#include "comedi_8254.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 000000000000..d719f76709ef
--- /dev/null
+++ b/drivers/comedi/drivers/adq12b.c
@@ -0,0 +1,243 @@
+// 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 "../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 000000000000..090607760be6
--- /dev/null
+++ b/drivers/comedi/drivers/adv_pci1710.c
@@ -0,0 +1,963 @@
+// 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 "../comedi_pci.h"
+
+#include "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 000000000000..2fcd7e8e7d85
--- /dev/null
+++ b/drivers/comedi/drivers/adv_pci1720.c
@@ -0,0 +1,186 @@
+// 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 "../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 000000000000..23660a9fdb9c
--- /dev/null
+++ b/drivers/comedi/drivers/adv_pci1723.c
@@ -0,0 +1,227 @@
+// 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 "../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 000000000000..e8ab573c839f
--- /dev/null
+++ b/drivers/comedi/drivers/adv_pci1724.c
@@ -0,0 +1,208 @@
+// 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 "../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 000000000000..6de8ab97d346
--- /dev/null
+++ b/drivers/comedi/drivers/adv_pci1760.c
@@ -0,0 +1,424 @@
+// 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 "../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		0x03	/* 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 000000000000..54c7419c8ca6
--- /dev/null
+++ b/drivers/comedi/drivers/adv_pci_dio.c
@@ -0,0 +1,801 @@
+// 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 "../comedi_pci.h"
+
+#include "8255.h"
+#include "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 000000000000..4829115921a3
--- /dev/null
+++ b/drivers/comedi/drivers/aio_aio12_8.c
@@ -0,0 +1,277 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+#include "8255.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 000000000000..fe3876235075
--- /dev/null
+++ b/drivers/comedi/drivers/aio_iiro_16.c
@@ -0,0 +1,235 @@
+// 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 "../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 000000000000..f738b91b2052
--- /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 000000000000..fa19c9e7c56b
--- /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 "../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 000000000000..745baaf940ee
--- /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 000000000000..a3454130d5f8
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_dio200_common.c
@@ -0,0 +1,858 @@
+// 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 "../comedidev.h"
+
+#include "amplc_dio200.h"
+#include "comedi_8254.h"
+#include "8255.h"		/* only for register defines */
+
+/* 200 series registers */
+#define DIO200_IO_SIZE		0x20
+#define DIO200_PCIE_IO_SIZE	0x4000
+#define DIO200_CLK_SCE(x)	(0x18 + (x))	/* Group X/Y/Z clock sel reg */
+#define DIO200_GAT_SCE(x)	(0x1b + (x))	/* Group X/Y/Z gate sel reg */
+#define DIO200_INT_SCE		0x1e	/* Interrupt enable/status register */
+/* Extra registers for new PCIe boards */
+#define DIO200_ENHANCE		0x20	/* 1 to enable enhanced features */
+#define DIO200_VERSION		0x24	/* Hardware version register */
+#define DIO200_TS_CONFIG	0x600	/* Timestamp timer config register */
+#define DIO200_TS_COUNT		0x602	/* Timestamp timer count register */
+
+/*
+ * Functions for constructing value for DIO_200_?CLK_SCE and
+ * DIO_200_?GAT_SCE registers:
+ *
+ * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2.
+ * 'chan' is the channel: 0, 1 or 2.
+ * 'source' is the signal source: 0 to 7, or 0 to 31 for "enhanced" boards.
+ */
+static unsigned char clk_gat_sce(unsigned int which, unsigned int chan,
+				 unsigned int source)
+{
+	return (which << 5) | (chan << 3) |
+	       ((source & 030) << 3) | (source & 007);
+}
+
+/*
+ * 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 000000000000..1bd7a42c8464
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_dio200_pci.c
@@ -0,0 +1,415 @@
+// 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 "../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 000000000000..c377af1d5246
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_pc236.c
@@ -0,0 +1,76 @@
+// 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 "../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 000000000000..7e72729f7492
--- /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 000000000000..981d281e87a1
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_pc236_common.c
@@ -0,0 +1,193 @@
+// 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 "../comedidev.h"
+
+#include "amplc_pc236.h"
+#include "8255.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 000000000000..68da6098ee84
--- /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 "../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 000000000000..bcf6d61af863
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_pci224.c
@@ -0,0 +1,1143 @@
+// 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 "../comedi_pci.h"
+
+#include "comedi_8254.h"
+
+/*
+ * PCI224/234 i/o space 1 (PCIBAR2) registers.
+ */
+#define PCI224_Z2_BASE	0x14	/* 82C54 counter/timer */
+#define PCI224_ZCLK_SCE	0x1A	/* Group Z Clock Configuration Register */
+#define PCI224_ZGAT_SCE	0x1D	/* Group Z Gate Configuration Register */
+#define PCI224_INT_SCE	0x1E	/* ISR Interrupt source mask register */
+				/* /Interrupt status */
+
+/*
+ * PCI224/234 i/o space 2 (PCIBAR3) 16-bit registers.
+ */
+#define PCI224_DACDATA	0x00	/* (w-o) DAC FIFO data. */
+#define PCI224_SOFTTRIG	0x00	/* (r-o) DAC software scan trigger. */
+#define PCI224_DACCON	0x02	/* (r/w) DAC status/configuration. */
+#define PCI224_FIFOSIZ	0x04	/* (w-o) FIFO size for wraparound mode. */
+#define PCI224_DACCEN	0x06	/* (w-o) DAC channel enable register. */
+
+/*
+ * DACCON values.
+ */
+/* (r/w) Scan trigger. */
+#define PCI224_DACCON_TRIG(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, &amplc_pci224_driver,
+				      id->driver_data);
+}
+
+static const struct pci_device_id amplc_pci224_pci_table[] = {
+	{ PCI_VDEVICE(AMPLICON, 0x0007), pci224_model },
+	{ PCI_VDEVICE(AMPLICON, 0x0008), pci234_model },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, amplc_pci224_pci_table);
+
+static struct pci_driver amplc_pci224_pci_driver = {
+	.name		= "amplc_pci224",
+	.id_table	= amplc_pci224_pci_table,
+	.probe		= amplc_pci224_pci_probe,
+	.remove		= comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci224_driver, amplc_pci224_pci_driver);
+
+MODULE_AUTHOR("Comedi 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 000000000000..8911dc2bd2c6
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_pci230.c
@@ -0,0 +1,2575 @@
+// 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 "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "8255.h"
+
+/*
+ * PCI230 PCI configuration register information
+ */
+#define PCI_DEVICE_ID_PCI230 0x0000
+#define PCI_DEVICE_ID_PCI260 0x0006
+
+/*
+ * PCI230 i/o space 1 registers.
+ */
+#define PCI230_PPI_X_BASE	0x00	/* User PPI (82C55) base */
+#define PCI230_PPI_X_A		0x00	/* User PPI (82C55) port A */
+#define PCI230_PPI_X_B		0x01	/* User PPI (82C55) port B */
+#define PCI230_PPI_X_C		0x02	/* User PPI (82C55) port C */
+#define PCI230_PPI_X_CMD	0x03	/* User PPI (82C55) control word */
+#define PCI230_Z2_CT_BASE	0x14	/* 82C54 counter/timer base */
+#define PCI230_ZCLK_SCE		0x1A	/* Group Z Clock Configuration */
+#define PCI230_ZGAT_SCE		0x1D	/* Group Z Gate Configuration */
+#define PCI230_INT_SCE		0x1E	/* Interrupt source mask (w) */
+#define PCI230_INT_STAT		0x1E	/* Interrupt status (r) */
+
+/*
+ * PCI230 i/o space 2 registers.
+ */
+#define PCI230_DACCON		0x00	/* DAC control */
+#define PCI230_DACOUT1		0x02	/* DAC channel 0 (w) */
+#define PCI230_DACOUT2		0x04	/* DAC channel 1 (w) (not FIFO mode) */
+#define PCI230_ADCDATA		0x08	/* ADC data (r) */
+#define PCI230_ADCSWTRIG	0x08	/* ADC software trigger (w) */
+#define PCI230_ADCCON		0x0A	/* ADC control */
+#define PCI230_ADCEN		0x0C	/* ADC channel enable bits */
+#define PCI230_ADCG		0x0E	/* ADC gain control bits */
+/* PCI230+ i/o space 2 additional registers. */
+#define PCI230P_ADCTRIG		0x10	/* ADC start acquisition trigger */
+#define PCI230P_ADCTH		0x12	/* ADC analog trigger threshold */
+#define PCI230P_ADCFFTH		0x14	/* ADC FIFO interrupt threshold */
+#define PCI230P_ADCFFLEV	0x16	/* ADC FIFO level (r) */
+#define PCI230P_ADCPTSC		0x18	/* ADC pre-trigger sample count (r) */
+#define PCI230P_ADCHYST		0x1A	/* ADC analog trigger hysteresys */
+#define PCI230P_EXTFUNC		0x1C	/* Extended functions */
+#define PCI230P_HWVER		0x1E	/* Hardware version (r) */
+/* PCI230+ hardware version 2 onwards. */
+#define PCI230P2_DACDATA	0x02	/* DAC data (FIFO mode) (w) */
+#define PCI230P2_DACSWTRIG	0x02	/* DAC soft trigger (FIFO mode) (r) */
+#define PCI230P2_DACEN		0x06	/* DAC channel enable (FIFO mode) */
+
+/*
+ * DACCON read-write values.
+ */
+#define PCI230_DAC_OR(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, &amplc_pci230_driver,
+				      id->driver_data);
+}
+
+static const struct pci_device_id amplc_pci230_pci_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI230) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI260) },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, amplc_pci230_pci_table);
+
+static struct pci_driver amplc_pci230_pci_driver = {
+	.name		= "amplc_pci230",
+	.id_table	= amplc_pci230_pci_table,
+	.probe		= amplc_pci230_pci_probe,
+	.remove		= comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci230_driver, amplc_pci230_pci_driver);
+
+MODULE_AUTHOR("Comedi 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 000000000000..e7f6fa4d101a
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_pci236.c
@@ -0,0 +1,144 @@
+// 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 "../comedi_pci.h"
+
+#include "amplc_pc236.h"
+#include "plx9052.h"
+
+/* Disable, and clear, interrupts */
+#define PCI236_INTR_DISABLE	(PLX9052_INTCSR_LI1POL |	\
+				 PLX9052_INTCSR_LI2POL |	\
+				 PLX9052_INTCSR_LI1SEL |	\
+				 PLX9052_INTCSR_LI1CLRINT)
+
+/* Enable, and clear, interrupts */
+#define PCI236_INTR_ENABLE	(PLX9052_INTCSR_LI1ENAB |	\
+				 PLX9052_INTCSR_LI1POL |	\
+				 PLX9052_INTCSR_LI2POL |	\
+				 PLX9052_INTCSR_PCIENAB |	\
+				 PLX9052_INTCSR_LI1SEL |	\
+				 PLX9052_INTCSR_LI1CLRINT)
+
+static void pci236_intr_update_cb(struct comedi_device *dev, bool enable)
+{
+	struct pc236_private *devpriv = dev->private;
+
+	/* this will also clear the "local interrupt 1" latch */
+	outl(enable ? PCI236_INTR_ENABLE : PCI236_INTR_DISABLE,
+	     devpriv->lcr_iobase + PLX9052_INTCSR);
+}
+
+static bool pci236_intr_chk_clr_cb(struct comedi_device *dev)
+{
+	struct pc236_private *devpriv = dev->private;
+
+	/* check if interrupt occurred */
+	if (!(inl(devpriv->lcr_iobase + PLX9052_INTCSR) &
+	      PLX9052_INTCSR_LI1STAT))
+		return false;
+	/* clear the interrupt */
+	pci236_intr_update_cb(dev, devpriv->enable_irq);
+	return true;
+}
+
+static const struct pc236_board pc236_pci_board = {
+	.name = "pci236",
+	.intr_update_cb = pci236_intr_update_cb,
+	.intr_chk_clr_cb = pci236_intr_chk_clr_cb,
+};
+
+static int pci236_auto_attach(struct comedi_device *dev,
+			      unsigned long context_unused)
+{
+	struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+	struct pc236_private *devpriv;
+	unsigned long iobase;
+	int ret;
+
+	dev_info(dev->class_dev, "amplc_pci236: attach pci %s\n",
+		 pci_name(pci_dev));
+
+	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+	if (!devpriv)
+		return -ENOMEM;
+
+	dev->board_ptr = &pc236_pci_board;
+	dev->board_name = pc236_pci_board.name;
+	ret = comedi_pci_enable(dev);
+	if (ret)
+		return ret;
+
+	devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
+	iobase = pci_resource_start(pci_dev, 2);
+	return amplc_pc236_common_attach(dev, iobase, pci_dev->irq,
+					 IRQF_SHARED);
+}
+
+static struct comedi_driver amplc_pci236_driver = {
+	.driver_name = "amplc_pci236",
+	.module = THIS_MODULE,
+	.auto_attach = pci236_auto_attach,
+	.detach = comedi_pci_detach,
+};
+
+static const struct pci_device_id pci236_pci_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) },
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, pci236_pci_table);
+
+static int amplc_pci236_pci_probe(struct pci_dev *dev,
+				  const struct pci_device_id *id)
+{
+	return comedi_pci_auto_config(dev, &amplc_pci236_driver,
+				      id->driver_data);
+}
+
+static struct pci_driver amplc_pci236_pci_driver = {
+	.name		= "amplc_pci236",
+	.id_table	= pci236_pci_table,
+	.probe		= &amplc_pci236_pci_probe,
+	.remove		= comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(amplc_pci236_driver, amplc_pci236_pci_driver);
+
+MODULE_AUTHOR("Comedi 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 000000000000..9217973f1141
--- /dev/null
+++ b/drivers/comedi/drivers/amplc_pci263.c
@@ -0,0 +1,111 @@
+// 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 "../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, &amplc_pci263_driver,
+				      id->driver_data);
+}
+
+static struct pci_driver amplc_pci263_pci_driver = {
+	.name		= "amplc_pci263",
+	.id_table	= pci263_pci_table,
+	.probe		= &amplc_pci263_pci_probe,
+	.remove		= comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci263_driver, amplc_pci263_pci_driver);
+
+MODULE_AUTHOR("Comedi 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 000000000000..786fd15698df
--- /dev/null
+++ b/drivers/comedi/drivers/c6xdigio.c
@@ -0,0 +1,298 @@
+// 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 "../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 000000000000..a5d171e71c33
--- /dev/null
+++ b/drivers/comedi/drivers/cb_das16_cs.c
@@ -0,0 +1,456 @@
+// 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 "../comedi_pcmcia.h"
+
+#include "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 000000000000..2f20bd56ec6c
--- /dev/null
+++ b/drivers/comedi/drivers/cb_pcidas.c
@@ -0,0 +1,1499 @@
+// 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 "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "8255.h"
+#include "amcc_s5933.h"
+
+#define AI_BUFFER_SIZE		1024	/* max ai fifo size */
+#define AO_BUFFER_SIZE		1024	/* max ao fifo size */
+
+/*
+ * 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 000000000000..41a8fea7f48a
--- /dev/null
+++ b/drivers/comedi/drivers/cb_pcidas64.c
@@ -0,0 +1,4119 @@
+// 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 "../comedi_pci.h"
+
+#include "8255.h"
+#include "plx9080.h"
+
+#define TIMER_BASE 25		/*  40MHz master clock */
+/*
+ * 100kHz 'prescaled' clock for slow acquisition,
+ * maybe I'll support this someday
+ */
+#define PRESCALED_TIMER_BASE	10000
+#define DMA_BUFFER_SIZE		0x1000
+#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 000000000000..78cf1603638c
--- /dev/null
+++ b/drivers/comedi/drivers/cb_pcidda.c
@@ -0,0 +1,421 @@
+// 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 "../comedi_pci.h"
+
+#include "8255.h"
+
+#define EEPROM_SIZE	128	/*  number of entries in eeprom */
+/* maximum number of ao channels for supported boards */
+#define MAX_AO_CHANNELS 8
+
+/* Digital I/O registers */
+#define CB_DDA_DIO0_8255_BASE		0x00
+#define CB_DDA_DIO1_8255_BASE		0x04
+
+/* DAC registers */
+#define CB_DDA_DA_CTRL_REG		0x00	   /* D/A Control Register  */
+#define CB_DDA_DA_CTRL_SU		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 000000000000..2292f69da4f4
--- /dev/null
+++ b/drivers/comedi/drivers/cb_pcimdas.c
@@ -0,0 +1,475 @@
+// 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 "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "plx9052.h"
+#include "8255.h"
+
+/*
+ * PCI Bar 1 Register map
+ * see plx9052.h for register and bit defines
+ */
+
+/*
+ * PCI Bar 2 Register map (devpriv->daqio)
+ */
+#define PCIMDAS_AI_REG			0x00
+#define PCIMDAS_AI_SOFTTRIG_REG		0x00
+#define PCIMDAS_AO_REG(x)		(0x02 + ((x) * 2))
+
+/*
+ * PCI Bar 3 Register map (devpriv->BADR3)
+ */
+#define PCIMDAS_MUX_REG			0x00
+#define PCIMDAS_MUX(_lo, _hi)		((_lo) | ((_hi) << 4))
+#define PCIMDAS_DI_DO_REG		0x01
+#define PCIMDAS_STATUS_REG		0x02
+#define PCIMDAS_STATUS_EOC		BIT(7)
+#define PCIMDAS_STATUS_UB		BIT(6)
+#define PCIMDAS_STATUS_MUX		BIT(5)
+#define PCIMDAS_STATUS_CLK		BIT(4)
+#define PCIMDAS_STATUS_TO_CURR_MUX(x)	((x) & 0xf)
+#define PCIMDAS_CONV_STATUS_REG		0x03
+#define PCIMDAS_CONV_STATUS_EOC		BIT(7)
+#define PCIMDAS_CONV_STATUS_EOB		BIT(6)
+#define PCIMDAS_CONV_STATUS_EOA		BIT(5)
+#define PCIMDAS_CONV_STATUS_FNE		BIT(4)
+#define PCIMDAS_CONV_STATUS_FHF		BIT(3)
+#define PCIMDAS_CONV_STATUS_OVERRUN	BIT(2)
+#define PCIMDAS_IRQ_REG			0x04
+#define PCIMDAS_IRQ_INTE		BIT(7)
+#define PCIMDAS_IRQ_INT			BIT(6)
+#define PCIMDAS_IRQ_OVERRUN		BIT(4)
+#define PCIMDAS_IRQ_EOA			BIT(3)
+#define PCIMDAS_IRQ_EOA_INT_SEL		BIT(2)
+#define PCIMDAS_IRQ_INTSEL(x)		((x) << 0)
+#define PCIMDAS_IRQ_INTSEL_EOC		PCIMDAS_IRQ_INTSEL(0)
+#define PCIMDAS_IRQ_INTSEL_FNE		PCIMDAS_IRQ_INTSEL(1)
+#define PCIMDAS_IRQ_INTSEL_EOB		PCIMDAS_IRQ_INTSEL(2)
+#define PCIMDAS_IRQ_INTSEL_FHF_EOA	PCIMDAS_IRQ_INTSEL(3)
+#define PCIMDAS_PACER_REG		0x05
+#define PCIMDAS_PACER_GATE_STATUS	BIT(6)
+#define PCIMDAS_PACER_GATE_POL		BIT(5)
+#define PCIMDAS_PACER_GATE_LATCH	BIT(4)
+#define PCIMDAS_PACER_GATE_EN		BIT(3)
+#define PCIMDAS_PACER_EXT_PACER_POL	BIT(2)
+#define PCIMDAS_PACER_SRC(x)		((x) << 0)
+#define PCIMDAS_PACER_SRC_POLLED	PCIMDAS_PACER_SRC(0)
+#define PCIMDAS_PACER_SRC_EXT		PCIMDAS_PACER_SRC(2)
+#define PCIMDAS_PACER_SRC_INT		PCIMDAS_PACER_SRC(3)
+#define PCIMDAS_PACER_SRC_MASK		(3 << 0)
+#define PCIMDAS_BURST_REG		0x06
+#define PCIMDAS_BURST_BME		BIT(1)
+#define PCIMDAS_BURST_CONV_EN		BIT(0)
+#define PCIMDAS_GAIN_REG		0x07
+#define PCIMDAS_8254_BASE		0x08
+#define PCIMDAS_USER_CNTR_REG		0x0c
+#define PCIMDAS_USER_CNTR_CTR1_CLK_SEL	BIT(0)
+#define PCIMDAS_RESIDUE_MSB_REG		0x0d
+#define PCIMDAS_RESIDUE_LSB_REG		0x0e
+
+/*
+ * PCI Bar 4 Register map (dev->iobase)
+ */
+#define PCIMDAS_8255_BASE		0x00
+
+static const struct comedi_lrange cb_pcimdas_ai_bip_range = {
+	4, {
+		BIP_RANGE(10),
+		BIP_RANGE(5),
+		BIP_RANGE(2.5),
+		BIP_RANGE(1.25)
+	}
+};
+
+static const struct comedi_lrange cb_pcimdas_ai_uni_range = {
+	4, {
+		UNI_RANGE(10),
+		UNI_RANGE(5),
+		UNI_RANGE(2.5),
+		UNI_RANGE(1.25)
+	}
+};
+
+/*
+ * The Analog Output range is not programmable. The DAC ranges are
+ * jumper-settable on the board. The settings are not software-readable.
+ */
+static const struct comedi_lrange cb_pcimdas_ao_range = {
+	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 000000000000..21fc7b3c5f60
--- /dev/null
+++ b/drivers/comedi/drivers/cb_pcimdda.c
@@ -0,0 +1,192 @@
+// 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 "../comedi_pci.h"
+
+#include "8255.h"
+
+/* device ids of the cards we support -- currently only 1 card supported */
+#define PCI_ID_PCIM_DDA06_16		0x0053
+
+/*
+ * Register map, 8-bit access only
+ */
+#define PCIMDDA_DA_CHAN(x)		(0x00 + (x) * 2)
+#define PCIMDDA_8255_BASE_REG		0x0c
+
+static int cb_pcimdda_ao_insn_write(struct comedi_device *dev,
+				    struct comedi_subdevice *s,
+				    struct comedi_insn *insn,
+				    unsigned int *data)
+{
+	unsigned int chan = CR_CHAN(insn->chanspec);
+	unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan);
+	unsigned int val = s->readback[chan];
+	int i;
+
+	for (i = 0; i < insn->n; i++) {
+		val = data[i];
+
+		/*
+		 * Write the LSB then MSB.
+		 *
+		 * If the simultaneous xfer mode is selected by the
+		 * jumper on the card, a read instruction is needed
+		 * in order to initiate the simultaneous transfer.
+		 * Otherwise, the DAC will be updated when the MSB
+		 * is written.
+		 */
+		outb(val & 0x00ff, offset);
+		outb((val >> 8) & 0x00ff, offset + 1);
+	}
+	s->readback[chan] = val;
+
+	return insn->n;
+}
+
+static int cb_pcimdda_ao_insn_read(struct comedi_device *dev,
+				   struct comedi_subdevice *s,
+				   struct comedi_insn *insn,
+				   unsigned int *data)
+{
+	unsigned int chan = CR_CHAN(insn->chanspec);
+
+	/* Initiate the simultaneous transfer */
+	inw(dev->iobase + PCIMDDA_DA_CHAN(chan));
+
+	return comedi_readback_insn_read(dev, s, insn, data);
+}
+
+static int cb_pcimdda_auto_attach(struct comedi_device *dev,
+				  unsigned long context_unused)
+{
+	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+	struct comedi_subdevice *s;
+	int ret;
+
+	ret = comedi_pci_enable(dev);
+	if (ret)
+		return ret;
+	dev->iobase = pci_resource_start(pcidev, 3);
+
+	ret = comedi_alloc_subdevices(dev, 2);
+	if (ret)
+		return ret;
+
+	s = &dev->subdevices[0];
+	/* analog output subdevice */
+	s->type		= COMEDI_SUBD_AO;
+	s->subdev_flags	= SDF_WRITABLE | SDF_READABLE;
+	s->n_chan	= 6;
+	s->maxdata	= 0xffff;
+	s->range_table	= &range_bipolar5;
+	s->insn_write	= cb_pcimdda_ao_insn_write;
+	s->insn_read	= cb_pcimdda_ao_insn_read;
+
+	ret = comedi_alloc_subdev_readback(s);
+	if (ret)
+		return ret;
+
+	s = &dev->subdevices[1];
+	/* digital i/o subdevice */
+	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 000000000000..d1d509e9add9
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_8254.c
@@ -0,0 +1,655 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+
+static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg)
+{
+	unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
+	unsigned int val;
+
+	switch (i8254->iosize) {
+	default:
+	case I8254_IO8:
+		if (i8254->mmio)
+			val = readb(i8254->mmio + reg_offset);
+		else
+			val = inb(i8254->iobase + reg_offset);
+		break;
+	case I8254_IO16:
+		if (i8254->mmio)
+			val = readw(i8254->mmio + reg_offset);
+		else
+			val = inw(i8254->iobase + reg_offset);
+		break;
+	case I8254_IO32:
+		if (i8254->mmio)
+			val = readl(i8254->mmio + reg_offset);
+		else
+			val = inl(i8254->iobase + reg_offset);
+		break;
+	}
+	return val & 0xff;
+}
+
+static void __i8254_write(struct comedi_8254 *i8254,
+			  unsigned int val, unsigned int reg)
+{
+	unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
+
+	switch (i8254->iosize) {
+	default:
+	case I8254_IO8:
+		if (i8254->mmio)
+			writeb(val, i8254->mmio + reg_offset);
+		else
+			outb(val, i8254->iobase + reg_offset);
+		break;
+	case I8254_IO16:
+		if (i8254->mmio)
+			writew(val, i8254->mmio + reg_offset);
+		else
+			outw(val, i8254->iobase + reg_offset);
+		break;
+	case I8254_IO32:
+		if (i8254->mmio)
+			writel(val, i8254->mmio + reg_offset);
+		else
+			outl(val, i8254->iobase + reg_offset);
+		break;
+	}
+}
+
+/**
+ * comedi_8254_status - return the status of a counter
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter:	the counter number
+ */
+unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter)
+{
+	unsigned int cmd;
+
+	if (counter > 2)
+		return 0;
+
+	cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter);
+	__i8254_write(i8254, cmd, I8254_CTRL_REG);
+
+	return __i8254_read(i8254, counter);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_status);
+
+/**
+ * comedi_8254_read - read the current counter value
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter:	the counter number
+ */
+unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter)
+{
+	unsigned int val;
+
+	if (counter > 2)
+		return 0;
+
+	/* latch counter */
+	__i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH,
+		      I8254_CTRL_REG);
+
+	/* read LSB then MSB */
+	val = __i8254_read(i8254, counter);
+	val |= (__i8254_read(i8254, counter) << 8);
+
+	return val;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_read);
+
+/**
+ * comedi_8254_write - load a 16-bit initial counter value
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter:	the counter number
+ * @val:	the initial value
+ */
+void comedi_8254_write(struct comedi_8254 *i8254,
+		       unsigned int counter, unsigned int val)
+{
+	unsigned int byte;
+
+	if (counter > 2)
+		return;
+	if (val > 0xffff)
+		return;
+
+	/* load LSB then MSB */
+	byte = val & 0xff;
+	__i8254_write(i8254, byte, counter);
+	byte = (val >> 8) & 0xff;
+	__i8254_write(i8254, byte, counter);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_write);
+
+/**
+ * comedi_8254_set_mode - set the mode of a counter
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter:	the counter number
+ * @mode:	the I8254_MODEx and I8254_BCD|I8254_BINARY
+ */
+int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter,
+			 unsigned int mode)
+{
+	unsigned int byte;
+
+	if (counter > 2)
+		return -EINVAL;
+	if (mode > (I8254_MODE5 | I8254_BCD))
+		return -EINVAL;
+
+	byte = I8254_CTRL_SEL_CTR(counter) |	/* select counter */
+	       I8254_CTRL_LSB_MSB |		/* load LSB then MSB */
+	       mode;				/* mode and BCD|binary */
+	__i8254_write(i8254, byte, I8254_CTRL_REG);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_set_mode);
+
+/**
+ * comedi_8254_load - program the mode and initial count of a counter
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter:	the counter number
+ * @mode:	the I8254_MODEx and I8254_BCD|I8254_BINARY
+ * @val:	the initial value
+ */
+int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter,
+		     unsigned int val, unsigned int mode)
+{
+	if (counter > 2)
+		return -EINVAL;
+	if (val > 0xffff)
+		return -EINVAL;
+	if (mode > (I8254_MODE5 | I8254_BCD))
+		return -EINVAL;
+
+	comedi_8254_set_mode(i8254, counter, mode);
+	comedi_8254_write(i8254, counter, val);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_load);
+
+/**
+ * comedi_8254_pacer_enable - set the mode and load the cascaded counters
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter1:	the counter number for the first divisor
+ * @counter2:	the counter number for the second divisor
+ * @enable:	flag to enable (load) the counters
+ */
+void comedi_8254_pacer_enable(struct comedi_8254 *i8254,
+			      unsigned int counter1,
+			      unsigned int counter2,
+			      bool enable)
+{
+	unsigned int mode;
+
+	if (counter1 > 2 || counter2 > 2 || counter1 == counter2)
+		return;
+
+	if (enable)
+		mode = I8254_MODE2 | I8254_BINARY;
+	else
+		mode = I8254_MODE0 | I8254_BINARY;
+
+	comedi_8254_set_mode(i8254, counter1, mode);
+	comedi_8254_set_mode(i8254, counter2, mode);
+
+	if (enable) {
+		/*
+		 * Divisors are loaded second counter then first counter to
+		 * avoid possible issues with the first counter expiring
+		 * before the second counter is loaded.
+		 */
+		comedi_8254_write(i8254, counter2, i8254->divisor2);
+		comedi_8254_write(i8254, counter1, i8254->divisor1);
+	}
+}
+EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable);
+
+/**
+ * comedi_8254_update_divisors - update the divisors for the cascaded counters
+ * @i8254:	comedi_8254 struct for the timer
+ */
+void comedi_8254_update_divisors(struct comedi_8254 *i8254)
+{
+	/* masking is done since counter maps zero to 0x10000 */
+	i8254->divisor = i8254->next_div & 0xffff;
+	i8254->divisor1 = i8254->next_div1 & 0xffff;
+	i8254->divisor2 = i8254->next_div2 & 0xffff;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_update_divisors);
+
+/**
+ * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values
+ * @i8254:	comedi_8254 struct for the timer
+ * @nanosec:	the desired ns time
+ * @flags:	comedi_cmd flags
+ */
+void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254,
+				     unsigned int *nanosec,
+				     unsigned int flags)
+{
+	unsigned int d1 = i8254->next_div1 ? i8254->next_div1 : I8254_MAX_COUNT;
+	unsigned int d2 = i8254->next_div2 ? i8254->next_div2 : I8254_MAX_COUNT;
+	unsigned int div = d1 * d2;
+	unsigned int ns_lub = 0xffffffff;
+	unsigned int ns_glb = 0;
+	unsigned int d1_lub = 0;
+	unsigned int d1_glb = 0;
+	unsigned int d2_lub = 0;
+	unsigned int d2_glb = 0;
+	unsigned int start;
+	unsigned int ns;
+	unsigned int ns_low;
+	unsigned int ns_high;
+
+	/* exit early if everything is already correct */
+	if (div * i8254->osc_base == *nanosec &&
+	    d1 > 1 && d1 <= I8254_MAX_COUNT &&
+	    d2 > 1 && d2 <= I8254_MAX_COUNT &&
+	    /* check for overflow */
+	    div > d1 && div > d2 &&
+	    div * i8254->osc_base > div &&
+	    div * i8254->osc_base > i8254->osc_base)
+		return;
+
+	div = *nanosec / i8254->osc_base;
+	d2 = I8254_MAX_COUNT;
+	start = div / d2;
+	if (start < 2)
+		start = 2;
+	for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; d1++) {
+		for (d2 = div / d1;
+		     d1 * d2 <= div + d1 + 1 && d2 <= I8254_MAX_COUNT; d2++) {
+			ns = i8254->osc_base * d1 * d2;
+			if (ns <= *nanosec && ns > ns_glb) {
+				ns_glb = ns;
+				d1_glb = d1;
+				d2_glb = d2;
+			}
+			if (ns >= *nanosec && ns < ns_lub) {
+				ns_lub = ns;
+				d1_lub = d1;
+				d2_lub = d2;
+			}
+		}
+	}
+
+	switch (flags & CMDF_ROUND_MASK) {
+	case CMDF_ROUND_NEAREST:
+	default:
+		ns_high = d1_lub * d2_lub * i8254->osc_base;
+		ns_low = d1_glb * d2_glb * i8254->osc_base;
+		if (ns_high - *nanosec < *nanosec - ns_low) {
+			d1 = d1_lub;
+			d2 = d2_lub;
+		} else {
+			d1 = d1_glb;
+			d2 = d2_glb;
+		}
+		break;
+	case CMDF_ROUND_UP:
+		d1 = d1_lub;
+		d2 = d2_lub;
+		break;
+	case CMDF_ROUND_DOWN:
+		d1 = d1_glb;
+		d2 = d2_glb;
+		break;
+	}
+
+	*nanosec = d1 * d2 * i8254->osc_base;
+	i8254->next_div1 = d1;
+	i8254->next_div2 = d2;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer);
+
+/**
+ * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing
+ * @i8254:	comedi_8254 struct for the timer
+ * @nanosec:	the desired ns time
+ * @flags:	comedi_cmd flags
+ */
+void comedi_8254_ns_to_timer(struct comedi_8254 *i8254,
+			     unsigned int *nanosec, unsigned int flags)
+{
+	unsigned int divisor;
+
+	switch (flags & CMDF_ROUND_MASK) {
+	default:
+	case CMDF_ROUND_NEAREST:
+		divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base);
+		break;
+	case CMDF_ROUND_UP:
+		divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base);
+		break;
+	case CMDF_ROUND_DOWN:
+		divisor = *nanosec / i8254->osc_base;
+		break;
+	}
+	if (divisor < 2)
+		divisor = 2;
+	if (divisor > I8254_MAX_COUNT)
+		divisor = I8254_MAX_COUNT;
+
+	*nanosec = divisor * i8254->osc_base;
+	i8254->next_div = divisor;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer);
+
+/**
+ * comedi_8254_set_busy - set/clear the "busy" flag for a given counter
+ * @i8254:	comedi_8254 struct for the timer
+ * @counter:	the counter number
+ * @busy:	set/clear flag
+ */
+void comedi_8254_set_busy(struct comedi_8254 *i8254,
+			  unsigned int counter, bool busy)
+{
+	if (counter < 3)
+		i8254->busy[counter] = busy;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_set_busy);
+
+static int comedi_8254_insn_read(struct comedi_device *dev,
+				 struct comedi_subdevice *s,
+				 struct comedi_insn *insn,
+				 unsigned int *data)
+{
+	struct comedi_8254 *i8254 = s->private;
+	unsigned int chan = CR_CHAN(insn->chanspec);
+	int i;
+
+	if (i8254->busy[chan])
+		return -EBUSY;
+
+	for (i = 0; i < insn->n; i++)
+		data[i] = comedi_8254_read(i8254, chan);
+
+	return insn->n;
+}
+
+static int comedi_8254_insn_write(struct comedi_device *dev,
+				  struct comedi_subdevice *s,
+				  struct comedi_insn *insn,
+				  unsigned int *data)
+{
+	struct comedi_8254 *i8254 = s->private;
+	unsigned int chan = CR_CHAN(insn->chanspec);
+
+	if (i8254->busy[chan])
+		return -EBUSY;
+
+	if (insn->n)
+		comedi_8254_write(i8254, chan, data[insn->n - 1]);
+
+	return insn->n;
+}
+
+static int comedi_8254_insn_config(struct comedi_device *dev,
+				   struct comedi_subdevice *s,
+				   struct comedi_insn *insn,
+				   unsigned int *data)
+{
+	struct comedi_8254 *i8254 = s->private;
+	unsigned int chan = CR_CHAN(insn->chanspec);
+	int ret;
+
+	if (i8254->busy[chan])
+		return -EBUSY;
+
+	switch (data[0]) {
+	case INSN_CONFIG_RESET:
+		ret = comedi_8254_set_mode(i8254, chan,
+					   I8254_MODE0 | I8254_BINARY);
+		if (ret)
+			return ret;
+		break;
+	case INSN_CONFIG_SET_COUNTER_MODE:
+		ret = comedi_8254_set_mode(i8254, chan, data[1]);
+		if (ret)
+			return ret;
+		break;
+	case INSN_CONFIG_8254_READ_STATUS:
+		data[1] = comedi_8254_status(i8254, chan);
+		break;
+	default:
+		/*
+		 * If available, call the driver provided (*insn_config)
+		 * to handle any driver implemented instructions.
+		 */
+		if (i8254->insn_config)
+			return i8254->insn_config(dev, s, insn, data);
+
+		return -EINVAL;
+	}
+
+	return insn->n;
+}
+
+/**
+ * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer
+ * @s:		comedi_subdevice struct
+ */
+void comedi_8254_subdevice_init(struct comedi_subdevice *s,
+				struct comedi_8254 *i8254)
+{
+	s->type		= COMEDI_SUBD_COUNTER;
+	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
+	s->n_chan	= 3;
+	s->maxdata	= 0xffff;
+	s->range_table	= &range_unknown;
+	s->insn_read	= comedi_8254_insn_read;
+	s->insn_write	= comedi_8254_insn_write;
+	s->insn_config	= comedi_8254_insn_config;
+
+	s->private	= i8254;
+}
+EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init);
+
+static struct comedi_8254 *__i8254_init(unsigned long iobase,
+					void __iomem *mmio,
+					unsigned int osc_base,
+					unsigned int iosize,
+					unsigned int regshift)
+{
+	struct comedi_8254 *i8254;
+	int i;
+
+	/* sanity check that the iosize is valid */
+	if (!(iosize == I8254_IO8 || iosize == I8254_IO16 ||
+	      iosize == I8254_IO32))
+		return NULL;
+
+	i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL);
+	if (!i8254)
+		return NULL;
+
+	i8254->iobase	= iobase;
+	i8254->mmio	= mmio;
+	i8254->iosize	= iosize;
+	i8254->regshift	= regshift;
+
+	/* default osc_base to the max speed of a generic 8254 timer */
+	i8254->osc_base	= osc_base ? osc_base : I8254_OSC_BASE_10MHZ;
+
+	/* reset all the counters by setting them to I8254_MODE0 */
+	for (i = 0; i < 3; i++)
+		comedi_8254_set_mode(i8254, i, I8254_MODE0 | I8254_BINARY);
+
+	return i8254;
+}
+
+/**
+ * comedi_8254_init - allocate and initialize the 8254 device for pio access
+ * @mmio:	port I/O base address
+ * @osc_base:	base time of the counter in ns
+ *		OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
+ * @iosize:	I/O register size
+ * @regshift:	register gap shift
+ */
+struct comedi_8254 *comedi_8254_init(unsigned long iobase,
+				     unsigned int osc_base,
+				     unsigned int iosize,
+				     unsigned int regshift)
+{
+	return __i8254_init(iobase, NULL, osc_base, iosize, regshift);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_init);
+
+/**
+ * comedi_8254_mm_init - allocate and initialize the 8254 device for mmio access
+ * @mmio:	memory mapped I/O base address
+ * @osc_base:	base time of the counter in ns
+ *		OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
+ * @iosize:	I/O register size
+ * @regshift:	register gap shift
+ */
+struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
+					unsigned int osc_base,
+					unsigned int iosize,
+					unsigned int regshift)
+{
+	return __i8254_init(0, mmio, osc_base, iosize, regshift);
+}
+EXPORT_SYMBOL_GPL(comedi_8254_mm_init);
+
+static int __init comedi_8254_module_init(void)
+{
+	return 0;
+}
+module_init(comedi_8254_module_init);
+
+static void __exit comedi_8254_module_exit(void)
+{
+}
+module_exit(comedi_8254_module_exit);
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/comedi/drivers/comedi_8254.h b/drivers/comedi/drivers/comedi_8254.h
new file mode 100644
index 000000000000..d8264417e53c
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_8254.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * comedi_8254.h
+ * Generic 8254 timer/counter support
+ * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ */
+
+#ifndef _COMEDI_8254_H
+#define _COMEDI_8254_H
+
+#include <linux/types.h>
+
+struct comedi_device;
+struct comedi_insn;
+struct comedi_subdevice;
+
+/*
+ * Common oscillator base values in nanoseconds
+ */
+#define I8254_OSC_BASE_10MHZ	100
+#define I8254_OSC_BASE_5MHZ	200
+#define I8254_OSC_BASE_4MHZ	250
+#define I8254_OSC_BASE_2MHZ	500
+#define I8254_OSC_BASE_1MHZ	1000
+#define I8254_OSC_BASE_100KHZ	10000
+#define I8254_OSC_BASE_10KHZ	100000
+#define I8254_OSC_BASE_1KHZ	1000000
+
+/*
+ * I/O access size used to read/write registers
+ */
+#define I8254_IO8		1
+#define I8254_IO16		2
+#define I8254_IO32		4
+
+/*
+ * Register map for generic 8254 timer (I8254_IO8 with 0 regshift)
+ */
+#define I8254_COUNTER0_REG		0x00
+#define I8254_COUNTER1_REG		0x01
+#define I8254_COUNTER2_REG		0x02
+#define I8254_CTRL_REG			0x03
+#define I8254_CTRL_SEL_CTR(x)		((x) << 6)
+#define I8254_CTRL_READBACK(x)		(I8254_CTRL_SEL_CTR(3) | BIT(x))
+#define I8254_CTRL_READBACK_COUNT	I8254_CTRL_READBACK(4)
+#define I8254_CTRL_READBACK_STATUS	I8254_CTRL_READBACK(5)
+#define I8254_CTRL_READBACK_SEL_CTR(x)	(2 << (x))
+#define I8254_CTRL_RW(x)		(((x) & 0x3) << 4)
+#define I8254_CTRL_LATCH		I8254_CTRL_RW(0)
+#define I8254_CTRL_LSB_ONLY		I8254_CTRL_RW(1)
+#define I8254_CTRL_MSB_ONLY		I8254_CTRL_RW(2)
+#define I8254_CTRL_LSB_MSB		I8254_CTRL_RW(3)
+
+/* counter maps zero to 0x10000 */
+#define I8254_MAX_COUNT			0x10000
+
+/**
+ * struct comedi_8254 - private data used by this module
+ * @iobase:		PIO base address of the registers (in/out)
+ * @mmio:		MMIO base address of the registers (read/write)
+ * @iosize:		I/O size used to access the registers (b/w/l)
+ * @regshift:		register gap shift
+ * @osc_base:		cascaded oscillator speed in ns
+ * @divisor:		divisor for single counter
+ * @divisor1:		divisor loaded into first cascaded counter
+ * @divisor2:		divisor loaded into second cascaded counter
+ * #next_div:		next divisor for single counter
+ * @next_div1:		next divisor to use for first cascaded counter
+ * @next_div2:		next divisor to use for second cascaded counter
+ * @clock_src;		current clock source for each counter (driver specific)
+ * @gate_src;		current gate source  for each counter (driver specific)
+ * @busy:		flags used to indicate that a counter is "busy"
+ * @insn_config:	driver specific (*insn_config) callback
+ */
+struct comedi_8254 {
+	unsigned long iobase;
+	void __iomem *mmio;
+	unsigned int iosize;
+	unsigned int regshift;
+	unsigned int osc_base;
+	unsigned int divisor;
+	unsigned int divisor1;
+	unsigned int divisor2;
+	unsigned int next_div;
+	unsigned int next_div1;
+	unsigned int next_div2;
+	unsigned int clock_src[3];
+	unsigned int gate_src[3];
+	bool busy[3];
+
+	int (*insn_config)(struct comedi_device *dev,
+			   struct comedi_subdevice *s,
+			   struct comedi_insn *insn, unsigned int *data);
+};
+
+unsigned int comedi_8254_status(struct comedi_8254 *i8254,
+				unsigned int counter);
+unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter);
+void comedi_8254_write(struct comedi_8254 *i8254,
+		       unsigned int counter, unsigned int val);
+
+int comedi_8254_set_mode(struct comedi_8254 *i8254,
+			 unsigned int counter, unsigned int mode);
+int comedi_8254_load(struct comedi_8254 *i8254,
+		     unsigned int counter, unsigned int val, unsigned int mode);
+
+void comedi_8254_pacer_enable(struct comedi_8254 *i8254,
+			      unsigned int counter1, unsigned int counter2,
+			      bool enable);
+void comedi_8254_update_divisors(struct comedi_8254 *i8254);
+void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254,
+				     unsigned int *nanosec, unsigned int flags);
+void comedi_8254_ns_to_timer(struct comedi_8254 *i8254,
+			     unsigned int *nanosec, unsigned int flags);
+
+void comedi_8254_set_busy(struct comedi_8254 *i8254,
+			  unsigned int counter, bool busy);
+
+void comedi_8254_subdevice_init(struct comedi_subdevice *s,
+				struct comedi_8254 *i8254);
+
+struct comedi_8254 *comedi_8254_init(unsigned long iobase,
+				     unsigned int osc_base,
+				     unsigned int iosize,
+				     unsigned int regshift);
+struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
+					unsigned int osc_base,
+					unsigned int iosize,
+					unsigned int regshift);
+
+#endif	/* _COMEDI_8254_H */
diff --git a/drivers/comedi/drivers/comedi_8255.c b/drivers/comedi/drivers/comedi_8255.c
new file mode 100644
index 000000000000..b7ca465933ee
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_8255.c
@@ -0,0 +1,276 @@
+// 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 "../comedidev.h"
+
+#include "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 000000000000..4392b5927a99
--- /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 "../comedi.h"
+#include "../comedilib.h"
+#include "../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 000000000000..c729094298c2
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_isadma.c
@@ -0,0 +1,267 @@
+// 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 <asm/dma.h>
+
+#include "../comedidev.h"
+
+#include "comedi_isadma.h"
+
+/**
+ * comedi_isadma_program - program and enable an ISA DMA transfer
+ * @desc:	the ISA DMA cookie to program and enable
+ */
+void comedi_isadma_program(struct comedi_isadma_desc *desc)
+{
+	unsigned long flags;
+
+	flags = claim_dma_lock();
+	clear_dma_ff(desc->chan);
+	set_dma_mode(desc->chan, desc->mode);
+	set_dma_addr(desc->chan, desc->hw_addr);
+	set_dma_count(desc->chan, desc->size);
+	enable_dma(desc->chan);
+	release_dma_lock(flags);
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_program);
+
+/**
+ * comedi_isadma_disable - disable the ISA DMA channel
+ * @dma_chan:	the DMA channel to disable
+ *
+ * Returns the residue (remaining bytes) left in the DMA transfer.
+ */
+unsigned int comedi_isadma_disable(unsigned int dma_chan)
+{
+	unsigned long flags;
+	unsigned int residue;
+
+	flags = claim_dma_lock();
+	disable_dma(dma_chan);
+	residue = get_dma_residue(dma_chan);
+	release_dma_lock(flags);
+
+	return residue;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_disable);
+
+/**
+ * comedi_isadma_disable_on_sample - disable the ISA DMA channel
+ * @dma_chan:	the DMA channel to disable
+ * @size:	the sample size (in bytes)
+ *
+ * Returns the residue (remaining bytes) left in the DMA transfer.
+ */
+unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan,
+					     unsigned int size)
+{
+	int stalled = 0;
+	unsigned long flags;
+	unsigned int residue;
+	unsigned int new_residue;
+
+	residue = comedi_isadma_disable(dma_chan);
+	while (residue % size) {
+		/* residue is a partial sample, enable DMA to allow more data */
+		flags = claim_dma_lock();
+		enable_dma(dma_chan);
+		release_dma_lock(flags);
+
+		udelay(2);
+		new_residue = comedi_isadma_disable(dma_chan);
+
+		/* is DMA stalled? */
+		if (new_residue == residue) {
+			stalled++;
+			if (stalled > 10)
+				break;
+		} else {
+			residue = new_residue;
+			stalled = 0;
+		}
+	}
+	return residue;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_disable_on_sample);
+
+/**
+ * comedi_isadma_poll - poll the current DMA transfer
+ * @dma:	the ISA DMA to poll
+ *
+ * Returns the position (in bytes) of the current DMA transfer.
+ */
+unsigned int comedi_isadma_poll(struct comedi_isadma *dma)
+{
+	struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
+	unsigned long flags;
+	unsigned int result;
+	unsigned int result1;
+
+	flags = claim_dma_lock();
+	clear_dma_ff(desc->chan);
+	if (!isa_dma_bridge_buggy)
+		disable_dma(desc->chan);
+	result = get_dma_residue(desc->chan);
+	/*
+	 * Read the counter again and choose higher value in order to
+	 * avoid reading during counter lower byte roll over if the
+	 * isa_dma_bridge_buggy is set.
+	 */
+	result1 = get_dma_residue(desc->chan);
+	if (!isa_dma_bridge_buggy)
+		enable_dma(desc->chan);
+	release_dma_lock(flags);
+
+	if (result < result1)
+		result = result1;
+	if (result >= desc->size || result == 0)
+		return 0;
+	return desc->size - result;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_poll);
+
+/**
+ * comedi_isadma_set_mode - set the ISA DMA transfer direction
+ * @desc:	the ISA DMA cookie to set
+ * @dma_dir:	the DMA direction
+ */
+void comedi_isadma_set_mode(struct comedi_isadma_desc *desc, char dma_dir)
+{
+	desc->mode = (dma_dir == COMEDI_ISADMA_READ) ? DMA_MODE_READ
+						     : DMA_MODE_WRITE;
+}
+EXPORT_SYMBOL_GPL(comedi_isadma_set_mode);
+
+/**
+ * comedi_isadma_alloc - allocate and initialize the ISA DMA
+ * @dev:	comedi_device struct
+ * @n_desc:	the number of cookies to allocate
+ * @dma_chan:	DMA channel for the first cookie
+ * @dma_chan2:	DMA channel for the second cookie
+ * @maxsize:	the size of the buffer to allocate for each cookie
+ * @dma_dir:	the DMA direction
+ *
+ * Returns the allocated and initialized ISA DMA or NULL if anything fails.
+ */
+struct comedi_isadma *comedi_isadma_alloc(struct comedi_device *dev,
+					  int n_desc, unsigned int dma_chan1,
+					  unsigned int dma_chan2,
+					  unsigned int maxsize, char dma_dir)
+{
+	struct comedi_isadma *dma = NULL;
+	struct comedi_isadma_desc *desc;
+	unsigned int dma_chans[2];
+	int i;
+
+	if (n_desc < 1 || n_desc > 2)
+		goto no_dma;
+
+	dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+	if (!dma)
+		goto no_dma;
+
+	desc = kcalloc(n_desc, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		goto no_dma;
+	dma->desc = desc;
+	dma->n_desc = n_desc;
+	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_isadma.h b/drivers/comedi/drivers/comedi_isadma.h
new file mode 100644
index 000000000000..9d2b12db7e6e
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_isadma.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * COMEDI ISA DMA support functions
+ * Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ */
+
+#ifndef _COMEDI_ISADMA_H
+#define _COMEDI_ISADMA_H
+
+#include <linux/types.h>
+
+struct comedi_device;
+struct device;
+
+/*
+ * These are used to avoid issues when <asm/dma.h> and the DMA_MODE_
+ * defines are not available.
+ */
+#define COMEDI_ISADMA_READ	0
+#define COMEDI_ISADMA_WRITE	1
+
+/**
+ * struct comedi_isadma_desc - cookie for ISA DMA
+ * @virt_addr:	virtual address of buffer
+ * @hw_addr:	hardware (bus) address of buffer
+ * @chan:	DMA channel
+ * @maxsize:	allocated size of buffer (in bytes)
+ * @size:	transfer size (in bytes)
+ * @mode:	DMA_MODE_READ or DMA_MODE_WRITE
+ */
+struct comedi_isadma_desc {
+	void *virt_addr;
+	dma_addr_t hw_addr;
+	unsigned int chan;
+	unsigned int maxsize;
+	unsigned int size;
+	char mode;
+};
+
+/**
+ * struct comedi_isadma - ISA DMA data
+ * @dev:	device to allocate non-coherent memory for
+ * @desc:	cookie for each DMA buffer
+ * @n_desc:	the number of cookies
+ * @cur_dma:	the current cookie in use
+ * @chan:	the first DMA channel requested
+ * @chan2:	the second DMA channel requested
+ */
+struct comedi_isadma {
+	struct device *dev;
+	struct comedi_isadma_desc *desc;
+	int n_desc;
+	int cur_dma;
+	unsigned int chan;
+	unsigned int chan2;
+};
+
+#if IS_ENABLED(CONFIG_ISA_DMA_API)
+
+void comedi_isadma_program(struct comedi_isadma_desc *desc);
+unsigned int comedi_isadma_disable(unsigned int dma_chan);
+unsigned int comedi_isadma_disable_on_sample(unsigned int dma_chan,
+					     unsigned int size);
+unsigned int comedi_isadma_poll(struct comedi_isadma *dma);
+void comedi_isadma_set_mode(struct comedi_isadma_desc *desc, char dma_dir);
+
+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);
+void comedi_isadma_free(struct comedi_isadma *dma);
+
+#else	/* !IS_ENABLED(CONFIG_ISA_DMA_API) */
+
+static inline void comedi_isadma_program(struct comedi_isadma_desc *desc)
+{
+}
+
+static inline unsigned int comedi_isadma_disable(unsigned int dma_chan)
+{
+	return 0;
+}
+
+static inline unsigned int
+comedi_isadma_disable_on_sample(unsigned int dma_chan, unsigned int size)
+{
+	return 0;
+}
+
+static inline unsigned int comedi_isadma_poll(struct comedi_isadma *dma)
+{
+	return 0;
+}
+
+static inline void comedi_isadma_set_mode(struct comedi_isadma_desc *desc,
+					  char dma_dir)
+{
+}
+
+static inline struct comedi_isadma *
+comedi_isadma_alloc(struct comedi_device *dev, int n_desc,
+		    unsigned int dma_chan1, unsigned int dma_chan2,
+		    unsigned int maxsize, char dma_dir)
+{
+	return NULL;
+}
+
+static inline void comedi_isadma_free(struct comedi_isadma *dma)
+{
+}
+
+#endif	/* !IS_ENABLED(CONFIG_ISA_DMA_API) */
+
+#endif	/* #ifndef _COMEDI_ISADMA_H */
diff --git a/drivers/comedi/drivers/comedi_parport.c b/drivers/comedi/drivers/comedi_parport.c
new file mode 100644
index 000000000000..5338b5eea440
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_parport.c
@@ -0,0 +1,306 @@
+// 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 "../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 000000000000..cbc225eb1991
--- /dev/null
+++ b/drivers/comedi/drivers/comedi_test.c
@@ -0,0 +1,849 @@
+// 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 "../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 000000000000..b8fdd9c1f166
--- /dev/null
+++ b/drivers/comedi/drivers/contec_pci_dio.c
@@ -0,0 +1,117 @@
+// 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 "../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 000000000000..5ef8114c2c85
--- /dev/null
+++ b/drivers/comedi/drivers/dac02.c
@@ -0,0 +1,137 @@
+// 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 "../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 000000000000..f64e747078bd
--- /dev/null
+++ b/drivers/comedi/drivers/daqboard2000.c
@@ -0,0 +1,787 @@
+// 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 "../comedi_pci.h"
+
+#include "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 000000000000..b50743c5b822
--- /dev/null
+++ b/drivers/comedi/drivers/das08.c
@@ -0,0 +1,470 @@
+// 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 "../comedidev.h"
+
+#include "8255.h"
+#include "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 000000000000..ef65a7e504ee
--- /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 000000000000..223479f9ea3c
--- /dev/null
+++ b/drivers/comedi/drivers/das08_cs.c
@@ -0,0 +1,104 @@
+// 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 "../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 000000000000..8c4cfa821423
--- /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 "../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 000000000000..1cd903336a4c
--- /dev/null
+++ b/drivers/comedi/drivers/das08_pci.c
@@ -0,0 +1,96 @@
+// 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 "../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 000000000000..4ac2622b0fac
--- /dev/null
+++ b/drivers/comedi/drivers/das16.c
@@ -0,0 +1,1200 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+#include "8255.h"
+
+#define DAS16_DMA_SIZE 0xff00	/*  size in bytes of allocated dma buffer */
+
+/*
+ * Register I/O map
+ */
+#define DAS16_TRIG_REG			0x00
+#define DAS16_AI_LSB_REG		0x00
+#define DAS16_AI_MSB_REG		0x01
+#define DAS16_MUX_REG			0x02
+#define DAS16_DIO_REG			0x03
+#define DAS16_AO_LSB_REG(x)		((x) ? 0x06 : 0x04)
+#define DAS16_AO_MSB_REG(x)		((x) ? 0x07 : 0x05)
+#define DAS16_STATUS_REG		0x08
+#define DAS16_STATUS_BUSY		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,
+					    sizeof(*lrange) + sizeof(*krange));
+		if (!lrange)
+			return &range_unknown;
+
+		/* initialize ai range */
+		lrange->length = 1;
+		krange = lrange->range;
+		krange->min = min;
+		krange->max = max;
+		krange->flags = UNIT_volt;
+
+		return lrange;
+	}
+
+	/* use software programmable range */
+	if (status & DAS16_STATUS_UNIPOLAR)
+		return das16_ai_uni_lranges[pg_type];
+	return das16_ai_bip_lranges[pg_type];
+}
+
+static const struct comedi_lrange *das16_ao_range(struct comedi_device *dev,
+						  struct comedi_subdevice *s,
+						  struct comedi_devconfig *it)
+{
+	unsigned int min = it->options[6];
+	unsigned int max = it->options[7];
+
+	/* get any user-defined output range */
+	if (min || max) {
+		struct comedi_lrange *lrange;
+		struct comedi_krange *krange;
+
+		/* allocate single-range range table */
+		lrange = comedi_alloc_spriv(s,
+					    sizeof(*lrange) + sizeof(*krange));
+		if (!lrange)
+			return &range_unknown;
+
+		/* initialize ao range */
+		lrange->length = 1;
+		krange = lrange->range;
+		krange->min = min;
+		krange->max = max;
+		krange->flags = UNIT_volt;
+
+		return lrange;
+	}
+
+	return &range_unknown;
+}
+
+static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+	const struct das16_board *board = dev->board_ptr;
+	struct das16_private_struct *devpriv;
+	struct comedi_subdevice *s;
+	unsigned int osc_base;
+	unsigned int status;
+	int ret;
+
+	/*  check that clock setting is valid */
+	if (it->options[3]) {
+		if (it->options[3] != 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 000000000000..75f3dbbe97ac
--- /dev/null
+++ b/drivers/comedi/drivers/das16m1.c
@@ -0,0 +1,622 @@
+// 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 "../comedidev.h"
+
+#include "8255.h"
+#include "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 000000000000..f50891a6ee7d
--- /dev/null
+++ b/drivers/comedi/drivers/das1800.c
@@ -0,0 +1,1364 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+/* misc. defines */
+#define DAS1800_SIZE           16	/* uses 16 io addresses */
+#define FIFO_SIZE              1024	/*  1024 sample fifo */
+#define 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 000000000000..96f4107b8054
--- /dev/null
+++ b/drivers/comedi/drivers/das6402.c
@@ -0,0 +1,669 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+
+/*
+ * Register I/O map
+ */
+#define DAS6402_AI_DATA_REG		0x00
+#define DAS6402_AI_MUX_REG		0x02
+#define DAS6402_AI_MUX_LO(x)		(((x) & 0x3f) << 0)
+#define DAS6402_AI_MUX_HI(x)		(((x) & 0x3f) << 8)
+#define DAS6402_DI_DO_REG		0x03
+#define DAS6402_AO_DATA_REG(x)		(0x04 + ((x) * 2))
+#define DAS6402_AO_LSB_REG(x)		(0x04 + ((x) * 2))
+#define DAS6402_AO_MSB_REG(x)		(0x05 + ((x) * 2))
+#define DAS6402_STATUS_REG		0x08
+#define DAS6402_STATUS_FFNE		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 000000000000..bc08324f422f
--- /dev/null
+++ b/drivers/comedi/drivers/das800.c
@@ -0,0 +1,744 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+
+#define N_CHAN_AI             8	/*  number of analog input channels */
+
+/* Registers for the das800 */
+
+#define DAS800_LSB            0
+#define   FIFO_EMPTY            0x1
+#define   FIFO_OVF              0x2
+#define DAS800_MSB            1
+#define DAS800_CONTROL1       2
+#define   CONTROL1_INTE         0x8
+#define DAS800_CONV_CONTROL   2
+#define   ITE                   0x1
+#define   CASC                  0x2
+#define   DTEN                  0x4
+#define   IEOC                  0x8
+#define   EACS                  0x10
+#define   CONV_HCEN             0x80
+#define DAS800_SCAN_LIMITS    2
+#define DAS800_STATUS         2
+#define   IRQ                   0x8
+#define   BUSY                  0x80
+#define DAS800_GAIN           3
+#define   CIO_FFOV              0x8   /* cio-das802/16 fifo overflow */
+#define   CIO_ENHF              0x90  /* cio-das802/16 fifo half full int ena */
+#define   CONTROL1              0x80
+#define   CONV_CONTROL          0xa0
+#define   SCAN_LIMITS           0xc0
+#define   ID                    0xe0
+#define DAS800_8254           4
+#define DAS800_STATUS2        7
+#define   STATUS2_HCEN          0x80
+#define   STATUS2_INTE          0X20
+#define DAS800_ID             7
+
+#define DAS802_16_HALF_FIFO_SZ	128
+
+struct das800_board {
+	const char *name;
+	int ai_speed;
+	const struct comedi_lrange *ai_range;
+	int resolution;
+};
+
+static const struct comedi_lrange range_das801_ai = {
+	9, {
+		BIP_RANGE(5),
+		BIP_RANGE(10),
+		UNI_RANGE(10),
+		BIP_RANGE(0.5),
+		UNI_RANGE(1),
+		BIP_RANGE(0.05),
+		UNI_RANGE(0.1),
+		BIP_RANGE(0.01),
+		UNI_RANGE(0.02)
+	}
+};
+
+static const struct comedi_lrange range_cio_das801_ai = {
+	9, {
+		BIP_RANGE(5),
+		BIP_RANGE(10),
+		UNI_RANGE(10),
+		BIP_RANGE(0.5),
+		UNI_RANGE(1),
+		BIP_RANGE(0.05),
+		UNI_RANGE(0.1),
+		BIP_RANGE(0.005),
+		UNI_RANGE(0.01)
+	}
+};
+
+static const struct comedi_lrange range_das802_ai = {
+	9, {
+		BIP_RANGE(5),
+		BIP_RANGE(10),
+		UNI_RANGE(10),
+		BIP_RANGE(2.5),
+		UNI_RANGE(5),
+		BIP_RANGE(1.25),
+		UNI_RANGE(2.5),
+		BIP_RANGE(0.625),
+		UNI_RANGE(1.25)
+	}
+};
+
+static const struct comedi_lrange range_das80216_ai = {
+	8, {
+		BIP_RANGE(10),
+		UNI_RANGE(10),
+		BIP_RANGE(5),
+		UNI_RANGE(5),
+		BIP_RANGE(2.5),
+		UNI_RANGE(2.5),
+		BIP_RANGE(1.25),
+		UNI_RANGE(1.25)
+	}
+};
+
+enum das800_boardinfo {
+	BOARD_DAS800,
+	BOARD_CIODAS800,
+	BOARD_DAS801,
+	BOARD_CIODAS801,
+	BOARD_DAS802,
+	BOARD_CIODAS802,
+	BOARD_CIODAS80216,
+};
+
+static const struct das800_board das800_boards[] = {
+	[BOARD_DAS800] = {
+		.name		= "das-800",
+		.ai_speed	= 25000,
+		.ai_range	= &range_bipolar5,
+		.resolution	= 12,
+	},
+	[BOARD_CIODAS800] = {
+		.name		= "cio-das800",
+		.ai_speed	= 20000,
+		.ai_range	= &range_bipolar5,
+		.resolution	= 12,
+	},
+	[BOARD_DAS801] = {
+		.name		= "das-801",
+		.ai_speed	= 25000,
+		.ai_range	= &range_das801_ai,
+		.resolution	= 12,
+	},
+	[BOARD_CIODAS801] = {
+		.name		= "cio-das801",
+		.ai_speed	= 20000,
+		.ai_range	= &range_cio_das801_ai,
+		.resolution	= 12,
+	},
+	[BOARD_DAS802] = {
+		.name		= "das-802",
+		.ai_speed	= 25000,
+		.ai_range	= &range_das802_ai,
+		.resolution	= 12,
+	},
+	[BOARD_CIODAS802] = {
+		.name		= "cio-das802",
+		.ai_speed	= 20000,
+		.ai_range	= &range_das802_ai,
+		.resolution	= 12,
+	},
+	[BOARD_CIODAS80216] = {
+		.name		= "cio-das802/16",
+		.ai_speed	= 10000,
+		.ai_range	= &range_das80216_ai,
+		.resolution	= 16,
+	},
+};
+
+struct das800_private {
+	unsigned int do_bits;	/* digital output bits */
+};
+
+static void das800_ind_write(struct comedi_device *dev,
+			     unsigned 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 000000000000..56682f01242f
--- /dev/null
+++ b/drivers/comedi/drivers/dmm32at.c
@@ -0,0 +1,616 @@
+// 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 "../comedidev.h"
+
+#include "8255.h"
+
+/* Board register addresses */
+#define DMM32AT_AI_START_CONV_REG	0x00
+#define DMM32AT_AI_LSB_REG		0x00
+#define DMM32AT_AUX_DOUT_REG		0x01
+#define DMM32AT_AUX_DOUT2		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 000000000000..0d571d817b4e
--- /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 "../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 000000000000..0eb5e6ba6916
--- /dev/null
+++ b/drivers/comedi/drivers/dt2811.c
@@ -0,0 +1,645 @@
+// 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 "../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 000000000000..ed44ce0d151b
--- /dev/null
+++ b/drivers/comedi/drivers/dt2814.c
@@ -0,0 +1,372 @@
+// 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 "../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 000000000000..5906f32aa01f
--- /dev/null
+++ b/drivers/comedi/drivers/dt2815.c
@@ -0,0 +1,217 @@
+// 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 "../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 000000000000..7c1463e835d3
--- /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 "../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 000000000000..2656b4b0e3d0
--- /dev/null
+++ b/drivers/comedi/drivers/dt282x.c
@@ -0,0 +1,1172 @@
+// 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 "../comedidev.h"
+
+#include "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 000000000000..ec27aa4730d4
--- /dev/null
+++ b/drivers/comedi/drivers/dt3000.c
@@ -0,0 +1,740 @@
+// 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 "../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 000000000000..634f57730c1e
--- /dev/null
+++ b/drivers/comedi/drivers/dt9812.c
@@ -0,0 +1,871 @@
+// 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/uaccess.h>
+
+#include "../comedi_usb.h"
+
+#define DT9812_DIAGS_BOARD_INFO_ADDR	0xFBFF
+#define DT9812_MAX_WRITE_CMD_PIPE_SIZE	32
+#define DT9812_MAX_READ_CMD_PIPE_SIZE	32
+
+/* usb_bulk_msg() 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;
+	int count, ret;
+
+	cmd.cmd = cpu_to_le32(DT9812_R_FLASH_DATA);
+	cmd.u.flash_data_info.address =
+	    cpu_to_le16(DT9812_DIAGS_BOARD_INFO_ADDR + offset);
+	cmd.u.flash_data_info.numbytes = cpu_to_le16(buf_size);
+
+	/* DT9812 only responds to 32 byte writes!! */
+	ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+			   &cmd, 32, &count, DT9812_USB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	return usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr),
+			    buf, buf_size, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_read_multiple_registers(struct comedi_device *dev,
+					  int reg_count, u8 *address,
+					  u8 *value)
+{
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	struct dt9812_private *devpriv = dev->private;
+	struct dt9812_usb_cmd cmd;
+	int i, count, ret;
+
+	cmd.cmd = cpu_to_le32(DT9812_R_MULTI_BYTE_REG);
+	cmd.u.read_multi_info.count = reg_count;
+	for (i = 0; i < reg_count; i++)
+		cmd.u.read_multi_info.address[i] = address[i];
+
+	/* DT9812 only responds to 32 byte writes!! */
+	ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+			   &cmd, 32, &count, DT9812_USB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	return usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr),
+			    value, reg_count, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_write_multiple_registers(struct comedi_device *dev,
+					   int reg_count, u8 *address,
+					   u8 *value)
+{
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	struct dt9812_private *devpriv = dev->private;
+	struct dt9812_usb_cmd cmd;
+	int i, count;
+
+	cmd.cmd = cpu_to_le32(DT9812_W_MULTI_BYTE_REG);
+	cmd.u.read_multi_info.count = reg_count;
+	for (i = 0; i < reg_count; i++) {
+		cmd.u.write_multi_info.write[i].address = address[i];
+		cmd.u.write_multi_info.write[i].value = value[i];
+	}
+
+	/* DT9812 only responds to 32 byte writes!! */
+	return usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+			    &cmd, 32, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_rmw_multiple_registers(struct comedi_device *dev,
+					 int reg_count,
+					 struct dt9812_rmw_byte *rmw)
+{
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	struct dt9812_private *devpriv = dev->private;
+	struct dt9812_usb_cmd cmd;
+	int i, count;
+
+	cmd.cmd = cpu_to_le32(DT9812_RMW_MULTI_BYTE_REG);
+	cmd.u.rmw_multi_info.count = reg_count;
+	for (i = 0; i < reg_count; i++)
+		cmd.u.rmw_multi_info.rmw[i] = rmw[i];
+
+	/* DT9812 only responds to 32 byte writes!! */
+	return usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+			    &cmd, 32, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_digital_in(struct comedi_device *dev, u8 *bits)
+{
+	struct dt9812_private *devpriv = dev->private;
+	u8 reg[2] = { F020_SFR_P3, F020_SFR_P1 };
+	u8 value[2];
+	int ret;
+
+	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 000000000000..c224422bb126
--- /dev/null
+++ b/drivers/comedi/drivers/dyna_pci10xx.c
@@ -0,0 +1,265 @@
+// 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 "../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 000000000000..b715f30659fa
--- /dev/null
+++ b/drivers/comedi/drivers/fl512.c
@@ -0,0 +1,143 @@
+// 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 "../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 000000000000..e35e4a743714
--- /dev/null
+++ b/drivers/comedi/drivers/gsc_hpdi.c
@@ -0,0 +1,723 @@
+// 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 "../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 000000000000..16d2b78de83c
--- /dev/null
+++ b/drivers/comedi/drivers/icp_multi.c
@@ -0,0 +1,336 @@
+// 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 "../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 000000000000..399255dbe388
--- /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 "../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 000000000000..7a02c4fa3cda
--- /dev/null
+++ b/drivers/comedi/drivers/jr3_pci.c
@@ -0,0 +1,816 @@
+// 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 "../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_min_full_scales(struct jr3_sensor __iomem *sensor)
+{
+	struct six_axis_t result;
+
+	result.fx = get_s16(&sensor->min_full_scale.fx);
+	result.fy = get_s16(&sensor->min_full_scale.fy);
+	result.fz = get_s16(&sensor->min_full_scale.fz);
+	result.mx = get_s16(&sensor->min_full_scale.mx);
+	result.my = get_s16(&sensor->min_full_scale.my);
+	result.mz = get_s16(&sensor->min_full_scale.mz);
+	return result;
+}
+
+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 min_full_scale;
+			struct six_axis_t max_full_scale;
+
+			min_full_scale = get_min_full_scales(sensor);
+			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 000000000000..acd4e5456ceb
--- /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 000000000000..bef1b20c1c8d
--- /dev/null
+++ b/drivers/comedi/drivers/ke_counter.c
@@ -0,0 +1,232 @@
+// 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 "../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 000000000000..0d3d4cafce2e
--- /dev/null
+++ b/drivers/comedi/drivers/me4000.c
@@ -0,0 +1,1278 @@
+// 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 "../comedi_pci.h"
+
+#include "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 000000000000..ef18e387471b
--- /dev/null
+++ b/drivers/comedi/drivers/me_daq.c
@@ -0,0 +1,556 @@
+// 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 "../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 000000000000..9da8dd748078
--- /dev/null
+++ b/drivers/comedi/drivers/mf6x4.c
@@ -0,0 +1,311 @@
+// 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 "../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 000000000000..70960e3ba878
--- /dev/null
+++ b/drivers/comedi/drivers/mite.c
@@ -0,0 +1,938 @@
+// 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 "../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 000000000000..c6c056069bb7
--- /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 000000000000..646f4c086204
--- /dev/null
+++ b/drivers/comedi/drivers/mpc624.c
@@ -0,0 +1,311 @@
+// 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 "../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 000000000000..c1897aee9a9a
--- /dev/null
+++ b/drivers/comedi/drivers/multiq3.c
@@ -0,0 +1,332 @@
+// 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 "../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 000000000000..f1a45cf7342a
--- /dev/null
+++ b/drivers/comedi/drivers/ni_6527.c
@@ -0,0 +1,493 @@
+// 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 "../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 000000000000..7cd8497420f2
--- /dev/null
+++ b/drivers/comedi/drivers/ni_65xx.c
@@ -0,0 +1,823 @@
+// 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 "../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 000000000000..e60d0125bcb2
--- /dev/null
+++ b/drivers/comedi/drivers/ni_660x.c
@@ -0,0 +1,1255 @@
+// 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 "../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 000000000000..c197e47486be
--- /dev/null
+++ b/drivers/comedi/drivers/ni_670x.c
@@ -0,0 +1,282 @@
+// 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 "../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 000000000000..10ad7b88713e
--- /dev/null
+++ b/drivers/comedi/drivers/ni_at_a2150.c
@@ -0,0 +1,782 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+#define A2150_DMA_BUFFER_SIZE	0xff00	/*  size in bytes of dma buffer */
+
+/* Registers and bits */
+#define CONFIG_REG		0x0
+#define   CHANNEL_BITS(x)	((x) & 0x7)
+#define   CHANNEL_MASK		0x7
+#define   CLOCK_SELECT_BITS(x)	(((x) & 0x3) << 3)
+#define   CLOCK_DIVISOR_BITS(x)	(((x) & 0x3) << 5)
+#define   CLOCK_MASK		(0xf << 3)
+/* 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 000000000000..2a0fb4d460db
--- /dev/null
+++ b/drivers/comedi/drivers/ni_at_ao.c
@@ -0,0 +1,374 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+
+/*
+ * Register map
+ *
+ * Register-level programming information can be found in NI
+ * document 320379.pdf.
+ */
+#define ATAO_DIO_REG		0x00
+#define ATAO_CFG2_REG		0x02
+#define ATAO_CFG2_CALLD_NOP	(0 << 14)
+#define ATAO_CFG2_CALLD(x)	((((x) >> 3) + 1) << 14)
+#define ATAO_CFG2_FFRTEN	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 000000000000..56c78da475e7
--- /dev/null
+++ b/drivers/comedi/drivers/ni_atmio.c
@@ -0,0 +1,360 @@
+// 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 "../comedidev.h"
+
+#include <linux/isapnp.h>
+
+#include "ni_stc.h"
+#include "8255.h"
+
+/* AT specific setup */
+static const struct ni_board_struct ni_boards[] = {
+	{
+		.name		= "at-mio-16e-1",
+		.device_id	= 44,
+		.isapnp_id	= 0x0000,	/* XXX unknown */
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 8192,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 800,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_fifo_depth	= 2048,
+		.ao_range_table	= &range_ni_E_ao_ext,
+		.ao_speed	= 1000,
+		.caldac		= { mb88341 },
+	}, {
+		.name		= "at-mio-16e-2",
+		.device_id	= 25,
+		.isapnp_id	= 0x1900,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 2048,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 2000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_fifo_depth	= 2048,
+		.ao_range_table	= &range_ni_E_ao_ext,
+		.ao_speed	= 1000,
+		.caldac		= { mb88341 },
+	}, {
+		.name		= "at-mio-16e-10",
+		.device_id	= 36,
+		.isapnp_id	= 0x2400,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 512,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 10000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_range_table	= &range_ni_E_ao_ext,
+		.ao_speed	= 10000,
+		.caldac		= { ad8804_debug },
+	}, {
+		.name		= "at-mio-16de-10",
+		.device_id	= 37,
+		.isapnp_id	= 0x2500,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 512,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 10000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_range_table	= &range_ni_E_ao_ext,
+		.ao_speed	= 10000,
+		.caldac		= { ad8804_debug },
+		.has_8255	= 1,
+	}, {
+		.name		= "at-mio-64e-3",
+		.device_id	= 38,
+		.isapnp_id	= 0x2600,
+		.n_adchan	= 64,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 2048,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 2000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_fifo_depth	= 2048,
+		.ao_range_table	= &range_ni_E_ao_ext,
+		.ao_speed	= 1000,
+		.caldac		= { ad8804_debug },
+	}, {
+		.name		= "at-mio-16xe-50",
+		.device_id	= 39,
+		.isapnp_id	= 0x2700,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0xffff,
+		.ai_fifo_depth	= 512,
+		.alwaysdither	= 1,
+		.gainlkup	= ai_gain_8,
+		.ai_speed	= 50000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_range_table	= &range_bipolar10,
+		.ao_speed	= 50000,
+		.caldac		= { dac8800, dac8043 },
+	}, {
+		.name		= "at-mio-16xe-10",
+		.device_id	= 50,
+		.isapnp_id	= 0x0000,	/* XXX unknown */
+		.n_adchan	= 16,
+		.ai_maxdata	= 0xffff,
+		.ai_fifo_depth	= 512,
+		.alwaysdither	= 1,
+		.gainlkup	= ai_gain_14,
+		.ai_speed	= 10000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0xffff,
+		.ao_fifo_depth	= 2048,
+		.ao_range_table	= &range_ni_E_ao_ext,
+		.ao_speed	= 1000,
+		.caldac		= { dac8800, dac8043, ad8522 },
+	}, {
+		.name		= "at-ai-16xe-10",
+		.device_id	= 51,
+		.isapnp_id	= 0x0000,	/* XXX unknown */
+		.n_adchan	= 16,
+		.ai_maxdata	= 0xffff,
+		.ai_fifo_depth	= 512,
+		.alwaysdither	= 1,		/* unknown */
+		.gainlkup	= ai_gain_14,
+		.ai_speed	= 10000,
+		.caldac		= { dac8800, dac8043, ad8522 },
+	},
+};
+
+static const int ni_irqpin[] = {
+	-1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7
+};
+
+#include "ni_mio_common.c"
+
+static 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 000000000000..dffce1aa3e69
--- /dev/null
+++ b/drivers/comedi/drivers/ni_atmio16d.c
@@ -0,0 +1,729 @@
+// 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 "../comedidev.h"
+
+#include "8255.h"
+
+/* Configuration and Status Registers */
+#define COM_REG_1	0x00	/* wo 16 */
+#define STAT_REG	0x00	/* ro 16 */
+#define COM_REG_2	0x02	/* wo 16 */
+/* Event Strobe Registers */
+#define START_CONVERT_REG	0x08	/* wo 16 */
+#define START_DAQ_REG		0x0A	/* wo 16 */
+#define AD_CLEAR_REG		0x0C	/* wo 16 */
+#define EXT_STROBE_REG		0x0E	/* wo 16 */
+/* Analog Output Registers */
+#define DAC0_REG		0x10	/* wo 16 */
+#define DAC1_REG		0x12	/* wo 16 */
+#define INT2CLR_REG		0x14	/* wo 16 */
+/* Analog Input Registers */
+#define MUX_CNTR_REG		0x04	/* wo 16 */
+#define MUX_GAIN_REG		0x06	/* wo 16 */
+#define AD_FIFO_REG		0x16	/* ro 16 */
+#define DMA_TC_INT_CLR_REG	0x16	/* wo 16 */
+/* AM9513A Counter/Timer Registers */
+#define AM9513A_DATA_REG	0x18	/* rw 16 */
+#define AM9513A_COM_REG		0x1A	/* wo 16 */
+#define AM9513A_STAT_REG	0x1A	/* ro 16 */
+/* MIO-16 Digital I/O Registers */
+#define MIO_16_DIG_IN_REG	0x1C	/* ro 16 */
+#define MIO_16_DIG_OUT_REG	0x1C	/* wo 16 */
+/* RTSI Switch Registers */
+#define RTSI_SW_SHIFT_REG	0x1E	/* wo 8 */
+#define RTSI_SW_STROBE_REG	0x1F	/* wo 8 */
+/* DIO-24 Registers */
+#define DIO_24_PORTA_REG	0x00	/* rw 8 */
+#define DIO_24_PORTB_REG	0x01	/* rw 8 */
+#define DIO_24_PORTC_REG	0x02	/* rw 8 */
+#define DIO_24_CNFG_REG		0x03	/* wo 8 */
+
+/* Command Register bits */
+#define COMREG1_2SCADC		0x0001
+#define COMREG1_1632CNT		0x0002
+#define COMREG1_SCANEN		0x0008
+#define COMREG1_DAQEN		0x0010
+#define COMREG1_DMAEN		0x0020
+#define COMREG1_CONVINTEN	0x0080
+#define COMREG2_SCN2		0x0010
+#define COMREG2_INTEN		0x0080
+#define COMREG2_DOUTEN0		0x0100
+#define COMREG2_DOUTEN1		0x0200
+/* Status Register bits */
+#define STAT_AD_OVERRUN		0x0100
+#define STAT_AD_OVERFLOW	0x0200
+#define STAT_AD_DAQPROG		0x0800
+#define STAT_AD_CONVAVAIL	0x2000
+#define STAT_AD_DAQSTOPINT	0x4000
+/* AM9513A Counter/Timer defines */
+#define CLOCK_1_MHZ		0x8B25
+#define CLOCK_100_KHZ	0x8C25
+#define CLOCK_10_KHZ	0x8D25
+#define CLOCK_1_KHZ		0x8E25
+#define CLOCK_100_HZ	0x8F25
+
+struct atmio16_board_t {
+	const char *name;
+	int has_8255;
+};
+
+/* range structs */
+static const struct comedi_lrange range_atmio16d_ai_10_bipolar = {
+	4, {
+		BIP_RANGE(10),
+		BIP_RANGE(1),
+		BIP_RANGE(0.1),
+		BIP_RANGE(0.02)
+	}
+};
+
+static const struct comedi_lrange range_atmio16d_ai_5_bipolar = {
+	4, {
+		BIP_RANGE(5),
+		BIP_RANGE(0.5),
+		BIP_RANGE(0.05),
+		BIP_RANGE(0.01)
+	}
+};
+
+static const struct comedi_lrange range_atmio16d_ai_unipolar = {
+	4, {
+		UNI_RANGE(10),
+		UNI_RANGE(1),
+		UNI_RANGE(0.1),
+		UNI_RANGE(0.02)
+	}
+};
+
+/* private data struct */
+struct atmio16d_private {
+	enum { adc_diff, adc_singleended } adc_mux;
+	enum { adc_bipolar10, adc_bipolar5, adc_unipolar10 } adc_range;
+	enum { adc_2comp, adc_straight } adc_coding;
+	enum { dac_bipolar, dac_unipolar } dac0_range, dac1_range;
+	enum { dac_internal, dac_external } dac0_reference, dac1_reference;
+	enum { dac_2comp, dac_straight } dac0_coding, dac1_coding;
+	const struct comedi_lrange *ao_range_type_list[2];
+	unsigned int com_reg_1_state; /* current state of command register 1 */
+	unsigned int com_reg_2_state; /* current state of command register 2 */
+};
+
+static void reset_counters(struct comedi_device *dev)
+{
+	/* Counter 2 */
+	outw(0xFFC2, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF02, dev->iobase + AM9513A_COM_REG);
+	outw(0x4, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF0A, dev->iobase + AM9513A_COM_REG);
+	outw(0x3, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF42, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF42, dev->iobase + AM9513A_COM_REG);
+	/* Counter 3 */
+	outw(0xFFC4, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF03, dev->iobase + AM9513A_COM_REG);
+	outw(0x4, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF0B, dev->iobase + AM9513A_COM_REG);
+	outw(0x3, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF44, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF44, dev->iobase + AM9513A_COM_REG);
+	/* Counter 4 */
+	outw(0xFFC8, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF04, dev->iobase + AM9513A_COM_REG);
+	outw(0x4, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF0C, dev->iobase + AM9513A_COM_REG);
+	outw(0x3, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF48, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF48, dev->iobase + AM9513A_COM_REG);
+	/* Counter 5 */
+	outw(0xFFD0, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF05, dev->iobase + AM9513A_COM_REG);
+	outw(0x4, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF0D, dev->iobase + AM9513A_COM_REG);
+	outw(0x3, dev->iobase + AM9513A_DATA_REG);
+	outw(0xFF50, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF50, dev->iobase + AM9513A_COM_REG);
+
+	outw(0, dev->iobase + AD_CLEAR_REG);
+}
+
+static void reset_atmio16d(struct comedi_device *dev)
+{
+	struct atmio16d_private *devpriv = dev->private;
+	int i;
+
+	/* now we need to initialize the board */
+	outw(0, dev->iobase + COM_REG_1);
+	outw(0, dev->iobase + COM_REG_2);
+	outw(0, dev->iobase + MUX_GAIN_REG);
+	/* init AM9513A timer */
+	outw(0xFFFF, dev->iobase + AM9513A_COM_REG);
+	outw(0xFFEF, dev->iobase + AM9513A_COM_REG);
+	outw(0xFF17, dev->iobase + AM9513A_COM_REG);
+	outw(0xF000, dev->iobase + AM9513A_DATA_REG);
+	for (i = 1; i <= 5; ++i) {
+		outw(0xFF00 + i, dev->iobase + AM9513A_COM_REG);
+		outw(0x0004, dev->iobase + AM9513A_DATA_REG);
+		outw(0xFF08 + i, dev->iobase + AM9513A_COM_REG);
+		outw(0x3, dev->iobase + AM9513A_DATA_REG);
+	}
+	outw(0xFF5F, dev->iobase + AM9513A_COM_REG);
+	/* timer init done */
+	outw(0, dev->iobase + AD_CLEAR_REG);
+	outw(0, dev->iobase + INT2CLR_REG);
+	/* select straight binary mode for Analog Input */
+	devpriv->com_reg_1_state |= 1;
+	outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1);
+	devpriv->adc_coding = adc_straight;
+	/* zero the analog outputs */
+	outw(2048, dev->iobase + DAC0_REG);
+	outw(2048, dev->iobase + DAC1_REG);
+}
+
+static irqreturn_t atmio16d_interrupt(int irq, void *d)
+{
+	struct comedi_device *dev = d;
+	struct comedi_subdevice *s = dev->read_subdev;
+	unsigned short val;
+
+	val = inw(dev->iobase + AD_FIFO_REG);
+	comedi_buf_write_samples(s, &val, 1);
+	comedi_handle_events(dev, s);
+
+	return IRQ_HANDLED;
+}
+
+static int atmio16d_ai_cmdtest(struct comedi_device *dev,
+			       struct comedi_subdevice *s,
+			       struct comedi_cmd *cmd)
+{
+	int err = 0;
+
+	/* Step 1 : check if triggers are trivially valid */
+
+	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
+	err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+					TRIG_FOLLOW | TRIG_TIMER);
+	err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+	if (err)
+		return 1;
+
+	/* Step 2a : make sure trigger sources are unique */
+
+	err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+	err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+	/* Step 2b : and mutually compatible */
+
+	if (err)
+		return 2;
+
+	/* Step 3: check if arguments are trivially valid */
+
+	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+
+	if (cmd->scan_begin_src == TRIG_FOLLOW) {
+		/* internal trigger */
+		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+	}
+
+	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 000000000000..d40fc89f9cef
--- /dev/null
+++ b/drivers/comedi/drivers/ni_daq_700.c
@@ -0,0 +1,280 @@
+// 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 "../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 000000000000..44fb65afc218
--- /dev/null
+++ b/drivers/comedi/drivers/ni_daq_dio24.c
@@ -0,0 +1,82 @@
+// 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 "../comedi_pcmcia.h"
+
+#include "8255.h"
+
+static int dio24_auto_attach(struct comedi_device *dev,
+			     unsigned long context)
+{
+	struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+	struct comedi_subdevice *s;
+	int ret;
+
+	link->config_flags |= CONF_AUTO_SET_IO;
+	ret = comedi_pcmcia_enable(dev, NULL);
+	if (ret)
+		return ret;
+	dev->iobase = link->resource[0]->start;
+
+	ret = comedi_alloc_subdevices(dev, 1);
+	if (ret)
+		return ret;
+
+	/* 8255 dio */
+	s = &dev->subdevices[0];
+	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 000000000000..1f4a07bd1d26
--- /dev/null
+++ b/drivers/comedi/drivers/ni_labpc.c
@@ -0,0 +1,116 @@
+// 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 "../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 000000000000..728e901f53cd
--- /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 000000000000..dd97946eacaf
--- /dev/null
+++ b/drivers/comedi/drivers/ni_labpc_common.c
@@ -0,0 +1,1363 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+#include "8255.h"
+#include "ni_labpc.h"
+#include "ni_labpc_regs.h"
+#include "ni_labpc_isadma.h"
+
+enum scan_mode {
+	MODE_SINGLE_CHAN,
+	MODE_SINGLE_CHAN_INTERVAL,
+	MODE_MULT_CHAN_UP,
+	MODE_MULT_CHAN_DOWN,
+};
+
+static const struct comedi_lrange range_labpc_plus_ai = {
+	16, {
+		BIP_RANGE(5),
+		BIP_RANGE(4),
+		BIP_RANGE(2.5),
+		BIP_RANGE(1),
+		BIP_RANGE(0.5),
+		BIP_RANGE(0.25),
+		BIP_RANGE(0.1),
+		BIP_RANGE(0.05),
+		UNI_RANGE(10),
+		UNI_RANGE(8),
+		UNI_RANGE(5),
+		UNI_RANGE(2),
+		UNI_RANGE(1),
+		UNI_RANGE(0.5),
+		UNI_RANGE(0.2),
+		UNI_RANGE(0.1)
+	}
+};
+
+static const struct comedi_lrange range_labpc_1200_ai = {
+	14, {
+		BIP_RANGE(5),
+		BIP_RANGE(2.5),
+		BIP_RANGE(1),
+		BIP_RANGE(0.5),
+		BIP_RANGE(0.25),
+		BIP_RANGE(0.1),
+		BIP_RANGE(0.05),
+		UNI_RANGE(10),
+		UNI_RANGE(5),
+		UNI_RANGE(2),
+		UNI_RANGE(1),
+		UNI_RANGE(0.5),
+		UNI_RANGE(0.2),
+		UNI_RANGE(0.1)
+	}
+};
+
+static const struct comedi_lrange range_labpc_ao = {
+	2, {
+		BIP_RANGE(5),
+		UNI_RANGE(10)
+	}
+};
+
+/*
+ * functions that do inb/outb and readb/writeb so we can use
+ * function pointers to decide which to use
+ */
+static unsigned int labpc_inb(struct comedi_device *dev, unsigned long reg)
+{
+	return inb(dev->iobase + reg);
+}
+
+static void labpc_outb(struct comedi_device *dev,
+		       unsigned int byte, unsigned long reg)
+{
+	outb(byte, dev->iobase + reg);
+}
+
+static unsigned int labpc_readb(struct comedi_device *dev, unsigned long reg)
+{
+	return readb(dev->mmio + reg);
+}
+
+static void labpc_writeb(struct comedi_device *dev,
+			 unsigned int byte, unsigned long reg)
+{
+	writeb(byte, dev->mmio + reg);
+}
+
+static int labpc_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+	struct labpc_private *devpriv = dev->private;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->spinlock, flags);
+	devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG);
+	devpriv->write_byte(dev, devpriv->cmd2, CMD2_REG);
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	devpriv->cmd3 = 0;
+	devpriv->write_byte(dev, devpriv->cmd3, CMD3_REG);
+
+	return 0;
+}
+
+static void labpc_ai_set_chan_and_gain(struct comedi_device *dev,
+				       enum scan_mode mode,
+				       unsigned int chan,
+				       unsigned int range,
+				       unsigned int aref)
+{
+	const struct labpc_boardinfo *board = dev->board_ptr;
+	struct labpc_private *devpriv = dev->private;
+
+	if (board->is_labpc1200) {
+		/*
+		 * The LabPC-1200 boards do not have a gain
+		 * of '0x10'. Skip the range values that would
+		 * result in this gain.
+		 */
+		range += (range > 0) + (range > 7);
+	}
+
+	/* munge channel bits for differential/scan disabled mode */
+	if ((mode == MODE_SINGLE_CHAN || mode == MODE_SINGLE_CHAN_INTERVAL) &&
+	    aref == AREF_DIFF)
+		chan *= 2;
+	devpriv->cmd1 = CMD1_MA(chan);
+	devpriv->cmd1 |= CMD1_GAIN(range);
+
+	devpriv->write_byte(dev, devpriv->cmd1, CMD1_REG);
+}
+
+static void labpc_setup_cmd6_reg(struct comedi_device *dev,
+				 struct comedi_subdevice *s,
+				 enum scan_mode mode,
+				 enum transfer_type xfer,
+				 unsigned int range,
+				 unsigned int aref,
+				 bool ena_intr)
+{
+	const struct labpc_boardinfo *board = dev->board_ptr;
+	struct labpc_private *devpriv = dev->private;
+
+	if (!board->is_labpc1200)
+		return;
+
+	/* reference inputs to ground or common? */
+	if (aref != AREF_GROUND)
+		devpriv->cmd6 |= CMD6_NRSE;
+	else
+		devpriv->cmd6 &= ~CMD6_NRSE;
+
+	/* bipolar or unipolar range? */
+	if (comedi_range_is_unipolar(s, range))
+		devpriv->cmd6 |= CMD6_ADCUNI;
+	else
+		devpriv->cmd6 &= ~CMD6_ADCUNI;
+
+	/*  interrupt on fifo half full? */
+	if (xfer == fifo_half_full_transfer)
+		devpriv->cmd6 |= CMD6_HFINTEN;
+	else
+		devpriv->cmd6 &= ~CMD6_HFINTEN;
+
+	/* enable interrupt on counter a1 terminal count? */
+	if (ena_intr)
+		devpriv->cmd6 |= CMD6_DQINTEN;
+	else
+		devpriv->cmd6 &= ~CMD6_DQINTEN;
+
+	/* are we scanning up or down through channels? */
+	if (mode == MODE_MULT_CHAN_UP)
+		devpriv->cmd6 |= CMD6_SCANUP;
+	else
+		devpriv->cmd6 &= ~CMD6_SCANUP;
+
+	devpriv->write_byte(dev, devpriv->cmd6, CMD6_REG);
+}
+
+static unsigned int labpc_read_adc_fifo(struct comedi_device *dev)
+{
+	struct labpc_private *devpriv = dev->private;
+	unsigned int lsb = devpriv->read_byte(dev, ADC_FIFO_REG);
+	unsigned int msb = devpriv->read_byte(dev, ADC_FIFO_REG);
+
+	return (msb << 8) | lsb;
+}
+
+static void labpc_clear_adc_fifo(struct comedi_device *dev)
+{
+	struct labpc_private *devpriv = dev->private;
+
+	devpriv->write_byte(dev, 0x1, ADC_FIFO_CLEAR_REG);
+	labpc_read_adc_fifo(dev);
+}
+
+static int labpc_ai_eoc(struct comedi_device *dev,
+			struct comedi_subdevice *s,
+			struct comedi_insn *insn,
+			unsigned long context)
+{
+	struct labpc_private *devpriv = dev->private;
+
+	devpriv->stat1 = devpriv->read_byte(dev, STAT1_REG);
+	if (devpriv->stat1 & STAT1_DAVAIL)
+		return 0;
+	return -EBUSY;
+}
+
+static int labpc_ai_insn_read(struct comedi_device *dev,
+			      struct comedi_subdevice *s,
+			      struct comedi_insn *insn,
+			      unsigned int *data)
+{
+	struct labpc_private *devpriv = dev->private;
+	unsigned int chan = CR_CHAN(insn->chanspec);
+	unsigned int range = CR_RANGE(insn->chanspec);
+	unsigned int aref = CR_AREF(insn->chanspec);
+	int ret;
+	int i;
+
+	/* disable timed conversions, interrupt generation and dma */
+	labpc_cancel(dev, s);
+
+	labpc_ai_set_chan_and_gain(dev, MODE_SINGLE_CHAN, chan, range, aref);
+
+	labpc_setup_cmd6_reg(dev, s, MODE_SINGLE_CHAN, fifo_not_empty_transfer,
+			     range, aref, false);
+
+	/* setup cmd4 register */
+	devpriv->cmd4 = 0;
+	devpriv->cmd4 |= CMD4_ECLKRCV;
+	/* single-ended/differential */
+	if (aref == AREF_DIFF)
+		devpriv->cmd4 |= CMD4_SEDIFF;
+	devpriv->write_byte(dev, devpriv->cmd4, CMD4_REG);
+
+	/* initialize pacer counter to prevent any problems */
+	comedi_8254_set_mode(devpriv->counter, 0, I8254_MODE2 | I8254_BINARY);
+
+	labpc_clear_adc_fifo(dev);
+
+	for (i = 0; i < insn->n; i++) {
+		/* trigger conversion */
+		devpriv->write_byte(dev, 0x1, ADC_START_CONVERT_REG);
+
+		ret = comedi_timeout(dev, s, insn, labpc_ai_eoc, 0);
+		if (ret)
+			return ret;
+
+		data[i] = labpc_read_adc_fifo(dev);
+	}
+
+	return insn->n;
+}
+
+static bool labpc_use_continuous_mode(const struct comedi_cmd *cmd,
+				      enum scan_mode mode)
+{
+	if (mode == MODE_SINGLE_CHAN || cmd->scan_begin_src == TRIG_FOLLOW)
+		return true;
+
+	return false;
+}
+
+static unsigned int labpc_ai_convert_period(const struct comedi_cmd *cmd,
+					    enum scan_mode mode)
+{
+	if (cmd->convert_src != TRIG_TIMER)
+		return 0;
+
+	if (mode == MODE_SINGLE_CHAN && cmd->scan_begin_src == TRIG_TIMER)
+		return cmd->scan_begin_arg;
+
+	return cmd->convert_arg;
+}
+
+static void labpc_set_ai_convert_period(struct comedi_cmd *cmd,
+					enum scan_mode mode, unsigned int ns)
+{
+	if (cmd->convert_src != TRIG_TIMER)
+		return;
+
+	if (mode == MODE_SINGLE_CHAN &&
+	    cmd->scan_begin_src == TRIG_TIMER) {
+		cmd->scan_begin_arg = ns;
+		if (cmd->convert_arg > cmd->scan_begin_arg)
+			cmd->convert_arg = cmd->scan_begin_arg;
+	} else {
+		cmd->convert_arg = ns;
+	}
+}
+
+static unsigned int labpc_ai_scan_period(const struct comedi_cmd *cmd,
+					 enum scan_mode mode)
+{
+	if (cmd->scan_begin_src != TRIG_TIMER)
+		return 0;
+
+	if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER)
+		return 0;
+
+	return cmd->scan_begin_arg;
+}
+
+static void labpc_set_ai_scan_period(struct comedi_cmd *cmd,
+				     enum scan_mode mode, unsigned int ns)
+{
+	if (cmd->scan_begin_src != TRIG_TIMER)
+		return;
+
+	if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER)
+		return;
+
+	cmd->scan_begin_arg = ns;
+}
+
+/* figures out what counter values to use based on command */
+static void labpc_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd,
+			     enum scan_mode mode)
+{
+	struct comedi_8254 *pacer = dev->pacer;
+	unsigned int convert_period = labpc_ai_convert_period(cmd, mode);
+	unsigned int scan_period = labpc_ai_scan_period(cmd, mode);
+	unsigned int base_period;
+
+	/*
+	 * If both convert and scan triggers are TRIG_TIMER, then they
+	 * both rely on counter b0. If only one TRIG_TIMER is used, we
+	 * can use the generic cascaded timing functions.
+	 */
+	if (convert_period && scan_period) {
+		/*
+		 * pick the lowest divisor value we can (for maximum input
+		 * clock speed on convert and scan counters)
+		 */
+		pacer->next_div1 = (scan_period - 1) /
+				   (pacer->osc_base * I8254_MAX_COUNT) + 1;
+
+		comedi_check_trigger_arg_min(&pacer->next_div1, 2);
+		comedi_check_trigger_arg_max(&pacer->next_div1,
+					     I8254_MAX_COUNT);
+
+		base_period = pacer->osc_base * pacer->next_div1;
+
+		/*  set a0 for conversion frequency and b1 for scan frequency */
+		switch (cmd->flags & CMDF_ROUND_MASK) {
+		default:
+		case CMDF_ROUND_NEAREST:
+			pacer->next_div = DIV_ROUND_CLOSEST(convert_period,
+							    base_period);
+			pacer->next_div2 = DIV_ROUND_CLOSEST(scan_period,
+							     base_period);
+			break;
+		case CMDF_ROUND_UP:
+			pacer->next_div = DIV_ROUND_UP(convert_period,
+						       base_period);
+			pacer->next_div2 = DIV_ROUND_UP(scan_period,
+							base_period);
+			break;
+		case CMDF_ROUND_DOWN:
+			pacer->next_div = convert_period / base_period;
+			pacer->next_div2 = scan_period / base_period;
+			break;
+		}
+		/*  make sure a0 and b1 values are acceptable */
+		comedi_check_trigger_arg_min(&pacer->next_div, 2);
+		comedi_check_trigger_arg_max(&pacer->next_div, I8254_MAX_COUNT);
+		comedi_check_trigger_arg_min(&pacer->next_div2, 2);
+		comedi_check_trigger_arg_max(&pacer->next_div2,
+					     I8254_MAX_COUNT);
+
+		/*  write corrected timings to command */
+		labpc_set_ai_convert_period(cmd, mode,
+					    base_period * pacer->next_div);
+		labpc_set_ai_scan_period(cmd, mode,
+					 base_period * pacer->next_div2);
+	} else if (scan_period) {
+		/*
+		 * calculate cascaded counter values
+		 * that give desired scan timing
+		 * (pacer->next_div2 / pacer->next_div1)
+		 */
+		comedi_8254_cascade_ns_to_timer(pacer, &scan_period,
+						cmd->flags);
+		labpc_set_ai_scan_period(cmd, mode, scan_period);
+	} else if (convert_period) {
+		/*
+		 * calculate cascaded counter values
+		 * that give desired conversion timing
+		 * (pacer->next_div / pacer->next_div1)
+		 */
+		comedi_8254_cascade_ns_to_timer(pacer, &convert_period,
+						cmd->flags);
+		/* transfer div2 value so correct timer gets updated */
+		pacer->next_div = pacer->next_div2;
+		labpc_set_ai_convert_period(cmd, mode, convert_period);
+	}
+}
+
+static enum scan_mode labpc_ai_scan_mode(const struct comedi_cmd *cmd)
+{
+	unsigned int chan0;
+	unsigned int chan1;
+
+	if (cmd->chanlist_len == 1)
+		return MODE_SINGLE_CHAN;
+
+	/* chanlist may be NULL during cmdtest */
+	if (!cmd->chanlist)
+		return MODE_MULT_CHAN_UP;
+
+	chan0 = CR_CHAN(cmd->chanlist[0]);
+	chan1 = CR_CHAN(cmd->chanlist[1]);
+
+	if (chan0 < chan1)
+		return MODE_MULT_CHAN_UP;
+
+	if (chan0 > chan1)
+		return MODE_MULT_CHAN_DOWN;
+
+	return MODE_SINGLE_CHAN_INTERVAL;
+}
+
+static int labpc_ai_check_chanlist(struct comedi_device *dev,
+				   struct comedi_subdevice *s,
+				   struct comedi_cmd *cmd)
+{
+	enum scan_mode mode = labpc_ai_scan_mode(cmd);
+	unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+	unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+	unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+	int i;
+
+	for (i = 0; i < cmd->chanlist_len; i++) {
+		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+		unsigned int range = CR_RANGE(cmd->chanlist[i]);
+		unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+		switch (mode) {
+		case MODE_SINGLE_CHAN:
+			break;
+		case MODE_SINGLE_CHAN_INTERVAL:
+			if (chan != chan0) {
+				dev_dbg(dev->class_dev,
+					"channel scanning order specified in chanlist is not supported by hardware\n");
+				return -EINVAL;
+			}
+			break;
+		case MODE_MULT_CHAN_UP:
+			if (chan != i) {
+				dev_dbg(dev->class_dev,
+					"channel scanning order specified in chanlist is not supported by hardware\n");
+				return -EINVAL;
+			}
+			break;
+		case MODE_MULT_CHAN_DOWN:
+			if (chan != (cmd->chanlist_len - i - 1)) {
+				dev_dbg(dev->class_dev,
+					"channel scanning order specified in chanlist is not supported by hardware\n");
+				return -EINVAL;
+			}
+			break;
+		}
+
+		if (range != range0) {
+			dev_dbg(dev->class_dev,
+				"entries in chanlist must all have the same range\n");
+			return -EINVAL;
+		}
+
+		if (aref != aref0) {
+			dev_dbg(dev->class_dev,
+				"entries in chanlist must all have the same reference\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int labpc_ai_cmdtest(struct comedi_device *dev,
+			    struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+	const struct labpc_boardinfo *board = dev->board_ptr;
+	int err = 0;
+	int tmp, tmp2;
+	unsigned int stop_mask;
+	enum scan_mode mode;
+
+	/* Step 1 : check if triggers are trivially valid */
+
+	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+	err |= comedi_check_trigger_src(&cmd->scan_begin_src,
+					TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT);
+	err |= comedi_check_trigger_src(&cmd->convert_src,
+					TRIG_TIMER | TRIG_EXT);
+	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+
+	stop_mask = TRIG_COUNT | TRIG_NONE;
+	if (board->is_labpc1200)
+		stop_mask |= TRIG_EXT;
+	err |= comedi_check_trigger_src(&cmd->stop_src, stop_mask);
+
+	if (err)
+		return 1;
+
+	/* Step 2a : make sure trigger sources are unique */
+
+	err |= comedi_check_trigger_is_unique(cmd->start_src);
+	err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
+	err |= comedi_check_trigger_is_unique(cmd->convert_src);
+	err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+	/* Step 2b : and mutually compatible */
+
+	/* can't have external stop and start triggers at once */
+	if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT)
+		err++;
+
+	if (err)
+		return 2;
+
+	/* Step 3: check if arguments are trivially valid */
+
+	switch (cmd->start_src) {
+	case TRIG_NOW:
+		err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+		break;
+	case TRIG_EXT:
+		/* start_arg value is ignored */
+		break;
+	}
+
+	if (!cmd->chanlist_len)
+		err |= -EINVAL;
+	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+					   cmd->chanlist_len);
+
+	if (cmd->convert_src == TRIG_TIMER) {
+		err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
+						    board->ai_speed);
+	}
+
+	/* make sure scan timing is not too fast */
+	if (cmd->scan_begin_src == TRIG_TIMER) {
+		if (cmd->convert_src == TRIG_TIMER) {
+			err |= comedi_check_trigger_arg_min(
+					&cmd->scan_begin_arg,
+					cmd->convert_arg * cmd->chanlist_len);
+		}
+		err |= comedi_check_trigger_arg_min(
+					&cmd->scan_begin_arg,
+					board->ai_speed * cmd->chanlist_len);
+	}
+
+	switch (cmd->stop_src) {
+	case TRIG_COUNT:
+		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+		break;
+	case TRIG_NONE:
+		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+		break;
+		/*
+		 * TRIG_EXT doesn't care since it doesn't
+		 * trigger off a numbered channel
+		 */
+	default:
+		break;
+	}
+
+	if (err)
+		return 3;
+
+	/* step 4: fix up any arguments */
+
+	tmp = cmd->convert_arg;
+	tmp2 = cmd->scan_begin_arg;
+	mode = labpc_ai_scan_mode(cmd);
+	labpc_adc_timing(dev, cmd, mode);
+	if (tmp != cmd->convert_arg || tmp2 != cmd->scan_begin_arg)
+		err++;
+
+	if (err)
+		return 4;
+
+	/* Step 5: check channel list if it exists */
+	if (cmd->chanlist && cmd->chanlist_len > 0)
+		err |= labpc_ai_check_chanlist(dev, s, cmd);
+
+	if (err)
+		return 5;
+
+	return 0;
+}
+
+static int labpc_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+	const struct labpc_boardinfo *board = dev->board_ptr;
+	struct labpc_private *devpriv = dev->private;
+	struct comedi_async *async = s->async;
+	struct comedi_cmd *cmd = &async->cmd;
+	enum scan_mode mode = labpc_ai_scan_mode(cmd);
+	unsigned int chanspec = (mode == MODE_MULT_CHAN_UP) ?
+				cmd->chanlist[cmd->chanlist_len - 1] :
+				cmd->chanlist[0];
+	unsigned int chan = CR_CHAN(chanspec);
+	unsigned int range = CR_RANGE(chanspec);
+	unsigned int aref = CR_AREF(chanspec);
+	enum transfer_type xfer;
+	unsigned long flags;
+
+	/* make sure board is disabled before setting up acquisition */
+	labpc_cancel(dev, s);
+
+	/*  initialize software conversion count */
+	if (cmd->stop_src == TRIG_COUNT)
+		devpriv->count = cmd->stop_arg * cmd->chanlist_len;
+
+	/*  setup hardware conversion counter */
+	if (cmd->stop_src == TRIG_EXT) {
+		/*
+		 * load counter a1 with count of 3
+		 * (pc+ manual says this is minimum allowed) using mode 0
+		 */
+		comedi_8254_load(devpriv->counter, 1,
+				 3, I8254_MODE0 | I8254_BINARY);
+	} else	{
+		/* just put counter a1 in mode 0 to set its output low */
+		comedi_8254_set_mode(devpriv->counter, 1,
+				     I8254_MODE0 | I8254_BINARY);
+	}
+
+	/* figure out what method we will use to transfer data */
+	if (devpriv->dma &&
+	    (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 000000000000..4f7e2fe21254
--- /dev/null
+++ b/drivers/comedi/drivers/ni_labpc_cs.c
@@ -0,0 +1,112 @@
+// 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 "../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 000000000000..a551aca6e615
--- /dev/null
+++ b/drivers/comedi/drivers/ni_labpc_isadma.c
@@ -0,0 +1,181 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "ni_labpc.h"
+#include "ni_labpc_regs.h"
+#include "ni_labpc_isadma.h"
+
+/* size in bytes of dma buffer */
+#define LABPC_ISADMA_BUFFER_SIZE	0xff00
+
+/* utility function that suggests a dma transfer size in bytes */
+static unsigned int labpc_suggest_transfer_size(struct comedi_device *dev,
+						struct comedi_subdevice *s,
+						unsigned int maxbytes)
+{
+	struct comedi_cmd *cmd = &s->async->cmd;
+	unsigned int sample_size = comedi_bytes_per_sample(s);
+	unsigned int size;
+	unsigned int freq;
+
+	if (cmd->convert_src == TRIG_TIMER)
+		freq = 1000000000 / cmd->convert_arg;
+	else
+		/* return some default value */
+		freq = 0xffffffff;
+
+	/* make buffer fill in no more than 1/3 second */
+	size = (freq / 3) * sample_size;
+
+	/* set a minimum and maximum size allowed */
+	if (size > maxbytes)
+		size = maxbytes;
+	else if (size < sample_size)
+		size = sample_size;
+
+	return size;
+}
+
+void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+	struct labpc_private *devpriv = dev->private;
+	struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
+	struct comedi_cmd *cmd = &s->async->cmd;
+	unsigned int sample_size = comedi_bytes_per_sample(s);
+
+	/* set appropriate size of transfer */
+	desc->size = labpc_suggest_transfer_size(dev, s, desc->maxsize);
+	if (cmd->stop_src == TRIG_COUNT &&
+	    devpriv->count * sample_size < desc->size)
+		desc->size = devpriv->count * sample_size;
+
+	comedi_isadma_program(desc);
+
+	/* set CMD3 bits for caller to enable DMA and interrupt */
+	devpriv->cmd3 |= (CMD3_DMAEN | CMD3_DMATCINTEN);
+}
+EXPORT_SYMBOL_GPL(labpc_setup_dma);
+
+void labpc_drain_dma(struct comedi_device *dev)
+{
+	struct labpc_private *devpriv = dev->private;
+	struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
+	struct comedi_subdevice *s = dev->read_subdev;
+	struct comedi_async *async = s->async;
+	struct comedi_cmd *cmd = &async->cmd;
+	unsigned int max_samples = comedi_bytes_to_samples(s, desc->size);
+	unsigned int residue;
+	unsigned int nsamples;
+	unsigned int leftover;
+
+	/*
+	 * residue is the number of bytes left to be done on the dma
+	 * transfer.  It should always be zero at this point unless
+	 * the stop_src is set to external triggering.
+	 */
+	residue = comedi_isadma_disable(desc->chan);
+
+	/*
+	 * Figure out how many samples to read for this transfer and
+	 * how many will be stored for next time.
+	 */
+	nsamples = max_samples - comedi_bytes_to_samples(s, residue);
+	if (cmd->stop_src == TRIG_COUNT) {
+		if (devpriv->count <= nsamples) {
+			nsamples = devpriv->count;
+			leftover = 0;
+		} else {
+			leftover = devpriv->count - nsamples;
+			if (leftover > max_samples)
+				leftover = max_samples;
+		}
+		devpriv->count -= nsamples;
+	} else {
+		leftover = max_samples;
+	}
+	desc->size = comedi_samples_to_bytes(s, leftover);
+
+	comedi_buf_write_samples(s, desc->virt_addr, nsamples);
+}
+EXPORT_SYMBOL_GPL(labpc_drain_dma);
+
+static void handle_isa_dma(struct comedi_device *dev)
+{
+	struct labpc_private *devpriv = dev->private;
+	struct comedi_isadma_desc *desc = &devpriv->dma->desc[0];
+
+	labpc_drain_dma(dev);
+
+	if (desc->size)
+		comedi_isadma_program(desc);
+
+	/* clear dma tc interrupt */
+	devpriv->write_byte(dev, 0x1, DMATC_CLEAR_REG);
+}
+
+void labpc_handle_dma_status(struct comedi_device *dev)
+{
+	const struct labpc_boardinfo *board = dev->board_ptr;
+	struct labpc_private *devpriv = dev->private;
+
+	/*
+	 * if a dma terminal count of external stop trigger
+	 * has occurred
+	 */
+	if (devpriv->stat1 & STAT1_GATA0 ||
+	    (board->is_labpc1200 && devpriv->stat2 & STAT2_OUTA1))
+		handle_isa_dma(dev);
+}
+EXPORT_SYMBOL_GPL(labpc_handle_dma_status);
+
+void labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan)
+{
+	struct labpc_private *devpriv = dev->private;
+
+	/* only DMA channels 3 and 1 are valid */
+	if (dma_chan != 1 && dma_chan != 3)
+		return;
+
+	/* DMA uses 1 buffer */
+	devpriv->dma = comedi_isadma_alloc(dev, 1, dma_chan, dma_chan,
+					   LABPC_ISADMA_BUFFER_SIZE,
+					   COMEDI_ISADMA_READ);
+}
+EXPORT_SYMBOL_GPL(labpc_init_dma_chan);
+
+void labpc_free_dma_chan(struct comedi_device *dev)
+{
+	struct labpc_private *devpriv = dev->private;
+
+	if (devpriv)
+		comedi_isadma_free(devpriv->dma);
+}
+EXPORT_SYMBOL_GPL(labpc_free_dma_chan);
+
+static int __init ni_labpc_isadma_init_module(void)
+{
+	return 0;
+}
+module_init(ni_labpc_isadma_init_module);
+
+static void __exit ni_labpc_isadma_cleanup_module(void)
+{
+}
+module_exit(ni_labpc_isadma_cleanup_module);
+
+MODULE_AUTHOR("Comedi 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 000000000000..f06f9353cb6c
--- /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 000000000000..ec180b0fedf7
--- /dev/null
+++ b/drivers/comedi/drivers/ni_labpc_pci.c
@@ -0,0 +1,132 @@
+// 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 "../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 000000000000..ace40065a25b
--- /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 000000000000..4f80a4991f95
--- /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 "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 000000000000..4f37b4e58f09
--- /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 "../comedi_pcmcia.h"
+#include "ni_stc.h"
+#include "8255.h"
+
+/*
+ *  AT specific setup
+ */
+
+static const struct ni_board_struct ni_boards[] = {
+	{
+		.name		= "DAQCard-ai-16xe-50",
+		.device_id	= 0x010d,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0xffff,
+		.ai_fifo_depth	= 1024,
+		.gainlkup	= ai_gain_8,
+		.ai_speed	= 5000,
+		.caldac		= { dac8800, dac8043 },
+	}, {
+		.name		= "DAQCard-ai-16e-4",
+		.device_id	= 0x010c,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 1024,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 4000,
+		.caldac		= { mb88341 },		/* verified */
+	}, {
+		.name		= "DAQCard-6062E",
+		.device_id	= 0x02c4,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 8192,
+		.gainlkup	= ai_gain_16,
+		.ai_speed	= 2000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_fifo_depth	= 2048,
+		.ao_range_table	= &range_bipolar10,
+		.ao_speed	= 1176,
+		.caldac		= { ad8804_debug },	/* verified */
+	 }, {
+		/* specs incorrect! */
+		.name		= "DAQCard-6024E",
+		.device_id	= 0x075e,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0x0fff,
+		.ai_fifo_depth	= 1024,
+		.gainlkup	= ai_gain_4,
+		.ai_speed	= 5000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0x0fff,
+		.ao_range_table	= &range_bipolar10,
+		.ao_speed	= 1000000,
+		.caldac		= { ad8804_debug },
+	}, {
+		/* specs incorrect! */
+		.name		= "DAQCard-6036E",
+		.device_id	= 0x0245,
+		.n_adchan	= 16,
+		.ai_maxdata	= 0xffff,
+		.ai_fifo_depth	= 1024,
+		.alwaysdither	= 1,
+		.gainlkup	= ai_gain_4,
+		.ai_speed	= 5000,
+		.n_aochan	= 2,
+		.ao_maxdata	= 0xffff,
+		.ao_range_table	= &range_bipolar10,
+		.ao_speed	= 1000000,
+		.caldac		= { ad8804_debug },
+	 },
+#if 0
+	{
+		.name		= "DAQCard-6715",
+		.device_id	= 0x0000,	/* unknown */
+		.n_aochan	= 8,
+		.ao_maxdata	= 0x0fff,
+		.ao_671x	= 8192,
+		.caldac		= { mb88341, mb88341 },
+	},
+#endif
+};
+
+#include "ni_mio_common.c"
+
+static const void *ni_getboardtype(struct comedi_device *dev,
+				   struct pcmcia_device *link)
+{
+	static const struct ni_board_struct *board;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ni_boards); i++) {
+		board = &ni_boards[i];
+		if (board->device_id == link->card_id)
+			return board;
+	}
+	return NULL;
+}
+
+static int mio_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data)
+{
+	int base, ret;
+
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
+
+	for (base = 0x000; base < 0x400; base += 0x20) {
+		p_dev->resource[0]->start = base;
+		ret = pcmcia_request_io(p_dev);
+		if (!ret)
+			return 0;
+	}
+	return -ENODEV;
+}
+
+static int mio_cs_auto_attach(struct comedi_device *dev,
+			      unsigned long context)
+{
+	struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+	static const struct ni_board_struct *board;
+	int ret;
+
+	board = ni_getboardtype(dev, link);
+	if (!board)
+		return -ENODEV;
+	dev->board_ptr = board;
+	dev->board_name = board->name;
+
+	link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+	ret = comedi_pcmcia_enable(dev, mio_pcmcia_config_loop);
+	if (ret)
+		return ret;
+	dev->iobase = link->resource[0]->start;
+
+	link->priv = dev;
+	ret = pcmcia_request_irq(link, ni_E_interrupt);
+	if (ret)
+		return ret;
+	dev->irq = link->irq;
+
+	ret = ni_alloc_private(dev);
+	if (ret)
+		return ret;
+
+	return ni_E_init(dev, 0, 1);
+}
+
+static void mio_cs_detach(struct comedi_device *dev)
+{
+	mio_common_detach(dev);
+	comedi_pcmcia_disable(dev);
+}
+
+static struct comedi_driver driver_ni_mio_cs = {
+	.driver_name	= "ni_mio_cs",
+	.module		= THIS_MODULE,
+	.auto_attach	= mio_cs_auto_attach,
+	.detach		= mio_cs_detach,
+};
+
+static int cs_attach(struct pcmcia_device *link)
+{
+	return comedi_pcmcia_auto_config(link, &driver_ni_mio_cs);
+}
+
+static const struct pcmcia_device_id ni_mio_cs_ids[] = {
+	PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010d),	/* DAQCard-ai-16xe-50 */
+	PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010c),	/* DAQCard-ai-16e-4 */
+	PCMCIA_DEVICE_MANF_CARD(0x010b, 0x02c4),	/* DAQCard-6062E */
+	PCMCIA_DEVICE_MANF_CARD(0x010b, 0x075e),	/* DAQCard-6024E */
+	PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0245),	/* DAQCard-6036E */
+	PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, ni_mio_cs_ids);
+
+static struct pcmcia_driver ni_mio_cs_driver = {
+	.name		= "ni_mio_cs",
+	.owner		= THIS_MODULE,
+	.id_table	= ni_mio_cs_ids,
+	.probe		= cs_attach,
+	.remove		= comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_ni_mio_cs, ni_mio_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for National Instruments DAQCard E series");
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/comedi/drivers/ni_pcidio.c b/drivers/comedi/drivers/ni_pcidio.c
new file mode 100644
index 000000000000..623f8d08d13a
--- /dev/null
+++ b/drivers/comedi/drivers/ni_pcidio.c
@@ -0,0 +1,1010 @@
+// 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 "../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 000000000000..6c813a490ba5
--- /dev/null
+++ b/drivers/comedi/drivers/ni_pcimio.c
@@ -0,0 +1,1477 @@
+// 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 "../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 000000000000..c426a9286f15
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routes.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 "../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 (memcmp(ni_all_route_values[i]->family, device_family,
+			   strnlen(device_family, 30)) == 0) {
+			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 (memcmp(ni_device_routes_list[i]->device, board_name,
+			   strnlen(board_name, 30)) == 0) {
+			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 000000000000..b7680fd2afe1
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routes.h
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 "../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 000000000000..b65c4ebedbc4
--- /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 000000000000..7b6a74dfe48b
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..b9f1c47d19e1
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..78b24138acb7
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/all.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..f1126a0cb285
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6070e.c
@@ -0,0 +1,639 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..74a59222963f
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6220.c
@@ -0,0 +1,1418 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..44dcbabf2a99
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6221.c
@@ -0,0 +1,1602 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..fa5794e4e2b3
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6229.c
@@ -0,0 +1,1602 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..645fd1cd2de4
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6251.c
@@ -0,0 +1,1652 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..056a240cd3a2
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6254.c
@@ -0,0 +1,1464 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..e0b5fa78c3bc
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6259.c
@@ -0,0 +1,1652 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..a2472ed288cf
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6534.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..91de9dac2d6a
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6602.c
@@ -0,0 +1,3378 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..d378b36d2084
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6713.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..e0cc57ab06e7
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6723.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..f6e1e17ab854
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pci-6733.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..9978d632117f
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6030e.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..1b89e27d7aa5
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6224.c
@@ -0,0 +1,1432 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..10dfc34bc87c
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6225.c
@@ -0,0 +1,1613 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..25db4b7363de
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6251.c
@@ -0,0 +1,1655 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..27da4433fc4a
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxi-6733.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..8354fe971d59
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6251.c
@@ -0,0 +1,1656 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..2ebb679e0129
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6535.c
@@ -0,0 +1,575 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..d88504314d7f
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_device_routes/pxie-6738.c
@@ -0,0 +1,3083 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..5901762734ed
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_route_values.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..80e0145fb82b
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_route_values.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 "../../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 000000000000..7227461500b5
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_route_values/all.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..f1c7e6646261
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_660x.c
@@ -0,0 +1,650 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..d1ab3c9ce585
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_eseries.c
@@ -0,0 +1,602 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..c59d8afe0ae9
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/ni_route_values/ni_mseries.c
@@ -0,0 +1,1752 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..e3ebffcd900e
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/.gitignore
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+comedi_h.py
+*.pyc
+ni_values.py
+convert_c_to_py
+c/
+csv/
+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 000000000000..6e92a06a44cb
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/Makefile
@@ -0,0 +1,80 @@
+# 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 ../../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"BIT(x)=(1UL<<(x))" -D__user=
+
+comedi_h.py : ../../../comedi.h
+	ctypesgen $< --include "sys/ioctl.h" --cpp 'gcc -E $(CPPFLAGS)' -o $@
+
+convert_c_to_py: all_cfiles.c
+	gcc -g convert_c_to_py.c -o convert_c_to_py -std=c99
+
+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 :
+	./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 : partial_clean
+	$(RM) -rf c/ csv/
+
+# 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 : ../../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}/../../comedi.h -c -D__user="" -D"BIT(x)=(1<<(x))" \
+# 		-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 000000000000..dedb6f2fc678
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/convert_c_to_py.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+
+#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 000000000000..532eb6372a5a
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/convert_csv_to_c.py
@@ -0,0 +1,503 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+# vim: ts=2:sw=2:et:tw=80:nowrap
+
+# 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+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..b3e6472bac22
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/convert_py_to_csv.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+# vim: ts=2:sw=2:et:tw=80:nowrap
+
+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 000000000000..12617329a928
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/csv_collection.py
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0+
+# vim: ts=2:sw=2:et:tw=80:nowrap
+
+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 000000000000..89c90a0ba24d
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/make_blank_csv.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+# vim: ts=2:sw=2:et:tw=80:nowrap
+
+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 000000000000..5f9b825968b1
--- /dev/null
+++ b/drivers/comedi/drivers/ni_routing/tools/ni_names.py
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0+
+# vim: ts=2:sw=2:et:tw=80:nowrap
+"""
+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 000000000000..fbc0b753a0f5
--- /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 000000000000..f6154addaa95
--- /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, &reg);
+	} else if (dest >= NI_CtrGate(0) && dest <= NI_CtrGate(-1)) {
+		ret = ni_tio_get_gate_src_raw(counter, 0, &reg);
+	} else if (dest >= NI_CtrAux(0) && dest <= NI_CtrAux(-1)) {
+		ret = ni_tio_get_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);
+	 */
+	}
+
+	if (ret)
+		return -EINVAL;
+
+	return reg;
+}
+EXPORT_SYMBOL_GPL(ni_tio_get_routing);
+
+/**
+ * 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 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 000000000000..e7b05718df9b
--- /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 "../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 000000000000..20fcd60038cd
--- /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 000000000000..ab6d9e8269f3
--- /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 000000000000..5b6d9d783b2f
--- /dev/null
+++ b/drivers/comedi/drivers/ni_usb6501.c
@@ -0,0 +1,602 @@
+// 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 "../comedi_usb.h"
+
+#define	NI6501_TIMEOUT	1000
+
+/* Port request packets */
+static const u8 READ_PORT_REQUEST[]	= {0x00, 0x01, 0x00, 0x10,
+					   0x00, 0x0C, 0x01, 0x0E,
+					   0x02, 0x10, 0x00, 0x00,
+					   0x00, 0x03, 0x00, 0x00};
+
+static const u8 WRITE_PORT_REQUEST[]	= {0x00, 0x01, 0x00, 0x14,
+					   0x00, 0x10, 0x01, 0x0F,
+					   0x02, 0x10, 0x00, 0x00,
+					   0x00, 0x03, 0x00, 0x00,
+					   0x03, 0x00, 0x00, 0x00};
+
+static const u8 SET_PORT_DIR_REQUEST[]	= {0x00, 0x01, 0x00, 0x18,
+					   0x00, 0x14, 0x01, 0x12,
+					   0x02, 0x10, 0x00, 0x00,
+					   0x00, 0x05, 0x00, 0x00,
+					   0x00, 0x00, 0x05, 0x00,
+					   0x00, 0x00, 0x00, 0x00};
+
+/* Counter request packets */
+static const u8 START_COUNTER_REQUEST[]	= {0x00, 0x01, 0x00, 0x0C,
+					   0x00, 0x08, 0x01, 0x09,
+					   0x02, 0x20, 0x00, 0x00};
+
+static const u8 STOP_COUNTER_REQUEST[]	= {0x00, 0x01, 0x00, 0x0C,
+					   0x00, 0x08, 0x01, 0x0C,
+					   0x02, 0x20, 0x00, 0x00};
+
+static const u8 READ_COUNTER_REQUEST[]	= {0x00, 0x01, 0x00, 0x0C,
+					   0x00, 0x08, 0x01, 0x0E,
+					   0x02, 0x20, 0x00, 0x00};
+
+static const u8 WRITE_COUNTER_REQUEST[]	= {0x00, 0x01, 0x00, 0x10,
+					   0x00, 0x0C, 0x01, 0x0F,
+					   0x02, 0x20, 0x00, 0x00,
+					   0x00, 0x00, 0x00, 0x00};
+
+/* Response packets */
+static const u8 GENERIC_RESPONSE[]	= {0x00, 0x01, 0x00, 0x0C,
+					   0x00, 0x08, 0x01, 0x00,
+					   0x00, 0x00, 0x00, 0x02};
+
+static const u8 READ_PORT_RESPONSE[]	= {0x00, 0x01, 0x00, 0x10,
+					   0x00, 0x0C, 0x01, 0x00,
+					   0x00, 0x00, 0x00, 0x02,
+					   0x00, 0x03, 0x00, 0x00};
+
+static const u8 READ_COUNTER_RESPONSE[]	= {0x00, 0x01, 0x00, 0x10,
+					   0x00, 0x0C, 0x01, 0x00,
+					   0x00, 0x00, 0x00, 0x02,
+					   0x00, 0x00, 0x00, 0x00};
+
+enum commands {
+	READ_PORT,
+	WRITE_PORT,
+	SET_PORT_DIR,
+	START_COUNTER,
+	STOP_COUNTER,
+	READ_COUNTER,
+	WRITE_COUNTER
+};
+
+struct ni6501_private {
+	struct usb_endpoint_descriptor *ep_rx;
+	struct usb_endpoint_descriptor *ep_tx;
+	struct 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;
+
+	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 000000000000..bd6f42fe9e3c
--- /dev/null
+++ b/drivers/comedi/drivers/pcl711.c
@@ -0,0 +1,513 @@
+// 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 "../comedidev.h"
+
+#include "comedi_8254.h"
+
+/*
+ * I/O port register map
+ */
+#define PCL711_TIMER_BASE	0x00
+#define PCL711_AI_LSB_REG	0x04
+#define PCL711_AI_MSB_REG	0x05
+#define PCL711_AI_MSB_DRDY	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 000000000000..1a5799278a7a
--- /dev/null
+++ b/drivers/comedi/drivers/pcl724.c
@@ -0,0 +1,153 @@
+// 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 "../comedidev.h"
+
+#include "8255.h"
+
+struct pcl724_board {
+	const char *name;
+	unsigned int io_range;
+	unsigned int can_have96:1;
+	unsigned int is_pet48:1;
+	int numofports;
+};
+
+static const struct pcl724_board boardtypes[] = {
+	{
+		.name		= "pcl724",
+		.io_range	= 0x04,
+		.numofports	= 1,	/* 24 DIO channels */
+	}, {
+		.name		= "pcl722",
+		.io_range	= 0x20,
+		.can_have96	= 1,
+		.numofports	= 6,	/* 144 (or 96) DIO channels */
+	}, {
+		.name		= "pcl731",
+		.io_range	= 0x08,
+		.numofports	= 2,	/* 48 DIO channels */
+	}, {
+		.name		= "acl7122",
+		.io_range	= 0x20,
+		.can_have96	= 1,
+		.numofports	= 6,	/* 144 (or 96) DIO channels */
+	}, {
+		.name		= "acl7124",
+		.io_range	= 0x04,
+		.numofports	= 1,	/* 24 DIO channels */
+	}, {
+		.name		= "pet48dio",
+		.io_range	= 0x02,
+		.is_pet48	= 1,
+		.numofports	= 2,	/* 48 DIO channels */
+	}, {
+		.name		= "pcmio48",
+		.io_range	= 0x08,
+		.numofports	= 2,	/* 48 DIO channels */
+	}, {
+		.name		= "onyx-mm-dio",
+		.io_range	= 0x10,
+		.numofports	= 2,	/* 48 DIO channels */
+	},
+};
+
+static int pcl724_8255mapped_io(struct comedi_device *dev,
+				int dir, int port, int data,
+				unsigned long iobase)
+{
+	int movport = I8255_SIZE * (iobase >> 12);
+
+	iobase &= 0x0fff;
+
+	outb(port + movport, iobase);
+	if (dir) {
+		outb(data, iobase + 1);
+		return 0;
+	}
+	return inb(iobase + 1);
+}
+
+static int pcl724_attach(struct comedi_device *dev,
+			 struct comedi_devconfig *it)
+{
+	const struct pcl724_board *board = dev->board_ptr;
+	struct comedi_subdevice *s;
+	unsigned long iobase;
+	unsigned int iorange;
+	int n_subdevices;
+	int ret;
+	int i;
+
+	iorange = board->io_range;
+	n_subdevices = board->numofports;
+
+	/* Handle PCL-724 in 96 DIO configuration */
+	if (board->can_have96 &&
+	    (it->options[2] == 1 || it->options[2] == 96)) {
+		iorange = 0x10;
+		n_subdevices = 4;
+	}
+
+	ret = comedi_request_region(dev, it->options[0], iorange);
+	if (ret)
+		return ret;
+
+	ret = comedi_alloc_subdevices(dev, n_subdevices);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < dev->n_subdevices; i++) {
+		s = &dev->subdevices[i];
+		if (board->is_pet48) {
+			iobase = dev->iobase + (i * 0x1000);
+			ret = subdev_8255_init(dev, s, pcl724_8255mapped_io,
+					       iobase);
+		} else {
+			ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE);
+		}
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static struct comedi_driver pcl724_driver = {
+	.driver_name	= "pcl724",
+	.module		= THIS_MODULE,
+	.attach		= pcl724_attach,
+	.detach		= comedi_legacy_detach,
+	.board_name	= &boardtypes[0].name,
+	.num_names	= ARRAY_SIZE(boardtypes),
+	.offset		= sizeof(struct pcl724_board),
+};
+module_comedi_driver(pcl724_driver);
+
+MODULE_AUTHOR("Comedi 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 000000000000..88f25d7e76f7
--- /dev/null
+++ b/drivers/comedi/drivers/pcl726.c
@@ -0,0 +1,425 @@
+// 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 "../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 000000000000..32a29129e6e8
--- /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 "../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 000000000000..b87ab3840eee
--- /dev/null
+++ b/drivers/comedi/drivers/pcl812.c
@@ -0,0 +1,1336 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.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 000000000000..c368a337a0ae
--- /dev/null
+++ b/drivers/comedi/drivers/pcl816.c
@@ -0,0 +1,696 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.h"
+
+/*
+ * Register I/O map
+ */
+#define PCL816_DO_DI_LSB_REG			0x00
+#define PCL816_DO_DI_MSB_REG			0x01
+#define PCL816_TIMER_BASE			0x04
+#define PCL816_AI_LSB_REG			0x08
+#define PCL816_AI_MSB_REG			0x09
+#define PCL816_RANGE_REG			0x09
+#define PCL816_CLRINT_REG			0x0a
+#define PCL816_MUX_REG				0x0b
+#define PCL816_MUX_SCAN(_first, _last)		(((_last) << 4) | (_first))
+#define PCL816_CTRL_REG				0x0c
+#define PCL816_CTRL_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 000000000000..f4b4a686c710
--- /dev/null
+++ b/drivers/comedi/drivers/pcl818.c
@@ -0,0 +1,1137 @@
+// 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 "../comedidev.h"
+
+#include "comedi_isadma.h"
+#include "comedi_8254.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 000000000000..0cb1ad060402
--- /dev/null
+++ b/drivers/comedi/drivers/pcm3724.c
@@ -0,0 +1,227 @@
+// 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 "../comedidev.h"
+
+#include "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;
+	buffer_config = 0;
+
+	/* 1 in io_bits indicates output, 1 in config indicates input */
+	if (!(s->io_bits & 0x0000ff))
+		config |= I8255_CTRL_A_IO;
+
+	if (!(s->io_bits & 0x00ff00))
+		config |= I8255_CTRL_B_IO;
+
+	if (!(s->io_bits & 0xff0000))
+		config |= I8255_CTRL_C_HI_IO | I8255_CTRL_C_LO_IO;
+
+	buffer_config = compute_buffer(0, 0, s_dio1);
+	buffer_config = compute_buffer(buffer_config, 1, s_dio2);
+
+	if (s == s_dio1)
+		port_8255_cfg = dev->iobase + I8255_CTRL_REG;
+	else
+		port_8255_cfg = dev->iobase + I8255_SIZE + I8255_CTRL_REG;
+
+	outb(buffer_config, dev->iobase + 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 000000000000..eec89a0afb2f
--- /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 "../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 000000000000..14ab1f0d1e9f
--- /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 "../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 000000000000..24a9568d3378
--- /dev/null
+++ b/drivers/comedi/drivers/pcmmio.c
@@ -0,0 +1,777 @@
+// 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 "../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 000000000000..b299d648a0eb
--- /dev/null
+++ b/drivers/comedi/drivers/pcmuio.c
@@ -0,0 +1,624 @@
+// 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 "../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 000000000000..e68a7afef025
--- /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 000000000000..aa0eda5a8093
--- /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 000000000000..fe4408ebf6b3
--- /dev/null
+++ b/drivers/comedi/drivers/quatech_daqp_cs.c
@@ -0,0 +1,842 @@
+// 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 "../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 000000000000..2d99a648b054
--- /dev/null
+++ b/drivers/comedi/drivers/rtd520.c
@@ -0,0 +1,1365 @@
+// 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 "../comedi_pci.h"
+
+#include "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 000000000000..327fd93b8b12
--- /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 "../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 000000000000..195e2b1ac4c1
--- /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 "../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 000000000000..085cf5b449e5
--- /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 "../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 000000000000..e7aba937d896
--- /dev/null
+++ b/drivers/comedi/drivers/s626.c
@@ -0,0 +1,2605 @@
+// 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 "../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 000000000000..749252b1d26b
--- /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 000000000000..016d315aa584
--- /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 "../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 000000000000..5ff7cdc32a32
--- /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 000000000000..e5aaaeab7bdd
--- /dev/null
+++ b/drivers/comedi/drivers/tests/comedi_example_test.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..32073850d545
--- /dev/null
+++ b/drivers/comedi/drivers/tests/ni_routes_test.c
@@ -0,0 +1,611 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..2da3beea2479
--- /dev/null
+++ b/drivers/comedi/drivers/tests/unittest.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* vim: set ts=8 sw=8 noet tw=80 nowrap: */
+/*
+ *  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 000000000000..0350f303d557
--- /dev/null
+++ b/drivers/comedi/drivers/usbdux.c
@@ -0,0 +1,1729 @@
+// 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 "../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 000000000000..4af012968cb6
--- /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 "../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 000000000000..54d7605e909f
--- /dev/null
+++ b/drivers/comedi/drivers/usbduxsigma.c
@@ -0,0 +1,1616 @@
+// 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 "../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 000000000000..9f920819cd74
--- /dev/null
+++ b/drivers/comedi/drivers/vmk80xx.c
@@ -0,0 +1,880 @@
+// 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 "../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)
+
+enum vmk80xx_model {
+	VMK8055_MODEL,
+	VMK8061_MODEL
+};
+
+static const struct comedi_lrange vmk8061_range = {
+	2, {
+		UNI_RANGE(5),
+		UNI_RANGE(10)
+	}
+};
+
+struct vmk80xx_board {
+	const char *name;
+	enum vmk80xx_model model;
+	const struct comedi_lrange *range;
+	int ai_nchans;
+	unsigned int ai_maxdata;
+	int ao_nchans;
+	int di_nchans;
+	unsigned int cnt_maxdata;
+	int pwm_nchans;
+	unsigned int pwm_maxdata;
+};
+
+static const struct vmk80xx_board vmk80xx_boardinfo[] = {
+	[DEVICE_VMK8055] = {
+		.name		= "K8055 (VM110)",
+		.model		= VMK8055_MODEL,
+		.range		= &range_unipolar5,
+		.ai_nchans	= 2,
+		.ai_maxdata	= 0x00ff,
+		.ao_nchans	= 2,
+		.di_nchans	= 6,
+		.cnt_maxdata	= 0xffff,
+	},
+	[DEVICE_VMK8061] = {
+		.name		= "K8061 (VM140)",
+		.model		= VMK8061_MODEL,
+		.range		= &vmk8061_range,
+		.ai_nchans	= 8,
+		.ai_maxdata	= 0x03ff,
+		.ao_nchans	= 8,
+		.di_nchans	= 8,
+		.cnt_maxdata	= 0,	/* unknown, device is not writeable */
+		.pwm_nchans	= 1,
+		.pwm_maxdata	= 0x03ff,
+	},
+};
+
+struct vmk80xx_private {
+	struct usb_endpoint_descriptor *ep_rx;
+	struct usb_endpoint_descriptor *ep_tx;
+	struct semaphore limit_sem;
+	unsigned char *usb_rx_buf;
+	unsigned char *usb_tx_buf;
+	enum vmk80xx_model model;
+};
+
+static void vmk80xx_do_bulk_msg(struct comedi_device *dev)
+{
+	struct vmk80xx_private *devpriv = dev->private;
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	__u8 tx_addr;
+	__u8 rx_addr;
+	unsigned int tx_pipe;
+	unsigned int rx_pipe;
+	size_t size;
+
+	tx_addr = devpriv->ep_tx->bEndpointAddress;
+	rx_addr = devpriv->ep_rx->bEndpointAddress;
+	tx_pipe = usb_sndbulkpipe(usb, tx_addr);
+	rx_pipe = usb_rcvbulkpipe(usb, rx_addr);
+
+	/*
+	 * The max packet size attributes of the K8061
+	 * input/output endpoints are identical
+	 */
+	size = usb_endpoint_maxp(devpriv->ep_tx);
+
+	usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf,
+		     size, NULL, devpriv->ep_tx->bInterval);
+	usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10);
+}
+
+static int vmk80xx_read_packet(struct comedi_device *dev)
+{
+	struct vmk80xx_private *devpriv = dev->private;
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	struct usb_endpoint_descriptor *ep;
+	unsigned int pipe;
+
+	if (devpriv->model == VMK8061_MODEL) {
+		vmk80xx_do_bulk_msg(dev);
+		return 0;
+	}
+
+	ep = devpriv->ep_rx;
+	pipe = usb_rcvintpipe(usb, ep->bEndpointAddress);
+	return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf,
+				 usb_endpoint_maxp(ep), NULL,
+				 HZ * 10);
+}
+
+static int vmk80xx_write_packet(struct comedi_device *dev, int cmd)
+{
+	struct vmk80xx_private *devpriv = dev->private;
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	struct usb_endpoint_descriptor *ep;
+	unsigned int pipe;
+
+	devpriv->usb_tx_buf[0] = cmd;
+
+	if (devpriv->model == VMK8061_MODEL) {
+		vmk80xx_do_bulk_msg(dev);
+		return 0;
+	}
+
+	ep = devpriv->ep_tx;
+	pipe = usb_sndintpipe(usb, ep->bEndpointAddress);
+	return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf,
+				 usb_endpoint_maxp(ep), NULL,
+				 HZ * 10);
+}
+
+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 = 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 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 000000000000..3ef5f9e79b89
--- /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 000000000000..8031142a105f
--- /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 000000000000..df9bba1b69ed
--- /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 "../comedi.h"
+#include "../comedilib.h"
+#include "../comedidev.h"
+
+MODULE_AUTHOR("David Schleef <ds@schleef.org>");
+MODULE_DESCRIPTION("Comedi kernel library");
+MODULE_LICENSE("GPL");
+
+struct comedi_device *comedi_open(const char *filename)
+{
+	struct comedi_device *dev, *retval = NULL;
+	unsigned int minor;
+
+	if (strncmp(filename, "/dev/comedi", 11) != 0)
+		return NULL;
+
+	if (kstrtouint(filename + 11, 0, &minor))
+		return NULL;
+
+	if (minor >= COMEDI_NUM_BOARD_MINORS)
+		return NULL;
+
+	dev = comedi_dev_get_from_minor(minor);
+	if (!dev)
+		return NULL;
+
+	down_read(&dev->attach_lock);
+	if (dev->attached)
+		retval = dev;
+	else
+		retval = NULL;
+	up_read(&dev->attach_lock);
+
+	if (!retval)
+		comedi_dev_put(dev);
+
+	return retval;
+}
+EXPORT_SYMBOL_GPL(comedi_open);
+
+int comedi_close(struct comedi_device *dev)
+{
+	comedi_dev_put(dev);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_close);
+
+static int comedi_do_insn(struct comedi_device *dev,
+			  struct comedi_insn *insn,
+			  unsigned int *data)
+{
+	struct comedi_subdevice *s;
+	int ret;
+
+	mutex_lock(&dev->mutex);
+
+	if (!dev->attached) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* a subdevice instruction */
+	if (insn->subdev >= dev->n_subdevices) {
+		ret = -EINVAL;
+		goto error;
+	}
+	s = &dev->subdevices[insn->subdev];
+
+	if (s->type == COMEDI_SUBD_UNUSED) {
+		dev_err(dev->class_dev,
+			"%d not usable subdevice\n", insn->subdev);
+		ret = -EIO;
+		goto error;
+	}
+
+	/* XXX check lock */
+
+	ret = comedi_check_chanlist(s, 1, &insn->chanspec);
+	if (ret < 0) {
+		dev_err(dev->class_dev, "bad chanspec\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (s->busy) {
+		ret = -EBUSY;
+		goto error;
+	}
+	s->busy = dev;
+
+	switch (insn->insn) {
+	case INSN_BITS:
+		ret = s->insn_bits(dev, s, insn, data);
+		break;
+	case INSN_CONFIG:
+		/* XXX should check instruction length */
+		ret = s->insn_config(dev, s, insn, data);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	s->busy = NULL;
+error:
+
+	mutex_unlock(&dev->mutex);
+	return ret;
+}
+
+int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
+			  unsigned int chan, unsigned int *io)
+{
+	struct comedi_insn insn;
+	unsigned int data[2];
+	int ret;
+
+	memset(&insn, 0, sizeof(insn));
+	insn.insn = INSN_CONFIG;
+	insn.n = 2;
+	insn.subdev = subdev;
+	insn.chanspec = CR_PACK(chan, 0, 0);
+	data[0] = INSN_CONFIG_DIO_QUERY;
+	data[1] = 0;
+	ret = comedi_do_insn(dev, &insn, data);
+	if (ret >= 0)
+		*io = data[1];
+	return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_get_config);
+
+int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
+		      unsigned int chan, unsigned int io)
+{
+	struct comedi_insn insn;
+
+	memset(&insn, 0, sizeof(insn));
+	insn.insn = INSN_CONFIG;
+	insn.n = 1;
+	insn.subdev = subdev;
+	insn.chanspec = CR_PACK(chan, 0, 0);
+
+	return comedi_do_insn(dev, &insn, &io);
+}
+EXPORT_SYMBOL_GPL(comedi_dio_config);
+
+int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
+			 unsigned int mask, unsigned int *bits,
+			 unsigned int base_channel)
+{
+	struct comedi_insn insn;
+	unsigned int data[2];
+	unsigned int n_chan;
+	unsigned int shift;
+	int ret;
+
+	base_channel = CR_CHAN(base_channel);
+	n_chan = comedi_get_n_channels(dev, subdev);
+	if (base_channel >= n_chan)
+		return -EINVAL;
+
+	memset(&insn, 0, sizeof(insn));
+	insn.insn = INSN_BITS;
+	insn.chanspec = base_channel;
+	insn.n = 2;
+	insn.subdev = subdev;
+
+	data[0] = mask;
+	data[1] = *bits;
+
+	/*
+	 * Most drivers ignore the base channel in insn->chanspec.
+	 * Fix this here if the subdevice has <= 32 channels.
+	 */
+	if (n_chan <= 32) {
+		shift = base_channel;
+		if (shift) {
+			insn.chanspec = 0;
+			data[0] <<= shift;
+			data[1] <<= shift;
+		}
+	} else {
+		shift = 0;
+	}
+
+	ret = comedi_do_insn(dev, &insn, data);
+	*bits = data[1] >> shift;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_bitfield2);
+
+int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
+				  unsigned int subd)
+{
+	struct comedi_subdevice *s;
+	int ret = -ENODEV;
+
+	down_read(&dev->attach_lock);
+	if (dev->attached)
+		for (; subd < dev->n_subdevices; subd++) {
+			s = &dev->subdevices[subd];
+			if (s->type == type) {
+				ret = subd;
+				break;
+			}
+		}
+	up_read(&dev->attach_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);
+
+int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice)
+{
+	int n;
+
+	down_read(&dev->attach_lock);
+	if (!dev->attached || subdevice >= dev->n_subdevices)
+		n = 0;
+	else
+		n = dev->subdevices[subdevice].n_chan;
+	up_read(&dev->attach_lock);
+
+	return n;
+}
+EXPORT_SYMBOL_GPL(comedi_get_n_channels);
+
+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 000000000000..8bc8e42beb90
--- /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 "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 000000000000..a4e6fe0fb729
--- /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 "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);