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
					
				| @ -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 New Issue
	
	Block a user