diff options
Diffstat (limited to 'src/platforms/esp32')
| -rw-r--r-- | src/platforms/esp32/esp32-default-pins.hpp | 18 | ||||
| -rw-r--r-- | src/platforms/esp32/esp32_i2s_parallel_dma.c.txt | 444 | ||||
| -rw-r--r-- | src/platforms/esp32/esp32_i2s_parallel_dma.cpp | 571 | ||||
| -rw-r--r-- | src/platforms/esp32/esp32_i2s_parallel_dma.h.txt | 106 | ||||
| -rw-r--r-- | src/platforms/esp32/esp32_i2s_parallel_dma.hpp | 138 |
5 files changed, 1277 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 |
