diff --git a/components/driver/.build-test-rules.yml b/components/driver/.build-test-rules.yml index 591d6256a27..7aefd1f0abd 100644 --- a/components/driver/.build-test-rules.yml +++ b/components/driver/.build-test-rules.yml @@ -64,6 +64,10 @@ components/driver/test_apps/mcpwm: disable: - if: SOC_MCPWM_SUPPORTED != 1 +components/driver/test_apps/parlio: + disable: + - if: SOC_PARLIO_SUPPORTED != 1 + components/driver/test_apps/pulse_cnt: disable: - if: SOC_PCNT_SUPPORTED != 1 diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index f1e499a2785..5599e9a6f88 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -17,6 +17,7 @@ set(includes "include" "i2s/include" "ledc/include" "mcpwm/include" + "parlio/include" "pcnt/include" "rmt/include" "sdio_slave/include" @@ -49,6 +50,11 @@ if(CONFIG_SOC_DAC_SUPPORTED) "deprecated/${target}/dac_legacy.c") endif() +# Parallel IO related source files +if(CONFIG_SOC_PARLIO_SUPPORTED) + list(APPEND srcs "parlio/parlio_common.c" "parlio/parlio_tx.c") +endif() + # GPIO related source files if(CONFIG_SOC_DEDICATED_GPIO_SUPPORTED) list(APPEND srcs "gpio/dedic_gpio.c") diff --git a/components/driver/Kconfig b/components/driver/Kconfig index b3985314f6c..72a12ef862b 100644 --- a/components/driver/Kconfig +++ b/components/driver/Kconfig @@ -478,4 +478,24 @@ menu "Driver Configurations" USB Serial/JTAG is in use. endmenu # USB Serial/JTAG Configuration + menu "Parallel IO Configuration" + depends on SOC_PARLIO_SUPPORTED + + config PARLIO_ENABLE_DEBUG_LOG + bool "Enable debug log" + default n + help + Wether to enable the debug log message for parallel IO driver. + Note that, this option only controls the parallel IO driver log, won't affect other drivers. + + config PARLIO_ISR_IRAM_SAFE + bool "Parallel IO ISR IRAM-Safe" + default n + select GDMA_CTRL_FUNC_IN_IRAM # the driver needs to start the GDMA in the interrupt + help + Ensure the Parallel IO interrupt is IRAM-Safe by allowing the interrupt handler to be + executable when the cache is disabled (e.g. SPI Flash write). + + endmenu # Parallel IO Configuration + endmenu # Driver configurations diff --git a/components/driver/gptimer/gptimer.c b/components/driver/gptimer/gptimer.c index f58531c2fd2..2e30850431c 100644 --- a/components/driver/gptimer/gptimer.c +++ b/components/driver/gptimer/gptimer.c @@ -87,7 +87,7 @@ static void gptimer_unregister_from_group(gptimer_t *timer) gptimer_release_group_handle(group); } -static esp_err_t gptimer_destory(gptimer_t *timer) +static esp_err_t gptimer_destroy(gptimer_t *timer) { if (timer->pm_lock) { ESP_RETURN_ON_ERROR(esp_pm_lock_delete(timer->pm_lock), TAG, "delete pm_lock failed"); @@ -145,7 +145,7 @@ esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *re err: if (timer) { - gptimer_destory(timer); + gptimer_destroy(timer); } return ret; } @@ -161,7 +161,7 @@ esp_err_t gptimer_del_timer(gptimer_handle_t timer) ESP_LOGD(TAG, "del timer (%d,%d)", group_id, timer_id); timer_hal_deinit(&timer->hal); // recycle memory resource - ESP_RETURN_ON_ERROR(gptimer_destory(timer), TAG, "destory gptimer failed"); + ESP_RETURN_ON_ERROR(gptimer_destroy(timer), TAG, "destroy gptimer failed"); switch (clk_src) { #if SOC_TIMER_GROUP_SUPPORT_RC_FAST diff --git a/components/driver/mcpwm/mcpwm_cap.c b/components/driver/mcpwm/mcpwm_cap.c index 97f29682c53..8ff7910bd9b 100644 --- a/components/driver/mcpwm/mcpwm_cap.c +++ b/components/driver/mcpwm/mcpwm_cap.c @@ -66,7 +66,7 @@ static void mcpwm_cap_timer_unregister_from_group(mcpwm_cap_timer_t *cap_timer) mcpwm_release_group_handle(group); } -static esp_err_t mcpwm_cap_timer_destory(mcpwm_cap_timer_t *cap_timer) +static esp_err_t mcpwm_cap_timer_destroy(mcpwm_cap_timer_t *cap_timer) { if (cap_timer->pm_lock) { ESP_RETURN_ON_ERROR(esp_pm_lock_delete(cap_timer->pm_lock), TAG, "delete pm_lock failed"); @@ -124,7 +124,7 @@ esp_err_t mcpwm_new_capture_timer(const mcpwm_capture_timer_config_t *config, mc err: if (cap_timer) { - mcpwm_cap_timer_destory(cap_timer); + mcpwm_cap_timer_destroy(cap_timer); } return ret; } @@ -140,7 +140,7 @@ esp_err_t mcpwm_del_capture_timer(mcpwm_cap_timer_handle_t cap_timer) ESP_LOGD(TAG, "del capture timer in group %d", group->group_id); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_cap_timer_destory(cap_timer), TAG, "destory capture timer failed"); + ESP_RETURN_ON_ERROR(mcpwm_cap_timer_destroy(cap_timer), TAG, "destroy capture timer failed"); return ESP_OK; } @@ -228,7 +228,7 @@ static void mcpwm_capture_channel_unregister_from_timer(mcpwm_cap_channel_t *cap portEXIT_CRITICAL(&cap_timer->spinlock); } -static esp_err_t mcpwm_capture_channel_destory(mcpwm_cap_channel_t *cap_chan) +static esp_err_t mcpwm_capture_channel_destroy(mcpwm_cap_channel_t *cap_chan) { if (cap_chan->intr) { ESP_RETURN_ON_ERROR(esp_intr_free(cap_chan->intr), TAG, "delete interrupt service failed"); @@ -282,7 +282,7 @@ esp_err_t mcpwm_new_capture_channel(mcpwm_cap_timer_handle_t cap_timer, const mc return ESP_OK; err: if (cap_chan) { - mcpwm_capture_channel_destory(cap_chan); + mcpwm_capture_channel_destroy(cap_chan); } return ret; } @@ -307,7 +307,7 @@ esp_err_t mcpwm_del_capture_channel(mcpwm_cap_channel_handle_t cap_channel) portEXIT_CRITICAL(&group->spinlock); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_capture_channel_destory(cap_channel), TAG, "destory capture channel failed"); + ESP_RETURN_ON_ERROR(mcpwm_capture_channel_destroy(cap_channel), TAG, "destroy capture channel failed"); return ESP_OK; } diff --git a/components/driver/mcpwm/mcpwm_cmpr.c b/components/driver/mcpwm/mcpwm_cmpr.c index 29c35003a55..fd0c1fd061c 100644 --- a/components/driver/mcpwm/mcpwm_cmpr.c +++ b/components/driver/mcpwm/mcpwm_cmpr.c @@ -58,7 +58,7 @@ static void mcpwm_comparator_unregister_from_operator(mcpwm_cmpr_t *cmpr) portEXIT_CRITICAL(&oper->spinlock); } -static esp_err_t mcpwm_comparator_destory(mcpwm_cmpr_t *cmpr) +static esp_err_t mcpwm_comparator_destroy(mcpwm_cmpr_t *cmpr) { if (cmpr->intr) { ESP_RETURN_ON_ERROR(esp_intr_free(cmpr->intr), TAG, "uninstall interrupt service failed"); @@ -97,7 +97,7 @@ esp_err_t mcpwm_new_comparator(mcpwm_oper_handle_t oper, const mcpwm_comparator_ err: if (cmpr) { - mcpwm_comparator_destory(cmpr); + mcpwm_comparator_destroy(cmpr); } return ret; } @@ -118,7 +118,7 @@ esp_err_t mcpwm_del_comparator(mcpwm_cmpr_handle_t cmpr) ESP_LOGD(TAG, "del comparator (%d,%d,%d)", group->group_id, oper_id, cmpr_id); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_comparator_destory(cmpr), TAG, "destory comparator failed"); + ESP_RETURN_ON_ERROR(mcpwm_comparator_destroy(cmpr), TAG, "destroy comparator failed"); return ESP_OK; } diff --git a/components/driver/mcpwm/mcpwm_fault.c b/components/driver/mcpwm/mcpwm_fault.c index f659b0909f9..4ec4b13ca31 100644 --- a/components/driver/mcpwm/mcpwm_fault.c +++ b/components/driver/mcpwm/mcpwm_fault.c @@ -71,7 +71,7 @@ static void mcpwm_gpio_fault_unregister_from_group(mcpwm_gpio_fault_t *fault) mcpwm_release_group_handle(group); } -static esp_err_t mcpwm_gpio_fault_destory(mcpwm_gpio_fault_t *fault) +static esp_err_t mcpwm_gpio_fault_destroy(mcpwm_gpio_fault_t *fault) { if (fault->intr) { ESP_RETURN_ON_ERROR(esp_intr_free(fault->intr), TAG, "uninstall interrupt service failed"); @@ -133,7 +133,7 @@ esp_err_t mcpwm_new_gpio_fault(const mcpwm_gpio_fault_config_t *config, mcpwm_fa err: if (fault) { - mcpwm_gpio_fault_destory(fault); + mcpwm_gpio_fault_destroy(fault); } return ret; } @@ -157,7 +157,7 @@ static esp_err_t mcpwm_del_gpio_fault(mcpwm_fault_handle_t fault) mcpwm_ll_fault_enable_detection(hal->dev, fault_id, false); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_gpio_fault_destory(gpio_fault), TAG, "destory GPIO fault failed"); + ESP_RETURN_ON_ERROR(mcpwm_gpio_fault_destroy(gpio_fault), TAG, "destroy GPIO fault failed"); return ESP_OK; } diff --git a/components/driver/mcpwm/mcpwm_gen.c b/components/driver/mcpwm/mcpwm_gen.c index e3678169a3c..fdd57c08edf 100644 --- a/components/driver/mcpwm/mcpwm_gen.c +++ b/components/driver/mcpwm/mcpwm_gen.c @@ -56,7 +56,7 @@ static void mcpwm_generator_unregister_from_operator(mcpwm_gen_t *gen) portEXIT_CRITICAL(&oper->spinlock); } -static esp_err_t mcpwm_generator_destory(mcpwm_gen_t *gen) +static esp_err_t mcpwm_generator_destroy(mcpwm_gen_t *gen) { if (gen->oper) { mcpwm_generator_unregister_from_operator(gen); @@ -105,7 +105,7 @@ esp_err_t mcpwm_new_generator(mcpwm_oper_handle_t oper, const mcpwm_generator_co err: if (gen) { - mcpwm_generator_destory(gen); + mcpwm_generator_destroy(gen); } return ret; } @@ -118,7 +118,7 @@ esp_err_t mcpwm_del_generator(mcpwm_gen_handle_t gen) ESP_LOGD(TAG, "del generator (%d,%d,%d)", group->group_id, oper->oper_id, gen->gen_id); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_generator_destory(gen), TAG, "destory generator failed"); + ESP_RETURN_ON_ERROR(mcpwm_generator_destroy(gen), TAG, "destroy generator failed"); return ESP_OK; } diff --git a/components/driver/mcpwm/mcpwm_oper.c b/components/driver/mcpwm/mcpwm_oper.c index a01ccb033f5..2053f4b81ef 100644 --- a/components/driver/mcpwm/mcpwm_oper.c +++ b/components/driver/mcpwm/mcpwm_oper.c @@ -68,7 +68,7 @@ static void mcpwm_operator_unregister_from_group(mcpwm_oper_t *oper) mcpwm_release_group_handle(group); } -static esp_err_t mcpwm_operator_destory(mcpwm_oper_t *oper) +static esp_err_t mcpwm_operator_destroy(mcpwm_oper_t *oper) { if (oper->intr) { ESP_RETURN_ON_ERROR(esp_intr_free(oper->intr), TAG, "uninstall interrupt service failed"); @@ -123,7 +123,7 @@ esp_err_t mcpwm_new_operator(const mcpwm_operator_config_t *config, mcpwm_oper_h err: if (oper) { - mcpwm_operator_destory(oper); + mcpwm_operator_destroy(oper); } return ret; } @@ -149,7 +149,7 @@ esp_err_t mcpwm_del_operator(mcpwm_oper_handle_t oper) ESP_LOGD(TAG, "del operator (%d,%d)", group->group_id, oper_id); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_operator_destory(oper), TAG, "destory operator failed"); + ESP_RETURN_ON_ERROR(mcpwm_operator_destroy(oper), TAG, "destroy operator failed"); return ESP_OK; } diff --git a/components/driver/mcpwm/mcpwm_sync.c b/components/driver/mcpwm/mcpwm_sync.c index c8b7e1bcc3a..dede456adc3 100644 --- a/components/driver/mcpwm/mcpwm_sync.c +++ b/components/driver/mcpwm/mcpwm_sync.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -57,7 +57,7 @@ static void mcpwm_timer_sync_src_unregister_from_timer(mcpwm_timer_sync_src_t *t portEXIT_CRITICAL(&timer->spinlock); } -static esp_err_t mcpwm_timer_sync_src_destory(mcpwm_timer_sync_src_t *timer_sync_src) +static esp_err_t mcpwm_timer_sync_src_destroy(mcpwm_timer_sync_src_t *timer_sync_src) { if (timer_sync_src->timer) { mcpwm_timer_sync_src_unregister_from_timer(timer_sync_src); @@ -104,7 +104,7 @@ esp_err_t mcpwm_new_timer_sync_src(mcpwm_timer_handle_t timer, const mcpwm_timer err: if (timer_sync_src) { - mcpwm_timer_sync_src_destory(timer_sync_src); + mcpwm_timer_sync_src_destroy(timer_sync_src); } return ret; } @@ -118,7 +118,7 @@ static esp_err_t mcpwm_del_timer_sync_src(mcpwm_sync_t *sync_src) mcpwm_ll_timer_disable_sync_out(group->hal.dev, timer_id); ESP_LOGD(TAG, "del timer sync_src in timer (%d,%d)", group->group_id, timer_id); - ESP_RETURN_ON_ERROR(mcpwm_timer_sync_src_destory(timer_sync_src), TAG, "destory timer sync_src failed"); + ESP_RETURN_ON_ERROR(mcpwm_timer_sync_src_destroy(timer_sync_src), TAG, "destroy timer sync_src failed"); return ESP_OK; } @@ -163,7 +163,7 @@ static void mcpwm_gpio_sync_src_unregister_from_group(mcpwm_gpio_sync_src_t *gpi mcpwm_release_group_handle(group); } -static esp_err_t mcpwm_gpio_sync_src_destory(mcpwm_gpio_sync_src_t *gpio_sync_src) +static esp_err_t mcpwm_gpio_sync_src_destroy(mcpwm_gpio_sync_src_t *gpio_sync_src) { if (gpio_sync_src->base.group) { mcpwm_gpio_sync_src_unregister_from_group(gpio_sync_src); @@ -217,7 +217,7 @@ esp_err_t mcpwm_new_gpio_sync_src(const mcpwm_gpio_sync_src_config_t *config, mc err: if (gpio_sync_src) { - mcpwm_gpio_sync_src_destory(gpio_sync_src); + mcpwm_gpio_sync_src_destroy(gpio_sync_src); } return ret; } @@ -231,7 +231,7 @@ static esp_err_t mcpwm_del_gpio_sync_src(mcpwm_sync_t *sync_src) gpio_reset_pin(gpio_sync_src->gpio_num); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_gpio_sync_src_destory(gpio_sync_src), TAG, "destory GPIO sync_src failed"); + ESP_RETURN_ON_ERROR(mcpwm_gpio_sync_src_destroy(gpio_sync_src), TAG, "destroy GPIO sync_src failed"); return ESP_OK; } diff --git a/components/driver/mcpwm/mcpwm_timer.c b/components/driver/mcpwm/mcpwm_timer.c index e9e9d0dd4f1..710b0716f5e 100644 --- a/components/driver/mcpwm/mcpwm_timer.c +++ b/components/driver/mcpwm/mcpwm_timer.c @@ -69,7 +69,7 @@ static void mcpwm_timer_unregister_from_group(mcpwm_timer_t *timer) mcpwm_release_group_handle(group); } -static esp_err_t mcpwm_timer_destory(mcpwm_timer_t *timer) +static esp_err_t mcpwm_timer_destroy(mcpwm_timer_t *timer) { if (timer->intr) { ESP_RETURN_ON_ERROR(esp_intr_free(timer->intr), TAG, "uninstall interrupt service failed"); @@ -136,7 +136,7 @@ esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle err: if (timer) { - mcpwm_timer_destory(timer); + mcpwm_timer_destroy(timer); } return ret; } @@ -159,7 +159,7 @@ esp_err_t mcpwm_del_timer(mcpwm_timer_handle_t timer) ESP_LOGD(TAG, "del timer (%d,%d)", group->group_id, timer_id); // recycle memory resource - ESP_RETURN_ON_ERROR(mcpwm_timer_destory(timer), TAG, "destory timer failed"); + ESP_RETURN_ON_ERROR(mcpwm_timer_destroy(timer), TAG, "destroy timer failed"); return ESP_OK; } diff --git a/components/driver/parlio/include/driver/parlio_tx.h b/components/driver/parlio/include/driver/parlio_tx.h new file mode 100644 index 00000000000..43b606ba42d --- /dev/null +++ b/components/driver/parlio/include/driver/parlio_tx.h @@ -0,0 +1,187 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/parlio_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Parallel IO TX unit configuration + */ +typedef struct { + parlio_clock_source_t clk_src; /*!< Parallel IO internal clock source */ + gpio_num_t clk_in_gpio_num; /*!< If the clock source is input from external, set the corresponding GPIO number. + Otherwise, set to `-1` and the driver will use the internal `clk_src` as clock source. + This option has higher priority than `clk_src` */ + uint32_t input_clk_src_freq_hz; /*!< Frequency of the input clock source, valid only if `clk_in_gpio_num` is not `-1` */ + uint32_t output_clk_freq_hz; /*!< Frequency of the output clock. It's divided from either internal `clk_src` or external clock source */ + size_t data_width; /*!< Parallel IO data width, can set to 1/2/4/8/..., but can't bigger than PARLIO_TX_UNIT_MAX_DATA_WIDTH */ + gpio_num_t data_gpio_nums[PARLIO_TX_UNIT_MAX_DATA_WIDTH]; /*!< Parallel IO data GPIO numbers, if any GPIO is not used, you can set it to `-1` */ + gpio_num_t clk_out_gpio_num; /*!< GPIO number of the output clock signal, the clock is synced with TX data */ + gpio_num_t valid_gpio_num; /*!< GPIO number of the valid signal, which stays high when transferring data. + Note that, the valid signal will always occupy the MSB data bit */ + size_t trans_queue_depth; /*!< Depth of internal transaction queue */ + size_t max_transfer_size; /*!< Maximum transfer size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction */ + parlio_sample_edge_t sample_edge; /*!< Parallel IO sample edge */ + parlio_bit_pack_order_t bit_pack_order; /*!< Set the order of packing the bits into bytes (only works when `data_width` < 8) */ + struct { + uint32_t clk_gate_en: 1; /*!< Enable TX clock gating, + the output clock will be controlled by the MSB bit of the data bus, + i.e. by data_gpio_nums[PARLIO_TX_UNIT_MAX_DATA_WIDTH-1]. High level to enable the clock output, low to disable */ + uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */ + } flags; /*!< Extra configuration flags */ +} parlio_tx_unit_config_t; + +/** + * @brief Create a Parallel IO TX unit + * + * @param[in] config Parallel IO TX unit configuration + * @param[out] ret_unit Returned Parallel IO TX unit handle + * @return + * - ESP_OK: Create Parallel IO TX unit successfully + * - ESP_ERR_INVALID_ARG: Create Parallel IO TX unit failed because of invalid argument + * - ESP_ERR_NO_MEM: Create Parallel IO TX unit failed because of out of memory + * - ESP_ERR_NOT_FOUND: Create Parallel IO TX unit failed because all TX units are used up and no more free one + * - ESP_ERR_NOT_SUPPORTED: Create Parallel IO TX unit failed because some feature is not supported by hardware, e.g. clock gating + * - ESP_FAIL: Create Parallel IO TX unit failed because of other error + */ +esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_unit_handle_t *ret_unit); + +/** + * @brief Delete a Parallel IO TX unit + * + * @param[in] unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @return + * - ESP_OK: Delete Parallel IO TX unit successfully + * - ESP_ERR_INVALID_ARG: Delete Parallel IO TX unit failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Delete Parallel IO TX unit failed because it is still in working + * - ESP_FAIL: Delete Parallel IO TX unit failed because of other error + */ +esp_err_t parlio_del_tx_unit(parlio_tx_unit_handle_t unit); + +/** + * @brief Enable the Parallel IO TX unit + * + * @note This function will transit the driver state from init to enable + * @note This function will acquire a PM lock that might be installed during channel allocation + * @note If there're transaction pending in the queue, this function will pick up the first one and start the transfer + * + * @param[in] unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @return + * - ESP_OK: Enable Parallel IO TX unit successfully + * - ESP_ERR_INVALID_ARG: Enable Parallel IO TX unit failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Enable Parallel IO TX unit failed because it is already enabled + * - ESP_FAIL: Enable Parallel IO TX unit failed because of other error + */ +esp_err_t parlio_tx_unit_enable(parlio_tx_unit_handle_t unit); + +/** + * @brief Disable the Parallel IO TX unit + * + * @note This function will transit the driver state from enable to init + * @note This function will release the PM lock that might be installed during channel allocation + * @note If one transaction is undergoing, this function will terminate it immediately + * + * @param[in] unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @return + * - ESP_OK: Disable Parallel IO TX unit successfully + * - ESP_ERR_INVALID_ARG: Disable Parallel IO TX unit failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Disable Parallel IO TX unit failed because it's not enabled yet + * - ESP_FAIL: Disable Parallel IO TX unit failed because of other error + */ +esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t unit); + +/** + * @brief Type of Parallel IO TX done event data + */ +typedef struct { +} parlio_tx_done_event_data_t; + +/** + * @brief Prototype of parlio tx event callback + * @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @param[in] edata Point to Parallel IO TX event data. The lifecycle of this pointer memory is inside this function, + * user should copy it into static memory if used outside this function. + * @param[in] user_ctx User registered context, passed from `parlio_tx_unit_register_event_callbacks` + * + * @return Whether a high priority task has been waken up by this callback function + */ +typedef bool (*parlio_tx_done_callback_t)(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx); + +/** + * @brief Group of Parallel IO TX callbacks + * @note The callbacks are all running under ISR environment + * @note When CONFIG_PARLIO_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. + */ +typedef struct { + parlio_tx_done_callback_t on_trans_done; /*!< Event callback, invoked when one transmission is finished */ +} parlio_tx_event_callbacks_t; + +/** + * @brief Set event callbacks for Parallel IO TX unit + * + * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. + * @note When CONFIG_PARLIO_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. + * + * @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + * - ESP_FAIL: Set event callbacks failed because of other error + */ +esp_err_t parlio_tx_unit_register_event_callbacks(parlio_tx_unit_handle_t tx_unit, const parlio_tx_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Parallel IO transmit configuration + */ +typedef struct { + uint32_t idle_value; /*!< The value on the data line when the parallel IO is in idle state */ +} parlio_transmit_config_t; + +/** + * @brief Transmit data on by Parallel IO TX unit + * + * @note After the function returns, it doesn't mean the transaction is finished. This function only constructs a transcation structure and push into a queue. + * + * @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @param[in] payload Pointer to the data to be transmitted + * @param[in] payload_bits Length of the data to be transmitted, in bits + * @param[in] config Transmit configuration + * @return + * - ESP_OK: Transmit data successfully + * - ESP_ERR_INVALID_ARG: Transmit data failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Transmit data failed because the Parallel IO TX unit is not enabled + * - ESP_FAIL: Transmit data failed because of other error + */ +esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *payload, size_t payload_bits, const parlio_transmit_config_t *config); + +/** + * @brief Wait for all pending TX transactions done + * + * @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit` + * @param[in] timeout_ms Timeout in milliseconds, `-1` means to wait forever + * @return + * - ESP_OK: All pending TX transactions is finished and recycled + * - ESP_ERR_INVALID_ARG: Wait for all pending TX transactions done failed because of invalid argument + * - ESP_ERR_TIMEOUT: Wait for all pending TX transactions done timeout + * - ESP_FAIL: Wait for all pending TX transactions done failed because of other error + */ +esp_err_t parlio_tx_unit_wait_all_done(parlio_tx_unit_handle_t tx_unit, int timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/parlio/include/driver/parlio_types.h b/components/driver/parlio/include/driver/parlio_types.h new file mode 100644 index 00000000000..c6dbff9b2a6 --- /dev/null +++ b/components/driver/parlio/include/driver/parlio_types.h @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "hal/parlio_types.h" +#include "hal/gpio_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of Parallel IO TX unit handle + */ +typedef struct parlio_tx_unit_t *parlio_tx_unit_handle_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/parlio/parlio_common.c b/components/driver/parlio/parlio_common.c new file mode 100644 index 00000000000..f3450ff79d7 --- /dev/null +++ b/components/driver/parlio/parlio_common.c @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#if CONFIG_PARLIO_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "clk_ctrl_os.h" +#include "soc/rtc.h" +#include "soc/parlio_periph.h" +#include "hal/parlio_ll.h" +#include "esp_private/esp_clk.h" +#include "esp_private/periph_ctrl.h" +#include "parlio_private.h" + +static const char *TAG = "parlio"; + +typedef struct parlio_platform_t { + _lock_t mutex; // platform level mutex lock + parlio_group_t *groups[SOC_PARLIO_GROUPS]; // array of parallel IO group instances + int group_ref_counts[SOC_PARLIO_GROUPS]; // reference count used to protect group install/uninstall +} parlio_platform_t; + +static parlio_platform_t s_platform; // singleton platform + +parlio_group_t *parlio_acquire_group_handle(int group_id) +{ + bool new_group = false; + parlio_group_t *group = NULL; + + // prevent install parlio group concurrently + _lock_acquire(&s_platform.mutex); + if (!s_platform.groups[group_id]) { + group = heap_caps_calloc(1, sizeof(parlio_group_t), PARLIO_MEM_ALLOC_CAPS); + if (group) { + new_group = true; + s_platform.groups[group_id] = group; + group->group_id = group_id; + group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // enable APB access PARLIO registers + periph_module_enable(parlio_periph_signals.groups[group_id].module); + periph_module_reset(parlio_periph_signals.groups[group_id].module); + // hal layer initialize + parlio_hal_init(&group->hal); + } + } else { // group already install + group = s_platform.groups[group_id]; + } + if (group) { + // someone acquired the group handle means we have a new object that refer to this group + s_platform.group_ref_counts[group_id]++; + } + _lock_release(&s_platform.mutex); + + if (new_group) { + ESP_LOGD(TAG, "new group(%d) at %p", group_id, group); + } + return group; +} + +void parlio_release_group_handle(parlio_group_t *group) +{ + int group_id = group->group_id; + bool do_deinitialize = false; + + _lock_acquire(&s_platform.mutex); + s_platform.group_ref_counts[group_id]--; + if (s_platform.group_ref_counts[group_id] == 0) { + do_deinitialize = true; + s_platform.groups[group_id] = NULL; + // hal layer deinitialize + parlio_hal_deinit(&group->hal); + periph_module_disable(parlio_periph_signals.groups[group_id].module); + free(group); + } + _lock_release(&s_platform.mutex); + + if (do_deinitialize) { + ESP_LOGD(TAG, "del group(%d)", group_id); + } +} diff --git a/components/driver/parlio/parlio_private.h b/components/driver/parlio/parlio_private.h new file mode 100644 index 00000000000..1eabbe81ccc --- /dev/null +++ b/components/driver/parlio/parlio_private.h @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "soc/soc_caps.h" +#include "hal/parlio_types.h" +#include "hal/parlio_hal.h" +#include "esp_heap_caps.h" +#include "driver/parlio_types.h" + +#if CONFIG_PARLIO_ISR_IRAM_SAFE +#define PARLIO_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define PARLIO_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#if SOC_PARLIO_TX_RX_SHARE_INTERRUPT +#define PARLIO_INTR_ALLOC_FLAG_SHARED ESP_INTR_FLAG_SHARED +#else +#define PARLIO_INTR_ALLOC_FLAG_SHARED 0 +#endif + +#if CONFIG_PARLIO_ISR_IRAM_SAFE +#define PARLIO_INTR_ALLOC_FLAG (ESP_INTR_FLAG_LOWMED | PARLIO_INTR_ALLOC_FLAG_SHARED | ESP_INTR_FLAG_IRAM) +#else +#define PARLIO_INTR_ALLOC_FLAG (ESP_INTR_FLAG_LOWMED | PARLIO_INTR_ALLOC_FLAG_SHARED) +#endif + +#define PARLIO_PM_LOCK_NAME_LEN_MAX 16 + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + PARLIO_TX_QUEUE_READY, + PARLIO_TX_QUEUE_PROGRESS, + PARLIO_TX_QUEUE_COMPLETE, + PARLIO_TX_QUEUE_MAX, +}; + +typedef enum { + PARLIO_TX_FSM_INIT_WAIT, + PARLIO_TX_FSM_INIT, + PARLIO_TX_FSM_ENABLE_WAIT, + PARLIO_TX_FSM_ENABLE, + PARLIO_TX_FSM_RUN_WAIT, + PARLIO_TX_FSM_RUN, +} parlio_tx_fsm_t; + +typedef struct parlio_group_t { + int group_id; // group ID, index from 0 + portMUX_TYPE spinlock; // to protect per-group register level concurrent access + parlio_hal_context_t hal; // hal layer for each group + parlio_tx_unit_handle_t tx_units[SOC_PARLIO_TX_UNITS_PER_GROUP]; // tx unit handles +} parlio_group_t; + +parlio_group_t *parlio_acquire_group_handle(int group_id); + +void parlio_release_group_handle(parlio_group_t *group); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/parlio/parlio_tx.c b/components/driver/parlio/parlio_tx.c new file mode 100644 index 00000000000..dc7254e0563 --- /dev/null +++ b/components/driver/parlio/parlio_tx.c @@ -0,0 +1,628 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#if CONFIG_PARLIO_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_rom_gpio.h" +#include "esp_intr_alloc.h" +#include "esp_pm.h" +#include "soc/parlio_periph.h" +#include "hal/parlio_ll.h" +#include "hal/gpio_hal.h" +#include "hal/dma_types.h" +#include "driver/gpio.h" +#include "driver/parlio_tx.h" +#include "parlio_private.h" +#include "esp_memory_utils.h" +#include "clk_tree.h" +#include "esp_private/gdma.h" + +static const char *TAG = "parlio-tx"; + +typedef struct { + uint32_t idle_value; // Parallel IO bus idle value + const void *payload; // payload to be transmitted + size_t payload_bits; // payload size in bits +} parlio_tx_trans_desc_t; + +typedef struct parlio_tx_unit_t { + int unit_id; // unit id + size_t data_width; // data width + parlio_group_t *group; // group handle + intr_handle_t intr; // allocated interrupt handle + esp_pm_lock_handle_t pm_lock; // power management lock + gdma_channel_handle_t dma_chan; // DMA channel +#if CONFIG_PM_ENABLE + char pm_lock_name[PARLIO_PM_LOCK_NAME_LEN_MAX]; // pm lock name +#endif + portMUX_TYPE spinlock; // prevent resource accessing by user and interrupt concurrently + uint32_t out_clk_freq_hz; // output clock frequency + size_t max_transfer_bits; // maximum transfer size in bits + size_t queue_depth; // size of transaction queue + size_t num_trans_inflight; // indicates the number of transactions that are undergoing but not recycled to ready_queue + void *queues_storage; // storage of transaction queues + QueueHandle_t trans_queues[PARLIO_TX_QUEUE_MAX]; // transaction queues + StaticQueue_t trans_queue_structs[PARLIO_TX_QUEUE_MAX]; // memory to store the static structure for trans_queues + parlio_tx_trans_desc_t *cur_trans; // points to current transaction + uint32_t idle_value_mask; // mask of idle value + _Atomic parlio_tx_fsm_t fsm; // Driver FSM state + parlio_tx_done_callback_t on_trans_done; // callback function when the transmission is done + void *user_data; // user data passed to the callback function + dma_descriptor_t *dma_nodes; // DMA descriptor nodes + parlio_tx_trans_desc_t trans_desc_pool[]; // transaction descriptor pool +} parlio_tx_unit_t; + +static void parlio_tx_default_isr(void *args); + +static esp_err_t parlio_tx_register_to_group(parlio_tx_unit_t *unit) +{ + parlio_group_t *group = NULL; + int unit_id = -1; + for (int i = 0; i < SOC_PARLIO_GROUPS; i++) { + group = parlio_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no memory for group (%d)", i); + portENTER_CRITICAL(&group->spinlock); + for (int j = 0; j < SOC_PARLIO_TX_UNITS_PER_GROUP; j++) { + if (group->tx_units[j] == NULL) { + group->tx_units[j] = unit; + unit_id = j; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (unit_id < 0) { + // didn't find a free unit slot in the group + parlio_release_group_handle(group); + group = NULL; + } else { + unit->unit_id = unit_id; + unit->group = group; + break; + } + } + ESP_RETURN_ON_FALSE(unit_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free tx unit"); + return ESP_OK; +} + +static void parlio_tx_unregister_to_group(parlio_tx_unit_t *unit, parlio_group_t *group) +{ + portENTER_CRITICAL(&group->spinlock); + group->tx_units[unit->unit_id] = NULL; + portEXIT_CRITICAL(&group->spinlock); + // the tx unit has a reference of the group, release it now + parlio_release_group_handle(group); +} + +static esp_err_t parlio_tx_create_trans_queue(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config) +{ + tx_unit->queue_depth = config->trans_queue_depth; + // the queue only saves transaction description pointers + tx_unit->queues_storage = heap_caps_calloc(config->trans_queue_depth * PARLIO_TX_QUEUE_MAX, sizeof(parlio_tx_trans_desc_t *), PARLIO_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(tx_unit->queues_storage, ESP_ERR_NO_MEM, TAG, "no mem for queue storage"); + parlio_tx_trans_desc_t **pp_trans_desc = (parlio_tx_trans_desc_t **)tx_unit->queues_storage; + for (int i = 0; i < PARLIO_TX_QUEUE_MAX; i++) { + tx_unit->trans_queues[i] = xQueueCreateStatic(config->trans_queue_depth, sizeof(parlio_tx_trans_desc_t *), + (uint8_t *)pp_trans_desc, &tx_unit->trans_queue_structs[i]); + pp_trans_desc += config->trans_queue_depth; + // because trans_queue_structs is guaranteed to be non-NULL, so the trans_queues will also not be NULL + assert(tx_unit->trans_queues[i]); + } + // initialize the ready queue + parlio_tx_trans_desc_t *p_trans_desc = NULL; + for (int i = 0; i < config->trans_queue_depth; i++) { + p_trans_desc = &tx_unit->trans_desc_pool[i]; + ESP_RETURN_ON_FALSE(xQueueSend(tx_unit->trans_queues[PARLIO_TX_QUEUE_READY], &p_trans_desc, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + return ESP_OK; +} + +static esp_err_t parlio_destroy_tx_unit(parlio_tx_unit_t *tx_unit) +{ + if (tx_unit->intr) { + ESP_RETURN_ON_ERROR(esp_intr_free(tx_unit->intr), TAG, "delete interrupt service failed"); + } + if (tx_unit->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(tx_unit->pm_lock), TAG, "delete pm lock failed"); + } + if (tx_unit->dma_chan) { + ESP_RETURN_ON_ERROR(gdma_disconnect(tx_unit->dma_chan), TAG, "disconnect dma channel failed"); + ESP_RETURN_ON_ERROR(gdma_del_channel(tx_unit->dma_chan), TAG, "delete dma channel failed"); + } + for (int i = 0; i < PARLIO_TX_QUEUE_MAX; i++) { + if (tx_unit->trans_queues[i]) { + vQueueDelete(tx_unit->trans_queues[i]); + } + } + if (tx_unit->group) { + // de-register from group + parlio_tx_unregister_to_group(tx_unit, tx_unit->group); + } + free(tx_unit->queues_storage); + free(tx_unit->dma_nodes); + free(tx_unit); + return ESP_OK; +} + +static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config) +{ + int group_id = tx_unit->group->group_id; + int unit_id = tx_unit->unit_id; + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_OUTPUT, + .pull_down_en = false, + .pull_up_en = true, + }; + + // connect peripheral signals via GPIO matrix + for (size_t i = 0; i < config->data_width; i++) { + if (config->data_gpio_nums[i] >= 0) { + gpio_conf.pin_bit_mask = BIT64(config->data_gpio_nums[i]); + ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config data GPIO failed"); + esp_rom_gpio_connect_out_signal(config->data_gpio_nums[i], + parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[i], false, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->data_gpio_nums[i]], PIN_FUNC_GPIO); + } + } + // Note: the valid signal will override TXD[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG] + if (config->valid_gpio_num >= 0) { + gpio_conf.pin_bit_mask = BIT64(config->valid_gpio_num); + ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config valid GPIO failed"); + esp_rom_gpio_connect_out_signal(config->valid_gpio_num, + parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG], + false, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->valid_gpio_num], PIN_FUNC_GPIO); + } + if (config->clk_out_gpio_num >= 0) { + gpio_conf.pin_bit_mask = BIT64(config->clk_out_gpio_num); + ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk out GPIO failed"); + esp_rom_gpio_connect_out_signal(config->clk_out_gpio_num, + parlio_periph_signals.groups[group_id].tx_units[unit_id].clk_out_sig, false, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->clk_out_gpio_num], PIN_FUNC_GPIO); + } + if (config->clk_in_gpio_num >= 0) { + gpio_conf.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_INPUT; + gpio_conf.pin_bit_mask = BIT64(config->clk_in_gpio_num); + ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk in GPIO failed"); + esp_rom_gpio_connect_in_signal(config->clk_in_gpio_num, + parlio_periph_signals.groups[group_id].tx_units[unit_id].clk_in_sig, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->clk_in_gpio_num], PIN_FUNC_GPIO); + } + return ESP_OK; +} + +static esp_err_t parlio_tx_unit_init_dma(parlio_tx_unit_t *tx_unit) +{ + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_TX, + }; + ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &tx_unit->dma_chan), TAG, "allocate TX DMA channel failed"); + gdma_connect(tx_unit->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_PARLIO, 0)); + gdma_strategy_config_t gdma_strategy_conf = { + .auto_update_desc = true, + .owner_check = true, + }; + gdma_apply_strategy(tx_unit->dma_chan, &gdma_strategy_conf); + return ESP_OK; +} + +static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config) +{ + parlio_hal_context_t *hal = &tx_unit->group->hal; + // parlio_ll_clock_source_t and parlio_clock_source_t are binary compatible if the clock source is from internal + parlio_ll_clock_source_t clk_src = (parlio_ll_clock_source_t)(config->clk_src); + uint32_t periph_src_clk_hz = 0; + // if the source clock is input from the GPIO, then we're in the slave mode + if (config->clk_in_gpio_num >= 0) { + clk_src = PARLIO_LL_CLK_SRC_PAD; + periph_src_clk_hz = config->input_clk_src_freq_hz; + } else { + // get the internal clock source frequency + clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz); + } + ESP_RETURN_ON_FALSE(periph_src_clk_hz, ESP_ERR_INVALID_ARG, TAG, "invalid clock source frequency"); + +#if CONFIG_PM_ENABLE + if (clk_src != PARLIO_LL_CLK_SRC_PAD) { + // XTAL and PLL clock source will be turned off in light sleep, so we need to create a NO_LIGHT_SLEEP lock + sprintf(tx_unit->pm_lock_name, "parlio_tx_%d_%d", tx_unit->group->group_id, tx_unit->unit_id); // e.g. parlio_tx_0_0 + esp_err_t ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, tx_unit->pm_lock_name, &tx_unit->pm_lock); + ESP_RETURN_ON_ERROR(ret, TAG, "create NO_LIGHT_SLEEP lock failed"); + } +#endif + + parlio_ll_tx_set_clock_source(hal->regs, clk_src); + // set clock division, round up + uint32_t div = (periph_src_clk_hz + config->output_clk_freq_hz - 1) / config->output_clk_freq_hz; + parlio_ll_tx_set_clock_div(hal->regs, div); + // precision lost due to division, calculate the real frequency + tx_unit->out_clk_freq_hz = periph_src_clk_hz / div; + if (tx_unit->out_clk_freq_hz != config->output_clk_freq_hz) { + ESP_LOGW(TAG, "precision loss, real output frequency: %"PRIu32, tx_unit->out_clk_freq_hz); + } + + return ESP_OK; +} + +esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_unit_handle_t *ret_unit) +{ +#if CONFIG_PARLIO_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + parlio_tx_unit_t *unit = NULL; + ESP_GOTO_ON_FALSE(config && ret_unit, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + size_t data_width = config->data_width; + // data_width must be power of 2 and less than or equal to SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH + ESP_GOTO_ON_FALSE(data_width && (data_width <= SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH) && ((data_width & (data_width - 1)) == 0), + ESP_ERR_INVALID_ARG, err, TAG, "invalid data width"); + // data_width must not conflict with the valid signal + ESP_GOTO_ON_FALSE(!(config->valid_gpio_num >= 0 && data_width > PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG), + ESP_ERR_INVALID_ARG, err, TAG, "valid signal conflicts with data signal"); + ESP_GOTO_ON_FALSE(config->max_transfer_size && config->max_transfer_size <= PARLIO_LL_TX_MAX_BITS_PER_FRAME / 8, + ESP_ERR_INVALID_ARG, err, TAG, "invalid max transfer size"); +#if SOC_PARLIO_TX_CLK_SUPPORT_GATING + // clock gating is controlled by either the MSB bit of data bus or the valid signal + ESP_GOTO_ON_FALSE(!(config->flags.clk_gate_en && config->valid_gpio_num < 0 && config->data_width <= PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE), + ESP_ERR_INVALID_ARG, err, TAG, "no gpio can control the clock gating"); +#else + ESP_GOTO_ON_FALSE(config->flags.clk_gate_en == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "clock gating is not supported"); +#endif // SOC_PARLIO_TX_CLK_SUPPORT_GATING + + // malloc unit memory + unit = heap_caps_calloc(1, sizeof(parlio_tx_unit_t) + sizeof(parlio_tx_trans_desc_t) * config->trans_queue_depth, PARLIO_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(unit, ESP_ERR_NO_MEM, err, TAG, "no memory for tx unit"); + size_t dma_nodes_num = config->max_transfer_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1; + // DMA descriptors must be placed in internal SRAM + unit->dma_nodes = heap_caps_calloc(dma_nodes_num, sizeof(dma_descriptor_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + ESP_GOTO_ON_FALSE(unit->dma_nodes, ESP_ERR_NO_MEM, err, TAG, "no memory for DMA nodes"); + unit->max_transfer_bits = config->max_transfer_size * 8; + + unit->data_width = data_width; + //create transaction queue + ESP_GOTO_ON_ERROR(parlio_tx_create_trans_queue(unit, config), err, TAG, "create transaction queue failed"); + + // register the unit to a group + ESP_GOTO_ON_ERROR(parlio_tx_register_to_group(unit), err, TAG, "register unit to group failed"); + parlio_group_t *group = unit->group; + parlio_hal_context_t *hal = &group->hal; + // select the clock source + ESP_GOTO_ON_ERROR(parlio_select_periph_clock(unit, config), err, TAG, "set clock source failed"); + + // install interrupt service + int isr_flags = PARLIO_INTR_ALLOC_FLAG; + ret = esp_intr_alloc_intrstatus(parlio_periph_signals.groups[group->group_id].tx_irq_id, isr_flags, + (uint32_t)parlio_ll_get_interrupt_status_reg(hal->regs), + PARLIO_LL_EVENT_TX_EOF, parlio_tx_default_isr, unit, &unit->intr); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed"); + + // install DMA service + ESP_GOTO_ON_ERROR(parlio_tx_unit_init_dma(unit), err, TAG, "install tx DMA failed"); + + // reset fifo and core clock domain + parlio_ll_tx_reset_clock(hal->regs); + parlio_ll_tx_reset_fifo(hal->regs); + // stop output clock + parlio_ll_tx_enable_clock(hal->regs, false); + // clock gating + parlio_ll_tx_enable_clock_gating(hal->regs, config->flags.clk_gate_en); + // set data width + parlio_ll_tx_set_bus_width(hal->regs, data_width); + unit->idle_value_mask = (1 << data_width) - 1; + // whether to use the valid signal + if (config->valid_gpio_num >= 0) { + parlio_ll_tx_treat_msb_as_valid(hal->regs, true); + unit->idle_value_mask &= ~(1 << PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG); + } else { + parlio_ll_tx_treat_msb_as_valid(hal->regs, false); + } + // set data byte packing order + if (data_width < 8) { + parlio_ll_tx_set_bit_pack_order(hal->regs, config->bit_pack_order); + } + // set sample clock edge + parlio_ll_tx_set_sample_clock_edge(hal->regs, config->sample_edge); + + // clear any pending interrupt + parlio_ll_clear_interrupt_status(hal->regs, PARLIO_LL_EVENT_TX_MASK); + + // GPIO Matrix/MUX configuration + ESP_GOTO_ON_ERROR(parlio_tx_unit_configure_gpio(unit, config), err, TAG, "configure gpio failed"); + + portMUX_INITIALIZE(&unit->spinlock); + atomic_init(&unit->fsm, PARLIO_TX_FSM_INIT); + // return TX unit handle + *ret_unit = unit; + ESP_LOGD(TAG, "new tx unit(%d,%d) at %p, out clk=%"PRIu32"Hz, queue_depth=%zu, idle_mask=%"PRIx32, + group->group_id, unit->unit_id, unit, unit->out_clk_freq_hz, unit->queue_depth, unit->idle_value_mask); + return ESP_OK; + +err: + if (unit) { + parlio_destroy_tx_unit(unit); + } + return ret; +} + +esp_err_t parlio_del_tx_unit(parlio_tx_unit_handle_t unit) +{ + ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(atomic_load(&unit->fsm) == PARLIO_TX_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state"); + ESP_LOGD(TAG, "del tx unit(%d,%d)", unit->group->group_id, unit->unit_id); + return parlio_destroy_tx_unit(unit); +} + +static void IRAM_ATTR parlio_tx_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len) +{ + size_t prepared_length = 0; + uint8_t *data = (uint8_t *)buffer; + dma_descriptor_t *desc = desc_head; + while (len > DMA_DESCRIPTOR_BUFFER_MAX_SIZE) { + desc->dw0.suc_eof = 0; // not the end of the transaction + desc->dw0.size = DMA_DESCRIPTOR_BUFFER_MAX_SIZE; + desc->dw0.length = DMA_DESCRIPTOR_BUFFER_MAX_SIZE; + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->buffer = &data[prepared_length]; + desc = desc->next; // move to next descriptor + prepared_length += DMA_DESCRIPTOR_BUFFER_MAX_SIZE; + len -= DMA_DESCRIPTOR_BUFFER_MAX_SIZE; + } + if (len) { + desc->dw0.suc_eof = 1; // end of the transaction + desc->dw0.size = len; + desc->dw0.length = len; + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->buffer = &data[prepared_length]; + desc = desc->next; // move to next descriptor + prepared_length += len; + } +} + +esp_err_t parlio_tx_unit_wait_all_done(parlio_tx_unit_handle_t tx_unit, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(tx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + TickType_t wait_ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + // recycle all pending transactions + parlio_tx_trans_desc_t *t = NULL; + size_t num_trans_inflight = tx_unit->num_trans_inflight; + for (size_t i = 0; i < num_trans_inflight; i++) { + ESP_RETURN_ON_FALSE(xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_COMPLETE], &t, wait_ticks) == pdTRUE, + ESP_ERR_TIMEOUT, TAG, "flush timeout"); + ESP_RETURN_ON_FALSE(xQueueSend(tx_unit->trans_queues[PARLIO_TX_QUEUE_READY], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + tx_unit->num_trans_inflight--; + } + return ESP_OK; +} + +esp_err_t parlio_tx_unit_register_event_callbacks(parlio_tx_unit_handle_t tx_unit, const parlio_tx_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(tx_unit && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + +#if CONFIG_PARLIO_ISR_IRAM_SAFE + if (cbs->on_trans_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_trans_done callback 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"); + } +#endif + + tx_unit->on_trans_done = cbs->on_trans_done; + tx_unit->user_data = user_data; + return ESP_OK; +} + +static void IRAM_ATTR parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_t *t) +{ + parlio_hal_context_t *hal = &tx_unit->group->hal; + + tx_unit->cur_trans = t; + + // DMA transfer data based on bytes not bits, so convert the bit length to bytes, round up + parlio_tx_mount_dma_data(tx_unit->dma_nodes, t->payload, (t->payload_bits + 7) / 8); + + parlio_ll_tx_reset_fifo(hal->regs); + parlio_ll_tx_reset_clock(hal->regs); + parlio_ll_tx_set_idle_data_value(hal->regs, t->idle_value); + parlio_ll_tx_set_trans_bit_len(hal->regs, t->payload_bits); + + gdma_start(tx_unit->dma_chan, (intptr_t)tx_unit->dma_nodes); + // wait until the data goes from the DMA to TX unit's FIFO + while (parlio_ll_tx_is_ready(hal->regs) == false); + // turn on the core clock after we start the TX unit + parlio_ll_tx_start(hal->regs, true); + parlio_ll_tx_enable_clock(hal->regs, true); +} + +esp_err_t parlio_tx_unit_enable(parlio_tx_unit_handle_t tx_unit) +{ + ESP_RETURN_ON_FALSE(tx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_INIT; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_ENABLE_WAIT)) { + // acquire power management lock + if (tx_unit->pm_lock) { + esp_pm_lock_acquire(tx_unit->pm_lock); + } + parlio_hal_context_t *hal = &tx_unit->group->hal; + parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_EOF, true); + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE); + } else { + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "unit not in init state"); + } + + // check if we need to start one pending transaction + parlio_tx_trans_desc_t *t = NULL; + expected_fsm = PARLIO_TX_FSM_ENABLE; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_RUN_WAIT)) { + // check if we need to start one transaction + if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE) { + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_RUN); + parlio_tx_do_transaction(tx_unit, t); + } else { + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE); + } + } + + return ESP_OK; +} + +esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t tx_unit) +{ + ESP_RETURN_ON_FALSE(tx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + bool valid_state = false; + // check the supported states, and switch to intermediate state: INIT_WAIT + parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_ENABLE; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_INIT_WAIT)) { + valid_state = true; + } + expected_fsm = PARLIO_TX_FSM_RUN; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_INIT_WAIT)) { + valid_state = true; + assert(tx_unit->cur_trans); + // recycle the interrupted transaction + if (xQueueSend(tx_unit->trans_queues[PARLIO_TX_QUEUE_COMPLETE], &tx_unit->cur_trans, 0) == pdFALSE) { + // this should never happen + valid_state = false; + } + tx_unit->cur_trans = NULL; + } + ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "unit can't be disabled in state %d", expected_fsm); + + // stop the TX engine + parlio_hal_context_t *hal = &tx_unit->group->hal; + gdma_stop(tx_unit->dma_chan); + parlio_ll_tx_start(hal->regs, false); + parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_EOF, false); + + // release power management lock + if (tx_unit->pm_lock) { + esp_pm_lock_release(tx_unit->pm_lock); + } + + // finally we switch to the INIT state + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_INIT); + + return ESP_OK; +} + +esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *payload, size_t payload_bits, const parlio_transmit_config_t *config) +{ + ESP_RETURN_ON_FALSE(tx_unit && payload && payload_bits, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE((payload_bits % tx_unit->data_width) == 0, ESP_ERR_INVALID_ARG, TAG, "payload bit length must align to bus width"); + ESP_RETURN_ON_FALSE(payload_bits <= tx_unit->max_transfer_bits, ESP_ERR_INVALID_ARG, TAG, "payload bit length too large"); +#if !SOC_PARLIO_TRANS_BIT_ALIGN + ESP_RETURN_ON_FALSE((payload_bits % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "payload bit length must be multiple of 8"); +#endif // !SOC_PARLIO_TRANS_BIT_ALIGN + + // acquire one transaction description from ready queue or complete queue + parlio_tx_trans_desc_t *t = NULL; + if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_READY], &t, 0) != pdTRUE) { + if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_COMPLETE], &t, 0) == pdTRUE) { + tx_unit->num_trans_inflight--; + } + } + ESP_RETURN_ON_FALSE(t, ESP_ERR_INVALID_STATE, TAG, "no free transaction descriptor, please consider increasing trans_queue_depth"); + + // fill in the transaction descriptor + memset(t, 0, sizeof(parlio_tx_trans_desc_t)); + t->payload = payload; + t->payload_bits = payload_bits; + t->idle_value = config->idle_value & tx_unit->idle_value_mask; + + // send the transaction descriptor to progress queue + ESP_RETURN_ON_FALSE(xQueueSend(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "failed to send transaction descriptor to progress queue"); + tx_unit->num_trans_inflight++; + + // check if we need to start one pending transaction + parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_ENABLE; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_RUN_WAIT)) { + // check if we need to start one transaction + if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE) { + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_RUN); + parlio_tx_do_transaction(tx_unit, t); + } else { + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE); + } + } + + return ESP_OK; +} + +static void IRAM_ATTR parlio_tx_default_isr(void *args) +{ + parlio_tx_unit_t *tx_unit = (parlio_tx_unit_t *)args; + parlio_group_t *group = tx_unit->group; + parlio_hal_context_t *hal = &group->hal; + BaseType_t high_task_woken = pdFALSE; + bool need_yield = false; + + uint32_t status = parlio_ll_tx_get_interrupt_status(hal->regs); + + if (status & PARLIO_LL_EVENT_TX_EOF) { + parlio_ll_clear_interrupt_status(hal->regs, PARLIO_LL_EVENT_TX_EOF); + parlio_ll_tx_enable_clock(hal->regs, false); + parlio_ll_tx_start(hal->regs, false); + + parlio_tx_trans_desc_t *trans_desc = NULL; + + parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_RUN; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_ENABLE_WAIT)) { + trans_desc = tx_unit->cur_trans; + // move current finished transaction to the complete queue + xQueueSendFromISR(tx_unit->trans_queues[PARLIO_TX_QUEUE_COMPLETE], &trans_desc, &high_task_woken); + if (high_task_woken == pdTRUE) { + need_yield = true; + } + tx_unit->cur_trans = NULL; + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE); + } + + // invoke callback + parlio_tx_done_callback_t done_cb = tx_unit->on_trans_done; + if (done_cb) { + if (done_cb(tx_unit, NULL, tx_unit->user_data)) { + need_yield = true; + } + } + + // if the tx unit is till in enable state (i.e. not disabled by user), let's try start the next pending transaction + expected_fsm = PARLIO_TX_FSM_ENABLE; + if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_RUN_WAIT)) { + if (xQueueReceiveFromISR(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &trans_desc, &high_task_woken) == pdTRUE) { + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_RUN); + parlio_tx_do_transaction(tx_unit, trans_desc); + if (high_task_woken == pdTRUE) { + need_yield = true; + } + } else { + atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE); + } + } + + if (need_yield) { + portYIELD_FROM_ISR(); + } + } +} diff --git a/components/driver/pcnt/pulse_cnt.c b/components/driver/pcnt/pulse_cnt.c index f3a68c9ee54..700ce4eeb20 100644 --- a/components/driver/pcnt/pulse_cnt.c +++ b/components/driver/pcnt/pulse_cnt.c @@ -155,7 +155,7 @@ static void pcnt_unregister_from_group(pcnt_unit_t *unit) pcnt_release_group_handle(group); } -static esp_err_t pcnt_destory(pcnt_unit_t *unit) +static esp_err_t pcnt_destroy(pcnt_unit_t *unit) { if (unit->pm_lock) { ESP_RETURN_ON_ERROR(esp_pm_lock_delete(unit->pm_lock), TAG, "delete pm lock failed"); @@ -233,7 +233,7 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re err: if (unit) { - pcnt_destory(unit); + pcnt_destroy(unit); } return ret; } @@ -252,7 +252,7 @@ esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit) ESP_LOGD(TAG, "del unit (%d,%d)", group_id, unit_id); // recycle memory resource - ESP_RETURN_ON_ERROR(pcnt_destory(unit), TAG, "destory pcnt unit failed"); + ESP_RETURN_ON_ERROR(pcnt_destroy(unit), TAG, "destroy pcnt unit failed"); return ESP_OK; } diff --git a/components/driver/rmt/include/driver/rmt_types.h b/components/driver/rmt/include/driver/rmt_types.h index 3ba13d7932f..2dea896ea67 100644 --- a/components/driver/rmt/include/driver/rmt_types.h +++ b/components/driver/rmt/include/driver/rmt_types.h @@ -42,7 +42,7 @@ typedef struct { * @brief Prototype of RMT event callback * @param[in] tx_chan RMT channel handle, created from `rmt_new_tx_channel()` * @param[in] edata Point to RMT event data. The lifecycle of this pointer memory is inside this function, - * user should copy it into static memory if used outside this funcion. + * user should copy it into static memory if used outside this function. * @param[in] user_ctx User registered context, passed from `rmt_tx_register_event_callbacks()` * * @return Whether a high priority task has been waken up by this callback function @@ -62,7 +62,7 @@ typedef struct { * * @param[in] rx_chan RMT channel handle, created from `rmt_new_rx_channel()` * @param[in] edata Point to RMT event data. The lifecycle of this pointer memory is inside this function, - * user should copy it into static memory if used outside this funcion. + * user should copy it into static memory if used outside this function. * @param[in] user_ctx User registered context, passed from `rmt_rx_register_event_callbacks()` * @return Whether a high priority task has been waken up by this function */ diff --git a/components/driver/rmt/rmt_rx.c b/components/driver/rmt/rmt_rx.c index 9dc3a94d5a8..c861e42c024 100644 --- a/components/driver/rmt/rmt_rx.c +++ b/components/driver/rmt/rmt_rx.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -155,7 +155,7 @@ static void rmt_rx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *gr rmt_release_group_handle(group); } -static esp_err_t rmt_rx_destory(rmt_rx_channel_t *rx_channel) +static esp_err_t rmt_rx_destroy(rmt_rx_channel_t *rx_channel) { if (rx_channel->base.intr) { ESP_RETURN_ON_ERROR(esp_intr_free(rx_channel->base.intr), TAG, "delete interrupt service failed"); @@ -289,7 +289,7 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_ err: if (rx_channel) { - rmt_rx_destory(rx_channel); + rmt_rx_destroy(rx_channel); } return ret; } @@ -302,7 +302,7 @@ static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel) int channel_id = channel->channel_id; ESP_LOGD(TAG, "del rx channel(%d,%d)", group_id, channel_id); // recycle memory resource - ESP_RETURN_ON_ERROR(rmt_rx_destory(rx_chan), TAG, "destory rx channel failed"); + ESP_RETURN_ON_ERROR(rmt_rx_destroy(rx_chan), TAG, "destroy rx channel failed"); return ESP_OK; } diff --git a/components/driver/rmt/rmt_tx.c b/components/driver/rmt/rmt_tx.c index 5d814a515ae..7a35f430cc5 100644 --- a/components/driver/rmt/rmt_tx.c +++ b/components/driver/rmt/rmt_tx.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -166,7 +166,7 @@ static esp_err_t rmt_tx_create_trans_queue(rmt_tx_channel_t *tx_channel, const r return ESP_OK; } -static esp_err_t rmt_tx_destory(rmt_tx_channel_t *tx_channel) +static esp_err_t rmt_tx_destroy(rmt_tx_channel_t *tx_channel) { if (tx_channel->base.intr) { ESP_RETURN_ON_ERROR(esp_intr_free(tx_channel->base.intr), TAG, "delete interrupt service failed"); @@ -307,7 +307,7 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_ err: if (tx_channel) { - rmt_tx_destory(tx_channel); + rmt_tx_destroy(tx_channel); } return ret; } @@ -320,7 +320,7 @@ static esp_err_t rmt_del_tx_channel(rmt_channel_handle_t channel) int channel_id = channel->channel_id; ESP_LOGD(TAG, "del tx channel(%d,%d)", group_id, channel_id); // recycle memory resource - ESP_RETURN_ON_ERROR(rmt_tx_destory(tx_chan), TAG, "destory tx channel failed"); + ESP_RETURN_ON_ERROR(rmt_tx_destroy(tx_chan), TAG, "destroy tx channel failed"); return ESP_OK; } diff --git a/components/driver/sigma_delta/sdm.c b/components/driver/sigma_delta/sdm.c index 5609554fdff..1eaa9bd5c10 100644 --- a/components/driver/sigma_delta/sdm.c +++ b/components/driver/sigma_delta/sdm.c @@ -177,7 +177,7 @@ static void sdm_unregister_from_group(sdm_channel_t *chan) sdm_release_group_handle(group); } -static esp_err_t sdm_destory(sdm_channel_t *chan) +static esp_err_t sdm_destroy(sdm_channel_t *chan) { if (chan->pm_lock) { ESP_RETURN_ON_ERROR(esp_pm_lock_delete(chan->pm_lock), TAG, "delete pm lock failed"); @@ -283,7 +283,7 @@ esp_err_t sdm_new_channel(const sdm_config_t *config, sdm_channel_handle_t *ret_ return ESP_OK; err: if (chan) { - sdm_destory(chan); + sdm_destroy(chan); } return ret; } @@ -297,7 +297,7 @@ esp_err_t sdm_del_channel(sdm_channel_handle_t chan) int chan_id = chan->chan_id; ESP_LOGD(TAG, "del channel (%d,%d)", group_id, chan_id); // recycle memory resource - ESP_RETURN_ON_ERROR(sdm_destory(chan), TAG, "destory channel failed"); + ESP_RETURN_ON_ERROR(sdm_destroy(chan), TAG, "destroy channel failed"); return ESP_OK; } diff --git a/components/driver/test_apps/parlio/CMakeLists.txt b/components/driver/test_apps/parlio/CMakeLists.txt new file mode 100644 index 00000000000..072413fca88 --- /dev/null +++ b/components/driver/test_apps/parlio/CMakeLists.txt @@ -0,0 +1,18 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(parlio_test) + +if(CONFIG_COMPILER_DUMP_RTL_FILES) + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/ + --elf-file ${CMAKE_BINARY_DIR}/parlio_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() diff --git a/components/driver/test_apps/parlio/README.md b/components/driver/test_apps/parlio/README.md new file mode 100644 index 00000000000..b450dc5ffac --- /dev/null +++ b/components/driver/test_apps/parlio/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | diff --git a/components/driver/test_apps/parlio/main/CMakeLists.txt b/components/driver/test_apps/parlio/main/CMakeLists.txt new file mode 100644 index 00000000000..e74e89fc26c --- /dev/null +++ b/components/driver/test_apps/parlio/main/CMakeLists.txt @@ -0,0 +1,7 @@ +set(srcs "test_app_main.c" + "test_parlio_tx.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/parlio/main/test_app_main.c b/components/driver/test_apps/parlio/main/test_app_main.c new file mode 100644 index 00000000000..8eace8a07e8 --- /dev/null +++ b/components/driver/test_apps/parlio/main/test_app_main.c @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in pulse_cnt driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + // ____ _ _ _ ___ ___ _____ _ + // | _ \ __ _ _ __ __ _| | | ___| | |_ _/ _ \ |_ _|__ ___| |_ + // | |_) / _` | '__/ _` | | |/ _ \ | | | | | | | |/ _ \/ __| __| + // | __/ (_| | | | (_| | | | __/ | | | |_| | | | __/\__ \ |_ + // |_| \__,_|_| \__,_|_|_|\___|_| |___\___/ |_|\___||___/\__| + printf(" ____ _ _ _ ___ ___ _____ _\r\n"); + printf("| _ \\ __ _ _ __ __ _| | | ___| | |_ _/ _ \\ |_ _|__ ___| |_\r\n"); + printf("| |_) / _` | '__/ _` | | |/ _ \\ | | | | | | | |/ _ \\/ __| __|\r\n"); + printf("| __/ (_| | | | (_| | | | __/ | | | |_| | | | __/\\__ \\ |_\r\n"); + printf("|_| \\__,_|_| \\__,_|_|_|\\___|_| |___\\___/ |_|\\___||___/\\__|\r\n"); + unity_run_menu(); +} diff --git a/components/driver/test_apps/parlio/main/test_board.h b/components/driver/test_apps/parlio/main/test_board.h new file mode 100644 index 00000000000..4d6c0139f61 --- /dev/null +++ b/components/driver/test_apps/parlio/main/test_board.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_IDF_TARGET_ESP32C6 +#define TEST_CLK_GPIO 10 +#define TEST_DATA0_GPIO 0 +#define TEST_DATA1_GPIO 1 +#define TEST_DATA2_GPIO 2 +#define TEST_DATA3_GPIO 3 +#define TEST_DATA4_GPIO 4 +#define TEST_DATA5_GPIO 5 +#define TEST_DATA6_GPIO 6 +#define TEST_DATA7_GPIO 7 +#elif CONFIG_IDF_TARGET_ESP32H2 +#define TEST_CLK_GPIO 10 +#define TEST_DATA0_GPIO 0 +#define TEST_DATA1_GPIO 1 +#define TEST_DATA2_GPIO 2 +#define TEST_DATA3_GPIO 3 +#define TEST_DATA4_GPIO 4 +#define TEST_DATA5_GPIO 5 +#define TEST_DATA6_GPIO 8 +#define TEST_DATA7_GPIO 9 +#else +#error "Unsupported target" +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/test_apps/parlio/main/test_parlio_tx.c b/components/driver/test_apps/parlio/main/test_parlio_tx.c new file mode 100644 index 00000000000..0b5dd59b35f --- /dev/null +++ b/components/driver/test_apps/parlio/main/test_parlio_tx.c @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/parlio_tx.h" +#include "driver/gpio.h" +#include "soc/soc_caps.h" +#include "esp_attr.h" +#include "test_board.h" + +#if CONFIG_PARLIO_ISR_IRAM_SAFE +#define TEST_PARLIO_CALLBACK_ATTR IRAM_ATTR +#else +#define TEST_PARLIO_CALLBACK_ATTR +#endif + +TEST_CASE("parallel_tx_unit_install_uninstall", "[parlio_tx]") +{ + printf("install tx units exhaustively\r\n"); + parlio_tx_unit_handle_t units[SOC_PARLIO_GROUPS * SOC_PARLIO_TX_UNITS_PER_GROUP]; + int k = 0; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH, + .clk_in_gpio_num = -1, // clock source from internal + .clk_out_gpio_num = 0, + .output_clk_freq_hz = 1 * 1000 * 1000, + .trans_queue_depth = 4, + .max_transfer_size = 64, + .valid_gpio_num = -1, + }; + for (int i = 0; i < SOC_PARLIO_GROUPS; i++) { + for (int j = 0; j < SOC_PARLIO_TX_UNITS_PER_GROUP; j++) { + TEST_ESP_OK(parlio_new_tx_unit(&config, &units[k++])); + } + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, parlio_new_tx_unit(&config, &units[0])); + + for (int i = 0; i < k; i++) { + TEST_ESP_OK(parlio_del_tx_unit(units[i])); + } + + printf("install tx unit with valid signal and external core clock\r\n"); + // clock from external + config.clk_in_gpio_num = 2; + // failed because of invalid clock source frequency + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_tx_unit(&config, &units[0])); + config.input_clk_src_freq_hz = 1000000; + + config.valid_gpio_num = 0; + // failed because of data line conflict with valid signal + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_tx_unit(&config, &units[0])); + + config.data_width = 4; + TEST_ESP_OK(parlio_new_tx_unit(&config, &units[0])); + TEST_ESP_OK(parlio_tx_unit_enable(units[0])); + // delete unit before it's disabled is not allowed + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, parlio_del_tx_unit(units[0])); + TEST_ESP_OK(parlio_tx_unit_disable(units[0])); + TEST_ESP_OK(parlio_del_tx_unit(units[0])); +} + +TEST_PARLIO_CALLBACK_ATTR +static bool test_parlio_tx_done_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx) +{ + BaseType_t high_task_wakeup = pdFALSE; + TaskHandle_t task = (TaskHandle_t)user_ctx; + vTaskNotifyGiveFromISR(task, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("parallel_tx_unit_trans_done_event", "[parlio_tx]") +{ + printf("install parlio tx unit\r\n"); + parlio_tx_unit_handle_t tx_unit = NULL; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = 8, + .clk_in_gpio_num = -1, // use internal clock source + .valid_gpio_num = -1, // don't generate valid signal + .clk_out_gpio_num = TEST_CLK_GPIO, + .data_gpio_nums = { + TEST_DATA0_GPIO, + TEST_DATA1_GPIO, + TEST_DATA2_GPIO, + TEST_DATA3_GPIO, + TEST_DATA4_GPIO, + TEST_DATA5_GPIO, + TEST_DATA6_GPIO, + TEST_DATA7_GPIO, + }, + .output_clk_freq_hz = 1 * 1000 * 1000, + .trans_queue_depth = 8, + .max_transfer_size = 128, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + }; + TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit)); + TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); + + printf("register trans_done event callback\r\n"); + parlio_tx_event_callbacks_t cbs = { + .on_trans_done = test_parlio_tx_done_callback, + }; + TEST_ESP_OK(parlio_tx_unit_register_event_callbacks(tx_unit, &cbs, xTaskGetCurrentTaskHandle())); + + printf("send packets and check event is fired\r\n"); + parlio_transmit_config_t transmit_config = { + .idle_value = 0x00, + }; + uint8_t payload[64] = {0}; + for (int i = 0; i < 64; i++) { + payload[i] = i; + } + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 64 * sizeof(uint8_t) * 8, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, portMAX_DELAY)); + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 64 * sizeof(uint8_t) * 8, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, portMAX_DELAY)); + + TEST_ESP_OK(parlio_tx_unit_disable(tx_unit)); + TEST_ESP_OK(parlio_del_tx_unit(tx_unit)); +}; + +TEST_CASE("parallel_tx_unit_enable_disable", "[parlio_tx]") +{ + printf("install parlio tx unit\r\n"); + parlio_tx_unit_handle_t tx_unit = NULL; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = 8, + .clk_in_gpio_num = -1, // use internal clock source + .valid_gpio_num = -1, // don't generate valid signal + .clk_out_gpio_num = TEST_CLK_GPIO, + .data_gpio_nums = { + TEST_DATA0_GPIO, + TEST_DATA1_GPIO, + TEST_DATA2_GPIO, + TEST_DATA3_GPIO, + TEST_DATA4_GPIO, + TEST_DATA5_GPIO, + TEST_DATA6_GPIO, + TEST_DATA7_GPIO, + }, + .output_clk_freq_hz = 1 * 1000 * 1000, + .trans_queue_depth = 64, + .max_transfer_size = 256, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + }; + TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit)); + TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); + + printf("send packets for multiple times\r\n"); + parlio_transmit_config_t transmit_config = { + .idle_value = 0x00, + }; + uint8_t payload[128] = {0}; + for (int i = 0; i < 128; i++) { + payload[i] = i; + } + for (int j = 0; j < 64; j++) { + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 128 * sizeof(uint8_t) * 8, &transmit_config)); + } + + printf("disable the transaction in the middle\r\n"); + while (parlio_tx_unit_disable(tx_unit) != ESP_OK) { + esp_rom_delay_us(1000); + } + vTaskDelay(pdMS_TO_TICKS(100)); + + printf("resume the transaction and pending packets should continue\r\n"); + TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); + TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1)); + TEST_ESP_OK(parlio_tx_unit_disable(tx_unit)); + TEST_ESP_OK(parlio_del_tx_unit(tx_unit)); +} + +TEST_CASE("parallel_tx_unit_idle_value", "[parlio_tx]") +{ + printf("install parlio tx unit\r\n"); + parlio_tx_unit_handle_t tx_unit = NULL; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = 8, + .clk_in_gpio_num = -1, // use internal clock source + .valid_gpio_num = -1, // don't generate valid signal + .clk_out_gpio_num = TEST_CLK_GPIO, + .data_gpio_nums = { + TEST_DATA0_GPIO, + TEST_DATA1_GPIO, + TEST_DATA2_GPIO, + TEST_DATA3_GPIO, + TEST_DATA4_GPIO, + TEST_DATA5_GPIO, + TEST_DATA6_GPIO, + TEST_DATA7_GPIO, + }, + .output_clk_freq_hz = 1 * 1000 * 1000, + .trans_queue_depth = 4, + .max_transfer_size = 64, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + .flags.io_loop_back = 1, // enable loop back by GPIO matrix, so that we can read the level of the data line by gpio driver + }; + TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit)); + TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); + + printf("send packet with different idle_value\r\n"); + parlio_transmit_config_t transmit_config = { + .idle_value = 0x00, + }; + uint8_t payload[8] = {0}; + for (int i = 0; i < 8; i++) { + payload[i] = i; + } + for (int j = 0; j < 16; j++) { + transmit_config.idle_value = j; + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, sizeof(payload) * 8, &transmit_config)); + TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, 100)); + TEST_ASSERT_EQUAL(j & 0x01, gpio_get_level(TEST_DATA0_GPIO)); + } + + TEST_ESP_OK(parlio_tx_unit_disable(tx_unit)); + TEST_ESP_OK(parlio_del_tx_unit(tx_unit)); +} + +#if SOC_PARLIO_TX_CLK_SUPPORT_GATING +TEST_CASE("parallel_tx_clock_gating", "[paralio_tx]") +{ + printf("install parlio tx unit\r\n"); + parlio_tx_unit_handle_t tx_unit = NULL; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = 2, + .clk_in_gpio_num = -1, // use internal clock source + .valid_gpio_num = TEST_DATA7_GPIO, // generate the valid signal + .clk_out_gpio_num = TEST_CLK_GPIO, + .data_gpio_nums = { + TEST_DATA0_GPIO, + TEST_DATA1_GPIO, + }, + .output_clk_freq_hz = 1 * 1000 * 1000, + .trans_queue_depth = 4, + .max_transfer_size = 64, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + .flags.clk_gate_en = true, // enable clock gating, controlled by the level of TEST_DATA7_GPIO + .flags.io_loop_back = true, // for reading the level of the clock line in IDLE state + }; + TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit)); + TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); + + printf("send packets and see if the clock is gated when there's no transaction on line\r\n"); + parlio_transmit_config_t transmit_config = { + .idle_value = 0x00, + }; + uint8_t payload[8] = {0}; + for (int i = 0; i < 8; i++) { + payload[i] = 0x1B; // 8'b00011011, in PARLIO_BIT_PACK_ORDER_MSB, you should see 2'b00, 2'b01, 2'b10, 2'b11 on the data line + } + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 8 * sizeof(uint8_t) * 8, &transmit_config)); + TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1)); + // check if the level on the clock line is low + TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_CLK_GPIO)); + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 8 * sizeof(uint8_t) * 8, &transmit_config)); + TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1)); + TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_CLK_GPIO)); + TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_CLK_GPIO)); + + TEST_ESP_OK(parlio_tx_unit_disable(tx_unit)); + TEST_ESP_OK(parlio_del_tx_unit(tx_unit)); +} +#endif // SOC_PARLIO_TX_CLK_SUPPORT_GATING diff --git a/components/driver/test_apps/parlio/pytest_parlio_unity.py b/components/driver/test_apps/parlio/pytest_parlio_unity.py new file mode 100644 index 00000000000..5e43dadda5f --- /dev/null +++ b/components/driver/test_apps/parlio/pytest_parlio_unity.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.esp32h2 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'iram_safe', + 'release', + ], + indirect=True, +) +def test_parlio(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('*') + dut.expect_unity_test_output() diff --git a/components/driver/test_apps/parlio/sdkconfig.ci.iram_safe b/components/driver/test_apps/parlio/sdkconfig.ci.iram_safe new file mode 100644 index 00000000000..990b21d96e6 --- /dev/null +++ b/components/driver/test_apps/parlio/sdkconfig.ci.iram_safe @@ -0,0 +1,7 @@ +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_COMPILER_OPTIMIZATION_NONE=y +CONFIG_PARLIO_ISR_IRAM_SAFE=y +# place non-ISR FreeRTOS functions in Flash +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y +# silent the error check, as the error string are stored in rodata, causing RTL check failure +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y diff --git a/components/driver/test_apps/parlio/sdkconfig.ci.release b/components/driver/test_apps/parlio/sdkconfig.ci.release new file mode 100644 index 00000000000..91d93f163e6 --- /dev/null +++ b/components/driver/test_apps/parlio/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/driver/test_apps/parlio/sdkconfig.defaults b/components/driver/test_apps/parlio/sdkconfig.defaults new file mode 100644 index 00000000000..448bb50f824 --- /dev/null +++ b/components/driver/test_apps/parlio/sdkconfig.defaults @@ -0,0 +1,5 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_FREERTOS_HZ=1000 diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index aed9ce17fbc..a944c360b3a 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -105,6 +105,10 @@ if(NOT BOOTLOADER_BUILD) list(APPEND srcs "etm_hal.c") endif() + if(CONFIG_SOC_PARLIO_SUPPORTED) + list(APPEND srcs "parlio_hal.c") + endif() + if(CONFIG_SOC_ADC_DMA_SUPPORTED) list(APPEND srcs "adc_hal.c") endif() diff --git a/components/hal/esp32c6/include/hal/clk_gate_ll.h b/components/hal/esp32c6/include/hal/clk_gate_ll.h index 4e221908e36..c2f80a173e3 100644 --- a/components/hal/esp32c6/include/hal/clk_gate_ll.h +++ b/components/hal/esp32c6/include/hal/clk_gate_ll.h @@ -58,6 +58,8 @@ static inline uint32_t periph_ll_get_clk_en_mask(periph_module_t periph) return PCR_PWM_CLK_EN; case PERIPH_ETM_MODULE: return PCR_ETM_CLK_EN; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_CLK_EN; case PERIPH_AES_MODULE: return PCR_AES_CLK_EN; case PERIPH_SHA_MODULE: @@ -136,6 +138,8 @@ static inline uint32_t periph_ll_get_rst_en_mask(periph_module_t periph, bool en return PCR_PWM_RST_EN; case PERIPH_ETM_MODULE: return PCR_ETM_RST_EN; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_RST_EN; case PERIPH_ECC_MODULE: return PCR_ECC_RST_EN; case PERIPH_TEMPSENSOR_MODULE: @@ -233,6 +237,8 @@ static uint32_t periph_ll_get_clk_en_reg(periph_module_t periph) return PCR_PWM_CONF_REG; case PERIPH_ETM_MODULE: return PCR_ETM_CONF_REG; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_IO_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: @@ -297,6 +303,8 @@ static uint32_t periph_ll_get_rst_en_reg(periph_module_t periph) return PCR_PWM_CONF_REG; case PERIPH_ETM_MODULE: return PCR_ETM_CONF_REG; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_IO_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: diff --git a/components/hal/esp32c6/include/hal/parlio_ll.h b/components/hal/esp32c6/include/hal/parlio_ll.h new file mode 100644 index 00000000000..a260d03521f --- /dev/null +++ b/components/hal/esp32c6/include/hal/parlio_ll.h @@ -0,0 +1,612 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Note that most of the register operations in this layer are non-atomic operations. + +#pragma once + +#include +#include +#include "hal/assert.h" +#include "hal/misc.h" +#include "soc/pcr_struct.h" +#include "soc/parl_io_struct.h" +#include "hal/parlio_types.h" + +#define PARLIO_LL_RX_MAX_BYTES_PER_FRAME 0xFFFF +#define PARLIO_LL_RX_MAX_CLOCK_DIV 0x10000 +#define PARLIO_LL_RX_MAX_TIMEOUT 0xFFFF + +#define PARLIO_LL_TX_MAX_BYTES_PER_FRAME 0xFFFF +#define PARLIO_LL_TX_MAX_BITS_PER_FRAME (PARLIO_LL_TX_MAX_BYTES_PER_FRAME * 8) +#define PARLIO_LL_TX_MAX_CLOCK_DIV 0x10000 + +#define PARLIO_LL_EVENT_TX_FIFO_EMPTY (1 << 0) +#define PARLIO_LL_EVENT_RX_FIFO_FULL (1 << 1) +#define PARLIO_LL_EVENT_TX_EOF (1 << 2) +#define PARLIO_LL_EVENT_TX_MASK (PARLIO_LL_EVENT_TX_FIFO_EMPTY | PARLIO_LL_EVENT_TX_EOF) +#define PARLIO_LL_EVENT_RX_MASK (PARLIO_LL_EVENT_RX_FIFO_FULL) + +#define PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG 15 // TXD[15] can be used a valid signal + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PARLIO_LL_CLK_SRC_XTAL = PARLIO_CLK_SRC_XTAL, + PARLIO_LL_CLK_SRC_PLL_F240M = PARLIO_CLK_SRC_PLL_F240M, + PARLIO_LL_CLK_SRC_PAD, // clock source from GPIO pad +} parlio_ll_clock_source_t; + +typedef enum { + PARLIO_LL_RX_EOF_COND_RX_FULL, /*!< RX unit generates EOF event when it receives enough data */ + PARLIO_LL_RX_EOF_COND_EN_INACTIVE, /*!< RX unit generates EOF event when the external enable signal becomes inactive */ +} parlio_ll_rx_eof_cond_t; + +///////////////////////////////////////RX Unit/////////////////////////////////////// + +/** + * @brief Set the clock source for the RX unit + * + * @param dev Parallel IO register base address + * @param src Clock source + */ +static inline void parlio_ll_rx_set_clock_source(parl_io_dev_t *dev, parlio_ll_clock_source_t src) +{ + (void)dev; + uint32_t clk_sel = 0; + switch (src) { + case PARLIO_LL_CLK_SRC_XTAL: + clk_sel = 0; + break; + case PARLIO_LL_CLK_SRC_PLL_F240M: + clk_sel = 1; + break; + case PARLIO_LL_CLK_SRC_PAD: + clk_sel = 3; + break; + + default: // unsupported clock source + HAL_ASSERT(false); + break; + } + PCR.parl_clk_rx_conf.parl_clk_rx_sel = clk_sel; +} + +/** + * @brief Set the clock divider for the RX unit + * + * @param dev Parallel IO register base address + * @param div Clock divider + */ +static inline void parlio_ll_rx_set_clock_div(parl_io_dev_t *dev, uint32_t div) +{ + (void)dev; + HAL_ASSERT(div > 0 && div <= PARLIO_LL_RX_MAX_CLOCK_DIV); + PCR.parl_clk_rx_conf.parl_clk_rx_div_num = div - 1; +} + +/** + * @brief Reset the RX unit Core clock domain + * + * @param dev Parallel IO register base address + */ +__attribute__((always_inline)) +static inline void parlio_ll_rx_reset_clock(parl_io_dev_t *dev) +{ + (void)dev; + PCR.parl_clk_rx_conf.parl_rx_rst_en = 1; + PCR.parl_clk_rx_conf.parl_rx_rst_en = 0; +} + +/** + * @brief Enable the RX unit Core clock domain + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +__attribute__((always_inline)) +static inline void parlio_ll_rx_enable_clock(parl_io_dev_t *dev, bool en) +{ + (void)dev; + PCR.parl_clk_rx_conf.parl_clk_rx_en = en; +} + +/** + * @brief Set the condition to generate the RX EOF event + * + * @param dev Parallel IO register base address + * @param cond RX EOF condition + */ +static inline void parlio_ll_rx_set_eof_condition(parl_io_dev_t *dev, parlio_ll_rx_eof_cond_t cond) +{ + dev->rx_cfg0.rx_eof_gen_sel = cond; +} + +/** + * @brief Start RX unit to sample the input data + * + * @param dev Parallel IO register base address + * @param en True to start, False to stop + */ +static inline void parlio_ll_rx_start(parl_io_dev_t *dev, bool en) +{ + dev->rx_cfg0.rx_start = en; +} + +/** + * @brief Set the receive length + * + * @note The receive length can be used to generate DMA EOF signal, or to work as a frame end delimiter + * + * @param dev Parallel IO register base address + * @param bitlen Number of bits to receive in the next transaction, bitlen must be a multiple of 8 + */ +static inline void parlio_ll_rx_set_recv_bit_len(parl_io_dev_t *dev, uint32_t bitlen) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->rx_cfg0, rx_data_bytelen, bitlen / 8); +} + +/** + * @brief Set the sub mode of the level controlled receive mode + * + * @param dev Parallel IO register base address + * @param active_level Level of the external enable signal, true for active high, false for active low + */ +static inline void parlio_ll_rx_set_level_recv_mode(parl_io_dev_t *dev, bool active_level) +{ + dev->rx_cfg0.rx_smp_mode_sel = 0; + dev->rx_cfg0.rx_level_submode_sel = !active_level; // 0: active low, 1: active high +} + +/** + * @brief Set the sub mode of the pulse controlled receive mode + * + * @param dev Parallel IO register base address + * @param start_inc Whether the start pulse is counted + * @param end_inc Whether the end pulse is counted + * @param end_by_len Whether to use the frame length to determine the end of the frame + * @param pulse_inv Whether the pulse is inverted + */ +static inline void parlio_ll_rx_set_pulse_recv_mode(parl_io_dev_t *dev, bool start_inc, bool end_inc, bool end_by_len, bool pulse_inv) +{ + uint32_t submode = 0; + uint32_t step = 1; + if (end_by_len) { + submode += 4; + } else { + step = 2; + if (!end_inc) { + submode += 1; + } + } + if (!start_inc) { + submode += step; + } + if (pulse_inv) { + submode += 6; + } + dev->rx_cfg0.rx_smp_mode_sel = 1; + dev->rx_cfg0.rx_pulse_submode_sel = submode; +} + +/** + * @brief Set the receive mode to software controlled receive mode + * + * @param dev Parallel IO register base address + */ +static inline void parlio_ll_rx_set_soft_recv_mode(parl_io_dev_t *dev) +{ + dev->rx_cfg0.rx_smp_mode_sel = 2; +} + +/** + * @brief Whether to start the software controlled receive mode + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_rx_start_soft_recv(parl_io_dev_t *dev, bool en) +{ + dev->rx_cfg0.rx_sw_en = en; +} + +/** + * @brief Set the sample clock edge + * + * @param dev Parallel IO register base address + * @param edge Sample clock edge + */ +static inline void parlio_ll_rx_set_sample_clock_edge(parl_io_dev_t *dev, parlio_sample_edge_t edge) +{ + dev->rx_cfg0.rx_clk_edge_sel = edge; +} + +/** + * @brief Set the order to pack bits into one byte + * + * @param dev Parallel IO register base address + * @param order Packing order + */ +static inline void parlio_ll_rx_set_bit_pack_order(parl_io_dev_t *dev, parlio_bit_pack_order_t order) +{ + dev->rx_cfg0.rx_bit_pack_order = order; +} + +/** + * @brief Set the bus width of the RX unit + * + * @param dev Parallel IO register base address + * @param width Bus width + */ +static inline void parlio_ll_rx_set_bus_width(parl_io_dev_t *dev, uint32_t width) +{ + uint32_t width_sel = 0; + switch (width) { + case 16: + width_sel = 0; + break; + case 8: + width_sel = 1; + break; + case 4: + width_sel = 2; + break; + case 2: + width_sel = 3; + break; + case 1: + width_sel = 4; + break; + default: + HAL_ASSERT(false); + } + dev->rx_cfg0.rx_bus_wid_sel = width_sel; +} + +/** + * @brief Reset RX Async FIFO + * + * @note During the reset of the asynchronous FIFO, it takes two clock cycles to synchronize within AHB clock domain (GDMA) and Core clock domain. + * The reset synchronization must be performed two clock cycles in advance. + * @note If the next frame transfer needs to be reset, you need to first switch to the internal free-running clock, + * and then switch to the actual clock after the reset is completed. + * + * @param dev Parallel IO register base address + */ +__attribute__((always_inline)) +static inline void parlio_ll_rx_reset_fifo(parl_io_dev_t *dev) +{ + dev->rx_cfg0.rx_fifo_srst = 1; + dev->rx_cfg0.rx_fifo_srst = 0; +} + +/** + * @brief Set which data line as the enable signal + * + * @param dev Parallel IO register base address + * @param line_num Data line number (0-15) + */ +static inline void parlio_ll_rx_treat_data_line_as_en(parl_io_dev_t *dev, uint32_t line_num) +{ + dev->rx_cfg1.rx_ext_en_sel = line_num; +} + +/** + * @brief Enable RX timeout feature + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_rx_enable_timeout(parl_io_dev_t *dev, bool en) +{ + dev->rx_cfg1.rx_timeout_en = en; +} + +/** + * @brief Set the threshold of RX timeout + * + * @param dev Parallel IO register base address + * @param thres Threshold of RX timeout + */ +static inline void parlio_ll_rx_set_timeout_thres(parl_io_dev_t *dev, uint32_t thres) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->rx_cfg1, rx_timeout_threshold, thres); +} + +/** + * @brief Update the RX configuration, to make the new configuration take effect + * + * @param dev Parallel IO register base address + */ +static inline void parlio_ll_rx_update_config(parl_io_dev_t *dev) +{ + dev->rx_cfg1.rx_reg_update = 1; + while (dev->rx_cfg1.rx_reg_update); +} + +///////////////////////////////////TX Unit/////////////////////////////////////// + +/** + * @brief Set the clock source for the TX unit + * + * @param dev Parallel IO register base address + * @param src Clock source + */ +static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_ll_clock_source_t src) +{ + (void)dev; + uint32_t clk_sel = 0; + switch (src) { + case PARLIO_LL_CLK_SRC_XTAL: + clk_sel = 0; + break; + case PARLIO_LL_CLK_SRC_PLL_F240M: + clk_sel = 1; + break; + case PARLIO_LL_CLK_SRC_PAD: + clk_sel = 3; + break; + + default: // unsupported clock source + HAL_ASSERT(false); + break; + } + PCR.parl_clk_tx_conf.parl_clk_tx_sel = clk_sel; +} + +/** + * @brief Set the clock divider for the TX unit + * + * @param dev Parallel IO register base address + * @param div Clock divider + */ +static inline void parlio_ll_tx_set_clock_div(parl_io_dev_t *dev, uint32_t div) +{ + (void)dev; + HAL_ASSERT(div > 0 && div <= PARLIO_LL_TX_MAX_CLOCK_DIV); + PCR.parl_clk_tx_conf.parl_clk_tx_div_num = div - 1; +} + +/** + * @brief Reset the TX unit Core clock domain + * + * @param dev Parallel IO register base address + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_reset_clock(parl_io_dev_t *dev) +{ + (void)dev; + PCR.parl_clk_tx_conf.parl_tx_rst_en = 1; + PCR.parl_clk_tx_conf.parl_tx_rst_en = 0; +} + +/** + * @brief Enable the TX unit Core clock domain + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_enable_clock(parl_io_dev_t *dev, bool en) +{ + (void)dev; + PCR.parl_clk_tx_conf.parl_clk_tx_en = en; +} + +/** + * @brief Set the data length to be transmitted + * + * @param dev Parallel IO register base address + * @param bitlen Data length in bits, must be a multiple of 8 + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t bitlen) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->tx_cfg0, tx_bytelen, bitlen / 8); +} + +/** + * @brief Wether to enable the TX clock gating + * + * @note The TXD[7] will be taken as the gating enable signal + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_tx_enable_clock_gating(parl_io_dev_t *dev, bool en) +{ + dev->tx_cfg0.tx_gating_en = en; +} + +/** + * @brief Start TX unit to transmit data + * + * @param dev Parallel IO register base address + * @param en True to start, False to stop + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en) +{ + dev->tx_cfg0.tx_start = en; +} + +/** + * @brief Whether to treat the MSB of TXD as the valid signal + * + * @note If enabled, TXD[15] will work as valid signal, which stay high during data transmission. + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_tx_treat_msb_as_valid(parl_io_dev_t *dev, bool en) +{ + dev->tx_cfg0.tx_hw_valid_en = en; +} + +/** + * @brief Set the sample clock edge + * + * @param dev Parallel IO register base address + * @param edge Sample clock edge + */ +static inline void parlio_ll_tx_set_sample_clock_edge(parl_io_dev_t *dev, parlio_sample_edge_t edge) +{ + dev->tx_cfg0.tx_smp_edge_sel = edge; +} + +/** + * @brief Set the order to unpack bits from a byte + * + * @param dev Parallel IO register base address + * @param order Packing order + */ +static inline void parlio_ll_tx_set_bit_pack_order(parl_io_dev_t *dev, parlio_bit_pack_order_t order) +{ + dev->tx_cfg0.tx_bit_unpack_order = order; +} + +/** + * @brief Set the bus width of the TX unit + * + * @param dev Parallel IO register base address + * @param width Bus width + */ +static inline void parlio_ll_tx_set_bus_width(parl_io_dev_t *dev, uint32_t width) +{ + uint32_t width_sel = 0; + switch (width) { + case 16: + width_sel = 0; + break; + case 8: + width_sel = 1; + break; + case 4: + width_sel = 2; + break; + case 2: + width_sel = 3; + break; + case 1: + width_sel = 4; + break; + default: + HAL_ASSERT(false); + } + dev->tx_cfg0.tx_bus_wid_sel = width_sel; +} + +/** + * @brief Reset TX Async FIFO + * + * @note During the reset of the asynchronous FIFO, it takes two clock cycles to synchronize within AHB clock domain (GDMA) and Core clock domain. + * The reset synchronization must be performed two clock cycles in advance. + * @note If the next frame transfer needs to be reset, you need to first switch to the internal free-running clock, + * and then switch to the actual clock after the reset is completed. + * + * @param dev Parallel IO register base address + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_reset_fifo(parl_io_dev_t *dev) +{ + dev->tx_cfg0.tx_fifo_srst = 1; + dev->tx_cfg0.tx_fifo_srst = 0; +} + +/** + * @brief Set the value to output on the TXD when the TX unit is in IDLE state + * + * @param dev Parallel IO register base address + * @param value Value to output + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_set_idle_data_value(parl_io_dev_t *dev, uint32_t value) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->tx_cfg1, tx_idle_value, value); +} + +/** + * @brief Check whether the TX unit is ready + * + * @param dev Parallel IO register base address + * @return true: ready, false: busy + */ +__attribute__((always_inline)) +static inline bool parlio_ll_tx_is_ready(parl_io_dev_t *dev) +{ + return dev->st.tx_ready; +} + +////////////////////////////////////Interrupt//////////////////////////////////////////////// + +/** + * @brief Enable Parallel IO interrupt for specific event mask + * + * @param dev Parallel IO register base address + * @param mask Event mask + * @param enable True to enable, False to disable + */ +static inline void parlio_ll_enable_interrupt(parl_io_dev_t *dev, uint32_t mask, bool enable) +{ + if (enable) { + dev->int_ena.val |= mask; + } else { + dev->int_ena.val &= ~mask; + } +} + +/** + * @brief Get interrupt status for TX unit + * + * @param dev Parallel IO register base address + * @return Interrupt status + */ +__attribute__((always_inline)) +static inline uint32_t parlio_ll_tx_get_interrupt_status(parl_io_dev_t *dev) +{ + return dev->int_st.val & PARLIO_LL_EVENT_TX_MASK; +} + +/** + * @brief Get interrupt status for RX unit + * + * @param dev Parallel IO register base address + * @return Interrupt status + */ +__attribute__((always_inline)) +static inline uint32_t parlio_ll_rx_get_interrupt_status(parl_io_dev_t *dev) +{ + return dev->int_st.val & PARLIO_LL_EVENT_RX_MASK; +} + +/** + * @brief Clear Parallel IO interrupt status by mask + * + * @param dev Parallel IO register base address + * @param mask Interrupt status mask + */ +__attribute__((always_inline)) +static inline void parlio_ll_clear_interrupt_status(parl_io_dev_t *dev, uint32_t mask) +{ + dev->int_clr.val = mask; +} + +/** + * @brief Get interrupt status register address + * + * @param dev Parallel IO register base address + * @return Register address + */ +static inline volatile void *parlio_ll_get_interrupt_status_reg(parl_io_dev_t *dev) +{ + return &dev->int_st; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32h2/include/hal/clk_gate_ll.h b/components/hal/esp32h2/include/hal/clk_gate_ll.h index f99ba01597b..d5da31a927d 100644 --- a/components/hal/esp32h2/include/hal/clk_gate_ll.h +++ b/components/hal/esp32h2/include/hal/clk_gate_ll.h @@ -57,6 +57,8 @@ static inline uint32_t periph_ll_get_clk_en_mask(periph_module_t periph) return PCR_PWM_CLK_EN; case PERIPH_ETM_MODULE: return PCR_ETM_CLK_EN; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_CLK_EN; case PERIPH_AES_MODULE: return PCR_AES_CLK_EN; case PERIPH_SHA_MODULE: @@ -130,6 +132,8 @@ static inline uint32_t periph_ll_get_rst_en_mask(periph_module_t periph, bool en return PCR_PWM_RST_EN; case PERIPH_ETM_MODULE: return PCR_ETM_RST_EN; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_RST_EN; case PERIPH_TEMPSENSOR_MODULE: return PCR_TSENS_RST_EN; case PERIPH_AES_MODULE: @@ -221,6 +225,8 @@ static uint32_t periph_ll_get_clk_en_reg(periph_module_t periph) return PCR_PWM_CONF_REG; case PERIPH_ETM_MODULE: return PCR_ETM_CONF_REG; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_IO_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: @@ -280,6 +286,8 @@ static uint32_t periph_ll_get_rst_en_reg(periph_module_t periph) return PCR_PWM_CONF_REG; case PERIPH_ETM_MODULE: return PCR_ETM_CONF_REG; + case PERIPH_PARLIO_MODULE: + return PCR_PARL_IO_CONF_REG; case PERIPH_AES_MODULE: return PCR_AES_CONF_REG; case PERIPH_SHA_MODULE: diff --git a/components/hal/esp32h2/include/hal/parlio_ll.h b/components/hal/esp32h2/include/hal/parlio_ll.h new file mode 100644 index 00000000000..ddf7946b99a --- /dev/null +++ b/components/hal/esp32h2/include/hal/parlio_ll.h @@ -0,0 +1,614 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Note that most of the register operations in this layer are non-atomic operations. + +#pragma once + +#include +#include +#include "hal/assert.h" +#include "hal/misc.h" +#include "soc/pcr_struct.h" +#include "soc/parl_io_struct.h" +#include "hal/parlio_types.h" + +#define PARLIO_LL_RX_MAX_BYTES_PER_FRAME 0xFFFF +#define PARLIO_LL_RX_MAX_CLOCK_DIV 0x10000 +#define PARLIO_LL_RX_MAX_TIMEOUT 0xFFFF + +#define PARLIO_LL_TX_MAX_BITS_PER_FRAME 0x7FFFF +#define PARLIO_LL_TX_MAX_CLOCK_DIV 0x10000 + +#define PARLIO_LL_EVENT_TX_FIFO_EMPTY (1 << 0) +#define PARLIO_LL_EVENT_RX_FIFO_FULL (1 << 1) +#define PARLIO_LL_EVENT_TX_EOF (1 << 2) +#define PARLIO_LL_EVENT_TX_MASK (PARLIO_LL_EVENT_TX_FIFO_EMPTY | PARLIO_LL_EVENT_TX_EOF) +#define PARLIO_LL_EVENT_RX_MASK (PARLIO_LL_EVENT_RX_FIFO_FULL) + +#define PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG 7 // TXD[7] can be used a valid signal +#define PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE 7 // TXD[7] can be used as clock gate signal + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PARLIO_LL_CLK_SRC_XTAL = PARLIO_CLK_SRC_XTAL, + PARLIO_LL_CLK_SRC_PLL_F96M = PARLIO_CLK_SRC_PLL_F96M, + PARLIO_LL_CLK_SRC_PAD, // clock source from GPIO pad +} parlio_ll_clock_source_t; + +typedef enum { + PARLIO_LL_RX_EOF_COND_RX_FULL, /*!< RX unit generates EOF event when it receives enough data */ + PARLIO_LL_RX_EOF_COND_EN_INACTIVE, /*!< RX unit generates EOF event when the external enable signal becomes inactive */ +} parlio_ll_rx_eof_cond_t; + +///////////////////////////////////////RX Unit/////////////////////////////////////// + +/** + * @brief Set the clock source for the RX unit + * + * @param dev Parallel IO register base address + * @param src Clock source + */ +static inline void parlio_ll_rx_set_clock_source(parl_io_dev_t *dev, parlio_ll_clock_source_t src) +{ + (void)dev; + uint32_t clk_sel = 0; + switch (src) { + case PARLIO_LL_CLK_SRC_XTAL: + clk_sel = 0; + break; + case PARLIO_LL_CLK_SRC_PLL_F96M: + clk_sel = 1; + break; + case PARLIO_LL_CLK_SRC_PAD: + clk_sel = 3; + break; + + default: // unsupported clock source + HAL_ASSERT(false); + break; + } + PCR.parl_clk_rx_conf.parl_clk_rx_sel = clk_sel; +} + +/** + * @brief Set the clock divider for the RX unit + * + * @param dev Parallel IO register base address + * @param div Clock divider + */ +static inline void parlio_ll_rx_set_clock_div(parl_io_dev_t *dev, uint32_t div) +{ + (void)dev; + HAL_ASSERT(div > 0 && div <= PARLIO_LL_RX_MAX_CLOCK_DIV); + PCR.parl_clk_rx_conf.parl_clk_rx_div_num = div - 1; +} + +/** + * @brief Reset the RX unit Core clock domain + * + * @param dev Parallel IO register base address + */ +static inline void parlio_ll_rx_reset_clock(parl_io_dev_t *dev) +{ + (void)dev; + PCR.parl_clk_rx_conf.parl_rx_rst_en = 1; + PCR.parl_clk_rx_conf.parl_rx_rst_en = 0; +} + +/** + * @brief Enable the RX unit Core clock domain + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_rx_enable_clock(parl_io_dev_t *dev, bool en) +{ + (void)dev; + PCR.parl_clk_rx_conf.parl_clk_rx_en = en; +} + +/** + * @brief Set the condition to generate the RX EOF event + * + * @param dev Parallel IO register base address + * @param cond RX EOF condition + */ +static inline void parlio_ll_rx_set_eof_condition(parl_io_dev_t *dev, parlio_ll_rx_eof_cond_t cond) +{ + dev->rx_genrl_cfg.rx_eof_gen_sel = cond; +} + +/** + * @brief Start RX unit to sample the input data + * + * @param dev Parallel IO register base address + * @param en True to start, False to stop + */ +static inline void parlio_ll_rx_start(parl_io_dev_t *dev, bool en) +{ + dev->rx_start_cfg.rx_start = en; +} + +/** + * @brief Set the receive length + * + * @note The receive length can be used to generate DMA EOF signal, or to work as a frame end delimiter + * + * @param dev Parallel IO register base address + * @param bitlen Number of bits to receive in the next transaction, bitlen must be a multiple of 8 + */ +static inline void parlio_ll_rx_set_recv_bit_len(parl_io_dev_t *dev, uint32_t bitlen) +{ + dev->rx_data_cfg.rx_bitlen = bitlen; +} + +/** + * @brief Set the sub mode of the level controlled receive mode + * + * @param dev Parallel IO register base address + * @param active_level Level of the external enable signal, true for active high, false for active low + */ +static inline void parlio_ll_rx_set_level_recv_mode(parl_io_dev_t *dev, bool active_level) +{ + dev->rx_mode_cfg.rx_smp_mode_sel = 0; + dev->rx_mode_cfg.rx_ext_en_inv = !active_level; // 0: active low, 1: active high +} + +/** + * @brief Set the sub mode of the pulse controlled receive mode + * + * @param dev Parallel IO register base address + * @param start_inc Whether the start pulse is counted + * @param end_inc Whether the end pulse is counted + * @param end_by_len Whether to use the frame length to determine the end of the frame + * @param pulse_inv Whether the pulse is inverted + */ +static inline void parlio_ll_rx_set_pulse_recv_mode(parl_io_dev_t *dev, bool start_inc, bool end_inc, bool end_by_len, bool pulse_inv) +{ + uint32_t submode = 0; + uint32_t step = 1; + if (end_by_len) { + submode += 4; + } else { // end by pulse + step = 2; + if (!end_inc) { + submode += 1; + } + } + if (!start_inc) { + submode += step; + } + dev->rx_mode_cfg.rx_smp_mode_sel = 1; + dev->rx_mode_cfg.rx_pulse_submode_sel = submode; + dev->rx_mode_cfg.rx_ext_en_inv = pulse_inv; +} + +/** + * @brief Set the receive mode to software controlled receive mode + * + * @param dev Parallel IO register base address + */ +static inline void parlio_ll_rx_set_soft_recv_mode(parl_io_dev_t *dev) +{ + dev->rx_mode_cfg.rx_smp_mode_sel = 2; +} + +/** + * @brief Whether to start the software controlled receive mode + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_rx_start_soft_recv(parl_io_dev_t *dev, bool en) +{ + dev->rx_mode_cfg.rx_sw_en = en; +} + +/** + * @brief Set the sample clock edge + * + * @param dev Parallel IO register base address + * @param edge Sample clock edge + */ +static inline void parlio_ll_rx_set_sample_clock_edge(parl_io_dev_t *dev, parlio_sample_edge_t edge) +{ + dev->rx_clk_cfg.rx_clk_i_inv = edge; + dev->rx_clk_cfg.rx_clk_o_inv = edge; +} + +/** + * @brief Set the order to pack bits into one byte + * + * @param dev Parallel IO register base address + * @param order Packing order + */ +static inline void parlio_ll_rx_set_bit_pack_order(parl_io_dev_t *dev, parlio_bit_pack_order_t order) +{ + dev->rx_data_cfg.rx_data_order_inv = order; +} + +/** + * @brief Set the bus width of the RX unit + * + * @param dev Parallel IO register base address + * @param width Bus width + */ +static inline void parlio_ll_rx_set_bus_width(parl_io_dev_t *dev, uint32_t width) +{ + uint32_t width_sel = 0; + switch (width) { + case 8: + width_sel = 3; + break; + case 4: + width_sel = 2; + break; + case 2: + width_sel = 1; + break; + case 1: + width_sel = 0; + break; + default: + HAL_ASSERT(false); + } + dev->rx_data_cfg.rx_bus_wid_sel = width_sel; +} + +/** + * @brief Reset RX Async FIFO + * + * @note During the reset of the asynchronous FIFO, it takes two clock cycles to synchronize within AHB clock domain (GDMA) and Core clock domain. + * The reset synchronization must be performed two clock cycles in advance. + * @note If the next frame transfer needs to be reset, you need to first switch to the internal free-running clock, + * and then switch to the actual clock after the reset is completed. + * + * @param dev Parallel IO register base address + */ +static inline void parlio_ll_rx_reset_fifo(parl_io_dev_t *dev) +{ + dev->fifo_cfg.rx_fifo_srst = 1; + dev->fifo_cfg.rx_fifo_srst = 0; +} + +/** + * @brief Set which data line as the enable signal + * + * @param dev Parallel IO register base address + * @param line_num Data line number (0-15) + */ +static inline void parlio_ll_rx_treat_data_line_as_en(parl_io_dev_t *dev, uint32_t line_num) +{ + dev->rx_mode_cfg.rx_ext_en_sel = line_num; +} + +/** + * @brief Wether to enable the RX clock gating + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_rx_enable_clock_gating(parl_io_dev_t *dev, bool en) +{ + dev->rx_genrl_cfg.rx_gating_en = en; +} + +/** + * @brief Enable RX timeout feature + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_rx_enable_timeout(parl_io_dev_t *dev, bool en) +{ + dev->rx_genrl_cfg.rx_timeout_en = en; +} + +/** + * @brief Set the threshold of RX timeout + * + * @param dev Parallel IO register base address + * @param thres Threshold of RX timeout + */ +static inline void parlio_ll_rx_set_timeout_thres(parl_io_dev_t *dev, uint32_t thres) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(dev->rx_genrl_cfg, rx_timeout_thres, thres); +} + +/** + * @brief Update the RX configuration, to make the new configuration take effect + * + * @param dev Parallel IO register base address + */ +static inline void parlio_ll_rx_update_config(parl_io_dev_t *dev) +{ + dev->reg_update.rx_reg_update = 1; + while (dev->reg_update.rx_reg_update); +} + +///////////////////////////////////TX Unit/////////////////////////////////////// + +/** + * @brief Set the clock source for the TX unit + * + * @param dev Parallel IO register base address + * @param src Clock source + */ +static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_ll_clock_source_t src) +{ + (void)dev; + uint32_t clk_sel = 0; + switch (src) { + case PARLIO_LL_CLK_SRC_XTAL: + clk_sel = 0; + break; + case PARLIO_LL_CLK_SRC_PLL_F96M: + clk_sel = 1; + break; + case PARLIO_LL_CLK_SRC_PAD: + clk_sel = 3; + break; + + default: // unsupported clock source + HAL_ASSERT(false); + break; + } + PCR.parl_clk_tx_conf.parl_clk_tx_sel = clk_sel; +} + +/** + * @brief Set the clock divider for the TX unit + * + * @param dev Parallel IO register base address + * @param div Clock divider + */ +static inline void parlio_ll_tx_set_clock_div(parl_io_dev_t *dev, uint32_t div) +{ + (void)dev; + HAL_ASSERT(div > 0 && div <= PARLIO_LL_TX_MAX_CLOCK_DIV); + PCR.parl_clk_tx_conf.parl_clk_tx_div_num = div - 1; +} + +/** + * @brief Reset the TX unit Core clock domain + * + * @param dev Parallel IO register base address + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_reset_clock(parl_io_dev_t *dev) +{ + (void)dev; + PCR.parl_clk_tx_conf.parl_tx_rst_en = 1; + PCR.parl_clk_tx_conf.parl_tx_rst_en = 0; +} + +/** + * @brief Enable the TX unit Core clock domain + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_enable_clock(parl_io_dev_t *dev, bool en) +{ + (void)dev; + PCR.parl_clk_tx_conf.parl_clk_tx_en = en; +} + +/** + * @brief Set the data length to be transmitted + * + * @param dev Parallel IO register base address + * @param bitlen Data length in bits, must be a multiple of 8 + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t bitlen) +{ + dev->tx_data_cfg.tx_bitlen = bitlen; +} + +/** + * @brief Wether to enable the TX clock gating + * + * @note The MSB of TXD will be taken as the gating enable signal + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_tx_enable_clock_gating(parl_io_dev_t *dev, bool en) +{ + dev->tx_genrl_cfg.tx_gating_en = en; +} + +/** + * @brief Start TX unit to transmit data + * + * @param dev Parallel IO register base address + * @param en True to start, False to stop + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en) +{ + dev->tx_start_cfg.tx_start = en; +} + +/** + * @brief Whether to treat the MSB of TXD as the valid signal + * + * @note If enabled, TXD[15] will work as valid signal, which stay high during data transmission. + * + * @param dev Parallel IO register base address + * @param en True to enable, False to disable + */ +static inline void parlio_ll_tx_treat_msb_as_valid(parl_io_dev_t *dev, bool en) +{ + dev->tx_genrl_cfg.tx_valid_output_en = en; +} + +/** + * @brief Set the sample clock edge + * + * @param dev Parallel IO register base address + * @param edge Sample clock edge + */ +static inline void parlio_ll_tx_set_sample_clock_edge(parl_io_dev_t *dev, parlio_sample_edge_t edge) +{ + dev->tx_clk_cfg.tx_clk_i_inv = edge; + dev->tx_clk_cfg.tx_clk_o_inv = edge; +} + +/** + * @brief Set the order to unpack bits from a byte + * + * @param dev Parallel IO register base address + * @param order Packing order + */ +static inline void parlio_ll_tx_set_bit_pack_order(parl_io_dev_t *dev, parlio_bit_pack_order_t order) +{ + dev->tx_data_cfg.tx_data_order_inv = order; +} + +/** + * @brief Set the bus width of the TX unit + * + * @param dev Parallel IO register base address + * @param width Bus width + */ +static inline void parlio_ll_tx_set_bus_width(parl_io_dev_t *dev, uint32_t width) +{ + uint32_t width_sel = 0; + switch (width) { + case 8: + width_sel = 3; + break; + case 4: + width_sel = 2; + break; + case 2: + width_sel = 1; + break; + case 1: + width_sel = 0; + break; + default: + HAL_ASSERT(false); + } + dev->tx_data_cfg.tx_bus_wid_sel = width_sel; +} + +/** + * @brief Reset TX Async FIFO + * + * @note During the reset of the asynchronous FIFO, it takes two clock cycles to synchronize within AHB clock domain (GDMA) and Core clock domain. + * The reset synchronization must be performed two clock cycles in advance. + * @note If the next frame transfer needs to be reset, you need to first switch to the internal free-running clock, + * and then switch to the actual clock after the reset is completed. + * + * @param dev Parallel IO register base address + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_reset_fifo(parl_io_dev_t *dev) +{ + dev->fifo_cfg.tx_fifo_srst = 1; + dev->fifo_cfg.tx_fifo_srst = 0; +} + +/** + * @brief Set the value to output on the TXD when the TX unit is in IDLE state + * + * @param dev Parallel IO register base address + * @param value Value to output + */ +__attribute__((always_inline)) +static inline void parlio_ll_tx_set_idle_data_value(parl_io_dev_t *dev, uint32_t value) +{ + dev->tx_genrl_cfg.tx_idle_value = value; +} + +/** + * @brief Check whether the TX unit is ready + * + * @param dev Parallel IO register base address + * @return true: ready, false: busy + */ +__attribute__((always_inline)) +static inline bool parlio_ll_tx_is_ready(parl_io_dev_t *dev) +{ + return dev->st.tx_ready; +} + +////////////////////////////////////Interrupt//////////////////////////////////////////////// + +/** + * @brief Enable Parallel IO interrupt for specific event mask + * + * @param dev Parallel IO register base address + * @param mask Event mask + * @param enable True to enable, False to disable + */ +static inline void parlio_ll_enable_interrupt(parl_io_dev_t *dev, uint32_t mask, bool enable) +{ + if (enable) { + dev->int_ena.val |= mask; + } else { + dev->int_ena.val &= ~mask; + } +} + +/** + * @brief Get interrupt status for TX unit + * + * @param dev Parallel IO register base address + * @return Interrupt status + */ +__attribute__((always_inline)) +static inline uint32_t parlio_ll_tx_get_interrupt_status(parl_io_dev_t *dev) +{ + return dev->int_st.val & PARLIO_LL_EVENT_TX_MASK; +} + +/** + * @brief Get interrupt status for RX unit + * + * @param dev Parallel IO register base address + * @return Interrupt status + */ +__attribute__((always_inline)) +static inline uint32_t parlio_ll_rx_get_interrupt_status(parl_io_dev_t *dev) +{ + return dev->int_st.val & PARLIO_LL_EVENT_RX_MASK; +} + +/** + * @brief Clear Parallel IO interrupt status by mask + * + * @param dev Parallel IO register base address + * @param mask Interrupt status mask + */ +__attribute__((always_inline)) +static inline void parlio_ll_clear_interrupt_status(parl_io_dev_t *dev, uint32_t mask) +{ + dev->int_clr.val = mask; +} + +/** + * @brief Get interrupt status register address + * + * @param dev Parallel IO register base address + * @return Register address + */ +static inline volatile void *parlio_ll_get_interrupt_status_reg(parl_io_dev_t *dev) +{ + return &dev->int_st; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/include/hal/gdma_types.h b/components/hal/include/hal/gdma_types.h index 1f2027bdab6..564d793f955 100644 --- a/components/hal/include/hal/gdma_types.h +++ b/components/hal/include/hal/gdma_types.h @@ -27,6 +27,7 @@ typedef enum { GDMA_TRIG_PERIPH_LCD, /*!< GDMA trigger peripheral: LCD */ GDMA_TRIG_PERIPH_CAM, /*!< GDMA trigger peripheral: CAM */ GDMA_TRIG_PERIPH_RMT, /*!< GDMA trigger peripheral: RMT */ + GDMA_TRIG_PERIPH_PARLIO, /*!< GDMA trigger peripheral: PARLIO */ } gdma_trigger_peripheral_t; /** diff --git a/components/hal/include/hal/parlio_hal.h b/components/hal/include/hal/parlio_hal.h new file mode 100644 index 00000000000..69dffbabd03 --- /dev/null +++ b/components/hal/include/hal/parlio_hal.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/******************************************************************************* + * NOTICE + * The hal is not public api, don't use in application code. + * See readme.md in hal/include/hal/readme.md + ******************************************************************************/ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct parl_io_dev_t *parlio_soc_handle_t; // Parallel IO SOC layer handle + +/** + * @brief HAL context type of Parallel IO driver + */ +typedef struct { + parlio_soc_handle_t regs; /*!< Parallel IO Register base address */ +} parlio_hal_context_t; + +/** + * @brief Initialize the Parallel IO HAL driver + * + * @param hal: Parallel IO HAL context + */ +void parlio_hal_init(parlio_hal_context_t *hal); + +/** + * @brief Deinitialize the Parallel IO HAL driver + * + * @param hal: Parallel IO HAL context + */ +void parlio_hal_deinit(parlio_hal_context_t *hal); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/include/hal/parlio_types.h b/components/hal/include/hal/parlio_types.h new file mode 100644 index 00000000000..53b99c59aba --- /dev/null +++ b/components/hal/include/hal/parlio_types.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "soc/soc_caps.h" +#include "soc/clk_tree_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Parallel IO sample edge + */ +typedef enum { + PARLIO_SAMPLE_EDGE_NEG, /*!< Sample data on falling edge of clock */ + PARLIO_SAMPLE_EDGE_POS, /*!< Sample data on rising edge of clock */ +} parlio_sample_edge_t; + +/** + * @brief Parallel IO bit packing order + * + * Data in memory: + * Byte 0: MSB < B0.7 B0.6 B0.5 B0.4 B0.3 B0.2 B0.1 B0.0 > LSB + * Byte 1: MSB < B1.7 B1.6 B1.5 B1.4 B1.3 B1.2 B1.1 B1.0 > LSB + * + * Output on line (PARLIO_BIT_PACK_ORDER_LSB): + * Cycle 0 Cycle 1 Cycle 2 ---> time + * GPIO 0: B0.0 B0.4 B1.0 + * GPIO 1: B0.1 B0.5 B1.1 + * GPIO 2: B0.2 B0.6 B1.2 + * GPIO 3: B0.3 B0.7 B1.3 + * + * Output on line (PARLIO_BIT_PACK_ORDER_MSB): + * Cycle 0 Cycle 1 Cycle 2 ---> time + * GPIO 0: B0.4 B0.0 B1.4 + * GPIO 1: B0.5 B0.1 B1.5 + * GPIO 2: B0.6 B0.2 B1.6 + * GPIO 3: B0.7 B0.3 B1.7 + */ +typedef enum { + PARLIO_BIT_PACK_ORDER_LSB, /*!< Bit pack order: LSB */ + PARLIO_BIT_PACK_ORDER_MSB, /*!< Bit pack order: MSB */ +} parlio_bit_pack_order_t; + +#if SOC_PARLIO_SUPPORTED +/** + * @brief Parallel IO clock source + * @note User should select the clock source based on the power and resolution requirement + */ +typedef soc_periph_parlio_clk_src_t parlio_clock_source_t; + +/// Maximum data width of TX unit +#define PARLIO_TX_UNIT_MAX_DATA_WIDTH SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH +#else +typedef int parlio_clock_source_t; +#define PARLIO_TX_UNIT_MAX_DATA_WIDTH 0 +#endif // SOC_PARLIO_SUPPORTED + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/parlio_hal.c b/components/hal/parlio_hal.c new file mode 100644 index 00000000000..d4430088293 --- /dev/null +++ b/components/hal/parlio_hal.c @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "hal/parlio_hal.h" +#include "hal/parlio_ll.h" + +void parlio_hal_init(parlio_hal_context_t *hal) +{ + hal->regs = &PARL_IO; +} + +void parlio_hal_deinit(parlio_hal_context_t *hal) +{ + hal->regs = NULL; +} diff --git a/components/soc/CMakeLists.txt b/components/soc/CMakeLists.txt index 162664116cf..35572de5383 100644 --- a/components/soc/CMakeLists.txt +++ b/components/soc/CMakeLists.txt @@ -78,6 +78,10 @@ if(CONFIG_SOC_LCDCAM_SUPPORTED OR CONFIG_SOC_LCD_I80_SUPPORTED) list(APPEND srcs "${target}/lcd_periph.c") endif() +if(CONFIG_SOC_PARLIO_SUPPORTED) + list(APPEND srcs "${target}/parlio_periph.c") +endif() + if(CONFIG_SOC_MCPWM_SUPPORTED) list(APPEND srcs "${target}/mcpwm_periph.c") endif() diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index 1edba08f72c..8459b1fd3f0 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -39,6 +39,10 @@ config SOC_ETM_SUPPORTED bool default y +config SOC_PARLIO_SUPPORTED + bool + default y + config SOC_BT_SUPPORTED bool default y @@ -667,6 +671,30 @@ config SOC_MCPWM_CAPTURE_CLK_FROM_GROUP bool default y +config SOC_PARLIO_GROUPS + int + default 1 + +config SOC_PARLIO_TX_UNITS_PER_GROUP + int + default 1 + +config SOC_PARLIO_RX_UNITS_PER_GROUP + int + default 1 + +config SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH + int + default 16 + +config SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH + int + default 16 + +config SOC_PARLIO_TX_RX_SHARE_INTERRUPT + bool + default y + config SOC_RSA_MAX_BIT_LEN int default 3072 diff --git a/components/soc/esp32c6/include/soc/clk_tree_defs.h b/components/soc/esp32c6/include/soc/clk_tree_defs.h index 62f2c0d013a..6e421b64374 100644 --- a/components/soc/esp32c6/include/soc/clk_tree_defs.h +++ b/components/soc/esp32c6/include/soc/clk_tree_defs.h @@ -405,6 +405,22 @@ typedef enum { LEDC_USE_RTC8M_CLK __attribute__((deprecated("please use 'LEDC_USE_RC_FAST_CLK' instead"))) = LEDC_USE_RC_FAST_CLK, /*!< Alias of 'LEDC_USE_RC_FAST_CLK' */ } soc_periph_ledc_clk_src_legacy_t; +//////////////////////////////////////////////////PARLIO//////////////////////////////////////////////////////////////// + +/** + * @brief Array initializer for all supported clock sources of PARLIO + */ +#define SOC_PARLIO_CLKS {SOC_MOD_CLK_XTAL, SOC_MOD_CLK_PLL_F240M} + +/** + * @brief PARLIO clock source + */ +typedef enum { + PARLIO_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */ + PARLIO_CLK_SRC_PLL_F240M = SOC_MOD_CLK_PLL_F240M, /*!< Select PLL_F240M as the source clock */ + PARLIO_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F240M, /*!< Select PLL_F240M as the default clock choice */ +} soc_periph_parlio_clk_src_t; + #ifdef __cplusplus } #endif diff --git a/components/soc/esp32c6/include/soc/periph_defs.h b/components/soc/esp32c6/include/soc/periph_defs.h index 90e8d777fdb..28bbdb8a46b 100644 --- a/components/soc/esp32c6/include/soc/periph_defs.h +++ b/components/soc/esp32c6/include/soc/periph_defs.h @@ -37,6 +37,7 @@ typedef enum { PERIPH_GDMA_MODULE, PERIPH_MCPWM0_MODULE, PERIPH_ETM_MODULE, + PERIPH_PARLIO_MODULE, PERIPH_SYSTIMER_MODULE, PERIPH_SARADC_MODULE, PERIPH_TEMPSENSOR_MODULE, diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index 08d8f5d649c..81c84d64c31 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -34,6 +34,7 @@ #define SOC_MCPWM_SUPPORTED 1 #define SOC_TWAI_SUPPORTED 1 #define SOC_ETM_SUPPORTED 1 +#define SOC_PARLIO_SUPPORTED 1 #define SOC_BT_SUPPORTED 1 #define SOC_IEEE802154_SUPPORTED 1 #define SOC_ASYNC_MEMCPY_SUPPORTED 1 @@ -279,6 +280,14 @@ /*------------------------ USB SERIAL JTAG CAPS ------------------------------*/ // #define SOC_USB_SERIAL_JTAG_SUPPORT_LIGHT_SLEEP (1) /*!< Support to maintain minimum usb communication during light sleep */ // TODO: IDF-6395 +/*-------------------------- PARLIO CAPS --------------------------------------*/ +#define SOC_PARLIO_GROUPS 1U /*!< Number of parallel IO peripherals */ +#define SOC_PARLIO_TX_UNITS_PER_GROUP 1U /*!< number of TX units in each group */ +#define SOC_PARLIO_RX_UNITS_PER_GROUP 1U /*!< number of RX units in each group */ +#define SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH 16 /*!< Number of data lines of the TX unit */ +#define SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH 16 /*!< Number of data lines of the RX unit */ +#define SOC_PARLIO_TX_RX_SHARE_INTERRUPT 1 /*!< TX and RX unit share the same interrupt source number */ + /*--------------------------- RSA CAPS ---------------------------------------*/ #define SOC_RSA_MAX_BIT_LEN (3072) diff --git a/components/soc/esp32c6/parlio_periph.c b/components/soc/esp32c6/parlio_periph.c new file mode 100644 index 00000000000..40ac06320c2 --- /dev/null +++ b/components/soc/esp32c6/parlio_periph.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/parlio_periph.h" +#include "soc/gpio_sig_map.h" + +const parlio_signal_conn_t parlio_periph_signals = { + .groups = { + [0] = { + .module = PERIPH_PARLIO_MODULE, + .tx_irq_id = ETS_PARL_IO_INTR_SOURCE, + .rx_irq_id = ETS_PARL_IO_INTR_SOURCE, + .tx_units = { + [0] = { + .data_sigs = { + PARL_TX_DATA0_IDX, + PARL_TX_DATA1_IDX, + PARL_TX_DATA2_IDX, + PARL_TX_DATA3_IDX, + PARL_TX_DATA4_IDX, + PARL_TX_DATA5_IDX, + PARL_TX_DATA6_IDX, + PARL_TX_DATA7_IDX, + PARL_TX_DATA8_IDX, + PARL_TX_DATA9_IDX, + PARL_TX_DATA10_IDX, + PARL_TX_DATA11_IDX, + PARL_TX_DATA12_IDX, + PARL_TX_DATA13_IDX, + PARL_TX_DATA14_IDX, + PARL_TX_DATA15_IDX, + }, + .clk_out_sig = PARL_TX_CLK_OUT_IDX, + .clk_in_sig = PARL_TX_CLK_IN_IDX, + } + }, + .rx_units = { + [0] = { + .data_sigs = { + PARL_RX_DATA0_IDX, + PARL_RX_DATA1_IDX, + PARL_RX_DATA2_IDX, + PARL_RX_DATA3_IDX, + PARL_RX_DATA4_IDX, + PARL_RX_DATA5_IDX, + PARL_RX_DATA6_IDX, + PARL_RX_DATA7_IDX, + PARL_RX_DATA8_IDX, + PARL_RX_DATA9_IDX, + PARL_RX_DATA10_IDX, + PARL_RX_DATA11_IDX, + PARL_RX_DATA12_IDX, + PARL_RX_DATA13_IDX, + PARL_RX_DATA14_IDX, + PARL_RX_DATA15_IDX, + }, + .clk_out_sig = -1, + .clk_in_sig = PARL_RX_CLK_IN_IDX, + } + } + }, + }, +}; diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index 2eceb915e7b..398c94501ae 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -79,6 +79,10 @@ config SOC_RMT_SUPPORTED bool default y +config SOC_PARLIO_SUPPORTED + bool + default y + config SOC_GPSPI_SUPPORTED bool default y @@ -615,6 +619,34 @@ config SOC_MCPWM_CAPTURE_CLK_FROM_GROUP bool default y +config SOC_PARLIO_GROUPS + int + default 1 + +config SOC_PARLIO_TX_UNITS_PER_GROUP + int + default 1 + +config SOC_PARLIO_RX_UNITS_PER_GROUP + int + default 1 + +config SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH + int + default 8 + +config SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH + int + default 8 + +config SOC_PARLIO_TX_CLK_SUPPORT_GATING + bool + default y + +config SOC_PARLIO_TRANS_BIT_ALIGN + bool + default y + config SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH int default 128 diff --git a/components/soc/esp32h2/include/soc/clk_tree_defs.h b/components/soc/esp32h2/include/soc/clk_tree_defs.h index b0866529b81..146308bd292 100644 --- a/components/soc/esp32h2/include/soc/clk_tree_defs.h +++ b/components/soc/esp32h2/include/soc/clk_tree_defs.h @@ -415,6 +415,22 @@ typedef enum { LEDC_USE_RTC8M_CLK __attribute__((deprecated("please use 'LEDC_USE_RC_FAST_CLK' instead"))) = LEDC_USE_RC_FAST_CLK, /*!< Alias of 'LEDC_USE_RC_FAST_CLK' */ } soc_periph_ledc_clk_src_legacy_t; +//////////////////////////////////////////////////PARLIO//////////////////////////////////////////////////////////////// + +/** + * @brief Array initializer for all supported clock sources of PARLIO + */ +#define SOC_PARLIO_CLKS {SOC_MOD_CLK_XTAL, SOC_MOD_CLK_PLL_F96M} + +/** + * @brief PARLIO clock source + */ +typedef enum { + PARLIO_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */ + PARLIO_CLK_SRC_PLL_F96M = SOC_MOD_CLK_PLL_F96M, /*!< Select PLL_F96M as the source clock */ + PARLIO_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F96M, /*!< Select PLL_F96M as the default clock choice */ +} soc_periph_parlio_clk_src_t; + #ifdef __cplusplus } #endif diff --git a/components/soc/esp32h2/include/soc/parl_io_struct.h b/components/soc/esp32h2/include/soc/parl_io_struct.h index b7dd931b8b2..97dc285531d 100644 --- a/components/soc/esp32h2/include/soc/parl_io_struct.h +++ b/components/soc/esp32h2/include/soc/parl_io_struct.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -467,7 +467,7 @@ typedef union { } parl_io_version_reg_t; -typedef struct { +typedef struct parl_io_dev_t { volatile parl_io_rx_mode_cfg_reg_t rx_mode_cfg; volatile parl_io_rx_data_cfg_reg_t rx_data_cfg; volatile parl_io_rx_genrl_cfg_reg_t rx_genrl_cfg; diff --git a/components/soc/esp32h2/include/soc/periph_defs.h b/components/soc/esp32h2/include/soc/periph_defs.h index 3b0c4e1794b..502ca39bd4b 100644 --- a/components/soc/esp32h2/include/soc/periph_defs.h +++ b/components/soc/esp32h2/include/soc/periph_defs.h @@ -40,6 +40,7 @@ typedef enum { PERIPH_GDMA_MODULE, PERIPH_MCPWM0_MODULE, PERIPH_ETM_MODULE, + PERIPH_PARLIO_MODULE, PERIPH_SYSTIMER_MODULE, PERIPH_SARADC_MODULE, PERIPH_TEMPSENSOR_MODULE, diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index 942e6c47ecb..077bfe19667 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -48,6 +48,7 @@ #define SOC_SDM_SUPPORTED 1 #define SOC_ETM_SUPPORTED 1 #define SOC_RMT_SUPPORTED 1 +#define SOC_PARLIO_SUPPORTED 1 #define SOC_GPSPI_SUPPORTED 1 #define SOC_LEDC_SUPPORTED 1 #define SOC_I2C_SUPPORTED 1 @@ -268,6 +269,15 @@ /*------------------------ USB SERIAL JTAG CAPS ------------------------------*/ // #define SOC_USB_SERIAL_JTAG_SUPPORT_LIGHT_SLEEP (1) /*!< Support to maintain minimum usb communication during light sleep */ // TODO: IDF-6395 +/*-------------------------- PARLIO CAPS --------------------------------------*/ +#define SOC_PARLIO_GROUPS 1U /*!< Number of parallel IO peripherals */ +#define SOC_PARLIO_TX_UNITS_PER_GROUP 1U /*!< number of TX units in each group */ +#define SOC_PARLIO_RX_UNITS_PER_GROUP 1U /*!< number of RX units in each group */ +#define SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH 8 /*!< Number of data lines of the TX unit */ +#define SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH 8 /*!< Number of data lines of the RX unit */ +#define SOC_PARLIO_TX_CLK_SUPPORT_GATING 1 /*!< Support gating TX clock */ +#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */ + // TODO: IDF-6267 (Copy from esp32c6, need check) /*-------------------------- RTC CAPS --------------------------------------*/ #define SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH (128) diff --git a/components/soc/esp32h2/parlio_periph.c b/components/soc/esp32h2/parlio_periph.c new file mode 100644 index 00000000000..6cc86f13cbf --- /dev/null +++ b/components/soc/esp32h2/parlio_periph.c @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/parlio_periph.h" +#include "soc/gpio_sig_map.h" + +const parlio_signal_conn_t parlio_periph_signals = { + .groups = { + [0] = { + .module = PERIPH_PARLIO_MODULE, + .tx_irq_id = ETS_PARL_IO_TX_INTR_SOURCE, + .rx_irq_id = ETS_PARL_IO_RX_INTR_SOURCE, + .tx_units = { + [0] = { + .data_sigs = { + PARL_TX_DATA0_IDX, + PARL_TX_DATA1_IDX, + PARL_TX_DATA2_IDX, + PARL_TX_DATA3_IDX, + PARL_TX_DATA4_IDX, + PARL_TX_DATA5_IDX, + PARL_TX_DATA6_IDX, + PARL_TX_DATA7_IDX, + }, + .clk_out_sig = PARL_TX_CLK_OUT_IDX, + .clk_in_sig = PARL_TX_CLK_IN_IDX, + } + }, + .rx_units = { + [0] = { + .data_sigs = { + PARL_RX_DATA0_IDX, + PARL_RX_DATA1_IDX, + PARL_RX_DATA2_IDX, + PARL_RX_DATA3_IDX, + PARL_RX_DATA4_IDX, + PARL_RX_DATA5_IDX, + PARL_RX_DATA6_IDX, + PARL_RX_DATA7_IDX, + }, + .clk_out_sig = PARL_RX_CLK_OUT_IDX, + .clk_in_sig = PARL_RX_CLK_IN_IDX, + } + } + }, + }, +}; diff --git a/components/soc/include/soc/parlio_periph.h b/components/soc/include/soc/parlio_periph.h new file mode 100644 index 00000000000..bd248901bf6 --- /dev/null +++ b/components/soc/include/soc/parlio_periph.h @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "soc/soc_caps.h" +#include "soc/periph_defs.h" +#include "soc/parl_io_reg.h" +#include "soc/parl_io_struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + struct { + struct { + const int data_sigs[SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH]; + const int clk_out_sig; + const int clk_in_sig; + } tx_units[SOC_PARLIO_TX_UNITS_PER_GROUP]; + struct { + const int data_sigs[SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH]; + const int clk_out_sig; + const int clk_in_sig; + } rx_units[SOC_PARLIO_RX_UNITS_PER_GROUP]; + const int tx_irq_id; + const int rx_irq_id; + const periph_module_t module; + } groups[SOC_PARLIO_GROUPS]; +} parlio_signal_conn_t; + +extern const parlio_signal_conn_t parlio_periph_signals; + +#ifdef __cplusplus +} +#endif diff --git a/docs/conf_common.py b/docs/conf_common.py index 6b51e808775..3afbda32d70 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -74,6 +74,8 @@ DEDIC_GPIO_DOCS = ['api-reference/peripherals/dedic_gpio.rst'] +PARLIO_DOCS = ['api-reference/peripherals/parlio.rst'] + PCNT_DOCS = ['api-reference/peripherals/pcnt.rst'] RMT_DOCS = ['api-reference/peripherals/rmt.rst'] @@ -173,6 +175,7 @@ 'SOC_USB_SERIAL_JTAG_SUPPORTED':USB_SERIAL_JTAG_DOCS, 'SOC_DEDICATED_GPIO_SUPPORTED':DEDIC_GPIO_DOCS, 'SOC_SPIRAM_SUPPORTED':SPIRAM_DOCS, + 'SOC_PARLIO_SUPPORTED':PARLIO_DOCS, 'SOC_PCNT_SUPPORTED':PCNT_DOCS, 'SOC_RMT_SUPPORTED':RMT_DOCS, 'SOC_DAC_SUPPORTED':DAC_DOCS, diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 05a845a494d..bfe48269824 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -88,6 +88,8 @@ INPUT = \ $(PROJECT_PATH)/components/driver/mcpwm/include/driver/mcpwm_sync.h \ $(PROJECT_PATH)/components/driver/mcpwm/include/driver/mcpwm_timer.h \ $(PROJECT_PATH)/components/driver/mcpwm/include/driver/mcpwm_types.h \ + $(PROJECT_PATH)/components/driver/parlio/include/driver/parlio_tx.h \ + $(PROJECT_PATH)/components/driver/parlio/include/driver/parlio_types.h \ $(PROJECT_PATH)/components/driver/pcnt/include/driver/pulse_cnt.h \ $(PROJECT_PATH)/components/driver/rmt/include/driver/rmt_common.h \ $(PROJECT_PATH)/components/driver/rmt/include/driver/rmt_encoder.h \ @@ -199,6 +201,7 @@ INPUT = \ $(PROJECT_PATH)/components/hal/include/hal/lcd_types.h \ $(PROJECT_PATH)/components/hal/include/hal/ledc_types.h \ $(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \ + $(PROJECT_PATH)/components/hal/include/hal/parlio_types.h \ $(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \ $(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \ $(PROJECT_PATH)/components/hal/include/hal/rtc_io_types.h \ diff --git a/docs/en/api-reference/peripherals/gpio/esp32h2.inc b/docs/en/api-reference/peripherals/gpio/esp32h2.inc index 4ecf2b1b9e3..7dffaa29862 100644 --- a/docs/en/api-reference/peripherals/gpio/esp32h2.inc +++ b/docs/en/api-reference/peripherals/gpio/esp32h2.inc @@ -62,11 +62,11 @@ The table below provides more information on pin usage, and please note the comm - Strapping pin * - GPIO10 - - + - Analog comparator reference voltage - * - GPIO11 - - + - Analog comparator input (non-inverting) - * - GPIO12 @@ -137,7 +137,8 @@ The table below provides more information on pin usage, and please note the comm - Strapping pin: GPIO2, GPIO3, GPIO8, GPIO9, and GPIO25 are strapping pins. For more infomation, please refer to `ESP32H2 datasheet `_. - SPI0/1: GPIO15-21 are usually used for SPI flash and not recommended for other uses. - - USB-JTAG: GPIO 26 and 27 are used by USB-JTAG by default. In order to use them as GPIOs, USB-JTAG will be disabled by the drivers. - - For chip variants with an SiP flash built in, GPIO15 ~ GPIO21 are dedicated to connecting the SiP flash; therefore, only the remaining 21 GPIO pins are available. + - USB-Serial-JTAG: GPIO 26 and 27 are used by USB-Serial-JTAG by default. In order to use them as GPIOs, USB-Serial-JTAG will be disabled by the drivers. + - For chip variants with an SiP flash built in, GPIO15 ~ GPIO21 are dedicated to connecting the SiP flash and are not fan-out to the external pins. In addition, GPIO6 ~ GPIO7 are also not fan-out to the external pins. In conclusion, only GPIO0~ GPIO5, GPIO8~ GPIO14, GPIO22~ GPIO27 are available to users. + - For chip variant without SiP flash, apart from the flash IOs mentioned above, GPIO22 is not fan-out to the external pin, thus they're not available to users. --- diff --git a/docs/en/api-reference/peripherals/index.rst b/docs/en/api-reference/peripherals/index.rst index da7e5cee64c..14e72e4d06f 100644 --- a/docs/en/api-reference/peripherals/index.rst +++ b/docs/en/api-reference/peripherals/index.rst @@ -22,6 +22,7 @@ Peripherals API lcd ledc :SOC_MCPWM_SUPPORTED: mcpwm + :SOC_PARLIO_SUPPORTED: parlio :SOC_PCNT_SUPPORTED: pcnt :SOC_RMT_SUPPORTED: rmt :esp32: sd_pullup_requirements diff --git a/docs/en/api-reference/peripherals/parlio.rst b/docs/en/api-reference/peripherals/parlio.rst new file mode 100644 index 00000000000..48014f96762 --- /dev/null +++ b/docs/en/api-reference/peripherals/parlio.rst @@ -0,0 +1,27 @@ +Parallel IO +=========== + +Introduction +------------ + +The Parallel IO peripheral is a general purpose parallel interface that can be used to connect to external devices such as LED matrix, LCD display, Printer and Camera. The peripheral has independent TX and RX units. Each unit can have up to 8 or 16 data signals plus 1 or 2 clock signals. [1]_ + +.. warning:: + At the moment, the Parallel IO driver only supports TX mode. The RX feature is still working in progress. + + +Application Examples +-------------------- + +* Simple REG LED Matrix with HUB75 interface: :example:`peripherals/parlio/simple_rgb_led_matrix`. + + +API Reference +------------- + +.. include-build-file:: inc/parlio_tx.inc +.. include-build-file:: inc/components/driver/parlio/include/driver/parlio_types.inc +.. include-build-file:: inc/components/hal/include/hal/parlio_types.inc + +.. [1] + Different ESP chip series might have different numbers of PARLIO TX/RX instances, and the maximum data bus can also be different. For more details, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > Chapter *Parallel IO (PARLIO)* [`PDF <{IDF_TARGET_TRM_EN_URL}#parlio>`__]. The driver will not forbid you from applying for more driver objects, but it will return error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g. :cpp:func:`parlio_new_tx_unit`). diff --git a/docs/zh_CN/api-reference/peripherals/gpio/esp32h2.inc b/docs/zh_CN/api-reference/peripherals/gpio/esp32h2.inc index 08402bb5785..80f517b43c4 100644 --- a/docs/zh_CN/api-reference/peripherals/gpio/esp32h2.inc +++ b/docs/zh_CN/api-reference/peripherals/gpio/esp32h2.inc @@ -64,11 +64,11 @@ - Strapping 管脚 * - GPIO10 - - + - 模拟比较器外部参考电压 - * - GPIO11 - - + - 模拟比较器同相输入 - * - GPIO12 @@ -137,9 +137,10 @@ .. note:: - - Strapping 管脚:GPIO2、GPIO3、GPIO8、GPIO9 和 GPIO25 是 Strapping 管脚。更多信息请参考 `ESP32-H2 技术规格书 `_。 - - SPI0/1:GPIO15-21 通常用于 SPI flash,不推荐用于其他用途。 - - USB-JTAG:GPIO26 和 GPIO27 默认用于 USB-JTAG。用做 GPIO 时驱动程序将禁用 USB-JTAG。 - - 对于内置 SiP flash 的芯片型号,GPIO15 ~ GPIO21 专门用于连接 SiP flash; 因此,对于这类芯片只有 21 个 GPIO 管脚可用。 + - Strapping 管脚: GPIO2、GPIO3、GPIO8、GPIO9 和 GPIO25 是 Strapping 管脚。更多信息请参考 `ESP32-H2 技术规格书 `_。 + - SPI0/1: GPIO15-21 通常用于 SPI flash, 不推荐用于其他用途。 + - USB-Serial-JTAG: GPIO26 和 GPIO27 默认用于 USB-Serial-JTAG。用做 GPIO 时驱动程序将禁用 USB-Serial-JTAG。 + - 对于合封了 flash 的芯片型号, GPIO15 ~ GPIO21 专门用于连接该 flash, 并未引出至芯片管脚。且 GPIO6 ~ GPIO7 也未引出至芯片管脚,用户不可用。用户可配置使用其他剩余的 19 个 GPIO 管脚, 编号为: GPIO0 ~ GPIO5、GPIO8 ~ GPIO14、GPIO22 ~ GPIO27。 + - 对于未合封 flash 的芯片型号, 除了以上提到的给 Flash 专用的 GPIO 以外, GPIO22 也并未引出至芯片管脚,用户不可用。 --- diff --git a/docs/zh_CN/api-reference/peripherals/index.rst b/docs/zh_CN/api-reference/peripherals/index.rst index 7e16f3864bf..6f0b99c2a68 100644 --- a/docs/zh_CN/api-reference/peripherals/index.rst +++ b/docs/zh_CN/api-reference/peripherals/index.rst @@ -22,6 +22,7 @@ lcd ledc :SOC_MCPWM_SUPPORTED: mcpwm + :SOC_PARLIO_SUPPORTED: parlio :SOC_PCNT_SUPPORTED: pcnt :SOC_RMT_SUPPORTED: rmt :SOC_SDMMC_HOST_SUPPORTED: sd_pullup_requirements diff --git a/docs/zh_CN/api-reference/peripherals/parlio.rst b/docs/zh_CN/api-reference/peripherals/parlio.rst new file mode 100644 index 00000000000..eca498c467d --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/parlio.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/peripherals/parlio.rst diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index c54e7df1042..e50f99c9932 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -111,6 +111,18 @@ examples/peripherals/mcpwm/mcpwm_bldc_hall_control: temporary: true reason: lack of runners +examples/peripherals/parlio: + disable: + - if: SOC_PARLIO_SUPPORTED != 1 + +examples/peripherals/parlio/simple_rgb_led_matrix: + disable: + - if: SOC_PARLIO_SUPPORTED != 1 or SOC_DEDICATED_GPIO_SUPPORTED != 1 + disable_test: + - if: IDF_TARGET != "esp32c6" + temporary: true + reason: lack of runners + examples/peripherals/pcnt: disable: - if: SOC_PCNT_SUPPORTED != 1 diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/CMakeLists.txt b/examples/peripherals/parlio/simple_rgb_led_matrix/CMakeLists.txt new file mode 100644 index 00000000000..b7b9e298f5c --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(rgb_led_matrix) diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/README.md b/examples/peripherals/parlio/simple_rgb_led_matrix/README.md new file mode 100644 index 00000000000..d995f56c6d9 --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/README.md @@ -0,0 +1,89 @@ +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +# Parallel IO TX Example: Simple RGB LED Matrix + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The common used LED Matrix board has a so-called HUB75 interface, which is a 16-bit parallel interface. This example shows how to use the Parallel IO TX unit to drive a RGB LED Matrix board. We take use of the Parallel IO TX unit to send the RGB data and control signals to the LED Matrix board, and use the fast GPIO bundle to control the line address. + +The example uses the [LVGL](https://lvgl.io/) library to draw some simple UI elements on the LED Matrix board. + +## How to Use Example + +### Hardware Required + +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) +* A USB cable for programming +* A RGB LED Matrix board, with HUB75 interface (this example will use the [`64*32` resolution version](https://www.waveshare.net/wiki/RGB-Matrix-P4-64x32)) +* A 5V-2A power supply for the LED Matrix board + +Connection : + +```plain_text + +-----------------------+ + | HUB75 | + | +-------------+ | + | | | | +PIN_NUM_R1---+-+---R1 G1 +-------+---PIN_NUM_G1 + | | | | +PIN_NUM_B1---+-+---B1 +-GND | | + | | | | | +PIN_NUM_R2---+-+-+-R2 | G2 +-------+---PIN_NUM_G2 + | | | | | +PIN_NUM_B2---+---+-B2 +-GND-+-------+---->GND + | | | | | +PIN_NUM_A----+---+-A | B +-------+---PIN_NUM_B + | | | | | +PIN_NUM_C----+-+-+-C | D +-------+---PIN_NUM_D + | | | | | +PIN_NUM_CLK--+-+---CLK | LAT +-------+---PIN_NUM_LAT + | | | | | +PIN_NUM_OE---+-+---OE +-GND | | + | | | PWR--+---->5V + | +-------------+ | + | RGB LED Matrix | + +-----------------------+ +``` + +The GPIOs used in this example are defined in the [source file](main/rgb_led_matrix_example_main.c). You can change them according to your hardware. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Console Output + +```plain_text +I (348) main_task: Started on CPU0 +I (348) main_task: Calling app_main() +I (348) example: Install fast GPIO bundle for line address control +I (358) example: Install parallel IO TX unit +I (368) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (368) gpio: GPIO[1]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (378) gpio: GPIO[2]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (388) gpio: GPIO[3]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (398) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (408) gpio: GPIO[5]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (418) gpio: GPIO[6]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (428) gpio: GPIO[7]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (438) gpio: GPIO[10]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (458) example: Initialize LVGL library +I (458) example: Register display driver to LVGL +I (468) example: Install LVGL tick timer +I (468) example: Display LVGL UI +``` + +## Limitations + +:warning: Because of the hardware limitation in ESP32-C6 and ESP32-H2, the transaction length can't be controlled by DMA, thus we can't flush the LED screen continuously within a hardware loop. Instead, this example uses a task to split the screen flush into multiple line transactions. This obviously will increase the CPU load and may also lead to a task watchdog timeout. So this example **disables** the task watchdog by default. + +Please note that, the example is only for illustrating the usage of parallel IO TX APIs, it's far from ideal for use in a real product. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/main/CMakeLists.txt b/examples/peripherals/parlio/simple_rgb_led_matrix/main/CMakeLists.txt new file mode 100644 index 00000000000..d8e280c0faf --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "rgb_led_matrix_example_main.c" "lvgl_demo_ui.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/main/idf_component.yml b/examples/peripherals/parlio/simple_rgb_led_matrix/main/idf_component.yml new file mode 100644 index 00000000000..fcdb51a8629 --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + idf: ">=5.1" + lvgl/lvgl: "~8.3.0" diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/main/lvgl_demo_ui.c b/examples/peripherals/parlio/simple_rgb_led_matrix/main/lvgl_demo_ui.c new file mode 100644 index 00000000000..0cd90f48bb1 --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/main/lvgl_demo_ui.c @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "lvgl.h" + +void example_lvgl_demo_ui(lv_disp_t *disp) +{ + lv_obj_t *scr = lv_disp_get_scr_act(disp); + lv_obj_t *up_label = lv_label_create(scr); + lv_label_set_recolor(up_label, true); + lv_label_set_text(up_label, "#FF0000 Hello# #0000FF World#"); + lv_label_set_long_mode(up_label, LV_LABEL_LONG_SCROLL_CIRCULAR); /* Circular scroll */ + /* Size of the screen */ + lv_obj_set_width(up_label, disp->driver->hor_res); + lv_obj_align_to(up_label, scr, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t *low_label = lv_label_create(scr); + lv_label_set_text(low_label, LV_SYMBOL_WIFI LV_SYMBOL_GPS LV_SYMBOL_BATTERY_2 LV_SYMBOL_AUDIO); + /* Size of the screen */ + lv_obj_set_width(low_label, disp->driver->hor_res); + lv_obj_align_to(low_label, scr, LV_ALIGN_BOTTOM_MID, 0, 0); +} diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/main/rgb_led_matrix_example_main.c b/examples/peripherals/parlio/simple_rgb_led_matrix/main/rgb_led_matrix_example_main.c new file mode 100644 index 00000000000..588f4cb96b8 --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/main/rgb_led_matrix_example_main.c @@ -0,0 +1,218 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "driver/parlio_tx.h" +#include "driver/dedic_gpio.h" +#include "driver/gptimer.h" +#include "lvgl.h" + +static const char *TAG = "example"; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LED Matrix spec /////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// color data, clock signal and control signals are powered by parlio_tx unit +#define EXAMPLE_HUB75_DATA_WIDTH 8 // 8-bit data width, used for transferring color data (RGB222) and control signals (OE and latch) +#define EXAMPLE_HUB75_R1_IDX 7 // R1 bit position +#define EXAMPLE_HUB75_R2_IDX 6 // R2 bit position +#define EXAMPLE_HUB75_LATCH_IDX 5 // LATCH bit position +#define EXAMPLE_HUB75_G1_IDX 4 // G1 bit position +#define EXAMPLE_HUB75_G2_IDX 3 // G2 bit position +#define EXAMPLE_HUB75_OE_IDX 2 // OE bit position +#define EXAMPLE_HUB75_B1_IDX 1 // B1 bit position +#define EXAMPLE_HUB75_B2_IDX 0 // B2 bit position +// GPIO assignment +#define EXAMPLE_PIN_NUM_R1 7 +#define EXAMPLE_PIN_NUM_G1 4 +#define EXAMPLE_PIN_NUM_B1 1 +#define EXAMPLE_PIN_NUM_R2 6 +#define EXAMPLE_PIN_NUM_G2 3 +#define EXAMPLE_PIN_NUM_B2 0 +#define EXAMPLE_PIN_NUM_LATCH 5 +#define EXAMPLE_PIN_NUM_OE 2 +#define EXAMPLE_PIN_NUM_PCLK 10 +// address signals are powered by fast GPIO module +#define EXAMPLE_PIN_NUM_A 20 +#define EXAMPLE_PIN_NUM_B 21 +#define EXAMPLE_PIN_NUM_C 22 +#define EXAMPLE_PIN_NUM_D 23 +// The pixel clock frequency +#define EXAMPLE_LED_MATRIX_PIXEL_CLOCK_HZ (10 * 1000 * 1000) // 10MHz +// The pixel number in horizontal and vertical +#define EXAMPLE_LED_MATRIX_H_RES 64 +#define EXAMPLE_LED_MATRIX_V_RES 32 + +// We use the gptimer interrupt to generate the LVGL tick interface +#define EXAMPLE_GPTIMER_RESOLUTION_HZ (1 * 1000 * 1000) // 1MHz +#define EXAMPLE_LVGL_TICK_PERIOD_MS 5 // 5ms + +// 0x92: we pick the first bit of each RGB element in the order of 3-3-2, that's 0b10010010 +// p1: pixel on the upper half screen +// p2: pixel on the lower half screen +// OE=1: disable the output +#define MERGE_TWO_LVGL_PIXELS(p1, p2) \ + do \ + { \ + p1->full &= 0x92; \ + p1->full |= (p2->full & 0x92) >> 1; \ + p1->full |= 1 << EXAMPLE_HUB75_OE_IDX; \ + } while (0) + +extern void example_lvgl_demo_ui(lv_disp_t *disp); + +// LED Matrix frame buffer, we use a dedicated task to flush this frame buffer +static lv_color_t s_frame_buffer[EXAMPLE_LED_MATRIX_H_RES * EXAMPLE_LED_MATRIX_V_RES / 2]; + +static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + lv_color_t *upper_half = color_map; + lv_color_t *lower_half = color_map + EXAMPLE_LED_MATRIX_V_RES * EXAMPLE_LED_MATRIX_H_RES / 2; + for (int line = 0; line < EXAMPLE_LED_MATRIX_V_RES / 2; line++) { + for (int col = 0; col < EXAMPLE_LED_MATRIX_H_RES - 1; col++) { + MERGE_TWO_LVGL_PIXELS(upper_half, lower_half); + upper_half++; + lower_half++; + } + MERGE_TWO_LVGL_PIXELS(upper_half, lower_half); + // need special handling for the last pixel in each line + // latch up at the end of each line + upper_half->full |= (1 << EXAMPLE_HUB75_LATCH_IDX); + upper_half++; + lower_half++; + } + memcpy(s_frame_buffer, color_map, sizeof(s_frame_buffer)); + lv_disp_flush_ready(drv); +} + +static IRAM_ATTR bool parlio_tx_line_done_cb(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx) +{ + static uint32_t line_number = 0; + dedic_gpio_bundle_handle_t gpio_bundle = (dedic_gpio_bundle_handle_t)user_ctx; + dedic_gpio_bundle_write(gpio_bundle, 0x0F, line_number++); + return false; +} + +static IRAM_ATTR bool gptimer_alarm_cb_lvgl_tick(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) +{ + /* Tell LVGL how many milliseconds has elapsed */ + lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS); + return false; +} + +void app_main(void) +{ + static lv_disp_draw_buf_t disp_buf; + static lv_disp_drv_t disp_drv; + + ESP_LOGI(TAG, "Install fast GPIO bundle for line address control"); + dedic_gpio_bundle_config_t dedic_gpio_conf = { + .flags.out_en = true, + .gpio_array = (int[]) + { + EXAMPLE_PIN_NUM_A, EXAMPLE_PIN_NUM_B, EXAMPLE_PIN_NUM_C, EXAMPLE_PIN_NUM_D + }, + .array_size = 4, + }; + dedic_gpio_bundle_handle_t led_line_control_gpio_bundle; + ESP_ERROR_CHECK(dedic_gpio_new_bundle(&dedic_gpio_conf, &led_line_control_gpio_bundle)); + // initial line address to 0 + dedic_gpio_bundle_write(led_line_control_gpio_bundle, 0x0F, 0x00); + + ESP_LOGI(TAG, "Install parallel IO TX unit"); + parlio_tx_unit_handle_t tx_unit = NULL; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = EXAMPLE_HUB75_DATA_WIDTH, + .clk_in_gpio_num = -1, // use internal clock source + .valid_gpio_num = -1, // don't generate valid signal + .clk_out_gpio_num = EXAMPLE_PIN_NUM_PCLK, + .data_gpio_nums = { + [EXAMPLE_HUB75_R1_IDX] = EXAMPLE_PIN_NUM_R1, + [EXAMPLE_HUB75_R2_IDX] = EXAMPLE_PIN_NUM_R2, + [EXAMPLE_HUB75_G1_IDX] = EXAMPLE_PIN_NUM_G1, + [EXAMPLE_HUB75_G2_IDX] = EXAMPLE_PIN_NUM_G2, + [EXAMPLE_HUB75_B1_IDX] = EXAMPLE_PIN_NUM_B1, + [EXAMPLE_HUB75_B2_IDX] = EXAMPLE_PIN_NUM_B2, + [EXAMPLE_HUB75_OE_IDX] = EXAMPLE_PIN_NUM_OE, + [EXAMPLE_HUB75_LATCH_IDX] = EXAMPLE_PIN_NUM_LATCH, + }, + .output_clk_freq_hz = EXAMPLE_LED_MATRIX_PIXEL_CLOCK_HZ, + .trans_queue_depth = 32, + .max_transfer_size = EXAMPLE_LED_MATRIX_H_RES * sizeof(lv_color_t) * 2, // 2 lines as the maximum transfer size + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + }; + ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit)); + ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit)); + + // we use the transaction done callback to update the line address + parlio_tx_event_callbacks_t parlio_cbs = { + .on_trans_done = parlio_tx_line_done_cb, + }; + ESP_ERROR_CHECK(parlio_tx_unit_register_event_callbacks(tx_unit, &parlio_cbs, led_line_control_gpio_bundle)); + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + // allocate two full-screen draw buffers + lv_color_t *buf1 = malloc(EXAMPLE_LED_MATRIX_H_RES * EXAMPLE_LED_MATRIX_V_RES * sizeof(lv_color_t)); + assert(buf1); + lv_color_t *buf2 = malloc(EXAMPLE_LED_MATRIX_H_RES * EXAMPLE_LED_MATRIX_V_RES * sizeof(lv_color_t)); + assert(buf2); + // initialize LVGL draw buffers + lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LED_MATRIX_H_RES * EXAMPLE_LED_MATRIX_V_RES); + + ESP_LOGI(TAG, "Register display driver to LVGL"); + lv_disp_drv_init(&disp_drv); + disp_drv.hor_res = EXAMPLE_LED_MATRIX_H_RES; + disp_drv.ver_res = EXAMPLE_LED_MATRIX_V_RES; + disp_drv.flush_cb = example_lvgl_flush_cb; + disp_drv.draw_buf = &disp_buf; + disp_drv.user_data = NULL; + disp_drv.full_refresh = true; // the full_refresh mode can maintain the synchronization between two adjacent frame buffers + lv_disp_t *disp = lv_disp_drv_register(&disp_drv); + + ESP_LOGI(TAG, "Install LVGL tick timer"); + // increase the LVGL tick in the GPTimer alarm callback + gptimer_handle_t lvgl_tick_timer = NULL; + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = EXAMPLE_GPTIMER_RESOLUTION_HZ, + }; + ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &lvgl_tick_timer)); + gptimer_alarm_config_t alarm_config = { + .alarm_count = EXAMPLE_LVGL_TICK_PERIOD_MS * EXAMPLE_GPTIMER_RESOLUTION_HZ / 1000, + .reload_count = 0, + .flags.auto_reload_on_alarm = true, + }; + ESP_ERROR_CHECK(gptimer_set_alarm_action(lvgl_tick_timer, &alarm_config)); + gptimer_event_callbacks_t gptimer_cbs = { + .on_alarm = gptimer_alarm_cb_lvgl_tick, + }; + ESP_ERROR_CHECK(gptimer_register_event_callbacks(lvgl_tick_timer, &gptimer_cbs, NULL)); + ESP_ERROR_CHECK(gptimer_enable(lvgl_tick_timer)); + + ESP_LOGI(TAG, "Display LVGL UI"); + example_lvgl_demo_ui(disp); + + ESP_ERROR_CHECK(gptimer_start(lvgl_tick_timer)); + + // the frame buffer is flushed to the screen in a while loop without yield, which may cause task watchdog timeout + uint8_t *payload = (uint8_t *)s_frame_buffer; + parlio_transmit_config_t transmit_config = { + .idle_value = 0x00, // the idle value will force the OE line to low, thus enable the output + }; + while (1) { + for (int i = 0; i < EXAMPLE_LED_MATRIX_V_RES / 2; i++) { + ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload + EXAMPLE_LED_MATRIX_H_RES * i, + EXAMPLE_LED_MATRIX_H_RES * sizeof(lv_color_t) * 8, &transmit_config)); + } + ESP_ERROR_CHECK(parlio_tx_unit_wait_all_done(tx_unit, -1)); + lv_timer_handler(); + } +} diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/pytest_simple_rgb_led_matrix.py b/examples/peripherals/parlio/simple_rgb_led_matrix/pytest_simple_rgb_led_matrix.py new file mode 100644 index 00000000000..940d78c6152 --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/pytest_simple_rgb_led_matrix.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 +@pytest.mark.generic +def test_simple_rgb_led_matrix_example(dut: Dut) -> None: + dut.expect_exact('example: Install fast GPIO bundle for line address control') + dut.expect_exact('example: Install parallel IO TX unit') + dut.expect_exact('example: Initialize LVGL library') + dut.expect_exact('example: Register display driver to LVGL') + dut.expect_exact('example: Install LVGL tick timer') + dut.expect_exact('example: Display LVGL UI') diff --git a/examples/peripherals/parlio/simple_rgb_led_matrix/sdkconfig.defaults b/examples/peripherals/parlio/simple_rgb_led_matrix/sdkconfig.defaults new file mode 100644 index 00000000000..788db185787 --- /dev/null +++ b/examples/peripherals/parlio/simple_rgb_led_matrix/sdkconfig.defaults @@ -0,0 +1,6 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_LV_COLOR_DEPTH_8=y +CONFIG_LV_THEME_DEFAULT_DARK=y