[PATCH v2 07/13] staging: comedi: ni_mio_common: implement global pfi, rtsi routing

Spencer E. Olson olsonse at umich.edu
Thu Sep 27 19:27:12 UTC 2018


Implement device-global config interface for ni_mio devices.  In
particular, this patch implements:
INSN_DEVICE_CONFIG_TEST_ROUTE,
INSN_DEVICE_CONFIG_CONNECT_ROUTE,
INSN_DEVICE_CONFIG_DISCONNECT_ROUTE,
INSN_DEVICE_CONFIG_GET_ROUTES
for the ni mio devices.  This means that the new abstracted signal/terminal
names can be used to define signal routing with regards to the PFI
terminals and RTSI trigger bus lines.

This also adds ability to identify PFI and RTSI channels on the PFI and
RTSI subdevices using the new device-global names.  This does not change
the values that are set for channel output selections using the subdevice
interfaces--these still require direct register values.

Annotates and updates tables of register values to reflect this new
implementation status.

Signed-off-by: Spencer E. Olson <olsonse at umich.edu>
---

Notes:
  - [PATCH 07/13]: This patch must be built upon an earlier patch recently
    submitted and in the queue for integration:
    "staging: comedi: ni_mio_common: protect register write overflow"

 .../staging/comedi/drivers/ni_mio_common.c    | 687 ++++++++++++++++--
 drivers/staging/comedi/drivers/ni_stc.h       |  68 ++
 2 files changed, 683 insertions(+), 72 deletions(-)

