diff --git a/src/platforms/esp32c6/dma_parallel_io.cpp b/src/platforms/esp32c6/dma_parallel_io.cpp new file mode 100644 index 0000000..04c6c74 --- /dev/null +++ b/src/platforms/esp32c6/dma_parallel_io.cpp @@ -0,0 +1,309 @@ +#include "dma_parallel_io.hpp" + +#ifdef CONFIG_IDF_TARGET_ESP32C6 + +//First implementation might have a lot of bugs, especially on deleting and reloading + + +#pragma message "Compiling for ESP32-C6" + +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif + +#include "soc/parl_io_struct.h" +#include "soc/parl_io_reg.h" +#include "soc/parlio_periph.h" +#include "hal/parlio_hal.h" +#include "hal/parlio_ll.h" +#include "hal/parlio_types.h" + +#include "esp_clk_tree.h" +#include "esp_private/periph_ctrl.h" +#include "esp_attr.h" +#include "esp_rom_sys.h" +#include "esp_rom_gpio.h" +#include "driver/gpio.h" + +DRAM_ATTR volatile bool previousBufferFree = true; + +// End-of-DMA-transfer callback +IRAM_ATTR bool gdma_on_trans_eof_callback(gdma_channel_handle_t dma_chan, + gdma_event_data_t *event_data, void *user_data) +{ + + //esp_rom_delay_us(100); + + previousBufferFree = true; + + return true; +} + +// ------------------------------------------------------------------------------ + +void Bus_Parallel16::config(const config_t &cfg) +{ + _cfg = cfg; + +} + +bool Bus_Parallel16::init(void) +{ + + periph_module_enable(PERIPH_PARLIO_MODULE); + periph_module_reset(PERIPH_PARLIO_MODULE); + + // Reset LCD bus + parlio_ll_tx_reset_fifo(&PARL_IO); + esp_rom_delay_us(1000); + + + parlio_ll_clock_source_t clk_src = (parlio_ll_clock_source_t)PARLIO_CLK_SRC_DEFAULT; + uint32_t periph_src_clk_hz = 0; + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz); + parlio_ll_tx_set_clock_source(&PARL_IO, clk_src); + uint32_t div = (periph_src_clk_hz + _cfg.bus_freq - 1) / _cfg.bus_freq; + parlio_ll_tx_set_clock_div(&PARL_IO, div); + _cfg.bus_freq = periph_src_clk_hz / div; + + + ESP_LOGI("C6", "Clock divider is %d", (int)div); + ESP_LOGD("C6", "Resulting output clock frequency: %d Mhz", (int)(160000000L / _cfg.bus_freq)); + + + + + // Allocate DMA channel and connect it to the LCD peripheral + static gdma_channel_alloc_config_t dma_chan_config = { + .sibling_chan = NULL, + .direction = GDMA_CHANNEL_DIRECTION_TX, + .flags = { + .reserve_sibling = 0}}; + gdma_new_channel(&dma_chan_config, &dma_chan); + gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_PARLIO, 0)); + static gdma_strategy_config_t strategy_config = { + .owner_check = false, + .auto_update_desc = false}; + gdma_apply_strategy(dma_chan, &strategy_config); + + gdma_transfer_ability_t ability = { + .sram_trans_align = 32, + .psram_trans_align = 64, + }; + gdma_set_transfer_ability(dma_chan, &ability); + + // Enable DMA transfer callback + static gdma_tx_event_callbacks_t tx_cbs = { + .on_trans_eof = gdma_on_trans_eof_callback}; + gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL); + + + parlio_ll_tx_reset_clock(&PARL_IO); + parlio_ll_tx_reset_fifo(&PARL_IO); + parlio_ll_tx_enable_clock(&PARL_IO, false); + parlio_ll_tx_enable_clock_gating(&PARL_IO, 0); + parlio_ll_tx_set_bus_width(&PARL_IO, 16); + parlio_ll_tx_treat_msb_as_valid(&PARL_IO, false); + + auto sample_edge = _cfg.invert_pclk ? PARLIO_SAMPLE_EDGE_NEG : PARLIO_SAMPLE_EDGE_POS; + parlio_ll_tx_set_sample_clock_edge(&PARL_IO, sample_edge); + + parlio_ll_clear_interrupt_status(&PARL_IO, PARLIO_LL_EVENT_TX_MASK); + + + int8_t *pins = _cfg.pin_data; + gpio_config_t gpio_conf = { + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + + for (int i = 0; i < 16; i++) + { + if (pins[i] >= 0) + { // -1 value will CRASH the ESP32! + gpio_conf.pin_bit_mask = BIT64(pins[i]); + gpio_config(&gpio_conf); + esp_rom_gpio_connect_out_signal(pins[i], parlio_periph_signals.groups[0].tx_units[0].data_sigs[i], false, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pins[i]], PIN_FUNC_GPIO); + gpio_set_drive_capability((gpio_num_t)pins[i], (gpio_drive_cap_t)3); + } + } + + // Clock + gpio_conf.pin_bit_mask = BIT64(_cfg.pin_wr); + gpio_config(&gpio_conf); + esp_rom_gpio_connect_out_signal(_cfg.pin_wr, parlio_periph_signals.groups[0].tx_units[0].clk_out_sig, _cfg.invert_pclk, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[_cfg.pin_wr], PIN_FUNC_GPIO); + gpio_set_drive_capability((gpio_num_t)_cfg.pin_wr, (gpio_drive_cap_t)3); + + parlio_ll_tx_set_idle_data_value(&PARL_IO, 0); + parlio_ll_tx_set_trans_bit_len(&PARL_IO, ((1<<16) -1) * 8); + + return true; // no return val = illegal instruction +} + +void Bus_Parallel16::release(void) +{ + if (_dmadesc_a) + { + heap_caps_free(_dmadesc_a); + _dmadesc_a = nullptr; + + } + if(_dmadesc_b){ + heap_caps_free(_dmadesc_b); + _dmadesc_b = nullptr; + } + _dmadesc_count = 0; +} + +void Bus_Parallel16::enable_double_dma_desc(void) +{ + ESP_LOGI("C6", "Enabled support for secondary DMA buffer."); + _double_dma_buffer = true; +} + +// Need this to work for double buffers etc. +bool Bus_Parallel16::allocate_dma_desc_memory(size_t len) +{ + if (_dmadesc_a) + heap_caps_free(_dmadesc_a); // free all dma descrptios previously + _dmadesc_count = len; + + ESP_LOGD("C6", "Allocating %d bytes memory for DMA descriptors.", (int)sizeof(HUB75_DMA_DESCRIPTOR_T) * len); + + _dmadesc_a = (HUB75_DMA_DESCRIPTOR_T *)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + + if (_dmadesc_a == nullptr) + { + ESP_LOGE("C6", "ERROR: Couldn't malloc _dmadesc_a. Not enough memory."); + return false; + } + + if (_double_dma_buffer) + { + _dmadesc_b = (HUB75_DMA_DESCRIPTOR_T *)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + + if (_dmadesc_b == nullptr) + { + ESP_LOGE("C6", "ERROR: Couldn't malloc _dmadesc_b. Not enough memory."); + _double_dma_buffer = false; + } + } + + /// override static + _dmadesc_a_idx = 0; + _dmadesc_b_idx = 0; + + return true; +} + +void Bus_Parallel16::create_dma_desc_link(void *data, size_t size, bool dmadesc_b) +{ + static constexpr size_t MAX_DMA_LEN = (4096 - 4); + + if (size > MAX_DMA_LEN) + { + size = MAX_DMA_LEN; + ESP_LOGW("C6", "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!"); + } + + if (dmadesc_b == true) + { + + _dmadesc_b[_dmadesc_b_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + //_dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = 0; + _dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = (_dmadesc_b_idx == (_dmadesc_count - 1)); + _dmadesc_b[_dmadesc_b_idx].dw0.size = _dmadesc_b[_dmadesc_b_idx].dw0.length = size; // sizeof(data); + _dmadesc_b[_dmadesc_b_idx].buffer = data; // data; + + if (_dmadesc_b_idx == _dmadesc_count - 1) + { + _dmadesc_b[_dmadesc_b_idx].next = (dma_descriptor_t *)&_dmadesc_b[0]; + } + else + { + _dmadesc_b[_dmadesc_b_idx].next = (dma_descriptor_t *)&_dmadesc_b[_dmadesc_b_idx + 1]; + } + + _dmadesc_b_idx++; + } + else + { + + if (_dmadesc_a_idx >= _dmadesc_count) + { + ESP_LOGE("C6", "Attempted to create more DMA descriptors than allocated. Expecting max %u descriptors.", (unsigned int)_dmadesc_count); + return; + } + + _dmadesc_a[_dmadesc_a_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + //_dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = 0; + _dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = (_dmadesc_a_idx == (_dmadesc_count - 1)); + _dmadesc_a[_dmadesc_a_idx].dw0.size = _dmadesc_a[_dmadesc_a_idx].dw0.length = size; // sizeof(data); + _dmadesc_a[_dmadesc_a_idx].buffer = data; // data; + + if (_dmadesc_a_idx == _dmadesc_count - 1) + { + _dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *)&_dmadesc_a[0]; + } + else + { + _dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *)&_dmadesc_a[_dmadesc_a_idx + 1]; + } + + _dmadesc_a_idx++; + } + +} // end create_dma_desc_link + +void Bus_Parallel16::dma_transfer_start() +{ + parlio_ll_tx_reset_fifo(&PARL_IO); + parlio_ll_tx_reset_clock(&PARL_IO); + + gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); + + while (parlio_ll_tx_is_ready(&PARL_IO) == false); + + parlio_ll_tx_start(&PARL_IO, true); + parlio_ll_tx_enable_clock(&PARL_IO, true); + +} // end + +void Bus_Parallel16::dma_transfer_stop() +{ + + gdma_stop(dma_chan); + +} // end + +void Bus_Parallel16::flip_dma_output_buffer(int back_buffer_id) +{ + + // if ( _double_dma_buffer == false) return; + + if (back_buffer_id == 1) // change across to everything 'b'' + { + _dmadesc_a[_dmadesc_count - 1].next = (dma_descriptor_t *)&_dmadesc_b[0]; + _dmadesc_b[_dmadesc_count - 1].next = (dma_descriptor_t *)&_dmadesc_b[0]; + } + else + { + _dmadesc_b[_dmadesc_count - 1].next = (dma_descriptor_t *)&_dmadesc_a[0]; + _dmadesc_a[_dmadesc_count - 1].next = (dma_descriptor_t *)&_dmadesc_a[0]; + } + + // current_back_buffer_id ^= 1; + + previousBufferFree = false; + + // while (i2s_parallel_is_previous_buffer_free() == false) {} + while (!previousBufferFree) + ; + +} // end flip + +#endif \ No newline at end of file diff --git a/src/platforms/esp32c6/dma_parallel_io.hpp b/src/platforms/esp32c6/dma_parallel_io.hpp new file mode 100644 index 0000000..3d6bbab --- /dev/null +++ b/src/platforms/esp32c6/dma_parallel_io.hpp @@ -0,0 +1,117 @@ +/* + Simple example of using the ESP32-C6's parallel IO peripheral for general-purpose + parallel data output with DMA. + + Credits to ESPRESSIF them selfe, they made the first example: + + https://github.com/espressif/esp-idf/tree/release/v5.1/examples/peripherals/parlio/simple_rgb_led_matrix + + And Credits to the guy who implemented the S3 version of this library. + There is a lot of resusable code + + +*/ + + +#pragma once + +#include + +#ifdef CONFIG_IDF_TARGET_ESP32C6 + +#include "driver/parlio_tx.h" + +#include +#include +#include + + +#define DMA_MAX (4096-4) +#define HUB75_DMA_DESCRIPTOR_T dma_descriptor_t + + +class Bus_Parallel16 + { + public: + Bus_Parallel16() + { + + } + + struct config_t + { + // LCD_CAM peripheral number. No need to change (only 0 for ESP32-S3.) + //int port = 0; + + // max 40MHz (when in 16 bit / 2 byte mode) + uint32_t bus_freq = 10000000; + int8_t pin_wr = -1; + //int8_t pin_rd = -1; + //int8_t pin_rs = -1; // D/C + bool invert_pclk = false; + //bool psram_clk_override = false; + union + { + int8_t pin_data[16]; + struct + { + int8_t pin_d0; + int8_t pin_d1; + int8_t pin_d2; + int8_t pin_d3; + int8_t pin_d4; + int8_t pin_d5; + int8_t pin_d6; + int8_t pin_d7; + int8_t pin_d8; + int8_t pin_d9; + int8_t pin_d10; + int8_t pin_d11; + int8_t pin_d12; + int8_t pin_d13; + int8_t pin_d14; + int8_t pin_d15; + }; + }; + }; + + const config_t& config(void) const { return _cfg; } + void config(const config_t& config); + + bool init(void) ; + + void release(void) ; + + void enable_double_dma_desc(); + bool allocate_dma_desc_memory(size_t len); + + void create_dma_desc_link(void *memory, size_t size, bool dmadesc_b = false); + + void dma_transfer_start(); + void dma_transfer_stop(); + + void flip_dma_output_buffer(int back_buffer_id); + + private: + + config_t _cfg; + + + gdma_channel_handle_t dma_chan; + + uint32_t _dmadesc_count = 0; // number of dma decriptors + + uint32_t _dmadesc_a_idx = 0; + uint32_t _dmadesc_b_idx = 0; + + HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr; + HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr; + + bool _double_dma_buffer = false; + //bool _dmadesc_a_active = true; + + }; + + + +#endif \ No newline at end of file diff --git a/src/platforms/esp32c6/esp32c6-default-pins.hpp b/src/platforms/esp32c6/esp32c6-default-pins.hpp new file mode 100644 index 0000000..ef9c04e --- /dev/null +++ b/src/platforms/esp32c6/esp32c6-default-pins.hpp @@ -0,0 +1,18 @@ +#pragma once + +// Avoid and QSPI pins + +#define R1_PIN_DEFAULT 7 +#define G1_PIN_DEFAULT 4 +#define B1_PIN_DEFAULT 1 +#define R2_PIN_DEFAULT 6 +#define G2_PIN_DEFAULT 3 +#define B2_PIN_DEFAULT 0 +#define A_PIN_DEFAULT 20 +#define B_PIN_DEFAULT 21 +#define C_PIN_DEFAULT 22 +#define D_PIN_DEFAULT 23 +#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32 +#define LAT_PIN_DEFAULT 5 +#define OE_PIN_DEFAULT 2 +#define CLK_PIN_DEFAULT 10 diff --git a/src/platforms/platform_detect.hpp b/src/platforms/platform_detect.hpp index 9532c88..fd2e7f5 100644 --- a/src/platforms/platform_detect.hpp +++ b/src/platforms/platform_detect.hpp @@ -22,11 +22,7 @@ Modified heavily for the ESP32 HUB75 DMA library by: #include - #if defined (CONFIG_IDF_TARGET_ESP32C3) - - #error "ERROR: ESP32C3 not supported." - - #elif defined (CONFIG_IDF_TARGET_ESP32S2) + #if defined (CONFIG_IDF_TARGET_ESP32S2) //#pragma message "Compiling for ESP32-S2" #include "esp32/esp32_i2s_parallel_dma.hpp" @@ -47,9 +43,14 @@ Modified heavily for the ESP32 HUB75 DMA library by: #define NO_FAST_FUNCTIONS 1 #endif - #elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32H2) + #elif defined(CONFIG_IDF_TARGET_ESP32C6) - #error "ESP32 RISC-V devices do not have an LCD interface and are therefore not supported by this library." + #include "esp32c6/dma_parallel_io.hpp" + #include "esp32c6/esp32c6-default-pins.hpp" + + #elif defined(CONFIG_IDF_TARGET_ESP32P4) + + #pragma message "you are ahead of your time. ESP32P4 Support is planned" #elif defined (CONFIG_IDF_TARGET_ESP32) || defined(ESP32) @@ -63,6 +64,11 @@ Modified heavily for the ESP32 HUB75 DMA library by: #include "esp32/esp32_i2s_parallel_dma.hpp" #include "esp32/esp32-default-pins.hpp" +#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32H2) + + #error "ESP32 C2 C3 and H2 devices are not supported by this library." + + #else #error "Unknown platform."