aboutsummaryrefslogtreecommitdiff
path: root/src/platforms
diff options
context:
space:
mode:
authormrfaptastic <12006953+mrfaptastic@users.noreply.github.com>2022-09-30 03:17:19 +0100
committermrfaptastic <12006953+mrfaptastic@users.noreply.github.com>2022-09-30 03:17:19 +0100
commitebe75dcaba0239d225243cdedd31aaf860abbd0a (patch)
treefda21143906b93de687447af52c40f9329956d21 /src/platforms
parent86063fe594cda6a572bd335e7e34af7c75226aad (diff)
Update to include S3 support.
Refactor tonnes of code. Double buffering not yet fully tested. PSRAM support doesn't work at all - garbled mess. Enable in platformIO using: build_flags = -DSPIRAM_FRAMEBUFFER=1
Diffstat (limited to 'src/platforms')
-rw-r--r--src/platforms/esp32/esp32-default-pins.hpp18
-rw-r--r--src/platforms/esp32/esp32_i2s_parallel_dma.c.txt444
-rw-r--r--src/platforms/esp32/esp32_i2s_parallel_dma.cpp571
-rw-r--r--src/platforms/esp32/esp32_i2s_parallel_dma.h.txt106
-rw-r--r--src/platforms/esp32/esp32_i2s_parallel_dma.hpp138
-rw-r--r--src/platforms/esp32s2/esp32s2-default-pins.hpp16
-rw-r--r--src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.pngbin0 -> 497667 bytes
-rw-r--r--src/platforms/esp32s3/Readme.md16
-rw-r--r--src/platforms/esp32s3/ReservedPinsForPSRAM.PNGbin0 -> 94554 bytes
-rw-r--r--src/platforms/esp32s3/esp32s3-default-pins.hpp18
-rw-r--r--src/platforms/esp32s3/gdma_lcd_parallel16.cpp356
-rw-r--r--src/platforms/esp32s3/gdma_lcd_parallel16.hpp176
-rw-r--r--src/platforms/platform_detect.hpp57
13 files changed, 1916 insertions, 0 deletions
diff --git a/src/platforms/esp32/esp32-default-pins.hpp b/src/platforms/esp32/esp32-default-pins.hpp
new file mode 100644
index 0000000..5ee94b9
--- /dev/null
+++ b/src/platforms/esp32/esp32-default-pins.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#define R1_PIN_DEFAULT 25
+#define G1_PIN_DEFAULT 26
+#define B1_PIN_DEFAULT 27
+#define R2_PIN_DEFAULT 14
+#define G2_PIN_DEFAULT 12
+#define B2_PIN_DEFAULT 13
+
+#define A_PIN_DEFAULT 23
+#define B_PIN_DEFAULT 19
+#define C_PIN_DEFAULT 5
+#define D_PIN_DEFAULT 17
+#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
+
+#define LAT_PIN_DEFAULT 4
+#define OE_PIN_DEFAULT 15
+#define CLK_PIN_DEFAULT 16
diff --git a/src/platforms/esp32/esp32_i2s_parallel_dma.c.txt b/src/platforms/esp32/esp32_i2s_parallel_dma.c.txt
new file mode 100644
index 0000000..82eebe6
--- /dev/null
+++ b/src/platforms/esp32/esp32_i2s_parallel_dma.c.txt
@@ -0,0 +1,444 @@
+/*
+ * ESP32_I2S_PARALLEL_DMA (Version 3)
+ *
+ * Author: Mrfaptastic @ https://github.com/mrfaptastic/
+ *
+ * Description: Multi-ESP32 product DMA setup functions for WROOM & S2, S3 mcu's.
+ *
+ * Credits:
+ * 1) https://www.esp32.com/viewtopic.php?f=17&t=3188 for original ref. implementation
+ * 2) https://github.com/TobleMiner/esp_i2s_parallel for a cleaner implementation
+ *
+ */
+
+// Header
+#include "esp32_i2s_parallel_dma.h"
+#include "esp32_i2s_parallel_mcu_def.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <driver/gpio.h>
+#include <driver/periph_ctrl.h>
+#include <soc/gpio_sig_map.h>
+
+// For I2S state management.
+static i2s_parallel_state_t *i2s_state = NULL;
+
+// ESP32-S2,S3,C3 only has IS20
+// Original ESP32 has two I2S's, but we'll stick with the lowest common denominator.
+
+#ifdef ESP32_ORIG
+static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1};
+#else
+static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0};
+#endif
+
+callback shiftCompleteCallback;
+void setShiftCompleteCallback(callback f) {
+ shiftCompleteCallback = f;
+}
+
+volatile int previousBufferOutputLoopCount = 0;
+volatile bool previousBufferFree = true;
+
+static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
+
+//i2s_port_t port = *((i2s_port_t*) arg);
+
+/* Saves a few cycles, no need to cast void ptr to i2s_port_t and then check 120 times second... */
+ SET_PERI_REG_BITS(I2S_INT_CLR_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S);
+
+ previousBufferFree = true;
+
+/*
+ if(shiftCompleteCallback) { // we've defined a callback function ?
+ shiftCompleteCallback();
+ }
+*/
+
+} // end irq_hndlr
+
+
+// For peripheral setup and configuration
+static inline int get_bus_width(i2s_parallel_cfg_bits_t width) {
+ switch(width) {
+ case I2S_PARALLEL_WIDTH_8:
+ return 8;
+ case I2S_PARALLEL_WIDTH_16:
+ return 16;
+ case I2S_PARALLEL_WIDTH_24:
+ return 24;
+ default:
+ return -ESP_ERR_INVALID_ARG;
+ }
+}
+
+static void iomux_set_signal(int gpio, int signal) {
+ if(gpio < 0) {
+ return;
+ }
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
+ gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
+ gpio_matrix_out(gpio, signal, false, false);
+
+ // More mA the better...
+ gpio_set_drive_capability((gpio_num_t)gpio, (gpio_drive_cap_t)3);
+
+}
+
+static void dma_reset(i2s_dev_t* dev) {
+ dev->lc_conf.in_rst = 1;
+ dev->lc_conf.in_rst = 0;
+ dev->lc_conf.out_rst = 1;
+ dev->lc_conf.out_rst = 0;
+
+ dev->lc_conf.ahbm_rst = 1;
+ dev->lc_conf.ahbm_rst = 0;
+
+
+}
+
+static void fifo_reset(i2s_dev_t* dev) {
+ dev->conf.rx_fifo_reset = 1;
+
+#ifdef ESP32_SXXX
+ while(dev->conf.rx_fifo_reset_st); // esp32-s2 only
+#endif
+ dev->conf.rx_fifo_reset = 0;
+
+ dev->conf.tx_fifo_reset = 1;
+#ifdef ESP32_SXXX
+ while(dev->conf.tx_fifo_reset_st); // esp32-s2 only
+#endif
+
+ dev->conf.tx_fifo_reset = 0;
+}
+
+static void dev_reset(i2s_dev_t* dev) {
+ fifo_reset(dev);
+ dma_reset(dev);
+ dev->conf.rx_reset=1;
+ dev->conf.tx_reset=1;
+ dev->conf.rx_reset=0;
+ dev->conf.tx_reset=0;
+}
+
+// DMA Linked List
+// Size must be less than DMA_MAX - need to handle breaking long transfer into two descriptors before call
+// DMA_MAX by the way is the maximum data packet size you can hold in one chunk
+void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size)
+{
+ if(size > DMA_MAX) size = DMA_MAX;
+
+ dmadesc->size = size;
+ dmadesc->length = size;
+ dmadesc->buf = memory;
+ dmadesc->eof = 0;
+ dmadesc->sosf = 0;
+ dmadesc->owner = 1;
+ dmadesc->qe.stqe_next = 0; // will need to set this elsewhere
+ dmadesc->offset = 0;
+
+ // link previous to current
+ if(prevdmadesc)
+ prevdmadesc->qe.stqe_next = (lldesc_t*)dmadesc;
+}
+
+
+
+esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* conf) {
+
+ //port = I2S_NUM_0; /// override.
+
+ if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ if(conf->sample_width < I2S_PARALLEL_WIDTH_8 || conf->sample_width >= I2S_PARALLEL_WIDTH_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ if(conf->sample_rate > I2S_PARALLEL_CLOCK_HZ || conf->sample_rate < 1) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ uint32_t clk_div_main = I2S_PARALLEL_CLOCK_HZ / conf->sample_rate / i2s_parallel_get_memory_width(port, conf->sample_width);
+ if(clk_div_main < 2 || clk_div_main > 0xFF) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ volatile int iomux_signal_base;
+ volatile int iomux_clock;
+ int irq_source;
+
+ // Initialize I2S0 peripheral
+ if (port == I2S_NUM_0) {
+ periph_module_reset(PERIPH_I2S0_MODULE);
+ periph_module_enable(PERIPH_I2S0_MODULE);
+ iomux_clock = I2S0O_WS_OUT_IDX;
+ irq_source = ETS_I2S0_INTR_SOURCE;
+
+ switch(conf->sample_width) {
+ case I2S_PARALLEL_WIDTH_8:
+ case I2S_PARALLEL_WIDTH_16:
+ iomux_signal_base = I2S0O_DATA_OUT8_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_24:
+ iomux_signal_base = I2S0O_DATA_OUT0_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_MAX:
+ return ESP_ERR_INVALID_ARG;
+ }
+ }
+#ifdef ESP32_ORIG
+ // Can't compile if I2S1 if it doesn't exist with that hardware's IDF....
+ else {
+// I2S = &I2S1;
+
+ periph_module_reset(PERIPH_I2S1_MODULE);
+ periph_module_enable(PERIPH_I2S1_MODULE);
+ iomux_clock = I2S1O_WS_OUT_IDX;
+ irq_source = ETS_I2S1_INTR_SOURCE;
+
+ switch(conf->sample_width) {
+ case I2S_PARALLEL_WIDTH_16:
+ iomux_signal_base = I2S1O_DATA_OUT8_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_8:
+ case I2S_PARALLEL_WIDTH_24:
+ iomux_signal_base = I2S1O_DATA_OUT0_IDX;
+ break;
+ case I2S_PARALLEL_WIDTH_MAX:
+ return ESP_ERR_INVALID_ARG;
+ }
+ }
+#endif
+
+ // Setup GPIOs
+ int bus_width = get_bus_width(conf->sample_width);
+
+ // Setup I2S peripheral
+ i2s_dev_t* dev = I2S[port];
+ //dev_reset(dev);
+
+
+ // Setup GPIO's
+ for(int i = 0; i < bus_width; i++) {
+ iomux_set_signal(conf->gpio_bus[i], iomux_signal_base + i);
+ }
+ iomux_set_signal(conf->gpio_clk, iomux_clock);
+
+ // invert clock phase if required
+ if (conf->clkphase)
+ GPIO.func_out_sel_cfg[conf->gpio_clk].inv_sel = 1;
+
+ // Setup i2s clock
+ dev->sample_rate_conf.val = 0;
+
+ // Third stage config, width of data to be written to IO (I think this should always be the actual data width?)
+ dev->sample_rate_conf.rx_bits_mod = bus_width;
+ dev->sample_rate_conf.tx_bits_mod = bus_width;
+
+ dev->sample_rate_conf.rx_bck_div_num = 2;
+ dev->sample_rate_conf.tx_bck_div_num = 2;
+
+ // Clock configuration
+ dev->clkm_conf.val=0; // Clear the clkm_conf struct
+
+#ifdef ESP32_SXXX
+ dev->clkm_conf.clk_sel = 2; // esp32-s2 only
+ dev->clkm_conf.clk_en = 1;
+#endif
+
+#ifdef ESP32_ORIG
+ dev->clkm_conf.clka_en=0; // Use the 160mhz system clock (PLL_D2_CLK) when '0'
+#endif
+
+ dev->clkm_conf.clkm_div_b=0; // Clock numerator
+ dev->clkm_conf.clkm_div_a=1; // Clock denominator
+
+
+ // Note: clkm_div_num must only be set here AFTER clkm_div_b, clkm_div_a, etc. Or weird things happen!
+ // On original ESP32, max I2S DMA parallel speed is 20Mhz.
+ dev->clkm_conf.clkm_div_num = clk_div_main;
+
+
+ // I2S conf2 reg
+ dev->conf2.val = 0;
+ dev->conf2.lcd_en = 1;
+ dev->conf2.lcd_tx_wrx2_en=0;
+ dev->conf2.lcd_tx_sdx2_en=0;
+
+ // I2S conf reg
+ dev->conf.val = 0;
+
+#ifdef ESP32_SXXX
+ dev->conf.tx_dma_equal=1; // esp32-s2 only
+ dev->conf.pre_req_en=1; // esp32-s2 only - enable I2S to prepare data earlier? wtf?
+#endif
+
+ // Now start setting up DMA FIFO
+ dev->fifo_conf.val = 0;
+ dev->fifo_conf.rx_data_num = 32; // Thresholds.
+ dev->fifo_conf.tx_data_num = 32;
+ dev->fifo_conf.dscr_en = 1;
+
+#ifdef ESP32_ORIG
+
+ // Enable "One datum will be written twice in LCD mode" - for some reason,
+ // if we don't do this in 8-bit mode, data is updated on half-clocks not clocks
+ if(conf->sample_width == I2S_PARALLEL_WIDTH_8)
+ dev->conf2.lcd_tx_wrx2_en=1;
+
+ // Not really described for non-pcm modes, although datasheet states it should be set correctly even for LCD mode
+ // First stage config. Configures how data is loaded into fifo
+ if(conf->sample_width == I2S_PARALLEL_WIDTH_24) {
+ // Mode 0, single 32-bit channel, linear 32 bit load to fifo
+ dev->fifo_conf.tx_fifo_mod = 3;
+ } else {
+ // Mode 1, single 16-bit channel, load 16 bit sample(*) into fifo and pad to 32 bit with zeros
+ // *Actually a 32 bit read where two samples are read at once. Length of fifo must thus still be word-aligned
+ dev->fifo_conf.tx_fifo_mod = 1;
+ }
+
+ // Dictated by ESP32 datasheet
+ dev->fifo_conf.rx_fifo_mod_force_en = 1;
+ dev->fifo_conf.tx_fifo_mod_force_en = 1;
+
+ // Second stage config
+ dev->conf_chan.val = 0;
+
+ // 16-bit single channel data
+ dev->conf_chan.tx_chan_mod = 1;
+ dev->conf_chan.rx_chan_mod = 1;
+
+#endif
+
+
+ // Device Reset
+ dev_reset(dev);
+ dev->conf1.val = 0;
+ dev->conf1.tx_stop_en = 0;
+
+ // Allocate I2S status structure for buffer swapping stuff
+ i2s_state = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t));
+ assert(i2s_state != NULL);
+ i2s_parallel_state_t *state = i2s_state;
+
+ state->desccount_a = conf->desccount_a;
+ state->desccount_b = conf->desccount_b;
+ state->dmadesc_a = conf->lldesc_a;
+ state->dmadesc_b = conf->lldesc_b;
+ state->i2s_interrupt_port_arg = port; // need to keep this somewhere in static memory for the ISR
+
+ dev->timing.val = 0;
+
+ // We using the double buffering switch logic?
+ if (conf->int_ena_out_eof)
+ {
+ // Get ISR setup
+ esp_err_t err = esp_intr_alloc(irq_source,
+ (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1),
+ irq_hndlr,
+ &state->i2s_interrupt_port_arg, NULL);
+
+ if(err) {
+ return err;
+ }
+
+
+ // Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual)
+ // "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet"
+ // ... whatever the hell that is supposed to mean... One massive linked list? So all pixels in the chain?
+ dev->int_ena.out_eof = 1;
+ }
+
+ return ESP_OK;
+}
+
+ esp_err_t i2s_parallel_stop_dma(i2s_port_t port) {
+ if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ i2s_dev_t* dev = I2S[port];
+
+ // Stop all ongoing DMA operations
+ dev->out_link.stop = 1;
+ dev->out_link.start = 0;
+ dev->conf.tx_start = 0;
+
+ return ESP_OK;
+}
+
+
+ esp_err_t i2s_parallel_send_dma(i2s_port_t port, lldesc_t* dma_descriptor) {
+ if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ i2s_dev_t* dev = I2S[port];
+
+
+ // Configure DMA burst mode
+ dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN;
+
+ // Set address of DMA descriptor
+ dev->out_link.addr = (uint32_t) dma_descriptor;
+
+ // Start DMA operation
+ dev->out_link.stop = 0;
+ dev->out_link.start = 1;
+
+ dev->conf.tx_start = 1;
+
+ return ESP_OK;
+}
+/*
+i2s_dev_t* i2s_parallel_get_dev(i2s_port_t port) {
+ if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) {
+ return NULL;
+ }
+
+#ifdef ESP32_ORIG
+if (port == I2S_NUM_1)
+ return &I2S1;
+#endif
+
+ return I2S0; // HARCODE THIS TO RETURN &I2S0
+}
+*/
+// Double buffering flipping
+// Flip to a buffer: 0 for bufa, 1 for bufb
+// dmadesc_a and dmadesc_b point to the same memory if double buffering isn't enabled.
+void i2s_parallel_flip_to_buffer(i2s_port_t port, int buffer_id) {
+
+ if (i2s_state == NULL) {
+ return; // :-()
+ }
+
+ lldesc_t *active_dma_chain;
+ if (buffer_id == 0) {
+ active_dma_chain=(lldesc_t*)&i2s_state->dmadesc_a[0];
+ } else {
+ active_dma_chain=(lldesc_t*)&i2s_state->dmadesc_b[0];
+ }
+
+ // setup linked list to refresh from new buffer (continuously) when the end of the current list has been reached
+ i2s_state->dmadesc_a[i2s_state->desccount_a-1].qe.stqe_next = active_dma_chain;
+ i2s_state->dmadesc_b[i2s_state->desccount_b-1].qe.stqe_next = active_dma_chain;
+
+ // we're still shifting out the buffer, so it shouldn't be written to yet.
+ //previousBufferFree = false;
+ i2s_parallel_set_previous_buffer_not_free();
+}
+
+bool i2s_parallel_is_previous_buffer_free() {
+ return previousBufferFree;
+}
+
+
+void i2s_parallel_set_previous_buffer_not_free() {
+ previousBufferFree = false;
+ previousBufferOutputLoopCount = 0;
+}
diff --git a/src/platforms/esp32/esp32_i2s_parallel_dma.cpp b/src/platforms/esp32/esp32_i2s_parallel_dma.cpp
new file mode 100644
index 0000000..94f0d50
--- /dev/null
+++ b/src/platforms/esp32/esp32_i2s_parallel_dma.cpp
@@ -0,0 +1,571 @@
+/*----------------------------------------------------------------------------/
+ Lovyan GFX - Graphics library for embedded devices.
+
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+
+Modified heavily for the ESP32 HUB75 DMA library by:
+ [mrfaptastic](https://github.com/mrfaptastic)
+
+/----------------------------------------------------------------------------*/
+
+static const char* TAG = "esp32_i2s_parallel_dma";
+
+#include <sdkconfig.h>
+#if defined (CONFIG_IDF_TARGET_ESP32)
+
+#include "esp32_i2s_parallel_dma.hpp"
+
+#include <driver/gpio.h>
+#include <driver/periph_ctrl.h>
+#include <soc/gpio_sig_map.h>
+
+#include <esp_err.h>
+#include <esp_log.h>
+
+/*
+
+callback shiftCompleteCallback;
+void setShiftCompleteCallback(callback f) {
+ shiftCompleteCallback = f;
+}
+
+volatile int previousBufferOutputLoopCount = 0;
+volatile bool previousBufferFree = true;
+
+static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
+
+ SET_PERI_REG_BITS(I2S_INT_CLR_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S);
+
+ previousBufferFree = true;
+
+
+
+} // end irq_hndlr
+*/
+
+
+ static i2s_dev_t* getDev(int port)
+ {
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+ return &I2S0;
+ #else
+ return (port == 0) ? &I2S0 : &I2S1;
+ #endif
+ }
+
+ void Bus_Parallel16::config(const config_t& cfg)
+ {
+ ESP_LOGI(TAG, "Performing config for ESP32 or ESP32-S2");
+ _cfg = cfg;
+ auto port = cfg.port;
+ _dev = getDev(port);
+ }
+
+
+//#if defined (CONFIG_IDF_TARGET_ESP32S2)
+
+ static void _gpio_pin_init(int pin)
+ {
+ if (pin >= 0)
+ {
+ gpio_pad_select_gpio(pin);
+ //gpio_hi(pin);
+ gpio_set_direction((gpio_num_t)pin, GPIO_MODE_OUTPUT);
+ gpio_set_drive_capability((gpio_num_t)pin, (gpio_drive_cap_t)3); // esp32s3 as well?
+ }
+ }
+
+
+ bool Bus_Parallel16::init(void) // The big one that gets everything setup.
+ {
+ ESP_LOGI(TAG, "Performing DMA bus init() for ESP32 or ESP32-S2");
+
+ if(_cfg.port < I2S_NUM_0 || _cfg.port >= I2S_NUM_MAX) {
+ //return ESP_ERR_INVALID_ARG;
+ return false;
+ }
+
+ if(_cfg.parallel_width < 8 || _cfg.parallel_width >= 24) {
+ return false;
+ }
+
+ //auto freq = (_cfg.freq_write, 50000000u); // ?
+ auto freq = (_cfg.bus_freq);
+
+ uint32_t _clkdiv_write = 0;
+ size_t _div_num = 10;
+
+ // Calculate clock divider for ESP32-S2
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+
+ static constexpr uint32_t pll_160M_clock_d2 = 160 * 1000 * 1000 >> 1;
+
+ // I2S_CLKM_DIV_NUM 2=40MHz / 3=27MHz / 4=20MHz / 5=16MHz / 8=10MHz / 10=8MHz
+ _div_num = std::min(255u, 1 + ((pll_160M_clock_d2) / (1 + _cfg.freq_write)));
+
+ _clkdiv_write = I2S_CLK_160M_PLL << I2S_CLK_SEL_S
+ | I2S_CLK_EN
+ | 1 << I2S_CLKM_DIV_A_S
+ | 0 << I2S_CLKM_DIV_B_S
+ | _div_num << I2S_CLKM_DIV_NUM_S
+ ;
+
+ #else
+
+
+ // clock = 80MHz(PLL_D2_CLK)
+ static constexpr uint32_t pll_d2_clock = 80 * 1000 * 1000;
+
+ // I2S_CLKM_DIV_NUM 4=20MHz / 5=16MHz / 8=10MHz / 10=8MHz
+ _div_num = std::min(255u, std::max(3u, 1 + (pll_d2_clock / (1 + freq))));
+
+ _clkdiv_write = I2S_CLK_EN
+ | 1 << I2S_CLKM_DIV_A_S
+ | 0 << I2S_CLKM_DIV_B_S
+ | _div_num << I2S_CLKM_DIV_NUM_S
+ ;
+ #endif
+
+ if(_div_num < 2 || _div_num > 16) {
+ return false;
+ }
+
+ //ESP_LOGI(TAG, "i2s pll clk_div_main is: %d", _div_num);
+
+ auto dev = _dev;
+ volatile int iomux_signal_base;
+ volatile int iomux_clock;
+ int irq_source;
+
+ // Initialize I2S0 peripheral
+ if (_cfg.port == 0)
+ {
+
+ periph_module_reset(PERIPH_I2S0_MODULE);
+ periph_module_enable(PERIPH_I2S0_MODULE);
+
+ iomux_clock = I2S0O_WS_OUT_IDX;
+ irq_source = ETS_I2S0_INTR_SOURCE;
+
+ switch(_cfg.parallel_width) {
+ case 8:
+ case 16:
+ iomux_signal_base = I2S0O_DATA_OUT8_IDX;
+ break;
+ case 24:
+ iomux_signal_base = I2S0O_DATA_OUT0_IDX;
+ break;
+ default:
+ return ESP_ERR_INVALID_ARG;
+ }
+ }
+
+ #if !defined (CONFIG_IDF_TARGET_ESP32S2)
+ // Can't compile if I2S1 if it doesn't exist with that hardware's IDF....
+ else {
+ periph_module_reset(PERIPH_I2S1_MODULE);
+ periph_module_enable(PERIPH_I2S1_MODULE);
+ iomux_clock = I2S1O_WS_OUT_IDX;
+ irq_source = ETS_I2S1_INTR_SOURCE;
+
+ switch(_cfg.parallel_width) {
+ case 16:
+ iomux_signal_base = I2S1O_DATA_OUT8_IDX;
+ break;
+ case 8:
+ case 24:
+ iomux_signal_base = I2S1O_DATA_OUT0_IDX;
+ break;
+ default:
+ return ESP_ERR_INVALID_ARG;
+ }
+ }
+ #endif
+
+ // Setup GPIOs
+ int bus_width = _cfg.parallel_width;
+
+ // Clock output GPIO setup
+ _gpio_pin_init(_cfg.pin_rd); // not used
+ _gpio_pin_init(_cfg.pin_wr); // clock
+ _gpio_pin_init(_cfg.pin_rs); // not used
+
+ // Data output GPIO setup
+ int8_t* pins = _cfg.pin_data;
+
+ for(int i = 0; i < bus_width; i++)
+ _gpio_pin_init(pins[i]);
+
+ // Route clock signal to clock pin
+ gpio_matrix_out(_cfg.pin_wr, iomux_clock, _cfg.invert_pclk, 0); // inverst clock if required
+
+ for (size_t i = 0; i < bus_width; i++) {
+
+ if (pins[i] >= 0) {
+ gpio_matrix_out(pins[i], iomux_signal_base + i, false, false);
+ }
+ }
+
+
+ // Setup i2s clock
+ dev->sample_rate_conf.val = 0;
+
+ // Third stage config, width of data to be written to IO (I think this should always be the actual data width?)
+ dev->sample_rate_conf.rx_bits_mod = bus_width;
+ dev->sample_rate_conf.tx_bits_mod = bus_width;
+
+ dev->sample_rate_conf.rx_bck_div_num = 2;
+ dev->sample_rate_conf.tx_bck_div_num = 2;
+
+ // Clock configuration
+ // dev->clkm_conf.val=0; // Clear the clkm_conf struct
+ /*
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+ dev->clkm_conf.clk_sel = 2; // esp32-s2 only
+ dev->clkm_conf.clk_en = 1;
+ #endif
+
+ #if !defined (CONFIG_IDF_TARGET_ESP32S2)
+ dev->clkm_conf.clka_en=0; // Use the 80mhz system clock (PLL_D2_CLK) when '0'
+ #endif
+
+ dev->clkm_conf.clkm_div_b=0; // Clock numerator
+ dev->clkm_conf.clkm_div_a=1; // Clock denominator
+ */
+
+ // Note: clkm_div_num must only be set here AFTER clkm_div_b, clkm_div_a, etc. Or weird things happen!
+ // On original ESP32, max I2S DMA parallel speed is 20Mhz.
+ //dev->clkm_conf.clkm_div_num = 32;
+ dev->clkm_conf.val = _clkdiv_write;
+
+ // I2S conf2 reg
+ dev->conf2.val = 0;
+ dev->conf2.lcd_en = 1;
+ dev->conf2.lcd_tx_wrx2_en=0;
+ dev->conf2.lcd_tx_sdx2_en=0;
+
+ // I2S conf reg
+ dev->conf.val = 0;
+
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+ dev->conf.tx_dma_equal=1; // esp32-s2 only
+ dev->conf.pre_req_en=1; // esp32-s2 only - enable I2S to prepare data earlier? wtf?
+ #endif
+
+ // Now start setting up DMA FIFO
+ dev->fifo_conf.val = 0;
+ dev->fifo_conf.rx_data_num = 32; // Thresholds.
+ dev->fifo_conf.tx_data_num = 32;
+ dev->fifo_conf.dscr_en = 1;
+
+ #if !defined (CONFIG_IDF_TARGET_ESP32S2)
+
+ // Enable "One datum will be written twice in LCD mode" - for some reason,
+ // if we don't do this in 8-bit mode, data is updated on half-clocks not clocks
+ if(_cfg.parallel_width == 8)
+ dev->conf2.lcd_tx_wrx2_en=1;
+
+ // Not really described for non-pcm modes, although datasheet states it should be set correctly even for LCD mode
+ // First stage config. Configures how data is loaded into fifo
+ if(_cfg.parallel_width == 24) {
+ // Mode 0, single 32-bit channel, linear 32 bit load to fifo
+ dev->fifo_conf.tx_fifo_mod = 3;
+ } else {
+ // Mode 1, single 16-bit channel, load 16 bit sample(*) into fifo and pad to 32 bit with zeros
+ // *Actually a 32 bit read where two samples are read at once. Length of fifo must thus still be word-aligned
+ dev->fifo_conf.tx_fifo_mod = 1;
+ }
+
+ // Dictated by ESP32 datasheet
+ dev->fifo_conf.rx_fifo_mod_force_en = 1;
+ dev->fifo_conf.tx_fifo_mod_force_en = 1;
+
+ // Second stage config
+ dev->conf_chan.val = 0;
+
+ // 16-bit single channel data
+ dev->conf_chan.tx_chan_mod = 1;
+ dev->conf_chan.rx_chan_mod = 1;
+
+ #endif
+
+ // Reset FIFO
+ dev->conf.rx_fifo_reset = 1;
+
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+ while(dev->conf.rx_fifo_reset_st); // esp32-s2 only
+ #endif
+
+ dev->conf.rx_fifo_reset = 0;
+ dev->conf.tx_fifo_reset = 1;
+
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+ while(dev->conf.tx_fifo_reset_st); // esp32-s2 only
+ #endif
+ dev->conf.tx_fifo_reset = 0;
+
+
+ // Reset DMA
+ dev->lc_conf.in_rst = 1;
+ dev->lc_conf.in_rst = 0;
+ dev->lc_conf.out_rst = 1;
+ dev->lc_conf.out_rst = 0;
+
+ dev->lc_conf.ahbm_rst = 1;
+ dev->lc_conf.ahbm_rst = 0;
+
+ dev->in_link.val = 0;
+ dev->out_link.val = 0;
+
+
+ // Device reset
+ dev->conf.rx_reset=1;
+ dev->conf.tx_reset=1;
+ dev->conf.rx_reset=0;
+ dev->conf.tx_reset=0;
+
+ dev->conf1.val = 0;
+ dev->conf1.tx_stop_en = 0;
+/*
+ // Allocate I2S status structure for buffer swapping stuff
+ i2s_state = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t));
+ assert(i2s_state != NULL);
+ i2s_parallel_state_t *state = i2s_state;
+
+ state->desccount_a = conf->desccount_a;
+ state->desccount_b = conf->desccount_b;
+ state->dmadesc_a = conf->lldesc_a;
+ state->dmadesc_b = conf->lldesc_b;
+ state->i2s_interrupt_port_arg = port; // need to keep this somewhere in static memory for the ISR
+*/
+
+ dev->timing.val = 0;
+/*
+ // We using the double buffering switch logic?
+ if (conf->int_ena_out_eof)
+ {
+ // Get ISR setup
+ esp_err_t err = esp_intr_alloc(irq_source,
+ (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1),
+ irq_hndlr,
+ &state->i2s_interrupt_port_arg, NULL);
+
+ if(err) {
+ return err;
+ }
+
+
+ // Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual)
+ // "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet"
+ // ... whatever the hell that is supposed to mean... One massive linked list? So all pixels in the chain?
+ dev->int_ena.out_eof = 1;
+ }
+*/
+
+
+ #if defined (CONFIG_IDF_TARGET_ESP32S2)
+ ESP_LOGD(TAG, "init() GPIO and clock configuration set for ESP32-S2");
+ #else
+ ESP_LOGD(TAG, "init() GPIO and clock configuration set for ESP32");
+ #endif
+
+
+ return true;
+ }
+
+
+ void Bus_Parallel16::release(void)
+ {
+ if (_dmadesc_a)
+ {
+ heap_caps_free(_dmadesc_a);
+ _dmadesc_a = nullptr;
+ _dmadesc_count = 0;
+ }
+
+ if (_dmadesc_b)
+ {
+ heap_caps_free(_dmadesc_b);
+ _dmadesc_b = nullptr;
+ _dmadesc_count = 0;
+ }
+ }
+
+ void Bus_Parallel16::enable_double_dma_desc(void)
+ {
+ _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_LOGI(TAG, "Allocating memory for %d DMA descriptors.", 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(TAG, "ERROR: Couldn't malloc _dmadesc_a. Not enough memory.");
+ return false;
+ }
+
+
+ if (_double_dma_buffer)
+ {
+ if (_dmadesc_b) heap_caps_free(_dmadesc_b); // free all dma descrptios previously
+
+ ESP_LOGD(TAG, "Allocating the second buffer (double buffer enabled).");
+
+ _dmadesc_b= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA);
+
+ if (_dmadesc_b == nullptr)
+ {
+ ESP_LOGE(TAG, "ERROR: Couldn't malloc _dmadesc_b. Not enough memory.");
+ _double_dma_buffer = false;
+ return false;
+ }
+ }
+
+ _dmadesc_a_idx = 0;
+ _dmadesc_b_idx = 0;
+
+ ESP_LOGD(TAG, "Allocating %d bytes of memory for DMA descriptors.", sizeof(HUB75_DMA_DESCRIPTOR_T) * len);
+
+
+ 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 (dmadesc_b)
+ ESP_LOGI(TAG, " * Double buffer descriptor.");
+
+ if (size > MAX_DMA_LEN)
+ {
+ size = MAX_DMA_LEN;
+ ESP_LOGW(TAG, "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!");
+ }
+
+ if ( (_dmadesc_a_idx+1) > _dmadesc_count) {
+ ESP_LOGE(TAG, "Attempted to create more DMA descriptors than allocated memory for. Expecting a maximum of %d DMA descriptors", _dmadesc_count);
+ return;
+ }
+
+ volatile lldesc_t *dmadesc;
+ volatile lldesc_t *next;
+ bool eof = false;
+
+/*
+ dmadesc_a[desccount-1].eof = 1;
+ dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
+*/
+
+
+ // ESP_LOGI(TAG, "Creating descriptor %d\n", _dmadesc_a_idx);
+ if ( (dmadesc_b == true) ) // for primary buffer
+ {
+ dmadesc = &_dmadesc_b[_dmadesc_b_idx];
+
+ next = (_dmadesc_b_idx < (_dmadesc_count-1) ) ? &_dmadesc_b[_dmadesc_b_idx+1]:_dmadesc_b;
+ eof = (_dmadesc_b_idx == (_dmadesc_count-1));
+ }
+ else
+ {
+ dmadesc = &_dmadesc_a[_dmadesc_a_idx];
+
+ // https://stackoverflow.com/questions/47170740/c-negative-array-index
+ next = (_dmadesc_a_idx < (_dmadesc_count-1) ) ? _dmadesc_a + _dmadesc_a_idx+1:_dmadesc_a;
+ eof = (_dmadesc_a_idx == (_dmadesc_count-1));
+ }
+
+ if ( _dmadesc_a_idx == (_dmadesc_count-1) ) {
+ ESP_LOGW(TAG, "Creating final DMA descriptor and linking back to 0.");
+ }
+
+ dmadesc->size = size;
+ dmadesc->length = size;
+ dmadesc->buf = (uint8_t*) data;
+ dmadesc->eof = 0;
+ dmadesc->sosf = 0;
+ dmadesc->owner = 1;
+ dmadesc->qe.stqe_next = (lldesc_t*) next;
+ dmadesc->offset = 0;
+
+ if ( (dmadesc_b == true) ) { // for primary buffer
+ _dmadesc_b_idx++;
+ } else {
+ _dmadesc_a_idx++;
+ }
+
+ } // end create_dma_desc_link
+
+ void Bus_Parallel16::dma_transfer_start()
+ {
+ auto dev = _dev;
+
+ // Configure DMA burst mode
+ dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN;
+
+ // Set address of DMA descriptor
+ dev->out_link.addr = (uint32_t) _dmadesc_a;
+
+ // Start DMA operation
+ dev->out_link.stop = 0;
+ dev->out_link.start = 1;
+
+ dev->conf.tx_start = 1;
+
+
+ } // end
+
+
+ void Bus_Parallel16::dma_transfer_stop()
+ {
+ auto dev = _dev;
+
+ // Stop all ongoing DMA operations
+ dev->out_link.stop = 1;
+ dev->out_link.start = 0;
+ dev->conf.tx_start = 0;
+
+ } // end
+
+
+ void Bus_Parallel16::flip_dma_output_buffer()
+ {
+ if ( _double_dma_buffer == false) return;
+
+ if ( _dmadesc_a_active == true) // change across to everything 'b''
+ {
+ _dmadesc_a[_dmadesc_count-1].qe.stqe_next = &_dmadesc_b[0];
+ _dmadesc_b[_dmadesc_count-1].qe.stqe_next = &_dmadesc_b[0];
+ }
+ else
+ {
+ _dmadesc_a[_dmadesc_count-1].qe.stqe_next = &_dmadesc_a[0];
+ _dmadesc_b[_dmadesc_count-1].qe.stqe_next = &_dmadesc_a[0];
+ }
+ } // end flip
+
+
+
+#endif \ No newline at end of file
diff --git a/src/platforms/esp32/esp32_i2s_parallel_dma.h.txt b/src/platforms/esp32/esp32_i2s_parallel_dma.h.txt
new file mode 100644
index 0000000..d7678bc
--- /dev/null
+++ b/src/platforms/esp32/esp32_i2s_parallel_dma.h.txt
@@ -0,0 +1,106 @@
+#pragma once
+/*
+ * ESP32_I2S_PARALLEL_DMA
+ */
+
+#pragma once
+
+#if defined(ESP32) || defined(IDF_VER)
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include <freertos/FreeRTOS.h>
+#include <driver/i2s.h>
+#include <esp_err.h>
+//#include <esp32/rom/lldesc.h>
+//#include <esp32/rom/gpio.h>
+#include <rom/lldesc.h>
+#include <rom/gpio.h>
+
+
+// Get MCU Type and Max CLK Hz for MCU
+#include <esp32_i2s_parallel_mcu_def.h>
+
+typedef enum {
+ I2S_PARALLEL_WIDTH_8,
+ I2S_PARALLEL_WIDTH_16,
+ I2S_PARALLEL_WIDTH_24,
+ I2S_PARALLEL_WIDTH_MAX
+} i2s_parallel_cfg_bits_t;
+
+typedef struct {
+ int gpio_bus[24]; // The parallel GPIOs to use, set gpio to -1 to disable
+ int gpio_clk;
+ int sample_rate; // 'clockspeed'
+ int sample_width;
+ int desccount_a;
+ lldesc_t * lldesc_a;
+ int desccount_b; // only used with double buffering
+ lldesc_t * lldesc_b; // only used with double buffering
+ bool clkphase; // Clock signal phase
+ bool int_ena_out_eof; // Do we raise an interrupt every time the DMA output loops? Don't do this unless we're doing double buffering!
+} i2s_parallel_config_t;
+
+static inline int i2s_parallel_get_memory_width(i2s_port_t port, i2s_parallel_cfg_bits_t width) {
+ switch(width) {
+ case I2S_PARALLEL_WIDTH_8:
+
+#ifdef ESP32_ORIG
+ // Only I2S1 on the legacy ESP32 WROOM MCU supports space saving single byte 8 bit parallel access
+ if(port == I2S_NUM_1) {
+ return 1;
+ } else {
+ return 2;
+ }
+#else
+ return 1;
+#endif
+
+ case I2S_PARALLEL_WIDTH_16:
+ return 2;
+ case I2S_PARALLEL_WIDTH_24:
+ return 4;
+ default:
+ return -ESP_ERR_INVALID_ARG;
+ }
+}
+
+// DMA Linked List Creation
+void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size);
+
+// I2S DMA Peripheral Setup Functions
+esp_err_t i2s_parallel_driver_install(i2s_port_t port, i2s_parallel_config_t* conf);
+esp_err_t i2s_parallel_send_dma(i2s_port_t port, lldesc_t* dma_descriptor);
+esp_err_t i2s_parallel_stop_dma(i2s_port_t port);
+//i2s_dev_t* i2s_parallel_get_dev(i2s_port_t port);
+
+// For frame buffer flipping / double buffering
+typedef struct {
+ volatile lldesc_t *dmadesc_a, *dmadesc_b;
+ int desccount_a, desccount_b;
+ i2s_port_t i2s_interrupt_port_arg;
+} i2s_parallel_state_t;
+
+void i2s_parallel_flip_to_buffer(i2s_port_t port, int bufid);
+bool i2s_parallel_is_previous_buffer_free();
+void i2s_parallel_set_previous_buffer_not_free();
+
+// Callback function for when whole length of DMA chain has been sent out.
+typedef void (*callback)(void);
+void setShiftCompleteCallback(callback f);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/src/platforms/esp32/esp32_i2s_parallel_dma.hpp b/src/platforms/esp32/esp32_i2s_parallel_dma.hpp
new file mode 100644
index 0000000..95664d8
--- /dev/null
+++ b/src/platforms/esp32/esp32_i2s_parallel_dma.hpp
@@ -0,0 +1,138 @@
+/*----------------------------------------------------------------------------/
+ Lovyan GFX - Graphics library for embedded devices.
+
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+
+ Modified heavily for the ESP32 HUB75 DMA library by:
+ [mrfaptastic](https://github.com/mrfaptastic)
+
+------------------------------------------------------------------------------
+
+ Putin’s Russia and its genocide in Ukraine is a disgrace to humanity.
+
+ https://www.reddit.com/r/ukraine/comments/xfuc6v/more_than_460_graves_have_already_been_found_in/
+
+ Xi Jinping and his communist China’s silence on the war in Ukraine says everything about
+ how China condones such genocide, especially if it's against 'the west' (aka. decency).
+
+ Whilst the good people at Espressif probably have nothing to do with this, the unfortunate
+ reality is libraries like this increase the popularity of Chinese silicon chips, which
+ indirectly funds (through CCP state taxes) the growth and empowerment of such a despot government.
+
+ Global democracy, decency and security is put at risk with every Chinese silicon chip that is bought.
+
+/----------------------------------------------------------------------------*/
+
+#pragma once
+
+#include <string.h> // memcpy
+#include <algorithm>
+#include <stdbool.h>
+
+#include <sys/types.h>
+#include <freertos/FreeRTOS.h>
+#include <driver/i2s.h>
+#include <rom/lldesc.h>
+#include <rom/gpio.h>
+
+#define DMA_MAX (4096-4)
+
+// The type used for this SoC
+#define HUB75_DMA_DESCRIPTOR_T lldesc_t
+
+//----------------------------------------------------------------------------
+
+ class Bus_Parallel16
+ {
+ public:
+ Bus_Parallel16()
+ {
+
+ }
+
+ struct config_t
+ {
+ int port = 0;
+
+ // max 20MHz (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;
+ int8_t parallel_width = 16; // do not change
+ 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();
+
+ private:
+
+ void _init_pins() { };
+
+ config_t _cfg;
+
+ bool _double_dma_buffer = false;
+ bool _dmadesc_a_active = true;
+
+ 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;
+
+ volatile i2s_dev_t* _dev;
+
+
+
+ }; \ No newline at end of file
diff --git a/src/platforms/esp32s2/esp32s2-default-pins.hpp b/src/platforms/esp32s2/esp32s2-default-pins.hpp
new file mode 100644
index 0000000..202b1c5
--- /dev/null
+++ b/src/platforms/esp32s2/esp32s2-default-pins.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#define R1_PIN_DEFAULT 45
+#define G1_PIN_DEFAULT 42
+#define B1_PIN_DEFAULT 41
+#define R2_PIN_DEFAULT 40
+#define G2_PIN_DEFAULT 39
+#define B2_PIN_DEFAULT 38
+#define A_PIN_DEFAULT 37
+#define B_PIN_DEFAULT 36
+#define C_PIN_DEFAULT 35
+#define D_PIN_DEFAULT 34
+#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 26
+#define OE_PIN_DEFAULT 21
+#define CLK_PIN_DEFAULT 33
diff --git a/src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.png b/src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.png
new file mode 100644
index 0000000..c4391b3
--- /dev/null
+++ b/src/platforms/esp32s3/ESP32-S3-DevKitC-1-pin-layout.png
Binary files differ
diff --git a/src/platforms/esp32s3/Readme.md b/src/platforms/esp32s3/Readme.md
new file mode 100644
index 0000000..6b6c716
--- /dev/null
+++ b/src/platforms/esp32s3/Readme.md
@@ -0,0 +1,16 @@
+https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/external-ram.html
+
+Restrictions
+
+External RAM use has the following restrictions:
+
+When flash cache is disabled (for example, if the flash is being written to), the external RAM also becomes inaccessible; any reads from or writes to it will lead to an illegal cache access exception. This is also the reason why ESP-IDF does not by default allocate any task stacks in external RAM (see below).
+
+ External RAM cannot be used as a place to store DMA transaction descriptors or as a buffer for a DMA transfer to read from or write into. Therefore when External RAM is enabled, any buffer that will be used in combination with DMA must be allocated using heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL) and can be freed using a standard free() call.
+
+*Note, although ESP32-S3 has hardware support for DMA to/from external RAM, this is not yet supported in ESP-IDF.*
+
+External RAM uses the same cache region as the external flash. This means that frequently accessed variables in external RAM can be read and modified almost as quickly as in internal ram. However, when accessing large chunks of data (>32 KB), the cache can be insufficient, and speeds will fall back to the access speed of the external RAM. Moreover, accessing large chunks of data can “push out” cached flash, possibly making the execution of code slower afterwards.
+
+In general, external RAM will not be used as task stack memory. xTaskCreate() and similar functions will always allocate internal memory for stack and task TCBs.
+
diff --git a/src/platforms/esp32s3/ReservedPinsForPSRAM.PNG b/src/platforms/esp32s3/ReservedPinsForPSRAM.PNG
new file mode 100644
index 0000000..994fda8
--- /dev/null
+++ b/src/platforms/esp32s3/ReservedPinsForPSRAM.PNG
Binary files differ
diff --git a/src/platforms/esp32s3/esp32s3-default-pins.hpp b/src/platforms/esp32s3/esp32s3-default-pins.hpp
new file mode 100644
index 0000000..57d8aae
--- /dev/null
+++ b/src/platforms/esp32s3/esp32s3-default-pins.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+// Avoid and QSPI pins
+
+#define R1_PIN_DEFAULT 4
+#define G1_PIN_DEFAULT 5
+#define B1_PIN_DEFAULT 6
+#define R2_PIN_DEFAULT 7
+#define G2_PIN_DEFAULT 15
+#define B2_PIN_DEFAULT 16
+#define A_PIN_DEFAULT 18
+#define B_PIN_DEFAULT 8
+#define C_PIN_DEFAULT 3
+#define D_PIN_DEFAULT 42
+#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 40
+#define OE_PIN_DEFAULT 2
+#define CLK_PIN_DEFAULT 41
diff --git a/src/platforms/esp32s3/gdma_lcd_parallel16.cpp b/src/platforms/esp32s3/gdma_lcd_parallel16.cpp
new file mode 100644
index 0000000..785e489
--- /dev/null
+++ b/src/platforms/esp32s3/gdma_lcd_parallel16.cpp
@@ -0,0 +1,356 @@
+/*
+ Simple example of using the ESP32-S3's LCD peripheral for general-purpose
+ (non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer),
+ cycles through a pattern among them at about 1 Hz.
+ This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32.
+ None of this is authoritative canon, just a lot of trial error w/datasheet
+ and register poking. Probably more robust ways of doing this still TBD.
+
+
+ FULL CREDIT goes to AdaFruit
+
+ https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/
+
+ PLEASE SUPPORT THEM!
+
+ */
+ #include <Arduino.h>
+ #include "gdma_lcd_parallel16.hpp"
+
+ static const char* TAG = "gdma_lcd_parallel16";
+
+
+ dma_descriptor_t desc; // DMA descriptor for testing
+/*
+ uint8_t data[8][312]; // Transmit buffer (2496 bytes total)
+ uint16_t* dmabuff2;
+*/
+ // End-of-DMA-transfer callback
+ static IRAM_ATTR bool dma_callback(gdma_channel_handle_t dma_chan,
+ gdma_event_data_t *event_data, void *user_data) {
+ // This DMA callback seems to trigger a moment before the last data has
+ // issued (buffering between DMA & LCD peripheral?), so pause a moment
+ // before stopping LCD data out. The ideal delay may depend on the LCD
+ // clock rate...this one was determined empirically by monitoring on a
+ // logic analyzer. YMMV.
+ esp_rom_delay_us(100);
+ // The LCD peripheral stops transmitting at the end of the DMA xfer, but
+ // clear the lcd_start flag anyway -- we poll it in loop() to decide when
+ // the transfer has finished, and the same flag is set later to trigger
+ // the next transfer.
+
+ LCD_CAM.lcd_user.lcd_start = 0;
+ return true;
+ }
+
+ static lcd_cam_dev_t* getDev(int port)
+ {
+ return &LCD_CAM;
+ }
+
+ // ------------------------------------------------------------------------------
+
+ void Bus_Parallel16::config(const config_t& cfg)
+ {
+ _cfg = cfg;
+ auto port = cfg.port;
+ _dev = getDev(port);
+ }
+
+
+ //https://github.com/adafruit/Adafruit_Protomatter/blob/master/src/arch/esp32-s3.h
+ bool Bus_Parallel16::init(void)
+ {
+ ///dmabuff2 = (uint16_t*)heap_caps_malloc(sizeof(uint16_t) * 64*32, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+
+ // LCD_CAM peripheral isn't enabled by default -- MUST begin with this:
+ periph_module_enable(PERIPH_LCD_CAM_MODULE);
+ periph_module_reset(PERIPH_LCD_CAM_MODULE);
+
+ // Reset LCD bus
+ LCD_CAM.lcd_user.lcd_reset = 1;
+ esp_rom_delay_us(100);
+
+ auto lcd_clkm_div_num = 160000000 / _cfg.bus_freq / 2;
+
+ ESP_LOGI(TAG, "Clock divider is %d", lcd_clkm_div_num);
+
+ // Configure LCD clock. Since this program generates human-perceptible
+ // output and not data for LED matrices or NeoPixels, use almost the
+ // slowest LCD clock rate possible. The S3-mini module used on Feather
+ // ESP32-S3 has a 40 MHz crystal. A 2-stage clock division of 1:16000
+ // is applied (250*64), yielding 2,500 Hz. Still much too fast for
+ // human eyes, so later we set up the data to repeat each output byte
+ // many times over.
+ LCD_CAM.lcd_clock.clk_en = 1; // Enable peripheral clock
+ LCD_CAM.lcd_clock.lcd_clk_sel = 2; // 160mhz source
+ LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in 1st half cycle
+ LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
+ LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 0; // PCLK = CLK / (CLKCNT_N+1)
+ LCD_CAM.lcd_clock.lcd_clkm_div_num = lcd_clkm_div_num; // 1st stage 1:250 divide
+ LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 0/1 fractional divide
+ LCD_CAM.lcd_clock.lcd_clkm_div_b = 0;
+
+ // See section 26.3.3.1 of the ESP32­S3 Technical Reference Manual
+ // for information on other clock sources and dividers.
+
+ // Configure LCD frame format. This is where we fiddle the peripheral
+ // to provide generic 8-bit output rather than actually driving an LCD.
+ // There's also a 16-bit mode but that's not shown here.
+ LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB)
+ LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter
+ LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame
+ LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays
+ LCD_CAM.lcd_user.lcd_always_out_en = 1; // Enable 'always out' mode
+ LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes
+ LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order
+ LCD_CAM.lcd_user.lcd_2byte_en = 1; // 8-bit data mode
+ LCD_CAM.lcd_user.lcd_dummy = 0; // Dummy phase(s) @ LCD start
+ LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0; // 1 dummy phase
+ LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
+ // "Dummy phases" are initial LCD peripheral clock cycles before data
+ // begins transmitting when requested. After much testing, determined
+ // that at least one dummy phase MUST be enabled for DMA to trigger
+ // reliably. A problem with dummy phase(s) is if we're also using the
+ // LCD_PCLK_IDX signal (not used in this code, but Adafruit_Protomatter
+ // does)...the clock signal will start a couple of pulses before data,
+ // which may or may not be problematic in some situations. You can
+ // disable the dummy phase but need to keep the LCD TX FIFO primed
+ // in that case, which gets complex.
+ // always_out_en is set above to allow aribtrary-length transfers,
+ // else lcd_dout_cyclelen is used...but is limited to 8K. Long (>4K)
+ // transfers need DMA linked lists, not used here but mentioned later.
+
+ // Route 8 LCD data signals to GPIO pins
+ int8_t* pins = _cfg.pin_data;
+
+ for(int i = 0; i < 16; i++)
+ {
+ if (pins[i] >= 0) { // -1 value will CRASH the ESP32!
+ esp_rom_gpio_connect_out_signal(pins[i], LCD_DATA_OUT0_IDX + 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);
+ }
+ }
+
+ /*
+ const struct {
+ int8_t pin;
+ uint8_t signal;
+ } mux[] = {
+ { 43, LCD_DATA_OUT0_IDX }, // These are 8 consecutive pins down one
+ { 42, LCD_DATA_OUT1_IDX }, // side of the ESP32-S3 Feather. The ESP32
+ { 2, LCD_DATA_OUT2_IDX }, // has super flexible pin MUX capabilities,
+ { 9, LCD_DATA_OUT3_IDX }, // so any signal can go to any pin!
+ { 10, LCD_DATA_OUT4_IDX },
+ { 11, LCD_DATA_OUT5_IDX },
+ { 12, LCD_DATA_OUT6_IDX },
+ { 13, LCD_DATA_OUT7_IDX },
+ };
+ for (int i = 0; i < 8; i++) {
+ esp_rom_gpio_connect_out_signal(mux[i].pin, LCD_DATA_OUT0_IDX + i, false, false);
+ gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[mux[i].pin], PIN_FUNC_GPIO);
+ gpio_set_drive_capability((gpio_num_t)mux[i].pin, (gpio_drive_cap_t)3);
+ }
+*/
+ // Clock
+ esp_rom_gpio_connect_out_signal(_cfg.pin_wr, LCD_PCLK_IDX, _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);
+
+ // This program has a known fixed-size data buffer (2496 bytes) that fits
+ // in a single DMA descriptor (max 4095 bytes). Large transfers would
+ // require a linked list of descriptors, but here it's just one...
+
+ desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
+ desc.dw0.suc_eof = 0; // Last descriptor
+ desc.next = &desc; // No linked list
+
+
+ // Remaining descriptor elements are initialized before each DMA transfer.
+
+ // 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_LCD, 0));
+ static gdma_strategy_config_t strategy_config = {
+ .owner_check = false,
+ .auto_update_desc = false
+ };
+ gdma_apply_strategy(dma_chan, &strategy_config);
+
+ // Enable DMA transfer callback
+ static gdma_tx_event_callbacks_t tx_cbs = {
+ .on_trans_eof = dma_callback
+ };
+ gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL);
+
+ // As mentioned earlier, the slowest clock we can get to the LCD
+ // peripheral is 40 MHz / 250 / 64 = 2500 Hz. To make an even slower
+ // bit pattern that's perceptible, we just repeat each value many
+ // times over. The pattern here just counts through each of 8 bits
+ // (each LED lights in sequence)...so to get this to repeat at about
+ // 1 Hz, each LED is lit for 2500/8 or 312 cycles, hence the
+ // data[8][312] declaration at the start of this code (it's not
+ // precisely 1 Hz because reality is messy, but sufficient for demo).
+ // In actual use, say controlling an LED matrix or NeoPixels, such
+ // shenanigans aren't necessary, as these operate at multiple MHz
+ // with much smaller clock dividers and can use 1 byte per datum.
+ /*
+ for (int i = 0; i < (sizeof(data) / sizeof(data[0])); i++) { // 0 to 7
+ for (int j = 0; j < sizeof(data[0]); j++) { // 0 to 311
+ data[i][j] = 1 << i;
+ }
+ }
+ */
+
+
+ // This uses a busy loop to wait for each DMA transfer to complete...
+ // but the whole point of DMA is that one's code can do other work in
+ // the interim. The CPU is totally free while the transfer runs!
+ while (LCD_CAM.lcd_user.lcd_start); // Wait for DMA completion callback
+
+ // After much experimentation, each of these steps is required to get
+ // a clean start on the next LCD transfer:
+ gdma_reset(dma_chan); // Reset DMA to known state
+ LCD_CAM.lcd_user.lcd_dout = 1; // Enable data out
+ LCD_CAM.lcd_user.lcd_update = 1; // Update registers
+ LCD_CAM.lcd_misc.lcd_afifo_reset = 1; // Reset LCD TX FIFO
+
+ // This program happens to send the same data over and over...but,
+ // if desired, one could fill the data buffer with a new bit pattern
+ // here, or point to a completely different buffer each time through.
+ // With two buffers, one can make best use of time by filling each
+ // with new data before the busy loop above, alternating between them.
+
+ // Reset elements of DMA descriptor. Just one in this code, long
+ // transfers would loop through a linked list.
+
+ /*
+ desc.dw0.size = desc.dw0.length = sizeof(data);
+ desc.buffer = dmabuff2; //data;
+ desc.next = &desc;
+*/
+
+
+/*
+ //gdma_start(dma_chan, (intptr_t)&desc); // Start DMA w/updated descriptor(s)
+ gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); // Start DMA w/updated descriptor(s)
+ esp_rom_delay_us(100); // Must 'bake' a moment before...
+ LCD_CAM.lcd_user.lcd_start = 1; // Trigger LCD DMA transfer
+*/
+
+
+ return true; // no return val = illegal instruction
+
+ }
+
+
+ void Bus_Parallel16::release(void)
+ {
+ if (_i80_bus)
+ {
+ esp_lcd_del_i80_bus(_i80_bus);
+ }
+ if (_dmadesc_a)
+ {
+ heap_caps_free(_dmadesc_a);
+ _dmadesc_a = nullptr;
+ _dmadesc_count = 0;
+ }
+
+ }
+
+ void Bus_Parallel16::enable_double_dma_desc(void)
+ {
+ //_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(TAG, "Allocating %d bytes memory for DMA descriptors.", 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(TAG, "ERROR: Couldn't malloc _dmadesc_a. Not enough memory.");
+ return false;
+ }
+
+ _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(TAG, "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!");
+ }
+
+ if ( _dmadesc_a_idx >= _dmadesc_count)
+ {
+ ESP_LOGE(TAG, "Attempted to create more DMA descriptors than allocated memory for. Expecting a maximum of %d DMA descriptors", _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.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()
+ {
+ gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); // Start DMA w/updated descriptor(s)
+ esp_rom_delay_us(100); // Must 'bake' a moment before...
+ LCD_CAM.lcd_user.lcd_start = 1; // Trigger LCD DMA transfer
+
+ } // end
+
+ void Bus_Parallel16::dma_transfer_stop()
+ {
+
+ LCD_CAM.lcd_user.lcd_reset = 1; // Trigger LCD DMA transfer
+ LCD_CAM.lcd_user.lcd_update = 1; // Trigger LCD DMA transfer
+
+ gdma_stop(dma_chan);
+
+ } // end
+
+
+ void Bus_Parallel16::flip_dma_output_buffer()
+ {
+
+
+ } // end flip
+
+
diff --git a/src/platforms/esp32s3/gdma_lcd_parallel16.hpp b/src/platforms/esp32s3/gdma_lcd_parallel16.hpp
new file mode 100644
index 0000000..810eacb
--- /dev/null
+++ b/src/platforms/esp32s3/gdma_lcd_parallel16.hpp
@@ -0,0 +1,176 @@
+/*
+ Simple example of using the ESP32-S3's LCD peripheral for general-purpose
+ (non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer),
+ cycles through a pattern among them at about 1 Hz.
+ This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32.
+ None of this is authoritative canon, just a lot of trial error w/datasheet
+ and register poking. Probably more robust ways of doing this still TBD.
+
+
+ FULL CREDIT goes to AdaFruit
+
+ https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/
+
+ PLEASE SUPPORT THEM!
+
+
+ Putin’s Russia and its genocide in Ukraine is a disgrace to humanity.
+
+ https://www.reddit.com/r/ukraine/comments/xfuc6v/more_than_460_graves_have_already_been_found_in/
+
+
+/----------------------------------------------------------------------------
+
+*/
+
+#pragma once
+
+#if __has_include (<esp_lcd_panel_io.h>)
+
+#include <sdkconfig.h>
+#include <esp_lcd_panel_io.h>
+
+//#include <freertos/portmacro.h>
+#include <esp_intr_alloc.h>
+
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include <driver/gpio.h>
+#include <soc/gpio_sig_map.h>
+
+
+#include <hal/gpio_ll.h>
+#include <hal/lcd_hal.h>
+
+
+
+#include <string.h>
+#include <math.h>
+
+#include <stdint.h>
+
+#if (ESP_IDF_VERSION_MAJOR == 5)
+#include <esp_private/periph_ctrl.h>
+#else
+#include <driver/periph_ctrl.h>
+#endif
+#include <esp_private/gdma.h>
+#include <esp_rom_gpio.h>
+#include <hal/dma_types.h>
+#include <hal/gpio_hal.h>
+#include <hal/lcd_ll.h>
+#include <soc/lcd_cam_reg.h>
+#include <soc/lcd_cam_struct.h>
+#include <esp_heap_caps.h>
+#include <esp_heap_caps_init.h>
+
+
+#if __has_include (<esp_private/periph_ctrl.h>)
+ #include <esp_private/periph_ctrl.h>
+#else
+ #include <driver/periph_ctrl.h>
+#endif
+
+#if __has_include(<esp_arduino_version.h>)
+ #include <esp_arduino_version.h>
+#endif
+
+#define DMA_MAX (4096-4)
+
+// The type used for this SoC
+#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 = 20000000;
+ int8_t pin_wr = -1;
+ int8_t pin_rd = -1;
+ int8_t pin_rs = -1; // D/C
+ bool invert_pclk = 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();
+
+ private:
+
+ config_t _cfg;
+
+ volatile lcd_cam_dev_t* _dev;
+ gdma_channel_handle_t dma_chan;
+
+ uint32_t _dmadesc_count = 0; // number of dma decriptors
+ uint32_t _dmadesc_a_idx = 0;
+
+ HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr;
+
+ // uint32_t _clock_reg_value;
+ // uint32_t _fast_wait = 0;
+
+ //bool _double_dma_buffer = false;
+ //bool _dmadesc_a_active = true;
+
+ //uint32_t _dmadesc_b_idx = 0;
+
+ //HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr;
+
+ esp_lcd_i80_bus_handle_t _i80_bus;
+
+
+ };
+
+
+#endif \ No newline at end of file
diff --git a/src/platforms/platform_detect.hpp b/src/platforms/platform_detect.hpp
new file mode 100644
index 0000000..f660a5e
--- /dev/null
+++ b/src/platforms/platform_detect.hpp
@@ -0,0 +1,57 @@
+/*----------------------------------------------------------------------------/
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+
+Modified heavily for the ESP32 HUB75 DMA library by:
+ [mrfaptastic](https://github.com/mrfaptastic)
+/----------------------------------------------------------------------------*/
+#pragma once
+
+#if defined (ESP_PLATFORM)
+
+ #include <sdkconfig.h>
+
+ #if defined (CONFIG_IDF_TARGET_ESP32C3)
+
+ #error "ERROR: ESP32C3 not supported."
+
+ #elif defined (CONFIG_IDF_TARGET_ESP32S2)
+
+ #pragma message "Compiling for ESP32-S2"
+ #include "esp32/esp32_i2s_parallel_dma.hpp"
+ #include "esp32s2/esp32s2-default-pins.hpp"
+
+
+ #elif defined (CONFIG_IDF_TARGET_ESP32S3)
+
+ #pragma message "Compiling for ESP32-S3"
+ #include "esp32s3/gdma_lcd_parallel16.hpp"
+ #include "esp32s3/esp32s3-default-pins.hpp"
+
+ #else
+
+ // Assume an ESP32 (the original 2015 version)
+ // Same include as ESP32S3
+ #pragma message "Compiling for original ESP32 (2015 release)"
+ #define ESP32_THE_ORIG 1
+ //#include "esp32/esp32_i2s_parallel_dma.hpp"
+ //#include "esp32/esp32_i2s_parallel_dma.h"
+ #include "esp32/esp32_i2s_parallel_dma.hpp"
+ #include "esp32/esp32-default-pins.hpp"
+
+ #endif
+
+
+#endif
+