diff --git a/drivers/staging/comedi/drivers/ni_mio_common.c b/drivers/staging/comedi/drivers/ni_mio_common.c index 31a567d593c9..d4c0fd07059b 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 }, @@ -4590,24 +4591,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; @@ -4615,19 +4625,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) @@ -4637,23 +4674,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; } @@ -5036,6 +5069,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) { @@ -5064,6 +5101,10 @@ 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); @@ -5078,17 +5119,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); @@ -5096,10 +5137,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); @@ -5107,23 +5145,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: @@ -5132,9 +5200,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; } @@ -5151,9 +5224,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 */ @@ -5166,28 +5505,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 @@ -5293,6 +5819,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) { @@ -5304,6 +5840,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; @@ -5634,15 +6186,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 771eaef3815d..1867b64e23cf 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) @@ -964,6 +967,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 @@ -1062,6 +1066,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;