Merge branch 'feature/parlio_driver' into 'master'

Parallel IO TX driver && LED Matrix example

Closes IDF-6607

See merge request espressif/esp-idf!22163
This commit is contained in:
morris 2023-03-01 17:08:16 +08:00
commit 14555a19db
70 changed files with 3597 additions and 59 deletions

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,187 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/lock.h>
#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);
}
}

View File

@ -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

View File

@ -0,0 +1,628 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#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();
}
}
}

View File

@ -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;
}

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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()

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |

View File

@ -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)

View File

@ -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();
}

View File

@ -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

View File

@ -0,0 +1,280 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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 <stdbool.h>
#include <stdint.h>
#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

View File

@ -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:

View File

@ -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 <stdbool.h>
#include <stdint.h>
#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

View File

@ -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;
/**

View File

@ -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 <stdint.h>
#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

View File

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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

View File

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#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;
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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,
}
}
},
},
};

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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)

View File

@ -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,
}
}
},
},
};

View File

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#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

View File

@ -74,6 +74,8 @@ MCPWM_DOCS = ['api-reference/peripherals/mcpwm.rst']
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 @@ conditional_include_dict = {'SOC_BT_SUPPORTED':BT_DOCS,
'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,

View File

@ -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 \

View File

@ -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 <https://www.espressif.com/sites/default/files/documentation/esp32h2_datasheet_en.pdf>`_.
- 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.
---

View File

@ -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

View File

@ -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`).

View File

@ -64,11 +64,11 @@
- Strapping 管脚
* - GPIO10
-
- 模拟比较器外部参考电压
-
* - GPIO11
-
- 模拟比较器同相输入
-
* - GPIO12
@ -137,9 +137,10 @@
.. note::
- Strapping 管脚GPIO2、GPIO3、GPIO8、GPIO9 GPIO25 Strapping 管脚。更多信息请参考 `ESP32-H2 技术规格书 <https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_cn.pdf>`_。
- SPI0/1GPIO15-21 通常用于 SPI flash不推荐用于其他用途。
- USB-JTAGGPIO26 GPIO27 默认用于 USB-JTAG。用做 GPIO 时驱动程序将禁用 USB-JTAG。
- 对于内置 SiP flash 的芯片型号GPIO15 ~ GPIO21 专门用于连接 SiP flash; 因此,对于这类芯片只有 21 GPIO 管脚可用。
- Strapping 管脚: GPIO2、GPIO3、GPIO8、GPIO9 GPIO25 Strapping 管脚。更多信息请参考 `ESP32-H2 技术规格书 <https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_cn.pdf>`_。
- 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 也并未引出至芯片管脚,用户不可用。
---

View File

@ -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

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/peripherals/parlio.rst

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "rgb_led_matrix_example_main.c" "lvgl_demo_ui.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,3 @@
dependencies:
idf: ">=5.1"
lvgl/lvgl: "~8.3.0"

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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')

View File

@ -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