diff --git a/Kconfig b/Kconfig index 56e1d8e5a0..603c6f924d 100644 --- a/Kconfig +++ b/Kconfig @@ -1729,7 +1729,7 @@ endif # DEBUG_REGMAP config DEBUG_RMT bool "RMT Debug Features" default n - depends on ESP32_RMT + depends on RMT ---help--- Enable RMT debug features. diff --git a/arch/xtensa/src/Makefile b/arch/xtensa/src/Makefile index 3ca6f2336d..2a6f450609 100644 --- a/arch/xtensa/src/Makefile +++ b/arch/xtensa/src/Makefile @@ -129,6 +129,7 @@ endif VPATH += chip VPATH += common +VPATH += common/espressif VPATH += $(ARCH_SUBDIR) VPATH += $(CHIP_DIR) diff --git a/arch/xtensa/src/common/espressif/Kconfig b/arch/xtensa/src/common/espressif/Kconfig new file mode 100644 index 0000000000..6bc8c13f71 --- /dev/null +++ b/arch/xtensa/src/common/espressif/Kconfig @@ -0,0 +1,7 @@ +config ESP_RMT + bool "Remote Control Module (RMT)" + default n + depends on RMT + ---help--- + Remote Control Module is currently used to control WS2812 + RGB LED normally used on LED strips. \ No newline at end of file diff --git a/arch/xtensa/src/common/espressif/Make.defs b/arch/xtensa/src/common/espressif/Make.defs new file mode 100644 index 0000000000..3a48c6b4a7 --- /dev/null +++ b/arch/xtensa/src/common/espressif/Make.defs @@ -0,0 +1,26 @@ +############################################################################ +# arch/xtensa/src/common/espressif/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_ESP_RMT),y) +CHIP_CSRCS += esp_rmt.c +ifeq ($(CONFIG_WS2812_NON_SPI_DRIVER),y) +CHIP_CSRCS += esp_ws2812.c +endif +endif diff --git a/arch/xtensa/src/common/espressif/esp_rmt.c b/arch/xtensa/src/common/espressif/esp_rmt.c new file mode 100644 index 0000000000..5d7ac7fef3 --- /dev/null +++ b/arch/xtensa/src/common/espressif/esp_rmt.c @@ -0,0 +1,1993 @@ +/**************************************************************************** + * arch/xtensa/src/common/espressif/esp_rmt.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "xtensa.h" +#ifdef CONFIG_ARCH_CHIP_ESP32 +#include "hardware/esp32_soc.h" +#include "esp32_gpio.h" +#include "esp32_irq.h" +#elif CONFIG_ARCH_CHIP_ESP32S2 +#include "hardware/esp32s2_soc.h" +#include "esp32s2_gpio.h" +#include "esp32s2_irq.h" +#elif CONFIG_ARCH_CHIP_ESP32S3 +#include "hardware/esp32s3_soc.h" +#include "esp32s3_gpio.h" +#include "esp32s3_irq.h" +#endif + +#include "hal/gpio_types.h" +#include "hal/rmt_hal.h" +#include "hal/rmt_ll.h" +#include "periph_ctrl.h" +#include "soc/gpio_sig_map.h" +#include "soc/rmt_periph.h" +#include "soc/soc_caps.h" +#include "esp_clk_tree.h" + +#include "esp_rmt.h" + +#ifdef CONFIG_ESP_RMT + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define RMT_RX_CHANNEL_ENCODING_START \ + (SOC_RMT_CHANNELS_PER_GROUP-SOC_RMT_TX_CANDIDATES_PER_GROUP) +#define RMT_TX_CHANNEL_ENCODING_END (SOC_RMT_TX_CANDIDATES_PER_GROUP-1) + +#define RMT_IS_RX_CHANNEL(channel) \ + ((channel) >= RMT_RX_CHANNEL_ENCODING_START) +#define RMT_IS_TX_CHANNEL(channel) \ + ((channel) <= RMT_TX_CHANNEL_ENCODING_END) +#define RMT_DECODE_RX_CHANNEL(encode_chan) \ + ((encode_chan - RMT_RX_CHANNEL_ENCODING_START)) +#define RMT_ENCODE_RX_CHANNEL(decode_chan) \ + ((decode_chan + RMT_RX_CHANNEL_ENCODING_START)) + +/* Default configuration for TX channel */ + +#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id) \ + { \ + .rmt_mode = RMT_MODE_TX, \ + .channel = channel_id, \ + .gpio_num = gpio, \ + .clk_div = RMT_DEFAULT_CLK_DIV, \ + .mem_block_num = 1, \ + .flags = 0, \ + .tx_config = { \ + .carrier_freq_hz = 38000, \ + .carrier_level = RMT_CARRIER_LEVEL_HIGH, \ + .idle_level = RMT_IDLE_LEVEL_LOW, \ + .carrier_duty_percent = 33, \ + .loop_count = 0, \ + .carrier_en = false, \ + .loop_en = false, \ + .idle_output_en = true, \ + } \ + } + +/* Default configuration for RX channel */ + +#define RMT_DEFAULT_CONFIG_RX(gpio, channel_id) \ + { \ + .rmt_mode = RMT_MODE_RX, \ + .channel = channel_id, \ + .gpio_num = gpio, \ + .clk_div = RMT_DEFAULT_CLK_DIV, \ + .mem_block_num = 1, \ + .flags = 0, \ + .rx_config = { \ + .idle_threshold = 12000, \ + .filter_ticks_thresh = 100, \ + .filter_en = true, \ + } \ + } + +#define rmt_item32_t rmt_symbol_word_t + +#ifdef CONFIG_ARCH_CHIP_ESP32 +# define esp_configgpio esp32_configgpio +# define esp_gpio_matrix_out esp32_gpio_matrix_out +# define esp_gpio_matrix_in esp32_gpio_matrix_in +# define esp_setup_irq esp32_setup_irq +# define esp_teardown_irq esp32_teardown_irq + +# define GPIO_OUT_FUNC OUTPUT_FUNCTION_3 +# define GPIO_IN_FUNC INPUT_FUNCTION_3 +# define ESP_CPUINT_LEVEL ESP32_CPUINT_LEVEL + +#elif CONFIG_ARCH_CHIP_ESP32S2 +# define esp_configgpio esp32s2_configgpio +# define esp_gpio_matrix_out esp32s2_gpio_matrix_out +# define esp_gpio_matrix_in esp32s2_gpio_matrix_in +# define esp_setup_irq esp32s2_setup_irq +# define esp_teardown_irq esp32s2_teardown_irq + +# define GPIO_OUT_FUNC OUTPUT_FUNCTION_2 +# define GPIO_IN_FUNC INPUT_FUNCTION_2 +# define ESP_CPUINT_LEVEL ESP32S2_CPUINT_LEVEL + +#elif CONFIG_ARCH_CHIP_ESP32S3 +# define esp_configgpio esp32s3_configgpio +# define esp_gpio_matrix_out esp32s3_gpio_matrix_out +# define esp_gpio_matrix_in esp32s3_gpio_matrix_in +# define esp_setup_irq esp32s3_setup_irq +# define esp_teardown_irq esp32s3_teardown_irq + +# define GPIO_OUT_FUNC OUTPUT_FUNCTION_2 +# define GPIO_IN_FUNC INPUT_FUNCTION_2 +# define ESP_CPUINT_LEVEL ESP32S3_CPUINT_LEVEL + +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* RMT channel ID */ + +enum rmt_channel_e +{ + RMT_CHANNEL_0, /* RMT channel number 0 */ + RMT_CHANNEL_1, /* RMT channel number 1 */ + RMT_CHANNEL_2, /* RMT channel number 2 */ + RMT_CHANNEL_3, /* RMT channel number 3 */ + RMT_CHANNEL_4, /* RMT channel number 4 */ + RMT_CHANNEL_5, /* RMT channel number 5 */ + RMT_CHANNEL_6, /* RMT channel number 6 */ + RMT_CHANNEL_7, /* RMT channel number 7 */ + RMT_CHANNEL_MAX /* Number of RMT channels */ +}; + +typedef enum rmt_channel_e rmt_channel_t; + +/* RMT Channel Working Mode (TX or RX) */ + +enum rmt_mode_e +{ + RMT_MODE_TX, /* RMT TX mode */ + RMT_MODE_RX, /* RMT RX mode */ + RMT_MODE_MAX +}; + +typedef enum rmt_mode_e rmt_mode_t; + +/* RMT Idle Level */ + +enum rmt_idle_level_e +{ + RMT_IDLE_LEVEL_LOW, /* RMT TX idle level: low Level */ + RMT_IDLE_LEVEL_HIGH, /* RMT TX idle level: high Level */ + RMT_IDLE_LEVEL_MAX, +}; + +typedef enum rmt_idle_level_e rmt_idle_level_t; + +/* RMT Carrier Level */ + +enum rmt_carrier_level_e +{ + RMT_CARRIER_LEVEL_LOW, /* RMT carrier wave is modulated for low Level output */ + RMT_CARRIER_LEVEL_HIGH, /* RMT carrier wave is modulated for high Level output */ + RMT_CARRIER_LEVEL_MAX +}; + +typedef enum rmt_carrier_level_e rmt_carrier_level_t; + +/* RMT Channel Status */ + +enum rmt_channel_status_e +{ + RMT_CHANNEL_UNINIT, /* RMT channel uninitialized */ + RMT_CHANNEL_IDLE, /* RMT channel status idle */ + RMT_CHANNEL_BUSY, /* RMT channel status busy */ +}; + +typedef enum rmt_channel_status_e rmt_channel_status_t; + +/* RMT hardware memory layout */ + +struct rmt_channel_data_s +{ + volatile rmt_item32_t data32[SOC_RMT_MEM_WORDS_PER_CHANNEL]; +}; + +struct rmt_mem_s +{ + struct rmt_channel_data_s chan[SOC_RMT_CHANNELS_PER_GROUP]; +}; + +typedef struct rmt_mem_s rmt_mem_t; + +struct rmt_dev_common_s +{ + rmt_hal_context_t hal; /* HAL context */ + rmutex_t rmt_driver_isr_lock; + + /* Mutex lock for protecting concurrent register/unregister of the RMT + * channels' ISR. + */ + + spinlock_t rmt_spinlock; + + /* Bitmask of installed drivers' channels, used to protect concurrent + * register/unregister of the RMT channels' ISR. + */ + + uint8_t rmt_driver_channels; + bool rmt_module_enabled; + + /* Bitmap of channels already added in the synchronous group */ + + uint32_t synchro_channel_mask; +}; + +struct rmt_dev_lowerhalf_s +{ + /* The following block is part of the upper-hald device struct */ + + FAR const struct rmt_ops_s *ops; + FAR struct circbuf_s *circbuf; + sem_t *recvsem; + int minor; + + /* The following is private to the ESP32 RMT driver */ + + rmt_mode_t mode; + struct rmt_dev_common_s *common; /* RMT peripheral common parameters */ +}; + +struct rmt_obj_s +{ + size_t tx_offset; + size_t tx_len_rem; + size_t tx_sub_len; + bool wait_done; /* Mark whether wait tx done */ + bool loop_autostop; /* mark whether loop auto-stop is enabled */ + rmt_channel_t channel; + const rmt_item32_t *tx_data; + sem_t tx_sem; +#if CONFIG_SPIRAM_USE_MALLOC + int intr_alloc_flags; + sem_t tx_sem_buffer; +#endif + rmt_item32_t *tx_buf; + struct circbuf_s rx_buf; + sem_t rx_sem; +#if SOC_RMT_SUPPORT_RX_PINGPONG + rmt_item32_t *rx_item_buf; + uint32_t rx_item_buf_size; + uint32_t rx_item_len; + int rx_item_start_idx; +#endif + void *tx_context; + size_t sample_size_remain; + const uint8_t *sample_cur; +}; + +typedef struct rmt_obj_s rmt_obj_t; + +/* Data struct of RMT TX configure parameters */ + +struct rmt_tx_config_s +{ + uint32_t carrier_freq_hz; /* RMT carrier frequency */ + rmt_carrier_level_t carrier_level; /* Level of the RMT output, when the carrier is applied */ + rmt_idle_level_t idle_level; /* RMT idle level */ + uint8_t carrier_duty_percent; /* RMT carrier duty (%) */ + uint32_t loop_count; /* Maximum loop count, only take effect for chips that is capable of `SOC_RMT_SUPPORT_TX_LOOP_COUNT` */ + bool carrier_en; /* RMT carrier enable */ + bool loop_en; /* Enable sending RMT items in a loop */ + bool idle_output_en; /* RMT idle level output enable */ +}; + +/* Data struct of RMT RX configure parameters */ + +struct rmt_rx_config_s +{ + uint16_t idle_threshold; /* RMT RX idle threshold */ + uint8_t filter_ticks_thresh; /* RMT filter tick number */ + bool filter_en; /* RMT receiver filter enable */ +#if SOC_RMT_SUPPORT_RX_DEMODULATION + bool rm_carrier; /* RMT receiver remove carrier enable */ + uint32_t carrier_freq_hz; /* RMT carrier frequency */ + uint8_t carrier_duty_percent; /* RMT carrier duty (%) */ + rmt_carrier_level_t carrier_level; /* The level to remove the carrier */ +#endif +}; + +struct rmt_channel_config_s +{ + rmt_mode_t rmt_mode; /* RMT mode: transmitter or receiver */ + rmt_channel_t channel; /* RMT channel */ + int gpio_num; /* RMT GPIO number */ + uint8_t clk_div; /* RMT channel counter divider */ + uint8_t mem_block_num; /* RMT memory block number */ + uint32_t flags; /* RMT channel extra configurations, OR'd with RMT_CHANNEL_FLAGS_[*] */ + union + { + struct rmt_tx_config_s tx_config; /* RMT TX parameter */ + struct rmt_rx_config_s rx_config; /* RMT RX parameter */ + }; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void rmt_module_enable(void); +static int rmt_rx_start(rmt_channel_t channel, bool rx_idx_rst); +static int rmt_tx_start(rmt_channel_t channel, bool tx_idx_rst); +static int rmt_set_tx_loop_mode(rmt_channel_t channel, bool loop_en); +static int rmt_set_tx_thr_intr_en(rmt_channel_t channel, bool en, + uint16_t evt_thresh); +static int rmt_set_gpio(rmt_channel_t channel, rmt_mode_t mode, + gpio_num_t gpio_num, bool invert_signal); +static bool rmt_is_channel_number_valid(rmt_channel_t channel, uint8_t mode); +static int rmt_internal_config(rmt_dev_t *dev, + const struct rmt_channel_config_s *rmt_param); +static int rmt_config(const struct rmt_channel_config_s *rmt_param); +static void rmt_fill_memory(rmt_channel_t channel, const rmt_item32_t *item, + uint16_t item_num, uint16_t mem_offset); +static int rmt_isr_register(int (*fn)(int, void *, void *), void *arg, + int intr_alloc_flags); +static int rmt_driver_isr_default(int irq, void *context, void *arg); +static int rmt_driver_install(rmt_channel_t channel, size_t rx_buf_size, + int intr_alloc_flags); +static int rmt_write_items(rmt_channel_t channel, + const rmt_item32_t *rmt_item, + int item_num, + bool wait_tx_done); +static ssize_t esp_rmt_read(struct rmt_dev_s *dev, char *buffer, + size_t buflen); +static ssize_t esp_rmt_write(FAR struct rmt_dev_s *dev, + FAR const char *buffer, + size_t buflen); +static struct rmt_dev_s + *esp_rmtinitialize(struct rmt_channel_config_s config); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct rmt_ops_s g_rmtops = +{ + .read = esp_rmt_read, + .write = esp_rmt_write, +}; + +static struct rmt_dev_common_s g_rmtdev_common = +{ + .hal.regs = &RMT, + .rmt_driver_isr_lock = NXRMUTEX_INITIALIZER, + .rmt_driver_channels = 0, + .rmt_module_enabled = false, + .synchro_channel_mask = 0 +}; + +static struct rmt_obj_s *p_rmt_obj[RMT_CHANNEL_MAX]; + +#ifdef CONFIG_RMT_LOOP_TEST_MODE +static rmt_channel_t g_tx_channel = RMT_CHANNEL_MAX; +static rmt_channel_t g_rx_channel = RMT_CHANNEL_MAX; +#endif + +#if SOC_RMT_CHANNEL_CLK_INDEPENDENT +static uint32_t s_rmt_source_clock_hz[RMT_CHANNEL_MAX]; +#else +static uint32_t s_rmt_source_clock_hz; +#endif + +/* RMTMEM address is declared in .peripherals.ld */ + +extern rmt_mem_t RMTMEM; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rmt_module_enable + * + * Description: + * This function enables the RMT (Remote Control) module if it's not + * already enabled. + * + * Input Parameters: + * None. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void rmt_module_enable(void) +{ + irqstate_t flags; + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + + if (g_rmtdev_common.rmt_module_enabled == false) + { + periph_module_reset(rmt_periph_signals.groups[0].module); + periph_module_enable(rmt_periph_signals.groups[0].module); + g_rmtdev_common.rmt_module_enabled = true; + } + + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); +} + +/**************************************************************************** + * Name: rmt_set_rx_thr_intr_en + * + * Description: + * This function enables or disables the RMT RX threshold interrupt. When + * the number of received items reaches the threshold, an interrupt is + * triggered if this feature is enabled. + * + * Input Parameters: + * channel - The RMT channel. + * en - Enable (true) or disable (false) the RX threshold int. + * evt_thresh - The number of received items that triggers the interrupt. + * + * Returned Value: + * Returns 0 on success; a negated errno value is returned on any failure. + * + ****************************************************************************/ + +#if SOC_RMT_SUPPORT_RX_PINGPONG +static int rmt_set_rx_thr_intr_en(rmt_channel_t channel, bool en, + uint16_t evt_thresh) +{ + irqstate_t flags; + uint32_t mask; + + DEBUGASSERT(RMT_IS_RX_CHANNEL(channel) && channel < RMT_CHANNEL_MAX); + + if (en) + { + uint32_t item_block_len = + rmt_ll_rx_get_mem_blocks(g_rmtdev_common.hal.regs, + RMT_DECODE_RX_CHANNEL(channel)) * + RMT_MEM_ITEM_NUM; + + if (evt_thresh >= item_block_len) + { + rmterr("Invalid threshold value %d\n", evt_thresh); + return -EINVAL; + } + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + rmt_ll_rx_set_limit(g_rmtdev_common.hal.regs, + RMT_DECODE_RX_CHANNEL(channel), evt_thresh); + mask = RMT_LL_EVENT_RX_THRES(RMT_DECODE_RX_CHANNEL(channel)); + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, mask, true); + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + } + else + { + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + mask = RMT_LL_EVENT_RX_THRES(RMT_DECODE_RX_CHANNEL(channel)); + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, mask, false); + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + } + + return OK; +} +#endif + +/**************************************************************************** + * Name: rmt_rx_start + * + * Description: + * This function starts the RMT module in receiving mode for a specific + * channel. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * rx_idx_rst - If true, the RX index for the channel is reset, which means + * the receiving process will start from the beginning of the + * RMT memory block. + * + * Returned Value: + * Returns OK on successful start of the RMT module in receiving mode; a + * negated errno value is returned on any failure. + * + ****************************************************************************/ + +static int rmt_rx_start(rmt_channel_t channel, bool rx_idx_rst) +{ + irqstate_t flags; + rmt_channel_t ch = RMT_DECODE_RX_CHANNEL(channel); +#if SOC_RMT_SUPPORT_RX_PINGPONG + const uint32_t item_block_len = + rmt_ll_rx_get_mem_blocks(g_rmtdev_common.hal.regs, ch) * + RMT_MEM_ITEM_NUM; +#endif + + DEBUGASSERT(RMT_IS_RX_CHANNEL(channel)); + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + + rmt_ll_rx_enable(g_rmtdev_common.hal.regs, ch, false); + if (rx_idx_rst) + { + rmt_ll_rx_reset_pointer(g_rmtdev_common.hal.regs, ch); + } + + rmt_ll_clear_interrupt_status(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_RX_DONE(ch)); + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_RX_DONE(ch), true); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + p_rmt_obj[channel]->rx_item_start_idx = 0; + p_rmt_obj[channel]->rx_item_len = 0; + rmt_set_rx_thr_intr_en(channel, true, item_block_len / 2); +#endif + + rmt_ll_rx_enable(g_rmtdev_common.hal.regs, ch, true); + + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + + return OK; +} + +/**************************************************************************** + * Name: rmt_tx_start + * + * Description: + * This function starts sending RMT items from the specific channel. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * tx_idx_rst - Set it true to reset memory index for TX. + * + * Returned Value: + * Returns OK on successful start of transmission. + * + ****************************************************************************/ + +static int rmt_tx_start(rmt_channel_t channel, bool tx_idx_rst) +{ + irqstate_t flags; + + DEBUGASSERT(RMT_IS_TX_CHANNEL(channel)); + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + if (tx_idx_rst) + { + rmt_ll_tx_reset_pointer(g_rmtdev_common.hal.regs, channel); + } + + rmt_ll_clear_interrupt_status(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_TX_DONE(channel)); + + /* enable tx end interrupt in non-loop mode */ + + if (!rmt_ll_tx_is_loop_enabled(g_rmtdev_common.hal.regs, channel)) + { + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_TX_DONE(channel), true); + } + else + { +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_tx_reset_loop_count(g_rmtdev_common.hal.regs, channel); + rmt_ll_tx_enable_loop_count(g_rmtdev_common.hal.regs, channel, true); + rmt_ll_clear_interrupt_status(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_TX_LOOP_END(channel)); + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_TX_LOOP_END(channel), true); +#endif + } + + rmt_ll_tx_start(g_rmtdev_common.hal.regs, channel); + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + + return OK; +} + +/**************************************************************************** + * Name: rmt_set_tx_loop_mode + * + * Description: + * This function enables or disables the loop mode for RMT transmission on + * the specified channel. The loop mode, when enabled, allows the RMT + * transmitter to continuously send items. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * loop_en - A boolean indicating whether to enable (true) or disable + * (false) the loop mode. + * + * Returned Value: + * Returns OK on successful setting of the loop mode. + * + ****************************************************************************/ + +static int rmt_set_tx_loop_mode(rmt_channel_t channel, bool loop_en) +{ + irqstate_t flags; + + DEBUGASSERT(RMT_IS_TX_CHANNEL(channel)); + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + rmt_ll_tx_enable_loop(g_rmtdev_common.hal.regs, channel, loop_en); + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + + return OK; +} + +/**************************************************************************** + * Name: rmt_set_tx_thr_intr_en + * + * Description: + * This function enables or disables the RMT TX threshold interrupt for the + * specified channel. The threshold is set to trigger an interrupt when the + * number of transmitted items reaches the specified value. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * en - A boolean indicating whether to enable (true) or disable + * (false) the TX threshold interrupt. + * evt_thresh - The number of transmitted items at which to trigger the + * interrupt. + * + * Returned Value: + * Returns OK on successful setting of the interrupt. + * + ****************************************************************************/ + +static int rmt_set_tx_thr_intr_en(rmt_channel_t channel, bool en, + uint16_t evt_thresh) +{ + irqstate_t flags; + + DEBUGASSERT(RMT_IS_TX_CHANNEL(channel)); + + if (en) + { + uint32_t item_block_len = + rmt_ll_tx_get_mem_blocks(g_rmtdev_common.hal.regs, channel) * \ + RMT_MEM_ITEM_NUM; + + DEBUGASSERT(evt_thresh <= item_block_len); + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + rmt_ll_tx_set_limit(g_rmtdev_common.hal.regs, channel, evt_thresh); + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_TX_THRES(channel), true); + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + } + else + { + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + rmt_ll_enable_interrupt(g_rmtdev_common.hal.regs, + RMT_LL_EVENT_TX_THRES(channel), false); + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + } + + return OK; +} + +/**************************************************************************** + * Name: rmt_set_gpio + * + * Description: + * This function configures the GPIO for the specified RMT (Remote Control) + * channel and mode. It sets the GPIO to the appropriate input or output + * function based on the mode, and configures the signal inversion if + * necessary. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * mode - The mode of operation for the RMT channel (RMT_MODE_TX + * for transmission, RMT_MODE_RX for reception). + * gpio_num - The GPIO number to configure for the RMT channel. + * invert_signal - A boolean indicating whether to invert the signal. + * + * Returned Value: + * Returns OK on successful configuration of the GPIO. + * + ****************************************************************************/ + +static int rmt_set_gpio(rmt_channel_t channel, rmt_mode_t mode, + gpio_num_t gpio_num, bool invert_signal) +{ + int ret; + + DEBUGASSERT(channel < RMT_CHANNEL_MAX); + DEBUGASSERT(mode < RMT_MODE_MAX); + DEBUGASSERT((GPIO_IS_VALID_GPIO(gpio_num) && (mode == RMT_MODE_RX)) || + (GPIO_IS_VALID_OUTPUT_GPIO(gpio_num) && + (mode == RMT_MODE_TX))); + + if (mode == RMT_MODE_TX) + { + DEBUGASSERT(RMT_IS_TX_CHANNEL(channel)); + esp_configgpio(gpio_num, GPIO_OUT_FUNC); + esp_gpio_matrix_out( + gpio_num, + rmt_periph_signals.groups[0].channels[channel].tx_sig, + invert_signal, 0); + } + else + { + DEBUGASSERT(RMT_IS_RX_CHANNEL(channel)); + esp_configgpio(gpio_num, GPIO_IN_FUNC); + esp_gpio_matrix_in( + gpio_num, + rmt_periph_signals.groups[0].channels[channel].rx_sig, + invert_signal); + } + + return OK; +} + +/**************************************************************************** + * Name: rmt_is_channel_number_valid + * + * Description: + * This function checks if the provided RMT channel number is valid for the + * specified mode (TX or RX). For RX mode, it checks if the channel number + * is within the range of valid RX channels and less than the maximum + * channel number. For TX mode, it checks if the channel number is a valid + * TX channel. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * mode - The mode of operation for the RMT channel (RMT_MODE_TX for + * transmission, RMT_MODE_RX for reception). + * + * Returned Value: + * Returns true if the channel number is valid, false otherwise. + * + ****************************************************************************/ + +static bool rmt_is_channel_number_valid(rmt_channel_t channel, uint8_t mode) +{ + if (mode == RMT_MODE_RX) + { + return RMT_IS_RX_CHANNEL(channel) && (channel < RMT_CHANNEL_MAX); + } + + return (channel >= 0) && RMT_IS_TX_CHANNEL(channel); +} + +/**************************************************************************** + * Name: rmt_internal_config + * + * Description: + * This function configures the RMT peripheral with provided parameters. + * It sets the mode (TX or RX), channel, GPIO number, memory block number, + * clock divider, carrier frequency, and carrier enable flag. It also + * configures the clock source, memory access, idle level, carrier + * modulation, and other settings based on the mode and parameters. + * + * Input Parameters: + * dev - Pointer to the RMT peripheral device structure. + * rmt_param - Pointer to the structure containing the RMT channel + * configuration parameters. + * + * Returned Value: + * Returns OK on successful configuration of the RMT peripheral. + * + ****************************************************************************/ + +static int rmt_internal_config(rmt_dev_t *dev, + const struct rmt_channel_config_s *rmt_param) +{ + uint8_t mode = rmt_param->rmt_mode; + uint8_t channel = rmt_param->channel; + uint8_t gpio_num = rmt_param->gpio_num; + uint8_t mem_cnt = rmt_param->mem_block_num; + uint8_t clk_div = rmt_param->clk_div; + uint32_t carrier_freq_hz = rmt_param->tx_config.carrier_freq_hz; + bool carrier_en = rmt_param->tx_config.carrier_en; + uint32_t rmt_source_clk_hz; + irqstate_t flags; + + if (!rmt_is_channel_number_valid(channel, mode)) + { + rmterr("Invalid channel number %u for %s mode!", + channel, mode == RMT_MODE_TX ? "transmitter" : "receiver"); + return -EINVAL; + } + + DEBUGASSERT(mem_cnt + channel <= SOC_RMT_CHANNELS_PER_GROUP && + mem_cnt > 0); + DEBUGASSERT(clk_div > 0); + + if (mode == RMT_MODE_TX && carrier_en && carrier_freq_hz <= 0) + { + return -EINVAL; + } + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + + rmt_ll_enable_mem_access_nonfifo(dev, true); + + if (rmt_param->flags & RMT_CHANNEL_FLAGS_AWARE_DFS) + { +#if SOC_RMT_SUPPORT_XTAL + + /* clock src: XTAL_CLK */ + + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)RMT_BASECLK_XTAL, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, + &rmt_source_clk_hz); + rmt_ll_set_group_clock_src(dev, channel, + (rmt_clock_source_t)RMT_BASECLK_XTAL, + 1, 0, 0); +#elif SOC_RMT_SUPPORT_REF_TICK + + /* clock src: REF_CLK */ + + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)RMT_BASECLK_REF, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, + &rmt_source_clk_hz); + rmt_ll_set_group_clock_src(dev, channel, + (rmt_clock_source_t)RMT_BASECLK_REF, + 1, 0, 0); +#else +#error "No clock source is aware of DFS" +#endif + } + else + { + /* fallback to use default clock source */ + + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)RMT_BASECLK_DEFAULT, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, + &rmt_source_clk_hz); + rmt_ll_set_group_clock_src(dev, channel, + (rmt_clock_source_t)RMT_BASECLK_DEFAULT, + 1, 0, 0); + } + + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + +#if SOC_RMT_CHANNEL_CLK_INDEPENDENT + s_rmt_source_clock_hz[channel] = rmt_source_clk_hz; +#else + if (s_rmt_source_clock_hz && rmt_source_clk_hz != s_rmt_source_clock_hz) + { + rmterr("RMT clock source has been configured to %"PRIu32" by other " + "channel, now reconfigure it to %"PRIu32"", + s_rmt_source_clock_hz, rmt_source_clk_hz); + } + + s_rmt_source_clock_hz = rmt_source_clk_hz; +#endif + rmtinfo("rmt_source_clk_hz: %"PRIu32, rmt_source_clk_hz); + + if (mode == RMT_MODE_TX) + { + uint16_t carrier_duty_percent = + rmt_param->tx_config.carrier_duty_percent; + uint8_t carrier_level = rmt_param->tx_config.carrier_level; + uint8_t idle_level = rmt_param->tx_config.idle_level; + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + rmt_ll_tx_set_channel_clock_div(dev, channel, clk_div); + rmt_ll_tx_set_mem_blocks(dev, channel, mem_cnt); + rmt_ll_tx_reset_pointer(dev, channel); + rmt_ll_tx_enable_loop(dev, channel, rmt_param->tx_config.loop_en); +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + if (rmt_param->tx_config.loop_en) + { + rmt_ll_tx_set_loop_count(dev, channel, + rmt_param->tx_config.loop_count); + } +#endif + + /* always enable tx ping-pong */ + + rmt_ll_tx_enable_wrap(dev, channel, true); + + /* Set idle level */ + + rmt_ll_tx_fix_idle_level(dev, channel, idle_level, + rmt_param->tx_config.idle_output_en); + + /* Set carrier */ + + rmt_ll_tx_enable_carrier_modulation(dev, channel, carrier_en); + if (carrier_en) + { + uint32_t duty_div; + uint32_t duty_h; + uint32_t duty_l; + duty_div = rmt_source_clk_hz / carrier_freq_hz; + duty_h = duty_div * carrier_duty_percent / 100; + duty_l = duty_div - duty_h; + rmt_ll_tx_set_carrier_level(dev, channel, carrier_level); + rmt_ll_tx_set_carrier_high_low_ticks(dev, channel, duty_h, duty_l); + } + else + { + rmt_ll_tx_set_carrier_level(dev, channel, 0); + } + + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + + rmtinfo("Rmt Tx Channel %u|Gpio %u|Sclk_Hz %"PRIu32"|Div %u|Carrier_Hz" + " %"PRIu32"|Duty %u", channel, gpio_num, rmt_source_clk_hz, + clk_div, carrier_freq_hz, carrier_duty_percent); + } + else if (RMT_MODE_RX == mode) + { + uint8_t filter_cnt = rmt_param->rx_config.filter_ticks_thresh; + uint16_t threshold = rmt_param->rx_config.idle_threshold; + + flags = spin_lock_irqsave(g_rmtdev_common.rmt_spinlock); + rmt_ll_rx_set_channel_clock_div(dev, RMT_DECODE_RX_CHANNEL(channel), + clk_div); + rmt_ll_rx_set_mem_blocks(dev, RMT_DECODE_RX_CHANNEL(channel), mem_cnt); + rmt_ll_rx_reset_pointer(dev, RMT_DECODE_RX_CHANNEL(channel)); + rmt_ll_rx_set_mem_owner(dev, RMT_DECODE_RX_CHANNEL(channel), + RMT_LL_MEM_OWNER_HW); + + /* Set idle threshold */ + + rmt_ll_rx_set_idle_thres(dev, RMT_DECODE_RX_CHANNEL(channel), + threshold); + + /* Set RX filter */ + + rmt_ll_rx_set_filter_thres(dev, RMT_DECODE_RX_CHANNEL(channel), + filter_cnt); + rmt_ll_rx_enable_filter(dev, RMT_DECODE_RX_CHANNEL(channel), + rmt_param->rx_config.filter_en); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + + /* always enable rx ping-pong */ + + rmt_ll_rx_enable_wrap(dev, RMT_DECODE_RX_CHANNEL(channel), true); +#endif + +#if SOC_RMT_SUPPORT_RX_DEMODULATION + rmt_ll_rx_enable_carrier_demodulation(dev, + RMT_DECODE_RX_CHANNEL(channel), + rmt_param->rx_config.rm_carrier); + if (rmt_param->rx_config.rm_carrier) + { + uint32_t duty_total; + uint32_t duty_high; + uint32_t ch_clk_div = + rmt_ll_rx_get_channel_clock_div(dev, + RMT_DECODE_RX_CHANNEL(channel)); + duty_total = rmt_source_clk_hz / \ + ch_clk_div / \ + rmt_param->rx_config.carrier_freq_hz; + duty_high = duty_total * + rmt_param->rx_config.carrier_duty_percent / 100; + + /* there could be residual in timing the carrier pulse, so double + * enlarge the theoretical value. + */ + + rmt_ll_rx_set_carrier_high_low_ticks( + dev, RMT_DECODE_RX_CHANNEL(channel), duty_high * 2, + (duty_total - duty_high) * 2); + rmt_ll_rx_set_carrier_level(dev, RMT_DECODE_RX_CHANNEL(channel), + rmt_param->rx_config.carrier_level); + } +#endif + + spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags); + + rmtinfo("Rmt Rx Channel %u|Gpio %u|Sclk_Hz %"PRIu32"|Div %u|Thresold " + "%u|Filter %u", channel, gpio_num, rmt_source_clk_hz, clk_div, + threshold, filter_cnt); + } + + return OK; +} + +/**************************************************************************** + * Name: rmt_config + * + * Description: + * This function configures the RMT channel with the provided parameters. + * It enables the RMT module, sets the GPIO for the RMT channel, and + * configures the RMT peripheral using the internal configuration function. + * + * Input Parameters: + * rmt_param - Pointer to the structure containing the RMT channel + * configuration parameters. + * + * Returned Value: + * Returns OK on successful configuration of the RMT channel; a negated + * errno value is returned on any failure. + * + ****************************************************************************/ + +static int rmt_config(const struct rmt_channel_config_s *rmt_param) +{ + int ret = ERROR; + + rmt_module_enable(); + + rmt_set_gpio(rmt_param->channel, rmt_param->rmt_mode, rmt_param->gpio_num, + rmt_param->flags & RMT_CHANNEL_FLAGS_INVERT_SIG); + + ret = rmt_internal_config(&RMT, rmt_param); + + return ret; +} + +/**************************************************************************** + * Name: rmt_fill_memory + * + * Description: + * This function fills the RMT memory with the provided items. It copies + * the items from the source to the RMT memory for the specified channel, + * starting at the specified memory offset. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * item - Pointer to the items to be copied to the RMT memory. + * item_num - The number of items to be copied. + * mem_offset - The memory offset at which to start copying. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void IRAM_ATTR rmt_fill_memory(rmt_channel_t channel, + const rmt_item32_t *item, + uint16_t item_num, + uint16_t mem_offset) +{ + uint32_t *from = (uint32_t *)item; + volatile uint32_t *to = + (volatile uint32_t *)&RMTMEM.chan[channel].data32[0].val; + + to += mem_offset; + + while (item_num--) + { + *to++ = *from++; + } +} + +/**************************************************************************** + * Name: rmt_isr_register + * + * Description: + * This function registers an interrupt service routine (ISR) for the RMT + * peripheral. It allocates a CPU interrupt, attaches the ISR to the + * interrupt, and returns the status of the operation. + * + * Input Parameters: + * fn - Pointer to the ISR function. + * arg - Pointer to the argument to be passed to the ISR. + * intr_alloc_flags - Flags for the interrupt allocation. + * + * Returned Value: + * Returns OK on successful registration of the ISR; a negated errno value + * is returned on any failure. + * + ****************************************************************************/ + +static int rmt_isr_register(int (*fn)(int, void *, void *), void *arg, + int intr_alloc_flags) +{ + int cpuint; + int ret; + int cpu = up_cpu_index(); + + DEBUGASSERT(fn); + DEBUGASSERT(g_rmtdev_common.rmt_driver_channels == 0); + + cpuint = esp_setup_irq( +#ifndef CONFIG_ARCH_CHIP_ESP32S2 + cpu, +#endif + rmt_periph_signals.groups[0].irq, 1, ESP_CPUINT_LEVEL); + if (cpuint < 0) + { + rmterr("Failed to allocate a CPU interrupt.\n"); + return -ENOMEM; + } + + ret = irq_attach(rmt_periph_signals.groups[0].irq + XTENSA_IRQ_FIRSTPERIPH, + fn, &g_rmtdev_common.hal); + if (ret < 0) + { + rmterr("Couldn't attach IRQ to handler.\n"); + esp_teardown_irq( +#ifndef CONFIG_ARCH_CHIP_ESP32S2 + cpu, +#endif + rmt_periph_signals.groups[0].irq, cpuint); + return ret; + } + + return ret; +} + +/**************************************************************************** + * Name: rmt_driver_isr_default + * + * Description: + * This function is the default interrupt service routine (ISR) for the RMT + * peripheral. It handles TX end, TX threshold, RX end, RX threshold, loop + * count, RX error, and TX error interrupts. For each interrupt type, it + * checks the status, clears the interrupt, and performs the appropriate + * actions based on the RMT object associated with the channel. + * + * Input Parameters: + * irq - The interrupt request number. + * context - Pointer to the interrupt context. + * arg - Pointer to the argument to be passed to the ISR. + * + * Returned Value: + * Returns OK after handling all active interrupts. + * + ****************************************************************************/ + +static int IRAM_ATTR rmt_driver_isr_default(int irq, void *context, + void *arg) +{ + uint32_t status = 0; + rmt_item32_t *addr = NULL; + uint8_t channel = 0; + rmt_hal_context_t *hal = (rmt_hal_context_t *)arg; + + /* Tx end interrupt */ + + status = rmt_ll_get_tx_end_interrupt_status(hal->regs); + while (status) + { + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[channel]; + if (p_rmt) + { + nxsem_post(&p_rmt->tx_sem); + rmt_ll_tx_reset_pointer(g_rmtdev_common.hal.regs, channel); + p_rmt->tx_data = NULL; + p_rmt->tx_len_rem = 0; + p_rmt->tx_offset = 0; + p_rmt->tx_sub_len = 0; + p_rmt->sample_cur = NULL; + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_TX_DONE(channel)); + } + + /* Tx thres interrupt */ + + status = rmt_ll_get_tx_thres_interrupt_status(hal->regs); + while (status) + { + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[channel]; + if (p_rmt) + { + const rmt_item32_t *pdata = p_rmt->tx_data; + size_t len_rem = p_rmt->tx_len_rem; + rmt_idle_level_t idle_level = + rmt_ll_tx_get_idle_level(hal->regs, channel); + rmt_item32_t stop_data = (rmt_item32_t) + { + .level0 = idle_level, + .duration0 = 0, + }; + + if (len_rem >= p_rmt->tx_sub_len) + { + rmt_fill_memory(channel, pdata, p_rmt->tx_sub_len, + p_rmt->tx_offset); + p_rmt->tx_data += p_rmt->tx_sub_len; + p_rmt->tx_len_rem -= p_rmt->tx_sub_len; + } + else if (len_rem == 0) + { + rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_offset); + } + else + { + rmt_fill_memory(channel, pdata, len_rem, p_rmt->tx_offset); + rmt_fill_memory(channel, &stop_data, 1, + p_rmt->tx_offset + len_rem); + p_rmt->tx_data += len_rem; + p_rmt->tx_len_rem -= len_rem; + } + + if (p_rmt->tx_offset == 0) + { + p_rmt->tx_offset = p_rmt->tx_sub_len; + } + else + { + p_rmt->tx_offset = 0; + } + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_TX_THRES(channel)); + } + + /* Rx end interrupt */ + + status = rmt_ll_get_rx_end_interrupt_status(hal->regs); + while (status) + { + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[RMT_ENCODE_RX_CHANNEL(channel)]; + if (p_rmt) + { + int item_len; + rmt_ll_rx_enable(g_rmtdev_common.hal.regs, channel, false); + item_len = + rmt_ll_rx_get_memory_writer_offset(g_rmtdev_common.hal.regs, + channel); + rmt_ll_rx_set_mem_owner(g_rmtdev_common.hal.regs, channel, + RMT_LL_MEM_OWNER_SW); + if (circbuf_is_init(&p_rmt->rx_buf)) + { + int bytes; + + addr = (rmt_item32_t *) + RMTMEM.chan[RMT_ENCODE_RX_CHANNEL(channel)].data32; +#if SOC_RMT_SUPPORT_RX_PINGPONG + if (item_len > p_rmt->rx_item_start_idx) + { + item_len = item_len - p_rmt->rx_item_start_idx; + } + + /* Check for RX buffer max length */ + + if ((p_rmt->rx_item_len + item_len) > \ + (p_rmt->rx_item_buf_size / 4)) + { + int remaining_len = (p_rmt->rx_item_buf_size / 4) - \ + p_rmt->rx_item_len; + rmterr("ERROR: RX buffer too small: %d items dropped\n", + item_len - remaining_len); + item_len = remaining_len; + } + + memcpy((void *)(p_rmt->rx_item_buf + p_rmt->rx_item_len), + (void *)(addr + p_rmt->rx_item_start_idx), + item_len * 4); + p_rmt->rx_item_len += item_len; + bytes = circbuf_write(&p_rmt->rx_buf, + (void *)(p_rmt->rx_item_buf), + p_rmt->rx_item_len * 4); +#else + bytes = circbuf_write(&p_rmt->rx_buf, (void *)addr, + item_len * 4); +#endif + nxsem_post(&p_rmt->rx_sem); + if (bytes < (item_len * 4)) + { + rmterr("RMT RX BUFFER FULL"); + } + } + else + { + rmterr("RMT RX BUFFER ERROR"); + } + +#if SOC_RMT_SUPPORT_RX_PINGPONG + p_rmt->rx_item_start_idx = 0; + p_rmt->rx_item_len = 0; + memset((void *)p_rmt->rx_item_buf, 0, p_rmt->rx_item_buf_size); +#endif + rmt_ll_rx_reset_pointer(g_rmtdev_common.hal.regs, channel); + rmt_ll_rx_set_mem_owner(g_rmtdev_common.hal.regs, channel, + RMT_LL_MEM_OWNER_HW); + rmt_ll_rx_enable(g_rmtdev_common.hal.regs, channel, true); + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_RX_DONE(channel)); + } + +#if SOC_RMT_SUPPORT_RX_PINGPONG + + /* Rx thres interrupt */ + + status = rmt_ll_get_rx_thres_interrupt_status(hal->regs); + while (status) + { + int mem_item_size; + int rx_thres_lim; + int item_len; + + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[RMT_ENCODE_RX_CHANNEL(channel)]; + mem_item_size = rmt_ll_rx_get_mem_blocks(g_rmtdev_common.hal.regs, + channel) * RMT_MEM_ITEM_NUM; + rx_thres_lim = rmt_ll_rx_get_limit(g_rmtdev_common.hal.regs, channel); + item_len = (p_rmt->rx_item_start_idx == 0) ? rx_thres_lim : \ + (mem_item_size - rx_thres_lim); + if ((p_rmt->rx_item_len + item_len) > (p_rmt->rx_item_buf_size / 4)) + { + int remaining_len = (p_rmt->rx_item_buf_size / 4) - \ + p_rmt->rx_item_len; + rmterr("ERROR: RX buffer too small!\n"); + item_len = remaining_len; + } + + rmt_ll_rx_set_mem_owner(g_rmtdev_common.hal.regs, channel, + RMT_LL_MEM_OWNER_SW); + memcpy( + (void *)(p_rmt->rx_item_buf + p_rmt->rx_item_len), + (void *)(RMTMEM.chan[RMT_ENCODE_RX_CHANNEL(channel)].data32 \ + + p_rmt->rx_item_start_idx), item_len * 4); + rmt_ll_rx_set_mem_owner(g_rmtdev_common.hal.regs, channel, + RMT_LL_MEM_OWNER_HW); + p_rmt->rx_item_len += item_len; + p_rmt->rx_item_start_idx += item_len; + if (p_rmt->rx_item_start_idx >= mem_item_size) + { + p_rmt->rx_item_start_idx = 0; + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_RX_THRES(channel)); + } +#endif + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + + /* loop count interrupt */ + + status = rmt_ll_get_tx_loop_interrupt_status(hal->regs); + while (status) + { + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[channel]; + if (p_rmt) + { + if (p_rmt->loop_autostop) + { +#ifndef SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + + /* hardware doesn't support automatically stop output so driver + * should stop output here (possibility already overshotted + * several us). + */ + + rmt_ll_tx_stop(g_rmtdev_common.hal.regs, channel); + rmt_ll_tx_reset_pointer(g_rmtdev_common.hal.regs, channel); +#endif + } + + nxsem_post(&p_rmt->tx_sem); + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_TX_LOOP_END(channel)); + } +#endif + + /* RX Err interrupt */ + + status = rmt_ll_get_rx_err_interrupt_status(hal->regs); + while (status) + { + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[RMT_ENCODE_RX_CHANNEL(channel)]; + if (p_rmt) + { + /* Reset the receiver's write/read addresses to prevent endless + * err interrupts. + */ + + rmt_ll_rx_reset_pointer(g_rmtdev_common.hal.regs, channel); + rmtinfo("RMT RX channel %d error", channel); + rmtinfo("status: 0x%08x", + rmt_ll_rx_get_status_word(g_rmtdev_common.hal.regs, + channel)); + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_RX_ERROR(channel)); + } + + /* TX Err interrupt */ + + status = rmt_ll_get_tx_err_interrupt_status(hal->regs); + while (status) + { + channel = __builtin_ffs(status) - 1; + status &= ~(1 << channel); + rmt_obj_t *p_rmt = p_rmt_obj[channel]; + if (p_rmt) + { + /* Reset the transmitter's write/read addresses to prevent + * endless err interrupts. + */ + + rmt_ll_tx_reset_pointer(g_rmtdev_common.hal.regs, channel); + rmtinfo("RMT TX channel %d error", channel); + rmtinfo("status: 0x%08x", + rmt_ll_tx_get_status_word(g_rmtdev_common.hal.regs, + channel)); + } + + rmt_ll_clear_interrupt_status(hal->regs, + RMT_LL_EVENT_TX_ERROR(channel)); + } + + return OK; +} + +/**************************************************************************** + * Name: rmt_driver_install + * + * Description: + * This function installs the RMT driver for a specific channel. It + * allocates memory for the RMT object, initializes the object properties, + * and sets up the RX buffer if specified. It also registers the default + * ISR if this is the first RMT channel using the driver, and resets the + * RMT channel. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * rx_buf_size - The size of the RX buffer. + * intr_alloc_flags - Flags for the interrupt allocation. + * + * Returned Value: + * Returns OK on successful installation of the RMT driver; a negated errno + * value is returned on any failure. + * + ****************************************************************************/ + +static int rmt_driver_install(rmt_channel_t channel, size_t rx_buf_size, + int intr_alloc_flags) +{ + DEBUGASSERT(channel < RMT_CHANNEL_MAX); + + int ret = OK; + + if (p_rmt_obj[channel]) + { + rmtwarn("RMT driver already installed"); + return ERROR; + } + +#if CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH + if (intr_alloc_flags & ESP_INTR_FLAG_IRAM) + { + rmterr("ringbuf ISR functions in flash, but used in IRAM interrupt"); + return -EINVAL; + } +#endif + +#if !CONFIG_SPIRAM_USE_MALLOC + p_rmt_obj[channel] = calloc(1, sizeof(rmt_obj_t)); +#else + if (!(intr_alloc_flags & ESP_INTR_FLAG_IRAM)) + { + p_rmt_obj[channel] = calloc(1, sizeof(rmt_obj_t)); + } + else + { + p_rmt_obj[channel] = heap_caps_calloc(1, sizeof(rmt_obj_t), + MALLOC_CAP_INTERNAL | \ + MALLOC_CAP_8BIT); + } +#endif + + if (p_rmt_obj[channel] == NULL) + { + rmterr("RMT driver malloc error"); + return -ENOMEM; + } + + p_rmt_obj[channel]->tx_len_rem = 0; + p_rmt_obj[channel]->tx_data = NULL; + p_rmt_obj[channel]->channel = channel; + p_rmt_obj[channel]->tx_offset = 0; + p_rmt_obj[channel]->tx_sub_len = 0; + p_rmt_obj[channel]->wait_done = false; + p_rmt_obj[channel]->loop_autostop = false; + +#if !CONFIG_SPIRAM_USE_MALLOC + nxsem_init(&p_rmt_obj[channel]->tx_sem, 0, 0); + nxsem_init(&p_rmt_obj[channel]->rx_sem, 0, 0); +#endif + + nxsem_post(&p_rmt_obj[channel]->tx_sem); + + if (!circbuf_is_init(&p_rmt_obj[channel]->rx_buf) && rx_buf_size > 0) + { + circbuf_init(&p_rmt_obj[channel]->rx_buf, NULL, rx_buf_size); + } + +#if SOC_RMT_SUPPORT_RX_PINGPONG + if (p_rmt_obj[channel]->rx_item_buf == NULL && rx_buf_size > 0) + { +#if !CONFIG_SPIRAM_USE_MALLOC + p_rmt_obj[channel]->rx_item_buf = calloc(1, rx_buf_size); +#else + if (!(p_rmt_obj[channel]->intr_alloc_flags & ESP_INTR_FLAG_IRAM)) + { + p_rmt_obj[channel]->rx_item_buf = calloc(1, rx_buf_size); + } + else + { + p_rmt_obj[channel]->rx_item_buf = + heap_caps_calloc(1, rx_buf_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + } + +#endif + if (p_rmt_obj[channel]->rx_item_buf == NULL) + { + rmterr("RMT malloc fail"); + nxsem_destroy(&p_rmt_obj[channel]->rx_sem); + return -ENOMEM; + } + + p_rmt_obj[channel]->rx_item_buf_size = rx_buf_size; + } +#endif + + nxrmutex_lock(&(g_rmtdev_common.rmt_driver_isr_lock)); + + if (g_rmtdev_common.rmt_driver_channels == 0) + { + /* first RMT channel using driver */ + + ret = rmt_isr_register(rmt_driver_isr_default, &g_rmtdev_common.hal, + intr_alloc_flags); + } + + if (ret == OK) + { + g_rmtdev_common.rmt_driver_channels |= BIT(channel); + } + + nxrmutex_unlock(&(g_rmtdev_common.rmt_driver_isr_lock)); + + rmt_module_enable(); + + if (RMT_IS_RX_CHANNEL(channel)) + { + rmt_hal_rx_channel_reset(&g_rmtdev_common.hal, + RMT_DECODE_RX_CHANNEL(channel)); + } + else + { + rmt_hal_tx_channel_reset(&g_rmtdev_common.hal, channel); + } + + return OK; +} + +/**************************************************************************** + * Name: rmt_write_items + * + * Description: + * This function writes items to the RMT memory for a specific channel. It + * checks the validity of the parameters, calculates the memory blocks and + * item lengths, and fills the memory with the items. If the number of + * items is greater than the memory block length, it enables the TX + * threshold interrupt and sets up the remaining items to be sent. If the + * number of items is less than the memory block length, it fills the + * remaining memory with idle level items. It then starts the TX process + * and waits for it to finish if specified. + * + * Input Parameters: + * channel - The RMT peripheral channel number. + * rmt_item - Pointer to the items to be written to the RMT memory. + * item_num - The number of items to be written. + * wait_tx_done - Flag to indicate whether to wait for the TX process to + * finish. + * + * Returned Value: + * Returns OK on successful writing of the items to the RMT memory; a + * negated errno value is returned on any failure. + * + ****************************************************************************/ + +static int rmt_write_items(rmt_channel_t channel, + const rmt_item32_t *rmt_item, + int item_num, + bool wait_tx_done) +{ + DEBUGASSERT(RMT_IS_TX_CHANNEL(channel)); + DEBUGASSERT(p_rmt_obj[channel]); + DEBUGASSERT(rmt_item); + DEBUGASSERT(item_num > 0); + + uint32_t mem_blocks = rmt_ll_tx_get_mem_blocks(g_rmtdev_common.hal.regs, + channel); + + DEBUGASSERT(mem_blocks + channel <= SOC_RMT_CHANNELS_PER_GROUP); +#if CONFIG_SPIRAM_USE_MALLOC + if (p_rmt_obj[channel]->intr_alloc_flags & ESP_INTR_FLAG_IRAM) + { + if (!esp_ptr_internal(rmt_item)) + { + remterr(RMT_PSRAM_BUFFER_WARN_STR); + return ESP_ERR_INVALID_ARG; + } + } +#endif + + rmt_obj_t *p_rmt = p_rmt_obj[channel]; + int item_block_len = mem_blocks * RMT_MEM_ITEM_NUM; + int item_sub_len = mem_blocks * RMT_MEM_ITEM_NUM / 2; + int len_rem = item_num; + nxsem_wait(&p_rmt->tx_sem); + + /* fill the memory block first */ + + if (item_num >= item_block_len) + { + rmt_fill_memory(channel, rmt_item, item_block_len, 0); + len_rem -= item_block_len; + rmt_set_tx_loop_mode(channel, false); + rmt_set_tx_thr_intr_en(channel, 1, item_sub_len); + p_rmt->tx_data = rmt_item + item_block_len; + p_rmt->tx_len_rem = len_rem; + p_rmt->tx_offset = 0; + p_rmt->tx_sub_len = item_sub_len; + } + else + { + rmt_idle_level_t idle_level; + rmt_fill_memory(channel, rmt_item, len_rem, 0); + idle_level = rmt_ll_tx_get_idle_level(g_rmtdev_common.hal.regs, + channel); + rmt_item32_t stop_data = (rmt_item32_t) + { + .level0 = idle_level, + .duration0 = 0, + }; + + rmt_fill_memory(channel, &stop_data, 1, len_rem); + p_rmt->tx_len_rem = 0; + } + + rmt_tx_start(channel, true); + p_rmt->wait_done = wait_tx_done; + if (wait_tx_done) + { + /* wait loop done */ + + if (rmt_ll_tx_is_loop_enabled(g_rmtdev_common.hal.regs, channel)) + { +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + nxsem_wait(&p_rmt->tx_sem); + nxsem_post(&p_rmt->tx_sem); +#endif + } + else + { + /* wait tx end */ + + nxsem_wait(&p_rmt->tx_sem); + nxsem_post(&p_rmt->tx_sem); + } + } + + return OK; +} + +/**************************************************************************** + * Name: esp_rmt_read + * + * Description: + * This function reads data from the RMT device. + * It starts the RMT module in receiving mode for a specific channel and + * checks for any errors. If an error occurs during the start of the RMT + * module, it returns the error code. Please note that this function + * starts the receiver, but the actual data is read from the ring buffer + * by the upper half driver. + * + * Input Parameters: + * dev - Pointer to the RMT device structure. + * buffer - Pointer to the buffer where the read data should be stored. + * buflen - The maximum amount of data to be read. + * + * Returned Value: + * Returns the number of bytes read from the RMT device; a negated errno + * value is returned on any failure. + * + ****************************************************************************/ + +static ssize_t esp_rmt_read(struct rmt_dev_s *dev, char *buffer, + size_t buflen) +{ + struct rmt_dev_lowerhalf_s *priv = (struct rmt_dev_lowerhalf_s *)dev; + rmt_mode_t mode = priv->mode; + int channel = priv->minor; + int ret; + ssize_t nread; + + if (mode != RMT_MODE_RX) + { + rmterr("ERROR: RMT channel %d is not in RX mode\n", channel); + return -EINVAL; + } + + DEBUGASSERT((buflen % 4) == 0); + + if ((buflen / 4) > (CONFIG_RMT_DEFAULT_RX_BUFFER_SIZE / 4)) + { + rmtwarn("WARN: RMT RX buffer (%d bytes) is smaller than requested " + "read bytes (%d bytes). A partial read will take place!\n", + CONFIG_RMT_DEFAULT_RX_BUFFER_SIZE, + buflen); + } + +#ifndef SOC_RMT_SUPPORT_RX_PINGPONG + if ((buflen / 4) > RMT_MEM_ITEM_NUM) + { + rmtwarn("WARN: RMT RX channel is able to receive up to " + "%d RMT items (%d bytes)!", + RMT_MEM_ITEM_NUM, RMT_MEM_ITEM_NUM * 4); + } +#endif + + ret = rmt_rx_start(channel, true); + if (ret < 0) + { + rmterr("ERROR: rmt_rx_start failed: %d\n", ret); + return (ssize_t)ret; + } + + return (ssize_t)ret; +} + +/**************************************************************************** + * Name: esp_rmt_write + * + * Description: + * This function writes data to the RMT memory for a specific channel. It + * asserts that the length of the data is a multiple of 4, then calls the + * rmt_write_items function to write the items to the RMT memory. + * + * Input Parameters: + * dev - Pointer to the RMT device structure. + * buffer - Pointer to the data to be written to the RMT memory. + * buflen - The length of the data to be written. + * + * Returned Value: + * Returns the number of items written to the RMT memory. + * + ****************************************************************************/ + +static ssize_t esp_rmt_write(struct rmt_dev_s *dev, const char *buffer, + size_t buflen) +{ + struct rmt_dev_lowerhalf_s *priv = (struct rmt_dev_lowerhalf_s *)dev; + rmt_mode_t mode = priv->mode; + int channel = priv->minor; + int ret; + struct timespec timeout; + + if (mode != RMT_MODE_TX) + { + rmterr("ERROR: RMT channel %d is not in TX mode\n", channel); + return -EINVAL; + } + + DEBUGASSERT((buflen % 4) == 0); + + ret = rmt_write_items(channel, (const rmt_item32_t *)buffer, + (buflen / 4), true); + + if (ret < 0) + { + rmterr("ERROR: rmt_write_items failed: %d\n", ret); + return (ssize_t)0; + } + + return (ssize_t)buflen; +} + +/**************************************************************************** + * Name: esp_rmtinitialize + * + * Description: + * This function initializes the specified RMT (Remote Control) device + * with the provided configuration. + * + * Input Parameters: + * config - A structure containing the configuration settings for the + * RMT channel to be initialized. + * + * Returned Value: + * On success, this function returns a valid pointer to the RMT device + * structure. If the initialization fails, it returns NULL. + * + ****************************************************************************/ + +static struct rmt_dev_s + *esp_rmtinitialize(struct rmt_channel_config_s config) +{ + struct rmt_dev_lowerhalf_s *priv; + int ret; +#ifdef CONFIG_RMT_LOOP_TEST_MODE + uint8_t channel; +#endif + +#if (CONFIG_RMT_DEFAULT_RX_BUFFER_SIZE % 4) != 0 +# error "CONFIG_RMT_DEFAULT_RX_BUFFER_SIZE must be a multiple of 4" +#endif + + priv = kmm_zalloc(sizeof(struct rmt_dev_lowerhalf_s)); + if (priv) + { + ret = rmt_config(&config); + if (ret < 0) + { + rmterr("ERROR: rmt_config failed: %d\n", ret); + return NULL; + } + +#ifdef CONFIG_RMT_LOOP_TEST_MODE + if (config.rmt_mode == RMT_MODE_TX) + { + if (g_tx_channel != RMT_CHANNEL_MAX) + { + rmterr("ERROR: only one TX channel can be used in loop test " + "mode\n"); + PANIC(); + } + + g_tx_channel = config.channel; + } + else + { + if (g_rx_channel != RMT_CHANNEL_MAX) + { + rmterr("ERROR: only one RX channel can be used in loop test " + "mode\n"); + PANIC(); + } + + g_rx_channel = config.channel; + } + + if (g_rx_channel != RMT_CHANNEL_MAX && g_tx_channel != RMT_CHANNEL_MAX) + { + esp_configgpio(config.gpio_num, GPIO_OUT_FUNC | GPIO_IN_FUNC); + esp_gpio_matrix_out(config.gpio_num, + RMT_SIG_OUT0_IDX + g_tx_channel, + 0, 0); + esp_gpio_matrix_in(config.gpio_num, + RMT_SIG_IN0_IDX + g_rx_channel, + 0); + rmtwarn("RX channel %d and TX channel %d are used in loop test " + "mode\n", g_rx_channel, g_tx_channel); + } +#endif + + ret = rmt_driver_install(config.channel, + config.rmt_mode == RMT_MODE_RX ? \ + CONFIG_RMT_DEFAULT_RX_BUFFER_SIZE : 0, 0); + if (ret < 0) + { + rmterr("ERROR: rmt_driver_install failed: %d\n", ret); + return NULL; + } + + priv->ops = &g_rmtops; + priv->recvsem = &p_rmt_obj[config.channel]->rx_sem; + priv->circbuf = &p_rmt_obj[config.channel]->rx_buf; + priv->minor = config.channel; + + priv->common = &g_rmtdev_common; + priv->mode = config.rmt_mode; + } + else + { + rmterr("ERROR: memory allocation failed\n"); + return NULL; + } + + return (struct rmt_dev_s *)priv; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_rmt_tx_init + * + * Description: + * Initialize the selected RMT device in TX mode + * + * Input Parameters: + * ch - the RMT's channel that will be used + * pin - The pin used for the TX channel + * + * Returned Value: + * Valid RMT device structure reference on success; NULL, otherwise. + * + ****************************************************************************/ + +struct rmt_dev_s *esp_rmt_tx_init(int ch, int pin) +{ + struct rmt_channel_config_s config = RMT_DEFAULT_CONFIG_TX(pin, ch); + + return esp_rmtinitialize(config); +} + +/**************************************************************************** + * Name: esp_rmt_rx_init + * + * Description: + * Initialize the selected RMT device in RC mode + * + * Input Parameters: + * ch - the RMT's channel that will be used + * pin - The pin used for the RX channel + * + * Returned Value: + * Valid RMT device structure reference on success; NULL, otherwise. + * + ****************************************************************************/ + +struct rmt_dev_s *esp_rmt_rx_init(int ch, int pin) +{ + struct rmt_channel_config_s config = RMT_DEFAULT_CONFIG_RX(pin, ch); + + return esp_rmtinitialize(config); +} + +#endif diff --git a/arch/xtensa/src/common/espressif/esp_rmt.h b/arch/xtensa/src/common/espressif/esp_rmt.h new file mode 100644 index 0000000000..336725bf3f --- /dev/null +++ b/arch/xtensa/src/common/espressif/esp_rmt.h @@ -0,0 +1,109 @@ +/**************************************************************************** + * arch/xtensa/src/common/espressif/esp_rmt.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_COMMON_ESPRESSIF_ESP_RMT_H +#define __ARCH_XTENSA_SRC_COMMON_ESPRESSIF_ESP_RMT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define RMT_MEM_ITEM_NUM SOC_RMT_MEM_WORDS_PER_CHANNEL + +#define RMT_DEFAULT_CLK_DIV 1 + +/* Channel can work during APB clock scaling */ + +#define RMT_CHANNEL_FLAGS_AWARE_DFS (1 << 0) + +/* Invert RMT signal */ + +#define RMT_CHANNEL_FLAGS_INVERT_SIG (1 << 1) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifndef __ASSEMBLY__ +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +#if defined(CONFIG_ESP_RMT) + +/**************************************************************************** + * Name: esp_rmt_tx_init + * + * Description: + * Initialize the selected RMT device in TX mode + * + * Input Parameters: + * ch - the RMT's channel that will be used + * pin - The pin used for the TX channel + * + * Returned Value: + * Valid RMT device structure reference on success; NULL, otherwise. + * + ****************************************************************************/ + +struct rmt_dev_s *esp_rmt_tx_init(int ch, int pin); + +/**************************************************************************** + * Name: esp_rmt_rx_init + * + * Description: + * Initialize the selected RMT device in RC mode + * + * Input Parameters: + * ch - the RMT's channel that will be used + * pin - The pin used for the RX channel + * + * Returned Value: + * Valid RMT device structure reference on success; NULL, otherwise. + * + ****************************************************************************/ + +struct rmt_dev_s *esp_rmt_rx_init(int ch, int pin); + +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __ASSEMBLY__ */ + +#endif /* __ARCH_XTENSA_SRC_COMMON_ESPRESSIF_ESP_RMT_H */ diff --git a/drivers/rmt/Kconfig b/drivers/rmt/Kconfig index 0e8de76d75..fa7b2891f0 100644 --- a/drivers/rmt/Kconfig +++ b/drivers/rmt/Kconfig @@ -33,4 +33,15 @@ config RMT_DEFAULT_RX_BUFFER_SIZE The RMT RX default buffer size. This is the expected buffer size that should be returned on a `read()` operation. +config RMT_LOOP_TEST_MODE + bool "RMT character driver loopback test mode (for testing only)" + depends on EXPERIMENTAL + default n + ---help--- + This enables a lower-half driver-specific loopback test + mode that attaches the transmitter to the receiver, being + able to test the RMT peripheral without any external + connection. This feature depends on lower-half driver + implementation. + endif # RMT