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);
+	i