feat(lcd): support buffer switch interrupt

This commit is contained in:
Chen Jichang
2026-06-01 18:28:50 +08:00
parent 3e24f276fa
commit f17bc5d033
34 changed files with 417 additions and 119 deletions
@@ -63,6 +63,7 @@ typedef bool (*gdma_event_callback_t)(gdma_channel_handle_t dma_chan, gdma_event
typedef struct {
gdma_event_callback_t on_trans_eof; /*!< Invoked when TX engine meets EOF descriptor */
gdma_event_callback_t on_descr_err; /*!< Invoked when DMA encounters a descriptor error */
gdma_event_callback_t on_link_switch; /*!< Invoked when TX link list switches to a new descriptor chain */
} gdma_tx_event_callbacks_t;
/**
@@ -305,6 +306,26 @@ esp_err_t gdma_get_group_channel_id(gdma_channel_handle_t dma_chan, int *group_i
*/
esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_tx_event_callbacks_t *cbs, void *user_data);
/**
* @brief Request an interrupt when the TX channel switches to a new descriptor chain
*
* @note This API is only available on targets that support TX link switch interrupt.
* @note Register the `on_link_switch` callback by `gdma_register_tx_event_callbacks()` before calling this API.
* @note The TX EOF event indicates that GDMA has reached an EOF descriptor. By contrast, the TX link switch
* event indicates that GDMA has started using the next descriptor chain after a link update.
* GDMA can prefetch descriptors from the updated link before the EOF event, so EOF doesn't always mean
* the previous descriptor chain is no longer referenced by GDMA. The link switch event is needed when
* the caller must know that the previous descriptor chain can be reused safely.
*
* @param[in] dma_tx_chan GDMA TX channel handle
* @return
* - ESP_OK: Request link switch event successfully
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NOT_SUPPORTED: Link switch event is not supported
* - ESP_ERR_INVALID_STATE: Link switch callback is not registered
*/
esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan);
/**
* @brief Set GDMA event callbacks for RX channel
* @note This API will install GDMA interrupt service for the channel internally
+4
View File
@@ -20,24 +20,28 @@ entries:
gdma_hal_top: gdma_hal_clear_intr (noflash)
gdma_hal_top: gdma_hal_read_intr_status (noflash)
gdma_hal_top: gdma_hal_get_eof_desc_addr (noflash)
gdma_hal_top: gdma_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AHB-DMA version 1
if SOC_AHB_GDMA_VERSION = 1:
gdma_hal_ahb_v1: gdma_ahb_hal_clear_intr (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_read_intr_status (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_get_eof_desc_addr (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AHB-DMA version 2
if SOC_AHB_GDMA_VERSION = 2:
gdma_hal_ahb_v2: gdma_ahb_hal_clear_intr (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_read_intr_status (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_get_eof_desc_addr (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AXI-DMA
if SOC_AXI_GDMA_SUPPORTED = y:
gdma_hal_axi: gdma_axi_hal_clear_intr (noflash)
gdma_hal_axi: gdma_axi_hal_read_intr_status (noflash)
gdma_hal_axi: gdma_axi_hal_get_eof_desc_addr (noflash)
gdma_hal_axi: gdma_axi_hal_is_tx_link_switch_event_supported (noflash)
# put GDMA control HAL functions in IRAM
if GDMA_CTRL_FUNC_IN_IRAM = y:
+52 -4
View File
@@ -550,6 +550,12 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
gdma_group_t *group = pair->group;
gdma_hal_context_t *hal = &group->hal;
gdma_tx_channel_t *tx_chan = __containerof(dma_chan, gdma_tx_channel_t, base);
bool link_switch_event_supported = gdma_hal_is_tx_link_switch_event_supported(hal);
if (cbs->on_link_switch) {
ESP_RETURN_ON_FALSE(link_switch_event_supported,
ESP_ERR_NOT_SUPPORTED, TAG, "on_link_switch not supported");
}
if (dma_chan->flags.isr_cache_safe) {
if (cbs->on_trans_eof) {
@@ -560,6 +566,10 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_descr_err), ESP_ERR_INVALID_ARG,
TAG, "on_descr_err not in IRAM");
}
if (cbs->on_link_switch) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_link_switch), ESP_ERR_INVALID_ARG,
TAG, "on_link_switch not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG,
TAG, "user context not in internal RAM");
@@ -571,8 +581,16 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
// enable/disable GDMA interrupt events for TX channel
esp_os_enter_critical(&pair->spinlock);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF, cbs->on_trans_eof != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR, cbs->on_descr_err != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF,
cbs->on_trans_eof != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR,
cbs->on_descr_err != NULL);
#if GDMA_LL_EVENT_TX_LINK_SWITCH
if (link_switch_event_supported) {
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_LINK_SWITCH,
cbs->on_link_switch != NULL);
}
#endif // GDMA_LL_EVENT_TX_LINK_SWITCH
esp_os_exit_critical(&pair->spinlock);
memcpy(&tx_chan->cbs, cbs, sizeof(gdma_tx_event_callbacks_t));
@@ -703,6 +721,30 @@ esp_err_t gdma_reset(gdma_channel_handle_t dma_chan)
return ESP_OK;
}
esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan)
{
if (!dma_tx_chan || dma_tx_chan->direction != GDMA_CHANNEL_DIRECTION_TX) {
return ESP_ERR_INVALID_ARG;
}
gdma_pair_t *pair = dma_tx_chan->pair;
gdma_group_t *group = pair->group;
gdma_hal_context_t *hal = &group->hal;
gdma_tx_channel_t *tx_chan = __containerof(dma_tx_chan, gdma_tx_channel_t, base);
if (!gdma_hal_is_tx_link_switch_event_supported(hal)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!tx_chan->cbs.on_link_switch) {
return ESP_ERR_INVALID_STATE;
}
esp_os_enter_critical_safe(&dma_tx_chan->spinlock);
gdma_hal_request_link_switch_event(hal, pair->pair_id, dma_tx_chan->direction);
esp_os_exit_critical_safe(&dma_tx_chan->spinlock);
return ESP_OK;
}
static void gdma_try_free_group_handle(gdma_group_t *group)
{
int group_id = group->group_id;
@@ -1000,6 +1042,12 @@ void gdma_default_tx_isr(void *args)
if ((intr_status & GDMA_LL_EVENT_TX_DESC_ERROR) && tx_chan->cbs.on_descr_err) {
need_yield |= tx_chan->cbs.on_descr_err(&tx_chan->base, NULL, tx_chan->user_data);
}
#if GDMA_LL_EVENT_TX_LINK_SWITCH
if (gdma_hal_is_tx_link_switch_event_supported(hal) &&
(intr_status & GDMA_LL_EVENT_TX_LINK_SWITCH) && tx_chan->cbs.on_link_switch) {
need_yield |= tx_chan->cbs.on_link_switch(&tx_chan->base, NULL, tx_chan->user_data);
}
#endif // GDMA_LL_EVENT_TX_LINK_SWITCH
if (need_yield) {
portYIELD_FROM_ISR();
}
@@ -1031,7 +1079,7 @@ static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan)
.source = pair_signals->rx_irq_id,
.flags = isr_flags,
.intrstatusreg = gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX),
.intrstatusmask = GDMA_LL_RX_EVENT_MASK,
.intrstatusmask = hal->priv_data->rx_event_mask,
.handler = gdma_default_rx_isr,
.arg = rx_chan,
.bind_by.name = tx_rx_share_irq ? pair_signals->name : NULL,
@@ -1077,7 +1125,7 @@ static esp_err_t gdma_install_tx_interrupt(gdma_tx_channel_t *tx_chan)
.source = pair_signals->tx_irq_id,
.flags = isr_flags,
.intrstatusreg = gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX),
.intrstatusmask = GDMA_LL_TX_EVENT_MASK,
.intrstatusmask = hal->priv_data->tx_event_mask,
.handler = gdma_default_tx_isr,
.arg = tx_chan,
.bind_by.name = tx_rx_share_irq ? pair_signals->name : NULL,