/* * 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_