diff --git a/docs/DMA_PWM.md b/docs/DMA_PWM.md index 460531d..d7cf04e 100644 --- a/docs/DMA_PWM.md +++ b/docs/DMA_PWM.md @@ -6,6 +6,28 @@ This document describes the DMA (Direct Memory Access) and PWM (Pulse Width Modu The DMA implementation provides hardware-accelerated data transfer without CPU intervention. +### DMA Engine Types + +The BCM2711 (Raspberry Pi 4) has three types of DMA engines, each optimized for different use cases: + +#### 1. Standard DMA (Channels 0-6, 8-10) +- Full-featured DMA with 32-bit addressing +- Supports complex transfers, 2D mode, peripheral mapping +- Best for general-purpose DMA operations +- Class: `rpl::Dma` + +#### 2. DMA Lite (Channel 7) +- Simplified DMA with reduced features +- Lower resource usage for basic operations +- Best for simple memory-to-memory transfers +- Class: `rpl::DmaLite` + +#### 3. DMA4 (Channels 11-14) +- Enhanced DMA with 40-bit addressing +- Supports full 4GB+ memory access +- Best for systems with >1GB RAM +- Class: `rpl::Dma4` + ### Features - Support for all 15 DMA channels (0-14) @@ -15,14 +37,18 @@ The DMA implementation provides hardware-accelerated data transfer without CPU i - Physical memory allocation and management - Control block chains for complex transfers - Priority and panic priority configuration +- Compile-time dispatch (no virtual functions) +- Shared common functionality via CRTP base class + +### Usage Examples -### Usage Example +#### Standard DMA (32-bit addressing) ```cpp #include "rpl4/peripheral/dma.hpp" #include "rpl4/system/dma_memory.hpp" -// Get DMA instance +// Get Standard DMA instance (channels 0-6, 8-10) auto dma = rpl::Dma::GetInstance(rpl::Dma::Channel::kChannel0); // Allocate physical memory @@ -50,6 +76,32 @@ dma_memory.Free(src_buffer); dma_memory.Free(dst_buffer); ``` +#### DMA Lite (simplified) + +```cpp +// Get DMA Lite instance (channel 7) +auto dma = rpl::DmaLite::GetInstance(rpl::DmaLite::Channel::kChannel7); + +// Same API as Standard DMA, but with reduced features +auto* cb = dma_memory.AllocateObject(); +rpl::DmaLite::ConfigureMemoryToMemory(cb, src_phys, dst_phys, size); +``` + +#### DMA4 (40-bit addressing) + +```cpp +// Get DMA4 instance (channels 11-14) +auto dma = rpl::Dma4::GetInstance(rpl::Dma4::Channel::kChannel11); + +// DMA4 uses 64-bit addresses for 40-bit addressing support +auto* cb = dma_memory.AllocateObject(); +rpl::Dma4::ConfigureMemoryToMemory( + cb, + static_cast(src_phys), // 40-bit source + static_cast(dst_phys), // 40-bit destination + size); +``` + ### DMA Memory Management The `DmaMemory` class manages physical memory allocation: @@ -59,12 +111,21 @@ The `DmaMemory` class manages physical memory allocation: - Supports typed object allocation with `AllocateObject()` - Automatic 32-byte alignment for DMA control blocks -### DMA Channels +### DMA Channel Assignment -- Channels 0-14 are available -- Channel 15 is reserved for the system -- Lite channels (7-10) have reduced features +| Channel(s) | Type | Class | Features | +|------------|---------------|-----------------|---------------------------------------------| +| 0-6 | Standard DMA | `Dma` | Full-featured, 32-bit addressing | +| 7 | DMA Lite | `DmaLite` | Simplified, reduced features | +| 8-10 | Standard DMA | `Dma` | Full-featured, 32-bit addressing | +| 11-14 | DMA4 | `Dma4` | Enhanced, 40-bit addressing, >1GB RAM | +| 15 | DMA Lite | Not mapped | Reserved/not available in current impl. | + +**Notes:** - Channel 4 is often used by the firmware +- DMA4 channels (11-14) are recommended for systems with >1GB RAM +- All types share common interface via `DmaBase` CRTP template +- No virtual functions - all dispatch at compile time ## PWM (Pulse Width Modulation) @@ -158,7 +219,19 @@ pwm->InitializeClock(25000000); ### dma_example.cpp -Demonstrates basic DMA memory-to-memory transfer with data verification. +Demonstrates basic DMA memory-to-memory transfer with data verification using Standard DMA. + +### dma_types_example.cpp + +**NEW:** Comprehensive example demonstrating all three DMA types: +- Standard DMA (Channel 0) - Full-featured 32-bit DMA +- DMA Lite (Channel 7) - Simplified DMA for basic operations +- DMA4 (Channel 11) - Enhanced DMA with 40-bit addressing + +Each type performs a memory-to-memory transfer with: +- Performance measurement and throughput calculation +- Data verification +- Comparison of different DMA engine capabilities ### pwm_dma_example.cpp @@ -173,10 +246,43 @@ Basic PWM usage with manual duty cycle control (backward compatible). - [BCM2711 Peripherals Datasheet](https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf) - [rpi_ws281x DMA Example](https://github.com/jgarff/rpi_ws281x) -## Notes +## Architecture Notes + +### CRTP Design Pattern + +The DMA implementation uses the Curiously Recurring Template Pattern (CRTP) to: +- Share common functionality across DMA types +- Avoid virtual function overhead +- Enable compile-time dispatch +- Maintain type safety + +```cpp +// Base class uses CRTP +template +class DmaBase { ... }; + +// Derived classes inherit from base +class Dma : public DmaBase { ... }; +class DmaLite : public DmaBase { ... }; +class Dma4 : public DmaBase { ... }; +``` + +### Type Traits + +Each DMA type has associated traits: +```cpp +template struct DmaTraits; +// Provides: RegisterMapType, ControlBlockType, kName +``` + +This allows compile-time customization without runtime overhead. + +## General Notes - DMA requires physical memory addresses, not virtual addresses - Control blocks must be 32-byte aligned +- DMA4 control block addresses are shifted right by 5 (32-byte alignment) - PWM FIFO can hold up to 16 32-bit values - DMA channel availability depends on system configuration - Root privileges required for /dev/mem and /dev/vcio access +- Backward compatibility maintained - existing code continues to work diff --git a/example/dma_types_example.cpp b/example/dma_types_example.cpp new file mode 100644 index 0000000..cb53e05 --- /dev/null +++ b/example/dma_types_example.cpp @@ -0,0 +1,230 @@ +#include +#include +#include +#include + +#include "rpl4/peripheral/dma.hpp" +#include "rpl4/rpl4.hpp" +#include "rpl4/system/dma_memory.hpp" + +/** + * @brief DMA Types Example + * @details Demonstrates the three types of DMA available on BCM2711: + * 1. Standard DMA (Channels 0-6, 8-10): Full-featured 32-bit DMA + * 2. DMA Lite (Channel 7): Simplified DMA for lighter workloads + * 3. DMA4 (Channels 11-14): Enhanced DMA with 40-bit addressing + * + * This example performs memory-to-memory transfers with each DMA type + * to show their usage and differences. + */ + +// Test data size +constexpr size_t kBufferSize = 256; + +// Helper to perform and verify a DMA transfer +template +bool TestDmaTransfer(std::shared_ptr dma, + rpl::DmaMemory& dma_memory, + const char* dma_name) { + std::cout << "\n=== Testing " << dma_name << " ===" << std::endl; + std::cout << "Channel: " << static_cast(dma->GetChannel()) << std::endl; + std::cout << "Type: " << dma->GetTypeName() << std::endl; + + // Allocate source and destination buffers + uint32_t* src_buffer = static_cast( + dma_memory.Allocate(kBufferSize * sizeof(uint32_t))); + uint32_t* dst_buffer = static_cast( + dma_memory.Allocate(kBufferSize * sizeof(uint32_t))); + + if (src_buffer == nullptr || dst_buffer == nullptr) { + std::cerr << "Failed to allocate DMA memory" << std::endl; + return false; + } + + // Initialize buffers + for (size_t i = 0; i < kBufferSize; i++) { + src_buffer[i] = 0xDEAD0000 + i; // Test pattern + dst_buffer[i] = 0; + } + + // Get physical addresses + uint32_t src_physical = dma_memory.GetPhysicalAddress(src_buffer); + uint32_t dst_physical = dma_memory.GetPhysicalAddress(dst_buffer); + + std::cout << "Source: virtual=0x" << std::hex << src_buffer + << ", physical=0x" << src_physical << std::endl; + std::cout << "Dest: virtual=0x" << std::hex << dst_buffer + << ", physical=0x" << dst_physical << std::endl; + + // Allocate and configure control block + auto* control_block = dma_memory.AllocateObject(); + if (control_block == nullptr) { + std::cerr << "Failed to allocate control block" << std::endl; + dma_memory.Free(src_buffer); + dma_memory.Free(dst_buffer); + return false; + } + + // Configure the transfer based on DMA type + if constexpr (std::is_same_v) { + // DMA4 uses 40-bit addresses + DmaType::ConfigureMemoryToMemory( + control_block, + static_cast(src_physical), + static_cast(dst_physical), + kBufferSize * sizeof(uint32_t)); + } else { + // Standard DMA and DMA Lite use 32-bit addresses + DmaType::ConfigureMemoryToMemory( + control_block, src_physical, dst_physical, + kBufferSize * sizeof(uint32_t)); + } + + uint32_t cb_physical = dma_memory.GetPhysicalAddress(control_block); + std::cout << "Control block physical: 0x" << std::hex << cb_physical + << std::endl; + + // Enable and configure DMA + dma->Enable(); + dma->Reset(); + dma->SetControlBlockAddress(cb_physical); + dma->SetPriority(8); + + // Measure transfer time + auto start_time = std::chrono::high_resolution_clock::now(); + + // Start transfer + std::cout << "Starting transfer..." << std::endl; + dma->Start(); + + // Wait for completion + bool success = dma->WaitForCompletion(1000); + + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast( + end_time - start_time); + + if (success) { + std::cout << "Transfer completed in " << std::dec << duration.count() + << " microseconds" << std::endl; + + // Verify data + bool verify_success = true; + for (size_t i = 0; i < kBufferSize; i++) { + if (dst_buffer[i] != src_buffer[i]) { + std::cerr << "Data mismatch at index " << std::dec << i << ": " + << "expected 0x" << std::hex << src_buffer[i] + << ", got 0x" << dst_buffer[i] << std::endl; + verify_success = false; + break; + } + } + + if (verify_success) { + std::cout << "✓ Data verification passed!" << std::endl; + std::cout << "Transferred " << std::dec + << (kBufferSize * sizeof(uint32_t)) << " bytes" << std::endl; + + // Calculate throughput + double bytes_per_sec = (kBufferSize * sizeof(uint32_t) * 1000000.0) / + duration.count(); + std::cout << "Throughput: " << (bytes_per_sec / (1024.0 * 1024.0)) + << " MB/s" << std::endl; + } else { + std::cerr << "✗ Data verification failed!" << std::endl; + success = false; + } + } else { + std::cerr << "✗ Transfer failed or timed out!" << std::endl; + } + + // Clean up + dma->Disable(); + dma_memory.FreeObject(control_block); + dma_memory.Free(src_buffer); + dma_memory.Free(dst_buffer); + + return success; +} + +int main(void) { + rpl::Init(); + + std::cout << "========================================" << std::endl; + std::cout << " BCM2711 DMA Types Demonstration" << std::endl; + std::cout << "========================================" << std::endl; + std::cout << "\nThis example demonstrates the three types of DMA" << std::endl; + std::cout << "available on the Raspberry Pi 4 (BCM2711):" << std::endl; + std::cout << "\n1. Standard DMA (Channels 0-6, 8-10):" << std::endl; + std::cout << " - Full-featured DMA with 32-bit addressing" << std::endl; + std::cout << " - Supports complex transfers, 2D mode, peripheral mapping" + << std::endl; + std::cout << "\n2. DMA Lite (Channel 7):" << std::endl; + std::cout << " - Simplified DMA for basic operations" << std::endl; + std::cout << " - Reduced features, suitable for simple transfers" + << std::endl; + std::cout << "\n3. DMA4 (Channels 11-14):" << std::endl; + std::cout << " - Enhanced DMA with 40-bit addressing" << std::endl; + std::cout << " - Supports full 4GB+ memory access" << std::endl; + std::cout << " - Best for systems with >1GB RAM" << std::endl; + std::cout << "\n========================================\n" << std::endl; + + auto& dma_memory = rpl::DmaMemory::GetInstance(); + bool all_tests_passed = true; + + // Test 1: Standard DMA (Channel 0) + auto dma_standard = rpl::Dma::GetInstance(rpl::Dma::Channel::kChannel0); + if (dma_standard) { + all_tests_passed &= TestDmaTransfer( + dma_standard, dma_memory, "Standard DMA"); + } else { + std::cerr << "Failed to get Standard DMA instance" << std::endl; + all_tests_passed = false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Test 2: DMA Lite (Channel 7) + auto dma_lite = rpl::DmaLite::GetInstance(rpl::DmaLite::Channel::kChannel7); + if (dma_lite) { + all_tests_passed &= TestDmaTransfer( + dma_lite, dma_memory, "DMA Lite"); + } else { + std::cerr << "Failed to get DMA Lite instance" << std::endl; + all_tests_passed = false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Test 3: DMA4 (Channel 11) + auto dma4 = rpl::Dma4::GetInstance(rpl::Dma4::Channel::kChannel11); + if (dma4) { + all_tests_passed &= TestDmaTransfer( + dma4, dma_memory, "DMA4 (40-bit addressing)"); + } else { + std::cerr << "Failed to get DMA4 instance" << std::endl; + all_tests_passed = false; + } + + // Summary + std::cout << "\n========================================" << std::endl; + std::cout << " Test Summary" << std::endl; + std::cout << "========================================" << std::endl; + if (all_tests_passed) { + std::cout << "✓ All DMA types tested successfully!" << std::endl; + std::cout << "\nKey Observations:" << std::endl; + std::cout << "- All three DMA types share common interface via base class" + << std::endl; + std::cout << "- No virtual functions - all dispatch at compile time" + << std::endl; + std::cout << "- DMA4 supports 40-bit addresses for full memory access" + << std::endl; + std::cout << "- Performance varies by hardware and system load" + << std::endl; + } else { + std::cerr << "✗ Some tests failed!" << std::endl; + } + std::cout << "========================================\n" << std::endl; + + return all_tests_passed ? 0 : 1; +} diff --git a/include/rpl4/peripheral/dma.hpp b/include/rpl4/peripheral/dma.hpp index b04d49c..709dd8f 100644 --- a/include/rpl4/peripheral/dma.hpp +++ b/include/rpl4/peripheral/dma.hpp @@ -1,198 +1,14 @@ #ifndef RPL4_PERIPHERAL_DMA_HPP_ #define RPL4_PERIPHERAL_DMA_HPP_ -#include -#include -#include - -#include "rpl4/registers/registers_dma.hpp" - -namespace rpl { - -class Dma { - public: - enum class Channel : size_t { - kChannel0 = 0, - kChannel1 = 1, - kChannel2 = 2, - kChannel3 = 3, - kChannel4 = 4, - kChannel5 = 5, - kChannel6 = 6, - kChannel7 = 7, - kChannel8 = 8, - kChannel9 = 9, - kChannel10 = 10, - kChannel11 = 11, - kChannel12 = 12, - kChannel13 = 13, - kChannel14 = 14, - }; - - /** - * @brief Get the Dma instance of specified channel. - * @details To save memory, only the channel instance obtained with - * GetInstance() is created. If a channel instance has already - * been created, the same instance will be returned. - * - * @param channel DMA channel - * @return std::shared_ptr - */ - static std::shared_ptr GetInstance(Channel channel); - - Dma(const Dma&) = delete; - Dma& operator=(const Dma&) = delete; - Dma(Dma&&) = delete; - Dma& operator=(Dma&&) = delete; - ~Dma() = default; - - /** - * @brief Get the DmaRegisterMap pointer. - * - * @return DmaRegisterMap* - */ - inline DmaRegisterMap* GetRegister() const { return register_map_; } - - /** - * @brief Get the channel number - * - * @return Channel number - */ - inline Channel GetChannel() const { return channel_; } - - /** - * @brief Enable the DMA channel - */ - void Enable(); - - /** - * @brief Disable the DMA channel - */ - void Disable(); - - /** - * @brief Reset the DMA channel - */ - void Reset(); - - /** - * @brief Abort the current DMA transfer - */ - void Abort(); - - /** - * @brief Check if DMA transfer is active - * - * @return true if active, false otherwise - */ - bool IsActive(); - - /** - * @brief Check if DMA transfer has completed - * - * @return true if completed, false otherwise - */ - bool IsComplete(); - - /** - * @brief Check if DMA has error - * - * @return true if error, false otherwise - */ - bool HasError(); - - /** - * @brief Clear interrupt flag - */ - void ClearInterrupt(); - - /** - * @brief Set control block address - * - * @param control_block_physical_addr Physical address of control block - * @note Dma::Reset() clears this setting, so if Reset() is called, - * this method needs to be called again before starting transfer. - */ - void SetControlBlockAddress(uint32_t control_block_physical_addr); - - /** - * @brief Start DMA transfer - */ - void Start(); - - /** - * @brief Wait for DMA transfer to complete - * - * @param timeout_ms Timeout in milliseconds (0 = no timeout) - * @return true if completed, false if timeout - */ - bool WaitForCompletion(uint32_t timeout_ms = 0); - - /** - * @brief Set DMA priority - * - * @param priority Priority level (0-15, higher is more important) - */ - void SetPriority(uint8_t priority); - - /** - * @brief Set DMA panic priority - * - * @param panic_priority Panic priority level (0-15) - */ - void SetPanicPriority(uint8_t panic_priority); - - /** - * @brief Configure a simple memory-to-memory transfer - * - * @param control_block Control block to configure - * @param src_physical Source physical address - * @param dest_physical Destination physical address - * @param length Transfer length in bytes - */ - static void ConfigureMemoryToMemory(DmaControlBlock* control_block, - uint32_t src_physical, - uint32_t dest_physical, - uint32_t length); - - /** - * @brief Configure a memory-to-peripheral transfer - * - * @param control_block Control block to configure - * @param src_physical Source physical address - * @param dest_physical Destination physical address (peripheral register) - * @param length Transfer length in bytes - * @param dreq DREQ signal mapping - */ - static void ConfigureMemoryToPeripheral( - DmaControlBlock* control_block, uint32_t src_physical, - uint32_t dest_physical, uint32_t length, - DmaRegisterMap::TI::PERMAP dreq); - - /** - * @brief Configure a peripheral-to-memory transfer - * - * @param control_block Control block to configure - * @param src_physical Source physical address (peripheral register) - * @param dest_physical Destination physical address - * @param length Transfer length in bytes - * @param dreq DREQ signal mapping - */ - static void ConfigurePeripheralToMemory( - DmaControlBlock* control_block, uint32_t src_physical, - uint32_t dest_physical, uint32_t length, - DmaRegisterMap::TI::PERMAP dreq); - - private: - Dma(DmaRegisterMap* register_map, Channel channel); - - static constexpr size_t kNumOfInstances = 15; - static std::array, kNumOfInstances> instances_; - - DmaRegisterMap* register_map_; - Channel channel_; -}; - -} // namespace rpl +// This header provides backward compatibility and convenience +// Include all DMA types +#include "rpl4/peripheral/dma4.hpp" +#include "rpl4/peripheral/dma_lite.hpp" +#include "rpl4/peripheral/dma_standard.hpp" + +// For backward compatibility, Dma class is now in dma_standard.hpp +// Users can continue to use #include "rpl4/peripheral/dma.hpp" and get +// all DMA types (Dma, DmaLite, Dma4) #endif // RPL4_PERIPHERAL_DMA_HPP_ diff --git a/include/rpl4/peripheral/dma4.hpp b/include/rpl4/peripheral/dma4.hpp new file mode 100644 index 0000000..c43992b --- /dev/null +++ b/include/rpl4/peripheral/dma4.hpp @@ -0,0 +1,117 @@ +#ifndef RPL4_PERIPHERAL_DMA4_HPP_ +#define RPL4_PERIPHERAL_DMA4_HPP_ + +#include +#include +#include +#include + +#include "rpl4/peripheral/dma_base.hpp" +#include "rpl4/registers/registers_dma.hpp" + +namespace rpl { + +/** + * @brief DMA4 class (Channels 11-14) + * @details Enhanced DMA with 40-bit addressing for full memory access + */ +class Dma4 : public DmaBase { + public: + friend class DmaBase; + + enum class Channel : size_t { + kChannel11 = 11, + kChannel12 = 12, + kChannel13 = 13, + kChannel14 = 14, + }; + + /** + * @brief Get the Dma4 instance of specified channel. + */ + static std::shared_ptr GetInstance(Channel channel); + + Dma4(const Dma4&) = delete; + Dma4& operator=(const Dma4&) = delete; + Dma4(Dma4&&) = delete; + Dma4& operator=(Dma4&&) = delete; + ~Dma4() = default; + + /** + * @brief Get the Dma4RegisterMap pointer. + */ + inline Dma4RegisterMap* GetRegister() const { return register_map_; } + + /** + * @brief Get the channel enum + */ + inline Channel GetChannel() const { return channel_; } + + /** + * @brief Set control block address (40-bit address) + * @param control_block_physical_addr Physical address of control block + * @note Address must be 32-byte aligned (lower 5 bits ignored) + */ + void SetControlBlockAddress(uint32_t control_block_physical_addr); + + /** + * @brief Configure a simple memory-to-memory transfer with 40-bit addressing + * @param control_block Control block to configure + * @param src_physical Source physical address (40-bit) + * @param dest_physical Destination physical address (40-bit) + * @param length Transfer length in bytes + */ + static void ConfigureMemoryToMemory(Dma4ControlBlock* control_block, + uint64_t src_physical, + uint64_t dest_physical, uint32_t length); + + /** + * @brief Configure a memory-to-peripheral transfer with 40-bit addressing + */ + static void ConfigureMemoryToPeripheral( + Dma4ControlBlock* control_block, uint64_t src_physical, + uint64_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq); + + /** + * @brief Configure a peripheral-to-memory transfer with 40-bit addressing + */ + static void ConfigurePeripheralToMemory( + Dma4ControlBlock* control_block, uint64_t src_physical, + uint64_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq); + + /** + * @brief Helper to split 40-bit address into 32-bit addr and 8-bit info + */ + static void SplitAddress40(uint64_t addr, volatile uint32_t& addr_lower, + volatile uint32_t& addr_upper) { + addr_lower = static_cast(addr & 0xFFFFFFFF); + addr_upper = static_cast((addr >> 32) & 0xFF); + } + + /** + * @brief Helper to combine 32-bit addr and 8-bit info into 40-bit address + */ + static uint64_t CombineAddress40(uint32_t addr_lower, uint32_t addr_upper) { + return static_cast(addr_lower) | + (static_cast(addr_upper & 0xFF) << 32); + } + + private: + Dma4(Dma4RegisterMap* register_map, Channel channel); + + // For base class CRTP access + volatile DmaRegisterMap::CS& GetRegisterCS() { return register_map_->cs; } + uint32_t GetChannelNumber() const { return static_cast(channel_); } + + static constexpr size_t kNumOfInstances = 4; + static std::array, kNumOfInstances> instances_; + + Dma4RegisterMap* register_map_; + Channel channel_; +}; + +} // namespace rpl + +#endif // RPL4_PERIPHERAL_DMA4_HPP_ diff --git a/include/rpl4/peripheral/dma_base.hpp b/include/rpl4/peripheral/dma_base.hpp new file mode 100644 index 0000000..cd336bd --- /dev/null +++ b/include/rpl4/peripheral/dma_base.hpp @@ -0,0 +1,204 @@ +#ifndef RPL4_PERIPHERAL_DMA_BASE_HPP_ +#define RPL4_PERIPHERAL_DMA_BASE_HPP_ + +#include +#include +#include + +#include "rpl4/registers/registers_dma.hpp" +#include "rpl4/system/log.hpp" + +namespace rpl { + +// DMA Type Tags for compile-time dispatch +struct StandardDmaTag {}; +struct DmaLiteTag {}; +struct Dma4Tag {}; + +// Traits for DMA types - specializations provide type-specific information +template +struct DmaTraits; + +// Standard DMA traits +template <> +struct DmaTraits { + using RegisterMapType = DmaRegisterMap; + using ControlBlockType = DmaControlBlock; + static constexpr const char* kName = "Standard DMA"; +}; + +// DMA Lite traits +template <> +struct DmaTraits { + using RegisterMapType = DmaLiteRegisterMap; + using ControlBlockType = DmaLiteControlBlock; + static constexpr const char* kName = "DMA Lite"; +}; + +// DMA4 traits +template <> +struct DmaTraits { + using RegisterMapType = Dma4RegisterMap; + using ControlBlockType = Dma4ControlBlock; + static constexpr const char* kName = "DMA4"; +}; + +// CRTP Base class for common DMA functionality +// Derived is the actual DMA class (Dma, DmaLite, or Dma4) +template +class DmaBase { + public: + using Traits = DmaTraits; + using RegisterMapType = typename Traits::RegisterMapType; + using ControlBlockType = typename Traits::ControlBlockType; + + /** + * @brief Enable the DMA channel + */ + void Enable() { + uint32_t channel_bit = 1 << static_cast(GetChannel()); + REG_DMA_ENABLE->enable |= channel_bit; + } + + /** + * @brief Disable the DMA channel + */ + void Disable() { + uint32_t channel_bit = 1 << static_cast(GetChannel()); + REG_DMA_ENABLE->enable &= ~channel_bit; + } + + /** + * @brief Reset the DMA channel + */ + void Reset() { + GetRegisterCS().reset = DmaRegisterMap::CS::RESET::kReset; + // Wait for reset to complete + using namespace std::chrono_literals; + std::this_thread::sleep_for(1ms); + } + + /** + * @brief Abort the current DMA transfer + */ + void Abort() { + GetRegisterCS().abort = DmaRegisterMap::CS::ABORT::kAbort; + } + + /** + * @brief Check if DMA transfer is active + */ + bool IsActive() { + return GetRegisterCS().active == DmaRegisterMap::CS::ACTIVE::kActive; + } + + /** + * @brief Check if DMA transfer has completed + */ + bool IsComplete() { + return GetRegisterCS().end == DmaRegisterMap::CS::END::kSet; + } + + /** + * @brief Check if DMA has error + */ + bool HasError() { + return GetRegisterCS().error == DmaRegisterMap::CS::ERROR::kError; + } + + /** + * @brief Clear interrupt flag + */ + void ClearInterrupt() { + GetRegisterCS().interrupt = DmaRegisterMap::CS::INT::kSet; + } + + /** + * @brief Start DMA transfer + */ + void Start() { + GetRegisterCS().active = DmaRegisterMap::CS::ACTIVE::kActive; + } + + /** + * @brief Wait for DMA transfer to complete + */ + bool WaitForCompletion(uint32_t timeout_ms = 0) { + using namespace std::chrono_literals; + auto start_time = std::chrono::steady_clock::now(); + + while (!IsComplete()) { + if (HasError()) { + Log(LogLevel::Error, "[%s] Transfer error on channel %d", + Traits::kName, static_cast(GetChannel())); + return false; + } + + if (timeout_ms > 0) { + auto current_time = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast( + current_time - start_time) + .count(); + if (elapsed >= timeout_ms) { + Log(LogLevel::Warning, "[%s] Transfer timeout on channel %d", + Traits::kName, static_cast(GetChannel())); + return false; + } + } + + std::this_thread::sleep_for(10us); + } + + return true; + } + + /** + * @brief Set DMA priority + */ + void SetPriority(uint8_t priority) { + if (priority > 15) { + priority = 15; + } + GetRegisterCS().priority = + static_cast(priority); + } + + /** + * @brief Set DMA panic priority + */ + void SetPanicPriority(uint8_t panic_priority) { + if (panic_priority > 15) { + panic_priority = 15; + } + GetRegisterCS().panic_priority = + static_cast(panic_priority); + } + + /** + * @brief Get the DMA type name + */ + const char* GetTypeName() const { return Traits::kName; } + + protected: + DmaBase() = default; + ~DmaBase() = default; + + private: + // CRTP: Get the derived class + Derived& derived() { return static_cast(*this); } + const Derived& derived() const { + return static_cast(*this); + } + + // Access CS register through derived class + volatile DmaRegisterMap::CS& GetRegisterCS() { + return derived().GetRegisterCS(); + } + + // Access channel through derived class + uint32_t GetChannel() const { return derived().GetChannelNumber(); } +}; + +} // namespace rpl + +#endif // RPL4_PERIPHERAL_DMA_BASE_HPP_ diff --git a/include/rpl4/peripheral/dma_lite.hpp b/include/rpl4/peripheral/dma_lite.hpp new file mode 100644 index 0000000..7786376 --- /dev/null +++ b/include/rpl4/peripheral/dma_lite.hpp @@ -0,0 +1,95 @@ +#ifndef RPL4_PERIPHERAL_DMA_LITE_HPP_ +#define RPL4_PERIPHERAL_DMA_LITE_HPP_ + +#include +#include +#include +#include + +#include "rpl4/peripheral/dma_base.hpp" +#include "rpl4/registers/registers_dma.hpp" + +namespace rpl { + +/** + * @brief DMA Lite class (Channel 7) + * @details Provides simplified DMA with reduced features for lighter workloads + */ +class DmaLite : public DmaBase { + public: + friend class DmaBase; + + enum class Channel : size_t { + kChannel7 = 7, + // Note: Channel 15 is not mapped in current BCM2711 implementation + }; + + /** + * @brief Get the DmaLite instance of specified channel. + */ + static std::shared_ptr GetInstance(Channel channel); + + DmaLite(const DmaLite&) = delete; + DmaLite& operator=(const DmaLite&) = delete; + DmaLite(DmaLite&&) = delete; + DmaLite& operator=(DmaLite&&) = delete; + ~DmaLite() = default; + + /** + * @brief Get the DmaLiteRegisterMap pointer. + */ + inline DmaLiteRegisterMap* GetRegister() const { return register_map_; } + + /** + * @brief Get the channel enum + */ + inline Channel GetChannel() const { return channel_; } + + /** + * @brief Set control block address + */ + void SetControlBlockAddress(uint32_t control_block_physical_addr); + + /** + * @brief Configure a simple memory-to-memory transfer + * @note DMA Lite has reduced functionality - avoid complex transfers + */ + static void ConfigureMemoryToMemory(DmaLiteControlBlock* control_block, + uint32_t src_physical, + uint32_t dest_physical, uint32_t length); + + /** + * @brief Configure a memory-to-peripheral transfer + * @note DMA Lite has reduced functionality compared to standard DMA + */ + static void ConfigureMemoryToPeripheral( + DmaLiteControlBlock* control_block, uint32_t src_physical, + uint32_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq); + + /** + * @brief Configure a peripheral-to-memory transfer + * @note DMA Lite has reduced functionality compared to standard DMA + */ + static void ConfigurePeripheralToMemory( + DmaLiteControlBlock* control_block, uint32_t src_physical, + uint32_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq); + + private: + DmaLite(DmaLiteRegisterMap* register_map, Channel channel); + + // For base class CRTP access + volatile DmaRegisterMap::CS& GetRegisterCS() { return register_map_->cs; } + uint32_t GetChannelNumber() const { return static_cast(channel_); } + + static constexpr size_t kNumOfInstances = 1; + static std::array, kNumOfInstances> instances_; + + DmaLiteRegisterMap* register_map_; + Channel channel_; +}; + +} // namespace rpl + +#endif // RPL4_PERIPHERAL_DMA_LITE_HPP_ diff --git a/include/rpl4/peripheral/dma_standard.hpp b/include/rpl4/peripheral/dma_standard.hpp new file mode 100644 index 0000000..df30722 --- /dev/null +++ b/include/rpl4/peripheral/dma_standard.hpp @@ -0,0 +1,104 @@ +#ifndef RPL4_PERIPHERAL_DMA_STANDARD_HPP_ +#define RPL4_PERIPHERAL_DMA_STANDARD_HPP_ + +#include +#include +#include +#include + +#include "rpl4/peripheral/dma_base.hpp" +#include "rpl4/registers/registers_dma.hpp" + +namespace rpl { + +// Forward declarations +class DmaLite; +class Dma4; + +/** + * @brief Standard DMA class (Channels 0-6, 8-10) + * @details Provides full-featured DMA with 32-bit addressing + */ +class Dma : public DmaBase { + public: + friend class DmaBase; + + enum class Channel : size_t { + kChannel0 = 0, + kChannel1 = 1, + kChannel2 = 2, + kChannel3 = 3, + kChannel4 = 4, + kChannel5 = 5, + kChannel6 = 6, + kChannel8 = 8, + kChannel9 = 9, + kChannel10 = 10, + }; + + /** + * @brief Get the Dma instance of specified channel. + */ + static std::shared_ptr GetInstance(Channel channel); + + Dma(const Dma&) = delete; + Dma& operator=(const Dma&) = delete; + Dma(Dma&&) = delete; + Dma& operator=(Dma&&) = delete; + ~Dma() = default; + + /** + * @brief Get the DmaRegisterMap pointer. + */ + inline DmaRegisterMap* GetRegister() const { return register_map_; } + + /** + * @brief Get the channel enum + */ + inline Channel GetChannel() const { return channel_; } + + /** + * @brief Set control block address + */ + void SetControlBlockAddress(uint32_t control_block_physical_addr); + + /** + * @brief Configure a simple memory-to-memory transfer + */ + static void ConfigureMemoryToMemory(DmaControlBlock* control_block, + uint32_t src_physical, + uint32_t dest_physical, uint32_t length); + + /** + * @brief Configure a memory-to-peripheral transfer + */ + static void ConfigureMemoryToPeripheral( + DmaControlBlock* control_block, uint32_t src_physical, + uint32_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq); + + /** + * @brief Configure a peripheral-to-memory transfer + */ + static void ConfigurePeripheralToMemory( + DmaControlBlock* control_block, uint32_t src_physical, + uint32_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq); + + private: + Dma(DmaRegisterMap* register_map, Channel channel); + + // For base class CRTP access + volatile DmaRegisterMap::CS& GetRegisterCS() { return register_map_->cs; } + uint32_t GetChannelNumber() const { return static_cast(channel_); } + + static constexpr size_t kNumOfInstances = 10; + static std::array, kNumOfInstances> instances_; + + DmaRegisterMap* register_map_; + Channel channel_; +}; + +} // namespace rpl + +#endif // RPL4_PERIPHERAL_DMA_STANDARD_HPP_ diff --git a/include/rpl4/registers/registers_dma.hpp b/include/rpl4/registers/registers_dma.hpp index d49bf45..32581cb 100644 --- a/include/rpl4/registers/registers_dma.hpp +++ b/include/rpl4/registers/registers_dma.hpp @@ -166,6 +166,7 @@ struct DmaRegisterMap { * @brief DMA Transfer Information */ struct TI { + TI() = default; TI(const volatile TI&); volatile TI& operator=(const volatile TI&) volatile; TI& operator=(const volatile TI&); @@ -482,6 +483,49 @@ struct DmaControlBlock { volatile uint32_t reserved[2]; // Reserved }; +// DMA Lite uses the same register map as standard DMA but with reduced functionality +// Channels 7 and 15 are DMA Lite +using DmaLiteRegisterMap = DmaRegisterMap; +using DmaLiteControlBlock = DmaControlBlock; + +extern DmaLiteRegisterMap* REG_DMA_LITE7; +extern DmaLiteRegisterMap* REG_DMA_LITE15; + +// DMA4 Register Map (Channels 11-14) - Enhanced DMA with 40-bit addressing +struct Dma4RegisterMap { + volatile DmaRegisterMap::CS cs; // 0x00 - Control and Status (same as standard DMA) + volatile uint32_t cb_addr; // 0x04 - Control Block Address (bits 39:5) + volatile DmaRegisterMap::DEBUG debug; // 0x08 - Debug + volatile uint32_t ti; // 0x0C - Transfer Information (read-only, from CB) + volatile uint32_t src_addr; // 0x10 - Source Address (read-only, from CB) + volatile uint32_t srci; // 0x14 - Source Information (upper 8 bits for 40-bit) + volatile uint32_t dst_addr; // 0x18 - Dest Address (read-only, from CB) + volatile uint32_t dsti; // 0x1C - Dest Information (upper 8 bits for 40-bit) + volatile uint32_t len; // 0x20 - Transfer Length (read-only, from CB) + volatile uint32_t next_cb; // 0x24 - Next CB Address (read-only, from CB) + volatile uint32_t debug2; // 0x28 - Extended Debug + volatile uint32_t reserved[53]; // 0x2C-0xFF +}; + +// DMA4 Control Block structure (must be 32-byte aligned in physical memory) +// Note: 40-bit addresses are split into addr (lower 32 bits) and info (upper 8 bits) +struct Dma4ControlBlock { + volatile DmaRegisterMap::TI transfer_info; // 0x00 - Transfer Information + volatile uint32_t source_addr; // 0x04 - Source Address (lower 32 bits) + volatile uint32_t source_info; // 0x08 - Source Info (bits 39:32 in lower byte) + volatile uint32_t dest_addr; // 0x0C - Destination Address (lower 32 bits) + volatile uint32_t dest_info; // 0x10 - Dest Info (bits 39:32 in lower byte) + volatile uint32_t transfer_length; // 0x14 - Transfer Length + volatile uint32_t stride; // 0x18 - 2D Mode Stride + volatile uint32_t next_control_block; // 0x1C - Next CB Address (bits 39:5, shifted right by 5) + volatile uint32_t reserved[2]; // 0x20-0x27 - Reserved +}; + +extern Dma4RegisterMap* REG_DMA4_11; +extern Dma4RegisterMap* REG_DMA4_12; +extern Dma4RegisterMap* REG_DMA4_13; +extern Dma4RegisterMap* REG_DMA4_14; + } // namespace rpl #endif // RPL4_REGISTERS_DMA_HPP_ \ No newline at end of file diff --git a/src/peripheral/dma4.cpp b/src/peripheral/dma4.cpp new file mode 100644 index 0000000..a03c011 --- /dev/null +++ b/src/peripheral/dma4.cpp @@ -0,0 +1,155 @@ +#include "rpl4/peripheral/dma4.hpp" + +#include + +#include "rpl4/system/system.hpp" + +namespace rpl { + +std::array, Dma4::kNumOfInstances> Dma4::instances_ = { + nullptr}; + +std::shared_ptr Dma4::GetInstance(Channel channel) { + size_t index; + Dma4RegisterMap* reg_map = nullptr; + + switch (channel) { + case Channel::kChannel11: + index = 0; + reg_map = REG_DMA4_11; + break; + case Channel::kChannel12: + index = 1; + reg_map = REG_DMA4_12; + break; + case Channel::kChannel13: + index = 2; + reg_map = REG_DMA4_13; + break; + case Channel::kChannel14: + index = 3; + reg_map = REG_DMA4_14; + break; + default: + Log(LogLevel::Fatal, "[Dma4::GetInstance()] Invalid channel."); + return nullptr; + } + + if (!IsInitialized()) { + Log(LogLevel::Error, "[Dma4::GetInstance()] RPL is not initialized."); + } else if (instances_[index] == nullptr) { + instances_[index] = std::shared_ptr(new Dma4(reg_map, channel)); + } + return instances_[index]; +} + +Dma4::Dma4(Dma4RegisterMap* register_map, Channel channel) + : register_map_(register_map), channel_(channel) { + Reset(); +} + +void Dma4::SetControlBlockAddress(uint32_t control_block_physical_addr) { + // DMA4 uses 40-bit addressing, CB address must be shifted right by 5 + // (32-byte aligned) + register_map_->cb_addr = control_block_physical_addr >> 5; +} + +void Dma4::ConfigureMemoryToMemory(Dma4ControlBlock* control_block, + uint64_t src_physical, + uint64_t dest_physical, uint32_t length) { + if (control_block == nullptr) { + return; + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(control_block, 0, sizeof(Dma4ControlBlock)); + #pragma GCC diagnostic pop + + // Configure transfer info + control_block->transfer_info.src_inc = DmaRegisterMap::TI::SRC_INC::kEnable; + control_block->transfer_info.dest_inc = DmaRegisterMap::TI::DEST_INC::kEnable; + control_block->transfer_info.wait_resp = + DmaRegisterMap::TI::WAIT_RESP::kEnable; + control_block->transfer_info.no_wide_bursts = + DmaRegisterMap::TI::NO_WIDE_BURSTS::kEnable; + + // Split 40-bit addresses + SplitAddress40(src_physical, control_block->source_addr, + control_block->source_info); + SplitAddress40(dest_physical, control_block->dest_addr, + control_block->dest_info); + + control_block->transfer_length = length; + control_block->stride = 0; + control_block->next_control_block = 0; +} + +void Dma4::ConfigureMemoryToPeripheral(Dma4ControlBlock* control_block, + uint64_t src_physical, + uint64_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq) { + if (control_block == nullptr) { + return; + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(control_block, 0, sizeof(Dma4ControlBlock)); + #pragma GCC diagnostic pop + + // Configure transfer info + control_block->transfer_info.src_inc = DmaRegisterMap::TI::SRC_INC::kEnable; + control_block->transfer_info.dest_dreq = + DmaRegisterMap::TI::DEST_DREQ::kEnable; + control_block->transfer_info.wait_resp = + DmaRegisterMap::TI::WAIT_RESP::kEnable; + control_block->transfer_info.no_wide_bursts = + DmaRegisterMap::TI::NO_WIDE_BURSTS::kEnable; + control_block->transfer_info.permap = dreq; + + // Split 40-bit addresses + SplitAddress40(src_physical, control_block->source_addr, + control_block->source_info); + SplitAddress40(dest_physical, control_block->dest_addr, + control_block->dest_info); + + control_block->transfer_length = length; + control_block->stride = 0; + control_block->next_control_block = 0; +} + +void Dma4::ConfigurePeripheralToMemory(Dma4ControlBlock* control_block, + uint64_t src_physical, + uint64_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq) { + if (control_block == nullptr) { + return; + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(control_block, 0, sizeof(Dma4ControlBlock)); + #pragma GCC diagnostic pop + + // Configure transfer info + control_block->transfer_info.src_dreq = DmaRegisterMap::TI::SRC_DREQ::kEnable; + control_block->transfer_info.dest_inc = DmaRegisterMap::TI::DEST_INC::kEnable; + control_block->transfer_info.wait_resp = + DmaRegisterMap::TI::WAIT_RESP::kEnable; + control_block->transfer_info.no_wide_bursts = + DmaRegisterMap::TI::NO_WIDE_BURSTS::kEnable; + control_block->transfer_info.permap = dreq; + + // Split 40-bit addresses + SplitAddress40(src_physical, control_block->source_addr, + control_block->source_info); + SplitAddress40(dest_physical, control_block->dest_addr, + control_block->dest_info); + + control_block->transfer_length = length; + control_block->stride = 0; + control_block->next_control_block = 0; +} + +} // namespace rpl diff --git a/src/peripheral/dma_lite.cpp b/src/peripheral/dma_lite.cpp new file mode 100644 index 0000000..b747f57 --- /dev/null +++ b/src/peripheral/dma_lite.cpp @@ -0,0 +1,130 @@ +#include "rpl4/peripheral/dma_lite.hpp" + +#include + +#include "rpl4/system/system.hpp" + +namespace rpl { + +std::array, DmaLite::kNumOfInstances> + DmaLite::instances_ = {nullptr}; + +std::shared_ptr DmaLite::GetInstance(Channel channel) { + size_t index; + DmaLiteRegisterMap* reg_map = nullptr; + + switch (channel) { + case Channel::kChannel7: + index = 0; + reg_map = REG_DMA_LITE7; + break; + default: + Log(LogLevel::Fatal, "[DmaLite::GetInstance()] Invalid channel."); + return nullptr; + } + + if (!IsInitialized()) { + Log(LogLevel::Error, "[DmaLite::GetInstance()] RPL is not initialized."); + } else if (instances_[index] == nullptr) { + instances_[index] = + std::shared_ptr(new DmaLite(reg_map, channel)); + } + return instances_[index]; +} + +DmaLite::DmaLite(DmaLiteRegisterMap* register_map, Channel channel) + : register_map_(register_map), channel_(channel) { + Reset(); +} + +void DmaLite::SetControlBlockAddress(uint32_t control_block_physical_addr) { + register_map_->conblk_ad.address = control_block_physical_addr; +} + +void DmaLite::ConfigureMemoryToMemory(DmaLiteControlBlock* control_block, + uint32_t src_physical, + uint32_t dest_physical, uint32_t length) { + if (control_block == nullptr) { + return; + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(control_block, 0, sizeof(DmaLiteControlBlock)); + #pragma GCC diagnostic pop + + // Configure transfer info (simplified for DMA Lite) + control_block->transfer_info.src_inc = DmaRegisterMap::TI::SRC_INC::kEnable; + control_block->transfer_info.dest_inc = DmaRegisterMap::TI::DEST_INC::kEnable; + control_block->transfer_info.wait_resp = + DmaRegisterMap::TI::WAIT_RESP::kEnable; + control_block->transfer_info.no_wide_bursts = + DmaRegisterMap::TI::NO_WIDE_BURSTS::kEnable; + + control_block->source_addr = src_physical; + control_block->dest_addr = dest_physical; + control_block->transfer_length = length; + control_block->stride = 0; + control_block->next_control_block = 0; +} + +void DmaLite::ConfigureMemoryToPeripheral( + DmaLiteControlBlock* control_block, uint32_t src_physical, + uint32_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq) { + if (control_block == nullptr) { + return; + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(control_block, 0, sizeof(DmaLiteControlBlock)); + #pragma GCC diagnostic pop + + // Configure transfer info (simplified for DMA Lite) + control_block->transfer_info.src_inc = DmaRegisterMap::TI::SRC_INC::kEnable; + control_block->transfer_info.dest_dreq = + DmaRegisterMap::TI::DEST_DREQ::kEnable; + control_block->transfer_info.wait_resp = + DmaRegisterMap::TI::WAIT_RESP::kEnable; + control_block->transfer_info.no_wide_bursts = + DmaRegisterMap::TI::NO_WIDE_BURSTS::kEnable; + control_block->transfer_info.permap = dreq; + + control_block->source_addr = src_physical; + control_block->dest_addr = dest_physical; + control_block->transfer_length = length; + control_block->stride = 0; + control_block->next_control_block = 0; +} + +void DmaLite::ConfigurePeripheralToMemory( + DmaLiteControlBlock* control_block, uint32_t src_physical, + uint32_t dest_physical, uint32_t length, + DmaRegisterMap::TI::PERMAP dreq) { + if (control_block == nullptr) { + return; + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(control_block, 0, sizeof(DmaLiteControlBlock)); + #pragma GCC diagnostic pop + + // Configure transfer info (simplified for DMA Lite) + control_block->transfer_info.src_dreq = DmaRegisterMap::TI::SRC_DREQ::kEnable; + control_block->transfer_info.dest_inc = DmaRegisterMap::TI::DEST_INC::kEnable; + control_block->transfer_info.wait_resp = + DmaRegisterMap::TI::WAIT_RESP::kEnable; + control_block->transfer_info.no_wide_bursts = + DmaRegisterMap::TI::NO_WIDE_BURSTS::kEnable; + control_block->transfer_info.permap = dreq; + + control_block->source_addr = src_physical; + control_block->dest_addr = dest_physical; + control_block->transfer_length = length; + control_block->stride = 0; + control_block->next_control_block = 0; +} + +} // namespace rpl diff --git a/src/peripheral/dma.cpp b/src/peripheral/dma_standard.cpp similarity index 52% rename from src/peripheral/dma.cpp rename to src/peripheral/dma_standard.cpp index c7cba9d..cb1ed51 100644 --- a/src/peripheral/dma.cpp +++ b/src/peripheral/dma_standard.cpp @@ -1,8 +1,6 @@ -#include "rpl4/peripheral/dma.hpp" +#include "rpl4/peripheral/dma_standard.hpp" -#include #include -#include #include "rpl4/system/system.hpp" @@ -12,63 +10,58 @@ std::array, Dma::kNumOfInstances> Dma::instances_ = { nullptr}; std::shared_ptr Dma::GetInstance(Channel channel) { - size_t index = static_cast(channel); - if (index >= kNumOfInstances) { - Log(LogLevel::Fatal, "[Dma::GetInstance()] Invalid channel %zu.", index); - return nullptr; + size_t index; + DmaRegisterMap* reg_map = nullptr; + + switch (channel) { + case Channel::kChannel0: + index = 0; + reg_map = REG_DMA0; + break; + case Channel::kChannel1: + index = 1; + reg_map = REG_DMA1; + break; + case Channel::kChannel2: + index = 2; + reg_map = REG_DMA2; + break; + case Channel::kChannel3: + index = 3; + reg_map = REG_DMA3; + break; + case Channel::kChannel4: + index = 4; + reg_map = REG_DMA4; + break; + case Channel::kChannel5: + index = 5; + reg_map = REG_DMA5; + break; + case Channel::kChannel6: + index = 6; + reg_map = REG_DMA6; + break; + case Channel::kChannel8: + index = 7; + reg_map = REG_DMA8; + break; + case Channel::kChannel9: + index = 8; + reg_map = REG_DMA9; + break; + case Channel::kChannel10: + index = 9; + reg_map = REG_DMA10; + break; + default: + Log(LogLevel::Fatal, "[Dma::GetInstance()] Invalid channel."); + return nullptr; } if (!IsInitialized()) { Log(LogLevel::Error, "[Dma::GetInstance()] RPL is not initialized."); } else if (instances_[index] == nullptr) { - DmaRegisterMap* reg_map = nullptr; - switch (channel) { - case Channel::kChannel0: - reg_map = REG_DMA0; - break; - case Channel::kChannel1: - reg_map = REG_DMA1; - break; - case Channel::kChannel2: - reg_map = REG_DMA2; - break; - case Channel::kChannel3: - reg_map = REG_DMA3; - break; - case Channel::kChannel4: - reg_map = REG_DMA4; - break; - case Channel::kChannel5: - reg_map = REG_DMA5; - break; - case Channel::kChannel6: - reg_map = REG_DMA6; - break; - case Channel::kChannel7: - reg_map = REG_DMA7; - break; - case Channel::kChannel8: - reg_map = REG_DMA8; - break; - case Channel::kChannel9: - reg_map = REG_DMA9; - break; - case Channel::kChannel10: - reg_map = REG_DMA10; - break; - case Channel::kChannel11: - reg_map = REG_DMA11; - break; - case Channel::kChannel12: - reg_map = REG_DMA12; - break; - case Channel::kChannel13: - reg_map = REG_DMA13; - break; - case Channel::kChannel14: - reg_map = REG_DMA14; - break; - } instances_[index] = std::shared_ptr(new Dma(reg_map, channel)); } return instances_[index]; @@ -79,96 +72,10 @@ Dma::Dma(DmaRegisterMap* register_map, Channel channel) Reset(); } -void Dma::Enable() { - uint32_t channel_bit = 1 << static_cast(channel_); - REG_DMA_ENABLE->enable |= channel_bit; -} - -void Dma::Disable() { - uint32_t channel_bit = 1 << static_cast(channel_); - REG_DMA_ENABLE->enable &= ~channel_bit; -} - -void Dma::Reset() { - register_map_->cs.reset = DmaRegisterMap::CS::RESET::kReset; - // Wait for reset to complete - using namespace std::chrono_literals; - std::this_thread::sleep_for(1ms); -} - -void Dma::Abort() { - register_map_->cs.abort = DmaRegisterMap::CS::ABORT::kAbort; -} - -bool Dma::IsActive() { - return register_map_->cs.active == DmaRegisterMap::CS::ACTIVE::kActive; -} - -bool Dma::IsComplete() { - return register_map_->cs.end == DmaRegisterMap::CS::END::kSet; -} - -bool Dma::HasError() { - return register_map_->cs.error == DmaRegisterMap::CS::ERROR::kError; -} - -void Dma::ClearInterrupt() { - register_map_->cs.interrupt = DmaRegisterMap::CS::INT::kSet; -} - void Dma::SetControlBlockAddress(uint32_t control_block_physical_addr) { register_map_->conblk_ad.address = control_block_physical_addr; } -void Dma::Start() { - register_map_->cs.active = DmaRegisterMap::CS::ACTIVE::kActive; -} - -bool Dma::WaitForCompletion(uint32_t timeout_ms) { - using namespace std::chrono_literals; - auto start_time = std::chrono::steady_clock::now(); - - while (!IsComplete()) { - if (HasError()) { - Log(LogLevel::Error, "[Dma] Transfer error on channel %d", - static_cast(channel_)); - return false; - } - - if (timeout_ms > 0) { - auto current_time = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast( - current_time - start_time) - .count(); - if (elapsed >= timeout_ms) { - Log(LogLevel::Warning, "[Dma] Transfer timeout on channel %d", - static_cast(channel_)); - return false; - } - } - - std::this_thread::sleep_for(10us); - } - - return true; -} - -void Dma::SetPriority(uint8_t priority) { - if (priority > 15) { - priority = 15; - } - register_map_->cs.priority = - static_cast(priority); -} - -void Dma::SetPanicPriority(uint8_t panic_priority) { - if (panic_priority > 15) { - panic_priority = 15; - } - register_map_->cs.panic_priority = - static_cast(panic_priority); -} - void Dma::ConfigureMemoryToMemory(DmaControlBlock* control_block, uint32_t src_physical, uint32_t dest_physical, uint32_t length) { diff --git a/src/system/system.cpp b/src/system/system.cpp index 481f6e9..b85ab98 100644 --- a/src/system/system.cpp +++ b/src/system/system.cpp @@ -31,11 +31,19 @@ DmaRegisterMap* REG_DMA7; DmaRegisterMap* REG_DMA8; DmaRegisterMap* REG_DMA9; DmaRegisterMap* REG_DMA10; -DmaRegisterMap* REG_DMA11; -DmaRegisterMap* REG_DMA12; -DmaRegisterMap* REG_DMA13; -DmaRegisterMap* REG_DMA14; +// Note: Channels 11-14 are DMA4 only, not standard DMA +DmaRegisterMap* REG_DMA11 = nullptr; +DmaRegisterMap* REG_DMA12 = nullptr; +DmaRegisterMap* REG_DMA13 = nullptr; +DmaRegisterMap* REG_DMA14 = nullptr; DmaEnableRegisterMap* REG_DMA_ENABLE; +DmaLiteRegisterMap* REG_DMA_LITE7; +// Note: Channel 15 is not available in BCM2711 +DmaLiteRegisterMap* REG_DMA_LITE15 = nullptr; +Dma4RegisterMap* REG_DMA4_11; +Dma4RegisterMap* REG_DMA4_12; +Dma4RegisterMap* REG_DMA4_13; +Dma4RegisterMap* REG_DMA4_14; GpioRegisterMap* REG_GPIO; PwmRegisterMap* REG_PWM0; PwmRegisterMap* REG_PWM1; @@ -105,10 +113,16 @@ uint8_t Init(void){ REG_DMA8 = reinterpret_cast(region0 + (kDma8AddressBase - region0_base) / 4); REG_DMA9 = reinterpret_cast(region0 + (kDma9AddressBase - region0_base) / 4); REG_DMA10 = reinterpret_cast(region0 + (kDma10AddressBase - region0_base) / 4); - REG_DMA11 = reinterpret_cast(region0 + (kDma11AddressBase - region0_base) / 4); - REG_DMA12 = reinterpret_cast(region0 + (kDma12AddressBase - region0_base) / 4); - REG_DMA13 = reinterpret_cast(region0 + (kDma13AddressBase - region0_base) / 4); + // Channels 11-14 are DMA4 only, mapped separately below REG_DMA_ENABLE = reinterpret_cast(region0 + (kDmaEnableAddressBase - region0_base) / 4); + + // DMA Lite channel 7 uses same register map as standard DMA + REG_DMA_LITE7 = reinterpret_cast(region0 + (kDma7AddressBase - region0_base) / 4); + + // DMA4 channels (11-14) have different register map (not standard DMA) + REG_DMA4_11 = reinterpret_cast(region0 + (kDma11AddressBase - region0_base) / 4); + REG_DMA4_12 = reinterpret_cast(region0 + (kDma12AddressBase - region0_base) / 4); + REG_DMA4_13 = reinterpret_cast(region0 + (kDma13AddressBase - region0_base) / 4); constexpr static uint32_t region1_base = 0xfe101000; constexpr static uint32_t region1_size = 0x1000; @@ -195,7 +209,9 @@ uint8_t Init(void){ close(fd); return -1; } - REG_DMA14 = reinterpret_cast(region7 + (kDma14AddressBase - region7_base) / 4); + // Channel 14 is DMA4 only (not standard DMA) + REG_DMA4_14 = reinterpret_cast(region7 + (kDma14AddressBase - region7_base) / 4); + // Note: Channel 15 (DMA Lite) is not available/mapped in BCM2711 system_initialized = true; close(fd);