// SPDX-License-Identifier: GPL-2.0+ /* * Support for NI general purpose counters * * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
*/
/* * Module: ni_tio * Description: National Instruments general purpose counters * Author: J.P. Mellor <jpmellor@rose-hulman.edu>, * Herman.Bruyninckx@mech.kuleuven.ac.be, * Wim.Meeussen@mech.kuleuven.ac.be, * Klaas.Gadeyne@mech.kuleuven.ac.be, * Frank Mori Hess <fmhess@users.sourceforge.net> * Updated: Thu Nov 16 09:50:32 EST 2006 * Status: works * * This module is not used directly by end-users. Rather, it * is used by other drivers (for example ni_660x and ni_pcimio) * to provide support for NI's general purpose counters. It was * originally based on the counter code from ni_660x.c and * ni_mio_common.c. * * References: * DAQ 660x Register-Level Programmer Manual (NI 370505A-01) * DAQ 6601/6602 User Manual (NI 322137B-01) * 340934b.pdf DAQ-STC reference manual * * TODO: Support use of both banks X and Y
*/
staticinlineunsignedint GI_PRESCALE_X2(enum ni_gpct_variant variant)
{ switch (variant) { case ni_gpct_variant_e_series: default: return 0; case ni_gpct_variant_m_series: return GI_M_PRESCALE_X2; case ni_gpct_variant_660x: return GI_660X_PRESCALE_X2;
}
}
staticinlineunsignedint GI_PRESCALE_X8(enum ni_gpct_variant variant)
{ switch (variant) { case ni_gpct_variant_e_series: default: return 0; case ni_gpct_variant_m_series: return GI_M_PRESCALE_X8; case ni_gpct_variant_660x: return GI_660X_PRESCALE_X8;
}
}
staticbool ni_tio_has_gate2_registers(conststruct ni_gpct_device *counter_dev)
{ switch (counter_dev->variant) { case ni_gpct_variant_e_series: default: returnfalse; case ni_gpct_variant_m_series: case ni_gpct_variant_660x: returntrue;
}
}
/** * ni_tio_write() - Write a TIO register using the driver provided callback. * @counter: struct ni_gpct counter. * @value: the value to write * @reg: the register to write.
*/ void ni_tio_write(struct ni_gpct *counter, unsignedint value, enum ni_gpct_register reg)
{ if (reg < NITIO_NUM_REGS)
counter->counter_dev->write(counter, value, reg);
}
EXPORT_SYMBOL_GPL(ni_tio_write);
/** * ni_tio_read() - Read a TIO register using the driver provided callback. * @counter: struct ni_gpct counter. * @reg: the register to read.
*/ unsignedint ni_tio_read(struct ni_gpct *counter, enum ni_gpct_register reg)
{ if (reg < NITIO_NUM_REGS) return counter->counter_dev->read(counter, reg); return 0;
}
EXPORT_SYMBOL_GPL(ni_tio_read);
switch (generic_clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK) { case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS:
clock_period_ps = 50000; break; case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS:
clock_period_ps = 10000000; break; case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS:
clock_period_ps = 12500; break; case NI_GPCT_PXI10_CLOCK_SRC_BITS:
clock_period_ps = 100000; break; default: /* * clock period is specified by user with prescaling * already taken into account.
*/
*period_ps = counter->clock_period_ps; return 0;
}
switch (generic_clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK) { case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS: break; case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS:
clock_period_ps *= 2; break; case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS:
clock_period_ps *= 8; break; default: return -EINVAL;
}
*period_ps = clock_period_ps; return 0;
}
/** * ni_tio_set_bits() - Safely write a counter register. * @counter: struct ni_gpct counter. * @reg: the register to write. * @mask: the bits to change. * @value: the new bits value. * * Used to write to, and update the software copy, a register whose bits may * be twiddled in interrupt context, or whose software copy may be read in * interrupt context.
*/ void ni_tio_set_bits(struct ni_gpct *counter, enum ni_gpct_register reg, unsignedint mask, unsignedint value)
{
ni_tio_set_bits_transient(counter, reg, mask, value, 0x0);
}
EXPORT_SYMBOL_GPL(ni_tio_set_bits);
/** * ni_tio_get_soft_copy() - Safely read the software copy of a counter register. * @counter: struct ni_gpct counter. * @reg: the register to read. * * Used to get the software copy of a register whose bits might be modified * in interrupt context, or whose software copy might need to be read in * interrupt context.
*/ unsignedint ni_tio_get_soft_copy(conststruct ni_gpct *counter, enum ni_gpct_register reg)
{ struct ni_gpct_device *counter_dev = counter->counter_dev; unsignedint chip = counter->chip_index; unsignedint value = 0; unsignedlong flags;
/* only m series and 660x variants have counting mode registers */ switch (counter_dev->variant) { case ni_gpct_variant_e_series: default: return; case ni_gpct_variant_m_series:
mask = GI_M_ALT_SYNC; break; case ni_gpct_variant_660x:
mask = GI_660X_ALT_SYNC; break;
}
reg = NITIO_CNT_MODE_REG(cidx);
mode = ni_tio_get_soft_copy(counter, reg); switch (mode & GI_CNT_MODE_MASK) { case GI_CNT_MODE_QUADX1: case GI_CNT_MODE_QUADX2: case GI_CNT_MODE_QUADX4: case GI_CNT_MODE_SYNC_SRC:
force_alt_sync = true; break; default:
force_alt_sync = false; break;
}
ret = ni_tio_generic_clock_src_select(counter, &clk_src); if (ret) return;
ret = ni_tio_clock_period_ps(counter, clk_src, &ps); if (ret) return; /* * It's not clear what we should do if clock_period is unknown, so we * are not using the alt sync bit in that case.
*/ if (force_alt_sync || (ps && ps < min_normal_sync_period_ps))
bits = mask;
/* only m series and 660x have counting mode registers */ switch (counter_dev->variant) { case ni_gpct_variant_e_series: default: break; case ni_gpct_variant_m_series:
mask = GI_M_HW_ARM_SEL_MASK; break; case ni_gpct_variant_660x:
mask = GI_660X_HW_ARM_SEL_MASK; break;
}
switch (start_trigger) { case NI_GPCT_ARM_IMMEDIATE:
transient_bits |= GI_ARM; break; case NI_GPCT_ARM_PAIRED_IMMEDIATE:
transient_bits |= GI_ARM | GI_ARM_COPY; break; default: /* * for m series and 660x, pass-through the least * significant bits so we can figure out what select * later
*/ if (mask && (start_trigger & NI_GPCT_ARM_UNKNOWN)) {
bits |= GI_HW_ARM_ENA |
(GI_HW_ARM_SEL(start_trigger) & mask);
} else { return -EINVAL;
} break;
}
switch (counter_dev->variant) { case ni_gpct_variant_660x:
ret = ni_660x_clk_src(clock_source, &bits); break; case ni_gpct_variant_e_series: case ni_gpct_variant_m_series: default:
ret = ni_m_clk_src(clock_source, &bits); break;
} if (ret) { struct comedi_device *dev = counter_dev->dev;
/* Set the mode bits for gate. */ staticinlinevoid ni_tio_set_gate_mode(struct ni_gpct *counter, unsignedint src)
{ unsignedint mode_bits = 0;
if (CR_CHAN(src) & NI_GPCT_DISABLED_GATE_SELECT) { /* * Allowing bitwise comparison here to allow non-zero raw * register value to be used for channel when disabling.
*/
mode_bits = GI_GATING_DISABLED;
} else { if (src & CR_INVERT)
mode_bits |= GI_GATE_POL_INVERT; if (src & CR_EDGE)
mode_bits |= GI_RISING_EDGE_GATING; else
mode_bits |= GI_LEVEL_GATING;
}
ni_tio_set_bits(counter, NITIO_MODE_REG(counter->counter_index),
GI_GATE_POL_INVERT | GI_GATING_MODE_MASK,
mode_bits);
}
/* * Set the mode bits for gate2. * * Previously, the code this function represents did not actually write anything * to the register. Rather, writing to this register was reserved for the code * ni ni_tio_set_gate2_raw.
*/ staticinlinevoid ni_tio_set_gate2_mode(struct ni_gpct *counter, unsignedint src)
{ /* * The GI_GATE2_MODE bit was previously set in the code that also sets * the gate2 source. * We'll set mode bits _after_ source bits now, and thus, this function * will effectively enable the second gate after all bits are set.
*/ unsignedint mode_bits = GI_GATE2_MODE;
if (CR_CHAN(src) & NI_GPCT_DISABLED_GATE_SELECT) /* * Allowing bitwise comparison here to allow non-zero raw * register value to be used for channel when disabling.
*/
mode_bits = GI_GATING_DISABLED; if (src & CR_INVERT)
mode_bits |= GI_GATE2_POL_INVERT;
switch (chan) { case NI_GPCT_TIMESTAMP_MUX_GATE_SELECT: case NI_GPCT_AI_START2_GATE_SELECT: case NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT: case NI_GPCT_NEXT_OUT_GATE_SELECT: case NI_GPCT_AI_START1_GATE_SELECT: case NI_GPCT_NEXT_SOURCE_GATE_SELECT: case NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT: case NI_GPCT_LOGIC_LOW_GATE_SELECT:
gate_sel = chan & 0x1f; break; default: for (i = 0; i <= NI_M_MAX_RTSI_CHAN; ++i) { if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) {
gate_sel = chan & 0x1f; break;
}
} if (i <= NI_M_MAX_RTSI_CHAN) break; for (i = 0; i <= NI_M_MAX_PFI_CHAN; ++i) { if (chan == NI_GPCT_PFI_GATE_SELECT(i)) {
gate_sel = chan & 0x1f; break;
}
} if (i <= NI_M_MAX_PFI_CHAN) break; return -EINVAL;
}
ni_tio_set_gate_raw(counter, gate_sel); return 0;
}
switch (chan) { case NI_GPCT_SOURCE_PIN_i_GATE_SELECT: case NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT: case NI_GPCT_SELECTED_GATE_GATE_SELECT: case NI_GPCT_NEXT_OUT_GATE_SELECT: case NI_GPCT_LOGIC_LOW_GATE_SELECT:
gate2_sel = chan & 0x1f; break; case NI_GPCT_NEXT_SOURCE_GATE_SELECT:
gate2_sel = NI_660X_NEXT_SRC_GATE2_SEL; break; default: for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { if (chan == NI_GPCT_RTSI_GATE_SELECT(i)) {
gate2_sel = chan & 0x1f; break;
}
} if (i <= NI_660X_MAX_RTSI_CHAN) break; for (i = 0; i <= NI_660X_MAX_UP_DOWN_PIN; ++i) { if (chan == NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i)) {
gate2_sel = chan & 0x1f; break;
}
} if (i <= NI_660X_MAX_UP_DOWN_PIN) break; return -EINVAL;
}
ni_tio_set_gate2_raw(counter, gate2_sel); return 0;
}
staticint ni_m_set_gate2(struct ni_gpct *counter, unsignedint gate_source)
{ /* * FIXME: We don't know what the m-series second gate codes are, * so we'll just pass the bits through for now.
*/
ni_tio_set_gate2_raw(counter, gate_source); return 0;
}
switch (gate) { case 0: /* 1. start by disabling gate */
ni_tio_set_gate_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); /* 2. set the requested gate source */
ni_tio_set_gate_raw(counter, src); /* 3. reenable & set mode to starts things back up */
ni_tio_set_gate_mode(counter, src); break; case 1: if (!ni_tio_has_gate2_registers(counter_dev)) return -EINVAL;
/* 1. start by disabling gate */
ni_tio_set_gate2_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); /* 2. set the requested gate source */
ni_tio_set_gate2_raw(counter, src); /* 3. reenable & set mode to starts things back up */
ni_tio_set_gate2_mode(counter, src); break; default: return -EINVAL;
} return 0;
}
EXPORT_SYMBOL_GPL(ni_tio_set_gate_src_raw);
int ni_tio_set_gate_src(struct ni_gpct *counter, unsignedint gate, unsignedint src)
{ struct ni_gpct_device *counter_dev = counter->counter_dev; /* * mask off disable flag. This high bit still passes CR_CHAN. * Doing this allows one to both set the gate as disabled, but also * change the route value of the gate.
*/ int chan = CR_CHAN(src) & (~NI_GPCT_DISABLED_GATE_SELECT); int ret;
switch (gate) { case 0: /* 1. start by disabling gate */
ni_tio_set_gate_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); /* 2. set the requested gate source */ switch (counter_dev->variant) { case ni_gpct_variant_e_series: case ni_gpct_variant_m_series:
ret = ni_m_set_gate(counter, chan); break; case ni_gpct_variant_660x:
ret = ni_660x_set_gate(counter, chan); break; default: return -EINVAL;
} if (ret) return ret; /* 3. reenable & set mode to starts things back up */
ni_tio_set_gate_mode(counter, src); break; case 1: if (!ni_tio_has_gate2_registers(counter_dev)) return -EINVAL;
/* 1. start by disabling gate */
ni_tio_set_gate2_mode(counter, NI_GPCT_DISABLED_GATE_SELECT); /* 2. set the requested gate source */ switch (counter_dev->variant) { case ni_gpct_variant_m_series:
ret = ni_m_set_gate2(counter, chan); break; case ni_gpct_variant_660x:
ret = ni_660x_set_gate2(counter, chan); break; default: return -EINVAL;
} if (ret) return ret; /* 3. reenable & set mode to starts things back up */
ni_tio_set_gate2_mode(counter, src); break; default: return -EINVAL;
} return 0;
}
EXPORT_SYMBOL_GPL(ni_tio_set_gate_src);
switch (gate) { case NI_660X_SRC_PIN_I_GATE2_SEL:
source = NI_GPCT_SOURCE_PIN_i_GATE_SELECT; break; case NI_660X_UD_PIN_I_GATE2_SEL:
source = NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT; break; case NI_660X_NEXT_SRC_GATE2_SEL:
source = NI_GPCT_NEXT_SOURCE_GATE_SELECT; break; case NI_660X_NEXT_OUT_GATE2_SEL:
source = NI_GPCT_NEXT_OUT_GATE_SELECT; break; case NI_660X_SELECTED_GATE2_SEL:
source = NI_GPCT_SELECTED_GATE_GATE_SELECT; break; case NI_660X_LOGIC_LOW_GATE2_SEL:
source = NI_GPCT_LOGIC_LOW_GATE_SELECT; break; default: for (i = 0; i <= NI_660X_MAX_RTSI_CHAN; ++i) { if (gate == NI_660X_RTSI_GATE2_SEL(i)) {
source = NI_GPCT_RTSI_GATE_SELECT(i); break;
}
} if (i <= NI_660X_MAX_RTSI_CHAN) break; for (i = 0; i <= NI_660X_MAX_UP_DOWN_PIN; ++i) { if (gate == NI_660X_UD_PIN_GATE2_SEL(i)) {
source = NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i); break;
}
} if (i <= NI_660X_MAX_UP_DOWN_PIN) break; return -EINVAL;
}
*src = source; return 0;
}
staticint ni_m_gate2_to_generic_gate(unsignedint gate, unsignedint *src)
{ /* * FIXME: the second gate sources for the m series are undocumented, * so we just return the raw bits for now.
*/
*src = gate; return 0;
}
if ((mode & GI_GATING_MODE_MASK) == GI_GATING_DISABLED)
ret |= NI_GPCT_DISABLED_GATE_SELECT; if (mode & GI_GATE_POL_INVERT)
ret |= CR_INVERT; if ((mode & GI_GATING_MODE_MASK) != GI_LEVEL_GATING)
ret |= CR_EDGE;
int ni_tio_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsignedint *data)
{ struct ni_gpct *counter = s->private; unsignedint cidx = counter->counter_index; unsignedint status; int ret = 0;
switch (data[0]) { case INSN_CONFIG_SET_COUNTER_MODE:
ret = ni_tio_set_counter_mode(counter, data[1]); break; case INSN_CONFIG_ARM:
ret = ni_tio_arm(counter, true, data[1]); break; case INSN_CONFIG_DISARM:
ret = ni_tio_arm(counter, false, 0); break; case INSN_CONFIG_GET_COUNTER_STATUS:
data[1] = 0;
status = ni_tio_read(counter, NITIO_SHARED_STATUS_REG(cidx)); if (status & GI_ARMED(cidx)) {
data[1] |= COMEDI_COUNTER_ARMED; if (status & GI_COUNTING(cidx))
data[1] |= COMEDI_COUNTER_COUNTING;
}
data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING; break; case INSN_CONFIG_SET_CLOCK_SRC:
ret = ni_tio_set_clock_src(counter, data[1], data[2]); break; case INSN_CONFIG_GET_CLOCK_SRC:
ret = ni_tio_get_clock_src(counter, &data[1], &data[2]); break; case INSN_CONFIG_SET_GATE_SRC:
ret = ni_tio_set_gate_src(counter, data[1], data[2]); break; case INSN_CONFIG_GET_GATE_SRC:
ret = ni_tio_get_gate_src(counter, data[1], &data[2]); break; case INSN_CONFIG_SET_OTHER_SRC:
ret = ni_tio_set_other_src(counter, data[1], data[2]); break; case INSN_CONFIG_RESET:
ni_tio_reset_count_and_disarm(counter); break; default: return -EINVAL;
} return ret ? ret : insn->n;
}
EXPORT_SYMBOL_GPL(ni_tio_insn_config);
/* * Retrieves the register value of 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.
*/ int ni_tio_get_routing(struct ni_gpct_device *counter_dev, unsignedint dest)
{ /* we need to know the actual counter below... */ int ctr_index = (dest - NI_COUNTER_NAMES_BASE) % NI_MAX_COUNTERS; struct ni_gpct *counter = &counter_dev->counters[ctr_index]; int ret = 1; unsignedint reg;
if (dest >= NI_CtrA(0) && dest <= NI_CtrZ(-1)) {
ret = ni_tio_get_other_src(counter, dest, ®);
} elseif (dest >= NI_CtrGate(0) && dest <= NI_CtrGate(-1)) {
ret = ni_tio_get_gate_src_raw(counter, 0, ®);
} elseif (dest >= NI_CtrAux(0) && dest <= NI_CtrAux(-1)) {
ret = ni_tio_get_gate_src_raw(counter, 1, ®); /* * This case is not possible through this interface. A user must use * INSN_CONFIG_SET_CLOCK_SRC instead. * } else if (dest >= NI_CtrSource(0) && dest <= NI_CtrSource(-1)) { * ret = ni_tio_set_clock_src(counter, ®, &period_ns);
*/
}
/** * ni_tio_set_routing() - Sets the register value of the selector MUX for the given destination. * @counter_dev: Pointer to general counter device. * @dest: Device-global identifier of route destination. * @reg: * The first several bits of this value should store the desired * value to write to the register. All other bits are for * transmitting information that modify the mode of the particular * destination/gate. These mode bits might include a bitwise or of * CR_INVERT and CR_EDGE. Note that the calling function should * have already validated the correctness of this value.
*/ int ni_tio_set_routing(struct ni_gpct_device *counter_dev, unsignedint dest, unsignedint reg)
{ /* we need to know the actual counter below... */ int ctr_index = (dest - NI_COUNTER_NAMES_BASE) % NI_MAX_COUNTERS; struct ni_gpct *counter = &counter_dev->counters[ctr_index]; int ret;
if (dest >= NI_CtrA(0) && dest <= NI_CtrZ(-1)) {
ret = ni_tio_set_other_src(counter, dest, reg);
} elseif (dest >= NI_CtrGate(0) && dest <= NI_CtrGate(-1)) {
ret = ni_tio_set_gate_src_raw(counter, 0, reg);
} elseif (dest >= NI_CtrAux(0) && dest <= NI_CtrAux(-1)) {
ret = ni_tio_set_gate_src_raw(counter, 1, reg); /* * This case is not possible through this interface. A user must use * INSN_CONFIG_SET_CLOCK_SRC instead. * } else if (dest >= NI_CtrSource(0) && dest <= NI_CtrSource(-1)) { * ret = ni_tio_set_clock_src(counter, reg, period_ns);
*/
} else { return -EINVAL;
}
/* * Sets the given destination MUX to its default value or disable it. * * Return: 0 if successful; -EINVAL if terminal is unknown.
*/ int ni_tio_unset_routing(struct ni_gpct_device *counter_dev, unsignedint dest)
{ if (dest >= NI_GATES_NAMES_BASE && dest <= NI_GATES_NAMES_MAX) /* Disable gate (via mode bits) and set to default 0-value */ return ni_tio_set_routing(counter_dev, dest,
NI_GPCT_DISABLED_GATE_SELECT); /* * This case is not possible through this interface. A user must use * INSN_CONFIG_SET_CLOCK_SRC instead. * if (dest >= NI_CtrSource(0) && dest <= NI_CtrSource(-1)) * return ni_tio_set_clock_src(counter, reg, period_ns);
*/
/* * The count doesn't get latched until the next clock edge, so it is * possible the count may change (once) while we are reading. Since * the read of the SW_Save_Reg isn't atomic (apparently even when it's * a 32 bit register according to 660x docs), we need to read twice * and make sure the reading hasn't changed. If it has, a third read * will be correct since the count value will definitely have latched * by then.
*/
val = ni_tio_read(counter, NITIO_SW_SAVE_REG(cidx)); if (val != ni_tio_read(counter, NITIO_SW_SAVE_REG(cidx)))
val = ni_tio_read(counter, NITIO_SW_SAVE_REG(cidx));
if (insn->n < 1) return 0;
load_val = data[insn->n - 1]; switch (channel) { case 0: /* * Unsafe if counter is armed. * Should probably check status and return -EBUSY if armed.
*/
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.