900 lines
26 KiB
C++
900 lines
26 KiB
C++
/*
|
|
* puzzlefw.hpp
|
|
*
|
|
* C++ library for PuzzleFW firmware.
|
|
*
|
|
* Joris van Rantwijk 2024
|
|
*/
|
|
|
|
#ifndef PUZZLEFW_H_
|
|
#define PUZZLEFW_H_
|
|
|
|
#include <stdint.h>
|
|
#include <stdexcept>
|
|
|
|
|
|
namespace puzzlefw {
|
|
|
|
/* Size of memory mapped register area. */
|
|
constexpr uint32_t REGS_SIZE = 0x1000;
|
|
|
|
/* Register addresses relative to register area base address. */
|
|
constexpr uint32_t REG_INFO = 0x000;
|
|
constexpr uint32_t REG_IRQ_ENABLE = 0x010;
|
|
constexpr uint32_t REG_IRQ_PENDING = 0x014;
|
|
constexpr uint32_t REG_DMA_EN = 0x100;
|
|
constexpr uint32_t REG_DMA_STATUS = 0x104;
|
|
constexpr uint32_t REG_DMA_CLEAR = 0x108;
|
|
constexpr uint32_t REG_TIMESTAMP_LO = 0x180;
|
|
constexpr uint32_t REG_TIMESTAMP_HI = 0x184;
|
|
constexpr uint32_t REG_TIMESTAMP_CLEAR = 0x188;
|
|
constexpr uint32_t REG_ACQ_ADDR_START = 0x200;
|
|
constexpr uint32_t REG_ACQ_ADDR_END = 0x204;
|
|
constexpr uint32_t REG_ACQ_ADDR_LIMIT = 0x208;
|
|
constexpr uint32_t REG_ACQ_ADDR_INTR = 0x20c;
|
|
constexpr uint32_t REG_ACQ_ADDR_PTR = 0x210;
|
|
constexpr uint32_t REG_ACQ_DMA_CTRL = 0x214;
|
|
constexpr uint32_t REG_ACQ_INTR_CTRL = 0x218;
|
|
constexpr uint32_t REG_ACQ_DMA_STATUS = 0x21c;
|
|
constexpr uint32_t REG_ACQUISITION_EN = 0x220;
|
|
constexpr uint32_t REG_RECORD_LENGTH = 0x224;
|
|
constexpr uint32_t REG_DECIMATION_FACTOR = 0x228;
|
|
constexpr uint32_t REG_SHIFT_STEPS = 0x22c;
|
|
constexpr uint32_t REG_AVERAGING_EN = 0x230;
|
|
constexpr uint32_t REG_CH4_MODE = 0x234;
|
|
constexpr uint32_t REG_SIMULATE_ADC = 0x238;
|
|
constexpr uint32_t REG_TRIGGER_MODE = 0x240;
|
|
constexpr uint32_t REG_TRIGGER_DELAY = 0x244;
|
|
constexpr uint32_t REG_TRIGGER_STATUS = 0x248;
|
|
constexpr uint32_t REG_ADC_SAMPLE = 0x280;
|
|
constexpr uint32_t REG_ADC23_SAMPLE = 0x284;
|
|
constexpr uint32_t REG_ADC_RANGE_CLEAR = 0x28c;
|
|
constexpr uint32_t REG_ADC0_MINMAX = 0x290;
|
|
constexpr uint32_t REG_ADC1_MINMAX = 0x294;
|
|
constexpr uint32_t REG_ADC2_MINMAX = 0x298;
|
|
constexpr uint32_t REG_ADC3_MINMAX = 0x29c;
|
|
constexpr uint32_t REG_TT_ADDR_START = 0x300;
|
|
constexpr uint32_t REG_TT_ADDR_END = 0x304;
|
|
constexpr uint32_t REG_TT_ADDR_LIMIT = 0x308;
|
|
constexpr uint32_t REG_TT_ADDR_INTR = 0x30c;
|
|
constexpr uint32_t REG_TT_ADDR_PTR = 0x310;
|
|
constexpr uint32_t REG_TT_DMA_CTRL = 0x314;
|
|
constexpr uint32_t REG_TT_INTR_CTRL = 0x318;
|
|
constexpr uint32_t REG_TT_DMA_STATUS = 0x31c;
|
|
constexpr uint32_t REG_TIMETAGGER_EN = 0x320;
|
|
constexpr uint32_t REG_TIMETAGGER_MARK = 0x324;
|
|
constexpr uint32_t REG_DIG_SIMULATE = 0x330;
|
|
constexpr uint32_t REG_DIG_SAMPLE = 0x338;
|
|
constexpr uint32_t REG_LED_STATE = 0x404;
|
|
|
|
/* Pending IRQ flags. */
|
|
constexpr uint32_t IRQ_PENDING_ACQ = 0x01;
|
|
constexpr uint32_t IRQ_PENDING_TT = 0x02;
|
|
|
|
/* DMA status flags. */
|
|
constexpr uint32_t DMA_STATUS_BUSY = 0x01;
|
|
constexpr uint32_t DMA_ERROR_READ = 0x02;
|
|
constexpr uint32_t DMA_ERROR_WRITE = 0x04;
|
|
constexpr uint32_t DMA_ERROR_ADDRESS = 0x08;
|
|
constexpr uint32_t DMA_ERROR_ANY = 0x10;
|
|
|
|
|
|
/** Synchronize between register access and DMA buffer access. */
|
|
static inline void sync_dma()
|
|
{
|
|
asm volatile ("dmb" : : : "memory");
|
|
}
|
|
|
|
|
|
/** Trigger modes. */
|
|
enum TriggerMode {
|
|
TRIG_NONE = 0, // trigger disabled, manual trigger only
|
|
TRIG_AUTO = 1, // continuous triggering
|
|
TRIG_EXTERNAL = 2 // external triggering
|
|
};
|
|
|
|
|
|
/** Firmware version information. */
|
|
struct VersionInfo {
|
|
uint8_t api_version;
|
|
uint8_t major_version;
|
|
uint8_t minor_version;
|
|
};
|
|
|
|
|
|
/**
|
|
* Device API for PuzzleFW firmware.
|
|
*
|
|
* This class uses the UIO driver framework and the custom PuzzleFW Linux
|
|
* kernel driver to access the PuzzleFW firmware.
|
|
*
|
|
* Only a single instance of this class should exist at any time in any
|
|
* process on the system. Creating multiple instances, even in separate
|
|
* processes, may cause the firmware to function incorrectly.
|
|
*
|
|
* Methods of this class may throw exceptions to report error conditions.
|
|
* "std::invalid_argument" is used to report invalid arguments.
|
|
* "std::runtime_error" is used to report errors during opening of the device.
|
|
*
|
|
* Unless stated otherwise, the methods of this class must not be called
|
|
* concurrently from multiple threads without explicit synchronization.
|
|
*/
|
|
class PuzzleFwDevice
|
|
{
|
|
public:
|
|
/** Minimum size of the DMA buffer. */
|
|
static constexpr size_t MIN_DMA_BUF_SIZE = 16384;
|
|
|
|
/** Maximum number of samples per trigger. */
|
|
static constexpr unsigned int MAX_DECIMATION_FACTOR = 1 << 18;
|
|
|
|
/** Maximum number of samples per trigger. */
|
|
static constexpr unsigned int MAX_RECORD_LENGTH = 1 << 16;
|
|
|
|
/** Maximum trigger delay. */
|
|
static constexpr unsigned int MAX_TRIGGER_DELAY = (1 << 16) - 1;
|
|
|
|
/** Constructor. Opens the device driver. */
|
|
PuzzleFwDevice();
|
|
|
|
// Delete copy constructor and assignment operator.
|
|
PuzzleFwDevice(const PuzzleFwDevice&) = delete;
|
|
PuzzleFwDevice& operator=(const PuzzleFwDevice&) = delete;
|
|
|
|
/** Destructor. Closes the device driver. */
|
|
~PuzzleFwDevice();
|
|
|
|
/**
|
|
* Return the file descriptor of the UIO device node.
|
|
*
|
|
* It can be used to detect FPGA interrupts.
|
|
*/
|
|
int uio_fd() const
|
|
{
|
|
return m_uio_fd;
|
|
}
|
|
|
|
/** Return a pointer to the DMA buffer. */
|
|
volatile void * dma_buffer() const
|
|
{
|
|
return m_dma_buf;
|
|
}
|
|
|
|
/** Return the DMA buffer size in bytes. */
|
|
size_t dma_buffer_size() const
|
|
{
|
|
return m_dma_buf_size;
|
|
}
|
|
|
|
/**
|
|
* Return the DMA address alignment.
|
|
*
|
|
* This alignment restriction applies to start address, end address,
|
|
* and the limit pointer of DMA stream buffers.
|
|
*/
|
|
size_t dma_alignment() const
|
|
{
|
|
return 128;
|
|
}
|
|
|
|
/** Read from FPGA register. */
|
|
uint32_t read_reg(uint32_t addr)
|
|
{
|
|
return m_regs[addr / 4];
|
|
}
|
|
|
|
/** Write to FPGA register. */
|
|
void write_reg(uint32_t addr, uint32_t value)
|
|
{
|
|
m_regs[addr / 4] = value;
|
|
}
|
|
|
|
/** Return firmware version information. */
|
|
VersionInfo get_version_info()
|
|
{
|
|
uint32_t v = read_reg(REG_INFO);
|
|
uint8_t api_version = (v >> 16) & 0xff;
|
|
uint8_t major_version = (v >> 8) & 0xff;
|
|
uint8_t minor_version = v & 0xff;
|
|
return VersionInfo{api_version, major_version, minor_version};
|
|
}
|
|
|
|
/** Return the number of analog channels (2 or 4). */
|
|
unsigned int get_analog_channel_count()
|
|
{
|
|
// TODO -- modify firmware to add ch4-support bit
|
|
uint32_t v = read_reg(REG_CH4_MODE);
|
|
return (v & 0x100) ? 4 : 2;
|
|
}
|
|
|
|
/** Return the current value of the timestamp counter. */
|
|
uint64_t get_timestamp()
|
|
{
|
|
uint32_t vhi, vlo;
|
|
vhi = read_reg(REG_TIMESTAMP_HI);
|
|
while (1) {
|
|
uint32_t vhi_prev = vhi;
|
|
vlo = read_reg(REG_TIMESTAMP_LO);
|
|
vhi = read_reg(REG_TIMESTAMP_HI);
|
|
if (vhi == vhi_prev) {
|
|
break;
|
|
}
|
|
}
|
|
return (((uint64_t)vhi) << 32) | vlo;
|
|
}
|
|
|
|
/** Clear the timestamp counter. */
|
|
void clear_timestamp()
|
|
{
|
|
write_reg(REG_TIMESTAMP_CLEAR, 1);
|
|
}
|
|
|
|
/** Return True if the selected LED is on. */
|
|
bool get_led_state(unsigned int led_index)
|
|
{
|
|
if (led_index > 7) {
|
|
throw std::invalid_argument("Invalid led_index");
|
|
}
|
|
uint32_t v = read_reg(REG_LED_STATE);
|
|
return ((v >> led_index) & 1) != 0;
|
|
}
|
|
|
|
/**
|
|
* Turn the specified LED on or off.
|
|
*
|
|
* Valid led_index values are 4 to 7.
|
|
*/
|
|
void set_led_state(unsigned int led_index, bool led_on)
|
|
{
|
|
if (led_index > 7) {
|
|
throw std::invalid_argument("Invalid led_index");
|
|
}
|
|
uint32_t v = read_reg(REG_LED_STATE);
|
|
uint32_t m = (1 << led_index);
|
|
if (led_on) {
|
|
v |= m;
|
|
} else {
|
|
v &= ~m;
|
|
}
|
|
write_reg(REG_LED_STATE, v);
|
|
}
|
|
|
|
/** Return the current trigger mode. */
|
|
TriggerMode get_trigger_mode()
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
if ((v & 0x01) != 0) {
|
|
return TRIG_AUTO;
|
|
}
|
|
if ((v & 0x02) != 0) {
|
|
return TRIG_EXTERNAL;
|
|
}
|
|
return TRIG_NONE;
|
|
}
|
|
|
|
/** Set trigger mode. */
|
|
void set_trigger_mode(TriggerMode mode)
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
v &= 0xfc;
|
|
if (mode == TRIG_AUTO) {
|
|
v |= 0x01;
|
|
}
|
|
if (mode == TRIG_EXTERNAL) {
|
|
v |= 0x02;
|
|
}
|
|
write_reg(REG_TRIGGER_MODE, v);
|
|
}
|
|
|
|
/** Return the selected external trigger channel. */
|
|
unsigned int get_trigger_ext_channel()
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
return ((v >> 4) & 7);
|
|
}
|
|
|
|
/** Select external trigger channel. */
|
|
void set_trigger_ext_channel(unsigned int channel)
|
|
{
|
|
if (channel > 7) {
|
|
throw std::invalid_argument("Invalid trigger channel");
|
|
}
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
v &= 0x8f;
|
|
v |= (channel << 4);
|
|
write_reg(REG_TRIGGER_MODE, v);
|
|
}
|
|
|
|
/** Return true if triggering on falling edge. */
|
|
bool get_trigger_ext_falling()
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
return ((v & 0x80) != 0);
|
|
}
|
|
|
|
/** Select active edge for external trigger signal. */
|
|
void set_trigger_ext_falling(bool falling)
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
v &= 0x7f;
|
|
if (falling) {
|
|
v |= 0x80;
|
|
}
|
|
write_reg(REG_TRIGGER_MODE, v);
|
|
}
|
|
|
|
/** Force a single trigger event. */
|
|
void trigger_force()
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_MODE);
|
|
write_reg(REG_TRIGGER_MODE, v | 0x100);
|
|
}
|
|
|
|
/** Return configured trigger delay (in ADC samples). */
|
|
unsigned int get_trigger_delay()
|
|
{
|
|
return read_reg(REG_TRIGGER_DELAY);
|
|
}
|
|
|
|
/** Set trigger delay (in ADC samples). */
|
|
void set_trigger_delay(unsigned int delay)
|
|
{
|
|
if (delay > MAX_TRIGGER_DELAY) {
|
|
throw std::invalid_argument("Invalid trigger delay");
|
|
}
|
|
write_reg(REG_TRIGGER_DELAY, delay);
|
|
}
|
|
|
|
/** Return true if analog acquisition is waiting for a trigger. */
|
|
bool is_waiting_for_trigger()
|
|
{
|
|
uint32_t v = read_reg(REG_TRIGGER_STATUS);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/** Return true if analog acquisition is enabled. */
|
|
bool is_acquisition_enabled()
|
|
{
|
|
uint32_t v = read_reg(REG_ACQUISITION_EN);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/** Enable or disable analog acquisition. */
|
|
void set_acquisition_enabled(bool enable)
|
|
{
|
|
write_reg(REG_ACQUISITION_EN, enable ? 1 : 0);
|
|
}
|
|
|
|
/** Return true if 4-channel mode is enabled. */
|
|
bool is_4channel_mode()
|
|
{
|
|
uint32_t v = read_reg(REG_CH4_MODE);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/** Enable or disable 4-channel mode. */
|
|
void set_4channel_mode(bool enable)
|
|
{
|
|
write_reg(REG_CH4_MODE, enable ? 1 : 0);
|
|
}
|
|
|
|
/** Return the decimation factor. */
|
|
unsigned int get_decimation_factor()
|
|
{
|
|
uint32_t v = read_reg(REG_DECIMATION_FACTOR);
|
|
return v + 1;
|
|
}
|
|
|
|
/**
|
|
* Set the decimation factor (number of ADC samples per decimated sample).
|
|
*
|
|
* Valid values are 1 to MAX_DECIMATION_FACTOR.
|
|
* This method writes the specified value minus 1 to the FPGA register.
|
|
*/
|
|
void set_decimation_factor(unsigned int decimation_factor)
|
|
{
|
|
if (decimation_factor == 0
|
|
|| decimation_factor > MAX_DECIMATION_FACTOR) {
|
|
throw std::invalid_argument("Invalid decimation factor");
|
|
}
|
|
write_reg(REG_DECIMATION_FACTOR, decimation_factor - 1);
|
|
}
|
|
|
|
/** Return true if sample averaging is enabled. */
|
|
bool is_averaging_enabled()
|
|
{
|
|
uint32_t v = read_reg(REG_AVERAGING_EN);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/** Enable or disable sample averaging. */
|
|
void set_averaging_enabled(bool enable)
|
|
{
|
|
write_reg(REG_AVERAGING_EN, enable ? 1 : 0);
|
|
}
|
|
|
|
/** Return the number of shift-right positions for decimated samples. */
|
|
int get_shift_steps()
|
|
{
|
|
return read_reg(REG_SHIFT_STEPS);
|
|
}
|
|
|
|
/**
|
|
* Set the number of shift-right positions for decimated samples.
|
|
*
|
|
* Valid values are 0 to 8 steps.
|
|
*/
|
|
void set_shift_steps(int shift_steps)
|
|
{
|
|
if (shift_steps < 0 || shift_steps > 8) {
|
|
throw std::invalid_argument("Invalid shift_steps");
|
|
}
|
|
write_reg(REG_SHIFT_STEPS, shift_steps);
|
|
}
|
|
|
|
/** Return the record length (number of samples per trigger). */
|
|
unsigned int get_record_length()
|
|
{
|
|
return read_reg(REG_RECORD_LENGTH) + 1;
|
|
}
|
|
|
|
/**
|
|
* Set record length (number of samples per trigger).
|
|
*
|
|
* Valid values are 1 to MAX_RECORD_LENGTH.
|
|
* This method writes the specified value minus 1 to the FPGA register.
|
|
*/
|
|
void set_record_length(unsigned int samples)
|
|
{
|
|
if (samples == 0 || samples > MAX_RECORD_LENGTH) {
|
|
throw std::invalid_argument("Invalid record length");
|
|
}
|
|
write_reg(REG_RECORD_LENGTH, samples - 1);
|
|
}
|
|
|
|
/** Return the most recent ADC sample for the specified channel. */
|
|
unsigned int get_adc_sample(unsigned int channel)
|
|
{
|
|
if (channel >= 4) {
|
|
throw std::invalid_argument("Invalid ADC channel");
|
|
}
|
|
uint32_t addr = (channel < 2) ? REG_ADC_SAMPLE : REG_ADC23_SAMPLE;
|
|
uint32_t v = read_reg(addr);
|
|
return (v >> (16 * (channel & 1))) & 0x3fff;
|
|
}
|
|
|
|
/** Return minimum and maximum sample value sinc last cleared. */
|
|
void get_adc_range(unsigned int channel,
|
|
unsigned int& min_sample,
|
|
unsigned int& max_sample)
|
|
{
|
|
if (channel >= 4) {
|
|
throw std::invalid_argument("Invalid ADC channel");
|
|
}
|
|
uint32_t addr = (channel == 0) ? REG_ADC0_MINMAX :
|
|
(channel == 1) ? REG_ADC1_MINMAX :
|
|
(channel == 2) ? REG_ADC2_MINMAX :
|
|
REG_ADC3_MINMAX;
|
|
uint32_t v = read_reg(addr);
|
|
min_sample = v & 0x3fff;
|
|
max_sample = (v >> 16) & 0x3fff;
|
|
}
|
|
|
|
/** Clear minimum/maximum sample ranges. */
|
|
void clear_adc_range()
|
|
{
|
|
write_reg(REG_ADC_RANGE_CLEAR, 1);
|
|
}
|
|
|
|
/** Return true if ADC sample simulation is enabled. */
|
|
bool is_adc_simulation_enabled()
|
|
{
|
|
uint32_t v = read_reg(REG_SIMULATE_ADC);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/** Enable or disable ADC sample simulation. */
|
|
void set_adc_simulation_enabled(bool enable)
|
|
{
|
|
write_reg(REG_SIMULATE_ADC, enable ? 1 : 0);
|
|
}
|
|
|
|
/** Return the mask of enabled timetagger event types. */
|
|
uint32_t get_timetagger_event_mask()
|
|
{
|
|
return read_reg(REG_TIMETAGGER_EN);
|
|
}
|
|
|
|
/**
|
|
* Set mask of enabled timetagger event types.
|
|
*
|
|
* Bit 0 enables rising edges on digital input channel 0.
|
|
* Bit 1 enables falling edges on digital input channel 0.
|
|
* Bit 2 enables rising edges on digital input channel 1.
|
|
* And so on.
|
|
*/
|
|
void set_timetagger_event_mask(uint32_t event_mask)
|
|
{
|
|
write_reg(REG_TIMETAGGER_EN, event_mask);
|
|
}
|
|
|
|
/** Request a marker record in the timetagger event stream. */
|
|
void timetagger_mark()
|
|
{
|
|
write_reg(REG_TIMETAGGER_MARK, 1);
|
|
}
|
|
|
|
/** Return a bitmask representing the current digital input state. */
|
|
uint32_t get_digital_input_state()
|
|
{
|
|
return read_reg(REG_DIG_SAMPLE);
|
|
}
|
|
|
|
/** Return true if digital signal simulation is enabled. */
|
|
bool is_digital_simulation_enabled()
|
|
{
|
|
uint32_t v = read_reg(REG_DIG_SIMULATE);
|
|
return ((v & 0x100) != 0);
|
|
}
|
|
|
|
/** Enable or disable digital signal simulation. */
|
|
void set_digital_simulation_enabled(bool enable)
|
|
{
|
|
uint32_t v = read_reg(REG_DIG_SIMULATE);
|
|
if (enable) {
|
|
v |= 0x100;
|
|
} else {
|
|
v &= 0xff;
|
|
}
|
|
write_reg(REG_DIG_SIMULATE, v);
|
|
}
|
|
|
|
/** Return simulated digital input state. */
|
|
uint32_t get_digital_simulation_state()
|
|
{
|
|
uint32_t v = read_reg(REG_DIG_SIMULATE);
|
|
return v & 0xff;
|
|
}
|
|
|
|
/** Set simulated digital input state. */
|
|
void set_digital_simulation_state(uint32_t mask)
|
|
{
|
|
uint32_t v = read_reg(REG_DIG_SIMULATE);
|
|
v &= 0x100;
|
|
v |= mask;
|
|
write_reg(REG_DIG_SIMULATE, v);
|
|
}
|
|
|
|
/** Return true if FPGA interrupts are enabled. */
|
|
bool is_irq_enabled()
|
|
{
|
|
uint32_t v = read_reg(REG_IRQ_ENABLE);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/**
|
|
* Enable FPGA interrupts.
|
|
*
|
|
* The Linux kernel driver will automatically disable interrupts
|
|
* whenever an interrupt occurs.
|
|
*
|
|
* It is safe to call this function concurrently from multiple threads.
|
|
*/
|
|
void enable_irq()
|
|
{
|
|
write_reg(REG_IRQ_ENABLE, 1);
|
|
}
|
|
|
|
/**
|
|
* Return bit mask of pending interrupt conditions.
|
|
*
|
|
* It is safe to call this function concurrently from multiple threads.
|
|
*/
|
|
uint32_t get_irq_pending()
|
|
{
|
|
return read_reg(REG_IRQ_PENDING);
|
|
}
|
|
|
|
/** Return true if the FPGA DMA engine is enabled. */
|
|
bool is_dma_enabled()
|
|
{
|
|
uint32_t v = read_reg(REG_DMA_EN);
|
|
return ((v & 1) != 0);
|
|
}
|
|
|
|
/** Enable or disable the FPGA DMA engine. */
|
|
void set_dma_enabled(bool enable)
|
|
{
|
|
write_reg(REG_DMA_EN, enable ? 1 : 0);
|
|
}
|
|
|
|
/** Return bit mask of DMA status and error flags. */
|
|
uint32_t get_dma_status()
|
|
{
|
|
return read_reg(REG_DMA_STATUS);
|
|
}
|
|
|
|
/**
|
|
* Clear pending DMA error flags.
|
|
*
|
|
* Before doing this, any active DMA data streams should be stopped.
|
|
*/
|
|
void clear_dma_errors()
|
|
{
|
|
write_reg(REG_DMA_CLEAR, 1);
|
|
}
|
|
|
|
private:
|
|
/** File descriptor of the UIO device. */
|
|
int m_uio_fd;
|
|
|
|
/** Pointer to device registers mapped in process address space. */
|
|
volatile uint32_t * m_regs;
|
|
|
|
/** Pointer to DMA buffer mapped in process address space. */
|
|
volatile void * m_dma_buf;
|
|
|
|
/** Size of DMA buffer (in bytes). */
|
|
size_t m_dma_buf_size;
|
|
|
|
/** Full name of the PuzzleFW platform device. */
|
|
std::string m_device_name;
|
|
|
|
/** Path name of UIO device node. */
|
|
std::string m_uio_path;
|
|
};
|
|
|
|
|
|
/**
|
|
* Manage a DMA data write stream.
|
|
*
|
|
* The PuzzleFW firmware provides two DMA write streams: one stream for
|
|
* analog sample data and one stream for timetagger data.
|
|
*
|
|
* Only a single instance of this class should exist for a specific
|
|
* DMA stream type at any time. I.e. at most one instance should exist
|
|
* for analog sample data, and at most one instance for timetagger data.
|
|
*
|
|
* Each DMA stream uses a segment within the global DMA buffer.
|
|
* Users of this class must make sure that the stream buffer segment
|
|
* is located within the global DMA buffer, and that it does not overlap
|
|
* with any buffer segment for another DMA stream.
|
|
*
|
|
* An instance of this class must not be accessed concurrently from
|
|
* multiple threads without explicit synchronization. However, multiple
|
|
* threads may safely access different instances of this class.
|
|
*/
|
|
class DmaWriteStream
|
|
{
|
|
public:
|
|
enum StreamType { DMA_ACQ = 1, DMA_TT };
|
|
|
|
/** Keep this number of bytes unused in the DMA stream buffer segment. */
|
|
static constexpr size_t POINTER_MARGIN = 4096;
|
|
|
|
/**
|
|
* Initialize DMA stream.
|
|
*
|
|
* The DMA stream is initially disabled.
|
|
*
|
|
* Parameters "buf_start" and "buf_end" specify the start and end offset
|
|
* of the buffer segment, relative to the start of the global DMA buffer.
|
|
* These must be aligned to the DMA address alignment as returned by
|
|
* "PuzzleFwDevice::dma_alignment()".
|
|
*
|
|
* The DMA stream instance keeps an internal pointer to the device
|
|
* instance. The device instance must remain valid for the life time
|
|
* of the DMA stream instance.
|
|
*/
|
|
DmaWriteStream(PuzzleFwDevice& device,
|
|
StreamType stream_type,
|
|
size_t buf_start,
|
|
size_t buf_end)
|
|
: m_device(device)
|
|
, m_reg((stream_type == DMA_ACQ) ? regs_acq : regs_tt)
|
|
, m_irq_pending_mask((stream_type == DMA_ACQ) ?
|
|
IRQ_PENDING_ACQ : IRQ_PENDING_TT)
|
|
, m_buf_start(buf_start)
|
|
, m_buf_end(buf_end)
|
|
, m_read_pointer(buf_start)
|
|
{
|
|
if (buf_start >= buf_end
|
|
|| buf_end - buf_start < 4 * POINTER_MARGIN
|
|
|| buf_end > m_device.dma_buffer_size()
|
|
|| buf_start % m_device.dma_alignment() != 0
|
|
|| buf_end % m_device.dma_alignment() != 0) {
|
|
throw std::invalid_argument("Invalid buffer segment");
|
|
}
|
|
|
|
// Disable DMA stream.
|
|
m_device.write_reg(m_reg.dma_ctrl, 0);
|
|
|
|
// Disable DMA stream interrupts and clear pending interrupts.
|
|
disable_interrupt();
|
|
|
|
// Set up DMA buffer segment.
|
|
m_device.write_reg(m_reg.addr_start, m_buf_start);
|
|
m_device.write_reg(m_reg.addr_end, m_buf_end);
|
|
m_device.write_reg(m_reg.addr_limit, m_buf_end - POINTER_MARGIN);
|
|
|
|
// Initialize DMA stream (reset write pointer to start of segment).
|
|
m_device.write_reg(m_reg.dma_ctrl, 2);
|
|
}
|
|
|
|
// Delete copy constructor and assignment operator.
|
|
DmaWriteStream(const DmaWriteStream&) = delete;
|
|
DmaWriteStream& operator=(const DmaWriteStream&) = delete;
|
|
|
|
/** Disable DMA stream. Remaining data in buffer is discarded. */
|
|
~DmaWriteStream()
|
|
{
|
|
// Disable DMA stream and re-initialize.
|
|
m_device.write_reg(m_reg.dma_ctrl, 0);
|
|
m_device.write_reg(m_reg.dma_ctrl, 2);
|
|
|
|
// Disable DMA stream interrupt and clear pending interrupt.
|
|
disable_interrupt();
|
|
}
|
|
|
|
/** Return the size of the DMA buffer segment. */
|
|
size_t buffer_segment_size() const
|
|
{
|
|
return m_buf_end - m_buf_start;
|
|
}
|
|
|
|
/** Return the DMA address alignment. */
|
|
size_t dma_alignment() const
|
|
{
|
|
return m_device.dma_alignment();
|
|
}
|
|
|
|
/** Enable or disable the DMA stream. */
|
|
void set_enabled(bool enable)
|
|
{
|
|
m_device.write_reg(m_reg.dma_ctrl, enable ? 1 : 0);
|
|
}
|
|
|
|
/** Return the number of bytes available in the DMA buffer. */
|
|
size_t get_data_available()
|
|
{
|
|
uint32_t write_pointer = m_device.read_reg(m_reg.addr_ptr);
|
|
if (write_pointer >= m_read_pointer) {
|
|
return write_pointer - m_read_pointer;
|
|
} else {
|
|
return m_buf_end - m_buf_start + write_pointer - m_read_pointer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a contiguous block of available data in the DMA buffer.
|
|
*
|
|
* This function may return a subset of the available data.
|
|
*/
|
|
void get_data_block(void *& ptr, size_t& n)
|
|
{
|
|
uint32_t write_pointer = m_device.read_reg(m_reg.addr_ptr);
|
|
|
|
// Ensure that upcoming memory access to the buffer occurs AFTER
|
|
// reading the pointer register.
|
|
sync_dma();
|
|
|
|
ptr = (char *)(m_device.dma_buffer()) + m_read_pointer;
|
|
if (write_pointer >= m_read_pointer) {
|
|
n = write_pointer - m_read_pointer;
|
|
} else {
|
|
n = m_buf_end - m_read_pointer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Release the specified number of bytes from the buffer.
|
|
*
|
|
* Update the read pointer and allow the DMA engine to reuse
|
|
* the released range for new data.
|
|
*
|
|
* The specified size must be a multiple of 8 bytes.
|
|
*/
|
|
void consume_data(size_t n)
|
|
{
|
|
if ((n & 7) != 0) {
|
|
throw std::invalid_argument("Invalid size");
|
|
}
|
|
|
|
// Update read pointer.
|
|
if (n < m_buf_end - m_read_pointer) {
|
|
m_read_pointer += n;
|
|
} else {
|
|
m_read_pointer = m_read_pointer + n - m_buf_end + m_buf_start;
|
|
}
|
|
|
|
// Update limit address.
|
|
// Round down to aligned address.
|
|
uint32_t limit;
|
|
if (m_read_pointer - m_buf_start < POINTER_MARGIN) {
|
|
limit = m_read_pointer - m_buf_start + m_buf_end - POINTER_MARGIN;
|
|
} else {
|
|
limit = m_read_pointer - POINTER_MARGIN;
|
|
}
|
|
limit &= ~((uint32_t)127);
|
|
|
|
sync_dma();
|
|
|
|
m_device.write_reg(m_reg.addr_limit, limit);
|
|
}
|
|
|
|
/** Return true if the interrupt for this DMA stream is pending. */
|
|
bool is_interrupt_pending()
|
|
{
|
|
uint32_t irq_pending = m_device.get_irq_pending();
|
|
return (irq_pending & m_irq_pending_mask) != 0;
|
|
}
|
|
|
|
/** Enable interrupt when specified amount of data is available. */
|
|
void enable_interrupt_on_data_available(size_t n)
|
|
{
|
|
// Update interrupt pointer.
|
|
uint32_t threshold;
|
|
if (m_buf_end - m_read_pointer > n) {
|
|
threshold = m_read_pointer + n;
|
|
} else {
|
|
threshold = m_read_pointer + n - m_buf_end + m_buf_start;
|
|
}
|
|
m_device.write_reg(m_reg.addr_intr, threshold);
|
|
|
|
// Enable interrupt.
|
|
m_device.write_reg(m_reg.intr_ctrl, 1);
|
|
}
|
|
|
|
/** Disable interrupt and clear pending interrupt for this DMA stream. */
|
|
void disable_interrupt()
|
|
{
|
|
m_device.write_reg(m_reg.intr_ctrl, 2);
|
|
}
|
|
|
|
private:
|
|
struct RegisterAddress {
|
|
uint32_t addr_start;
|
|
uint32_t addr_end;
|
|
uint32_t addr_limit;
|
|
uint32_t addr_intr;
|
|
uint32_t addr_ptr;
|
|
uint32_t dma_ctrl;
|
|
uint32_t intr_ctrl;
|
|
uint32_t dma_status;
|
|
};
|
|
|
|
static constexpr RegisterAddress regs_acq {
|
|
REG_ACQ_ADDR_START,
|
|
REG_ACQ_ADDR_END,
|
|
REG_ACQ_ADDR_LIMIT,
|
|
REG_ACQ_ADDR_INTR,
|
|
REG_ACQ_ADDR_PTR,
|
|
REG_ACQ_DMA_CTRL,
|
|
REG_ACQ_INTR_CTRL,
|
|
REG_ACQ_DMA_STATUS
|
|
};
|
|
|
|
static constexpr RegisterAddress regs_tt {
|
|
REG_TT_ADDR_START,
|
|
REG_TT_ADDR_END,
|
|
REG_TT_ADDR_LIMIT,
|
|
REG_TT_ADDR_INTR,
|
|
REG_TT_ADDR_PTR,
|
|
REG_TT_DMA_CTRL,
|
|
REG_TT_INTR_CTRL,
|
|
REG_TT_DMA_STATUS
|
|
};
|
|
|
|
PuzzleFwDevice& m_device;
|
|
const RegisterAddress m_reg;
|
|
const uint32_t m_irq_pending_mask;
|
|
const uint32_t m_buf_start;
|
|
const uint32_t m_buf_end;
|
|
uint32_t m_read_pointer;
|
|
};
|
|
|
|
|
|
}; // namespace puzzlefw
|
|
|
|
#endif // PUZZLEFW_H_
|