// SPDX-License-Identifier: GPL-2.0+ /* * gsc_hpdi.c * Comedi driver the General Standards Corporation * High Speed Parallel Digital Interface rs485 boards. * * Author: Frank Mori Hess * Copyright (C) 2003 Coherent Imaging Systems * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 1997-8 David A. Schleef */ /* * Driver: gsc_hpdi * Description: General Standards Corporation High * Speed Parallel Digital Interface rs485 boards * Author: Frank Mori Hess * Status: only receive mode works, transmit not supported * Updated: Thu, 01 Nov 2012 16:17:38 +0000 * Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi), * PMC-HPDI32 * * Configuration options: * None. * * Manual configuration of supported devices is not supported; they are * configured automatically. * * There are some additional hpdi models available from GSC for which * support could be added to this driver. */ #include #include #include #include "../comedi_pci.h" #include "plx9080.h" /* * PCI BAR2 Register map (dev->mmio) */ #define FIRMWARE_REV_REG 0x00 #define FEATURES_REG_PRESENT_BIT BIT(15) #define BOARD_CONTROL_REG 0x04 #define BOARD_RESET_BIT BIT(0) #define TX_FIFO_RESET_BIT BIT(1) #define RX_FIFO_RESET_BIT BIT(2) #define TX_ENABLE_BIT BIT(4) #define RX_ENABLE_BIT BIT(5) #define DEMAND_DMA_DIRECTION_TX_BIT BIT(6) /* ch 0 only */ #define LINE_VALID_ON_STATUS_VALID_BIT BIT(7) #define START_TX_BIT BIT(8) #define CABLE_THROTTLE_ENABLE_BIT BIT(9) #define TEST_MODE_ENABLE_BIT BIT(31) #define BOARD_STATUS_REG 0x08 #define COMMAND_LINE_STATUS_MASK (0x7f << 0) #define TX_IN_PROGRESS_BIT BIT(7) #define TX_NOT_EMPTY_BIT BIT(8) #define TX_NOT_ALMOST_EMPTY_BIT BIT(9) #define TX_NOT_ALMOST_FULL_BIT BIT(10) #define TX_NOT_FULL_BIT BIT(11) #define RX_NOT_EMPTY_BIT BIT(12) #define RX_NOT_ALMOST_EMPTY_BIT BIT(13) #define RX_NOT_ALMOST_FULL_BIT BIT(14) #define RX_NOT_FULL_BIT BIT(15) #define BOARD_JUMPER0_INSTALLED_BIT BIT(16) #define BOARD_JUMPER1_INSTALLED_BIT BIT(17) #define TX_OVERRUN_BIT BIT(21) #define RX_UNDERRUN_BIT BIT(22) #define RX_OVERRUN_BIT BIT(23) #define TX_PROG_ALMOST_REG 0x0c #define RX_PROG_ALMOST_REG 0x10 #define ALMOST_EMPTY_BITS(x) (((x) & 0xffff) << 0) #define ALMOST_FULL_BITS(x) (((x) & 0xff) << 16) #define FEATURES_REG 0x14 #define FIFO_SIZE_PRESENT_BIT BIT(0) #define FIFO_WORDS_PRESENT_BIT BIT(1) #define LEVEL_EDGE_INTERRUPTS_PRESENT_BIT BIT(2) #define GPIO_SUPPORTED_BIT BIT(3) #define PLX_DMA_CH1_SUPPORTED_BIT BIT(4) #define OVERRUN_UNDERRUN_SUPPORTED_BIT BIT(5) #define FIFO_REG 0x18 #define TX_STATUS_COUNT_REG 0x1c #define TX_LINE_VALID_COUNT_REG 0x20, #define TX_LINE_INVALID_COUNT_REG 0x24 #define RX_STATUS_COUNT_REG 0x28 #define RX_LINE_COUNT_REG 0x2c #define INTERRUPT_CONTROL_REG 0x30 #define FRAME_VALID_START_INTR BIT(0) #define FRAME_VALID_END_INTR BIT(1) #define TX_FIFO_EMPTY_INTR BIT(8) #define TX_FIFO_ALMOST_EMPTY_INTR BIT(9) #define TX_FIFO_ALMOST_FULL_INTR BIT(10) #define TX_FIFO_FULL_INTR BIT(11) #define RX_EMPTY_INTR BIT(12) #define RX_ALMOST_EMPTY_INTR BIT(13) #define RX_ALMOST_FULL_INTR BIT(14) #define RX_FULL_INTR BIT(15) #define INTERRUPT_STATUS_REG 0x34 #define TX_CLOCK_DIVIDER_REG 0x38 #define TX_FIFO_SIZE_REG 0x40 #define RX_FIFO_SIZE_REG 0x44 #define FIFO_SIZE_MASK (0xfffff << 0) #define TX_FIFO_WORDS_REG 0x48 #define RX_FIFO_WORDS_REG 0x4c #define INTERRUPT_EDGE_LEVEL_REG 0x50 #define INTERRUPT_POLARITY_REG 0x54 #define TIMER_BASE 50 /* 20MHz master clock */ #define DMA_BUFFER_SIZE 0x10000 #define NUM_DMA_BUFFERS 4 #define NUM_DMA_DESCRIPTORS 256 struct hpdi_private { void __iomem *plx9080_mmio; u32 *dio_buffer[NUM_DMA_BUFFERS]; /* dma buffers */ /* physical addresses of dma buffers */ dma_addr_t dio_buffer_phys_addr[NUM_DMA_BUFFERS]; /* * array of dma descriptors read by plx9080, allocated to get proper * alignment */ struct plx_dma_desc *dma_desc; /* physical address of dma descriptor array */ dma_addr_t dma_desc_phys_addr; unsigned int num_dma_descriptors; /* pointer to start of buffers indexed by descriptor */ u32 *desc_dio_buffer[NUM_DMA_DESCRIPTORS]; /* index of the dma descriptor that is currently being used */ unsigned int dma_desc_index; unsigned int tx_fifo_size; unsigned int rx_fifo_size; unsigned long dio_count; /* number of bytes at which to generate COMEDI_CB_BLOCK events */ unsigned int block_size; }; static void gsc_hpdi_drain_dma(struct comedi_device *dev, unsigned int channel) { struct hpdi_private *devpriv = dev->private; struct comedi_subdevice *s = dev->read_subdev; struct comedi_cmd *cmd = &s->async->cmd; unsigned int idx; unsigned int start; unsigned int desc; unsigned int size; unsigned int next; next = readl(devpriv->plx9080_mmio + PLX_REG_DMAPADR(channel)); idx = devpriv->dma_desc_index; start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr); /* loop until we have read all the full buffers */ for (desc = 0; (next < start || next >= start + devpriv->block_size) && desc < devpriv->num_dma_descriptors; desc++) { /* transfer data from dma buffer to comedi buffer */ size = devpriv->block_size / sizeof(u32); if (cmd->stop_src == TRIG_COUNT) { if (size > devpriv->dio_count) size = devpriv->dio_count; devpriv->dio_count -= size; } comedi_buf_write_samples(s, devpriv->desc_dio_buffer[idx], size); idx++; idx %= devpriv->num_dma_descriptors; start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr); devpriv->dma_desc_index = idx; } /* XXX check for buffer overrun somehow */ } static irqreturn_t gsc_hpdi_interrupt(int irq, void *d) { struct comedi_device *dev = d; struct hpdi_private *devpriv = dev->private; struct comedi_subdevice *s = dev->read_subdev; struct comedi_async *async = s->async; u32 hpdi_intr_status, hpdi_board_status; u32 plx_status; u32 plx_bits; u8 dma0_status, dma1_status; unsigned long flags; if (!dev->attached) return IRQ_NONE; plx_status = readl(devpriv->plx9080_mmio + PLX_REG_INTCSR); if ((plx_status & (PLX_INTCSR_DMA0IA | PLX_INTCSR_DMA1IA | PLX_INTCSR_PLIA)) == 0) return IRQ_NONE; hpdi_intr_status = readl(dev->mmio + INTERRUPT_STATUS_REG); hpdi_board_status = readl(dev->mmio + BOARD_STATUS_REG); if (hpdi_intr_status) writel(hpdi_intr_status, dev->mmio + INTERRUPT_STATUS_REG); /* spin lock makes sure no one else changes plx dma control reg */ spin_lock_irqsave(&dev->spinlock, flags); dma0_status = readb(devpriv->plx9080_mmio + PLX_REG_DMACSR0); if (plx_status & PLX_INTCSR_DMA0IA) { /* dma chan 0 interrupt */ writeb((dma0_status & PLX_DMACSR_ENABLE) | PLX_DMACSR_CLEARINTR, devpriv->plx9080_mmio + PLX_REG_DMACSR0); if (dma0_status & PLX_DMACSR_ENABLE) gsc_hpdi_drain_dma(dev, 0); } spin_unlock_irqrestore(&dev->spinlock, flags); /* spin lock makes sure no one else changes plx dma control reg */ spin_lock_irqsave(&dev->spinlock, flags); dma1_status = readb(devpriv->plx9080_mmio + PLX_REG_DMACSR1); if (plx_status & PLX_INTCSR_DMA1IA) { /* XXX */ /* dma chan 1 interrupt */ writeb((dma1_status & PLX_DMACSR_ENABLE) | PLX_DMACSR_CLEARINTR, devpriv->plx9080_mmio + PLX_REG_DMACSR1); } spin_unlock_irqrestore(&dev->spinlock, flags); /* clear possible plx9080 interrupt sources */ if (plx_status & PLX_INTCSR_LDBIA) { /* clear local doorbell interrupt */ plx_bits = readl(devpriv->plx9080_mmio + PLX_REG_L2PDBELL); writel(plx_bits, devpriv->plx9080_mmio + PLX_REG_L2PDBELL); } if (hpdi_board_status & RX_OVERRUN_BIT) { dev_err(dev->class_dev, "rx fifo overrun\n"); async->events |= COMEDI_CB_ERROR; } if (hpdi_board_status & RX_UNDERRUN_BIT) { dev_err(dev->class_dev, "rx fifo underrun\n"); async->events |= COMEDI_CB_ERROR; } if (devpriv->dio_count == 0) async->events |= COMEDI_CB_EOA; comedi_handle_events(dev, s); return IRQ_HANDLED; } static void gsc_hpdi_abort_dma(struct comedi_device *dev, unsigned int channel) { struct hpdi_private *devpriv = dev->private; unsigned long flags; /* spinlock for plx dma control/status reg */ spin_lock_irqsave(&dev->spinlock, flags); plx9080_abort_dma(devpriv->plx9080_mmio, channel); spin_unlock_irqrestore(&dev->spinlock, flags); } static int gsc_hpdi_cancel(struct comedi_device *dev, struct comedi_subdevice *s) { writel(0, dev->mmio + BOARD_CONTROL_REG); writel(0, dev->mmio + INTERRUPT_CONTROL_REG); gsc_hpdi_abort_dma(dev, 0); return 0; } static int gsc_hpdi_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { struct hpdi_private *devpriv = dev->private; struct comedi_async *async = s->async; struct comedi_cmd *cmd = &async->cmd; unsigned long flags; u32 bits; if (s->io_bits) return -EINVAL; writel(RX_FIFO_RESET_BIT, dev->mmio + BOARD_CONTROL_REG); gsc_hpdi_abort_dma(dev, 0); devpriv->dma_desc_index = 0; /* * These register are supposedly unused during chained dma, * but I have found that left over values from last operation * occasionally cause problems with transfer of first dma * block. Initializing them to zero seems to fix the problem. */ writel(0, devpriv->plx9080_mmio + PLX_REG_DMASIZ0); writel(0, devpriv->plx9080_mmio + PLX_REG_DMAPADR0); writel(0, devpriv->plx9080_mmio + PLX_REG_DMALADR0); /* give location of first dma descriptor */ bits = devpriv->dma_desc_phys_addr | PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR | PLX_DMADPR_XFERL2P; writel(bits, devpriv->plx9080_mmio + PLX_REG_DMADPR0); /* enable dma transfer */ spin_lock_irqsave(&dev->spinlock, flags); writeb(PLX_DMACSR_ENABLE | PLX_DMACSR_START | PLX_DMACSR_CLEARINTR, devpriv->plx9080_mmio + PLX_REG_DMACSR0); spin_unlock_irqrestore(&dev->spinlock, flags); if (cmd->stop_src == TRIG_COUNT) devpriv->dio_count = cmd->stop_arg; else devpriv->dio_count = 1; /* clear over/under run status flags */ writel(RX_UNDERRUN_BIT | RX_OVERRUN_BIT, dev->mmio + BOARD_STATUS_REG); /* enable interrupts */ writel(RX_FULL_INTR, dev->mmio + INTERRUPT_CONTROL_REG); writel(RX_ENABLE_BIT, dev->mmio + BOARD_CONTROL_REG); return 0; } static int gsc_hpdi_check_chanlist(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd) { int i; for (i = 0; i < cmd->chanlist_len; i++) { unsigned int chan = CR_CHAN(cmd->chanlist[i]); if (chan != i) { dev_dbg(dev->class_dev, "chanlist must be ch 0 to 31 in order\n"); return -EINVAL; } } return 0; } static int gsc_hpdi_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd) { int err = 0; if (s->io_bits) return -EINVAL; /* Step 1 : check if triggers are trivially valid */ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); if (err) return 1; /* Step 2a : make sure trigger sources are unique */ err |= comedi_check_trigger_is_unique(cmd->stop_src); /* Step 2b : and mutually compatible */ if (err) return 2; /* Step 3: check if arguments are trivially valid */ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); if (!cmd->chanlist_len || !cmd->chanlist) { cmd->chanlist_len = 32; err |= -EINVAL; } err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); if (cmd->stop_src == TRIG_COUNT) err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); else /* TRIG_NONE */ err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); if (err) return 3; /* Step 4: fix up any arguments */ /* Step 5: check channel list if it exists */ if (cmd->chanlist && cmd->chanlist_len > 0) err |= gsc_hpdi_check_chanlist(dev, s, cmd); if (err) return 5; return 0; } /* setup dma descriptors so a link completes every 'len' bytes */ static int gsc_hpdi_setup_dma_descriptors(struct comedi_device *dev, unsigned int len) { struct hpdi_private *devpriv = dev->private; dma_addr_t phys_addr = devpriv->dma_desc_phys_addr; u32 next_bits = PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR | PLX_DMADPR_XFERL2P; unsigned int offset = 0; unsigned int idx = 0; unsigned int i; if (len > DMA_BUFFER_SIZE) len = DMA_BUFFER_SIZE; len -= len % sizeof(u32); if (len == 0) return -EINVAL; for (i = 0; i < NUM_DMA_DESCRIPTORS && idx < NUM_DMA_BUFFERS; i++) { devpriv->dma_desc[i].pci_start_addr = cpu_to_le32(devpriv->dio_buffer_phys_addr[idx] + offset); devpriv->dma_desc[i].local_start_addr = cpu_to_le32(FIFO_REG); devpriv->dma_desc[i].transfer_size = cpu_to_le32(len); devpriv->dma_desc[i].next = cpu_to_le32((phys_addr + (i + 1) * sizeof(devpriv->dma_desc[0])) | next_bits); devpriv->desc_dio_buffer[i] = devpriv->dio_buffer[idx] + (offset / sizeof(u32)); offset += len; if (len + offset > DMA_BUFFER_SIZE) { offset = 0; idx++; } } devpriv->num_dma_descriptors = i; /* fix last descriptor to point back to first */ devpriv->dma_desc[i - 1].next = cpu_to_le32(phys_addr | next_bits); devpriv->block_size = len; return len; } static int gsc_hpdi_dio_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { int ret; switch (data[0]) { case INSN_CONFIG_BLOCK_SIZE: ret = gsc_hpdi_setup_dma_descriptors(dev, data[1]); if (ret) return ret; data[1] = ret; break; default: ret = comedi_dio_insn_config(dev, s, insn, data, 0xffffffff); if (ret) return ret; break; } return insn->n; } static void gsc_hpdi_free_dma(struct comedi_device *dev) { struct pci_dev *pcidev = comedi_to_pci_dev(dev); struct hpdi_private *devpriv = dev->private; int i; if (!devpriv) return; /* free pci dma buffers */ for (i = 0; i < NUM_DMA_BUFFERS; i++) { if (devpriv->dio_buffer[i]) dma_free_coherent(&pcidev->dev, DMA_BUFFER_SIZE, devpriv->dio_buffer[i], devpriv->dio_buffer_phys_addr[i]); } /* free dma descriptors */ if (devpriv->dma_desc) dma_free_coherent(&pcidev->dev, sizeof(struct plx_dma_desc) * NUM_DMA_DESCRIPTORS, devpriv->dma_desc, devpriv->dma_desc_phys_addr); } static int gsc_hpdi_init(struct comedi_device *dev) { struct hpdi_private *devpriv = dev->private; u32 plx_intcsr_bits; /* wait 10usec after reset before accessing fifos */ writel(BOARD_RESET_BIT, dev->mmio + BOARD_CONTROL_REG); usleep_range(10, 1000); writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32), dev->mmio + RX_PROG_ALMOST_REG); writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32), dev->mmio + TX_PROG_ALMOST_REG); devpriv->tx_fifo_size = readl(dev->mmio + TX_FIFO_SIZE_REG) & FIFO_SIZE_MASK; devpriv->rx_fifo_size = readl(dev->mmio + RX_FIFO_SIZE_REG) & FIFO_SIZE_MASK; writel(0, dev->mmio + INTERRUPT_CONTROL_REG); /* enable interrupts */ plx_intcsr_bits = PLX_INTCSR_LSEABORTEN | PLX_INTCSR_LSEPARITYEN | PLX_INTCSR_PIEN | PLX_INTCSR_PLIEN | PLX_INTCSR_PABORTIEN | PLX_INTCSR_LIOEN | PLX_INTCSR_DMA0IEN; writel(plx_intcsr_bits, devpriv->plx9080_mmio + PLX_REG_INTCSR); return 0; } static void gsc_hpdi_init_plx9080(struct comedi_device *dev) { struct hpdi_private *devpriv = dev->private; u32 bits; void __iomem *plx_iobase = devpriv->plx9080_mmio; #ifdef __BIG_ENDIAN bits = PLX_BIGEND_DMA0 | PLX_BIGEND_DMA1; #else bits = 0; #endif writel(bits, devpriv->plx9080_mmio + PLX_REG_BIGEND); writel(0, devpriv->plx9080_mmio + PLX_REG_INTCSR); gsc_hpdi_abort_dma(dev, 0); gsc_hpdi_abort_dma(dev, 1); /* configure dma0 mode */ bits = 0; /* enable ready input */ bits |= PLX_DMAMODE_READYIEN; /* enable dma chaining */ bits |= PLX_DMAMODE_CHAINEN; /* * enable interrupt on dma done * (probably don't need this, since chain never finishes) */ bits |= PLX_DMAMODE_DONEIEN; /* * don't increment local address during transfers * (we are transferring from a fixed fifo register) */ bits |= PLX_DMAMODE_LACONST; /* route dma interrupt to pci bus */ bits |= PLX_DMAMODE_INTRPCI; /* enable demand mode */ bits |= PLX_DMAMODE_DEMAND; /* enable local burst mode */ bits |= PLX_DMAMODE_BURSTEN; bits |= PLX_DMAMODE_WIDTH_32; writel(bits, plx_iobase + PLX_REG_DMAMODE0); } static int gsc_hpdi_auto_attach(struct comedi_device *dev, unsigned long context_unused) { struct pci_dev *pcidev = comedi_to_pci_dev(dev); struct hpdi_private *devpriv; struct comedi_subdevice *s; int i; int retval; dev->board_name = "pci-hpdi32"; devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); if (!devpriv) return -ENOMEM; retval = comedi_pci_enable(dev); if (retval) return retval; pci_set_master(pcidev); devpriv->plx9080_mmio = pci_ioremap_bar(pcidev, 0); dev->mmio = pci_ioremap_bar(pcidev, 2); if (!devpriv->plx9080_mmio || !dev->mmio) { dev_warn(dev->class_dev, "failed to remap io memory\n"); return -ENOMEM; } gsc_hpdi_init_plx9080(dev); /* get irq */ if (request_irq(pcidev->irq, gsc_hpdi_interrupt, IRQF_SHARED, dev->board_name, dev)) { dev_warn(dev->class_dev, "unable to allocate irq %u\n", pcidev->irq); return -EINVAL; } dev->irq = pcidev->irq; dev_dbg(dev->class_dev, " irq %u\n", dev->irq); /* allocate pci dma buffers */ for (i = 0; i < NUM_DMA_BUFFERS; i++) { devpriv->dio_buffer[i] = dma_alloc_coherent(&pcidev->dev, DMA_BUFFER_SIZE, &devpriv->dio_buffer_phys_addr[i], GFP_KERNEL); if (!devpriv->dio_buffer[i]) { dev_warn(dev->class_dev, "failed to allocate DMA buffer\n"); return -ENOMEM; } } /* allocate dma descriptors */ devpriv->dma_desc = dma_alloc_coherent(&pcidev->dev, sizeof(struct plx_dma_desc) * NUM_DMA_DESCRIPTORS, &devpriv->dma_desc_phys_addr, GFP_KERNEL); if (!devpriv->dma_desc) { dev_warn(dev->class_dev, "failed to allocate DMA descriptors\n"); return -ENOMEM; } if (devpriv->dma_desc_phys_addr & 0xf) { dev_warn(dev->class_dev, " dma descriptors not quad-word aligned (bug)\n"); return -EIO; } retval = gsc_hpdi_setup_dma_descriptors(dev, 0x1000); if (retval < 0) return retval; retval = comedi_alloc_subdevices(dev, 1); if (retval) return retval; /* Digital I/O subdevice */ s = &dev->subdevices[0]; dev->read_subdev = s; s->type = COMEDI_SUBD_DIO; s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | SDF_CMD_READ; s->n_chan = 32; s->len_chanlist = 32; s->maxdata = 1; s->range_table = &range_digital; s->insn_config = gsc_hpdi_dio_insn_config; s->do_cmd = gsc_hpdi_cmd; s->do_cmdtest = gsc_hpdi_cmd_test; s->cancel = gsc_hpdi_cancel; return gsc_hpdi_init(dev); } static void gsc_hpdi_detach(struct comedi_device *dev) { struct hpdi_private *devpriv = dev->private; if (dev->irq) free_irq(dev->irq, dev); if (devpriv) { if (devpriv->plx9080_mmio) { writel(0, devpriv->plx9080_mmio + PLX_REG_INTCSR); iounmap(devpriv->plx9080_mmio); } if (dev->mmio) iounmap(dev->mmio); } comedi_pci_disable(dev); gsc_hpdi_free_dma(dev); } static struct comedi_driver gsc_hpdi_driver = { .driver_name = "gsc_hpdi", .module = THIS_MODULE, .auto_attach = gsc_hpdi_auto_attach, .detach = gsc_hpdi_detach, }; static int gsc_hpdi_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { return comedi_pci_auto_config(dev, &gsc_hpdi_driver, id->driver_data); } static const struct pci_device_id gsc_hpdi_pci_table[] = { { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9080, PCI_VENDOR_ID_PLX, 0x2400) }, { 0 } }; MODULE_DEVICE_TABLE(pci, gsc_hpdi_pci_table); static struct pci_driver gsc_hpdi_pci_driver = { .name = "gsc_hpdi", .id_table = gsc_hpdi_pci_table, .probe = gsc_hpdi_pci_probe, .remove = comedi_pci_auto_unconfig, }; module_comedi_pci_driver(gsc_hpdi_driver, gsc_hpdi_pci_driver); MODULE_AUTHOR("Comedi https://www.comedi.org"); MODULE_DESCRIPTION("Comedi driver for General Standards PCI-HPDI32/PMC-HPDI32"); MODULE_LICENSE("GPL");