From 3959f4d4cf93ffd775441cca1f1e833d199aa5d0 Mon Sep 17 00:00:00 2001 From: Frank Mori Hess Date: Tue, 17 Feb 2009 16:30:02 -0800 Subject: Staging: comedi: add gsc_hpdi driver From: Frank Mori Hess Driver for the General Standards Corporation High Speed Parallel Digital Interface rs485 boards. From: Frank Mori Hess Cc: David Schleef Cc: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/gsc_hpdi.c | 1056 ++++++++++++++++++++++++++++++ 1 file changed, 1056 insertions(+) create mode 100644 drivers/staging/comedi/drivers/gsc_hpdi.c --- /dev/null +++ b/drivers/staging/comedi/drivers/gsc_hpdi.c @@ -0,0 +1,1056 @@ +/* + comedi/drivers/gsc_hpdi.c + This is a driver for 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 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ + +/* + +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: 2003-02-20 +Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi), + PMC-HPDI32 + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + +There are some additional hpdi models available from GSC for which +support could be added to this driver. + +*/ + +#include "../comedidev.h" +#include + +#include "comedi_pci.h" +#include "plx9080.h" +#include "comedi_fc.h" + +static int hpdi_attach(comedi_device * dev, comedi_devconfig * it); +static int hpdi_detach(comedi_device * dev); +void abort_dma(comedi_device * dev, unsigned int channel); +static int hpdi_cmd(comedi_device * dev, comedi_subdevice * s); +static int hpdi_cmd_test(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd); +static int hpdi_cancel(comedi_device * dev, comedi_subdevice * s); +static irqreturn_t handle_interrupt(int irq, void *d PT_REGS_ARG); +static int dio_config_block_size(comedi_device * dev, lsampl_t * data); + +#undef HPDI_DEBUG // disable debugging messages +//#define HPDI_DEBUG // enable debugging code + +#ifdef HPDI_DEBUG +#define DEBUG_PRINT(format, args...) rt_printk(format , ## args ) +#else +#define DEBUG_PRINT(format, args...) +#endif + +#define TIMER_BASE 50 // 20MHz master clock +#define DMA_BUFFER_SIZE 0x10000 +#define NUM_DMA_BUFFERS 4 +#define NUM_DMA_DESCRIPTORS 256 + +// indices of base address regions +enum base_address_regions { + PLX9080_BADDRINDEX = 0, + HPDI_BADDRINDEX = 2, +}; + +enum hpdi_registers { + FIRMWARE_REV_REG = 0x0, + BOARD_CONTROL_REG = 0x4, + BOARD_STATUS_REG = 0x8, + TX_PROG_ALMOST_REG = 0xc, + RX_PROG_ALMOST_REG = 0x10, + FEATURES_REG = 0x14, + FIFO_REG = 0x18, + TX_STATUS_COUNT_REG = 0x1c, + TX_LINE_VALID_COUNT_REG = 0x20, + TX_LINE_INVALID_COUNT_REG = 0x24, + RX_STATUS_COUNT_REG = 0x28, + RX_LINE_COUNT_REG = 0x2c, + INTERRUPT_CONTROL_REG = 0x30, + INTERRUPT_STATUS_REG = 0x34, + TX_CLOCK_DIVIDER_REG = 0x38, + TX_FIFO_SIZE_REG = 0x40, + RX_FIFO_SIZE_REG = 0x44, + TX_FIFO_WORDS_REG = 0x48, + RX_FIFO_WORDS_REG = 0x4c, + INTERRUPT_EDGE_LEVEL_REG = 0x50, + INTERRUPT_POLARITY_REG = 0x54, +}; + +int command_channel_valid(unsigned int channel) +{ + if (channel == 0 || channel > 6) { + rt_printk("gsc_hpdi: bug! invalid cable command channel\n"); + return 0; + } + return 1; +} + +// bit definitions + +enum firmware_revision_bits { + FEATURES_REG_PRESENT_BIT = 0x8000, +}; +int firmware_revision(uint32_t fwr_bits) +{ + return fwr_bits & 0xff; +} + +int pcb_revision(uint32_t fwr_bits) +{ + return (fwr_bits >> 8) & 0xff; +} + +int hpdi_subid(uint32_t fwr_bits) +{ + return (fwr_bits >> 16) & 0xff; +} + +enum board_control_bits { + BOARD_RESET_BIT = 0x1, /* wait 10usec before accessing fifos */ + TX_FIFO_RESET_BIT = 0x2, + RX_FIFO_RESET_BIT = 0x4, + TX_ENABLE_BIT = 0x10, + RX_ENABLE_BIT = 0x20, + DEMAND_DMA_DIRECTION_TX_BIT = 0x40, /* for channel 0, channel 1 can only transmit (when present) */ + LINE_VALID_ON_STATUS_VALID_BIT = 0x80, + START_TX_BIT = 0x10, + CABLE_THROTTLE_ENABLE_BIT = 0x20, + TEST_MODE_ENABLE_BIT = 0x80000000, +}; +uint32_t command_discrete_output_bits(unsigned int channel, int output, + int output_value) +{ + uint32_t bits = 0; + + if (command_channel_valid(channel) == 0) + return 0; + if (output) { + bits |= 0x1 << (16 + channel); + if (output_value) + bits |= 0x1 << (24 + channel); + } else + bits |= 0x1 << (24 + channel); + + return bits; +} + +enum board_status_bits { + COMMAND_LINE_STATUS_MASK = 0x7f, + TX_IN_PROGRESS_BIT = 0x80, + TX_NOT_EMPTY_BIT = 0x100, + TX_NOT_ALMOST_EMPTY_BIT = 0x200, + TX_NOT_ALMOST_FULL_BIT = 0x400, + TX_NOT_FULL_BIT = 0x800, + RX_NOT_EMPTY_BIT = 0x1000, + RX_NOT_ALMOST_EMPTY_BIT = 0x2000, + RX_NOT_ALMOST_FULL_BIT = 0x4000, + RX_NOT_FULL_BIT = 0x8000, + BOARD_JUMPER0_INSTALLED_BIT = 0x10000, + BOARD_JUMPER1_INSTALLED_BIT = 0x20000, + TX_OVERRUN_BIT = 0x200000, + RX_UNDERRUN_BIT = 0x400000, + RX_OVERRUN_BIT = 0x800000, +}; + +uint32_t almost_full_bits(unsigned int num_words) +{ +// XXX need to add or subtract one? + return (num_words << 16) & 0xff0000; +} + +uint32_t almost_empty_bits(unsigned int num_words) +{ + return num_words & 0xffff; +} +unsigned int almost_full_num_words(uint32_t bits) +{ +// XXX need to add or subtract one? + return (bits >> 16) & 0xffff; +} +unsigned int almost_empty_num_words(uint32_t bits) +{ + return bits & 0xffff; +} + +enum features_bits { + FIFO_SIZE_PRESENT_BIT = 0x1, + FIFO_WORDS_PRESENT_BIT = 0x2, + LEVEL_EDGE_INTERRUPTS_PRESENT_BIT = 0x4, + GPIO_SUPPORTED_BIT = 0x8, + PLX_DMA_CH1_SUPPORTED_BIT = 0x10, + OVERRUN_UNDERRUN_SUPPORTED_BIT = 0x20, +}; + +enum interrupt_sources { + FRAME_VALID_START_INTR = 0, + FRAME_VALID_END_INTR = 1, + TX_FIFO_EMPTY_INTR = 8, + TX_FIFO_ALMOST_EMPTY_INTR = 9, + TX_FIFO_ALMOST_FULL_INTR = 10, + TX_FIFO_FULL_INTR = 11, + RX_EMPTY_INTR = 12, + RX_ALMOST_EMPTY_INTR = 13, + RX_ALMOST_FULL_INTR = 14, + RX_FULL_INTR = 15, +}; +int command_intr_source(unsigned int channel) +{ + if (command_channel_valid(channel) == 0) + channel = 1; + return channel + 1; +} + +uint32_t intr_bit(int interrupt_source) +{ + return 0x1 << interrupt_source; +} + +uint32_t tx_clock_divisor_bits(unsigned int divisor) +{ + return divisor & 0xff; +} + +unsigned int fifo_size(uint32_t fifo_size_bits) +{ + return fifo_size_bits & 0xfffff; +} + +unsigned int fifo_words(uint32_t fifo_words_bits) +{ + return fifo_words_bits & 0xfffff; +} + +uint32_t intr_edge_bit(int interrupt_source) +{ + return 0x1 << interrupt_source; +} + +uint32_t intr_active_high_bit(int interrupt_source) +{ + return 0x1 << interrupt_source; +} + +typedef struct { + char *name; + int device_id; // pci device id + int subdevice_id; // pci subdevice id +} hpdi_board; + +static const hpdi_board hpdi_boards[] = { + { + name: "pci-hpdi32", + device_id:PCI_DEVICE_ID_PLX_9080, + subdevice_id:0x2400, + }, +#if 0 + { + name: "pxi-hpdi32", + device_id:0x9656, + subdevice_id:0x2705, + }, +#endif +}; + +static inline unsigned int num_boards(void) +{ + return sizeof(hpdi_boards) / sizeof(hpdi_board); +} + +static DEFINE_PCI_DEVICE_TABLE(hpdi_pci_table) = { + {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9080, PCI_VENDOR_ID_PLX, 0x2400, + 0, 0, 0}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, hpdi_pci_table); + +static inline hpdi_board *board(const comedi_device * dev) +{ + return (hpdi_board *) dev->board_ptr; +} + +typedef struct { + struct pci_dev *hw_dev; // pointer to board's pci_dev struct + // base addresses (physical) + resource_size_t plx9080_phys_iobase; + resource_size_t hpdi_phys_iobase; + // base addresses (ioremapped) + void *plx9080_iobase; + void *hpdi_iobase; + uint32_t *dio_buffer[NUM_DMA_BUFFERS]; // dma buffers + dma_addr_t dio_buffer_phys_addr[NUM_DMA_BUFFERS]; // physical addresses of dma buffers + struct plx_dma_desc *dma_desc; // array of dma descriptors read by plx9080, allocated to get proper alignment + dma_addr_t dma_desc_phys_addr; // physical address of dma descriptor array + unsigned int num_dma_descriptors; + uint32_t *desc_dio_buffer[NUM_DMA_DESCRIPTORS]; // pointer to start of buffers indexed by descriptor + volatile unsigned int dma_desc_index; // index of the dma descriptor that is currently being used + unsigned int tx_fifo_size; + unsigned int rx_fifo_size; + volatile unsigned long dio_count; + volatile uint32_t bits[24]; // software copies of values written to hpdi registers + volatile unsigned int block_size; // number of bytes at which to generate COMEDI_CB_BLOCK events + unsigned dio_config_output:1; +} hpdi_private; + +static inline hpdi_private *priv(comedi_device * dev) +{ + return dev->private; +} + +static comedi_driver driver_hpdi = { + driver_name:"gsc_hpdi", + module:THIS_MODULE, + attach:hpdi_attach, + detach:hpdi_detach, +}; + +COMEDI_PCI_INITCLEANUP(driver_hpdi, hpdi_pci_table); + +static int dio_config_insn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + priv(dev)->dio_config_output = 1; + return insn->n; + break; + case INSN_CONFIG_DIO_INPUT: + priv(dev)->dio_config_output = 0; + return insn->n; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + priv(dev)-> + dio_config_output ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + case INSN_CONFIG_BLOCK_SIZE: + return dio_config_block_size(dev, data); + break; + default: + break; + } + + return -EINVAL; +} + +static void disable_plx_interrupts(comedi_device * dev) +{ + writel(0, priv(dev)->plx9080_iobase + PLX_INTRCS_REG); +} + +// initialize plx9080 chip +static void init_plx9080(comedi_device * dev) +{ + uint32_t bits; + void *plx_iobase = priv(dev)->plx9080_iobase; + + // plx9080 dump + DEBUG_PRINT(" plx interrupt status 0x%x\n", + readl(plx_iobase + PLX_INTRCS_REG)); + DEBUG_PRINT(" plx id bits 0x%x\n", readl(plx_iobase + PLX_ID_REG)); + DEBUG_PRINT(" plx control reg 0x%x\n", + readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG)); + + DEBUG_PRINT(" plx revision 0x%x\n", + readl(plx_iobase + PLX_REVISION_REG)); + DEBUG_PRINT(" plx dma channel 0 mode 0x%x\n", + readl(plx_iobase + PLX_DMA0_MODE_REG)); + DEBUG_PRINT(" plx dma channel 1 mode 0x%x\n", + readl(plx_iobase + PLX_DMA1_MODE_REG)); + DEBUG_PRINT(" plx dma channel 0 pci address 0x%x\n", + readl(plx_iobase + PLX_DMA0_PCI_ADDRESS_REG)); + DEBUG_PRINT(" plx dma channel 0 local address 0x%x\n", + readl(plx_iobase + PLX_DMA0_LOCAL_ADDRESS_REG)); + DEBUG_PRINT(" plx dma channel 0 transfer size 0x%x\n", + readl(plx_iobase + PLX_DMA0_TRANSFER_SIZE_REG)); + DEBUG_PRINT(" plx dma channel 0 descriptor 0x%x\n", + readl(plx_iobase + PLX_DMA0_DESCRIPTOR_REG)); + DEBUG_PRINT(" plx dma channel 0 command status 0x%x\n", + readb(plx_iobase + PLX_DMA0_CS_REG)); + DEBUG_PRINT(" plx dma channel 0 threshold 0x%x\n", + readl(plx_iobase + PLX_DMA0_THRESHOLD_REG)); + DEBUG_PRINT(" plx bigend 0x%x\n", readl(plx_iobase + PLX_BIGEND_REG)); +#ifdef __BIG_ENDIAN + bits = BIGEND_DMA0 | BIGEND_DMA1; +#else + bits = 0; +#endif + writel(bits, priv(dev)->plx9080_iobase + PLX_BIGEND_REG); + + disable_plx_interrupts(dev); + + abort_dma(dev, 0); + abort_dma(dev, 1); + + // configure dma0 mode + bits = 0; + // enable ready input + bits |= PLX_DMA_EN_READYIN_BIT; + // enable dma chaining + bits |= PLX_EN_CHAIN_BIT; + // enable interrupt on dma done (probably don't need this, since chain never finishes) + bits |= PLX_EN_DMA_DONE_INTR_BIT; + // don't increment local address during transfers (we are transferring from a fixed fifo register) + bits |= PLX_LOCAL_ADDR_CONST_BIT; + // route dma interrupt to pci bus + bits |= PLX_DMA_INTR_PCI_BIT; + // enable demand mode + bits |= PLX_DEMAND_MODE_BIT; + // enable local burst mode + bits |= PLX_DMA_LOCAL_BURST_EN_BIT; + bits |= PLX_LOCAL_BUS_32_WIDE_BITS; + writel(bits, plx_iobase + PLX_DMA0_MODE_REG); +} + +/* Allocate and initialize the subdevice structures. + */ +static int setup_subdevices(comedi_device * dev) +{ + comedi_subdevice *s; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + dev->read_subdev = s; +/* dev->write_subdev = s; */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = + SDF_READABLE | SDF_WRITEABLE | SDF_LSAMPL | SDF_CMD_READ; + s->n_chan = 32; + s->len_chanlist = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = dio_config_insn; + s->do_cmd = hpdi_cmd; + s->do_cmdtest = hpdi_cmd_test; + s->cancel = hpdi_cancel; + + return 0; +} + +static int init_hpdi(comedi_device * dev) +{ + uint32_t plx_intcsr_bits; + + writel(BOARD_RESET_BIT, priv(dev)->hpdi_iobase + BOARD_CONTROL_REG); + comedi_udelay(10); + + writel(almost_empty_bits(32) | almost_full_bits(32), + priv(dev)->hpdi_iobase + RX_PROG_ALMOST_REG); + writel(almost_empty_bits(32) | almost_full_bits(32), + priv(dev)->hpdi_iobase + TX_PROG_ALMOST_REG); + + priv(dev)->tx_fifo_size = fifo_size(readl(priv(dev)->hpdi_iobase + + TX_FIFO_SIZE_REG)); + priv(dev)->rx_fifo_size = fifo_size(readl(priv(dev)->hpdi_iobase + + RX_FIFO_SIZE_REG)); + + writel(0, priv(dev)->hpdi_iobase + INTERRUPT_CONTROL_REG); + + // enable interrupts + plx_intcsr_bits = + ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE | + ICS_DMA0_E; + writel(plx_intcsr_bits, priv(dev)->plx9080_iobase + PLX_INTRCS_REG); + + return 0; +} + +// setup dma descriptors so a link completes every 'transfer_size' bytes +static int setup_dma_descriptors(comedi_device * dev, + unsigned int transfer_size) +{ + unsigned int buffer_index, buffer_offset; + uint32_t next_bits = PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT | + PLX_XFER_LOCAL_TO_PCI; + unsigned int i; + + if (transfer_size > DMA_BUFFER_SIZE) + transfer_size = DMA_BUFFER_SIZE; + transfer_size -= transfer_size % sizeof(uint32_t); + if (transfer_size == 0) + return -1; + + DEBUG_PRINT(" transfer_size %i\n", transfer_size); + DEBUG_PRINT(" descriptors at 0x%lx\n", + (unsigned long)priv(dev)->dma_desc_phys_addr); + + buffer_offset = 0; + buffer_index = 0; + for (i = 0; i < NUM_DMA_DESCRIPTORS && + buffer_index < NUM_DMA_BUFFERS; i++) { + priv(dev)->dma_desc[i].pci_start_addr = + cpu_to_le32(priv(dev)-> + dio_buffer_phys_addr[buffer_index] + buffer_offset); + priv(dev)->dma_desc[i].local_start_addr = cpu_to_le32(FIFO_REG); + priv(dev)->dma_desc[i].transfer_size = + cpu_to_le32(transfer_size); + priv(dev)->dma_desc[i].next = + cpu_to_le32((priv(dev)->dma_desc_phys_addr + (i + + 1) * + sizeof(priv(dev)->dma_desc[0])) | next_bits); + + priv(dev)->desc_dio_buffer[i] = + priv(dev)->dio_buffer[buffer_index] + + (buffer_offset / sizeof(uint32_t)); + + buffer_offset += transfer_size; + if (transfer_size + buffer_offset > DMA_BUFFER_SIZE) { + buffer_offset = 0; + buffer_index++; + } + + DEBUG_PRINT(" desc %i\n", i); + DEBUG_PRINT(" start addr virt 0x%p, phys 0x%lx\n", + priv(dev)->desc_dio_buffer[i], + (unsigned long)priv(dev)->dma_desc[i].pci_start_addr); + DEBUG_PRINT(" next 0x%lx\n", + (unsigned long)priv(dev)->dma_desc[i].next); + } + priv(dev)->num_dma_descriptors = i; + // fix last descriptor to point back to first + priv(dev)->dma_desc[i - 1].next = + cpu_to_le32(priv(dev)->dma_desc_phys_addr | next_bits); + DEBUG_PRINT(" desc %i next fixup 0x%lx\n", i - 1, + (unsigned long)priv(dev)->dma_desc[i - 1].next); + + priv(dev)->block_size = transfer_size; + + return transfer_size; +} + +static int hpdi_attach(comedi_device * dev, comedi_devconfig * it) +{ + struct pci_dev *pcidev; + int i; + int retval; + + printk("comedi%d: gsc_hpdi\n", dev->minor); + + if (alloc_private(dev, sizeof(hpdi_private)) < 0) + return -ENOMEM; + + pcidev = NULL; + for (i = 0; i < num_boards() && dev->board_ptr == NULL; i++) { + do { + pcidev = pci_get_subsys(PCI_VENDOR_ID_PLX, + hpdi_boards[i].device_id, PCI_VENDOR_ID_PLX, + hpdi_boards[i].subdevice_id, pcidev); + // was a particular bus/slot requested? + if (it->options[0] || it->options[1]) { + // are we on the wrong bus/slot? + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != + it->options[1]) + continue; + } + if (pcidev) { + priv(dev)->hw_dev = pcidev; + dev->board_ptr = hpdi_boards + i; + break; + } + } while (pcidev != NULL); + } + if (dev->board_ptr == NULL) { + printk("gsc_hpdi: no hpdi card found\n"); + return -EIO; + } + + printk("gsc_hpdi: found %s on bus %i, slot %i\n", board(dev)->name, + pcidev->bus->number, PCI_SLOT(pcidev->devfn)); + + if (comedi_pci_enable(pcidev, driver_hpdi.driver_name)) { + printk(KERN_WARNING + " failed enable PCI device and request regions\n"); + return -EIO; + } + pci_set_master(pcidev); + + //Initialize dev->board_name + dev->board_name = board(dev)->name; + + priv(dev)->plx9080_phys_iobase = + pci_resource_start(pcidev, PLX9080_BADDRINDEX); + priv(dev)->hpdi_phys_iobase = + pci_resource_start(pcidev, HPDI_BADDRINDEX); + + // remap, won't work with 2.0 kernels but who cares + priv(dev)->plx9080_iobase = ioremap(priv(dev)->plx9080_phys_iobase, + pci_resource_len(pcidev, PLX9080_BADDRINDEX)); + priv(dev)->hpdi_iobase = ioremap(priv(dev)->hpdi_phys_iobase, + pci_resource_len(pcidev, HPDI_BADDRINDEX)); + if (!priv(dev)->plx9080_iobase || !priv(dev)->hpdi_iobase) { + printk(" failed to remap io memory\n"); + return -ENOMEM; + } + + DEBUG_PRINT(" plx9080 remapped to 0x%p\n", priv(dev)->plx9080_iobase); + DEBUG_PRINT(" hpdi remapped to 0x%p\n", priv(dev)->hpdi_iobase); + + init_plx9080(dev); + + // get irq + if (comedi_request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED, + driver_hpdi.driver_name, dev)) { + printk(" unable to allocate irq %u\n", pcidev->irq); + return -EINVAL; + } + dev->irq = pcidev->irq; + + printk(" irq %u\n", dev->irq); + + // alocate pci dma buffers + for (i = 0; i < NUM_DMA_BUFFERS; i++) { + priv(dev)->dio_buffer[i] = + pci_alloc_consistent(priv(dev)->hw_dev, DMA_BUFFER_SIZE, + &priv(dev)->dio_buffer_phys_addr[i]); + DEBUG_PRINT("dio_buffer at virt 0x%p, phys 0x%lx\n", + priv(dev)->dio_buffer[i], + (unsigned long)priv(dev)->dio_buffer_phys_addr[i]); + } + // allocate dma descriptors + priv(dev)->dma_desc = pci_alloc_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) * NUM_DMA_DESCRIPTORS, + &priv(dev)->dma_desc_phys_addr); + if (priv(dev)->dma_desc_phys_addr & 0xf) { + printk(" dma descriptors not quad-word aligned (bug)\n"); + return -EIO; + } + + retval = setup_dma_descriptors(dev, 0x1000); + if (retval < 0) + return retval; + + retval = setup_subdevices(dev); + if (retval < 0) + return retval; + + return init_hpdi(dev); +} + +static int hpdi_detach(comedi_device * dev) +{ + unsigned int i; + + printk("comedi%d: gsc_hpdi: remove\n", dev->minor); + + if (dev->irq) + comedi_free_irq(dev->irq, dev); + if (priv(dev)) { + if (priv(dev)->hw_dev) { + if (priv(dev)->plx9080_iobase) { + disable_plx_interrupts(dev); + iounmap((void *)priv(dev)->plx9080_iobase); + } + if (priv(dev)->hpdi_iobase) + iounmap((void *)priv(dev)->hpdi_iobase); + // free pci dma buffers + for (i = 0; i < NUM_DMA_BUFFERS; i++) { + if (priv(dev)->dio_buffer[i]) + pci_free_consistent(priv(dev)->hw_dev, + DMA_BUFFER_SIZE, + priv(dev)->dio_buffer[i], + priv(dev)-> + dio_buffer_phys_addr[i]); + } + // free dma descriptors + if (priv(dev)->dma_desc) + pci_free_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) * + NUM_DMA_DESCRIPTORS, + priv(dev)->dma_desc, + priv(dev)->dma_desc_phys_addr); + if (priv(dev)->hpdi_phys_iobase) { + comedi_pci_disable(priv(dev)->hw_dev); + } + pci_dev_put(priv(dev)->hw_dev); + } + } + return 0; +} + +static int dio_config_block_size(comedi_device * dev, lsampl_t * data) +{ + unsigned int requested_block_size; + int retval; + + requested_block_size = data[1]; + + retval = setup_dma_descriptors(dev, requested_block_size); + if (retval < 0) + return retval; + + data[1] = retval; + + return 2; +} + +static int di_cmd_test(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd) +{ + int err = 0; + int tmp; + int i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + // uniqueness check + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 32; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + if (cmd->chanlist) { + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != i) { + // XXX could support 8 channels or 16 channels + comedi_error(dev, + "chanlist must be channels 0 to 31 in order"); + err++; + break; + } + } + } + + if (err) + return 5; + + return 0; +} + +static int hpdi_cmd_test(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd) +{ + if (priv(dev)->dio_config_output) { + return -EINVAL; + } else + return di_cmd_test(dev, s, cmd); +} + +static inline void hpdi_writel(comedi_device * dev, uint32_t bits, + unsigned int offset) +{ + writel(bits | priv(dev)->bits[offset / sizeof(uint32_t)], + priv(dev)->hpdi_iobase + offset); +} + +static int di_cmd(comedi_device * dev, comedi_subdevice * s) +{ + uint32_t bits; + unsigned long flags; + comedi_async *async = s->async; + comedi_cmd *cmd = &async->cmd; + + hpdi_writel(dev, RX_FIFO_RESET_BIT, BOARD_CONTROL_REG); + + DEBUG_PRINT("hpdi: in di_cmd\n"); + + abort_dma(dev, 0); + + priv(dev)->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, priv(dev)->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG); + writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG); + writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG); + // give location of first dma descriptor + bits = priv(dev)-> + dma_desc_phys_addr | PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT | + PLX_XFER_LOCAL_TO_PCI; + writel(bits, priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG); + + // spinlock for plx dma control/status reg + comedi_spin_lock_irqsave(&dev->spinlock, flags); + // enable dma transfer + writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + + if (cmd->stop_src == TRIG_COUNT) + priv(dev)->dio_count = cmd->stop_arg; + else + priv(dev)->dio_count = 1; + + // clear over/under run status flags + writel(RX_UNDERRUN_BIT | RX_OVERRUN_BIT, + priv(dev)->hpdi_iobase + BOARD_STATUS_REG); + // enable interrupts + writel(intr_bit(RX_FULL_INTR), + priv(dev)->hpdi_iobase + INTERRUPT_CONTROL_REG); + + DEBUG_PRINT("hpdi: starting rx\n"); + hpdi_writel(dev, RX_ENABLE_BIT, BOARD_CONTROL_REG); + + return 0; +} + +static int hpdi_cmd(comedi_device * dev, comedi_subdevice * s) +{ + if (priv(dev)->dio_config_output) { + return -EINVAL; + } else + return di_cmd(dev, s); +} + +static void drain_dma_buffers(comedi_device * dev, unsigned int channel) +{ + comedi_async *async = dev->read_subdev->async; + uint32_t next_transfer_addr; + int j; + int num_samples = 0; + void *pci_addr_reg; + + if (channel) + pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG; + else + pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG; + + // loop until we have read all the full buffers + j = 0; + for (next_transfer_addr = readl(pci_addr_reg); + (next_transfer_addr < + le32_to_cpu(priv(dev)->dma_desc[priv(dev)-> + dma_desc_index].pci_start_addr) + || next_transfer_addr >= + le32_to_cpu(priv(dev)->dma_desc[priv(dev)-> + dma_desc_index].pci_start_addr) + + priv(dev)->block_size) + && j < priv(dev)->num_dma_descriptors; j++) { + // transfer data from dma buffer to comedi buffer + num_samples = priv(dev)->block_size / sizeof(uint32_t); + if (async->cmd.stop_src == TRIG_COUNT) { + if (num_samples > priv(dev)->dio_count) + num_samples = priv(dev)->dio_count; + priv(dev)->dio_count -= num_samples; + } + cfc_write_array_to_buffer(dev->read_subdev, + priv(dev)->desc_dio_buffer[priv(dev)->dma_desc_index], + num_samples * sizeof(uint32_t)); + priv(dev)->dma_desc_index++; + priv(dev)->dma_desc_index %= priv(dev)->num_dma_descriptors; + + DEBUG_PRINT("next desc addr 0x%lx\n", (unsigned long) + priv(dev)->dma_desc[priv(dev)->dma_desc_index].next); + DEBUG_PRINT("pci addr reg 0x%x\n", next_transfer_addr); + } + // XXX check for buffer overrun somehow +} + +static irqreturn_t handle_interrupt(int irq, void *d PT_REGS_ARG) +{ + comedi_device *dev = d; + comedi_subdevice *s = dev->read_subdev; + comedi_async *async = s->async; + uint32_t hpdi_intr_status, hpdi_board_status; + uint32_t plx_status; + uint32_t plx_bits; + uint8_t dma0_status, dma1_status; + unsigned long flags; + + if (!dev->attached) { + return IRQ_NONE; + } + + plx_status = readl(priv(dev)->plx9080_iobase + PLX_INTRCS_REG); + if ((plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LIA)) == 0) { + return IRQ_NONE; + } + + hpdi_intr_status = readl(priv(dev)->hpdi_iobase + INTERRUPT_STATUS_REG); + hpdi_board_status = readl(priv(dev)->hpdi_iobase + BOARD_STATUS_REG); + + async->events = 0; + + if (hpdi_intr_status) { + DEBUG_PRINT("hpdi: intr status 0x%x, ", hpdi_intr_status); + writel(hpdi_intr_status, + priv(dev)->hpdi_iobase + INTERRUPT_STATUS_REG); + } + // spin lock makes sure noone else changes plx dma control reg + comedi_spin_lock_irqsave(&dev->spinlock, flags); + dma0_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + if (plx_status & ICS_DMA0_A) { // dma chan 0 interrupt + writeb((dma0_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + + DEBUG_PRINT("dma0 status 0x%x\n", dma0_status); + if (dma0_status & PLX_DMA_EN_BIT) { + drain_dma_buffers(dev, 0); + } + DEBUG_PRINT(" cleared dma ch0 interrupt\n"); + } + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + + // spin lock makes sure noone else changes plx dma control reg + comedi_spin_lock_irqsave(&dev->spinlock, flags); + dma1_status = readb(priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + if (plx_status & ICS_DMA1_A) // XXX + { // dma chan 1 interrupt + writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + DEBUG_PRINT("dma1 status 0x%x\n", dma1_status); + + DEBUG_PRINT(" cleared dma ch1 interrupt\n"); + } + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + + // clear possible plx9080 interrupt sources + if (plx_status & ICS_LDIA) { // clear local doorbell interrupt + plx_bits = readl(priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG); + writel(plx_bits, priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG); + DEBUG_PRINT(" cleared local doorbell bits 0x%x\n", plx_bits); + } + + if (hpdi_board_status & RX_OVERRUN_BIT) { + comedi_error(dev, "rx fifo overrun"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + DEBUG_PRINT("dma0_status 0x%x\n", + (int)readb(priv(dev)->plx9080_iobase + + PLX_DMA0_CS_REG)); + } + + if (hpdi_board_status & RX_UNDERRUN_BIT) { + comedi_error(dev, "rx fifo underrun"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } + + if (priv(dev)->dio_count == 0) + async->events |= COMEDI_CB_EOA; + + DEBUG_PRINT("board status 0x%x, ", hpdi_board_status); + DEBUG_PRINT("plx status 0x%x\n", plx_status); + if (async->events) + DEBUG_PRINT(" events 0x%x\n", async->events); + + cfc_handle_events(dev, s); + + return IRQ_HANDLED; +} + +void abort_dma(comedi_device * dev, unsigned int channel) +{ + unsigned long flags; + + // spinlock for plx dma control/status reg + comedi_spin_lock_irqsave(&dev->spinlock, flags); + + plx9080_abort_dma(priv(dev)->plx9080_iobase, channel); + + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static int hpdi_cancel(comedi_device * dev, comedi_subdevice * s) +{ + hpdi_writel(dev, 0, BOARD_CONTROL_REG); + + writel(0, priv(dev)->hpdi_iobase + INTERRUPT_CONTROL_REG); + + abort_dma(dev, 0); + + return 0; +}