mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
staging: comedi: addi_apci_1564: add Change-of-State interrupt subdevice and required functions
This board supports an interrupt that can be generated by an AND/OR combination of 16 of the input channels. Create a separate subdevice to handle this interrupt. The apci1564_di_config() function is used to configure which inputs are used to generate the interrupt. Currently this function is broken since it does not follow the comedi API for insn_config functions. Fix this function by implementing the config instruction INSN_CONFIG_DIGITAL_TRIG. Add the remaining subdevice operations necessary for the interrupt subdevice to support async commands. Signed-off-by: Chase Southwood <chase.southwood@gmail.com> Reviewed-by: Ian Abbott <abbotti@mev.co.uk> Cc: H Hartley Sweeten <hsweeten@visionengravers.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
4c95a2b6b5
commit
1e15687ea4
2 changed files with 232 additions and 62 deletions
|
@ -26,12 +26,12 @@
|
|||
#define APCI1564_ADDRESS_RANGE 128
|
||||
|
||||
/* Digital Input IRQ Function Selection */
|
||||
#define ADDIDATA_OR 0
|
||||
#define ADDIDATA_AND 1
|
||||
#define APCI1564_DI_INT_OR (0 << 1)
|
||||
#define APCI1564_DI_INT_AND (1 << 1)
|
||||
|
||||
/* Digital Input Interrupt Enable Disable. */
|
||||
#define APCI1564_DIGITAL_IP_INTERRUPT_ENABLE 0x4
|
||||
#define APCI1564_DIGITAL_IP_INTERRUPT_DISABLE 0xfffffffb
|
||||
#define APCI1564_DI_INT_ENABLE 0x4
|
||||
#define APCI1564_DI_INT_DISABLE 0xfffffffb
|
||||
|
||||
/* Digital Output Interrupt Enable Disable. */
|
||||
#define APCI1564_DIGITAL_OP_VCC_INTERRUPT_ENABLE 0x1
|
||||
|
@ -89,42 +89,6 @@
|
|||
#define APCI1564_TCW_WARN_TIMEVAL_REG(x) (0x18 + ((x) * 0x20))
|
||||
#define APCI1564_TCW_WARN_TIMEBASE_REG(x) (0x1c + ((x) * 0x20))
|
||||
|
||||
/*
|
||||
* Configures the digital input Subdevice
|
||||
*
|
||||
* data[0] 1 = Enable interrupt, 0 = Disable interrupt
|
||||
* data[1] 0 = ADDIDATA Interrupt OR LOGIC, 1 = ADDIDATA Interrupt AND LOGIC
|
||||
* data[2] Interrupt mask for the mode 1
|
||||
* data[3] Interrupt mask for the mode 2
|
||||
*/
|
||||
static int apci1564_di_config(struct comedi_device *dev,
|
||||
struct comedi_subdevice *s,
|
||||
struct comedi_insn *insn,
|
||||
unsigned int *data)
|
||||
{
|
||||
struct apci1564_private *devpriv = dev->private;
|
||||
|
||||
devpriv->tsk_current = current;
|
||||
|
||||
/* Set the digital input logic */
|
||||
if (data[0] == 1) {
|
||||
data[2] = data[2] << 4;
|
||||
data[3] = data[3] << 4;
|
||||
outl(data[2], devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
|
||||
outl(data[3], devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
|
||||
if (data[1] == ADDIDATA_OR)
|
||||
outl(0x4, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
|
||||
else
|
||||
outl(0x6, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
|
||||
} else {
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
|
||||
}
|
||||
|
||||
return insn->n;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configures The Digital Output Subdevice.
|
||||
*
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
struct apci1564_private {
|
||||
unsigned int amcc_iobase; /* base of AMCC I/O registers */
|
||||
unsigned int mode1; /* riding-edge/high level channels */
|
||||
unsigned int mode2; /* falling-edge/low level channels */
|
||||
unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
|
||||
unsigned int do_int_type;
|
||||
unsigned char timer_select_mode;
|
||||
unsigned char mode_select_register;
|
||||
|
@ -16,6 +19,38 @@ struct apci1564_private {
|
|||
|
||||
#include "addi-data/hwdrv_apci1564.c"
|
||||
|
||||
static int apci1564_reset(struct comedi_device *dev)
|
||||
{
|
||||
struct apci1564_private *devpriv = dev->private;
|
||||
|
||||
devpriv->do_int_type = 0;
|
||||
|
||||
/* Disable the input interrupts and reset status register */
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
|
||||
inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
|
||||
|
||||
/* Reset the output channels and disable interrupts */
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_INT_CTRL_REG);
|
||||
|
||||
/* Reset the watchdog registers */
|
||||
addi_watchdog_reset(devpriv->amcc_iobase + APCI1564_WDOG_REG);
|
||||
|
||||
/* Reset the timer registers */
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_RELOAD_REG);
|
||||
|
||||
/* Reset the counter registers */
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t v_ADDI_Interrupt(int irq, void *d)
|
||||
{
|
||||
apci1564_interrupt(irq, d);
|
||||
|
@ -51,35 +86,188 @@ static int apci1564_do_insn_bits(struct comedi_device *dev,
|
|||
return insn->n;
|
||||
}
|
||||
|
||||
static int apci1564_reset(struct comedi_device *dev)
|
||||
/*
|
||||
* 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
|
||||
* 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;
|
||||
|
||||
switch (data[0]) {
|
||||
case INSN_CONFIG_DIGITAL_TRIG:
|
||||
if (data[1] != 0)
|
||||
return -EINVAL;
|
||||
shift = data[3];
|
||||
oldmask = (1U << shift) - 1;
|
||||
switch (data[2]) {
|
||||
case COMEDI_DIGITAL_TRIG_DISABLE:
|
||||
devpriv->ctrl = 0;
|
||||
devpriv->mode1 = 0;
|
||||
devpriv->mode2 = 0;
|
||||
apci1564_reset(dev);
|
||||
break;
|
||||
case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
|
||||
if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
|
||||
APCI1564_DI_INT_OR)) {
|
||||
/* switching to 'OR' mode */
|
||||
devpriv->ctrl = APCI1564_DI_INT_ENABLE |
|
||||
APCI1564_DI_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 |= data[4] << shift;
|
||||
devpriv->mode2 |= data[5] << shift;
|
||||
break;
|
||||
case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
|
||||
if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
|
||||
APCI1564_DI_INT_AND)) {
|
||||
/* switching to 'AND' mode */
|
||||
devpriv->ctrl = APCI1564_DI_INT_ENABLE |
|
||||
APCI1564_DI_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 |= data[4] << shift;
|
||||
devpriv->mode2 |= data[5] << shift;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
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 |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
|
||||
err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
|
||||
err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
|
||||
err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
|
||||
err |= cfc_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 */
|
||||
|
||||
if (err)
|
||||
return 2;
|
||||
|
||||
/* Step 3: check if arguments are trivially valid */
|
||||
|
||||
err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
|
||||
err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
|
||||
err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
|
||||
err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
|
||||
err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
|
||||
|
||||
if (err)
|
||||
return 3;
|
||||
|
||||
/* step 4: ignored */
|
||||
|
||||
if (err)
|
||||
return 4;
|
||||
|
||||
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;
|
||||
|
||||
devpriv->do_int_type = 0;
|
||||
if (!devpriv->ctrl) {
|
||||
dev_warn(dev->class_dev,
|
||||
"Interrupts disabled due to mode configuration!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
outl(devpriv->mode1, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
|
||||
outl(devpriv->mode2, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
|
||||
outl(devpriv->ctrl, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apci1564_cos_cancel(struct comedi_device *dev,
|
||||
struct comedi_subdevice *s)
|
||||
{
|
||||
struct apci1564_private *devpriv = dev->private;
|
||||
|
||||
/* Disable the input interrupts and reset status register */
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
|
||||
inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
|
||||
|
||||
/* Reset the output channels and disable interrupts */
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_INT_CTRL_REG);
|
||||
|
||||
/* Reset the watchdog registers */
|
||||
addi_watchdog_reset(devpriv->amcc_iobase + APCI1564_WDOG_REG);
|
||||
|
||||
/* Reset the timer registers */
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
|
||||
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_RELOAD_REG);
|
||||
|
||||
/* Reset the counter registers */
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
|
||||
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -113,7 +301,7 @@ static int apci1564_auto_attach(struct comedi_device *dev,
|
|||
dev->irq = pcidev->irq;
|
||||
}
|
||||
|
||||
ret = comedi_alloc_subdevices(dev, 3);
|
||||
ret = comedi_alloc_subdevices(dev, 4);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -125,7 +313,6 @@ static int apci1564_auto_attach(struct comedi_device *dev,
|
|||
s->maxdata = 1;
|
||||
s->len_chanlist = 32;
|
||||
s->range_table = &range_digital;
|
||||
s->insn_config = apci1564_di_config;
|
||||
s->insn_bits = apci1564_di_insn_bits;
|
||||
|
||||
/* Allocate and Initialise DO Subdevice Structures */
|
||||
|
@ -152,6 +339,25 @@ static int apci1564_auto_attach(struct comedi_device *dev,
|
|||
s->insn_read = apci1564_timer_read;
|
||||
s->insn_config = apci1564_timer_config;
|
||||
|
||||
/* Change-Of-State (COS) interrupt subdevice */
|
||||
s = &dev->subdevices[3];
|
||||
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->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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue