aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/comedi/drivers/adv_pci_dio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/comedi/drivers/adv_pci_dio.c')
-rw-r--r--drivers/staging/comedi/drivers/adv_pci_dio.c289
1 files changed, 277 insertions, 12 deletions
diff --git a/drivers/staging/comedi/drivers/adv_pci_dio.c b/drivers/staging/comedi/drivers/adv_pci_dio.c
index 0df28ec00f37..8e222b6ff2b4 100644
--- a/drivers/staging/comedi/drivers/adv_pci_dio.c
+++ b/drivers/staging/comedi/drivers/adv_pci_dio.c
@@ -34,9 +34,15 @@
*/
/* PCI-1730, PCI-1733, PCI-1736 interrupt control registers */
-#define PCI173X_INT_EN_REG 0x08 /* R/W: enable/disable */
-#define PCI173X_INT_RF_REG 0x0c /* R/W: falling/rising edge */
-#define PCI173X_INT_CLR_REG 0x10 /* R/W: clear */
+#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 */
@@ -63,6 +69,7 @@
#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,
@@ -84,7 +91,12 @@ enum pci_dio_boardid {
struct diosubd_data {
int chans; /* num of chans or 8255 devices */
- unsigned long addr; /* PCI address ofset */
+ 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 {
@@ -93,6 +105,7 @@ struct dio_boardtype {
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;
@@ -101,12 +114,17 @@ struct dio_boardtype {
static const struct dio_boardtype boardtypes[] = {
[TYPE_PCI1730] = {
.name = "pci1730",
- .nsubdevs = 5,
+ /* 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",
@@ -205,6 +223,188 @@ static const struct dio_boardtype boardtypes[] = {
},
};
+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,
@@ -283,6 +483,7 @@ static int pci_dio_insn_bits_do_w(struct comedi_device *dev,
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);
@@ -292,9 +493,13 @@ static int pci_dio_reset(struct comedi_device *dev, unsigned long cardtype)
case TYPE_PCI1730:
case TYPE_PCI1733:
case TYPE_PCI1736:
- outb(0, dev->iobase + PCI173X_INT_EN_REG);
+ 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);
- outb(0, dev->iobase + PCI173X_INT_RF_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:
@@ -346,8 +551,8 @@ static int pci_dio_auto_attach(struct comedi_device *dev,
{
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
const struct dio_boardtype *board = NULL;
- const struct diosubd_data *d;
struct comedi_subdevice *s;
+ struct pci_dio_dev_private_data *dev_private;
int ret, subdev, i, j;
if (context < ARRAY_SIZE(boardtypes))
@@ -357,6 +562,10 @@ static int pci_dio_auto_attach(struct comedi_device *dev,
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;
@@ -365,15 +574,25 @@ static int pci_dio_auto_attach(struct comedi_device *dev,
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++) {
- d = &board->sdi[i];
+ const struct diosubd_data *d = &board->sdi[i];
+
if (d->chans) {
s = &dev->subdevices[subdev++];
s->type = COMEDI_SUBD_DI;
@@ -385,11 +604,13 @@ static int pci_dio_auto_attach(struct comedi_device *dev,
? 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++) {
- d = &board->sdo[i];
+ const struct diosubd_data *d = &board->sdo[i];
+
if (d->chans) {
s = &dev->subdevices[subdev++];
s->type = COMEDI_SUBD_DO;
@@ -420,7 +641,8 @@ static int pci_dio_auto_attach(struct comedi_device *dev,
}
for (i = 0; i < PCI_DIO_MAX_DIO_SUBDEVG; i++) {
- d = &board->sdio[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,
@@ -454,14 +676,57 @@ static int pci_dio_auto_attach(struct comedi_device *dev,
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 = comedi_pci_detach,
+ .detach = pci_dio_detach,
};
static unsigned long pci_dio_override_cardtype(struct pci_dev *pcidev,