1965 lines
62 KiB
C
1965 lines
62 KiB
C
/****************************************************************************
|
|
* arch/risc-v/src/common/espressif/esp_rmt.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <nuttx/config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <arch/board/board.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/rmt/rmt.h>
|
|
#include <nuttx/spinlock.h>
|
|
#include <nuttx/circbuf.h>
|
|
|
|
#include "esp_gpio.h"
|
|
#include "esp_irq.h"
|
|
|
|
#include "esp_attr.h"
|
|
#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, \
|
|
} \
|
|
}
|
|
|
|
#if SOC_PERIPH_CLK_CTRL_SHARED
|
|
#define RMT_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
|
|
#else
|
|
#define RMT_CLOCK_SRC_ATOMIC()
|
|
#endif
|
|
|
|
#if !SOC_RCC_IS_INDEPENDENT
|
|
#define RMT_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
|
|
#else
|
|
#define RMT_RCC_ATOMIC()
|
|
#endif
|
|
|
|
#define rmt_item32_t rmt_symbol_word_t
|
|
|
|
/****************************************************************************
|
|
* 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 */
|
|
#if SOC_RMT_CHANNELS_PER_GROUP > 4
|
|
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 */
|
|
#endif
|
|
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-half device struct */
|
|
|
|
const struct rmt_ops_s *ops;
|
|
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;
|
|
#ifdef 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;
|
|
#ifdef 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(struct rmt_dev_s *dev,
|
|
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
|
|
uint32_t g_rmt_source_clock_hz[RMT_CHANNEL_MAX];
|
|
#else
|
|
uint32_t g_rmt_source_clock_hz;
|
|
#endif
|
|
|
|
/* RMTMEM address is declared in <target>.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)
|
|
{
|
|
RMT_RCC_ATOMIC()
|
|
{
|
|
rmt_ll_enable_bus_clock(0, true);
|
|
rmt_ll_reset_register(0);
|
|
}
|
|
|
|
periph_module_reset(PERIPH_RMT_MODULE);
|
|
periph_module_enable(PERIPH_RMT_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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef 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);
|
|
#ifdef 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);
|
|
|
|
#ifdef 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, OUTPUT);
|
|
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, INPUT);
|
|
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);
|
|
}
|
|
|
|
RMT_CLOCK_SRC_ATOMIC()
|
|
{
|
|
rmt_ll_enable_group_clock(dev, true);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&g_rmtdev_common.rmt_spinlock, flags);
|
|
|
|
#if SOC_RMT_CHANNEL_CLK_INDEPENDENT
|
|
g_rmt_source_clock_hz[channel] = rmt_source_clk_hz;
|
|
#else
|
|
if (g_rmt_source_clock_hz && rmt_source_clk_hz != g_rmt_source_clock_hz)
|
|
{
|
|
rmterr("RMT clock source has been configured to %"PRIu32" by other "
|
|
"channel, now reconfigure it to %"PRIu32"",
|
|
g_rmt_source_clock_hz, rmt_source_clk_hz);
|
|
}
|
|
|
|
g_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);
|
|
|
|
#ifdef 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|Threshold "
|
|
"%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 = this_cpu();
|
|
|
|
DEBUGASSERT(fn);
|
|
DEBUGASSERT(g_rmtdev_common.rmt_driver_channels == 0);
|
|
|
|
cpuint = esp_setup_irq(rmt_periph_signals.groups[0].irq,
|
|
ESP_IRQ_PRIORITY_DEFAULT,
|
|
ESP_IRQ_TRIGGER_LEVEL);
|
|
if (cpuint < 0)
|
|
{
|
|
rmterr("Failed to allocate a CPU interrupt.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = irq_attach(ESP_SOURCE2IRQ(rmt_periph_signals.groups[0].irq),
|
|
fn, &g_rmtdev_common.hal);
|
|
if (ret < 0)
|
|
{
|
|
rmterr("Couldn't attach IRQ to handler.\n");
|
|
esp_teardown_irq(rmt_periph_signals.groups[0].irq, cpuint);
|
|
return ret;
|
|
}
|
|
|
|
up_enable_irq(ESP_SOURCE2IRQ(rmt_periph_signals.groups[0].irq));
|
|
|
|
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;
|
|
#ifdef 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");
|
|
}
|
|
|
|
#ifdef 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));
|
|
}
|
|
|
|
#ifdef 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%08lx",
|
|
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%08lx",
|
|
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
|
|
|
|
#ifndef CONFIG_SPIRAM_USE_MALLOC
|
|
p_rmt_obj[channel] = kmm_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] = kmm_calloc(1, sizeof(rmt_obj_t));
|
|
}
|
|
#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;
|
|
|
|
#ifndef 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);
|
|
}
|
|
|
|
#ifdef SOC_RMT_SUPPORT_RX_PINGPONG
|
|
if (p_rmt_obj[channel]->rx_item_buf == NULL && rx_buf_size > 0)
|
|
{
|
|
# ifndef CONFIG_SPIRAM_USE_MALLOC
|
|
p_rmt_obj[channel]->rx_item_buf = kmm_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 = kmm_calloc(1, rx_buf_size);
|
|
}
|
|
|
|
# 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);
|
|
#ifdef CONFIG_SPIRAM_USE_MALLOC
|
|
if (p_rmt_obj[channel]->intr_alloc_flags & ESP_INTR_FLAG_IRAM)
|
|
{
|
|
if (!esp_ptr_internal(rmt_item))
|
|
{
|
|
rmterr(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, OUTPUT | INPUT);
|
|
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
|