/* * ESP32_I2S_PARALLEL_DMA (Version 3) * * Author: Mrfaptastic @ https://github.com/mrfaptastic/ * * Description: Multi-ESP32 product DMA setup functions for ESP32 C3/H2 RISC-V chips * * ESP32C series doesn't support LCD mode / parallel DMA! * */ // Header #include "esp32_i2s_parallel_dma.h" #if defined(ESP32_CXXX) #include // Turn on and off a periphal #include // GPIO #include #include #include #include #include #include // DMA Linked List Struct #include #include // I2S #include #include // GDMA #include #include #include #include // 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. static i2s_dev_t* I2S = &I2S0; // Device to use for this library, change if you want. callback shiftCompleteCallback; void setShiftCompleteCallback(callback f) { shiftCompleteCallback = f; } volatile bool previousBufferFree = true; static void IRAM_ATTR gdma_irq_handler(void* arg) { // if we use I2S1 (default) GDMA.intr[0].clr.out_eof = 1; // at this point, the previously active buffer is free, go ahead and write to it // previousBufferFree = true; // if(shiftCompleteCallback) // we've defined a callback function ? // shiftCompleteCallback(); // at this point, the previously active buffer is free, go ahead and write to it 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 gpio_setup_out(int gpio, int sig) { if(gpio < 0) { return; } // Configure GPIO // https://github.com/espressif/esp-idf/blob/d5f58ab13551cd883e8d8478ba367b6e4543ffec/examples/peripherals/gpio/generic_gpio/main/gpio_example_main.c gpio_config_t io_conf = {}; io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.mode = GPIO_MODE_DEF_OUTPUT; io_conf.pin_bit_mask = (1ULL << gpio); io_conf.pull_down_en = 1; io_conf.pull_up_en = 0; gpio_config(&io_conf); // Set IOMUX to GPIO //gpio_iomux_out(gpio, sig, false); // ?? is sig right? gpio_matrix_out(gpio, sig, false, false); // Drive Strength to MAX gpio_set_drive_capability((gpio_num_t)gpio, (gpio_drive_cap_t)3); } // 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* cfg) { port = I2S_NUM_0; /// override. if(port < I2S_NUM_0 || port >= I2S_NUM_MAX) { return ESP_ERR_INVALID_ARG; } if(cfg->sample_width < I2S_PARALLEL_WIDTH_8 || cfg->sample_width >= I2S_PARALLEL_WIDTH_MAX) { return ESP_ERR_INVALID_ARG; } if(cfg->sample_rate > I2S_PARALLEL_CLOCK_HZ || cfg->sample_rate < 1) { return ESP_ERR_INVALID_ARG; } uint32_t clk_div_main = I2S_PARALLEL_CLOCK_HZ / cfg->sample_rate / i2s_parallel_get_memory_width(port, cfg->sample_width); if(clk_div_main < 2 || clk_div_main > 0xFF) { return ESP_ERR_INVALID_ARG; } // Setup GPIOs int bus_width = get_bus_width(cfg->sample_width); i2s_dev_t *i2s_dev = I2S; // There's only one I2S device on C3.... //Figure out which signal numbers to use for routing //printf("Setting up parallel I2S bus at I2S%d\n", i2snum(dev)); int sig_data_base = I2SO_SD_OUT_IDX; int sig_clk = I2SO_WS_OUT_IDX; //Route the signals for (int x=0; x < bus_width; x++) { gpio_setup_out(cfg->gpio_bus[x], sig_data_base+x); } //ToDo: Clk/WS may need inversion? gpio_setup_out(cfg->gpio_clk, sig_clk); // Power on I2S1 (or 0) periph_module_enable(PERIPH_I2S1_MODULE); // Now it's apparently I2S0 !? //i2s_dev_t *i2s_dev = &I2S0; // Reset RX (not that we use it) i2s_dev->rx_conf.val = 0; i2s_dev->rx_conf.rx_reset=1; i2s_dev->rx_conf.rx_reset=0; i2s_dev->rx_conf.rx_fifo_reset=1; i2s_dev->rx_conf.rx_fifo_reset=0; // Reset TX (what we care about) i2s_dev->tx_conf.val = 0; i2s_dev->tx_conf.tx_reset=1; i2s_dev->tx_conf.tx_reset=0; i2s_dev->tx_conf.tx_fifo_reset=1; i2s_dev->tx_conf.tx_fifo_reset=0; i2s_dev->tx_conf.tx_chan_equal=1; // Device setup i2s_dev->tx_conf1.val = 0; i2s_dev->tx_conf1.tx_bits_mod=16;//cfg->bits; i2s_dev->rx_conf1.val = 0; i2s_dev->rx_conf1.rx_bits_mod=16; //cfg->bits; i2s_dev->tx_conf1.tx_bck_div_num=2; i2s_dev->rx_conf1.rx_bck_div_num=2; i2s_dev->tx_clkm_conf.val=0; i2s_dev->tx_clkm_conf.tx_clk_sel=2; // 160mhz // clock speed i2s_dev->tx_clkm_conf.tx_clkm_div_num=160/16; // 10Mhz //i2s_dev->tx_clkm_div_conf.val = 0; // i2s_dev->tx_clkm_div_conf.tx_clkm_div_x = 0; // > ? i2s_dev->tx_clkm_div_conf.tx_clkm_div_y = 0; // i2s_dev->tx_clkm_div_conf.tx_clkm_div_yn1 = 0; i2s_dev->tx_clkm_div_conf.tx_clkm_div_z = 0; i2s_dev->tx_clkm_conf.tx_clk_active=1; // Start //Allocate DMA descriptors i2s_state = malloc(sizeof(i2s_parallel_state_t)); assert(i2s_state != NULL); i2s_parallel_state_t *st= i2s_state; st->desccount_a = cfg->desccount_a; st->desccount_b = cfg->desccount_b; st->dmadesc_a = cfg->lldesc_a; st->dmadesc_b = cfg->lldesc_b; // setup I2S Interrupt // SET_PERI_REG_BITS(I2S_INT_ENA_REG(1), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S); // allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete esp_intr_alloc(ETS_I2S1_INTR_SOURCE, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), gdma_irq_handler, NULL, NULL); GDMA.intr[0].ena.out_eof = 1; //? // Reset GDMA device GDMA.channel[0].out.out_conf0.out_rst = 1; // REG_SET_BIT(DMA_OUT_CONF0_CH0_REG, DMA_OUT_RST_CH0); GDMA.channel[0].out.out_conf0.out_rst = 0; // REG_CLR_BIT(DMA_OUT_CONF0_CH0_REG, DMA_OUT_RST_CH0); // GDMA.channel[0].out.out_conf0.out_eof_mode = 1; ? GDMA.misc_conf.ahbm_rst_inter = 1; GDMA.misc_conf.ahbm_rst_inter = 0; // Setup interrupt // Setup outlink GDMA.channel[0].out.out_link.addr = ((uint32_t)(&st->dmadesc_a[0]));// Set a vlaue here GDMA.channel[0].out.out_peri_sel.sel = SOC_GDMA_TRIG_PERIPH_I2S0; // 3 = I2S0 GDMA.channel[0].out.out_conf0.out_data_burst_en = 1; GDMA.channel[0].out.out_conf0.outdscr_burst_en = 1; GDMA.channel[0].out.out_link.start = 1; while (!GDMA.intr->raw.out_eof) { } // check status i2s_dev->tx_conf.tx_start = 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; } // Not implemented 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; } // Not implemented 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; } return I2S; // HARCODE THIS TO RETURN &I2S0 } // Double buffering flipping // Flip to a buffer: 0 for bufa, 1 for bufb 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; } bool i2s_parallel_is_previous_buffer_free() { return previousBufferFree; } // End ESP32 original / S2, S3 check #endif