// SPDX-License-Identifier: GPL-2.0+ /* * comedi/drivers/s626.c * Sensoray s626 Comedi driver * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 2000 David A. Schleef <ds@schleef.org> * * Based on Sensoray Model 626 Linux driver Version 0.2 * Copyright (C) 2002-2004 Sensoray Co., Inc.
*/
/** * struct s626_private - Working data for s626 driver. * @ai_cmd_running: non-zero if ai_cmd is running. * @ai_sample_timer: time between samples in units of the timer. * @ai_convert_count: conversion counter. * @ai_convert_timer: time between conversion in units of the timer. * @counter_int_enabs: counter interrupt enable mask for MISC2 register. * @adc_items: number of items in ADC poll list. * @rps_buf: DMA buffer used to hold ADC (RPS1) program. * @ana_buf: DMA buffer used to receive ADC data and hold DAC data. * @dac_wbuf: pointer to logical adrs of DMA buffer used to hold DAC data. * @dacpol: image of DAC polarity register. * @trim_setpoint: images of TrimDAC setpoints. * @i2c_adrs: I2C device address for onboard EEPROM (board rev dependent)
*/ struct s626_private {
u8 ai_cmd_running; unsignedint ai_sample_timer; int ai_convert_count; unsignedint ai_convert_timer;
u16 counter_int_enabs;
u8 adc_items; struct s626_buffer_dma rps_buf; struct s626_buffer_dma ana_buf;
u32 *dac_wbuf;
u16 dacpol;
u8 trim_setpoint[12];
u32 i2c_adrs;
};
/* * Enable/disable a function or test status bit(s) that are accessed * through Main Control Registers 1 or 2.
*/ staticvoid s626_mc_enable(struct comedi_device *dev, unsignedint cmd, unsignedint reg)
{ unsignedint val = (cmd << 16) | cmd;
/* * Execute a DEBI transfer. This must be called from within a critical section.
*/ staticvoid s626_debi_transfer(struct comedi_device *dev)
{ staticconstint timeout = 10000; int i;
/* Initiate upload of shadow RAM to DEBI control register */
s626_mc_enable(dev, S626_MC2_UPLD_DEBI, S626_P_MC2);
/* * Wait for completion of upload from shadow RAM to * DEBI control register.
*/ for (i = 0; i < timeout; i++) { if (s626_mc_test(dev, S626_MC2_UPLD_DEBI, S626_P_MC2)) break;
udelay(1);
} if (i == timeout)
dev_err(dev->class_dev, "Timeout while uploading to DEBI control register\n");
/* Wait until DEBI transfer is done */ for (i = 0; i < timeout; i++) { if (!(readl(dev->mmio + S626_P_PSR) & S626_PSR_DEBI_S)) break;
udelay(1);
} if (i == timeout)
dev_err(dev->class_dev, "DEBI transfer timeout\n");
}
/* * Read a value from a gate array register.
*/ static u16 s626_debi_read(struct comedi_device *dev, u16 addr)
{ /* Set up DEBI control register value in shadow RAM */
writel(S626_DEBI_CMD_RDWORD | addr, dev->mmio + S626_P_DEBICMD);
/* Execute the DEBI transfer. */
s626_debi_transfer(dev);
return readl(dev->mmio + S626_P_DEBIAD);
}
/* * Write a value to a gate array register.
*/ staticvoid s626_debi_write(struct comedi_device *dev, u16 addr,
u16 wdata)
{ /* Set up DEBI control register value in shadow RAM */
writel(S626_DEBI_CMD_WRWORD | addr, dev->mmio + S626_P_DEBICMD);
writel(wdata, dev->mmio + S626_P_DEBIAD);
/* Execute the DEBI transfer. */
s626_debi_transfer(dev);
}
/* * Replace the specified bits in a gate array register. Imports: mask * specifies bits that are to be preserved, wdata is new value to be * or'd with the masked original.
*/ staticvoid s626_debi_replace(struct comedi_device *dev, unsignedint addr, unsignedint mask, unsignedint wdata)
{ unsignedint val;
/* Write I2C command to I2C Transfer Control shadow register */
writel(val, dev->mmio + S626_P_I2CCTRL);
/* * Upload I2C shadow registers into working registers and * wait for upload confirmation.
*/
s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0); if (ret) return ret;
/* Wait until I2C bus transfer is finished or an error occurs */ do {
ctrl = readl(dev->mmio + S626_P_I2CCTRL);
} while ((ctrl & (S626_I2C_BUSY | S626_I2C_ERR)) == S626_I2C_BUSY);
switch (context) { case s626_send_dac_wait_not_mc1_a2out:
status = readl(dev->mmio + S626_P_MC1); if (!(status & S626_MC1_A2OUT)) return 0; break; case s626_send_dac_wait_ssr_af2_out:
status = readl(dev->mmio + S626_P_SSR); if (status & S626_SSR_AF2_OUT) return 0; break; case s626_send_dac_wait_fb_buffer2_msb_00:
status = readl(dev->mmio + S626_P_FB_BUFFER2); if (!(status & 0xff000000)) return 0; break; case s626_send_dac_wait_fb_buffer2_msb_ff:
status = readl(dev->mmio + S626_P_FB_BUFFER2); if (status & 0xff000000) return 0; break; default: return -EINVAL;
} return -EBUSY;
}
/* * Private helper function: Transmit serial data to DAC via Audio * channel 2. Assumes: (1) TSL2 slot records initialized, and (2) * dacpol contains valid target image.
*/ staticint s626_send_dac(struct comedi_device *dev, u32 val)
{ struct s626_private *devpriv = dev->private; int ret;
/* START THE SERIAL CLOCK RUNNING ------------- */
/* * Assert DAC polarity control and enable gating of DAC serial clock * and audio bit stream signals. At this point in time we must be * assured of being in time slot 0. If we are not in slot 0, the * serial clock and audio stream signals will be disabled; this is * because the following s626_debi_write statement (which enables * signals to be passed through the gate array) would execute before * the trailing edge of WS1/WS3 (which turns off the signals), thus * causing the signals to be inactive during the DAC write.
*/
s626_debi_write(dev, S626_LP_DACPOL, devpriv->dacpol);
/* TRANSFER OUTPUT DWORD VALUE INTO A2'S OUTPUT FIFO ---------------- */
/* Copy DAC setpoint value to DAC's output DMA buffer. */ /* writel(val, dev->mmio + (uint32_t)devpriv->dac_wbuf); */
*devpriv->dac_wbuf = val;
/* * Enable the output DMA transfer. This will cause the DMAC to copy * the DAC's data value to A2's output FIFO. The DMA transfer will * then immediately terminate because the protection address is * reached upon transfer of the first DWORD value.
*/
s626_mc_enable(dev, S626_MC1_A2OUT, S626_P_MC1);
/* While the DMA transfer is executing ... */
/* * Reset Audio2 output FIFO's underflow flag (along with any * other FIFO underflow/overflow flags). When set, this flag * will indicate that we have emerged from slot 0.
*/
writel(S626_ISR_AFOU, dev->mmio + S626_P_ISR);
/* * Wait for the DMA transfer to finish so that there will be data * available in the FIFO when time slot 1 tries to transfer a DWORD * from the FIFO to the output buffer register. We test for DMA * Done by polling the DMAC enable flag; this flag is automatically * cleared when the transfer has finished.
*/
ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
s626_send_dac_wait_not_mc1_a2out); if (ret) {
dev_err(dev->class_dev, "DMA transfer timeout\n"); return ret;
}
/* START THE OUTPUT STREAM TO THE TARGET DAC -------------------- */
/* * FIFO data is now available, so we enable execution of time slots * 1 and higher by clearing the EOS flag in slot 0. Note that SD3 * will be shifted in and stored in FB_BUFFER2 for end-of-slot-list * detection.
*/
writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2,
dev->mmio + S626_VECTPORT(0));
/* * Wait for slot 1 to execute to ensure that the Packet will be * transmitted. This is detected by polling the Audio2 output FIFO * underflow flag, which will be set when slot 1 execution has * finished transferring the DAC's data DWORD from the output FIFO * to the output buffer register.
*/
ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
s626_send_dac_wait_ssr_af2_out); if (ret) {
dev_err(dev->class_dev, "TSL timeout waiting for slot 1 to execute\n"); return ret;
}
/* * Set up to trap execution at slot 0 when the TSL sequencer cycles * back to slot 0 after executing the EOS in slot 5. Also, * simultaneously shift out and in the 0x00 that is ALWAYS the value * stored in the last byte to be shifted out of the FIFO's DWORD * buffer register.
*/
writel(S626_XSD2 | S626_XFIFO_2 | S626_RSD2 | S626_SIB_A2 | S626_EOS,
dev->mmio + S626_VECTPORT(0));
/* WAIT FOR THE TRANSACTION TO FINISH ----------------------- */
/* * Wait for the TSL to finish executing all time slots before * exiting this function. We must do this so that the next DAC * write doesn't start, thereby enabling clock/chip select signals: * * 1. Before the TSL sequence cycles back to slot 0, which disables * the clock/cs signal gating and traps slot // list execution. * we have not yet finished slot 5 then the clock/cs signals are * still gated and we have not finished transmitting the stream. * * 2. While slots 2-5 are executing due to a late slot 0 trap. In * this case, the slot sequence is currently repeating, but with * clock/cs signals disabled. We must wait for slot 0 to trap * execution before setting up the next DAC setpoint DMA transfer * and enabling the clock/cs signals. To detect the end of slot 5, * we test for the FB_BUFFER2 MSB contents to be equal to 0xFF. If * the TSL has not yet finished executing slot 5 ...
*/ if (readl(dev->mmio + S626_P_FB_BUFFER2) & 0xff000000) { /* * The trap was set on time and we are still executing somewhere * in slots 2-5, so we now wait for slot 0 to execute and trap * TSL execution. This is detected when FB_BUFFER2 MSB changes * from 0xFF to 0x00, which slot 0 causes to happen by shifting * out/in on SD2 the 0x00 that is always referenced by slot 5.
*/
ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
s626_send_dac_wait_fb_buffer2_msb_00); if (ret) {
dev_err(dev->class_dev, "TSL timeout waiting for slot 0 to execute\n"); return ret;
}
} /* * Either (1) we were too late setting the slot 0 trap; the TSL * sequencer restarted slot 0 before we could set the EOS trap flag, * or (2) we were not late and execution is now trapped at slot 0. * In either case, we must now change slot 0 so that it will store * value 0xFF (instead of 0x00) to FB_BUFFER2 next time it executes. * In order to do this, we reprogram slot 0 so that it will shift in * SD3, which is driven only by a pull-up resistor.
*/
writel(S626_RSD3 | S626_SIB_A2 | S626_EOS,
dev->mmio + S626_VECTPORT(0));
/* * Wait for slot 0 to execute, at which time the TSL is setup for * the next DAC write. This is detected when FB_BUFFER2 MSB changes * from 0x00 to 0xFF.
*/
ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
s626_send_dac_wait_fb_buffer2_msb_ff); if (ret) {
dev_err(dev->class_dev, "TSL timeout waiting for slot 0 to execute\n"); return ret;
} return 0;
}
/* * Adjust DAC data polarity and set up Polarity Control Register image.
*/
signmask = 1 << chan; if (dacdata < 0) {
dacdata = -dacdata;
devpriv->dacpol |= signmask;
} else {
devpriv->dacpol &= ~signmask;
}
/* Limit DAC setpoint value to valid range. */ if ((u16)dacdata > 0x1FFF)
dacdata = 0x1FFF;
/* * Set up TSL2 records (aka "vectors") for DAC update. Vectors V2 * and V3 transmit the setpoint to the target DAC. V4 and V5 send * data to a non-existent TrimDac channel just to keep the clock * running after sending data to the target DAC. This is necessary * to eliminate the clock glitch that would otherwise occur at the * end of the target DAC's serial data stream. When the sequence * restarts at V0 (after executing V5), the gate array automatically * disables gating for the DAC clock and all DAC chip selects.
*/
/* Choose DAC chip select to be asserted */
ws_image = (chan & 2) ? S626_WS1 : S626_WS2; /* Slot 2: Transmit high data byte to target DAC */
writel(S626_XSD2 | S626_XFIFO_1 | ws_image,
dev->mmio + S626_VECTPORT(2)); /* Slot 3: Transmit low data byte to target DAC */
writel(S626_XSD2 | S626_XFIFO_0 | ws_image,
dev->mmio + S626_VECTPORT(3)); /* Slot 4: Transmit to non-existent TrimDac channel to keep clock */
writel(S626_XSD2 | S626_XFIFO_3 | S626_WS3,
dev->mmio + S626_VECTPORT(4)); /* Slot 5: running after writing target DAC's low data byte */
writel(S626_XSD2 | S626_XFIFO_2 | S626_WS3 | S626_EOS,
dev->mmio + S626_VECTPORT(5));
/* * Construct and transmit target DAC's serial packet: * (A10D DDDD), (DDDD DDDD), (0x0F), (0x00) where A is chan<0>, * and D<12:0> is the DAC setpoint. Append a WORD value (that writes * to a non-existent TrimDac channel) that serves to keep the clock * running after the packet has been sent to the target DAC.
*/
val = 0x0F000000; /* Continue clock after target DAC data * (write to non-existent trimdac).
*/
val |= 0x00004000; /* Address the two main dual-DAC devices * (TSL's chip select enables target device).
*/
val |= ((u32)(chan & 1) << 15); /* Address the DAC channel * within the device.
*/
val |= (u32)dacdata; /* Include DAC setpoint data. */ return s626_send_dac(dev, val);
}
/* * Save the new setpoint in case the application needs to read it back * later.
*/
devpriv->trim_setpoint[logical_chan] = dac_data;
/* Map logical channel number to physical channel number. */
chan = s626_trimchan[logical_chan];
/* * Set up TSL2 records for TrimDac write operation. All slots shift * 0xFF in from pulled-up SD3 so that the end of the slot sequence * can be detected.
*/
/* * Construct and transmit target DAC's serial packet: * (0000 AAAA), (DDDD DDDD), (0x00), (0x00) where A<3:0> is the * DAC channel's address, and D<7:0> is the DAC setpoint. Append a * WORD value (that writes a channel 0 NOP command to a non-existent * main DAC channel) that serves to keep the clock running after the * packet has been sent to the target DAC.
*/
/* * Address the DAC channel within the trimdac device. * Include DAC setpoint data.
*/ return s626_send_dac(dev, (chan << 8) | dac_data);
}
staticint s626_load_trim_dacs(struct comedi_device *dev)
{
u8 i; int ret;
/* Copy TrimDac setpoint values from EEPROM to TrimDacs. */ for (i = 0; i < ARRAY_SIZE(s626_trimchan); i++) {
ret = s626_write_trim_dac(dev, i,
s626_i2c_read(dev, s626_trimadrs[i])); if (ret) return ret;
} return 0;
}
/* ****** COUNTER FUNCTIONS ******* */
/* * All counter functions address a specific counter by means of the * "Counter" argument, which is a logical counter number. The Counter * argument may have any of the following legal values: 0=0A, 1=1A, * 2=2A, 3=0B, 4=1B, 5=2B.
*/
/* * Return/set a counter pair's latch trigger source. 0: On read * access, 1: A index latches A, 2: B index latches B, 3: A overflow * latches B.
*/ staticvoid s626_set_latch_source(struct comedi_device *dev, unsignedint chan, u16 value)
{
s626_debi_replace(dev, S626_LP_CRB(chan),
~(S626_CRBMSK_INTCTRL | S626_CRBMSK_LATCHSRC),
S626_SET_CRB_LATCHSRC(value));
}
/* * Write value into counter preload register.
*/ staticvoid s626_preload(struct comedi_device *dev, unsignedint chan, u32 value)
{
s626_debi_write(dev, S626_LP_CNTR(chan), value);
s626_debi_write(dev, S626_LP_CNTR(chan) + 2, value >> 16);
}
/* ****** PRIVATE COUNTER FUNCTIONS ****** */
/* * Reset a counter's index and overflow event capture flags.
*/ staticvoid s626_reset_cap_flags(struct comedi_device *dev, unsignedint chan)
{
u16 set;
set = S626_SET_CRB_INTRESETCMD(1); if (chan < 3)
set |= S626_SET_CRB_INTRESET_A(1); else
set |= S626_SET_CRB_INTRESET_B(1);
/* * Set the operating mode for the specified counter. The setup * parameter is treated as a COUNTER_SETUP data type. The following * parameters are programmable (all other parms are ignored): ClkMult, * ClkPol, ClkEnab, IndexSrc, IndexPol, LoadSrc.
*/ staticvoid s626_set_mode_a(struct comedi_device *dev, unsignedint chan, u16 setup,
u16 disable_int_src)
{ struct s626_private *devpriv = dev->private;
u16 cra;
u16 crb; unsignedint cntsrc, clkmult, clkpol;
/* Initialize CRA and CRB images. */ /* Preload trigger is passed through. */
cra = S626_SET_CRA_LOADSRC_A(S626_GET_STD_LOADSRC(setup)); /* IndexSrc is passed through. */
cra |= S626_SET_CRA_INDXSRC_A(S626_GET_STD_INDXSRC(setup));
/* Force IntSrc to Disabled if disable_int_src is asserted. */ if (!disable_int_src)
cra |= S626_SET_CRA_INTSRC_A(S626_GET_STD_INTSRC(setup));
/* Populate all mode-dependent attributes of CRA & CRB images. */
clkpol = S626_GET_STD_CLKPOL(setup); switch (S626_GET_STD_ENCMODE(setup)) { case S626_ENCMODE_EXTENDER: /* Extender Mode: */ /* Force to Timer mode (Extender valid only for B counters). */ /* Fall through to case S626_ENCMODE_TIMER: */ case S626_ENCMODE_TIMER: /* Timer Mode: */ /* CntSrcA<1> selects system clock */
cntsrc = S626_CNTSRC_SYSCLK; /* Count direction (CntSrcA<0>) obtained from ClkPol. */
cntsrc |= clkpol; /* ClkPolA behaves as always-on clock enable. */
clkpol = 1; /* ClkMult must be 1x. */
clkmult = S626_CLKMULT_1X; break; default: /* Counter Mode: */ /* Select ENC_C and ENC_D as clock/direction inputs. */
cntsrc = S626_CNTSRC_ENCODER; /* Clock polarity is passed through. */ /* Force multiplier to x1 if not legal, else pass through. */
clkmult = S626_GET_STD_CLKMULT(setup); if (clkmult == S626_CLKMULT_SPECIAL)
clkmult = S626_CLKMULT_1X; break;
}
cra |= S626_SET_CRA_CNTSRC_A(cntsrc) | S626_SET_CRA_CLKPOL_A(clkpol) |
S626_SET_CRA_CLKMULT_A(clkmult);
/* * Force positive index polarity if IndxSrc is software-driven only, * otherwise pass it through.
*/ if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT)
cra |= S626_SET_CRA_INDXPOL_A(S626_GET_STD_INDXPOL(setup));
/* * If IntSrc has been forced to Disabled, update the MISC2 interrupt * enable mask to indicate the counter interrupt is disabled.
*/ if (disable_int_src)
devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) |
S626_INDXMASK(chan));
/* * While retaining CounterB and LatchSrc configurations, program the * new counter operating mode.
*/
s626_debi_replace(dev, S626_LP_CRA(chan),
S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B, cra);
s626_debi_replace(dev, S626_LP_CRB(chan),
~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A), crb);
}
/* Force IntSrc to Disabled if disable_int_src is asserted. */ if (!disable_int_src)
crb |= S626_SET_CRB_INTSRC_B(S626_GET_STD_INTSRC(setup));
/* Populate all mode-dependent attributes of CRA & CRB images. */
clkpol = S626_GET_STD_CLKPOL(setup); switch (S626_GET_STD_ENCMODE(setup)) { case S626_ENCMODE_TIMER: /* Timer Mode: */ /* CntSrcB<1> selects system clock */
cntsrc = S626_CNTSRC_SYSCLK; /* with direction (CntSrcB<0>) obtained from ClkPol. */
cntsrc |= clkpol; /* ClkPolB behaves as always-on clock enable. */
clkpol = 1; /* ClkMultB must be 1x. */
clkmult = S626_CLKMULT_1X; break; case S626_ENCMODE_EXTENDER: /* Extender Mode: */ /* CntSrcB source is OverflowA (same as "timer") */
cntsrc = S626_CNTSRC_SYSCLK; /* with direction obtained from ClkPol. */
cntsrc |= clkpol; /* ClkPolB controls IndexB -- always set to active. */
clkpol = 1; /* ClkMultB selects OverflowA as the clock source. */
clkmult = S626_CLKMULT_SPECIAL; break; default: /* Counter Mode: */ /* Select ENC_C and ENC_D as clock/direction inputs. */
cntsrc = S626_CNTSRC_ENCODER; /* ClkPol is passed through. */ /* Force ClkMult to x1 if not legal, otherwise pass through. */
clkmult = S626_GET_STD_CLKMULT(setup); if (clkmult == S626_CLKMULT_SPECIAL)
clkmult = S626_CLKMULT_1X; break;
}
cra |= S626_SET_CRA_CNTSRC_B(cntsrc);
crb |= S626_SET_CRB_CLKPOL_B(clkpol) | S626_SET_CRB_CLKMULT_B(clkmult);
/* * Force positive index polarity if IndxSrc is software-driven only, * otherwise pass it through.
*/ if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT)
crb |= S626_SET_CRB_INDXPOL_B(S626_GET_STD_INDXPOL(setup));
/* * If IntSrc has been forced to Disabled, update the MISC2 interrupt * enable mask to indicate the counter interrupt is disabled.
*/ if (disable_int_src)
devpriv->counter_int_enabs &= ~(S626_OVERMASK(chan) |
S626_INDXMASK(chan));
/* * While retaining CounterA and LatchSrc configurations, program the * new counter operating mode.
*/
s626_debi_replace(dev, S626_LP_CRA(chan),
~(S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B), cra);
s626_debi_replace(dev, S626_LP_CRB(chan),
S626_CRBMSK_CLKENAB_A | S626_CRBMSK_LATCHSRC, crb);
}
/* clear all dio pending events and interrupt */ for (group = 0; group < S626_DIO_BANKS; group++)
s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff);
if (devpriv->ai_cmd_running) { /* check if interrupt is an ai acquisition start trigger */ if ((irqbit >> (cmd->start_arg - (16 * group))) == 1 &&
cmd->start_src == TRIG_EXT) { /* Start executing the RPS program */
s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
staticbool s626_handle_eos_interrupt(struct comedi_device *dev)
{ struct s626_private *devpriv = dev->private; struct comedi_subdevice *s = dev->read_subdev; struct comedi_async *async = s->async; struct comedi_cmd *cmd = &async->cmd; /* * Init ptr to DMA buffer that holds new ADC data. We skip the * first uint16_t in the buffer because it contains junk data * from the final ADC of the previous poll list scan.
*/
u32 *readaddr = (u32 *)devpriv->ana_buf.logical_base + 1; int i;
/* get the data and hand it over to comedi */ for (i = 0; i < cmd->chanlist_len; i++) { unsignedshort tempdata;
/* * Convert ADC data to 16-bit integer values and copy * to application buffer.
*/
tempdata = s626_ai_reg_to_uint(*readaddr);
readaddr++;
/* Construct RPS program in rps_buf DMA buffer */ if (cmd->scan_begin_src != TRIG_FOLLOW) { /* Wait for Start trigger. */
*rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC;
*rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC;
}
/* * SAA7146 BUG WORKAROUND Do a dummy DEBI Write. This is necessary * because the first RPS DEBI Write following a non-RPS DEBI write * seems to always fail. If we don't do this dummy write, the ADC * gain might not be set to the value required for the first slot in * the poll list; the ADC gain would instead remain unchanged from * the previously programmed value.
*/ /* Write DEBI Write command and address to shadow RAM. */
*rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
*rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL;
*rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2); /* Write DEBI immediate data to shadow RAM: */
*rps++ = S626_GSEL_BIPOLAR5V; /* arbitrary immediate data value. */
*rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI; /* Reset "shadow RAM uploaded" flag. */ /* Invoke shadow RAM upload. */
*rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI; /* Wait for shadow upload to finish. */
*rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
/* * Digitize all slots in the poll list. This is implemented as a * for loop to limit the slot count to 16 in case the application * forgot to set the S626_EOPL flag in the final slot.
*/ for (devpriv->adc_items = 0; devpriv->adc_items < 16;
devpriv->adc_items++) { /* * Convert application's poll list item to private board class * format. Each app poll list item is an uint8_t with form * (EOPL,x,x,RANGE,CHAN<3:0>), where RANGE code indicates 0 = * +-10V, 1 = +-5V, and EOPL = End of Poll List marker.
*/
local_ppl = (*ppl << 8) | (*ppl & 0x10 ? S626_GSEL_BIPOLAR5V :
S626_GSEL_BIPOLAR10V);
/* * Delay at least 10 microseconds for analog input settling. * Instead of padding with NOPs, we use S626_RPS_JUMP * instructions here; this allows us to produce a longer delay * than is possible with NOPs because each S626_RPS_JUMP * flushes the RPS' instruction prefetch pipeline.
*/
jmp_adrs =
(u32)devpriv->rps_buf.physical_base +
(u32)((unsignedlong)rps -
(unsignedlong)devpriv->rps_buf.logical_base); for (i = 0; i < (10 * S626_RPSCLK_PER_US / 2); i++) {
jmp_adrs += 8; /* Repeat to implement time delay: */ /* Jump to next RPS instruction. */
*rps++ = S626_RPS_JUMP;
*rps++ = jmp_adrs;
}
if (cmd->convert_src != TRIG_NOW) { /* Wait for Start trigger. */
*rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC;
*rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC;
} /* Start ADC by pulsing GPIO1. */ /* Begin ADC Start pulse. */
*rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
*rps++ = S626_GPIO_BASE | S626_GPIO1_LO;
*rps++ = S626_RPS_NOP; /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */ /* End ADC Start pulse. */
*rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
*rps++ = S626_GPIO_BASE | S626_GPIO1_HI; /* * Wait for ADC to complete (GPIO2 is asserted high when ADC not * busy) and for data from previous conversion to shift into FB * BUFFER 1 register.
*/ /* Wait for ADC done. */
*rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2;
/* Transfer ADC data from FB BUFFER 1 register to DMA buffer. */
*rps++ = S626_RPS_STREG |
(S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2);
*rps++ = (u32)devpriv->ana_buf.physical_base +
(devpriv->adc_items << 2);
/* * If this slot's EndOfPollList flag is set, all channels have * now been processed.
*/ if (*ppl++ & S626_EOPL) {
devpriv->adc_items++; /* Adjust poll list item count. */ break; /* Exit poll list processing loop. */
}
}
/* * VERSION 2.01 CHANGE: DELAY CHANGED FROM 250NS to 2US. Allow the * ADC to stabilize for 2 microseconds before starting the final * (dummy) conversion. This delay is necessary to allow sufficient * time between last conversion finished and the start of the dummy * conversion. Without this delay, the last conversion's data value * is sometimes set to the previous conversion's data value.
*/ for (n = 0; n < (2 * S626_RPSCLK_PER_US); n++)
*rps++ = S626_RPS_NOP;
/* * Start a dummy conversion to cause the data from the last * conversion of interest to be shifted in.
*/ /* Begin ADC Start pulse. */
*rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
*rps++ = S626_GPIO_BASE | S626_GPIO1_LO;
*rps++ = S626_RPS_NOP; /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */
*rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2); /* End ADC Start pulse. */
*rps++ = S626_GPIO_BASE | S626_GPIO1_HI;
/* * Wait for the data from the last conversion of interest to arrive * in FB BUFFER 1 register.
*/
*rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2; /* Wait for ADC done. */
/* Transfer final ADC data from FB BUFFER 1 register to DMA buffer. */
*rps++ = S626_RPS_STREG | (S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2);
*rps++ = (u32)devpriv->ana_buf.physical_base +
(devpriv->adc_items << 2);
/* Indicate ADC scan loop is finished. */ /* Signal ReadADC() that scan is done. */ /* *rps++= S626_RPS_CLRSIGNAL | S626_RPS_SIGADC; */
/* Restart RPS program at its beginning. */
*rps++ = S626_RPS_JUMP; /* Branch to start of RPS program. */
*rps++ = (u32)devpriv->rps_buf.physical_base;
/* * Wait for ADC to complete (GPIO2 is asserted high when * ADC not busy) and for data from previous conversion to * shift into FB BUFFER 1 register.
*/
/* Wait for ADC done */
ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0); if (ret) return ret;
/* * Allow the ADC to stabilize for 4 microseconds before * starting the next (final) conversion. This delay is * necessary to allow sufficient time between last * conversion finished and the start of the next * conversion. Without this delay, the last conversion's * data value is sometimes set to the previous * conversion's data value.
*/
udelay(4);
}
/* * Start a dummy conversion to cause the data from the * previous conversion to be shifted in.
*/
gpio_image = readl(dev->mmio + S626_P_GPIO); /* Assert ADC Start command */
writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); /* and stretch it out */
writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
writel(gpio_image & ~S626_GPIO1_HI, dev->mmio + S626_P_GPIO); /* Negate ADC Start command */
writel(gpio_image | S626_GPIO1_HI, dev->mmio + S626_P_GPIO);
/* Wait for the data to arrive in FB BUFFER 1 register. */
/* Wait for ADC done */
ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0); if (ret) return ret;
/* Fetch ADC data from audio interface's input shift register. */
/* Start executing the RPS program */
s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
s->async->inttrig = NULL;
return 1;
}
/* * This function doesn't require a particular form, this is just what * happens to be used in some of the drivers. It should convert ns * nanoseconds to a counter value suitable for programming the device. * Also, it should adjust ns so that it cooresponds to the actual time * that the device will use.
*/ staticint s626_ns_to_timer(unsignedint *nanosec, unsignedint flags)
{ int divider, base;
base = 500; /* 2MHz internal clock */
switch (flags & CMDF_ROUND_MASK) { case CMDF_ROUND_NEAREST: default:
divider = DIV_ROUND_CLOSEST(*nanosec, base); break; case CMDF_ROUND_DOWN:
divider = (*nanosec) / base; break; case CMDF_ROUND_UP:
divider = DIV_ROUND_UP(*nanosec, base); break;
}
*nanosec = base * divider; return divider - 1;
}
staticvoid s626_timer_load(struct comedi_device *dev, unsignedint chan, int tick)
{
u16 setup = /* Preload upon index. */
S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) | /* Disable hardware index. */
S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) | /* Operating mode is Timer. */
S626_SET_STD_ENCMODE(S626_ENCMODE_TIMER) | /* Count direction is Down. */
S626_SET_STD_CLKPOL(S626_CNTDIR_DOWN) | /* Clock multiplier is 1x. */
S626_SET_STD_CLKMULT(S626_CLKMULT_1X) | /* Enabled by index */
S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
u16 value_latchsrc = S626_LATCHSRC_A_INDXA; /* uint16_t enab = S626_CLKENAB_ALWAYS; */
s626_set_mode(dev, chan, setup, false);
/* Set the preload register */
s626_preload(dev, chan, tick);
/* * Software index pulse forces the preload register to load * into the counter
*/
s626_set_load_trig(dev, chan, 0);
s626_pulse_index(dev, chan);
/* set reload on counter overflow */
s626_set_load_trig(dev, chan, 1);
/* set interrupt on overflow */
s626_set_int_src(dev, chan, S626_INTSRC_OVER);
switch (cmd->scan_begin_src) { case TRIG_FOLLOW: break; case TRIG_TIMER: /* * set a counter to generate adc trigger at scan_begin_arg * interval
*/
tick = s626_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
/* load timer value and enable interrupt */
s626_timer_load(dev, 5, tick);
s626_set_enable(dev, 5, S626_CLKENAB_ALWAYS); break; case TRIG_EXT: /* set the digital line and interrupt for scan trigger */ if (cmd->start_src != TRIG_EXT)
s626_dio_set_irq(dev, cmd->scan_begin_arg); break;
}
switch (cmd->convert_src) { case TRIG_NOW: break; case TRIG_TIMER: /* * set a counter to generate adc trigger at convert_arg * interval
*/
tick = s626_ns_to_timer(&cmd->convert_arg, cmd->flags);
/* load timer value and enable interrupt */
s626_timer_load(dev, 4, tick);
s626_set_enable(dev, 4, S626_CLKENAB_INDEX); break; case TRIG_EXT: /* set the digital line and interrupt for convert trigger */ if (cmd->scan_begin_src != TRIG_EXT &&
cmd->start_src == TRIG_EXT)
s626_dio_set_irq(dev, cmd->convert_arg); break;
}
for (i = 0; i < insn->n; i++) {
s16 dacdata = (s16)data[i]; int ret;
dacdata -= (0x1fff);
ret = s626_set_dac(dev, chan, dacdata); if (ret) return ret;
s->readback[chan] = data[i];
}
return insn->n;
}
/* *************** DIGITAL I/O FUNCTIONS *************** */
/* * All DIO functions address a group of DIO channels by means of * "group" argument. group may be 0, 1 or 2, which correspond to DIO * ports A, B and C, respectively.
*/
/* Prepare to treat writes to WRCapSel as capture disables. */
s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
/* For each group of sixteen channels ... */ for (group = 0; group < S626_DIO_BANKS; group++) { /* Disable all interrupts */
s626_debi_write(dev, S626_LP_WRINTSEL(group), 0); /* Disable all event captures */
s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff); /* Init all DIOs to default edge polarity */
s626_debi_write(dev, S626_LP_WREDGSEL(group), 0); /* Program all outputs to inactive state */
s626_debi_write(dev, S626_LP_WRDOUT(group), 0);
}
}
/* * Now this function initializes the value of the counter (data[0]) * and set the subdevice. To complete with trigger and interrupt * configuration. * * FIXME: data[0] is supposed to be an INSN_CONFIG_xxx constant indicating * what is being configured, but this function appears to be using data[0] * as a variable.
*/ staticint s626_enc_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsignedint *data)
{ unsignedint chan = CR_CHAN(insn->chanspec);
u16 setup = /* Preload upon index. */
S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) | /* Disable hardware index. */
S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) | /* Operating mode is Counter. */
S626_SET_STD_ENCMODE(S626_ENCMODE_COUNTER) | /* Active high clock. */
S626_SET_STD_CLKPOL(S626_CLKPOL_POS) | /* Clock multiplier is 1x. */
S626_SET_STD_CLKMULT(S626_CLKMULT_1X) | /* Enabled by index */
S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX); /* uint16_t disable_int_src = true; */ /* uint32_t Preloadvalue; //Counter initial value */
u16 value_latchsrc = S626_LATCHSRC_AB_READ;
u16 enab = S626_CLKENAB_ALWAYS;
/* Set the preload register */
s626_preload(dev, chan, data[0]);
/* * Software index pulse forces the preload register to load * into the counter
*/
s626_set_load_trig(dev, chan, 0);
s626_pulse_index(dev, chan);
s626_set_load_trig(dev, chan, 2);
/* * Configure DEBI operating mode * * Local bus is 16 bits wide * Declare DEBI transfer timeout interval * Set up byte lane steering * Intel-compatible local bus (DEBI never times out)
*/
writel(S626_DEBI_CFG_SLAVE16 |
(S626_DEBI_TOUT << S626_DEBI_CFG_TOUT_BIT) | S626_DEBI_SWAP |
S626_DEBI_CFG_INTEL, dev->mmio + S626_P_DEBICFG);
/* * Issue an I2C ABORT command to halt any I2C * operation in progress and reset BUSY flag.
*/
writel(S626_I2C_CLKSEL | S626_I2C_ABORT,
dev->mmio + S626_P_I2CSTAT);
s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0); if (ret) return ret;
/* * Per SAA7146 data sheet, write to STATUS * reg twice to reset all I2C error flags.
*/
--> --------------------
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.