diff --git a/drivers/staging/comedi/drivers/ni_mio_common.c b/drivers/staging/comedi/drivers/ni_mio_common.c
index b033f0be9b8a..c42d480df7ff 100644
--- a/drivers/staging/comedi/drivers/ni_mio_common.c
+++ b/drivers/staging/comedi/drivers/ni_mio_common.c
@@ -351,7 +351,8 @@ static const struct mio_regmap m_series_stc_write_regmap[] = {
 	[NISTC_AO_PERSONAL_REG]		= { 0x19c, 2 },
 	[NISTC_RTSI_TRIGA_OUT_REG]	= { 0x19e, 2 },
 	[NISTC_RTSI_TRIGB_OUT_REG]	= { 0x1a0, 2 },
-	[NISTC_RTSI_BOARD_REG]		= { 0, 0 }, /* Unknown */
+	/* doc for following line: mhddk/nimseries/ChipObjects/tMSeries.h */
+	[NISTC_RTSI_BOARD_REG]		= { 0x1a2, 2 },
 	[NISTC_CFG_MEM_CLR_REG]		= { 0x1a4, 2 },
 	[NISTC_ADC_FIFO_CLR_REG]	= { 0x1a6, 2 },
 	[NISTC_DAC_FIFO_CLR_REG]	= { 0x1a8, 2 },
@@ -4566,24 +4567,33 @@ static unsigned int ni_get_pfi_routing(struct comedi_device *dev,
 {
 	struct ni_private *devpriv = dev->private;
 
+	if (chan >= NI_PFI(0)) {
+		/* allow new and old names of pfi channels to work. */
+		chan -= NI_PFI(0);
+	}
 	return (devpriv->is_m_series)
 			? ni_m_series_get_pfi_routing(dev, chan)
 			: ni_old_get_pfi_routing(dev, chan);
 }
 
+/* Sets the output mux for the specified PFI channel. */
 static int ni_set_pfi_routing(struct comedi_device *dev,
 			      unsigned int chan, unsigned int source)
 {
 	struct ni_private *devpriv = dev->private;
 
+	if (chan >= NI_PFI(0)) {
+		/* allow new and old names of pfi channels to work. */
+		chan -= NI_PFI(0);
+	}
 	return (devpriv->is_m_series)
 			? ni_m_series_set_pfi_routing(dev, chan, source)
 			: ni_old_set_pfi_routing(dev, chan, source);
 }
 
-static int ni_config_filter(struct comedi_device *dev,
-			    unsigned int pfi_channel,
-			    enum ni_pfi_filter_select filter)
+static int ni_config_pfi_filter(struct comedi_device *dev,
+				unsigned int chan,
+				enum ni_pfi_filter_select filter)
 {
 	struct ni_private *devpriv = dev->private;
 	unsigned int bits;
@@ -4591,19 +4601,46 @@ static int ni_config_filter(struct comedi_device *dev,
 	if (!devpriv->is_m_series)
 		return -ENOTSUPP;
 
+	if (chan >= NI_PFI(0)) {
+		/* allow new and old names of pfi channels to work. */
+		chan -= NI_PFI(0);
+	}
+
 	bits = ni_readl(dev, NI_M_PFI_FILTER_REG);
-	bits &= ~NI_M_PFI_FILTER_SEL_MASK(pfi_channel);
-	bits |= NI_M_PFI_FILTER_SEL(pfi_channel, filter);
+	bits &= ~NI_M_PFI_FILTER_SEL_MASK(chan);
+	bits |= NI_M_PFI_FILTER_SEL(chan, filter);
 	ni_writel(dev, bits, NI_M_PFI_FILTER_REG);
 	return 0;
 }
 
+static void ni_set_pfi_direction(struct comedi_device *dev, int chan,
+				 unsigned int direction)
+{
+	if (chan >= NI_PFI(0)) {
+		/* allow new and old names of pfi channels to work. */
+		chan -= NI_PFI(0);
+	}
+	direction = (direction == COMEDI_OUTPUT) ? 1u : 0u;
+	ni_set_bits(dev, NISTC_IO_BIDIR_PIN_REG, 1 << chan, direction);
+}
+
+static int ni_get_pfi_direction(struct comedi_device *dev, int chan)
+{
+	struct ni_private *devpriv = dev->private;
+
+	if (chan >= NI_PFI(0)) {
+		/* allow new and old names of pfi channels to work. */
+		chan -= NI_PFI(0);
+	}
+	return devpriv->io_bidirection_pin_reg & (1 << chan) ?
+	       COMEDI_OUTPUT : COMEDI_INPUT;
+}
+
 static int ni_pfi_insn_config(struct comedi_device *dev,
 			      struct comedi_subdevice *s,
 			      struct comedi_insn *insn,
 			      unsigned int *data)
 {
-	struct ni_private *devpriv = dev->private;
 	unsigned int chan;
 
 	if (insn->n < 1)
@@ -4613,23 +4650,19 @@ static int ni_pfi_insn_config(struct comedi_device *dev,
 
 	switch (data[0]) {
 	case COMEDI_OUTPUT:
-		ni_set_bits(dev, NISTC_IO_BIDIR_PIN_REG, 1 << chan, 1);
-		break;
 	case COMEDI_INPUT:
-		ni_set_bits(dev, NISTC_IO_BIDIR_PIN_REG, 1 << chan, 0);
+		ni_set_pfi_direction(dev, chan, data[0]);
 		break;
 	case INSN_CONFIG_DIO_QUERY:
-		data[1] =
-		    (devpriv->io_bidirection_pin_reg & (1 << chan)) ?
-		    COMEDI_OUTPUT : COMEDI_INPUT;
-		return 0;
+		data[1] = ni_get_pfi_direction(dev, chan);
+		break;
 	case INSN_CONFIG_SET_ROUTING:
 		return ni_set_pfi_routing(dev, chan, data[1]);
 	case INSN_CONFIG_GET_ROUTING:
 		data[1] = ni_get_pfi_routing(dev, chan);
 		break;
 	case INSN_CONFIG_FILTER:
-		return ni_config_filter(dev, chan, data[1]);
+		return ni_config_pfi_filter(dev, chan, data[1]);
 	default:
 		return -EINVAL;
 	}
@@ -5012,6 +5045,10 @@ static int ni_set_rtsi_routing(struct comedi_device *dev,
 {
 	struct ni_private *devpriv = dev->private;
 
+	if (chan >= TRIGGER_LINE(0))
+		/* allow new and old names of rtsi channels to work. */
+		chan -= TRIGGER_LINE(0);
+
 	if (ni_valid_rtsi_output_source(dev, chan, src) == 0)
 		return -EINVAL;
 	if (chan < 4) {
@@ -5040,13 +5077,17 @@ static unsigned int ni_get_rtsi_routing(struct comedi_device *dev,
 {
 	struct ni_private *devpriv = dev->private;
 
+	if (chan >= TRIGGER_LINE(0))
+		/* allow new and old names of rtsi channels to work. */
+		chan -= TRIGGER_LINE(0);
+
 	if (chan < 4) {
 		return NISTC_RTSI_TRIG_TO_SRC(chan,
 					      devpriv->rtsi_trig_a_output_reg);
 	} else if (chan < NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series)) {
 		return NISTC_RTSI_TRIG_TO_SRC(chan,
 					      devpriv->rtsi_trig_b_output_reg);
-	} else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN)
+	} else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) {
 		return NI_RTSI_OUTPUT_RTSI_OSC;
 	}
 
@@ -5054,17 +5095,17 @@ static unsigned int ni_get_rtsi_routing(struct comedi_device *dev,
 	return -EINVAL;
 }
 
-static int ni_rtsi_insn_config(struct comedi_device *dev,
-			       struct comedi_subdevice *s,
-			       struct comedi_insn *insn,
-			       unsigned int *data)
+static void ni_set_rtsi_direction(struct comedi_device *dev, int chan,
+				  unsigned int direction)
 {
 	struct ni_private *devpriv = dev->private;
-	unsigned int chan = CR_CHAN(insn->chanspec);
 	unsigned int max_chan = NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series);
 
-	switch (data[0]) {
-	case INSN_CONFIG_DIO_OUTPUT:
+	if (chan >= TRIGGER_LINE(0))
+		/* allow new and old names of rtsi channels to work. */
+		chan -= TRIGGER_LINE(0);
+
+	if (direction == COMEDI_OUTPUT) {
 		if (chan < max_chan) {
 			devpriv->rtsi_trig_direction_reg |=
 			    NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series);
@@ -5072,10 +5113,7 @@ static int ni_rtsi_insn_config(struct comedi_device *dev,
 			devpriv->rtsi_trig_direction_reg |=
 			    NISTC_RTSI_TRIG_DRV_CLK;
 		}
-		ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
-			      NISTC_RTSI_TRIG_DIR_REG);
-		break;
-	case INSN_CONFIG_DIO_INPUT:
+	} else {
 		if (chan < max_chan) {
 			devpriv->rtsi_trig_direction_reg &=
 			    ~NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series);
@@ -5083,23 +5121,53 @@ static int ni_rtsi_insn_config(struct comedi_device *dev,
 			devpriv->rtsi_trig_direction_reg &=
 			    ~NISTC_RTSI_TRIG_DRV_CLK;
 		}
-		ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
-			      NISTC_RTSI_TRIG_DIR_REG);
+	}
+	ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
+		      NISTC_RTSI_TRIG_DIR_REG);
+}
+
+static int ni_get_rtsi_direction(struct comedi_device *dev, int chan)
+{
+	struct ni_private *devpriv = dev->private;
+	unsigned int max_chan = NISTC_RTSI_TRIG_NUM_CHAN(devpriv->is_m_series);
+
+	if (chan >= TRIGGER_LINE(0))
+		/* allow new and old names of rtsi channels to work. */
+		chan -= TRIGGER_LINE(0);
+
+	if (chan < max_chan) {
+		return (devpriv->rtsi_trig_direction_reg &
+			NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series))
+			   ? COMEDI_OUTPUT : COMEDI_INPUT;
+	} else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) {
+		return (devpriv->rtsi_trig_direction_reg &
+			NISTC_RTSI_TRIG_DRV_CLK)
+			   ? COMEDI_OUTPUT : COMEDI_INPUT;
+	}
+	return -EINVAL;
+}
+
+static int ni_rtsi_insn_config(struct comedi_device *dev,
+			       struct comedi_subdevice *s,
+			       struct comedi_insn *insn,
+			       unsigned int *data)
+{
+	struct ni_private *devpriv = dev->private;
+	unsigned int chan = CR_CHAN(insn->chanspec);
+
+	switch (data[0]) {
+	case COMEDI_OUTPUT:
+	case COMEDI_INPUT:
+		ni_set_rtsi_direction(dev, chan, data[0]);
 		break;
-	case INSN_CONFIG_DIO_QUERY:
-		if (chan < max_chan) {
-			data[1] =
-			    (devpriv->rtsi_trig_direction_reg &
-			     NISTC_RTSI_TRIG_DIR(chan, devpriv->is_m_series))
-				? INSN_CONFIG_DIO_OUTPUT
-				: INSN_CONFIG_DIO_INPUT;
-		} else if (chan == NISTC_RTSI_TRIG_OLD_CLK_CHAN) {
-			data[1] = (devpriv->rtsi_trig_direction_reg &
-				   NISTC_RTSI_TRIG_DRV_CLK)
-				  ? INSN_CONFIG_DIO_OUTPUT
-				  : INSN_CONFIG_DIO_INPUT;
-		}
+	case INSN_CONFIG_DIO_QUERY: {
+		int ret = ni_get_rtsi_direction(dev, chan);
+
+		if (ret < 0)
+			return ret;
+		data[1] = ret;
 		return 2;
+	}
 	case INSN_CONFIG_SET_CLOCK_SRC:
 		return ni_set_master_clock(dev, data[1], data[2]);
 	case INSN_CONFIG_GET_CLOCK_SRC:
@@ -5108,9 +5176,14 @@ static int ni_rtsi_insn_config(struct comedi_device *dev,
 		return 3;
 	case INSN_CONFIG_SET_ROUTING:
 		return ni_set_rtsi_routing(dev, chan, data[1]);
-	case INSN_CONFIG_GET_ROUTING:
-		data[1] = ni_get_rtsi_routing(dev, chan);
+	case INSN_CONFIG_GET_ROUTING: {
+		int ret = ni_get_rtsi_routing(dev, chan);
+
+		if (ret < 0)
+			return ret;
+		data[1] = ret;
 		return 2;
+	}
 	default:
 		return -EINVAL;
 	}
@@ -5127,9 +5200,275 @@ static int ni_rtsi_insn_bits(struct comedi_device *dev,
 	return insn->n;
 }
 
+/*
+ * Default routing for RTSI trigger lines.
+ *
+ * These values are used here in the init function, as well as in the
+ * disconnect_route function, after a RTSI route has been disconnected.
+ */
+static const int default_rtsi_routing[] = {
+	[0] = NI_RTSI_OUTPUT_ADR_START1,
+	[1] = NI_RTSI_OUTPUT_ADR_START2,
+	[2] = NI_RTSI_OUTPUT_SCLKG,
+	[3] = NI_RTSI_OUTPUT_DACUPDN,
+	[4] = NI_RTSI_OUTPUT_DA_START1,
+	[5] = NI_RTSI_OUTPUT_G_SRC0,
+	[6] = NI_RTSI_OUTPUT_G_GATE0,
+	[7] = NI_RTSI_OUTPUT_RTSI_OSC,
+};
+
+/*
+ * Route signals through RGOUT0 terminal.
+ * @reg: raw register value of RGOUT0 bits (only bit0 is important).
+ * @dev: comedi device handle.
+ */
+static void set_rgout0_reg(int reg, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+
+	if (devpriv->is_m_series) {
+		devpriv->rtsi_trig_direction_reg &=
+			~NISTC_RTSI_TRIG_DIR_SUB_SEL1;
+		devpriv->rtsi_trig_direction_reg |=
+			(reg << NISTC_RTSI_TRIG_DIR_SUB_SEL1_SHIFT) &
+			NISTC_RTSI_TRIG_DIR_SUB_SEL1;
+		ni_stc_writew(dev, devpriv->rtsi_trig_direction_reg,
+			      NISTC_RTSI_TRIG_DIR_REG);
+	} else {
+		devpriv->rtsi_trig_b_output_reg &= ~NISTC_RTSI_TRIGB_SUB_SEL1;
+		devpriv->rtsi_trig_b_output_reg |=
+			(reg << NISTC_RTSI_TRIGB_SUB_SEL1_SHIFT) &
+			NISTC_RTSI_TRIGB_SUB_SEL1;
+		ni_stc_writew(dev, devpriv->rtsi_trig_b_output_reg,
+			      NISTC_RTSI_TRIGB_OUT_REG);
+	}
+}
+
+static int get_rgout0_reg(struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int reg;
+
+	if (devpriv->is_m_series)
+		reg = (devpriv->rtsi_trig_direction_reg &
+		       NISTC_RTSI_TRIG_DIR_SUB_SEL1)
+		    >> NISTC_RTSI_TRIG_DIR_SUB_SEL1_SHIFT;
+	else
+		reg = (devpriv->rtsi_trig_b_output_reg &
+		       NISTC_RTSI_TRIGB_SUB_SEL1)
+		    >> NISTC_RTSI_TRIGB_SUB_SEL1_SHIFT;
+	return reg;
+}
+
+static inline int get_rgout0_src(struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int reg = get_rgout0_reg(dev);
+
+	return ni_find_route_source(reg, NI_RGOUT0, &devpriv->routing_tables);
+}
+
+/*
+ * Route signals through RGOUT0 terminal and increment the RGOUT0 use for this
+ * particular route.
+ * @src: device-global signal name
+ * @dev: comedi device handle
+ *
+ * Return: -EINVAL if the source is not valid to route to RGOUT0;
+ *	   -EBUSY if the RGOUT0 is already used;
+ *	   0 if successful.
+ */
+static int incr_rgout0_src_use(int src, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	s8 reg = ni_lookup_route_register(CR_CHAN(src), NI_RGOUT0,
+					  &devpriv->routing_tables);
+
+	if (reg < 0)
+		return -EINVAL;
+
+	if (devpriv->rgout0_usage > 0 && get_rgout0_reg(dev) != reg)
+		return -EBUSY;
+
+	++devpriv->rgout0_usage;
+	set_rgout0_reg(reg, dev);
+	return 0;
+}
+
+/*
+ * Unroute signals through RGOUT0 terminal and deccrement the RGOUT0 use for
+ * this particular source.  This function does not actually unroute anything
+ * with respect to RGOUT0.  It does, on the other hand, decrement the usage
+ * counter for the current src->RGOUT0 mapping.
+ *
+ * Return: -EINVAL if the source is not already routed to RGOUT0 (or usage is
+ *	already at zero); 0 if successful.
+ */
+static int decr_rgout0_src_use(int src, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	s8 reg = ni_lookup_route_register(CR_CHAN(src), NI_RGOUT0,
+					  &devpriv->routing_tables);
+
+	if (devpriv->rgout0_usage > 0 && get_rgout0_reg(dev) == reg) {
+		--devpriv->rgout0_usage;
+		if (!devpriv->rgout0_usage)
+			set_rgout0_reg(0, dev); /* ok default? */
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/*
+ * Route signals through given NI_RTSI_BRD mux.
+ * @i: index of mux to route
+ * @reg: raw register value of RTSI_BRD bits
+ * @dev: comedi device handle
+ */
+static void set_ith_rtsi_brd_reg(int i, int reg, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int reg_i_sz = 3; /* value for e-series */
+	int reg_i_mask;
+	int reg_i_shift;
+
+	if (devpriv->is_m_series)
+		reg_i_sz = 4;
+	reg_i_mask = ~((~0) << reg_i_sz);
+	reg_i_shift = i * reg_i_sz;
+
+	/* clear out the current reg_i for ith brd */
+	devpriv->rtsi_shared_mux_reg &= ~(reg_i_mask       << reg_i_shift);
+	/* (softcopy) write the new reg_i for ith brd */
+	devpriv->rtsi_shared_mux_reg |= (reg & reg_i_mask) << reg_i_shift;
+	/* (hardcopy) write the new reg_i for ith brd */
+	ni_stc_writew(dev, devpriv->rtsi_shared_mux_reg, NISTC_RTSI_BOARD_REG);
+}
+
+static int get_ith_rtsi_brd_reg(int i, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int reg_i_sz = 3; /* value for e-series */
+	int reg_i_mask;
+	int reg_i_shift;
+
+	if (devpriv->is_m_series)
+		reg_i_sz = 4;
+	reg_i_mask = ~((~0) << reg_i_sz);
+	reg_i_shift = i * reg_i_sz;
+
+	return (devpriv->rtsi_shared_mux_reg >> reg_i_shift) & reg_i_mask;
+}
+
+static inline int get_rtsi_brd_src(int brd, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int brd_index = brd;
+	int reg;
+
+	if (brd >= NI_RTSI_BRD(0))
+		brd_index = brd - NI_RTSI_BRD(0);
+	else
+		brd = NI_RTSI_BRD(brd);
+	/*
+	 * And now:
+	 * brd : device-global name
+	 * brd_index : index number of RTSI_BRD mux
+	 */
+
+	reg = get_ith_rtsi_brd_reg(brd_index, dev);
+
+	return ni_find_route_source(reg, brd, &devpriv->routing_tables);
+}
+
+/*
+ * Route signals through NI_RTSI_BRD mux and increment the use counter for this
+ * particular route.
+ *
+ * Return: -EINVAL if the source is not valid to route to NI_RTSI_BRD(i);
+ *	   -EBUSY if all NI_RTSI_BRD muxes are already used;
+ *	   NI_RTSI_BRD(i) of allocated ith mux if successful.
+ */
+static int incr_rtsi_brd_src_use(int src, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int first_available = -1;
+	int err = -EINVAL;
+	s8 reg;
+	int i;
+
+	/* first look for a mux that is already configured to provide src */
+	for (i = 0; i < NUM_RTSI_SHARED_MUXS; ++i) {
+		reg = ni_lookup_route_register(CR_CHAN(src), NI_RTSI_BRD(i),
+					       &devpriv->routing_tables);
+
+		if (reg < 0)
+			continue; /* invalid route */
+
+		if (!devpriv->rtsi_shared_mux_usage[i]) {
+			if (first_available < 0)
+				/* found the first unused, but usable mux */
+				first_available = i;
+		} else {
+			/*
+			 * we've seen at least one possible route, so change the
+			 * final error to -EBUSY in case there are no muxes
+			 * available.
+			 */
+			err = -EBUSY;
+
+			if (get_ith_rtsi_brd_reg(i, dev) == reg) {
+				/*
+				 * we've found a mux that is already being used
+				 * to provide the requested signal.  Reuse it.
+				 */
+				goto success;
+			}
+		}
+	}
+
+	if (first_available < 0)
+		return err;
+
+	/* we did not find a mux to reuse, but there is at least one usable */
+	i = first_available;
+
+success:
+	++devpriv->rtsi_shared_mux_usage[i];
+	set_ith_rtsi_brd_reg(i, reg, dev);
+	return NI_RTSI_BRD(i);
+}
+
+/*
+ * Unroute signals through NI_RTSI_BRD mux and decrement the user counter for
+ * this particular route.
+ *
+ * Return: -EINVAL if the source is not already routed to rtsi_brd(i) (or usage
+ *	is already at zero); 0 if successful.
+ */
+static int decr_rtsi_brd_src_use(int src, int rtsi_brd,
+				 struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	s8 reg = ni_lookup_route_register(CR_CHAN(src), rtsi_brd,
+					  &devpriv->routing_tables);
+	const int i = rtsi_brd - NI_RTSI_BRD(0);
+
+	if (devpriv->rtsi_shared_mux_usage[i] > 0 &&
+	    get_ith_rtsi_brd_reg(i, dev) == reg) {
+		--devpriv->rtsi_shared_mux_usage[i];
+		if (!devpriv->rtsi_shared_mux_usage[i])
+			set_ith_rtsi_brd_reg(i, 0, dev); /* ok default? */
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
 static void ni_rtsi_init(struct comedi_device *dev)
 {
 	struct ni_private *devpriv = dev->private;
+	int i;
 
 	/*  Initialises the RTSI bus signal switch to a default state */
 
@@ -5142,28 +5481,215 @@ static void ni_rtsi_init(struct comedi_device *dev)
 	/*  Set clock mode to internal */
 	if (ni_set_master_clock(dev, NI_MIO_INTERNAL_CLOCK, 0) < 0)
 		dev_err(dev->class_dev, "ni_set_master_clock failed, bug?\n");
-	/*  default internal lines routing to RTSI bus lines */
-	devpriv->rtsi_trig_a_output_reg =
-	    NISTC_RTSI_TRIG(0, NI_RTSI_OUTPUT_ADR_START1) |
-	    NISTC_RTSI_TRIG(1, NI_RTSI_OUTPUT_ADR_START2) |
-	    NISTC_RTSI_TRIG(2, NI_RTSI_OUTPUT_SCLKG) |
-	    NISTC_RTSI_TRIG(3, NI_RTSI_OUTPUT_DACUPDN);
-	ni_stc_writew(dev, devpriv->rtsi_trig_a_output_reg,
-		      NISTC_RTSI_TRIGA_OUT_REG);
-	devpriv->rtsi_trig_b_output_reg =
-	    NISTC_RTSI_TRIG(4, NI_RTSI_OUTPUT_DA_START1) |
-	    NISTC_RTSI_TRIG(5, NI_RTSI_OUTPUT_G_SRC0) |
-	    NISTC_RTSI_TRIG(6, NI_RTSI_OUTPUT_G_GATE0);
-	if (devpriv->is_m_series)
-		devpriv->rtsi_trig_b_output_reg |=
-		    NISTC_RTSI_TRIG(7, NI_RTSI_OUTPUT_RTSI_OSC);
-	ni_stc_writew(dev, devpriv->rtsi_trig_b_output_reg,
-		      NISTC_RTSI_TRIGB_OUT_REG);
+
+	/* default internal lines routing to RTSI bus lines */
+	for (i = 0; i < 8; ++i) {
+		ni_set_rtsi_direction(dev, i, COMEDI_INPUT);
+		ni_set_rtsi_routing(dev, i, default_rtsi_routing[i]);
+	}
 
 	/*
-	 * Sets the source and direction of the 4 on board lines
-	 * ni_stc_writew(dev, 0, NISTC_RTSI_BOARD_REG);
+	 * Sets the source and direction of the 4 on board lines.
+	 * This configures all board lines to be:
+	 * for e-series:
+	 *   1) inputs (not sure what "output" would mean)
+	 *   2) copying TRIGGER_LINE(0) (or RTSI0) output
+	 * for m-series:
+	 *   copying NI_PFI(0) output
 	 */
+	devpriv->rtsi_shared_mux_reg = 0;
+	for (i = 0; i < 4; ++i)
+		set_ith_rtsi_brd_reg(i, 0, dev);
+	memset(devpriv->rtsi_shared_mux_usage, 0,
+	       sizeof(devpriv->rtsi_shared_mux_usage));
+
+	/* initialize rgout0 pin as unused. */
+	devpriv->rgout0_usage = 0;
+	set_rgout0_reg(0, dev);
+}
+
+/*
+ * Retrieves the current source of the output selector for the given
+ * destination.  If the terminal for the destination is not already configured
+ * as an output, this function returns -EINVAL as error.
+ *
+ * Return: the register value of the destination output selector;
+ *	   -EINVAL if terminal is not configured for output.
+ */
+static int get_output_select_source(int dest, struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	int reg = -1;
+
+	if (channel_is_pfi(dest)) {
+		if (ni_get_pfi_direction(dev, dest) == COMEDI_OUTPUT)
+			reg = ni_get_pfi_routing(dev, dest);
+	} else if (channel_is_rtsi(dest)) {
+		if (ni_get_rtsi_direction(dev, dest) == COMEDI_OUTPUT) {
+			reg = ni_get_rtsi_routing(dev, dest);
+
+			if (reg == NI_RTSI_OUTPUT_RGOUT0) {
+				dest = NI_RGOUT0; /* prepare for lookup below */
+				reg = get_rgout0_reg(dev);
+			} else if (reg >= NI_RTSI_OUTPUT_RTSI_BRD(0) &&
+				   reg <= NI_RTSI_OUTPUT_RTSI_BRD(3)) {
+				const int i = reg - NI_RTSI_OUTPUT_RTSI_BRD(0);
+
+				dest = NI_RTSI_BRD(i); /* prepare for lookup */
+				reg = get_ith_rtsi_brd_reg(i, dev);
+			}
+		}
+	} else {
+		dev_dbg(dev->class_dev, "%s: unhandled destination (%d) queried\n",
+			__func__, dest);
+	}
+
+	if (reg >= 0)
+		return ni_find_route_source(CR_CHAN(reg), dest,
+					    &devpriv->routing_tables);
+	return -EINVAL;
+}
+
+/*
+ * Test a route:
+ *
+ * Return: -1 if not connectible;
+ *	    0 if connectible and not connected;
+ *	    1 if connectible and connected.
+ */
+static int test_route(unsigned int src, unsigned int dest,
+		      struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	s8 reg = ni_route_to_register(CR_CHAN(src), dest,
+				      &devpriv->routing_tables);
+
+	if (reg < 0)
+		return -1;
+	if (get_output_select_source(dest, dev) != CR_CHAN(src))
+		return 0;
+	return 1;
+}
+
+/* Connect the actual route.  */
+static int connect_route(unsigned int src, unsigned int dest,
+			 struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	s8 reg = ni_route_to_register(CR_CHAN(src), dest,
+				      &devpriv->routing_tables);
+	s8 current_src;
+
+	if (reg < 0)
+		/* route is not valid */
+		return -EINVAL;
+
+	current_src = get_output_select_source(dest, dev);
+	if (current_src == CR_CHAN(src))
+		return -EALREADY;
+	if (current_src >= 0)
+		/* destination mux is already busy. complain, don't overwrite */
+		return -EBUSY;
+
+	/* The route is valid and available. Now connect... */
+	if (channel_is_pfi(dest)) {
+		/* set routing source, then open output */
+		ni_set_pfi_routing(dev, dest, reg);
+		ni_set_pfi_direction(dev, dest, COMEDI_OUTPUT);
+	} else if (channel_is_rtsi(dest)) {
+		if (reg == NI_RTSI_OUTPUT_RGOUT0) {
+			int ret = incr_rgout0_src_use(src, dev);
+
+			if (ret < 0)
+				return ret;
+		} else if (ni_rtsi_route_requires_mux(reg)) {
+			/* Attempt to allocate and  route (src->brd) */
+			int brd = incr_rtsi_brd_src_use(src, dev);
+
+			if (brd < 0)
+				return brd;
+
+			/* Now lookup the register value for (brd->dest) */
+			reg = ni_lookup_route_register(
+				brd, dest, &devpriv->routing_tables);
+		}
+
+		ni_set_rtsi_direction(dev, dest, COMEDI_OUTPUT);
+		ni_set_rtsi_routing(dev, dest, reg);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int disconnect_route(unsigned int src, unsigned int dest,
+			    struct comedi_device *dev)
+{
+	struct ni_private *devpriv = dev->private;
+	s8 reg = ni_route_to_register(CR_CHAN(src), dest,
+				      &devpriv->routing_tables);
+
+	if (reg < 0)
+		/* route is not valid */
+		return -EINVAL;
+	if (get_output_select_source(dest, dev) != src)
+		/* cannot disconnect something not connected */
+		return -EINVAL;
+
+	/* The route is valid and is connected.  Now disconnect... */
+	if (channel_is_pfi(dest)) {
+		/* set the pfi to high impedance, and disconnect */
+		ni_set_pfi_direction(dev, dest, COMEDI_INPUT);
+		ni_set_pfi_routing(dev, dest, NI_PFI_OUTPUT_PFI_DEFAULT);
+	} else if (channel_is_rtsi(dest)) {
+		if (reg == NI_RTSI_OUTPUT_RGOUT0) {
+			int ret = decr_rgout0_src_use(src, dev);
+
+			if (ret < 0)
+				return ret;
+		} else if (ni_rtsi_route_requires_mux(reg)) {
+			/* find which RTSI_BRD line is source for rtsi pin */
+			int brd = ni_find_route_source(
+				ni_get_rtsi_routing(dev, dest), dest,
+				&devpriv->routing_tables);
+
+			if (brd < 0)
+				return brd;
+
+			/* decrement/disconnect RTSI_BRD line from source */
+			decr_rtsi_brd_src_use(src, brd, dev);
+		}
+
+		/* set rtsi output selector to default state */
+		reg = default_rtsi_routing[dest - TRIGGER_LINE(0)];
+		ni_set_rtsi_direction(dev, dest, COMEDI_INPUT);
+		ni_set_rtsi_routing(dev, dest, reg);
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int ni_global_insn_config(struct comedi_device *dev,
+				 struct comedi_insn *insn,
+				 unsigned int *data)
+{
+	switch (data[0]) {
+	case INSN_DEVICE_CONFIG_TEST_ROUTE:
+		data[0] = test_route(data[1], data[2], dev);
+		return 2;
+	case INSN_DEVICE_CONFIG_CONNECT_ROUTE:
+		return connect_route(data[1], data[2], dev);
+	case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE:
+		return disconnect_route(data[1], data[2], dev);
+	/*
+	 * This case is already handled one level up.
+	 * case INSN_DEVICE_CONFIG_GET_ROUTES:
+	 */
+	default:
+		return -EINVAL;
+	}
+	return 1;
 }
 
 #ifdef PCIDMA
@@ -5269,6 +5795,16 @@ static int ni_alloc_private(struct comedi_device *dev)
 	return 0;
 }
 
+static unsigned int _ni_get_valid_routes(struct comedi_device *dev,
+					 unsigned int n_pairs,
+					 unsigned int *pair_data)
+{
+	struct ni_private *devpriv = dev->private;
+
+	return ni_get_valid_routes(&devpriv->routing_tables, n_pairs,
+				   pair_data);
+}
+
 static int ni_E_init(struct comedi_device *dev,
 		     unsigned int interrupt_pin, unsigned int irq_polarity)
 {
@@ -5280,6 +5816,22 @@ static int ni_E_init(struct comedi_device *dev,
 	const char *dev_family = devpriv->is_m_series ? "ni_mseries"
 						      : "ni_eseries";
 
+	/* prepare the device for globally-named routes. */
+	if (ni_assign_device_routes(dev_family, board->name,
+				    &devpriv->routing_tables) < 0) {
+		dev_warn(dev->class_dev, "%s: %s device has no signal routing table.\n",
+			 __func__, board->name);
+		dev_warn(dev->class_dev, "%s: High level NI signal names will not be available for this %s board.\n",
+			 __func__, board->name);
+	} else {
+		/*
+		 * only(?) assign insn_device_config if we have global names for
+		 * this device.
+		 */
+		dev->insn_device_config = ni_global_insn_config;
+		dev->get_valid_routes = _ni_get_valid_routes;
+	}
+
 	if (board->n_aochan > MAX_N_AO_CHAN) {
 		dev_err(dev->class_dev, "bug! n_aochan > MAX_N_AO_CHAN\n");
 		return -EINVAL;
@@ -5610,15 +6162,6 @@ static int ni_E_init(struct comedi_device *dev,
 		ni_writeb(dev, 0x0, NI_M_AO_CALIB_REG);
 	}
 
-	/* prepare the device for globally-named routes. */
-	if (ni_assign_device_routes(dev_family, board->name,
-				    &devpriv->routing_tables) < 0) {
-		dev_warn(dev->class_dev, "%s: %s device has no signal routing table.\n",
-			 __func__, board->name);
-		dev_warn(dev->class_dev, "%s: High level NI signal names will not be available for this %s board.\n",
-			 __func__, board->name);
-	}
-
 	return 0;
 }
 
diff --git a/drivers/staging/comedi/drivers/ni_stc.h b/drivers/staging/comedi/drivers/ni_stc.h
index d6b5120dd5fa..44aa83b1c352 100644
--- a/drivers/staging/comedi/drivers/ni_stc.h
+++ b/drivers/staging/comedi/drivers/ni_stc.h
@@ -254,6 +254,8 @@
 #define NISTC_RTSI_TRIG_OLD_CLK_CHAN	7
 #define NISTC_RTSI_TRIG_NUM_CHAN(_m)	((_m) ? 8 : 7)
 #define NISTC_RTSI_TRIG_DIR(_c, _m)	((_m) ? BIT(8 + (_c)) : BIT(7 + (_c)))
+#define NISTC_RTSI_TRIG_DIR_SUB_SEL1	BIT(2)	/* only for M-Series */
+#define NISTC_RTSI_TRIG_DIR_SUB_SEL1_SHIFT	2	/* only for M-Series */
 #define NISTC_RTSI_TRIG_USE_CLK		BIT(1)
 #define NISTC_RTSI_TRIG_DRV_CLK		BIT(0)
 
@@ -423,6 +425,7 @@
 #define NISTC_RTSI_TRIGA_OUT_REG	79
 #define NISTC_RTSI_TRIGB_OUT_REG	80
 #define NISTC_RTSI_TRIGB_SUB_SEL1	BIT(15)	/* not for M-Series */
+#define NISTC_RTSI_TRIGB_SUB_SEL1_SHIFT	15	/* not for M-Series */
 #define NISTC_RTSI_TRIG(_c, _s)		(((_s) & 0xf) << (((_c) % 4) * 4))
 #define NISTC_RTSI_TRIG_MASK(_c)	NISTC_RTSI_TRIG((_c), 0xf)
 #define NISTC_RTSI_TRIG_TO_SRC(_c, _b)	(((_b) >> (((_c) % 4) * 4)) & 0xf)
@@ -963,6 +966,7 @@ struct ni_board_struct {
 #define NUM_GPCT			2
 
 #define NUM_PFI_OUTPUT_SELECT_REGS	6
+#define NUM_RTSI_SHARED_MUXS		(NI_RTSI_BRD(-1) - NI_RTSI_BRD(0) + 1)
 
 #define M_SERIES_EEPROM_SIZE		1024
 
@@ -1061,6 +1065,70 @@ struct ni_private {
 
 	/* device signal route tables */
 	struct ni_route_tables routing_tables;
+
+	/*
+	 * Number of clients (RTSI lines) for current RTSI MUX source.
+	 *
+	 * This allows resource management of RTSI board/shared mux lines by
+	 * marking the RTSI line that is using a particular MUX.  Currently,
+	 * these lines are only automatically allocated based on source of the
+	 * route requested.  Furthermore, the only way that this auto-allocation
+	 * and configuration works is via the globally-named ni signal/terminal
+	 * names.
+	 */
+	u8 rtsi_shared_mux_usage[NUM_RTSI_SHARED_MUXS];
+
+	/*
+	 * softcopy register for rtsi shared mux/board lines.
+	 * For e-series, the bit layout of this register is
+	 * (docs: mhddk/nieseries/ChipObjects/tSTC.{h,ipp},
+	 *        DAQ-STC, Jan 1999, 340934B-01):
+	 *   bits 0:2  --  NI_RTSI_BRD(0) source selection
+	 *   bits 3:5  --  NI_RTSI_BRD(1) source selection
+	 *   bits 6:8  --  NI_RTSI_BRD(2) source selection
+	 *   bits 9:11 --  NI_RTSI_BRD(3) source selection
+	 *   bit  12   --  NI_RTSI_BRD(0) direction, 0:input, 1:output
+	 *   bit  13   --  NI_RTSI_BRD(1) direction, 0:input, 1:output
+	 *   bit  14   --  NI_RTSI_BRD(2) direction, 0:input, 1:output
+	 *   bit  15   --  NI_RTSI_BRD(3) direction, 0:input, 1:output
+	 *   According to DAQ-STC:
+	 *     RTSI Board Interface--Configured as an input, each bidirectional
+	 *     RTSI_BRD pin can drive any of the seven RTSI_TRIGGER pins.
+	 *     RTSI_BRD<0..1> can also be driven by AI STOP and RTSI_BRD<2..3>
+	 *     can also be driven by the AI START and SCAN_IN_PROG signals.
+	 *     These pins provide a mechanism for additional board-level signals
+	 *     to be sent on or received from the RTSI bus.
+	 *   Couple of comments:
+	 *   - Neither the DAQ-STC nor the MHDDK is clear on what the direction
+	 *     of the RTSI_BRD pins actually means.  There does not appear to be
+	 *     any clear indication on what "output" would mean, since the point
+	 *     of the RTSI_BRD lines is to always drive one of the
+	 *     RTSI_TRIGGER<0..6> lines.
+	 *   - The DAQ-STC also indicates that the NI_RTSI_BRD lines can be
+	 *     driven by any of the RTSI_TRIGGER<0..6> lines.
+	 *     But, looking at valid device routes, as visually imported from
+	 *     NI-MAX, there appears to be only one family (so far) that has the
+	 *     ability to route a signal from one TRIGGER_LINE to another
+	 *     TRIGGER_LINE: the 653x family of DIO devices.
+	 *
+	 * For m-series, the bit layout of this register is
+	 * (docs: mhddk/nimseries/ChipObjects/tMSeries.{h,ipp}):
+	 *   bits  0:3  --  NI_RTSI_BRD(0) source selection
+	 *   bits  4:7  --  NI_RTSI_BRD(1) source selection
+	 *   bits  8:11 --  NI_RTSI_BRD(2) source selection
+	 *   bits 12:15 --  NI_RTSI_BRD(3) source selection
+	 *   Note:  The m-series does not have any option to change direction of
+	 *   NI_RTSI_BRD muxes.  Furthermore, there are no register values that
+	 *   indicate the ability to have TRIGGER_LINES driving the output of
+	 *   the NI_RTSI_BRD muxes.
+	 */
+	u16 rtsi_shared_mux_reg;
+
+	/*
+	 * Number of clients (RTSI lines) for current RGOUT0 path.
+	 * Stored in part of in RTSI_TRIG_DIR or RTSI_TRIGB registers
+	 */
+	u8 rgout0_usage;
 };
 
 static const struct comedi_lrange range_ni_E_ao_ext;
-- 
2.17.1



More information about the devel mailing list