blob: bb45a49759182947202732a444bf193a1e16d349 [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/esp32/esp32_serial.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 <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#ifdef CONFIG_SERIAL_TERMIOS
# include <termios.h>
#endif
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/serial/serial.h>
#include <nuttx/spinlock.h>
#include <arch/board/board.h>
#include "xtensa.h"
#include "hardware/esp32_soc.h"
#include "hardware/esp32_iomux.h"
#include "hardware/esp32_gpio_sigmap.h"
#include "hardware/esp32_uart.h"
#include "hardware/esp32_uhci.h"
#include "hardware/esp32_dma.h"
#include "esp32_config.h"
#include "esp32_gpio.h"
#include "esp32_irq.h"
#include "esp32_dma.h"
#include "hardware/esp32_dport.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef USE_SERIALDRIVER
/* Which UART with be tty0/console and which tty1-2? */
/* First pick the console and ttys0. This could be any of UART0-5 */
#if defined(CONFIG_UART0_SERIAL_CONSOLE)
# define CONSOLE_DEV g_uart0port /* UART0 is console */
# define TTYS0_DEV g_uart0port /* UART0 is ttyS0 */
# define UART0_ASSIGNED 1
#elif defined(CONFIG_UART1_SERIAL_CONSOLE)
# define CONSOLE_DEV g_uart1port /* UART1 is console */
# define TTYS0_DEV g_uart1port /* UART1 is ttyS0 */
# define UART1_ASSIGNED 1
#elif defined(CONFIG_UART2_SERIAL_CONSOLE)
# define CONSOLE_DEV g_uart2port /* UART2 is console */
# define TTYS0_DEV g_uart2port /* UART2 is ttyS0 */
# define UART2_ASSIGNED 1
#else
# undef CONSOLE_DEV /* No console */
# if defined(CONFIG_ESP32_UART0)
# define TTYS0_DEV g_uart0port /* UART0 is ttyS0 */
# define UART0_ASSIGNED 1
# elif defined(CONFIG_ESP32_UART1)
# define TTYS0_DEV g_uart1port /* UART1 is ttyS0 */
# define UART1_ASSIGNED 1
# elif defined(CONFIG_ESP32_UART2)
# define TTYS0_DEV g_uart2port /* UART2 is ttyS0 */
# define UART2_ASSIGNED 1
# endif
#endif
/* Pick ttys1. This could be any of UART0-2 excluding the console
* UART.
*/
#if defined(CONFIG_ESP32_UART0) && !defined(UART0_ASSIGNED)
# define TTYS1_DEV g_uart0port /* UART0 is ttyS1 */
# define UART0_ASSIGNED 1
#elif defined(CONFIG_ESP32_UART1) && !defined(UART1_ASSIGNED)
# define TTYS1_DEV g_uart1port /* UART1 is ttyS1 */
# define UART1_ASSIGNED 1
#elif defined(CONFIG_ESP32_UART2) && !defined(UART2_ASSIGNED)
# define TTYS1_DEV g_uart2port /* UART2 is ttyS1 */
# define UART2_ASSIGNED 1
#endif
/* Pick ttys2. This could be one of UART1-2. It can't be UART0
* because that was either assigned as ttyS0 or ttys1. One of these
* could also be the console.
*/
#if defined(CONFIG_ESP32_UART1) && !defined(UART1_ASSIGNED)
# define TTYS2_DEV g_uart1port /* UART1 is ttyS2 */
# define UART1_ASSIGNED 1
#elif defined(CONFIG_ESP32_UART2) && !defined(UART2_ASSIGNED)
# define TTYS2_DEV g_uart2port /* UART2 is ttyS2 */
# define UART2_ASSIGNED 1
#endif
/* UART source clock for BAUD generation */
#define UART_CLK_FREQ APB_CLK_FREQ
/* DMA related */
#ifdef CONFIG_SERIAL_RXDMA
# error "SERIAL_RXDMA is not supported in ESP32 due to a hardware issue"
#endif
#ifdef CONFIG_SERIAL_TXDMA
#define UART_SELECT_SHIFT UHCI_UART0_CE_S
/* UART DMA RX/TX number of descriptors */
#define UART_DMADESC_NUM (CONFIG_UART_DMADESC_NUM)
/* In case the three UARTs select TX DMA support,
* let DMA 0 exclusive to one and DMA 1
* shared with others.
*/
#if defined(CONFIG_ESP32_UART0_TXDMA) && defined(CONFIG_ESP32_UART1_TXDMA) && defined(CONFIG_ESP32_UART2_TXDMA)
# ifdef CONFIG_ESP32_UART0_EXC
# define UART0_DMA 0
# define UART1_DMA 1
# define UART2_DMA 1
# elif defined(CONFIG_ESP32_UART1_EXC)
# define UART0_DMA 1
# define UART1_DMA 0
# define UART2_DMA 1
# elif defined(CONFIG_ESP32_UART2_EXC)
# define UART0_DMA 1
# define UART1_DMA 1
# define UART2_DMA 0
# endif
# define USE_DMA0 1
# define USE_DMA1 1
#else
# ifdef CONFIG_ESP32_UART0_TXDMA
# define UART0_DMA 0
# define USE_DMA0 1
# endif
# ifdef CONFIG_ESP32_UART1_TXDMA
# ifndef USE_DMA0
# define UART1_DMA 0
# define USE_DMA0 1
# else
# define UART1_DMA 1
# define USE_DMA1 1
# endif
# endif
# ifdef CONFIG_ESP32_UART2_TXDMA
# ifndef USE_DMA0
# define UART2_DMA 0
# define USE_DMA0 1
# else
# define UART2_DMA 1
# define USE_DMA1 1
# endif
# endif
#endif
/* UART DMA controllers */
#if defined(USE_DMA0) && defined(USE_DMA1)
# define UART_DMA_CONTROLLERS_NUM 2
#else
# define UART_DMA_CONTROLLERS_NUM 1
#endif
/* Semaphores to control access to each DMA.
* These semaphores ensure a new transfer is
* triggered only after the previous one is completed,
* and it also avoids competing issues with multiple UART
* instances requesting to the same DMA.
*/
#ifdef USE_DMA0
static sem_t g_dma0_sem = SEM_INITIALIZER(1);
#endif
#ifdef USE_DMA1
static sem_t g_dma1_sem = SEM_INITIALIZER(1);
#endif
/* UART DMA RX/TX descriptors */
struct esp32_dmadesc_s s_dma_rxdesc[UART_DMA_CONTROLLERS_NUM]
[UART_DMADESC_NUM];
struct esp32_dmadesc_s s_dma_txdesc[UART_DMA_CONTROLLERS_NUM]
[UART_DMADESC_NUM];
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* Constant properties of the UART. Other configuration setting may be
* changeable via Termios IOCTL calls.
*/
struct esp32_config_s
{
const uint8_t id; /* UART id */
uint8_t periph; /* UART peripheral ID */
uint8_t irq; /* IRQ number assigned to the peripheral */
uint8_t txpin; /* Tx pin number (0-39) */
uint8_t rxpin; /* Rx pin number (0-39) */
uint8_t txsig; /* Tx signal */
uint8_t rxsig; /* Rx signal */
#ifdef CONFIG_SERIAL_IFLOWCONTROL
uint8_t rtspin; /* RTS pin number (0-39) */
uint8_t rtssig; /* RTS signal */
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
uint8_t ctspin; /* CTS pin number (0-39) */
uint8_t ctssig; /* CTS signal */
#endif
#ifdef CONFIG_SERIAL_TXDMA
uint8_t dma_chan; /* DMA instance 0-1 */
sem_t *dma_sem; /* DMA semaphore */
#endif
#ifdef HAVE_RS485
uint8_t rs485_dir_gpio; /* UART RS-485 DIR GPIO pin cfg */
bool rs485_dir_polarity; /* UART RS-485 DIR TXEN polarity */
#endif
};
/* Current state of the UART */
struct esp32_dev_s
{
const struct esp32_config_s *config; /* Constant configuration */
uint32_t baud; /* Configured baud */
uint32_t status; /* Saved status bits */
int cpuint; /* CPU interrupt assigned to this UART */
uint8_t cpu; /* CPU ID */
uint8_t parity; /* 0=none, 1=odd, 2=even */
uint8_t bits; /* Number of bits (5-9) */
bool stopbits2; /* true: Configure with 2 stop bits instead of 1 */
#ifdef CONFIG_SERIAL_TXDMA
bool txdma; /* TX DMA enabled for this UART */
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
bool iflow; /* Input flow control (RTS) enabled */
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
bool oflow; /* Output flow control (CTS) enabled */
#endif
spinlock_t lock;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int esp32_setup(struct uart_dev_s *dev);
static void esp32_shutdown(struct uart_dev_s *dev);
static int esp32_attach(struct uart_dev_s *dev);
static void esp32_detach(struct uart_dev_s *dev);
static int esp32_interrupt(int cpuint, void *context, void *arg);
static int esp32_ioctl(struct file *filep, int cmd, unsigned long arg);
static int esp32_receive(struct uart_dev_s *dev, unsigned int *status);
static void esp32_rxint(struct uart_dev_s *dev, bool enable);
static bool esp32_rxavailable(struct uart_dev_s *dev);
static void esp32_send(struct uart_dev_s *dev, int ch);
static void esp32_txint(struct uart_dev_s *dev, bool enable);
static bool esp32_txready(struct uart_dev_s *dev);
static bool esp32_txempty(struct uart_dev_s *dev);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
static bool esp32_rxflowcontrol(struct uart_dev_s *dev,
unsigned int nbuffered, bool upper);
#endif
#ifdef CONFIG_SERIAL_TXDMA
static void esp32_dmasend(struct uart_dev_s *dev);
static void esp32_dmatxavail(struct uart_dev_s *dev);
static void dma_config(uint8_t dma_chan);
static void dma_attach(uint8_t dma_chan);
static inline void dma_enable_int(uint8_t dma_chan);
static inline void dma_disable_int(uint8_t dma_chan);
static int esp32_interrupt_dma(int cpuint, void *context, void *arg);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
#define UART_TX_FIFO_SIZE 128
#define UART_RX_FIFO_FULL_THRHD 112
#define UART_RX_TOUT_THRHD_VALUE 0x02
#define UART_RX_FLOW_THRHD_VALUE 64 /* Almost half RX FIFO size */
static const struct uart_ops_s g_uart_ops =
{
.setup = esp32_setup,
.shutdown = esp32_shutdown,
.attach = esp32_attach,
.detach = esp32_detach,
.ioctl = esp32_ioctl,
.receive = esp32_receive,
.rxint = esp32_rxint,
.rxavailable = esp32_rxavailable,
.send = esp32_send,
.txint = esp32_txint,
.txready = esp32_txready,
.txempty = esp32_txempty,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
.rxflowcontrol = esp32_rxflowcontrol,
#endif
#ifdef CONFIG_SERIAL_TXDMA
.dmasend = esp32_dmasend,
.dmatxavail = esp32_dmatxavail,
#endif
};
/* I/O buffers */
#ifdef CONFIG_ESP32_UART0
static char g_uart0rxbuffer[CONFIG_UART0_RXBUFSIZE];
static char g_uart0txbuffer[CONFIG_UART0_TXBUFSIZE];
#endif
#ifdef CONFIG_ESP32_UART1
static char g_uart1rxbuffer[CONFIG_UART1_RXBUFSIZE];
static char g_uart1txbuffer[CONFIG_UART1_TXBUFSIZE];
#endif
#ifdef CONFIG_ESP32_UART2
static char g_uart2rxbuffer[CONFIG_UART2_RXBUFSIZE];
static char g_uart2txbuffer[CONFIG_UART2_TXBUFSIZE];
#endif
/* This describes the state of the UART0 port. */
#ifdef CONFIG_ESP32_UART0
static const struct esp32_config_s g_uart0config =
{
.id = 0,
.periph = ESP32_PERIPH_UART,
.irq = ESP32_IRQ_UART,
.txpin = CONFIG_ESP32_UART0_TXPIN,
.rxpin = CONFIG_ESP32_UART0_RXPIN,
.txsig = U0TXD_OUT_IDX,
.rxsig = U0RXD_IN_IDX,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
.rtspin = CONFIG_ESP32_UART0_RTSPIN,
.rtssig = U0RTS_OUT_IDX,
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
.ctspin = CONFIG_ESP32_UART0_CTSPIN,
.ctssig = U0CTS_IN_IDX,
#endif
#ifdef CONFIG_SERIAL_TXDMA
#ifdef CONFIG_ESP32_UART0_TXDMA
.dma_chan = UART0_DMA,
#if UART0_DMA == 0
.dma_sem = &g_dma0_sem,
#else
.dma_sem = &g_dma1_sem,
#endif
#endif
#endif
#ifdef CONFIG_ESP32_UART0_RS485
.rs485_dir_gpio = CONFIG_ESP32_UART0_RS485_DIR_PIN,
#if (CONFIG_ESP32_UART0_RS485_DIR_POLARITY == 0)
.rs485_dir_polarity = false,
#else
.rs485_dir_polarity = true,
#endif
#endif
};
static struct esp32_dev_s g_uart0priv =
{
.config = &g_uart0config,
.baud = CONFIG_UART0_BAUD,
.parity = CONFIG_UART0_PARITY,
.bits = CONFIG_UART0_BITS,
.stopbits2 = CONFIG_UART0_2STOP,
#ifdef CONFIG_SERIAL_TXDMA
# ifdef CONFIG_ESP32_UART0_TXDMA
.txdma = true, /* TX DMA enabled for this UART */
# else
.txdma = false, /* TX DMA disabled for this UART */
# endif
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
# ifdef CONFIG_UART0_IFLOWCONTROL
.iflow = true, /* Input flow control (RTS) enabled */
# else
.iflow = false, /* Input flow control (RTS) disabled */
# endif
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
# ifdef CONFIG_UART0_OFLOWCONTROL
.oflow = true, /* Output flow control (CTS) enabled */
# else
.oflow = false, /* Output flow control (CTS) disabled */
# endif
#endif
};
static uart_dev_t g_uart0port =
{
.recv =
{
.size = CONFIG_UART0_RXBUFSIZE,
.buffer = g_uart0rxbuffer,
},
.xmit =
{
.size = CONFIG_UART0_TXBUFSIZE,
.buffer = g_uart0txbuffer,
},
.ops = &g_uart_ops,
.priv = &g_uart0priv,
};
#endif
/* This describes the state of the UART1 port. */
#ifdef CONFIG_ESP32_UART1
static const struct esp32_config_s g_uart1config =
{
.id = 1,
.periph = ESP32_PERIPH_UART1,
.irq = ESP32_IRQ_UART1,
.txpin = CONFIG_ESP32_UART1_TXPIN,
.rxpin = CONFIG_ESP32_UART1_RXPIN,
.txsig = U1TXD_OUT_IDX,
.rxsig = U1RXD_IN_IDX,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
.rtspin = CONFIG_ESP32_UART1_RTSPIN,
.rtssig = U1RTS_OUT_IDX,
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
.ctspin = CONFIG_ESP32_UART1_CTSPIN,
.ctssig = U1CTS_IN_IDX,
#endif
#ifdef CONFIG_SERIAL_TXDMA
# ifdef CONFIG_ESP32_UART1_TXDMA
.dma_chan = UART1_DMA,
# if UART1_DMA == 0
.dma_sem = &g_dma0_sem,
# else
.dma_sem = &g_dma1_sem,
# endif
# endif
#endif
#ifdef CONFIG_ESP32_UART1_RS485
.rs485_dir_gpio = CONFIG_ESP32_UART1_RS485_DIR_PIN,
# if (CONFIG_ESP32_UART1_RS485_DIR_POLARITY == 0)
.rs485_dir_polarity = false,
# else
.rs485_dir_polarity = true,
# endif
#endif
};
static struct esp32_dev_s g_uart1priv =
{
.config = &g_uart1config,
.baud = CONFIG_UART1_BAUD,
.parity = CONFIG_UART1_PARITY,
.bits = CONFIG_UART1_BITS,
.stopbits2 = CONFIG_UART1_2STOP,
#ifdef CONFIG_SERIAL_TXDMA
# ifdef CONFIG_ESP32_UART1_TXDMA
.txdma = true, /* TX DMA enabled for this UART */
# else
.txdma = false, /* TX DMA disabled for this UART */
# endif
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
# ifdef CONFIG_UART1_IFLOWCONTROL
.iflow = true, /* input flow control (RTS) enabled */
# else
.iflow = false, /* input flow control (RTS) disabled */
# endif
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
# ifdef CONFIG_UART1_OFLOWCONTROL
.oflow = true, /* output flow control (CTS) enabled */
# else
.oflow = false, /* output flow control (CTS) disabled */
# endif
#endif
};
static uart_dev_t g_uart1port =
{
.recv =
{
.size = CONFIG_UART1_RXBUFSIZE,
.buffer = g_uart1rxbuffer,
},
.xmit =
{
.size = CONFIG_UART1_TXBUFSIZE,
.buffer = g_uart1txbuffer,
},
.ops = &g_uart_ops,
.priv = &g_uart1priv,
};
#endif
/* This describes the state of the UART2 port. */
#ifdef CONFIG_ESP32_UART2
static const struct esp32_config_s g_uart2config =
{
.id = 2,
.periph = ESP32_PERIPH_UART2,
.irq = ESP32_IRQ_UART2,
.txpin = CONFIG_ESP32_UART2_TXPIN,
.rxpin = CONFIG_ESP32_UART2_RXPIN,
.txsig = U2TXD_OUT_IDX,
.rxsig = U2RXD_IN_IDX,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
.rtspin = CONFIG_ESP32_UART2_RTSPIN,
.rtssig = U2RTS_OUT_IDX,
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
.ctspin = CONFIG_ESP32_UART2_CTSPIN,
.ctssig = U2CTS_IN_IDX,
#endif
#ifdef CONFIG_SERIAL_TXDMA
# ifdef CONFIG_ESP32_UART2_TXDMA
.dma_chan = UART2_DMA,
# if UART2_DMA == 0
.dma_sem = &g_dma0_sem,
# else
.dma_sem = &g_dma1_sem,
# endif
# endif
#endif
#ifdef CONFIG_ESP32_UART2_RS485
.rs485_dir_gpio = CONFIG_ESP32_UART2_RS485_DIR_PIN,
# if (CONFIG_ESP32_UART2_RS485_DIR_POLARITY == 0)
.rs485_dir_polarity = false,
# else
.rs485_dir_polarity = true,
# endif
#endif
};
static struct esp32_dev_s g_uart2priv =
{
.config = &g_uart2config,
.baud = CONFIG_UART2_BAUD,
.parity = CONFIG_UART2_PARITY,
.bits = CONFIG_UART2_BITS,
.stopbits2 = CONFIG_UART2_2STOP,
#ifdef CONFIG_SERIAL_TXDMA
# ifdef CONFIG_ESP32_UART2_TXDMA
.txdma = true, /* TX DMA enabled for this UART */
# else
.txdma = false, /* TX DMA disabled for this UART */
# endif
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
# ifdef CONFIG_UART2_IFLOWCONTROL
.iflow = true, /* input flow control (RTS) enabled */
# else
.iflow = false, /* input flow control (RTS) disabled */
# endif
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
# ifdef CONFIG_UART2_OFLOWCONTROL
.oflow = true, /* output flow control (CTS) enabled */
# else
.oflow = false, /* output flow control (CTS) disabled */
# endif
#endif
};
static uart_dev_t g_uart2port =
{
.recv =
{
.size = CONFIG_UART2_RXBUFSIZE,
.buffer = g_uart2rxbuffer,
},
.xmit =
{
.size = CONFIG_UART2_TXBUFSIZE,
.buffer = g_uart2txbuffer,
},
.ops = &g_uart_ops,
.priv = &g_uart2priv,
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_SERIAL_TXDMA
/****************************************************************************
* Name: esp32_dmasend
*
* Description:
* Prepare the descriptor linked-list and initialize a transfer.
*
* Parameters:
* priv - Pointer to the serial driver struct.
*
****************************************************************************/
static void esp32_dmasend(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
if (priv->txdma)
{
struct esp32_dmadesc_s *dmadesc;
uint8_t *tp;
#ifdef CONFIG_ESP32_SPIRAM
uint8_t *alloctp = NULL;
#endif
/* If the buffer comes from PSRAM, allocate a new one from
* Internal SRAM.
*/
#ifdef CONFIG_ESP32_SPIRAM
if (esp32_ptr_extram(dev->dmatx.buffer))
{
# ifdef CONFIG_MM_KERNEL_HEAP
alloctp = kmm_malloc(dev->dmatx.length);
# elif defined(CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP)
alloctp = xtensa_imm_malloc(dev->dmatx.length);
# endif
DEBUGASSERT(alloctp != NULL);
memcpy(alloctp, dev->dmatx.buffer, dev->dmatx.length);
tp = alloctp;
}
else
#endif
{
tp = (uint8_t *)dev->dmatx.buffer;
}
/* Initialize descriptor linked-list.
* esp32_dma_init divides the buffer into the list, perform
* the required alignment and fill all descriptor words.
*/
dmadesc = s_dma_txdesc[priv->config->dma_chan];
esp32_dma_init(dmadesc, UART_DMADESC_NUM, tp,
(uint32_t) dev->dmatx.length);
/* Set the 1st descriptor address */
modifyreg32(UHCI_DMA_OUT_LINK_REG(priv->config->dma_chan),
UHCI_OUTLINK_ADDR_M,
(((uintptr_t) dmadesc) & UHCI_OUTLINK_ADDR_M));
/* Trigger DMA transfer */
modifyreg32(UHCI_DMA_OUT_LINK_REG(priv->config->dma_chan),
UHCI_OUTLINK_STOP_M, UHCI_OUTLINK_START_M);
#ifdef CONFIG_ESP32_SPIRAM
if (alloctp != NULL)
{
# ifdef CONFIG_MM_KERNEL_HEAP
kmm_free(alloctp);
# elif defined(CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP)
xtensa_imm_free(alloctp);
# endif
}
#endif
}
}
/****************************************************************************
* Name: esp32_dmatxavail
*
* Description:
* Verifies if the DMA is available for a transfer. If so, trigger a
* transfer.
*
* Parameters:
* priv - Pointer to the serial driver struct.
*
****************************************************************************/
static void esp32_dmatxavail(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
/* Check if this UART instance has DMA TX enabled */
if (priv->txdma)
{
/* Try to acquire the semaphore.
* This semaphore is always unlocked at 1st time.
* Then, the next times, it is released when a DMA transfer
* is completed.
*/
if (nxsem_trywait(priv->config->dma_sem) == OK)
{
if (priv->config->dma_chan == 0)
{
modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_UHCI0_CLK_EN);
}
else
{
modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_UHCI1_CLK_EN);
}
modifyreg32(UHCI_CONF0_REG(priv->config->dma_chan),
UHCI_UART0_CE | UHCI_UART1_CE | UHCI_UART2_CE,
1 << (UART_SELECT_SHIFT + priv->config->id));
dma_enable_int(priv->config->dma_chan);
uart_xmitchars_dma(dev);
}
}
}
#endif
#ifndef CONFIG_SUPPRESS_UART_CONFIG
/****************************************************************************
* Name: esp32_reset_rx_fifo
*
* Description:
* Resets the RX FIFO.
* NOTE: We can not use rxfifo_rst to reset the hardware RX FIFO.
*
* Parameters:
* priv - Pointer to the serial driver struct.
*
****************************************************************************/
static void esp32_reset_rx_fifo(struct esp32_dev_s *priv)
{
uint32_t rx_status_reg = getreg32(UART_STATUS_REG(priv->config->id));
uint32_t fifo_cnt = REG_MASK(rx_status_reg, UART_RXFIFO_CNT);
uint32_t mem_rx_status_reg = getreg32(UART_MEM_RX_STATUS_REG
(priv->config->id));
uint32_t rd_address = REG_MASK(mem_rx_status_reg, UART_RD_ADDRESS);
uint32_t wr_address = REG_MASK(mem_rx_status_reg, UART_WR_ADDRESS);
while ((fifo_cnt != 0) || (rd_address != wr_address))
{
getreg32(DR_UART_FIFO_REG(priv->config->id));
rx_status_reg = getreg32(UART_STATUS_REG(priv->config->id));
fifo_cnt = REG_MASK(rx_status_reg, UART_RXFIFO_CNT);
mem_rx_status_reg = getreg32(UART_MEM_RX_STATUS_REG(priv->config->id));
rd_address = REG_MASK(mem_rx_status_reg, UART_RD_ADDRESS);
wr_address = REG_MASK(mem_rx_status_reg, UART_WR_ADDRESS);
}
}
#endif
/****************************************************************************
* Name: esp32_get_rx_fifo_len
*
* Description:
* Get the real value on rx fixo.
* RX_FIFO_CNT shouldn't be used alone accordingly to:
* https://www.espressif.com/sites/default/files/documentation/eco_
* and_workarounds_for_bugs_in_esp32_en.pdf.
* So, some arithmetic with the read and write RX FIFO pointers are
* necessary.
*
* Parameters:
* priv - Pointer to the serial driver struct.
*
* Return:
* The number of bytes in RX fifo.
*
****************************************************************************/
static uint32_t esp32_get_rx_fifo_len(struct esp32_dev_s *priv)
{
uint32_t rd_address;
uint32_t wr_address;
uint32_t fifo_cnt;
uint32_t mem_rx_status_reg;
uint32_t rx_status_reg;
uint32_t len;
mem_rx_status_reg = getreg32(UART_MEM_RX_STATUS_REG(priv->config->id));
rd_address = REG_MASK(mem_rx_status_reg, UART_RD_ADDRESS);
wr_address = REG_MASK(mem_rx_status_reg, UART_WR_ADDRESS);
rx_status_reg = getreg32(UART_STATUS_REG(priv->config->id));
fifo_cnt = REG_MASK(rx_status_reg, UART_RXFIFO_CNT);
if (wr_address > rd_address)
{
len = wr_address - rd_address;
}
else if (wr_address < rd_address)
{
len = (wr_address + 128) - rd_address;
}
else
{
len = fifo_cnt > 0 ? 128 : 0;
}
return len;
}
/****************************************************************************
* Name: esp32_restoreuartint
****************************************************************************/
static inline void esp32_restoreuartint(struct esp32_dev_s *priv,
uint32_t intena)
{
/* Restore the previous interrupt state
* (assuming all interrupts disabled)
*/
putreg32(intena, UART_INT_ENA_REG(priv->config->id));
}
/****************************************************************************
* Name: esp32_disableallints
****************************************************************************/
static void esp32_disableallints(struct esp32_dev_s *priv, uint32_t *intena)
{
irqstate_t flags;
/* The following must be atomic */
flags = spin_lock_irqsave(&priv->lock);
if (intena)
{
/* Return the current interrupt mask */
*intena = getreg32(UART_INT_ENA_REG(priv->config->id));
}
/* Disable all interrupts */
putreg32(0, UART_INT_ENA_REG(priv->config->id));
spin_unlock_irqrestore(&priv->lock, flags);
}
/****************************************************************************
* Name: esp32_setup
*
* Description:
* Configure the UART baud, bits, parity, etc. This method is called the
* first time that the serial port is opened.
*
****************************************************************************/
static int esp32_setup(struct uart_dev_s *dev)
{
#ifndef CONFIG_SUPPRESS_UART_CONFIG
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
uint32_t clkdiv;
uint32_t regval;
uint32_t conf0;
/* Note: The logic here depends on the fact that that the UART module
* was enabled and the pins were configured in esp32_lowsetup().
*/
/* The shutdown method will put the UART in a known, disabled state */
esp32_shutdown(dev);
/* Set up the CONF0 register. */
conf0 = UART_TICK_REF_ALWAYS_ON | UART_ERR_WR_MASK_M;
#ifdef CONFIG_SERIAL_OFLOWCONTROL
/* Check if output flow control is enabled for this UART controller. */
if (priv->oflow)
{
/* Enable output hardware flow control */
conf0 |= UART_TX_FLOW_EN;
}
#endif
/* OR in settings for the selected number of bits */
if (priv->bits == 5)
{
/* 0=5 bits */
}
else if (priv->bits == 6)
{
conf0 |= (1 << UART_BIT_NUM_S); /* 1=6 bits */
}
else if (priv->bits == 7)
{
conf0 |= (2 << UART_BIT_NUM_S); /* 2=7 bits */
}
else /* if (priv->bits == 8) */
{
conf0 |= (3 << UART_BIT_NUM_S); /* 3=8 bits */
}
/* OR in settings for the selected parity */
if (priv->parity == 1)
{
conf0 |= UART_PARITY_EN;
}
else if (priv->parity == 2)
{
conf0 |= UART_PARITY_EN | UART_PARITY;
}
/* OR in settings for the number of stop bits */
if (priv->stopbits2)
{
conf0 |= 2 << UART_STOP_BIT_NUM_S;
}
else
{
conf0 |= 1 << UART_STOP_BIT_NUM_S;
}
/* Configure the UART BAUD */
clkdiv = (UART_CLK_FREQ << 4) / priv->baud;
regval = (clkdiv >> 4) << UART_CLKDIV_S;
regval |= (clkdiv & 15) << UART_CLKDIV_FRAG_S;
putreg32(regval, UART_CLKDIV_REG(priv->config->id));
/* Reset the RX and TX FIFO */
esp32_reset_rx_fifo(priv);
/* Configure and enable the UART */
putreg32(conf0, UART_CONF0_REG(priv->config->id));
regval = VALUE_TO_FIELD(UART_RX_FIFO_FULL_THRHD, UART_RXFIFO_FULL_THRHD) |
VALUE_TO_FIELD(UART_RX_TOUT_THRHD_VALUE, UART_RX_TOUT_THRHD) |
UART_RX_TOUT_EN;
putreg32(regval, UART_CONF1_REG(priv->config->id));
/* Enable RX and error interrupts. Clear and pending interrtupt */
regval = UART_RXFIFO_FULL_INT_ENA | UART_FRM_ERR_INT_ENA |
UART_RXFIFO_TOUT_INT_ENA;
putreg32(regval, UART_INT_ENA_REG(priv->config->id));
putreg32(UINT32_MAX, UART_INT_CLR_REG(priv->config->id));
#ifdef CONFIG_SERIAL_IFLOWCONTROL
/* Check if input flow control is enabled for this UART controller */
if (priv->iflow)
{
/* Enable input hardware flow control */
regval |= VALUE_TO_FIELD(UART_RX_FLOW_THRHD_VALUE, UART_RX_FLOW_THRHD)
| UART_RX_FLOW_EN;
modifyreg32(UART_CONF1_REG(priv->config->id), 0, regval);
}
#endif
#endif
return OK;
}
/****************************************************************************
* Name: esp32_shutdown
*
* Description:
* Disable the UART. This method is called when the serial port is
* closed. It is assumed that esp32_detach was called earlier in the
* shutdown sequence.
*
****************************************************************************/
static void esp32_shutdown(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
uint32_t status;
/* Wait for outgoing FIFO to clear. The ROM bootloader does not flush
* the FIFO before handing over to user code, so some of this output is
* not currently seen when the UART is reconfigured in early stages of
* startup.
*/
do
{
status = getreg32(UART_STATUS_REG(priv->config->id));
}
while ((status & UART_TXFIFO_CNT_M) != 0);
/* Disable all UART interrupts */
esp32_disableallints(priv, NULL);
putreg32(UINT32_MAX, UART_INT_CLR_REG(priv->config->id));
}
/****************************************************************************
* Name: esp32_attach
*
* Description:
* Configure the UART to operation in interrupt driven mode. This method
* is called when the serial port is opened. Normally, this is just after
* the the setup() method is called, however, the serial console may
* operate in a non-interrupt driven mode during the boot phase.
*
* RX and TX interrupts are not enabled when by the attach method (unless
* the hardware supports multiple levels of interrupt enabling). The RX
* and TX interrupts are not enabled until the txint() and rxint() methods
* are called.
*
****************************************************************************/
static int esp32_attach(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
int ret = OK;
/* Set up to receive peripheral interrupts on the current CPU */
priv->cpu = this_cpu();
priv->cpuint = esp32_setup_irq(priv->cpu, priv->config->periph,
1, ESP32_CPUINT_LEVEL);
if (priv->cpuint < 0)
{
/* Failed to allocate a CPU interrupt of this type */
return priv->cpuint;
}
/* Attach and enable the IRQ */
ret = irq_attach(priv->config->irq, esp32_interrupt, dev);
if (ret == OK)
{
/* Enable the CPU interrupt (RX and TX interrupts are still disabled
* in the UART
*/
up_enable_irq(priv->config->irq);
}
return ret;
}
/****************************************************************************
* Name: esp32_detach
*
* Description:
* Detach UART interrupts. This method is called when the serial port is
* closed normally just before the shutdown method is called. The
* exception is the serial console which is never shutdown.
*
****************************************************************************/
static void esp32_detach(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
/* Disable and detach the CPU interrupt */
up_disable_irq(priv->config->irq);
irq_detach(priv->config->irq);
/* Disassociate the peripheral interrupt from the CPU interrupt */
esp32_teardown_irq(priv->cpu, priv->config->periph, priv->cpuint);
priv->cpuint = -1;
}
#ifdef CONFIG_SERIAL_TXDMA
/****************************************************************************
* Name: dma_enable_int
*
* Description:
* Enable UHCI interrupt.
*
* Parameters:
* dma_chan - DMA instance.
*
****************************************************************************/
static inline void dma_enable_int(uint8_t dma_chan)
{
/* Interrupt will be triggered when all descriptors were transferred or
* in case of error with output descriptor.
*/
uint32_t int_mask = UHCI_OUT_TOTAL_EOF_INT_ENA_M |
UHCI_OUT_DSCR_ERR_INT_ENA_M;
putreg32(int_mask, UHCI_INT_ENA_REG(dma_chan));
}
/****************************************************************************
* Name: dma_disable_int.
*
* Description:
* Disable UHCI interrupt.
*
* Parameters:
* dma_chan - DMA instance.
*
****************************************************************************/
static inline void dma_disable_int(uint8_t dma_chan)
{
putreg32(0, UHCI_INT_ENA_REG(dma_chan));
}
/****************************************************************************
* Name: dma_attach
*
* Description:
* Configure an DMA interrupt, attach to a CPU interrupt and enable it.
*
* Parameters:
* dma_chan - DMA instance.
*
****************************************************************************/
static void dma_attach(uint8_t dma_chan)
{
int dma_cpuint;
int cpu;
int ret;
int periph;
int irq;
/* Clear the interrupts */
putreg32(UINT32_MAX, UHCI_INT_CLR_REG(dma_chan));
/* Attach the UHCI interrupt to the allocated CPU interrupt
* and attach and enable the IRQ.
*/
if (dma_chan == 0)
{
periph = ESP32_PERIPH_UHCI0;
irq = ESP32_IRQ_UHCI0;
}
else
{
periph = ESP32_PERIPH_UHCI1;
irq = ESP32_IRQ_UHCI1;
}
/* Set up to receive peripheral interrupts on the current CPU */
cpu = this_cpu();
dma_cpuint = esp32_setup_irq(cpu, periph, 1, ESP32_CPUINT_LEVEL);
if (dma_cpuint < 0)
{
/* Failed to allocate a CPU interrupt of this type */
dmaerr("Failed to allocate a CPU interrupt.\n");
return;
}
ret = irq_attach(irq, esp32_interrupt_dma, NULL);
if (ret == OK)
{
/* Enable the CPU interrupt */
up_enable_irq(irq);
}
else
{
dmaerr("Couldn't attach IRQ to handler.\n");
}
}
/****************************************************************************
* Name: esp32_interrupt_dma
*
* Description:
* DMA interrupt.
*
****************************************************************************/
static int esp32_interrupt_dma(int irq, void *context, void *arg)
{
uint32_t value;
uint32_t status;
uint8_t uhci = irq - ESP32_IRQ_UHCI0;
struct uart_dev_s *dev = NULL;
/* Disable interrupt, stop UHCI, save interrupt status
* clear interrupts.
*/
status = getreg32(UHCI_INT_ST_REG(uhci));
dma_disable_int(uhci);
modifyreg32(UHCI_DMA_OUT_LINK_REG(uhci),
UHCI_OUTLINK_START_M, UHCI_OUTLINK_STOP_M);
putreg32(UINT32_MAX, UHCI_INT_CLR_REG(uhci));
/* Check which UART is using DMA now and calls
* uart_xmitchars_done to adjust TX software buffer.
*/
value = getreg32(UHCI_CONF0_REG(uhci));
value = value & (UHCI_UART2_CE_M | UHCI_UART1_CE_M | UHCI_UART0_CE_M);
switch (value)
{
# ifdef CONFIG_ESP32_UART0_TXDMA
case UHCI_UART0_CE_M:
dev = &g_uart0port;
break;
# endif
# ifdef CONFIG_ESP32_UART1_TXDMA
case UHCI_UART1_CE_M:
dev = &g_uart1port;
break;
# endif
# ifdef CONFIG_ESP32_UART2_TXDMA
case UHCI_UART2_CE_M:
dev = &g_uart2port;
break;
# endif
default:
dmaerr("No UART selected\n");
}
if (dev != NULL)
{
dev->dmatx.nbytes = dev->dmatx.length;
uart_xmitchars_done(dev);
}
/* Post on semaphore to allow new transfers and share the resource.
* Disable clk gate for UHCI, so RX can work properly.
*/
if (status & UHCI_OUT_TOTAL_EOF_INT_ENA_M)
{
if (uhci == 0)
{
nxsem_post(&g_dma0_sem);
modifyreg32(DPORT_PERIP_CLK_EN_REG, DPORT_UHCI0_CLK_EN, 0);
}
# ifdef USE_DMA1
else
{
nxsem_post(&g_dma1_sem);
modifyreg32(DPORT_PERIP_CLK_EN_REG, DPORT_UHCI1_CLK_EN, 0);
}
# endif
}
else
{
dmaerr("Error with the output descriptor in DMA 0\n");
}
return OK;
}
/****************************************************************************
* Name: dma_config
*
* Description:
* Configure the UHCI peripheral.
*
* Parameters:
* dma_chan - DMA instance.
*
****************************************************************************/
static void dma_config(uint8_t dma_chan)
{
/* Enable peripheral CLK and RST module */
if (dma_chan == 0)
{
modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_UHCI0_CLK_EN);
modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_UHCI0_RST, DPORT_UHCI0_RST);
modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_UHCI0_RST, 0);
}
else
{
modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_UHCI1_CLK_EN);
modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_UHCI1_RST, DPORT_UHCI1_RST);
modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_UHCI1_RST, 0);
}
/* Configure registers */
putreg32(UHCI_CLK_EN_M, UHCI_CONF0_REG(dma_chan));
putreg32(0, UHCI_CONF1_REG(dma_chan));
putreg32(UHCI_CHECK_OWNER | (100 << UHCI_DMA_INFIFO_FULL_THRS_S),
UHCI_CONF1_REG(dma_chan));
putreg32(0, UHCI_HUNG_CONF_REG(dma_chan));
modifyreg32(UHCI_CONF0_REG(dma_chan), UHCI_IN_RST, UHCI_IN_RST);
modifyreg32(UHCI_CONF0_REG(dma_chan), UHCI_IN_RST, 0);
modifyreg32(UHCI_CONF0_REG(dma_chan), UHCI_OUT_RST, UHCI_OUT_RST);
modifyreg32(UHCI_CONF0_REG(dma_chan), UHCI_OUT_RST, 0);
}
#endif
/****************************************************************************
* Name: esp32_interrupt
*
* Description:
* This is the common UART interrupt handler. It will be invoked when an
* interrupt is received on the 'irq'. It should call uart_xmitchars or
* uart_recvchars to perform the appropriate data transfers. The
* interrupt handling logic must be able to map the 'arg' to the
* appropriate uart_dev_s structure in order to call these functions.
*
****************************************************************************/
static int esp32_interrupt(int cpuint, void *context, void *arg)
{
struct uart_dev_s *dev = (struct uart_dev_s *)arg;
struct esp32_dev_s *priv;
uint32_t regval;
uint32_t status;
uint32_t enabled;
unsigned int nfifo;
int passes;
bool handled;
DEBUGASSERT(dev != NULL && dev->priv != NULL);
priv = (struct esp32_dev_s *)dev->priv;
/* Loop until there are no characters to be transferred or, until we have
* been looping for a long time.
*/
handled = true;
for (passes = 0; passes < 256 && handled; passes++)
{
handled = false;
priv->status = getreg32(UART_INT_RAW_REG(priv->config->id));
status = getreg32(UART_STATUS_REG(priv->config->id));
enabled = getreg32(UART_INT_ENA_REG(priv->config->id));
/* Clear pending interrupts */
regval = (UART_RXFIFO_FULL_INT_CLR | UART_FRM_ERR_INT_CLR |
UART_RXFIFO_TOUT_INT_CLR | UART_TX_DONE_INT_CLR |
UART_TXFIFO_EMPTY_INT_CLR | UART_TX_BRK_IDLE_DONE_INT_CLR);
putreg32(regval, UART_INT_CLR_REG(priv->config->id));
#ifdef HAVE_RS485
if ((enabled & UART_TX_BRK_IDLE_DONE_INT_ENA) != 0 &&
(status & UART_TX_DONE_INT_ST) != 0)
{
/* If all bytes were transmitted, then we can disable the RS485
* transmit (TX/nTX) pin.
*/
nfifo = REG_MASK(status, UART_TXFIFO_CNT);
if (nfifo == 0)
{
esp32_gpiowrite(priv->config->rs485_dir_gpio,
!priv->config->rs485_dir_polarity);
}
}
#endif
/* Are Rx interrupts enabled? The upper layer may hold off Rx input
* by disabling the Rx interrupts if there is no place to saved the
* data, possibly resulting in an overrun error.
*/
if ((enabled & (UART_RXFIFO_FULL_INT_ENA |
UART_RXFIFO_TOUT_INT_ENA)) != 0)
{
/* Is there any data waiting in the Rx FIFO? */
nfifo = esp32_get_rx_fifo_len(priv);
if (nfifo > 0)
{
/* Received data in the RXFIFO! ... Process incoming bytes */
uart_recvchars(dev);
handled = true;
}
}
/* Are Tx interrupts enabled? The upper layer will disable Tx
* interrupts when it has nothing to send.
*/
if ((enabled & (UART_TX_DONE_INT_ENA | UART_TXFIFO_EMPTY_INT_ENA))
!= 0)
{
nfifo = REG_MASK(status, UART_TXFIFO_CNT);
if (nfifo < 0x7f)
{
/* The TXFIFO is not full ... process outgoing bytes */
uart_xmitchars(dev);
handled = true;
}
}
}
return OK;
}
/****************************************************************************
* Name: esp32_ioctl
*
* Description:
* All ioctl calls will be routed through this method
*
****************************************************************************/
static int esp32_ioctl(struct file *filep, int cmd, unsigned long arg)
{
#if defined(CONFIG_SERIAL_TERMIOS) || defined(CONFIG_SERIAL_TIOCSERGSTRUCT)
struct inode *inode = filep->f_inode;
struct uart_dev_s *dev = inode->i_private;
#endif
int ret = OK;
switch (cmd)
{
#ifdef CONFIG_SERIAL_TIOCSERGSTRUCT
case TIOCSERGSTRUCT:
{
struct esp32_dev_s *user = (struct esp32_dev_s *)arg;
if (!user)
{
ret = -EINVAL;
}
else
{
memcpy(user, dev, sizeof(struct esp32_dev_s));
}
}
break;
#endif
#ifdef CONFIG_SERIAL_TERMIOS
case TCGETS:
{
struct termios *termiosp = (struct termios *)arg;
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
if (!termiosp)
{
ret = -EINVAL;
break;
}
/* Return parity */
termiosp->c_cflag = ((priv->parity != 0) ? PARENB : 0) |
((priv->parity == 1) ? PARODD : 0);
/* Return stop bits */
termiosp->c_cflag |= (priv->stopbits2) ? CSTOPB : 0;
/* Return flow control */
# ifdef CONFIG_SERIAL_OFLOWCONTROL
termiosp->c_cflag |= (priv->oflow) ? CCTS_OFLOW : 0;
# endif
# ifdef CONFIG_SERIAL_IFLOWCONTROL
termiosp->c_cflag |= (priv->iflow) ? CRTS_IFLOW : 0;
# endif
/* Return baud */
cfsetispeed(termiosp, priv->baud);
/* Return number of bits */
switch (priv->bits)
{
case 5:
termiosp->c_cflag |= CS5;
break;
case 6:
termiosp->c_cflag |= CS6;
break;
case 7:
termiosp->c_cflag |= CS7;
break;
default:
case 8:
termiosp->c_cflag |= CS8;
break;
case 9:
termiosp->c_cflag |= CS8 /* CS9 */ ;
break;
}
}
break;
case TCSETS:
{
struct termios *termiosp = (struct termios *)arg;
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
uint32_t baud;
uint32_t intena;
uint8_t parity;
uint8_t nbits;
bool stop2;
# ifdef CONFIG_SERIAL_IFLOWCONTROL
bool iflow;
# endif
# ifdef CONFIG_SERIAL_OFLOWCONTROL
bool oflow;
# endif
if (!termiosp)
{
ret = -EINVAL;
break;
}
/* Decode baud. */
ret = OK;
baud = cfgetispeed(termiosp);
/* Decode number of bits */
switch (termiosp->c_cflag & CSIZE)
{
case CS5:
nbits = 5;
break;
case CS6:
nbits = 6;
break;
case CS7:
nbits = 7;
break;
case CS8:
nbits = 8;
break;
# if 0
case CS9:
nbits = 9;
break;
# endif
default:
ret = -EINVAL;
break;
}
/* Decode parity */
if ((termiosp->c_cflag & PARENB) != 0)
{
parity = (termiosp->c_cflag & PARODD) ? 1 : 2;
}
else
{
parity = 0;
}
/* Decode stop bits */
stop2 = (termiosp->c_cflag & CSTOPB) != 0;
/* Decode flow control */
# ifdef CONFIG_SERIAL_IFLOWCONTROL
iflow = (termiosp->c_cflag & CRTS_IFLOW) != 0;
# endif
# ifdef CONFIG_SERIAL_OFLOWCONTROL
oflow = (termiosp->c_cflag & CCTS_OFLOW) != 0;
# endif
/* Verify that all settings are valid before committing */
if (ret == OK)
{
/* Commit */
priv->baud = baud;
priv->parity = parity;
priv->bits = nbits;
priv->stopbits2 = stop2;
# ifdef CONFIG_SERIAL_IFLOWCONTROL
priv->iflow = iflow;
# endif
# ifdef CONFIG_SERIAL_OFLOWCONTROL
priv->oflow = oflow;
# endif
/* effect the changes immediately - note that we do not
* implement TCSADRAIN / TCSAFLUSH
*/
esp32_disableallints(priv, &intena);
ret = esp32_setup(dev);
/* Restore the interrupt state */
esp32_restoreuartint(priv, intena);
}
}
break;
#endif /* CONFIG_SERIAL_TERMIOS */
default:
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Name: esp32_receive
*
* Description:
* Called (usually) from the interrupt level to receive one
* character from the UART. Error bits associated with the
* receipt are provided in the return 'status'.
*
****************************************************************************/
static int esp32_receive(struct uart_dev_s *dev, unsigned int *status)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
uint32_t rx_fifo;
/* Return the error information in the saved status */
*status = (unsigned int)priv->status;
priv->status = 0;
/* Then return the actual received byte */
rx_fifo = getreg32(DR_UART_FIFO_REG(priv->config->id));
return (int)REG_MASK(rx_fifo, UART_RXFIFO_RD_BYTE);
}
/****************************************************************************
* Name: esp32_rxint
*
* Description:
* Call to enable or disable RXRDY interrupts
*
****************************************************************************/
static void esp32_rxint(struct uart_dev_s *dev, bool enable)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
irqstate_t flags;
int regval;
flags = spin_lock_irqsave(&priv->lock);
if (enable)
{
/* Receive an interrupt when their is anything in the Rx data register
* (or an Rx timeout occurs).
*/
#ifndef CONFIG_SUPPRESS_SERIAL_INTS
regval = getreg32(UART_INT_ENA_REG(priv->config->id));
regval |= (UART_RXFIFO_FULL_INT_ENA | UART_FRM_ERR_INT_ENA |
UART_RXFIFO_TOUT_INT_ENA);
putreg32(regval, UART_INT_ENA_REG(priv->config->id));
#endif
}
else
{
/* Disable the RX interrupts */
regval = getreg32(UART_INT_ENA_REG(priv->config->id));
regval &= ~(UART_RXFIFO_FULL_INT_ENA | UART_FRM_ERR_INT_ENA |
UART_RXFIFO_TOUT_INT_ENA);
putreg32(regval, UART_INT_ENA_REG(priv->config->id));
}
spin_unlock_irqrestore(&priv->lock, flags);
}
/****************************************************************************
* Name: esp32_rxavailable
*
* Description:
* Return true if the receive holding register is not empty
*
****************************************************************************/
static bool esp32_rxavailable(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
return esp32_get_rx_fifo_len(priv) > 0;
}
/****************************************************************************
* Name: esp32_send
*
* Description:
* This method will send one byte on the UART.
*
****************************************************************************/
static void esp32_send(struct uart_dev_s *dev, int ch)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
#ifdef HAVE_RS485
if (priv->config->rs485_dir_gpio != 0)
{
esp32_gpiowrite(priv->config->rs485_dir_gpio,
priv->config->rs485_dir_polarity);
}
#endif
putreg32((uint32_t)ch, AHB_UART_FIFO_REG(priv->config->id));
}
/****************************************************************************
* Name: esp32_txint
*
* Description:
* Call to enable or disable TX interrupts
*
****************************************************************************/
static void esp32_txint(struct uart_dev_s *dev, bool enable)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
irqstate_t flags;
#ifdef CONFIG_SERIAL_TXDMA
if (priv->txdma == false)
{
#endif
flags = spin_lock_irqsave(&priv->lock);
if (enable)
{
/* After all bytes physically transmitted in the RS485 bus
* the TX_BRK_IDLE will indicate we can disable the TX pin.
*/
#ifdef HAVE_RS485
if (priv->config->rs485_dir_gpio != 0)
{
modifyreg32(UART_INT_ENA_REG(priv->config->id),
0, UART_TX_BRK_IDLE_DONE_INT_ENA);
}
#endif
/* Set to receive an interrupt when the TX holding register
* is empty.
*/
#ifndef CONFIG_SUPPRESS_SERIAL_INTS
modifyreg32(UART_INT_ENA_REG(priv->config->id),
0, (UART_TX_DONE_INT_ENA | UART_TXFIFO_EMPTY_INT_ENA));
#else
/* Fake a TX interrupt here by just calling uart_xmitchars() with
* interrupts disabled (note this may recurse).
*/
spin_unlock_irqrestore(&priv->lock, flags);
uart_xmitchars(dev);
#endif
}
else
{
/* Disable the TX interrupt */
modifyreg32(UART_INT_ENA_REG(priv->config->id),
(UART_TX_DONE_INT_ENA | UART_TXFIFO_EMPTY_INT_ENA), 0);
}
spin_unlock_irqrestore(&priv->lock, flags);
#ifdef CONFIG_SERIAL_TXDMA
}
#endif
}
/****************************************************************************
* Name: esp32_txready
*
* Description:
* Return true if the transmit holding register is empty (TXRDY)
*
****************************************************************************/
static bool esp32_txready(struct uart_dev_s *dev)
{
uint32_t txcnt;
uint32_t reg;
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
reg = getreg32(UART_STATUS_REG(priv->config->id));
txcnt = REG_MASK(reg, UART_TXFIFO_CNT);
return (txcnt < (UART_TX_FIFO_SIZE - 1));
}
/****************************************************************************
* Name: esp32_txempty
*
* Description:
* Return true if the transmit holding and shift registers are empty
*
****************************************************************************/
static bool esp32_txempty(struct uart_dev_s *dev)
{
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
return ((getreg32(UART_STATUS_REG(priv->config->id))
& UART_TXFIFO_CNT_M) == 0);
}
#ifndef CONFIG_SUPPRESS_UART_CONFIG
/****************************************************************************
* Name: esp32_config_pins
*
* Description:
* Performs the pin configuration.
*
* Parameters:
* priv - Pointer to the serial driver struct.
*
****************************************************************************/
static void esp32_config_pins(struct esp32_dev_s *priv)
{
/* Configure UART pins
*
* Internal signals can be output to multiple GPIO pads.
* But only one GPIO pad can connect with input signal
*/
/* Keep TX pin in high level to avoid "?" trash character
* This "?" is the Unicode replacement character (U+FFFD)
*/
esp32_gpiowrite(priv->config->txpin, true);
esp32_configgpio(priv->config->txpin, OUTPUT_FUNCTION_3);
esp32_gpio_matrix_out(priv->config->txpin, priv->config->txsig, 0, 0);
esp32_configgpio(priv->config->rxpin, INPUT_FUNCTION_3);
esp32_gpio_matrix_in(priv->config->rxpin, priv->config->rxsig, 0);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
if (priv->iflow)
{
esp32_configgpio(priv->config->rtspin, OUTPUT_FUNCTION_3);
esp32_gpio_matrix_out(priv->config->rtspin, priv->config->rtssig,
0, 0);
}
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
if (priv->oflow)
{
esp32_configgpio(priv->config->ctspin, INPUT_FUNCTION_3);
esp32_gpio_matrix_in(priv->config->ctspin, priv->config->ctssig, 0);
}
#endif
#ifdef HAVE_RS485
if (priv->config->rs485_dir_gpio != 0)
{
esp32_configgpio(priv->config->rs485_dir_gpio, OUTPUT_FUNCTION_3);
esp32_gpio_matrix_out(priv->config->rs485_dir_gpio,
SIG_GPIO_OUT_IDX, 0, 0);
esp32_gpiowrite(priv->config->rs485_dir_gpio,
!priv->config->rs485_dir_polarity);
}
#endif
}
/****************************************************************************
* Name: esp32_rxflowcontrol
*
* Description:
* Called when upper half RX buffer is full (or exceeds configured
* watermark levels if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is defined).
* Return true if UART activated RX flow control to block more incoming
* data.
* NOTE: ESP32 has a hardware RX FIFO threshold mechanism to control RTS
* line and to stop receiving data. This is very similar to the concept
* behind upper watermark level. The hardware threshold is used here
* to control the RTS line. When setting the threshold to zero, RTS will
* immediately be asserted. If nbuffered = 0 or the lower watermark is
* crossed and the serial driver decides to disable RX flow control, the
* threshold will be changed to UART_RX_FLOW_THRHD_VALUE, which is almost
* half the HW RX FIFO capacity. It keeps some space to keep the data
* received after the RTS is asserted, but before the sender stops.
*
* Input Parameters:
* dev - UART device instance
* nbuffered - the number of characters currently buffered
* (if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is
* not defined the value will be 0 for an empty buffer or the
* defined buffer size for a full buffer)
* upper - true indicates the upper watermark was crossed where
* false indicates the lower watermark has been crossed
*
* Returned Value:
* true if RX flow control activated.
*
****************************************************************************/
#ifdef CONFIG_SERIAL_IFLOWCONTROL
static bool esp32_rxflowcontrol(struct uart_dev_s *dev,
unsigned int nbuffered, bool upper)
{
bool ret = false;
struct esp32_dev_s *priv = (struct esp32_dev_s *)dev->priv;
if (priv->iflow)
{
if (nbuffered == 0 || upper == false)
{
uint32_t regval;
/* Empty buffer, RTS should be de-asserted and logic in above
* layers should re-enable RX interrupt.
*/
regval = VALUE_TO_FIELD(UART_RX_FLOW_THRHD_VALUE,
UART_RX_FLOW_THRHD);
modifyreg32(UART_CONF1_REG(priv->config->id), 0, regval);
esp32_rxint(dev, true);
ret = false;
}
else
{
/* If the RX buffer is not zero and watermarks are not enabled,
* then this function is called to announce RX buffer is full.
* The first thing it should do is to immediately assert RTS.
*/
modifyreg32(UART_CONF1_REG(priv->config->id), UART_RX_FLOW_THRHD_M,
0);
/* Software RX FIFO is full, so besides asserting RTS, it's
* necessary to disable RX interrupts to prevent remaining bytes
* (that arrive after asserting RTS) to be pushed to the
* SW RX FIFO.
*/
esp32_rxint(dev, false);
ret = true;
}
}
return ret;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: esp32_lowsetup
*
* Description:
* Performs the pin configuration for all UARTs.
* This functions is intended to be called in the __start function.
*
****************************************************************************/
void esp32_lowsetup(void)
{
esp32_config_pins(TTYS0_DEV.priv);
#ifdef TTYS1_DEV
esp32_config_pins(TTYS1_DEV.priv);
#endif
#ifdef TTYS2_DEV
esp32_config_pins(TTYS2_DEV.priv);
#endif
}
#endif /* CONFIG_SUPPRESS_UART_CONFIG */
/****************************************************************************
* Name: xtensa_early_serial_initialize
*
* Description:
* Performs the low level UART initialization early in debug so that the
* serial console will be available during boot up. This must be called
* before xtensa_serialinit.
*
****************************************************************************/
#ifdef USE_EARLYSERIALINIT
void xtensa_earlyserialinit(void)
{
/* NOTE: All GPIO configuration for the UARTs was performed in
* esp32_lowsetup
*/
/* Disable all UARTS */
esp32_disableallints(TTYS0_DEV.priv, NULL);
#ifdef TTYS1_DEV
esp32_disableallints(TTYS1_DEV.priv, NULL);
#endif
#ifdef TTYS2_DEV
esp32_disableallints(TTYS2_DEV.priv, NULL);
#endif
/* Configuration whichever one is the console */
#ifdef HAVE_SERIAL_CONSOLE
CONSOLE_DEV.isconsole = true;
esp32_setup(&CONSOLE_DEV);
#endif
}
#endif
/****************************************************************************
* Name: xtensa_serial_initialize
*
* Description:
* Register serial console and serial ports. This assumes
* that xtensa_earlyserialinit was called previously.
*
****************************************************************************/
void xtensa_serialinit(void)
{
/* Register the console */
#ifdef HAVE_SERIAL_CONSOLE
uart_register("/dev/console", &CONSOLE_DEV);
#endif
/* Register all UARTs */
uart_register("/dev/ttyS0", &TTYS0_DEV);
#ifdef TTYS1_DEV
uart_register("/dev/ttyS1", &TTYS1_DEV);
#endif
#ifdef TTYS2_DEV
uart_register("/dev/ttyS2", &TTYS2_DEV);
#endif
/* DMA related */
#ifdef CONFIG_SERIAL_TXDMA
#ifdef USE_DMA0
dma_config(0);
dma_attach(0);
#endif
#ifdef USE_DMA1
dma_config(1);
dma_attach(1);
#endif
#endif
}
/****************************************************************************
* Name: up_putc
*
* Description:
* Provide priority, low-level access to support OS debug writes
*
****************************************************************************/
void up_putc(int ch)
{
#ifdef HAVE_SERIAL_CONSOLE
uint32_t intena;
esp32_disableallints(CONSOLE_DEV.priv, &intena);
while (!esp32_txready(&CONSOLE_DEV));
esp32_send(&CONSOLE_DEV, ch);
esp32_restoreuartint(CONSOLE_DEV.priv, intena);
#endif
}
#endif /* USE_SERIALDRIVER */