diff --git a/components/atx_psu/atx_psu.c b/components/atx_psu/atx_psu.c index 1d98f26e..4d64ead7 100644 --- a/components/atx_psu/atx_psu.c +++ b/components/atx_psu/atx_psu.c @@ -1,3 +1,5 @@ +#define ERROR + #include #include @@ -61,7 +63,7 @@ static IRAM_ATTR void atx_psu_gpio_interrupt(gpio_pins_t pins, void *arg) LOG_ISR_DEBUG("pins=" GPIO_PINS_FMT, GPIO_PINS_ARGS(pins)); if (!xEventGroupSetBitsFromISR(atx_psu->events, ATX_PSU_EVENTS_INTR_BIT, &xHigherPriorityTaskWoken)) { - LOG_ISR_WARN("xEventGroupSetBitsFromISR"); + LOG_ISR_ERROR("xEventGroupSetBitsFromISR"); } #if CONFIG_IDF_TARGET_ESP8266 diff --git a/components/i2s_out/CMakeLists.txt b/components/i2s_out/CMakeLists.txt index f98b8e25..9ceaebd6 100644 --- a/components/i2s_out/CMakeLists.txt +++ b/components/i2s_out/CMakeLists.txt @@ -2,6 +2,7 @@ idf_component_register( SRC_DIRS . ${IDF_TARGET} INCLUDE_DIRS "include" PRIV_REQUIRES logging + LDFRAGMENTS ${IDF_TARGET}.lf ) target_compile_options(${COMPONENT_LIB} PRIVATE -O2) diff --git a/components/i2s_out/esp32.lf b/components/i2s_out/esp32.lf new file mode 100644 index 00000000..4474a170 --- /dev/null +++ b/components/i2s_out/esp32.lf @@ -0,0 +1,4 @@ +[mapping:i2s_out] +archive: i2s_out.a +entries: + intr_iram (noflash) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index 23d9ca80..944fd2ea 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -20,7 +20,7 @@ // shrink size to aligment #define TRUNC(size, align) ((size) & ~((align) - 1)) -#define DMA_EOF_BUF_SIZE (DMA_DESC_SIZE_MIN) +#define DMA_END_BUF_SIZE (DMA_DESC_SIZE_MIN) /* Allocate memory from appropriate heap region for DMA */ static inline void *dma_malloc(size_t size) @@ -47,6 +47,7 @@ void init_dma_desc(struct dma_desc *head, unsigned count, uint8_t *buf, size_t s desc->size = (size > TRUNC(DMA_DESC_SIZE_MAX, align)) ? TRUNC(DMA_DESC_SIZE_MAX, align) : size; desc->len = 0; + desc->eof = 1; // trigger I2S_OUT_EOF_INT on each DMA link desc->owner = 0; desc->buf = buf; @@ -64,53 +65,6 @@ void init_dma_desc(struct dma_desc *head, unsigned count, uint8_t *buf, size_t s } } -/* Prepare desc for DMA start */ -struct dma_desc *commit_dma_desc(struct dma_desc *desc) -{ - desc->owner = 1; - - if (desc->next) { - return desc->next; - } else { - return desc; - } -} - -void init_dma_eof_desc(struct dma_desc *eof_desc, uint32_t value, unsigned count) -{ - uint32_t *ptr = (uint32_t *) eof_desc->buf; - - for (unsigned i = 0; i < count; i++) { - ptr[i] = value; - } - - eof_desc->len = count * sizeof(value); - eof_desc->eof = 1; -} - -void reinit_dma_desc(struct dma_desc *head, unsigned count, struct dma_desc *next) -{ - struct dma_desc **nextp = NULL; - - for (unsigned i = 0; i < count; i++) { - struct dma_desc *desc = &head[i]; - - if (nextp) { - *nextp = desc; - } - - desc->len = 0; - desc->owner = 0; - - nextp = &desc->next; - } - - if (nextp) { - // loop - *nextp = next; - } -} - int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigned repeat) { size_t buf_size = 0; @@ -136,110 +90,240 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigne LOG_DEBUG("size=%u align=%u repeat=%u -> desc_count=%u buf_size=%u", size, align, repeat, desc_count, buf_size); // allocate single word-aligned buffer - if (!(i2s_out->dma_rx_buf = dma_malloc(buf_size))) { - LOG_ERROR("dma_malloc(dma_rx_buf)"); + if (!(i2s_out->dma_data_buf = dma_malloc(buf_size))) { + LOG_ERROR("dma_malloc(dma_data_buf)"); return -1; } else { - LOG_DEBUG("dma_rx_buf=%p[%u]", i2s_out->dma_rx_buf, buf_size); + LOG_DEBUG("dma_data_buf=%p[%u]", i2s_out->dma_data_buf, buf_size); } - if (!(i2s_out->dma_eof_buf = dma_malloc(DMA_EOF_BUF_SIZE))) { - LOG_ERROR("dma_malloc(dma_eof_buf)"); + if (!(i2s_out->dma_end_buf = dma_malloc(DMA_END_BUF_SIZE))) { + LOG_ERROR("dma_malloc(dma_end_buf)"); return -1; } else { - LOG_DEBUG("dma_eof_buf=%p[%u]", i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE); + LOG_DEBUG("dma_end_buf=%p[%u]", i2s_out->dma_end_buf, DMA_END_BUF_SIZE); } // allocate DMA descriptors - if (!(i2s_out->dma_rx_desc = dma_calloc(desc_count, sizeof(*i2s_out->dma_rx_desc)))) { - LOG_ERROR("dma_calloc(dma_rx_desc)"); + if (!(i2s_out->dma_data_desc = dma_calloc(desc_count, sizeof(*i2s_out->dma_data_desc)))) { + LOG_ERROR("dma_calloc(dma_data_desc)"); return -1; } - if (repeat && !(i2s_out->dma_repeat_desc = dma_calloc(desc_count * repeat, sizeof(*i2s_out->dma_rx_desc)))) { + if (repeat && !(i2s_out->dma_repeat_desc = dma_calloc(desc_count * repeat, sizeof(*i2s_out->dma_repeat_desc)))) { LOG_ERROR("dma_calloc(dma_repeat_desc)"); return -1; } - if (!(i2s_out->dma_eof_desc = dma_calloc(1, sizeof(*i2s_out->dma_eof_desc)))) { - LOG_ERROR("dma_calloc(dma_eof_desc)"); + if (!(i2s_out->dma_end_desc = dma_calloc(1, sizeof(*i2s_out->dma_end_desc)))) { + LOG_ERROR("dma_calloc(dma_end_desc)"); return -1; } // initialize linked list of DMA descriptors - init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + init_dma_desc(i2s_out->dma_data_desc, desc_count, i2s_out->dma_data_buf, buf_size, align, NULL); for (unsigned i = 0; i < repeat; i++) { - init_dma_desc(i2s_out->dma_repeat_desc + i * desc_count, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + init_dma_desc(i2s_out->dma_repeat_desc + i * desc_count, desc_count, i2s_out->dma_data_buf, buf_size, align, NULL); } - init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), NULL); + init_dma_desc(i2s_out->dma_end_desc, 1, i2s_out->dma_end_buf, DMA_END_BUF_SIZE, sizeof(uint32_t), NULL); - i2s_out->dma_rx_count = desc_count; - i2s_out->dma_rx_repeat = repeat; + i2s_out->dma_data_count = desc_count; + i2s_out->dma_repeat_count = repeat; return 0; } +/* Prepare end desc + buffer */ +static size_t init_dma_end(struct dma_desc *end_desc, uint32_t value, unsigned count) +{ + uint32_t *ptr = (uint32_t *) end_desc->buf; + size_t len = count * sizeof(value); + + for (unsigned i = 0; i < count; i++) { + ptr[i] = value; + } + + end_desc->len = len; + end_desc->next = NULL; + + return len; +} + +static void reset_dma_desc(struct dma_desc *head, unsigned count, struct dma_desc *next) +{ + struct dma_desc **nextp = NULL; + + for (unsigned i = 0; i < count; i++) { + struct dma_desc *desc = &head[i]; + + if (nextp) { + *nextp = desc; + } + + desc->len = 0; + desc->owner = 0; + + nextp = &desc->next; + } + + if (nextp) { + // loop + *nextp = next; + } +} + +static void init_dma_repeat(struct i2s_out *i2s_out, unsigned count, struct dma_desc *next) +{ + struct dma_desc **nextp = NULL; + + LOG_DEBUG("count=%u", count); + + for (unsigned i = 0; i < count; i++) { + for (unsigned j = 0; j < i2s_out->dma_data_count; j++) { + struct dma_desc *s = &i2s_out->dma_data_desc[j]; + struct dma_desc *d = &i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j]; + + if (nextp) { + *nextp = d; + } + + d->len = s->len; + d->owner = s->owner; + + nextp = &d->next; + } + } + + if (nextp) { + *nextp = next; + } +} + int i2s_out_dma_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options) { LOG_DEBUG("..."); - if (options->eof_count * sizeof(options->eof_value) > DMA_EOF_BUF_SIZE) { - LOG_ERROR("eof_count=%u is too large for eof buf size=%u", options->eof_count, DMA_EOF_BUF_SIZE); + if (options->eof_count * sizeof(options->eof_value) > DMA_END_BUF_SIZE) { + LOG_ERROR("eof_count=%u is too large for end buf size=%u", options->eof_count, DMA_END_BUF_SIZE); return -1; } - // init EOF buffer - init_dma_eof_desc(i2s_out->dma_eof_desc, options->eof_value, options->eof_count); - - // init RX desc - reinit_dma_desc(i2s_out->dma_rx_desc, i2s_out->dma_rx_count, NULL); + // reinit out desc + reset_dma_desc(i2s_out->dma_data_desc, i2s_out->dma_data_count, options->repeat_data_count ? i2s_out->dma_repeat_desc : i2s_out->dma_end_desc); + init_dma_repeat(i2s_out, options->repeat_data_count, i2s_out->dma_end_desc); + i2s_out->dma_end_len = init_dma_end(i2s_out->dma_end_desc, options->eof_value, options->eof_count); taskENTER_CRITICAL(&i2s_out->mux); i2s_ll_rx_stop_link(i2s_out->dev); i2s_ll_tx_stop_link(i2s_out->dev); - i2s_intr_disable(i2s_out->dev, I2S_OUT_EOF_INT_ENA | I2S_OUT_DSCR_ERR_INT_ENA); - i2s_intr_clear(i2s_out->dev, I2S_OUT_EOF_INT_CLR | I2S_OUT_DSCR_ERR_INT_CLR); + i2s_intr_disable(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_ENA | I2S_OUT_EOF_INT_ENA); + i2s_intr_clear(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_CLR | I2S_OUT_EOF_INT_CLR); i2s_ll_enable_dma(i2s_out->dev, false); i2s_ll_rx_reset_dma(i2s_out->dev); i2s_ll_tx_reset_dma(i2s_out->dev); - i2s_ll_dma_enable_eof_on_fifo_empty(i2s_out->dev, true); - i2s_ll_dma_enable_owner_check(i2s_out->dev, true); - i2s_ll_dma_enable_auto_write_back(i2s_out->dev, true); + taskEXIT_CRITICAL(&i2s_out->mux); - i2s_ll_set_out_link_addr(i2s_out->dev, (uint32_t) i2s_out->dma_rx_desc); + // reset eof/write state + i2s_out->dma_start = false; + i2s_out->dma_write_desc = i2s_out->dma_data_desc; + i2s_out->dma_out_desc = NULL; + i2s_out->dma_out_task = NULL; + i2s_out->dma_done = false; + i2s_out->dma_done_task = NULL; - taskEXIT_CRITICAL(&i2s_out->mux); + return 0; +} - // reset eof state - xEventGroupClearBits(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_DMA_EOF); +/* + * Return pointer to current uncommitted dma_write_desc. + */ +struct dma_desc *i2s_out_dma_wait(struct i2s_out *i2s_out, TickType_t timeout) +{ + if (i2s_out->dma_start) { + do { + uint32_t bits; + bool dma_write_owner; - // reset write state - i2s_out->dma_start = false; - i2s_out->dma_write_desc = i2s_out->dma_rx_desc; + taskENTER_CRITICAL(&i2s_out->mux); + + if ((dma_write_owner = i2s_out->dma_write_desc->owner)) { + i2s_out->dma_out_task = xTaskGetCurrentTaskHandle(); + } + + taskEXIT_CRITICAL(&i2s_out->mux); + + if (!dma_write_owner) { + break; + } else if (!xTaskNotifyWait(0, I2S_OUT_TASK_NOTIFY_BIT_DMA_OUT, &bits, timeout)) { + LOG_ERROR("timeout bits=%08x, dma_data_desc=%p...%p dma_write_desc=%p dma_out_desc=%p dma_end_desc=%p dma_done=%d", + bits, + i2s_out->dma_data_desc, i2s_out->dma_data_desc + i2s_out->dma_data_count - 1, + i2s_out->dma_write_desc, + i2s_out->dma_out_desc, + i2s_out->dma_end_desc, + i2s_out->dma_done + ); + + i2s_out->dma_out_task = NULL; + + return NULL; + } else { + LOG_DEBUG("wait bits=%08x, dma_data_desc=%p...%p dma_write_desc=%p dma_out_desc=%p dma_end_desc=%p dma_done=%d", + bits, + i2s_out->dma_data_desc, i2s_out->dma_data_desc + i2s_out->dma_data_count - 1, + i2s_out->dma_write_desc, + i2s_out->dma_out_desc, + i2s_out->dma_end_desc, + i2s_out->dma_done + ); + + break; + } + } while (1); + } + + if (i2s_out->dma_write_desc->owner) { + LOG_WARN("dma_write_desc=%p already committed with owner=%d", i2s_out->dma_write_desc, i2s_out->dma_write_desc->owner); + return NULL; + } + + return i2s_out->dma_write_desc; +} + +/* + * Commit current dma_write_desc and return next. + */ +struct dma_desc *i2s_out_dma_next(struct i2s_out *i2s_out, TickType_t timeout) +{ + if (i2s_out->dma_write_desc->owner) { + LOG_WARN("dma_write_desc=%p already committed with owner=%d", i2s_out->dma_write_desc, i2s_out->dma_write_desc->owner); + } - LOG_DEBUG("dma_write_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", + LOG_DEBUG("commit desc=%p: owner=%u eof=%u buf=%p len=%u size=%u next=%p", i2s_out->dma_write_desc, i2s_out->dma_write_desc->owner, i2s_out->dma_write_desc->eof, + i2s_out->dma_write_desc->buf, i2s_out->dma_write_desc->len, i2s_out->dma_write_desc->size, - i2s_out->dma_write_desc->buf, i2s_out->dma_write_desc->next ); - LOG_DEBUG("dma_eof_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", - i2s_out->dma_eof_desc, - i2s_out->dma_eof_desc->owner, - i2s_out->dma_eof_desc->eof, - i2s_out->dma_eof_desc->len, - i2s_out->dma_eof_desc->size, - i2s_out->dma_eof_desc->buf, - i2s_out->dma_eof_desc->next - ); + // prepare for DMA + i2s_out->dma_write_desc->owner = 1; - return 0; + if (i2s_out->dma_write_desc + 1 >= i2s_out->dma_data_desc + i2s_out->dma_data_count) { + LOG_WARN("dma_write_desc=%p committed, no more descs available", i2s_out->dma_write_desc); + return NULL; + } + + // start using next desc + i2s_out->dma_write_desc++; + + // wait for it to be available + return i2s_out_dma_wait(i2s_out, timeout); } /* @@ -247,48 +331,57 @@ int i2s_out_dma_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt * * @return size of usable buffer in units of size, up to count. 0 if full */ -size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, size_t size) +size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, size_t size, TickType_t timeout) { - for (;;) { - struct dma_desc *desc = i2s_out->dma_write_desc; + struct dma_desc *desc; - // hit dma_eof_desc? - if (desc->owner || desc->eof) { - LOG_DEBUG("eof desc=%p (owner=%u eof=%u buf=%p len=%u size=%u)", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size); + // wait for desc to be available + if (!(desc = i2s_out_dma_wait(i2s_out, timeout))) { + LOG_WARN("i2s_out_dma_wait"); - // unable to find a usable DMA buffer, TX buffers full - *ptr = NULL; + // unable to find a usable DMA buffer, timeout or TX buffers full? + *ptr = NULL; - // TODO: start DMA early and wait for a free buffer? - return 0; - } + return 0; + } - // can fit minimum size - if (desc->len + size > desc->size) { - LOG_DEBUG("commit desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) < size=%u -> next=%p", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, size, desc->next); + // advance to next desc if full + if (desc->len + size <= desc->size) { + // fits - // commit, try with the next desc, if available - i2s_out->dma_write_desc = commit_dma_desc(desc); + } else if (!(desc = i2s_out_dma_next(i2s_out, timeout))) { + LOG_WARN("i2s_out_dma_next"); - continue; - } + // unable to find a usable DMA buffer, timeout or TX buffers full? + *ptr = NULL; + + return 0; + } + + if (desc->len + size > desc->size) { + LOG_WARN("overflow desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) < size=%u", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, size); - if (desc->len + count * size > desc->size) { - LOG_DEBUG("limited desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) < count=%u size=%u", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, count, size); + // unable to find a usable DMA buffer, size too big + *ptr = NULL; - // limit to available buffer size - count = (desc->size - desc->len) / size; + return 0; - } else { - LOG_DEBUG("complete desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) >= count=%u size=%u", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, count, size); - } + } else if (desc->len + count * size > desc->size) { + // limit to available buffer size + count = (desc->size - desc->len) / size; - *ptr = desc->buf + desc->len; + LOG_TRACE("short desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) -> count=%u size=%u", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, count, size); - LOG_DEBUG("return ptr=%p count=%u size=%u", *ptr, count, size); + } else if (desc->len + count * size < desc->size) { + LOG_TRACE("long desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) -> count=%u size=%u", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, count, size); - return count; + } else { + LOG_TRACE("fill desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) -> count=%u size=%u", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, count, size); } + + *ptr = desc->buf + desc->len; + + return count; } void i2s_out_dma_commit(struct i2s_out *i2s_out, unsigned count, size_t size) @@ -298,15 +391,15 @@ void i2s_out_dma_commit(struct i2s_out *i2s_out, unsigned count, size_t size) desc->len += count * size; } -int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size) +int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size, TickType_t timeout) { void *ptr; - int len = i2s_out_dma_buffer(i2s_out, &ptr, size, 1); // single unaligned bytes + int len = i2s_out_dma_buffer(i2s_out, &ptr, size, 1, timeout); // single unaligned bytes if (len) { // copy data to desc buf - LOG_DEBUG("copy len=%u -> ptr=%p", len, ptr); - LOG_DEBUG_BUFFER(data, len); + LOG_TRACE("copy len=%u -> ptr=%p", len, ptr); + LOG_TRACE_BUFFER(data, len); memcpy(ptr, data, len); @@ -316,128 +409,233 @@ int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size) return len; } -void i2s_out_dma_repeat(struct i2s_out *i2s_out, unsigned count) +int i2s_out_dma_pending(struct i2s_out *i2s_out) { - struct dma_desc **nextp = &i2s_out->dma_write_desc->next; - - // commit - i2s_out->dma_write_desc->owner = 1; - - for (unsigned i = 0; i < count; i++) { - for (unsigned j = 0; j < i2s_out->dma_rx_count; j++) { - struct dma_desc *s = &i2s_out->dma_rx_desc[j]; - struct dma_desc *d = &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j]; - - if (!s->owner) { - break; - } - - if (nextp) { - *nextp = d; - } - - d->len = s->len; - d->owner = s->owner; - - nextp = &d->next; - } + // XXX: this will also be true immediately after dma_start()? + if (i2s_out->dma_write_desc > i2s_out->dma_data_desc || i2s_out->dma_write_desc->len > 0) { + // write() happened + return 1; } - if (nextp) { - // eof - *nextp = i2s_out->dma_eof_desc; - } + return 0; } -int i2s_out_dma_pending(struct i2s_out *i2s_out) +int i2s_out_dma_running(struct i2s_out *i2s_out) { if (i2s_out->dma_start) { - // start() already haṕpened - return 0; - } - - if (i2s_out->dma_write_desc != i2s_out->dma_rx_desc || i2s_out->dma_write_desc->len > 0) { - // write() happened + // start() has been called, stop() has not return 1; } return 0; } -void i2s_out_dma_start(struct i2s_out *i2s_out) +int i2s_out_dma_start(struct i2s_out *i2s_out) { - // commit if not repeat() - if (!i2s_out->dma_write_desc->owner) { - i2s_out->dma_write_desc->owner = 1; + if (i2s_out->dma_start) { + LOG_ERROR("dma_start=%u", i2s_out->dma_start); + return -1; } - if (!i2s_out->dma_write_desc->next) { - i2s_out->dma_write_desc->next = i2s_out->dma_eof_desc; + // desc len is reset by out isr, restore from setup() + i2s_out->dma_end_desc->len = i2s_out->dma_end_len; + + for (unsigned i = 0; i < i2s_out->dma_repeat_count; i++) { + for (unsigned j = 0; j < i2s_out->dma_data_count; j++) { + struct dma_desc *s = &i2s_out->dma_data_desc[j]; + struct dma_desc *d = &i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j]; + + d->len = s->len; + } } - i2s_out->dma_eof_desc->owner = 1; - i2s_out->dma_eof_desc->next = i2s_out->dma_eof_desc; + // commit all descs, including repeat and end + for (struct dma_desc *desc = i2s_out->dma_write_desc; desc; desc = desc->next) { + if (desc->owner) { + continue; + } + + LOG_DEBUG("commit desc=%p: owner=%u eof=%u buf=%p len=%u size=%u next=%p", + desc, + desc->owner, + desc->eof, + desc->buf, + desc->len, + desc->size, + desc->next + ); - for (unsigned i = 0; i < i2s_out->dma_rx_count; i++) { - LOG_DEBUG("dma_rx_desc[%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, - &i2s_out->dma_rx_desc[i], - i2s_out->dma_rx_desc[i].owner, - i2s_out->dma_rx_desc[i].eof, - i2s_out->dma_rx_desc[i].len, - i2s_out->dma_rx_desc[i].size, - i2s_out->dma_rx_desc[i].buf, - i2s_out->dma_rx_desc[i].next + desc->owner = 1; + } + + for (unsigned i = 0; i < i2s_out->dma_data_count; i++) { + LOG_DEBUG("dma_data_desc[%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, + &i2s_out->dma_data_desc[i], + i2s_out->dma_data_desc[i].owner, + i2s_out->dma_data_desc[i].eof, + i2s_out->dma_data_desc[i].len, + i2s_out->dma_data_desc[i].size, + i2s_out->dma_data_desc[i].buf, + i2s_out->dma_data_desc[i].next ); + + assert(i2s_out->dma_data_desc[i].eof); + assert(i2s_out->dma_data_desc[i].next); } - for (unsigned i = 0; i < i2s_out->dma_rx_repeat; i++) { - for (unsigned j = 0; j < i2s_out->dma_rx_count; j++) { + for (unsigned i = 0; i < i2s_out->dma_repeat_count; i++) { + for (unsigned j = 0; j < i2s_out->dma_data_count; j++) { LOG_DEBUG("dma_repeat_desc[%u][%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, j, - &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j], - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].owner, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].eof, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].len, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].size, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].buf, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].next + &i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j], + i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].owner, + i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].eof, + i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].len, + i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].size, + i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].buf, + i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].next ); + + assert(i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].eof); + assert(i2s_out->dma_repeat_desc[i * i2s_out->dma_data_count + j].next); } } - LOG_DEBUG("dma_eof_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", - i2s_out->dma_eof_desc, - i2s_out->dma_eof_desc->owner, - i2s_out->dma_eof_desc->eof, - i2s_out->dma_eof_desc->len, - i2s_out->dma_eof_desc->size, - i2s_out->dma_eof_desc->buf, - i2s_out->dma_eof_desc->next + LOG_DEBUG("dma_end_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", + i2s_out->dma_end_desc, + i2s_out->dma_end_desc->owner, + i2s_out->dma_end_desc->eof, + i2s_out->dma_end_desc->len, + i2s_out->dma_end_desc->size, + i2s_out->dma_end_desc->buf, + i2s_out->dma_end_desc->next ); + assert(i2s_out->dma_end_desc->eof); + assert(i2s_out->dma_end_desc->next == NULL); + + // initialize eof/done state + i2s_out->dma_out_desc = i2s_out->dma_data_desc; + i2s_out->dma_out_task = NULL; + i2s_out->dma_done = false; + i2s_out->dma_done_task = NULL; + taskENTER_CRITICAL(&i2s_out->mux); i2s_ll_tx_reset_dma(i2s_out->dev); i2s_ll_tx_reset_fifo(i2s_out->dev); i2s_ll_tx_reset(i2s_out->dev); - i2s_intr_clear(i2s_out->dev, I2S_OUT_EOF_INT_CLR | I2S_OUT_DSCR_ERR_INT_CLR); - i2s_intr_enable(i2s_out->dev, I2S_OUT_EOF_INT_ENA | I2S_OUT_DSCR_ERR_INT_ENA); + i2s_ll_set_out_link_addr(i2s_out->dev, (uint32_t) i2s_out->dma_data_desc); + + // TODO: use *_burst_en=1 with out_eof_mode=0? + i2s_ll_dma_enable_eof_on_fifo_empty(i2s_out->dev, true); + i2s_ll_dma_enable_owner_check(i2s_out->dev, true); + i2s_ll_dma_enable_auto_write_back(i2s_out->dev, false); + + i2s_intr_clear(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_CLR | I2S_OUT_EOF_INT_CLR); + i2s_intr_enable(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_ENA | I2S_OUT_EOF_INT_ENA); i2s_ll_enable_dma(i2s_out->dev, true); i2s_ll_start_out_link(i2s_out->dev); taskEXIT_CRITICAL(&i2s_out->mux); + // reset state for next write() i2s_out->dma_start = true; + i2s_out->dma_write_desc = i2s_out->dma_data_desc; + + return 0; } -int i2s_out_dma_flush(struct i2s_out *i2s_out) +int i2s_out_dma_flush(struct i2s_out *i2s_out, TickType_t timeout) { - LOG_DEBUG("wait event_group bits=%08x", I2S_OUT_EVENT_GROUP_BIT_DMA_EOF); + bool dma_done = false; + uint32_t bits = 0; - xEventGroupWaitBits(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_DMA_EOF, false, false, portMAX_DELAY); + LOG_DEBUG("..."); - LOG_DEBUG("wait done"); + taskENTER_CRITICAL(&i2s_out->mux); - return 0; + dma_done = i2s_out->dma_done; + + if (!dma_done) { + i2s_out->dma_done_task = xTaskGetCurrentTaskHandle(); + } + + taskEXIT_CRITICAL(&i2s_out->mux); + + if (dma_done) { + LOG_DEBUG("done bits=%08x, dma_data_desc=%p...%p dma_write_desc=%p dma_out_desc=%p dma_end_desc=%p dma_done=%d", + bits, + i2s_out->dma_data_desc, i2s_out->dma_data_desc + i2s_out->dma_data_count - 1, + i2s_out->dma_write_desc, + i2s_out->dma_out_desc, + i2s_out->dma_end_desc, + i2s_out->dma_done + ); + + return 0; + } else if (!xTaskNotifyWait(0, I2S_OUT_TASK_NOTIFY_BIT_DMA_DONE, &bits, timeout)) { + LOG_ERROR("timeout bits=%08x, dma_data_desc=%p...%p dma_write_desc=%p dma_out_desc=%p dma_end_desc=%p dma_done=%d", + bits, + i2s_out->dma_data_desc, i2s_out->dma_data_desc + i2s_out->dma_data_count - 1, + i2s_out->dma_write_desc, + i2s_out->dma_out_desc, + i2s_out->dma_end_desc, + i2s_out->dma_done + ); + + i2s_out->dma_done_task = NULL; + + return -1; + } else { + LOG_DEBUG("wait bits=%08x, dma_data_desc=%p...%p dma_write_desc=%p dma_out_desc=%p dma_end_desc=%p dma_done=%d", + bits, + i2s_out->dma_data_desc, i2s_out->dma_data_desc + i2s_out->dma_data_count - 1, + i2s_out->dma_write_desc, + i2s_out->dma_out_desc, + i2s_out->dma_end_desc, + i2s_out->dma_done + ); + + return 0; + } +} + +void i2s_out_dma_stop(struct i2s_out *i2s_out) +{ + LOG_DEBUG(""); + + // reset write state + i2s_out->dma_start = false; + + taskENTER_CRITICAL(&i2s_out->mux); + + i2s_intr_disable(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_ENA | I2S_OUT_EOF_INT_ENA); + i2s_intr_clear(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_CLR | I2S_OUT_EOF_INT_CLR); + + i2s_ll_rx_stop_link(i2s_out->dev); + i2s_ll_tx_stop_link(i2s_out->dev); + + i2s_ll_enable_dma(i2s_out->dev, false); + + i2s_ll_rx_reset_dma(i2s_out->dev); + i2s_ll_tx_reset_dma(i2s_out->dev); + + taskEXIT_CRITICAL(&i2s_out->mux); + + // reset eof state + i2s_out->dma_out_desc = NULL; + i2s_out->dma_out_task = NULL; + i2s_out->dma_done = false; + i2s_out->dma_done_task = NULL; +} + +void i2s_out_dma_free(struct i2s_out *i2s_out) +{ + free(i2s_out->dma_end_buf); + free(i2s_out->dma_data_buf); + free(i2s_out->dma_data_desc); + free(i2s_out->dma_repeat_desc); + free(i2s_out->dma_end_desc); } diff --git a/components/i2s_out/esp32/dma.h b/components/i2s_out/esp32/dma.h index a0b076a8..87da96e3 100644 --- a/components/i2s_out/esp32/dma.h +++ b/components/i2s_out/esp32/dma.h @@ -19,3 +19,15 @@ struct dma_desc { // linked list struct dma_desc *next; }; + +/* Prepare desc for re-use after DMA EOF */ +static inline void i2s_dma_desc_reset(struct dma_desc *eof_desc) +{ + eof_desc->len = 0; + eof_desc->owner = 0; +} + +static inline void i2s_dma_tx_get_des_addr(i2s_dev_t *hw, uint32_t *dscr_addr) +{ + *dscr_addr = hw->out_link_dscr; +} diff --git a/components/i2s_out/esp32/i2s.c b/components/i2s_out/esp32/i2s.c index 45a79fac..aafe41c5 100644 --- a/components/i2s_out/esp32/i2s.c +++ b/components/i2s_out/esp32/i2s.c @@ -116,8 +116,9 @@ int i2s_out_i2s_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt i2s_out->dev->sample_rate_conf.val ); - // reset eof state - xEventGroupClearBits(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_I2S_EOF); + // reset event state + i2s_out->i2s_done = false; + i2s_out->i2s_done_task = NULL; return 0; } @@ -126,6 +127,10 @@ void i2s_out_i2s_start(struct i2s_out *i2s_out) { LOG_DEBUG(""); + // reset event state + i2s_out->i2s_done = false; + i2s_out->i2s_done_task = NULL; + taskENTER_CRITICAL(&i2s_out->mux); // NOTE: there seems to always be three extra BCK cycles at the start of TX @@ -137,27 +142,44 @@ void i2s_out_i2s_start(struct i2s_out *i2s_out) taskEXIT_CRITICAL(&i2s_out->mux); } -int i2s_out_i2s_flush(struct i2s_out *i2s_out) +int i2s_out_i2s_flush(struct i2s_out *i2s_out, TickType_t timeout) { - EventBits_t bits; + bool i2s_done = false; + uint32_t bits = 0; - if ((bits = xEventGroupGetBits(i2s_out->event_group)) & I2S_OUT_EVENT_GROUP_BIT_I2S_EOF) { - // TX_REMPTY has already occured - LOG_DEBUG("skip event_group bits=%08x", bits); - } else { - taskENTER_CRITICAL(&i2s_out->mux); + LOG_DEBUG("..."); + + taskENTER_CRITICAL(&i2s_out->mux); + + i2s_done = i2s_out->i2s_done; + + if (!i2s_done) { + i2s_out->i2s_done_task = xTaskGetCurrentTaskHandle(); i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + } - taskEXIT_CRITICAL(&i2s_out->mux); + taskEXIT_CRITICAL(&i2s_out->mux); - LOG_DEBUG("wait event_group bits=%08x", I2S_OUT_EVENT_GROUP_BIT_I2S_EOF); + if (i2s_done) { + LOG_DEBUG("done"); - xEventGroupWaitBits(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_I2S_EOF, false, false, portMAX_DELAY); - } + return 0; + } else if (!xTaskNotifyWait(0, I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE, &bits, timeout)) { + LOG_ERROR("timeout -> bits=%08x i2s_done=%d", + bits, + i2s_out->i2s_done + ); - return 0; + i2s_out->i2s_done_task = NULL; + + return -1; + } else { + LOG_DEBUG("wait -> done"); + + return 0; + } } void i2s_out_i2s_stop(struct i2s_out *i2s_out) @@ -169,4 +191,8 @@ void i2s_out_i2s_stop(struct i2s_out *i2s_out) i2s_ll_tx_stop(i2s_out->dev); taskEXIT_CRITICAL(&i2s_out->mux); + + // reset event state + i2s_out->i2s_done = false; + i2s_out->i2s_done_task = NULL; } diff --git a/components/i2s_out/esp32/intr.c b/components/i2s_out/esp32/intr.c index c991c31a..727f1244 100644 --- a/components/i2s_out/esp32/intr.c +++ b/components/i2s_out/esp32/intr.c @@ -9,101 +9,34 @@ #include -// use a non-shared intr -#define I2S_INTR_ALLOC_FLAGS (0) +// use a non-shared, IRAM-safe intr +#define I2S_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM) static const int i2s_irq[I2S_PORT_MAX] = { [I2S_PORT_0] = ETS_I2S0_INTR_SOURCE, [I2S_PORT_1] = ETS_I2S1_INTR_SOURCE, }; -void IRAM_ATTR i2s_intr_out_dscr_err_handler(struct i2s_out *i2s_out, BaseType_t *task_wokenp) -{ - LOG_ISR_WARN(""); - - i2s_intr_clear(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_CLR); -} - -void IRAM_ATTR i2s_intr_out_eof_handler(struct i2s_out *i2s_out, BaseType_t *task_wokenp) -{ - uint32_t eof_addr; - - i2s_ll_tx_get_eof_des_addr(i2s_out->dev, &eof_addr); - - struct dma_desc *eof_desc = (struct dma_desc *) eof_addr; - - LOG_ISR_DEBUG("desc=%p", eof_desc); - - // NOTE: this is unlikely to stop DMA before this repeats at least once - i2s_ll_tx_stop_link(i2s_out->dev); - i2s_ll_rx_stop_link(i2s_out->dev); - - // unblock flush() task - xEventGroupSetBitsFromISR(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_DMA_EOF, task_wokenp); - - i2s_intr_clear(i2s_out->dev, I2S_OUT_EOF_INT_CLR); -} - -void IRAM_ATTR i2s_intr_tx_rempty_handler(struct i2s_out *i2s_out, BaseType_t *task_wokenp) -{ - LOG_ISR_DEBUG(""); - - // unblock flush() task - xEventGroupSetBitsFromISR(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_I2S_EOF, task_wokenp); - - // interrupt will fire until disabled - i2s_intr_disable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); - i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); -} - -void IRAM_ATTR i2s_intr_handler(void *arg) -{ - struct i2s_out *i2s_out = arg; - uint32_t int_st = i2s_ll_get_intr_status(i2s_out->dev); - BaseType_t task_woken = pdFALSE; - - if (!int_st) { - return; - } - - taskENTER_CRITICAL_ISR(&i2s_out->mux); - - if (int_st & I2S_OUT_DSCR_ERR_INT_ST) { - i2s_intr_out_dscr_err_handler(i2s_out, &task_woken); - } - if (int_st & I2S_OUT_EOF_INT_ST) { - i2s_intr_out_eof_handler(i2s_out, &task_woken); - } - if (int_st & I2S_TX_REMPTY_INT_ST) { - i2s_intr_tx_rempty_handler(i2s_out, &task_woken); - } - - taskEXIT_CRITICAL_ISR(&i2s_out->mux); - - if (task_woken == pdTRUE) { - portYIELD_FROM_ISR(); - } -} - int i2s_out_intr_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options) { esp_err_t err = 0; - LOG_DEBUG("intr=%p", i2s_out->intr); + if (i2s_out->intr) { + return 0; + } taskENTER_CRITICAL(&i2s_out->mux); i2s_intr_disable_all(i2s_out->dev); - - if (!i2s_out->intr) { - err = esp_intr_alloc(i2s_irq[i2s_out->port], I2S_INTR_ALLOC_FLAGS, i2s_intr_handler, i2s_out, &i2s_out->intr); - } + err = esp_intr_alloc(i2s_irq[i2s_out->port], I2S_INTR_ALLOC_FLAGS, i2s_intr_handler, i2s_out, &i2s_out->intr); taskEXIT_CRITICAL(&i2s_out->mux); if (err) { LOG_ERROR("esp_intr_alloc: %s", esp_err_to_name(err)); return -1; + } else { + LOG_DEBUG("intr=%p", i2s_out->intr); } return 0; diff --git a/components/i2s_out/esp32/intr.h b/components/i2s_out/esp32/intr.h index 6dab1e67..c0175994 100644 --- a/components/i2s_out/esp32/intr.h +++ b/components/i2s_out/esp32/intr.h @@ -22,3 +22,5 @@ static inline void i2s_intr_disable_all(i2s_dev_t *hw) { hw->int_ena.val = 0; } + +void i2s_intr_handler(void *arg); diff --git a/components/i2s_out/esp32/intr_iram.c b/components/i2s_out/esp32/intr_iram.c new file mode 100644 index 00000000..72488c9b --- /dev/null +++ b/components/i2s_out/esp32/intr_iram.c @@ -0,0 +1,129 @@ +#define ERROR + +#include "../i2s_out.h" +#include "dma.h" +#include "intr.h" + +#include + +// we may miss some EOF ISR for intermediate DMA descriptors, ensure we unblock i2s_out_dma_wait() on owner up to eof_desc +static void i2s_out_intr_dma_out_desc(struct i2s_out *i2s_out, struct dma_desc *eof_desc, BaseType_t *pxHigherPriorityTaskWoken) +{ + // update dma_out_desc and reset descs for write + for (struct dma_desc *desc = i2s_out->dma_out_desc; (desc != NULL) && (desc != eof_desc); desc = desc->next) { + LOG_ISR_WARN("missed desc=%p owner=%u len=%u", desc, desc->owner, desc->len); + + i2s_dma_desc_reset(desc); + } + + i2s_dma_desc_reset(eof_desc); + + i2s_out->dma_out_desc = eof_desc->next; + + // unblock wait() + if (i2s_out->dma_out_task) { + xTaskNotifyFromISR(i2s_out->dma_out_task, I2S_OUT_TASK_NOTIFY_BIT_DMA_OUT, eSetBits, pxHigherPriorityTaskWoken); + + i2s_out->dma_out_task = NULL; + } +} + +static void i2s_out_intr_dma_done(struct i2s_out *i2s_out, BaseType_t *pxHigherPriorityTaskWoken) +{ + // unblock flush() + i2s_out->dma_done = true; + + if (i2s_out->dma_done_task) { + xTaskNotifyFromISR(i2s_out->dma_done_task, I2S_OUT_TASK_NOTIFY_BIT_DMA_DONE, eSetBits, pxHigherPriorityTaskWoken); + + i2s_out->dma_done_task = NULL; + } +} + +static void i2s_intr_out_dscr_err_handler(struct i2s_out *i2s_out) +{ + uint32_t dscr_addr; + + i2s_intr_clear(i2s_out->dev, I2S_OUT_DSCR_ERR_INT_CLR); + i2s_dma_tx_get_des_addr(i2s_out->dev, &dscr_addr); + + LOG_ISR_ERROR("desc=%p", dscr_addr); +} + +static void i2s_intr_out_eof_handler(struct i2s_out *i2s_out, BaseType_t *pxHigherPriorityTaskWoken) +{ + uint32_t eof_addr; + + i2s_intr_clear(i2s_out->dev, I2S_OUT_EOF_INT_CLR); + i2s_ll_tx_get_eof_des_addr(i2s_out->dev, &eof_addr); + + struct dma_desc *eof_desc = (struct dma_desc *) eof_addr; + + if (eof_desc >= i2s_out->dma_data_desc && eof_desc < i2s_out->dma_data_desc + i2s_out->dma_data_count) { + LOG_ISR_DEBUG("data desc=%p owner=%u len=%u", eof_desc, eof_desc->owner, eof_desc->len); + + i2s_out_intr_dma_out_desc(i2s_out, eof_desc, pxHigherPriorityTaskWoken); + + } else if (i2s_out->dma_repeat_desc && eof_desc >= i2s_out->dma_repeat_desc && eof_desc < i2s_out->dma_repeat_desc + i2s_out->dma_data_count * i2s_out->dma_repeat_count) { + LOG_ISR_DEBUG("repeat desc=%p owner=%u len=%u", eof_desc, eof_desc->owner, eof_desc->len); + + i2s_out_intr_dma_out_desc(i2s_out, eof_desc, pxHigherPriorityTaskWoken); + + } else if (eof_desc == i2s_out->dma_end_desc) { + LOG_ISR_DEBUG("end desc=%p owner=%u len=%u, dma_write_desc=%p dma_out_desc=%p", eof_desc, eof_desc->owner, eof_desc->len, i2s_out->dma_write_desc, i2s_out->dma_out_desc); + + // we may miss some EOF ISR for intermediate DMA descriptors, unblock i2s_out_dma_wait() + i2s_out_intr_dma_out_desc(i2s_out, eof_desc, pxHigherPriorityTaskWoken); + i2s_out_intr_dma_done(i2s_out, pxHigherPriorityTaskWoken); + + } else { + LOG_ISR_ERROR("unknown desc=%p owner=%u len=%u", eof_desc, eof_desc->owner, eof_desc->len); + } +} + +static void i2s_intr_tx_rempty_handler(struct i2s_out *i2s_out, BaseType_t *pxHigherPriorityTaskWoken) +{ + LOG_ISR_DEBUG(""); + + // interrupt will fire until disabled + i2s_intr_disable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + + // unblock flush() task + i2s_out->i2s_done = true; + + if (i2s_out->i2s_done_task) { + xTaskNotifyFromISR(i2s_out->i2s_done_task, I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE, eSetBits, pxHigherPriorityTaskWoken); + + i2s_out->i2s_done_task = NULL; + } +} + +void i2s_intr_handler(void *arg) +{ + struct i2s_out *i2s_out = arg; + uint32_t int_st = i2s_ll_get_intr_status(i2s_out->dev); + BaseType_t pxHigherPriorityTaskWoken = pdFALSE; + + if (!int_st) { + return; + } + + taskENTER_CRITICAL_ISR(&i2s_out->mux); + + if (int_st & I2S_OUT_DSCR_ERR_INT_ST) { + i2s_intr_out_dscr_err_handler(i2s_out); + } + if (int_st & I2S_OUT_EOF_INT_ST) { + i2s_intr_out_eof_handler(i2s_out, &pxHigherPriorityTaskWoken); + } + if (int_st & I2S_TX_REMPTY_INT_ST) { + i2s_intr_tx_rempty_handler(i2s_out, &pxHigherPriorityTaskWoken); + } + + taskEXIT_CRITICAL_ISR(&i2s_out->mux); + + if (pxHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} diff --git a/components/i2s_out/esp8266/dma.c b/components/i2s_out/esp8266/dma.c index 89587ca0..8367fc98 100644 --- a/components/i2s_out/esp8266/dma.c +++ b/components/i2s_out/esp8266/dma.c @@ -1,3 +1,5 @@ +#define ERROR + #include "../i2s_out.h" #include "slc.h" #include "slc_isr.h" @@ -204,7 +206,9 @@ void IRAM_ATTR i2s_out_slc_isr(void *arg) slc_stop(&SLC0); // unblock flush() task - xEventGroupSetBitsFromISR(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_DMA_EOF, &task_woken); + if (!xEventGroupSetBitsFromISR(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_DMA_EOF, &task_woken)) { + LOG_ISR_ERROR("xEventGroupSetBitsFromISR") + } } if (SLC0.int_st.rx_dscr_err) { LOG_ISR_WARN("rx_dscr_err"); diff --git a/components/i2s_out/esp8266/i2s.c b/components/i2s_out/esp8266/i2s.c index fa8fe197..3636be80 100644 --- a/components/i2s_out/esp8266/i2s.c +++ b/components/i2s_out/esp8266/i2s.c @@ -1,3 +1,5 @@ +#define ERROR + #include "../i2s_out.h" #include "i2s.h" @@ -24,7 +26,9 @@ static void IRAM_ATTR i2s_isr(void *arg) if (I2S0.int_st.tx_rempty) { // unblock flush() task - xEventGroupSetBitsFromISR(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_I2S_EOF, &task_woken); + if (!xEventGroupSetBitsFromISR(i2s_out->event_group, I2S_OUT_EVENT_GROUP_BIT_I2S_EOF, &task_woken)) { + LOG_ISR_ERROR("xEventGroupSetBitsFromISR"); + } // interrupt will fire until disabled I2S0.int_ena.tx_rempty = 0; diff --git a/components/i2s_out/i2s_out.c b/components/i2s_out/i2s_out.c index 4db3ee1e..8069eb87 100644 --- a/components/i2s_out/i2s_out.c +++ b/components/i2s_out/i2s_out.c @@ -21,10 +21,12 @@ int i2s_out_init(struct i2s_out *i2s_out, i2s_port_t port) return -1; } +#if CONFIG_IDF_TARGET_ESP8266 if (!(i2s_out->event_group = xEventGroupCreate())) { LOG_ERROR("xEventGroupCreate"); return -1; } +#endif #if CONFIG_IDF_TARGET_ESP32 portMUX_INITIALIZE(&i2s_out->mux); @@ -68,20 +70,17 @@ int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, return 0; error: - free(i2s_out->dma_eof_buf); - free(i2s_out->dma_rx_buf); - free(i2s_out->dma_rx_desc); - free(i2s_out->dma_eof_desc); + i2s_out_dma_free(i2s_out); free(i2s_out); return err; } -int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options) +int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options, TickType_t timeout) { int err = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } @@ -111,6 +110,8 @@ int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options) goto error; } + i2s_out->setup = true; + return 0; error: @@ -119,17 +120,22 @@ int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options) return err; } -static int i2s_out_write(struct i2s_out *i2s_out, const void *data, size_t size) +static int i2s_out_write(struct i2s_out *i2s_out, const void *data, size_t size, TickType_t timeout) { int ret = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + while (size) { - if ((ret = i2s_out_dma_write(i2s_out, data, size)) < 0) { + if ((ret = i2s_out_dma_write(i2s_out, data, size, timeout)) < 0) { LOG_ERROR("i2s_out_dma_write"); break; } else if (!ret) { @@ -150,20 +156,25 @@ static int i2s_out_write(struct i2s_out *i2s_out, const void *data, size_t size) return ret; } -int i2s_out_write_serial16(struct i2s_out *i2s_out, const uint16_t data[], size_t count) +int i2s_out_write_serial16(struct i2s_out *i2s_out, const uint16_t data[], size_t count, TickType_t timeout) { - return i2s_out_write(i2s_out, data, count * sizeof(*data)); + return i2s_out_write(i2s_out, data, count * sizeof(*data), timeout); } -int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t count) +int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t count, TickType_t timeout) { int ret = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + uint32_t (*buf)[1]; unsigned index = 0; @@ -172,7 +183,7 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t void *ptr; size_t len; - if (!(len = i2s_out_dma_buffer(i2s_out, &ptr, count - index, sizeof(*buf)))) { + if (!(len = i2s_out_dma_buffer(i2s_out, &ptr, count - index, sizeof(*buf), timeout))) { LOG_WARN("i2s_out_dma_buffer: DMA buffer full"); ret = 1; goto error; @@ -199,15 +210,20 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t } #if I2S_OUT_PARALLEL_SUPPORTED - int i2s_out_write_parallel8x8(struct i2s_out *i2s_out, uint8_t *data, unsigned width) + int i2s_out_write_parallel8x8(struct i2s_out *i2s_out, uint8_t *data, unsigned width, TickType_t timeout) { int ret = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + uint32_t (*buf)[2]; unsigned index = 0; @@ -216,7 +232,7 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t void *ptr; size_t count; - if (!(count = i2s_out_dma_buffer(i2s_out, &ptr, width - index, sizeof(*buf)))) { + if (!(count = i2s_out_dma_buffer(i2s_out, &ptr, width - index, sizeof(*buf), timeout))) { LOG_WARN("i2s_out_dma_buffer: DMA buffer full"); ret = 1; goto error; @@ -242,15 +258,20 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t return ret; } - int i2s_out_write_parallel8x16(struct i2s_out *i2s_out, uint16_t *data, unsigned width) + int i2s_out_write_parallel8x16(struct i2s_out *i2s_out, uint16_t *data, unsigned width, TickType_t timeout) { int ret = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + uint32_t (*buf)[4]; unsigned index = 0; @@ -259,7 +280,7 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t void *ptr; size_t count; - if (!(count = i2s_out_dma_buffer(i2s_out, &ptr, width - index, sizeof(*buf)))) { + if (!(count = i2s_out_dma_buffer(i2s_out, &ptr, width - index, sizeof(*buf), timeout))) { LOG_WARN("i2s_out_dma_buffer: DMA buffer full"); ret = 1; goto error; @@ -285,15 +306,20 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t return ret; } - int i2s_out_write_parallel8x32(struct i2s_out *i2s_out, uint32_t *data, unsigned width) + int i2s_out_write_parallel8x32(struct i2s_out *i2s_out, uint32_t *data, unsigned width, TickType_t timeout) { int ret = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + uint32_t (*buf)[8]; unsigned index = 0; @@ -302,7 +328,7 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t void *ptr; size_t count; - if (!(count = i2s_out_dma_buffer(i2s_out, &ptr, width - index, sizeof(*buf)))) { + if (!(count = i2s_out_dma_buffer(i2s_out, &ptr, width - index, sizeof(*buf), timeout))) { LOG_WARN("i2s_out_dma_buffer: DMA buffer full"); ret = 1; goto error; @@ -329,47 +355,101 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t } #endif -int i2s_out_repeat(struct i2s_out *i2s_out, unsigned count) +int i2s_out_wait(struct i2s_out *i2s_out, TickType_t timeout) { int err = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } - i2s_out_dma_repeat(i2s_out, count); + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + // wait for previous start() to complete? + if (i2s_out_dma_running(i2s_out)) { + if ((err = i2s_out_dma_flush(i2s_out, timeout))) { + LOG_ERROR("i2s_out_dma_flush"); + goto error; + } + + if ((err = i2s_out_i2s_flush(i2s_out, timeout))) { + LOG_ERROR("i2s_out_i2s_flush"); + goto error; + } + + i2s_out_dma_stop(i2s_out); + i2s_out_i2s_stop(i2s_out); + } + +error: if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { LOG_WARN("xSemaphoreGiveRecursive"); } return err; + } -int i2s_out_flush(struct i2s_out *i2s_out) +int i2s_out_start(struct i2s_out *i2s_out, TickType_t timeout) { int err = 0; - if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + + // wait for previous start() to complete? + if ((err = i2s_out_wait(i2s_out, timeout))) { + goto error; + } + + // have write()? if (i2s_out_dma_pending(i2s_out)) { - i2s_out_dma_start(i2s_out); + if ((err = i2s_out_dma_start(i2s_out))) { + LOG_ERROR("i2s_out_dma_start"); + goto error; + } + i2s_out_i2s_start(i2s_out); } - // wait for TX DMA EOF - if ((err = i2s_out_dma_flush(i2s_out))) { - LOG_ERROR("i2s_out_dma_flush"); +error: + if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { + LOG_WARN("xSemaphoreGiveRecursive"); + } + + return err; +} + +int i2s_out_flush(struct i2s_out *i2s_out, TickType_t timeout) +{ + int err = 0; + + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { + LOG_ERROR("xSemaphoreTakeRecursive"); + return -1; + } + + if (!i2s_out->setup) { + LOG_ERROR("setup"); + return -1; + } + + if ((err = i2s_out_start(i2s_out, timeout))) { goto error; } - // wait for I2S TX done - if ((err = i2s_out_i2s_flush(i2s_out))) { - LOG_ERROR("i2s_out_i2s_flush"); + if ((err = i2s_out_wait(i2s_out, timeout))) { goto error; } @@ -381,13 +461,69 @@ int i2s_out_flush(struct i2s_out *i2s_out) return err; } -int i2s_out_close(struct i2s_out *i2s_out) +int i2s_out_close(struct i2s_out *i2s_out, TickType_t timeout) { - int err = i2s_out_flush(i2s_out); + int err = 0; + + if (!xSemaphoreTakeRecursive(i2s_out->mutex, timeout)) { + LOG_ERROR("xSemaphoreTakeRecursive"); + return -1; + } + + if (!i2s_out->setup) { + LOG_WARN("setup"); + err = 1; + goto error; + } + err = i2s_out_flush(i2s_out, timeout); + + i2s_out_dma_stop(i2s_out); i2s_out_i2s_stop(i2s_out); i2s_out_pin_teardown(i2s_out); + i2s_out->setup = false; + + if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { + LOG_WARN("xSemaphoreGiveRecursive"); + } + +error: + // from open() + if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { + LOG_WARN("xSemaphoreGiveRecursive"); + } + + return err; +} + +int i2s_out_reset(struct i2s_out *i2s_out) +{ + int err = 0; + + if (!xSemaphoreTakeRecursive(i2s_out->mutex, 0)) { + LOG_ERROR("xSemaphoreTakeRecursive"); + return -1; + } + + if (!i2s_out->setup) { + LOG_WARN("setup"); + err = 1; + goto error; + } + + i2s_out_dma_stop(i2s_out); + i2s_out_i2s_stop(i2s_out); + i2s_out_pin_teardown(i2s_out); + + i2s_out->setup = false; + + if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { + LOG_WARN("xSemaphoreGiveRecursive"); + } + +error: + // from open() if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { LOG_WARN("xSemaphoreGiveRecursive"); } @@ -397,17 +533,26 @@ int i2s_out_close(struct i2s_out *i2s_out) int i2s_out_teardown(struct i2s_out *i2s_out) { + int err = 0; + if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { LOG_ERROR("xSemaphoreTakeRecursive"); return -1; } + if (i2s_out->setup) { + LOG_WARN("setup"); + err = 1; + goto error; + } + i2s_out_intr_teardown(i2s_out); i2s_out_dev_teardown(i2s_out); +error: if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { LOG_WARN("xSemaphoreGiveRecursive"); } - return 0; + return err; } diff --git a/components/i2s_out/i2s_out.h b/components/i2s_out/i2s_out.h index f7901750..ba6fd6f6 100644 --- a/components/i2s_out/i2s_out.h +++ b/components/i2s_out/i2s_out.h @@ -12,8 +12,17 @@ struct dma_desc; -#define I2S_OUT_EVENT_GROUP_BIT_DMA_EOF (1 << 0) -#define I2S_OUT_EVENT_GROUP_BIT_I2S_EOF (1 << 1) +#if CONFIG_IDF_TARGET_ESP32 + #define I2S_OUT_TASK_NOTIFY_BIT_DMA_OUT (1 << 0) + #define I2S_OUT_TASK_NOTIFY_BIT_DMA_DONE (1 << 1) + #define I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE (1 << 2) +#endif + +#if CONFIG_IDF_TARGET_ESP8266 + #define I2S_OUT_EVENT_GROUP_BIT_DMA_EOF (1 << 0) + #define I2S_OUT_EVENT_GROUP_BIT_DMA_DONE (1 << 1) + #define I2S_OUT_EVENT_GROUP_BIT_I2S_EOF (1 << 2) +#endif struct i2s_out { i2s_port_t port; @@ -21,7 +30,10 @@ struct i2s_out { #if CONFIG_IDF_TARGET_ESP32 portMUX_TYPE mux; #endif +#if CONFIG_IDF_TARGET_ESP8266 EventGroupHandle_t event_group; +#endif + bool setup; /* dev */ SemaphoreHandle_t dev_mutex; @@ -40,35 +52,47 @@ struct i2s_out { #endif /* dma */ - uint8_t *dma_rx_buf, *dma_eof_buf; - struct dma_desc *dma_rx_desc; + uint8_t *dma_data_buf, *dma_end_buf; + struct dma_desc *dma_data_desc; struct dma_desc *dma_repeat_desc; - struct dma_desc *dma_eof_desc; + struct dma_desc *dma_end_desc; - unsigned dma_rx_count, dma_rx_repeat; + unsigned dma_data_count, dma_repeat_count; + size_t dma_end_len; - // pointer to software-owned dma_rx_desc used for write() + // software-owned dma_data_desc used for write() struct dma_desc *dma_write_desc; - bool dma_start; // set by i2s_out_dma_start + // current hardware-owned dma desc (data, repeat, end, NULL) - updated by ISR + struct dma_desc *dma_out_desc; + + bool dma_start; // initialized by i2s_out_dma_setup(), set by i2s_out_dma_start(), cleared by i2s_out_dma_stop() + bool dma_done; + bool i2s_done; + + TaskHandle_t dma_out_task; // task waiting for dma_out_desc to update + TaskHandle_t dma_done_task; // task waiting for dma_done to be set + TaskHandle_t i2s_done_task; // task waiting for i2s_done to be set }; /* dma.c */ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigned repeat); int i2s_out_dma_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options); -size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, size_t size); +size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, size_t size, TickType_t timeout); void i2s_out_dma_commit(struct i2s_out *i2s_out, unsigned count, size_t size); -int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size); -void i2s_out_dma_repeat(struct i2s_out *i2s_out, unsigned count); +int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size, TickType_t timeout); +int i2s_out_dma_running(struct i2s_out *i2s_out); int i2s_out_dma_pending(struct i2s_out *i2s_out); -void i2s_out_dma_start(struct i2s_out *i2s_out); -int i2s_out_dma_flush(struct i2s_out *i2s_out); +int i2s_out_dma_start(struct i2s_out *i2s_out); +int i2s_out_dma_flush(struct i2s_out *i2s_out, TickType_t timeout); +void i2s_out_dma_stop(struct i2s_out *i2s_out); +void i2s_out_dma_free(struct i2s_out *i2s_out); /* i2s.c */ int i2s_out_i2s_init(struct i2s_out *i2s_out); int i2s_out_i2s_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options); void i2s_out_i2s_start(struct i2s_out *i2s_out); -int i2s_out_i2s_flush(struct i2s_out *i2s_out); +int i2s_out_i2s_flush(struct i2s_out *i2s_out, TickType_t timeout); void i2s_out_i2s_stop(struct i2s_out *i2s_out); /* dev.c */ diff --git a/components/i2s_out/include/i2s_out.h b/components/i2s_out/include/i2s_out.h index b0cc0518..bc22add3 100644 --- a/components/i2s_out/include/i2s_out.h +++ b/components/i2s_out/include/i2s_out.h @@ -126,6 +126,9 @@ struct i2s_out_options { // number of parallel data bits in use, 0 -> all unsigned parallel_data_bits; #endif + + // number of times to repeat data, 0 -> off + unsigned repeat_data_count; }; /** @@ -145,7 +148,7 @@ int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, * * Returns <0 on error, 0 on success. */ -int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options); +int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options, TickType_t timeout); /** * Copy exactly `count` 16-bit words from `data` into the internal TX DMA buffer for serial output in little-endian order. @@ -158,7 +161,7 @@ int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options) * * Returns <0 error, 0 on success, >0 if TX buffer is full. */ -int i2s_out_write_serial16(struct i2s_out *i2s_out, const uint16_t data[], size_t count); +int i2s_out_write_serial16(struct i2s_out *i2s_out, const uint16_t data[], size_t count, TickType_t timeout); /** * Copy exactly `count` 32-bit words from `data` into the internal TX DMA buffer for serial output in little-endian order. @@ -173,7 +176,7 @@ int i2s_out_write_serial16(struct i2s_out *i2s_out, const uint16_t data[], size_ * * Returns <0 error, 0 on success, >0 if TX buffer is full. */ -int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t count); +int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t count, TickType_t timeout); #if I2S_OUT_PARALLEL_SUPPORTED /** @@ -188,7 +191,7 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t * * Returns <0 error, 0 on success, >0 if TX buffer is full. */ - int i2s_out_write_parallel8x8(struct i2s_out *i2s_out, uint8_t *data, unsigned width); + int i2s_out_write_parallel8x8(struct i2s_out *i2s_out, uint8_t *data, unsigned width, TickType_t timeout); /** * Copy 8 channels of `width` x 16-bit `data` into the internal TX DMA buffer, transposing the buffers for @@ -202,7 +205,7 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t * * Returns <0 error, 0 on success, >0 if TX buffer is full. */ - int i2s_out_write_parallel8x16(struct i2s_out *i2s_out, uint16_t *data, unsigned width); + int i2s_out_write_parallel8x16(struct i2s_out *i2s_out, uint16_t *data, unsigned width, TickType_t timeout); /** * Copy 8 channels of `width` x 32-bit `data` into the internal TX DMA buffer, transposing the buffers for @@ -216,29 +219,40 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t * * Returns <0 error, 0 on success, >0 if TX buffer is full. */ - int i2s_out_write_parallel8x32(struct i2s_out *i2s_out, uint32_t *data, unsigned width); + int i2s_out_write_parallel8x32(struct i2s_out *i2s_out, uint32_t *data, unsigned width, TickType_t timeout); #endif /** - * Setup DMA to repeat all written buffers given count of times. Completes any writes. + * Start I2S output, do not wait for TX. * * Returns <0 on error, 0 on success. */ -int i2s_out_repeat(struct i2s_out *i2s_out, unsigned count); +int i2s_out_start(struct i2s_out *i2s_out, TickType_t timeout); /** * Start I2S output, and wait for the complete TX buffer and EOF frame to be written. * * Returns <0 on error, 0 on success. */ -int i2s_out_flush(struct i2s_out *i2s_out); +int i2s_out_flush(struct i2s_out *i2s_out, TickType_t timeout); /** * Flush I2S output, and release the lock acquired using `i2s_out_open()`. * * Tears down the data_gpio pin. */ -int i2s_out_close(struct i2s_out *i2s_out); +int i2s_out_close(struct i2s_out *i2s_out, TickType_t timeout); + +/** + * Reset I2S output, and release the lock acquired using `i2s_out_open()`. + * + * Tears down the data_gpio pin. + * + * Can only fail if the interface is locked by a different task. + * + * Returns >0 if the i2s_out is not setup(), <0 on errors. + */ +int i2s_out_reset(struct i2s_out *i2s_out); /** * Tear down all state setup by i2s_out_open(), including state typically shared across multiple open -> close calls. diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 51436bc1..72bf06b5 100644 --- a/components/leds/include/leds.h +++ b/components/leds/include/leds.h @@ -230,6 +230,9 @@ enum leds_interface leds_interface_for_protocol(enum leds_protocol protocol); struct i2s_out *i2s_out; + // General timeout for all i2s_out_*() operations + TickType_t timeout; + SemaphoreHandle_t pin_mutex; TickType_t pin_timeout; @@ -449,5 +452,40 @@ unsigned leds_count_total_power(struct leds *leds); */ bool leds_is_active(struct leds *leds); -/* Output frames on interface */ +/* + * Check for setup() state. + */ +bool leds_is_interface_setup(struct leds *leds); + +/* + * Setup persistent LEDs interface. + * + * It is also possible to call leds_tx() in sync mode without setup() / close(). + * A persistently setup interface cannot be shared across multiple leds instances, but may perform better. + */ +int leds_interface_setup(struct leds *leds); + +/* + * Output frames on interface. + * + * With a persistent setup(), this does not wait for TX to complete, and leaves the leds interface open. + * Otherwise, this is equivalent to setup() -> tx -> close(), and waits for TX to complete. + */ int leds_tx(struct leds *leds); + +/* + * Close persistent LEDs interface. + * + * Waits for any in-progress tx to complete, leaving the interface in a defined state. + */ +int leds_interface_close(struct leds *leds); + +/* + * Reset persistent LEDs interface. + * + * Used to recover from errors, where the interface is in an undefined state. + * + * Does not timeout, can only fail if the i2s_out is locked by a different task. + */ +int leds_interface_reset(struct leds *leds); + diff --git a/components/leds/include/leds_stats.h b/components/leds/include/leds_stats.h index b3acff71..08f83746 100644 --- a/components/leds/include/leds_stats.h +++ b/components/leds/include/leds_stats.h @@ -9,6 +9,7 @@ struct leds_interface_i2s_stats { struct stats_timer open; struct stats_timer write; + struct stats_timer start; struct stats_timer flush; }; #endif diff --git a/components/leds/interface.c b/components/leds/interface.c index 9266a7d3..3173208a 100644 --- a/components/leds/interface.c +++ b/components/leds/interface.c @@ -53,7 +53,7 @@ int leds_interface_init(union leds_interface_state *interface, const struct leds LOG_ERROR("unsupported interface=I2S for protocol=%d", options->protocol); return -1; - } else if ((err = leds_interface_i2s_init(&interface->i2s, &options->i2s, protocol_type->i2s_interface_mode, protocol_type->i2s_interface_func, leds_interface_i2s_stats(options->interface)))) { + } else if ((err = leds_interface_i2s_init(&interface->i2s, &options->i2s, protocol_type->i2s_interface_mode, protocol_type->i2s_interface_func, options->count, leds_interface_i2s_stats(options->interface)))) { LOG_ERROR("leds_interface_i2s_init"); return err; } @@ -68,3 +68,163 @@ int leds_interface_init(union leds_interface_state *interface, const struct leds return 0; } + +bool leds_is_interface_setup(struct leds *leds) +{ + switch (leds->options.interface) { + case LEDS_INTERFACE_NONE: + return 0; + + #if CONFIG_LEDS_SPI_ENABLED + case LEDS_INTERFACE_SPI: + return 0; + #endif + + #if CONFIG_LEDS_UART_ENABLED + case LEDS_INTERFACE_UART: + return 0; + #endif + + #if CONFIG_LEDS_I2S_ENABLED + # if LEDS_I2S_INTERFACE_COUNT > 0 + case LEDS_INTERFACE_I2S0: + # endif + # if LEDS_I2S_INTERFACE_COUNT > 1 + case LEDS_INTERFACE_I2S1: + # endif + return leds->interface.i2s.i2s_out_setup; + #endif + + default: + LOG_ERROR("unsupported interface=%#x", leds->options.interface); + return -1; + } +} + +int leds_interface_setup(struct leds *leds) +{ + switch (leds->options.interface) { + case LEDS_INTERFACE_NONE: + return 0; + + #if CONFIG_LEDS_SPI_ENABLED + case LEDS_INTERFACE_SPI: + return 0; + #endif + + #if CONFIG_LEDS_UART_ENABLED + case LEDS_INTERFACE_UART: + return 0; + #endif + + #if CONFIG_LEDS_I2S_ENABLED + # if LEDS_I2S_INTERFACE_COUNT > 0 + case LEDS_INTERFACE_I2S0: + # endif + # if LEDS_I2S_INTERFACE_COUNT > 1 + case LEDS_INTERFACE_I2S1: + # endif + return leds_interface_i2s_setup(&leds->interface.i2s); + #endif + + default: + LOG_ERROR("unsupported interface=%#x", leds->options.interface); + return -1; + } +} + +int leds_interface_tx(struct leds *leds) +{ + switch (leds->options.interface) { + case LEDS_INTERFACE_NONE: + return 0; + + #if CONFIG_LEDS_SPI_ENABLED + case LEDS_INTERFACE_SPI: + return leds_interface_spi_tx(&leds->interface.spi, leds->pixels, leds->options.count, &leds->limit); + #endif + + #if CONFIG_LEDS_UART_ENABLED + case LEDS_INTERFACE_UART: + return leds_interface_uart_tx(&leds->interface.uart, leds->pixels, leds->options.count, &leds->limit); + #endif + + #if CONFIG_LEDS_I2S_ENABLED + # if LEDS_I2S_INTERFACE_COUNT > 0 + case LEDS_INTERFACE_I2S0: + # endif + # if LEDS_I2S_INTERFACE_COUNT > 1 + case LEDS_INTERFACE_I2S1: + # endif + return leds_interface_i2s_tx(&leds->interface.i2s, leds->pixels, leds->options.count, &leds->limit); + #endif + + default: + LOG_ERROR("unsupported interface=%#x", leds->options.interface); + return -1; + } +} + +int leds_interface_close(struct leds *leds) +{ + switch (leds->options.interface) { + case LEDS_INTERFACE_NONE: + return 0; + + #if CONFIG_LEDS_SPI_ENABLED + case LEDS_INTERFACE_SPI: + return 0; + #endif + + #if CONFIG_LEDS_UART_ENABLED + case LEDS_INTERFACE_UART: + return 0; + #endif + + #if CONFIG_LEDS_I2S_ENABLED + # if LEDS_I2S_INTERFACE_COUNT > 0 + case LEDS_INTERFACE_I2S0: + # endif + # if LEDS_I2S_INTERFACE_COUNT > 1 + case LEDS_INTERFACE_I2S1: + # endif + return leds_interface_i2s_close(&leds->interface.i2s); + #endif + + default: + LOG_ERROR("unsupported interface=%#x", leds->options.interface); + return -1; + } +} + +int leds_interface_reset(struct leds *leds) +{ + switch (leds->options.interface) { + case LEDS_INTERFACE_NONE: + return 0; + + #if CONFIG_LEDS_SPI_ENABLED + case LEDS_INTERFACE_SPI: + return 0; + #endif + + #if CONFIG_LEDS_UART_ENABLED + case LEDS_INTERFACE_UART: + return 0; + #endif + + #if CONFIG_LEDS_I2S_ENABLED + # if LEDS_I2S_INTERFACE_COUNT > 0 + case LEDS_INTERFACE_I2S0: + # endif + # if LEDS_I2S_INTERFACE_COUNT > 1 + case LEDS_INTERFACE_I2S1: + # endif + return leds_interface_i2s_reset(&leds->interface.i2s); + #endif + + default: + LOG_ERROR("unsupported interface=%#x", leds->options.interface); + return -1; + } +} diff --git a/components/leds/interface.h b/components/leds/interface.h index cdfcb626..f063bf40 100644 --- a/components/leds/interface.h +++ b/components/leds/interface.h @@ -23,3 +23,5 @@ union leds_interface_state { struct leds_interface_uart uart; #endif }; + +int leds_interface_tx(struct leds *leds); diff --git a/components/leds/interfaces/i2s.h b/components/leds/interfaces/i2s.h index 7850e046..a0ee9b02 100644 --- a/components/leds/interfaces/i2s.h +++ b/components/leds/interfaces/i2s.h @@ -51,15 +51,17 @@ union leds_interface_i2s_func { #define LEDS_INTERFACE_I2S_FUNC(type, func) ((union leds_interface_i2s_func) { .type = func }) struct leds_interface_i2s { + const struct leds_interface_i2s_options *options; + enum leds_interface_i2s_mode mode; union leds_interface_i2s_func func; union leds_interface_i2s_buf *buf; unsigned parallel; - unsigned repeat; struct i2s_out *i2s_out; struct i2s_out_options i2s_out_options; + bool i2s_out_setup; struct leds_interface_options_gpio gpio; struct leds_interface_i2s_stats *stats; @@ -68,7 +70,10 @@ struct leds_interface_i2s { size_t leds_interface_i2s_buffer_size(enum leds_interface_i2s_mode mode, unsigned led_count, unsigned pin_count); size_t leds_interface_i2s_buffer_align(enum leds_interface_i2s_mode mode, unsigned pin_count); -int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct leds_interface_i2s_options *options, enum leds_interface_i2s_mode mode, union leds_interface_i2s_func func, struct leds_interface_i2s_stats *stats); +int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct leds_interface_i2s_options *options, enum leds_interface_i2s_mode mode, union leds_interface_i2s_func func, unsigned count, struct leds_interface_i2s_stats *stats); +int leds_interface_i2s_setup(struct leds_interface_i2s *interface); int leds_interface_i2s_tx(struct leds_interface_i2s *interface, const struct leds_color *pixels, unsigned count, const struct leds_limit *limit); +int leds_interface_i2s_close(struct leds_interface_i2s *interface); +int leds_interface_i2s_reset(struct leds_interface_i2s *interface); #endif diff --git a/components/leds/interfaces/i2s/setup.c b/components/leds/interfaces/i2s/setup.c new file mode 100644 index 00000000..ef46c88e --- /dev/null +++ b/components/leds/interfaces/i2s/setup.c @@ -0,0 +1,203 @@ +#include "../i2s.h" +#include "../../leds.h" +#include "../../stats.h" +#include "../../gpio.h" + +#include + +int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct leds_interface_i2s_options *options, enum leds_interface_i2s_mode mode, union leds_interface_i2s_func func, unsigned count, struct leds_interface_i2s_stats *stats) +{ + interface->options = options; + interface->mode = mode; + interface->func = func; + +#if LEDS_I2S_PARALLEL_ENABLED + interface->parallel = options->parallel; +#else + interface->parallel = 0; +#endif + + if (!(interface->buf = calloc(1, leds_interface_i2s_buf_size(interface->mode, interface->parallel)))) { + LOG_ERROR("calloc"); + return -1; + } + + interface->i2s_out = options->i2s_out; + interface->i2s_out_options = (struct i2s_out_options) { + // shared IO pins + .pin_mutex = options->pin_mutex, + .pin_timeout = options->pin_timeout, + }; + + switch(mode) { + case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: + #if LEDS_I2S_PARALLEL_ENABLED + if (options->parallel) { + interface->i2s_out_options.mode = I2S_OUT_MODE_8BIT_PARALLEL; + } else { + interface->i2s_out_options.mode = I2S_OUT_MODE_32BIT_SERIAL; + } + #else + interface->i2s_out_options.mode = I2S_OUT_MODE_32BIT_SERIAL; + #endif + + break; + + case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: + case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: + case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: + #if LEDS_I2S_PARALLEL_ENABLED + if (options->parallel) { + interface->i2s_out_options.mode = I2S_OUT_MODE_8BIT_PARALLEL; + } else { + // using 4x4bit -> 16-bit samples + interface->i2s_out_options.mode = I2S_OUT_MODE_16BIT_SERIAL; + } + #else + interface->i2s_out_options.mode = I2S_OUT_MODE_16BIT_SERIAL; + #endif + + break; + + default: + LOG_FATAL("unknown mode=%d", mode); + } + + switch(mode) { + case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: + interface->i2s_out_options.clock = i2s_out_clock(options->clock_rate); + + break; + + case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: + // 3.333MHz bit clock => 0.300us per I2S bit + // four I2S bits per 1.20us protocol bit + interface->i2s_out_options.clock = I2S_OUT_CLOCK_3M333; + + break; + + case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: + case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: + // 3.2MHz bit clock => 0.3125us per I2S bit + // four I2S bits per 1.25us protocol bit + interface->i2s_out_options.clock = I2S_OUT_CLOCK_3M2; + + break; + + default: + LOG_FATAL("unknown mode=%d", mode); + } + + switch (mode) { + case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: + // XXX: required to workaround I2S start glitch looping previous data bits + interface->i2s_out_options.eof_value = 0x00000000; + + // one clock cycle per pixel, min 32 cycles + interface->i2s_out_options.eof_count = (1 + count / 32); + + break; + + case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: + case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: + case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: + // 1.25us per 4-bit = 2.5us per byte * four bytes per I2S sample = 10us per 32-bit I2S sample + interface->i2s_out_options.eof_value = 0x00000000; + + // hold low for 8 * 10us + interface->i2s_out_options.eof_count = 8; + + break; + + default: + LOG_FATAL("unknown mode=%d", interface->mode); + } + + +#if LEDS_I2S_GPIO_PINS_ENABLED + for (int i = 0; i < LEDS_I2S_GPIO_PINS_SIZE; i++) { + interface->i2s_out_options.bck_gpios[i] = (i < options->gpio_pins_count) ? options->clock_pins[i] : GPIO_NUM_NC; + interface->i2s_out_options.data_gpios[i] = (i < options->gpio_pins_count) ? options->data_pins[i] : GPIO_NUM_NC; + interface->i2s_out_options.inv_data_gpios[i] = (i < options->gpio_pins_count) ? options->inv_data_pins[i] : GPIO_NUM_NC; + } +#endif + +#if LEDS_I2S_PARALLEL_ENABLED + // allow multiple copies of parallel data bits on different GPIOs + interface->i2s_out_options.parallel_data_bits = options->parallel; + + if (options->parallel) { + // I2S LCD mode requires the BCK/WS signal to be inverted when routed through the GPIO matrix + // invert BCK to idle high, transition on falling edge, and sample data on rising edge + interface->i2s_out_options.bck_inv = true; + } else { + // BCK is idle low, transition on falling edge, and sample data on rising edge + interface->i2s_out_options.bck_inv = false; + } +#endif + + if (options->repeat) { + interface->i2s_out_options.repeat_data_count = options->repeat; + } + + interface->gpio = options->gpio; + interface->stats = stats; + + return 0; +} + +int leds_interface_i2s_setup(struct leds_interface_i2s *interface) +{ + int err = 0; + + WITH_STATS_TIMER(&interface->stats->open) { + if ((err = i2s_out_open(interface->i2s_out, &interface->i2s_out_options, interface->options->timeout))) { + LOG_ERROR("i2s_out_open"); + return err; + } + } + + interface->i2s_out_setup = true; + +#if CONFIG_LEDS_GPIO_ENABLED + leds_gpio_setup(&interface->gpio); +#endif + + return 0; +} + +int leds_interface_i2s_close(struct leds_interface_i2s *interface) +{ + int err = 0; + + interface->i2s_out_setup = false; + +#if CONFIG_LEDS_GPIO_ENABLED + leds_gpio_close(&interface->gpio); +#endif + + if ((err = i2s_out_close(interface->i2s_out, interface->options->timeout))) { + LOG_ERROR("i2s_out_close"); + return err; + } + + return err; +} + +int leds_interface_i2s_reset(struct leds_interface_i2s *interface) +{ + int err = 0; + +#if CONFIG_LEDS_GPIO_ENABLED + leds_gpio_close(&interface->gpio); +#endif + + if ((err = i2s_out_reset(interface->i2s_out))) { + LOG_ERROR("i2s_out_reset"); + return err; + } + + interface->i2s_out_setup = false; + + return err; +} diff --git a/components/leds/interfaces/i2s/tx.c b/components/leds/interfaces/i2s/tx.c index 95e44709..29f1842d 100644 --- a/components/leds/interfaces/i2s/tx.c +++ b/components/leds/interfaces/i2s/tx.c @@ -1,7 +1,6 @@ #include "../i2s.h" #include "../../leds.h" #include "../../stats.h" -#include "../../gpio.h" #include @@ -12,7 +11,7 @@ static int leds_interface_i2s_tx_32bit_bck_serial32(struct leds_interface_i2s *i // start frame uint32_t start_frame = leds_interface_i2s_mode_start_frame(interface->mode); - if ((err = i2s_out_write_serial32(interface->i2s_out, &start_frame, 1))) { + if ((err = i2s_out_write_serial32(interface->i2s_out, &start_frame, 1, interface->options->timeout))) { LOG_ERROR("i2s_out_write_serial32"); return err; } @@ -22,7 +21,7 @@ static int leds_interface_i2s_tx_32bit_bck_serial32(struct leds_interface_i2s *i // 32-bit pixel data interface->func.i2s_mode_32bit(interface->buf->i2s_mode_32bit, pixels, i, limit); - if ((err = i2s_out_write_serial32(interface->i2s_out, interface->buf->i2s_mode_32bit, 1))) { + if ((err = i2s_out_write_serial32(interface->i2s_out, interface->buf->i2s_mode_32bit, 1, interface->options->timeout))) { LOG_ERROR("i2s_out_write_serial32"); return err; } @@ -39,7 +38,7 @@ static int leds_interface_i2s_tx_24bit_4x4_serial16(struct leds_interface_i2s *i // 6x16-bit pixel data interface->func.i2s_mode_24bit_4x4(interface->buf->i2s_mode_24bit_4x4, pixels, i, limit); - if ((err = i2s_out_write_serial16(interface->i2s_out, interface->buf->i2s_mode_24bit_4x4, 6))) { + if ((err = i2s_out_write_serial16(interface->i2s_out, interface->buf->i2s_mode_24bit_4x4, 6, interface->options->timeout))) { LOG_ERROR("i2s_out_write_serial16"); return err; } @@ -56,7 +55,7 @@ static int leds_interface_i2s_tx_32bit_4x4_serial16(struct leds_interface_i2s *i // 8x16-bit pixel data interface->func.i2s_mode_32bit_4x4(interface->buf->i2s_mode_32bit_4x4, pixels, i, limit); - if ((err = i2s_out_write_serial16(interface->i2s_out, interface->buf->i2s_mode_32bit_4x4, 8))) { + if ((err = i2s_out_write_serial16(interface->i2s_out, interface->buf->i2s_mode_32bit_4x4, 8, interface->options->timeout))) { LOG_ERROR("i2s_out_write_serial16"); return err; } @@ -74,7 +73,7 @@ static int leds_interface_i2s_tx_32bit_4x4_serial16(struct leds_interface_i2s *i // start frame uint32_t start_frame[8] = { [0 ... 7] = leds_interface_i2s_mode_start_frame(interface->mode) }; - if ((err = i2s_out_write_parallel8x32(interface->i2s_out, start_frame, 1))) { + if ((err = i2s_out_write_parallel8x32(interface->i2s_out, start_frame, 1, interface->options->timeout))) { LOG_ERROR("i2s_out_write_parallel8x32"); return err; } @@ -85,7 +84,7 @@ static int leds_interface_i2s_tx_32bit_4x4_serial16(struct leds_interface_i2s *i interface->func.i2s_mode_32bit(interface->buf->i2s_mode_32bit_parallel8[j], pixels, j * length + i, limit); } - if ((err = i2s_out_write_parallel8x32(interface->i2s_out, (uint32_t *) interface->buf->i2s_mode_32bit_parallel8, 1))) { + if ((err = i2s_out_write_parallel8x32(interface->i2s_out, (uint32_t *) interface->buf->i2s_mode_32bit_parallel8, 1, interface->options->timeout))) { LOG_ERROR("i2s_out_write_parallel8x32"); return err; } @@ -105,7 +104,7 @@ static int leds_interface_i2s_tx_32bit_4x4_serial16(struct leds_interface_i2s *i interface->func.i2s_mode_24bit_4x4(interface->buf->i2s_mode_24bit_4x4_parallel8[j], pixels, j * length + i, limit); } - if ((err = i2s_out_write_parallel8x16(interface->i2s_out, (uint16_t *) interface->buf->i2s_mode_24bit_4x4_parallel8, 6))) { + if ((err = i2s_out_write_parallel8x16(interface->i2s_out, (uint16_t *) interface->buf->i2s_mode_24bit_4x4_parallel8, 6, interface->options->timeout))) { LOG_ERROR("i2s_out_write_parallel8x16"); return err; } @@ -125,7 +124,7 @@ static int leds_interface_i2s_tx_32bit_4x4_serial16(struct leds_interface_i2s *i interface->func.i2s_mode_32bit_4x4(interface->buf->i2s_mode_32bit_4x4_parallel8[j], pixels, j * length + i, limit); } - if ((err = i2s_out_write_parallel8x16(interface->i2s_out, (uint16_t *) interface->buf->i2s_mode_32bit_4x4_parallel8, 8))) { + if ((err = i2s_out_write_parallel8x16(interface->i2s_out, (uint16_t *) interface->buf->i2s_mode_32bit_4x4_parallel8, 8, interface->options->timeout))) { LOG_ERROR("i2s_out_write_parallel8x16"); return err; } @@ -177,185 +176,48 @@ static int leds_interface_i2s_tx_write(struct leds_interface_i2s *interface, con } } -int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct leds_interface_i2s_options *options, enum leds_interface_i2s_mode mode, union leds_interface_i2s_func func, struct leds_interface_i2s_stats *stats) -{ - interface->mode = mode; - interface->func = func; - -#if LEDS_I2S_PARALLEL_ENABLED - interface->parallel = options->parallel; -#else - interface->parallel = 0; -#endif - interface->repeat = options->repeat; - - if (!(interface->buf = calloc(1, leds_interface_i2s_buf_size(interface->mode, interface->parallel)))) { - LOG_ERROR("calloc"); - return -1; - } - - interface->i2s_out = options->i2s_out; - interface->i2s_out_options = (struct i2s_out_options) { - // shared IO pins - .pin_mutex = options->pin_mutex, - .pin_timeout = options->pin_timeout, - }; - - switch(mode) { - case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: - #if LEDS_I2S_PARALLEL_ENABLED - if (options->parallel) { - interface->i2s_out_options.mode = I2S_OUT_MODE_8BIT_PARALLEL; - } else { - interface->i2s_out_options.mode = I2S_OUT_MODE_32BIT_SERIAL; - } - #else - interface->i2s_out_options.mode = I2S_OUT_MODE_32BIT_SERIAL; - #endif - - break; - - case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: - case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: - case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: - #if LEDS_I2S_PARALLEL_ENABLED - if (options->parallel) { - interface->i2s_out_options.mode = I2S_OUT_MODE_8BIT_PARALLEL; - } else { - // using 4x4bit -> 16-bit samples - interface->i2s_out_options.mode = I2S_OUT_MODE_16BIT_SERIAL; - } - #else - interface->i2s_out_options.mode = I2S_OUT_MODE_16BIT_SERIAL; - #endif - - break; - - default: - LOG_FATAL("unknown mode=%d", mode); - } - - switch(mode) { - case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: - interface->i2s_out_options.clock = i2s_out_clock(options->clock_rate); - - break; - - case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: - // 3.333MHz bit clock => 0.300us per I2S bit - // four I2S bits per 1.20us protocol bit - interface->i2s_out_options.clock = I2S_OUT_CLOCK_3M333; - - break; - - case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: - case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: - // 3.2MHz bit clock => 0.3125us per I2S bit - // four I2S bits per 1.25us protocol bit - interface->i2s_out_options.clock = I2S_OUT_CLOCK_3M2; - - break; - - default: - LOG_FATAL("unknown mode=%d", mode); - } - -#if LEDS_I2S_GPIO_PINS_ENABLED - for (int i = 0; i < LEDS_I2S_GPIO_PINS_SIZE; i++) { - interface->i2s_out_options.bck_gpios[i] = (i < options->gpio_pins_count) ? options->clock_pins[i] : GPIO_NUM_NC; - interface->i2s_out_options.data_gpios[i] = (i < options->gpio_pins_count) ? options->data_pins[i] : GPIO_NUM_NC; - interface->i2s_out_options.inv_data_gpios[i] = (i < options->gpio_pins_count) ? options->inv_data_pins[i] : GPIO_NUM_NC; - } -#endif - -#if LEDS_I2S_PARALLEL_ENABLED - // allow multiple copies of parallel data bits on different GPIOs - interface->i2s_out_options.parallel_data_bits = options->parallel; - - if (options->parallel) { - // I2S LCD mode requires the BCK/WS signal to be inverted when routed through the GPIO matrix - // invert BCK to idle high, transition on falling edge, and sample data on rising edge - interface->i2s_out_options.bck_inv = true; - } else { - // BCK is idle low, transition on falling edge, and sample data on rising edge - interface->i2s_out_options.bck_inv = false; - } -#endif - - interface->gpio = options->gpio; - interface->stats = stats; - - return 0; -} int leds_interface_i2s_tx(struct leds_interface_i2s *interface, const struct leds_color *pixels, unsigned count, const struct leds_limit *limit) { - int err; - - switch (interface->mode) { - case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: - // XXX: required to workaround I2S start glitch looping previous data bits - interface->i2s_out_options.eof_value = 0x00000000; - - // one clock cycle per pixel, min 32 cycles - interface->i2s_out_options.eof_count = (1 + count / 32); - - break; - - case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: - case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: - case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: - // 1.25us per 4-bit = 2.5us per byte * four bytes per I2S sample = 10us per 32-bit I2S sample - interface->i2s_out_options.eof_value = 0x00000000; - - // hold low for 8 * 10us - interface->i2s_out_options.eof_count = 8; - - break; - - default: - LOG_FATAL("unknown mode=%d", interface->mode); - } + bool setup = interface->i2s_out_setup; // sync setup() -> write -> flush -> close()? + int err = 0; - WITH_STATS_TIMER(&interface->stats->open) { - if ((err = i2s_out_open(interface->i2s_out, &interface->i2s_out_options))) { - LOG_ERROR("i2s_out_open"); + if (!setup) { + if ((err = leds_interface_i2s_setup(interface))) { + LOG_ERROR("leds_interface_i2s_setup"); return err; } } -#if CONFIG_LEDS_GPIO_ENABLED - leds_gpio_setup(&interface->gpio); -#endif - WITH_STATS_TIMER(&interface->stats->write) { if ((err = leds_interface_i2s_tx_write(interface, pixels, count, limit))) { goto error; } } - if (interface->repeat) { - if ((err = i2s_out_repeat(interface->i2s_out, interface->repeat))) { - LOG_ERROR("i2s_out_repeat"); - goto error; + if (!setup) { + // sync, wait for done before close + WITH_STATS_TIMER(&interface->stats->flush) { + if ((err = i2s_out_flush(interface->i2s_out, interface->options->timeout))) { + LOG_ERROR("i2s_out_flush"); + goto error; + } } - } - - WITH_STATS_TIMER(&interface->stats->flush) { - if ((err = i2s_out_flush(interface->i2s_out))) { - LOG_ERROR("i2s_out_flush"); - goto error; + } else { + // async, do not wait for done + WITH_STATS_TIMER(&interface->stats->start) { + if ((err = i2s_out_start(interface->i2s_out, interface->options->timeout))) { + LOG_ERROR("i2s_out_start"); + goto error; + } } } error: -#if CONFIG_LEDS_GPIO_ENABLED - leds_gpio_close(&interface->gpio); -#endif - - if ((err = i2s_out_close(interface->i2s_out))) { - LOG_ERROR("i2s_out_close"); - return err; + if (!setup) { + if (leds_interface_i2s_close(interface)) { + LOG_WARN("leds_interface_i2s_close"); + } } return err; diff --git a/components/leds/leds.c b/components/leds/leds.c index 353245a0..9e629c6d 100644 --- a/components/leds/leds.c +++ b/components/leds/leds.c @@ -163,32 +163,5 @@ int leds_tx(struct leds *leds) { leds_limit_update(leds); - switch (leds->options.interface) { - case LEDS_INTERFACE_NONE: - return 0; - - #if CONFIG_LEDS_SPI_ENABLED - case LEDS_INTERFACE_SPI: - return leds_interface_spi_tx(&leds->interface.spi, leds->pixels, leds->options.count, &leds->limit); - #endif - - #if CONFIG_LEDS_UART_ENABLED - case LEDS_INTERFACE_UART: - return leds_interface_uart_tx(&leds->interface.uart, leds->pixels, leds->options.count, &leds->limit); - #endif - - #if CONFIG_LEDS_I2S_ENABLED - # if LEDS_I2S_INTERFACE_COUNT > 0 - case LEDS_INTERFACE_I2S0: - # endif - # if LEDS_I2S_INTERFACE_COUNT > 1 - case LEDS_INTERFACE_I2S1: - # endif - return leds_interface_i2s_tx(&leds->interface.i2s, leds->pixels, leds->options.count, &leds->limit); - #endif - - default: - LOG_ERROR("unsupported interface=%#x", leds->options.interface); - return -1; - } + return leds_interface_tx(leds); } diff --git a/components/leds/stats.c b/components/leds/stats.c index 7bfa424a..1d4d9b90 100644 --- a/components/leds/stats.c +++ b/components/leds/stats.c @@ -24,6 +24,7 @@ void leds_reset_interface_stats() #if LEDS_I2S_INTERFACE_COUNT > 1 stats_timer_init(&leds_interface_stats.i2s1.open); stats_timer_init(&leds_interface_stats.i2s1.write); + stats_timer_init(&leds_interface_stats.i2s1.start); stats_timer_init(&leds_interface_stats.i2s1.flush); #endif } diff --git a/components/leds/test.c b/components/leds/test.c index 1c3db83a..60d521c3 100644 --- a/components/leds/test.c +++ b/components/leds/test.c @@ -8,7 +8,7 @@ #include -#define TEST_FRAME_RATE (25) +#define TEST_FRAME_RATE (30) #define TEST_FRAME_TICKS (1000 / TEST_FRAME_RATE / portTICK_RATE_MS) #define TEST_MODE_COLOR_FRAMES 25 diff --git a/components/logging/include/logging.h b/components/logging/include/logging.h index 9dc941ad..0a340fd2 100644 --- a/components/logging/include/logging.h +++ b/components/logging/include/logging.h @@ -13,21 +13,48 @@ #include #include +#ifdef ERROR + #define ISR_ERROR 1 +#else + #define ISR_ERROR 0 +#endif + +#ifdef WARN + #undef ISR_ERROR + + #define ISR_WARN 1 + #define ISR_ERROR 1 +#else + #define ISR_WARN 0 +#endif + #ifdef DEBUG #undef DEBUG + #undef ISR_WARN + #undef ISR_ERROR + #define DEBUG 1 + #define ISR_WARN 1 + #define ISR_ERROR 1 #else #define DEBUG 0 #endif +#ifdef TRACE + #undef TRACE + #define TRACE 1 +#else + #define TRACE 0 +#endif + #if CONFIG_LOG_COLORS #undef LOG_COLOR_D #define LOG_COLOR_D LOG_COLOR(LOG_COLOR_BLUE) #endif /* Bypass stdio buffering/interrupts, blocking write directly to UART */ -#define LOG_ISR_ERROR(fmt, ...) do { if (DEBUG) esp_rom_printf(LOG_FORMAT(E, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) -#define LOG_ISR_WARN(fmt, ...) do { if (DEBUG) esp_rom_printf(LOG_FORMAT(W, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) +#define LOG_ISR_ERROR(fmt, ...) do { if (ISR_ERROR) esp_rom_printf(LOG_FORMAT(E, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) +#define LOG_ISR_WARN(fmt, ...) do { if (ISR_WARN) esp_rom_printf(LOG_FORMAT(W, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) #define LOG_ISR_INFO(fmt, ...) do { if (DEBUG) esp_rom_printf(LOG_FORMAT(I, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) #define LOG_ISR_DEBUG(fmt, ...) do { if (DEBUG) esp_rom_printf(LOG_FORMAT(D, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) @@ -43,11 +70,9 @@ #define LOG_WARN(fmt, ...) do { fprintf(stderr, LOG_FORMAT(W, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) #define LOG_INFO(fmt, ...) do { fprintf(stderr, LOG_FORMAT(I, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) #define LOG_DEBUG(fmt, ...) do { if (DEBUG) fprintf(stderr, LOG_FORMAT(D, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) +#define LOG_TRACE(fmt, ...) do { if (TRACE) fprintf(stderr, LOG_FORMAT(D, fmt), esp_log_timestamp(), __func__, ##__VA_ARGS__); } while(0) -// XXX: CONFIG_LOG_DEFAULT_LEVEL defaults to skip ESP_LOG_DEBUG, and raising will include ALL debug output by default - not possible to override this per-call -#if DEBUG - #define LOG_DEBUG_BUFFER(buf, len) do { if (DEBUG) ESP_LOG_BUFFER_HEX_LEVEL(__func__, buf, len, ESP_LOG_INFO); } while(0) -#else - #define LOG_DEBUG_BUFFER(buf, len) -#endif +// XXX: use ESP_LOG_INFO because CONFIG_LOG_DEFAULT_LEVEL defaults to skip ESP_LOG_DEBUG, and raising will include ALL debug output by default - not possible to override this per-call #define LOG_INFO_BUFFER(buf, len) ESP_LOG_BUFFER_HEX_LEVEL(__func__, buf, len, ESP_LOG_INFO); +#define LOG_DEBUG_BUFFER(buf, len) do { if (DEBUG) ESP_LOG_BUFFER_HEX_LEVEL(__func__, buf, len, ESP_LOG_INFO); } while(0) +#define LOG_TRACE_BUFFER(buf, len) do { if (TRACE) ESP_LOG_BUFFER_HEX_LEVEL(__func__, buf, len, ESP_LOG_INFO); } while(0) diff --git a/components/usb_pd_sink/stusb4500_nvm.c b/components/usb_pd_sink/stusb4500_nvm.c index cfe6a3ff..9ec1f34f 100644 --- a/components/usb_pd_sink/stusb4500_nvm.c +++ b/components/usb_pd_sink/stusb4500_nvm.c @@ -4,8 +4,6 @@ #include -#define DEBUG - #include static int stusb4500_nvm_unlock(struct stusb4500 *stusb4500) diff --git a/components/user_leds/gpio.c b/components/user_leds/gpio.c index 84f2ee49..c5413de8 100644 --- a/components/user_leds/gpio.c +++ b/components/user_leds/gpio.c @@ -1,3 +1,5 @@ +#define ERROR + #include #include "user_leds.h" @@ -28,7 +30,7 @@ static IRAM_ATTR void user_leds_gpio_interrupt(gpio_pins_t pins, void *arg) } if (!xEventGroupSetBitsFromISR(leds->event_group, USER_LEDS_EVENT_BIT(led->index), &xHigherPriorityTaskWoken)) { - LOG_ISR_WARN("xEventGroupSetBitsFromISR"); + LOG_ISR_ERROR("xEventGroupSetBitsFromISR"); } } diff --git a/main/leds.c b/main/leds.c index 015707af..5ab11e71 100644 --- a/main/leds.c +++ b/main/leds.c @@ -149,6 +149,61 @@ int start_leds() return 0; } +int setup_leds(struct leds_state *state) +{ + int err; + + if (!state->leds) { + LOG_ERROR("leds%d: not initialized", state->index + 1); + return -1; + } + + if (!state->config->interface_setup) { + return 0; + } + + LOG_INFO("Setup LEDS interface in async mode"); + + if ((err = leds_interface_setup(state->leds))) { + LOG_ERROR("leds_interface_setup"); + return err; + } + + return 0; +} + +int reset_leds(struct leds_state *state) +{ + int err; + + if (!state->leds) { + LOG_ERROR("leds%d: not initialized", state->index + 1); + return -1; + } + + if (leds_is_interface_setup(state->leds)) { + LOG_WARN("Reset LEDS interface"); + + if ((err = leds_interface_reset(state->leds))) { + // crash and restart + LOG_FATAL("leds_interface_reset"); + return err; + } + } + + if (state->config->interface_setup) { + LOG_WARN("Setup LEDS interface"); + + if ((err = leds_interface_setup(state->leds))) { + // crash and restart + LOG_FATAL("leds_interface_setup"); + return err; + } + } + + return 0; +} + int check_leds_interface(struct leds_state *state) { if (!state->leds) { diff --git a/main/leds_cmd.c b/main/leds_cmd.c index da36a16c..8253d7ea 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -368,12 +368,14 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) #if LEDS_I2S_INTERFACE_COUNT > 0 print_stats_timer("i2s0", "open", &stats.i2s0.open); print_stats_timer("i2s0", "write", &stats.i2s0.write); + print_stats_timer("i2s0", "start", &stats.i2s0.start); print_stats_timer("i2s0", "flush", &stats.i2s0.flush); printf("\n"); #endif #if LEDS_I2S_INTERFACE_COUNT > 1 print_stats_timer("i2s1", "open", &stats.i2s1.open); print_stats_timer("i2s1", "write", &stats.i2s1.write); + print_stats_timer("i2s1", "start", &stats.i2s1.start); print_stats_timer("i2s1", "flush", &stats.i2s1.flush); printf("\n"); #endif diff --git a/main/leds_config.h b/main/leds_config.h index 869ee1dc..25733088 100644 --- a/main/leds_config.h +++ b/main/leds_config.h @@ -110,7 +110,10 @@ struct leds_config { bool enabled; int interface; + bool interface_setup; + int protocol; + uint16_t count; uint16_t limit_total, limit_group; uint16_t limit_groups; diff --git a/main/leds_configtab.i b/main/leds_configtab.i index 400091d8..66cae892 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -13,6 +13,10 @@ const struct configtab LEDS_CONFIGTAB[] = { ), .enum_type = { .value = &LEDS_CONFIG.interface, .values = leds_interface_enum }, }, + { CONFIG_TYPE_BOOL, "interface_setup", + .description = "Setup dedicated interface, operate in async mode. I2S only.", + .bool_type = { .value = &LEDS_CONFIG.interface_setup, .default_value = true }, + }, { CONFIG_TYPE_ENUM, "protocol", .enum_type = { .value = &LEDS_CONFIG.protocol, .values = leds_protocol_enum }, }, @@ -73,15 +77,6 @@ const struct configtab LEDS_CONFIGTAB[] = { .description = "Output I2S bit rate. Only used for protocols with a separate clock/data.", .enum_type = { .value = &LEDS_CONFIG.i2s_clock, .values = leds_i2s_clock_enum, .default_value = I2S_CLOCK_DEFAULT }, }, - { CONFIG_TYPE_UINT16, "i2s_data_copies", - .description = ( - "Output multiple copies of the data on each I2S output, to control a series of identical LEDs.\n" - "\tFor example, use count = 600, i2s_data_width = 4, i2s_data_copies = 4 to control 4 sets of 150 LEDs on each of four outputs." - "Each output will be independently controllable, but each set of 150 LEDs per output will behave identically.\n" - "\t0 -> disabled, 1 -> no effect, N -> repeat data for a total of N copies per output." - ), - .uint16_type = { .value = &LEDS_CONFIG.i2s_data_copies, .max = LEDS_I2S_REPEAT_MAX }, - }, # if LEDS_I2S_PARALLEL_ENABLED { CONFIG_TYPE_UINT16, "i2s_data_width", .description = ( @@ -95,6 +90,15 @@ const struct configtab LEDS_CONFIGTAB[] = { .ctx = &LEDS_CONFIG, }, # endif + { CONFIG_TYPE_UINT16, "i2s_data_copies", + .description = ( + "Output multiple copies of the data on each I2S output, to control a series of identical LEDs.\n" + "\tFor example, use count = 600, i2s_data_width = 4, i2s_data_copies = 4 to control 4 sets of 150 LEDs on each of four outputs." + "Each output will be independently controllable, but each set of 150 LEDs per output will behave identically.\n" + "\t0 -> disabled, 1 -> no effect, N -> repeat data for a total of N copies per output." + ), + .uint16_type = { .value = &LEDS_CONFIG.i2s_data_copies, .max = LEDS_I2S_REPEAT_MAX }, + }, # if LEDS_I2S_GPIO_PINS_ENABLED { CONFIG_TYPE_UINT16, "i2s_clock_pin", .description = "Output I2S bit clock to GPIO pin. Only used for protocols with a separate clock/data.", diff --git a/main/leds_i2s.c b/main/leds_i2s.c index dc6c44b2..26c81eef 100644 --- a/main/leds_i2s.c +++ b/main/leds_i2s.c @@ -6,6 +6,9 @@ #include +// generic timeout for all i2s_out interface +#define LEDS_I2S_INTERFACE_TIMEOUT (1000 / portTICK_RATE_MS) + // do not block if pin in use, timeout immediately #define LEDS_I2S_PIN_TIMEOUT portMAX_DELAY @@ -121,6 +124,7 @@ } options->i2s_out = leds_i2s_out[port]; + options->timeout = LEDS_I2S_INTERFACE_TIMEOUT; #if CONFIG_IDF_TARGET_ESP8266 // TODO: use i2s_pin_mutex for arbitrary gpio pins with LEDS_I2S_GPIO_PINS_ENABLED? options->pin_mutex = pin_mutex[PIN_MUTEX_I2S0_DATA]; // shared with console uart0 diff --git a/main/leds_state.h b/main/leds_state.h index 67435d2b..5a205e3e 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -51,6 +51,16 @@ extern struct leds_state leds_states[LEDS_COUNT]; # endif #endif +/* + * Optional persistent leds interface setup. + */ +int setup_leds(struct leds_state *state); + +/* + * Reset leds interface setup if necessary. + */ +int reset_leds(struct leds_state *state); + /* * Update LEDs output with given USER_ACTIVITY_LEDS_* source. */ diff --git a/main/leds_task.c b/main/leds_task.c index 5b2eb2a7..21ead1ff 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -102,6 +102,11 @@ static void leds_main(void *ctx) struct leds_state *state = ctx; struct leds_stats *stats = &leds_stats[state->index]; + if (setup_leds(state)) { + LOG_ERROR("setup_leds"); + goto error; + } + for(struct stats_timer_sample loop_sample;; stats_timer_stop(&stats->loop, &loop_sample)) { EventBits_t event_bits = leds_task_wait(state); enum user_activity update_activity = 0; @@ -160,11 +165,18 @@ static void leds_main(void *ctx) WITH_STATS_TIMER(&stats->update) { if (update_leds(state, update_activity)) { LOG_WARN("leds%d: update_leds", state->index + 1); - continue; + user_alert(USER_ALERT_ERROR_LEDS); + reset_leds(state); } } } } + +error: + user_alert(USER_ALERT_ERROR_LEDS); + LOG_ERROR("task=%p stopped", state->task); + state->task = NULL; + vTaskDelete(NULL); } int init_leds_task(struct leds_state *state, const struct leds_config *config) diff --git a/main/user.c b/main/user.c index 3bc6f944..232bfb97 100644 --- a/main/user.c +++ b/main/user.c @@ -67,7 +67,8 @@ const char *user_alert_str(enum user_alert alert) case USER_ALERT_ERROR_WIFI: return "ERROR_WIFI"; case USER_ALERT_ERROR_START: return "ERROR_START"; case USER_ALERT_ERROR_DMX: return "ERROR_DMX"; - + + case USER_ALERT_ERROR_LEDS: return "ERROR_LEDS"; case USER_ALERT_ERROR_LEDS_SEQUENCE: return "ERROR_LEDS_SEQUENCE"; case USER_ALERT_ERROR_LEDS_SEQUENCE_READ: return "ERROR_LEDS_SEQUENCE_READ"; case USER_ALERT_ERROR_ATX_PSU_TIMEOUT: return "ERROR_ATX_PSU_TIMEOUT"; diff --git a/main/user.h b/main/user.h index 511d5d66..d4d115f1 100644 --- a/main/user.h +++ b/main/user.h @@ -48,6 +48,7 @@ enum user_alert { USER_ALERT_ERROR_WIFI, USER_ALERT_ERROR_START, USER_ALERT_ERROR_DMX, + USER_ALERT_ERROR_LEDS, USER_ALERT_ERROR_LEDS_SEQUENCE, USER_ALERT_ERROR_LEDS_SEQUENCE_READ, USER_ALERT_ERROR_ATX_PSU_TIMEOUT,