incubator-nuttx/drivers/serial/serial_pl011.c

850 lines
24 KiB
C

/***************************************************************************
* drivers/serial/serial_pl011.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/bits.h>
#include <nuttx/spinlock.h>
#include <nuttx/init.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/semaphore.h>
#include <nuttx/serial/serial.h>
#include <nuttx/serial/uart_pl011.h>
#ifdef CONFIG_UART_PL011
/***************************************************************************
* Pre-processor Definitions
***************************************************************************/
/* Which UART with be tty0/console and which tty1-4? The console will
* always be ttyS0. If there is no console then will use the lowest
* numbered UART.
*/
/* First pick the console and ttys0. This could be any of UART1-5 */
#if 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
#endif
#define PL011_BIT_MASK(x, y) (((2 << (x)) - 1) << (y))
/* PL011 Uart Flags Register */
#define PL011_FR_CTS BIT(0) /* clear to send - inverted */
#define PL011_FR_DSR BIT(1) /* data set ready - inverted
*/
#define PL011_FR_DCD BIT(2) /* data carrier detect -
* inverted */
#define PL011_FR_BUSY BIT(3) /* busy transmitting data */
#define PL011_FR_RXFE BIT(4) /* receive FIFO empty */
#define PL011_FR_TXFF BIT(5) /* transmit FIFO full */
#define PL011_FR_RXFF BIT(6) /* receive FIFO full */
#define PL011_FR_TXFE BIT(7) /* transmit FIFO empty */
#define PL011_FR_RI BIT(8) /* ring indicator - inverted */
/* PL011 Integer baud rate register */
#define PL011_IBRD_BAUD_DIVINT_MASK 0xff /* 16 bits of divider */
/* PL011 Fractional baud rate register */
#define PL011_FBRD_BAUD_DIVFRAC 0x3f
#define PL011_FBRD_WIDTH 6u
/* PL011 Receive status register / error clear register */
#define PL011_RSR_ECR_FE BIT(0) /* framing error */
#define PL011_RSR_ECR_PE BIT(1) /* parity error */
#define PL011_RSR_ECR_BE BIT(2) /* break error */
#define PL011_RSR_ECR_OE BIT(3) /* overrun error */
#define PL011_RSR_ERROR_MASK (PL011_RSR_ECR_FE | PL011_RSR_ECR_PE | \
PL011_RSR_ECR_BE | PL011_RSR_ECR_OE)
/* PL011 Line Control Register */
#define PL011_LCRH_BRK BIT(0) /* send break */
#define PL011_LCRH_PEN BIT(1) /* enable parity */
#define PL011_LCRH_EPS BIT(2) /* select even parity */
#define PL011_LCRH_STP2 BIT(3) /* select two stop bits */
#define PL011_LCRH_FEN BIT(4) /* enable FIFOs */
#define PL011_LCRH_WLEN_SHIFT 5 /* word length */
#define PL011_LCRH_WLEN_WIDTH 2
#define PL011_LCRH_SPS BIT(7) /* stick parity bit */
#define PL011_LCRH_WLEN_SIZE(x) ((x) - 5)
#define PL011_LCRH_FORMAT_MASK (PL011_LCRH_PEN | PL011_LCRH_EPS | \
PL011_LCRH_SPS | \
PL011_BIT_MASK(PL011_LCRH_WLEN_WIDTH, \
PL011_LCRH_WLEN_SHIFT))
#define PL011_LCRH_PARTIY_EVEN (PL011_LCRH_PEN | PL011_LCRH_EPS)
#define PL011_LCRH_PARITY_ODD (PL011_LCRH_PEN)
#define PL011_LCRH_PARITY_NONE (0)
/* PL011 Control Register */
#define PL011_CR_UARTEN BIT(0) /* enable uart operations */
#define PL011_CR_SIREN BIT(1) /* enable IrDA SIR */
#define PL011_CR_SIRLP BIT(2) /* IrDA SIR low power mode */
#define PL011_CR_LBE BIT(7) /* loop back enable */
#define PL011_CR_TXE BIT(8) /* transmit enable */
#define PL011_CR_RXE BIT(9) /* receive enable */
#define PL011_CR_DTR BIT(10) /* data transmit ready */
#define PL011_CR_RTS BIT(11) /* request to send */
#define PL011_CR_Out1 BIT(12)
#define PL011_CR_Out2 BIT(13)
#define PL011_CR_RTSEn BIT(14) /* RTS hw flow control enable
*/
#define PL011_CR_CTSEn BIT(15) /* CTS hw flow control enable
*/
/* PL011 Interrupt Fifo Level Select Register */
#define PL011_IFLS_TXIFLSEL_SHIFT 0 /* bits 2:0 */
#define PL011_IFLS_TXIFLSEL_WIDTH 3
#define PL011_IFLS_RXIFLSEL_SHIFT 3 /* bits 5:3 */
#define PL011_IFLS_RXIFLSEL_WIDTH 3
/* PL011 Interrupt Mask Set/Clear Register */
#define PL011_IMSC_RIMIM BIT(0) /* RTR modem interrupt mask */
#define PL011_IMSC_CTSMIM BIT(1) /* CTS modem interrupt mask */
#define PL011_IMSC_DCDMIM BIT(2) /* DCD modem interrupt mask */
#define PL011_IMSC_DSRMIM BIT(3) /* DSR modem interrupt mask */
#define PL011_IMSC_RXIM BIT(4) /* receive interrupt mask */
#define PL011_IMSC_TXIM BIT(5) /* transmit interrupt mask */
#define PL011_IMSC_RTIM BIT(6) /* receive timeout interrupt
* mask */
#define PL011_IMSC_FEIM BIT(7) /* framing error interrupt
* mask */
#define PL011_IMSC_PEIM BIT(8) /* parity error interrupt mask
*/
#define PL011_IMSC_BEIM BIT(9) /* break error interrupt mask
*/
#define PL011_IMSC_OEIM BIT(10) /* overrun error interrupt
* mask */
#define PL011_IMSC_ERROR_MASK (PL011_IMSC_FEIM | \
PL011_IMSC_PEIM | PL011_IMSC_BEIM | \
PL011_IMSC_OEIM)
#define PL011_IMSC_MASK_ALL (PL011_IMSC_OEIM | PL011_IMSC_BEIM | \
PL011_IMSC_PEIM | PL011_IMSC_FEIM | \
PL011_IMSC_RIMIM | \
PL011_IMSC_CTSMIM | \
PL011_IMSC_DCDMIM | \
PL011_IMSC_DSRMIM | \
PL011_IMSC_RXIM | PL011_IMSC_TXIM | \
PL011_IMSC_RTIM)
/***************************************************************************
* Private Types
***************************************************************************/
/* UART PL011 register map structure */
struct pl011_regs
{
uint32_t dr; /* data register */
union
{
uint32_t rsr;
uint32_t ecr;
};
uint32_t reserved_0[4];
uint32_t fr; /* flags register */
uint32_t reserved_1;
uint32_t ilpr;
uint32_t ibrd;
uint32_t fbrd;
uint32_t lcr_h;
uint32_t cr;
uint32_t ifls;
uint32_t imsc;
uint32_t ris;
uint32_t mis;
uint32_t icr;
uint32_t dmacr;
};
struct pl011_config
{
volatile struct pl011_regs *uart;
uint32_t sys_clk_freq;
};
/* Device data structure */
struct pl011_data
{
uint32_t baud_rate;
bool sbsa;
};
struct pl011_uart_port_s
{
struct pl011_data data;
struct pl011_config config;
unsigned int irq_num;
bool is_console;
};
/***************************************************************************
* Private Functions
***************************************************************************/
static void pl011_enable(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->cr |= PL011_CR_UARTEN;
}
static void pl011_disable(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->cr &= ~PL011_CR_UARTEN;
}
static void pl011_enable_fifo(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->lcr_h |= PL011_LCRH_FEN;
}
static void pl011_disable_fifo(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->lcr_h &= ~PL011_LCRH_FEN;
}
static int pl011_set_baudrate(const struct pl011_uart_port_s *sport,
uint32_t clk, uint32_t baudrate)
{
const struct pl011_config *config = &sport->config;
/* Avoiding float calculations, bauddiv is left shifted by 6 */
uint64_t bauddiv =
(((uint64_t)clk) << PL011_FBRD_WIDTH) / (baudrate * 16U);
/* Valid bauddiv value
* uart_clk (min) >= 16 x baud_rate (max)
* uart_clk (max) <= 16 x 65535 x baud_rate (min)
*/
if ((bauddiv < (1U << PL011_FBRD_WIDTH)) ||
(bauddiv > (65535U << PL011_FBRD_WIDTH)))
{
return -EINVAL;
}
config->uart->ibrd = bauddiv >> PL011_FBRD_WIDTH;
config->uart->fbrd = bauddiv & ((1U << PL011_FBRD_WIDTH) - 1U);
/* In order to internally update the contents of ibrd or fbrd, a
* lcr_h write must always be performed at the end
* ARM DDI 0183F, Pg 3-13
*/
config->uart->lcr_h = config->uart->lcr_h;
return 0;
}
static void pl011_irq_tx_enable(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->imsc |= PL011_IMSC_TXIM;
}
static void pl011_irq_tx_disable(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->imsc &= ~PL011_IMSC_TXIM;
}
static void pl011_irq_rx_enable(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->imsc |= PL011_IMSC_RXIM | PL011_IMSC_RTIM;
}
static void pl011_irq_rx_disable(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
config->uart->imsc &= ~(PL011_IMSC_RXIM | PL011_IMSC_RTIM);
}
static int pl011_irq_tx_complete(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
/* check for TX FIFO empty */
return config->uart->fr & PL011_FR_TXFE;
}
static int pl011_irq_rx_ready(const struct pl011_uart_port_s *sport)
{
const struct pl011_config *config = &sport->config;
const struct pl011_data *data = &sport->data;
if (!data->sbsa && !(config->uart->cr & PL011_CR_RXE))
{
return false;
}
return (config->uart->imsc & PL011_IMSC_RXIM) &&
(!(config->uart->fr & PL011_FR_RXFE));
}
/***************************************************************************
* Name: pl011_txready
*
* Description:
* Return true if the tranmsit fifo is not full
*
***************************************************************************/
static bool pl011_txready(struct uart_dev_s *dev)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
const struct pl011_config *config = &sport->config;
struct pl011_data *data = &sport->data;
if (!data->sbsa && !(config->uart->cr & PL011_CR_TXE))
{
return false;
}
return (config->uart->imsc & PL011_IMSC_TXIM) &&
pl011_irq_tx_complete(sport);
}
/***************************************************************************
* Name: pl011_txempty
*
* Description:
* Return true if the transmit fifo is empty
*
***************************************************************************/
static bool pl011_txempty(struct uart_dev_s *dev)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
return pl011_irq_tx_complete(sport);
}
/***************************************************************************
* Name: pl011_send
*
* Description:
* This method will send one byte on the UART
*
***************************************************************************/
static void pl011_send(struct uart_dev_s *dev, int ch)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
const struct pl011_config *config = &sport->config;
config->uart->dr = ch;
}
/***************************************************************************
* Name: pl011_rxavailable
*
* Description:
* Return true if the receive fifo is not empty
*
***************************************************************************/
static bool pl011_rxavailable(struct uart_dev_s *dev)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
const struct pl011_config *config = &sport->config;
struct pl011_data *data = &sport->data;
if (!data->sbsa &&
(!(config->uart->cr & PL011_CR_UARTEN) ||
!(config->uart->cr & PL011_CR_RXE)))
{
return false;
}
return (config->uart->fr & PL011_FR_RXFE) == 0U;
}
/***************************************************************************
* Name: pl011_rxint
*
* Description:
* Call to enable or disable RX interrupts
*
***************************************************************************/
static void pl011_rxint(struct uart_dev_s *dev, bool enable)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
if (enable)
{
pl011_irq_rx_enable(sport);
}
else
{
pl011_irq_rx_disable(sport);
}
}
/***************************************************************************
* Name: pl011_txint
*
* Description:
* Call to enable or disable TX interrupts
*
***************************************************************************/
static void pl011_txint(struct uart_dev_s *dev, bool enable)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
irqstate_t flags;
flags = enter_critical_section();
if (enable)
{
pl011_irq_tx_enable(sport);
/* Fake a TX interrupt here by just calling uart_xmitchars() with
* interrupts disabled (note this may recurse).
*/
uart_xmitchars(dev);
}
else
{
pl011_irq_tx_disable(sport);
}
leave_critical_section(flags);
}
/***************************************************************************
* Name: pl011_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 pl011_receive(struct uart_dev_s *dev, unsigned int *status)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
const struct pl011_config *config = &sport->config;
unsigned int rx;
rx = config->uart->dr;
*status = 0;
return rx;
}
/***************************************************************************
* Name: pl011_ioctl
*
* Description:
* All ioctl calls will be routed through this method
* for current qemu configure,
*
***************************************************************************/
static int pl011_ioctl(struct file *filep, int cmd, unsigned long arg)
{
int ret = OK;
UNUSED(filep);
UNUSED(arg);
switch (cmd)
{
case TIOCSBRK: /* BSD compatibility: Turn break on, unconditionally */
case TIOCCBRK: /* BSD compatibility: Turn break off, unconditionally */
default:
{
ret = -ENOTTY;
break;
}
}
return ret;
}
/***************************************************************************
* Name: pl011_irq_handler (and front-ends)
*
* Description:
* This is the common UART interrupt handler. It should cal
* uart_transmitchars or uart_receivechar to perform the appropriate data
* transfers.
*
***************************************************************************/
static int pl011_irq_handler(int irq, void *context, void *arg)
{
struct uart_dev_s *dev = (struct uart_dev_s *)arg;
struct pl011_uart_port_s *sport;
UNUSED(irq);
UNUSED(context);
DEBUGASSERT(dev != NULL && dev->priv != NULL);
sport = (struct pl011_uart_port_s *)dev->priv;
if (pl011_irq_rx_ready(sport))
{
uart_recvchars(dev);
}
if (pl011_txready(dev))
{
uart_xmitchars(dev);
}
return OK;
}
/***************************************************************************
* Name: pl011_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 pl011_detach(struct uart_dev_s *dev)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
up_disable_irq(sport->irq_num);
irq_detach(sport->irq_num);
}
/***************************************************************************
* Name: pl011_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 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 pl011_attach(struct uart_dev_s *dev)
{
struct pl011_uart_port_s *sport;
struct pl011_data *data;
int ret;
sport = (struct pl011_uart_port_s *)dev->priv;
data = &sport->data;
ret = irq_attach(sport->irq_num, pl011_irq_handler, dev);
if (ret == OK)
{
up_enable_irq(sport->irq_num);
}
else
{
sinfo("error ret=%d\n", ret);
}
if (!data->sbsa)
{
pl011_enable(sport);
}
return ret;
}
/***************************************************************************
* Name: pl011_shutdown
*
* Description:
* Disable the UART. This method is called when the serial
* port is closed
*
***************************************************************************/
static void pl011_shutdown(struct uart_dev_s *dev)
{
UNUSED(dev);
sinfo("%s: call unexpected\n", __func__);
}
static int pl011_setup(struct uart_dev_s *dev)
{
struct pl011_uart_port_s *sport = (struct pl011_uart_port_s *)dev->priv;
const struct pl011_config *config = &sport->config;
struct pl011_data *data = &sport->data;
int ret;
uint32_t lcrh;
irqstate_t i_flags;
i_flags = up_irq_save();
/* If working in SBSA mode, we assume that UART is already configured,
* or does not require configuration at all (if UART is emulated by
* virtualization software).
*/
if (!data->sbsa)
{
/* disable the uart */
pl011_disable(sport);
pl011_disable_fifo(sport);
/* Set baud rate */
ret = pl011_set_baudrate(sport, config->sys_clk_freq,
data->baud_rate);
if (ret != 0)
{
up_irq_restore(i_flags);
return ret;
}
/* Setting the default character format */
lcrh = config->uart->lcr_h & ~(PL011_LCRH_FORMAT_MASK);
lcrh &= ~(BIT(0) | BIT(7));
lcrh |= PL011_LCRH_WLEN_SIZE(8) << PL011_LCRH_WLEN_SHIFT;
config->uart->lcr_h = lcrh;
/* Enabling the FIFOs */
pl011_enable_fifo(sport);
}
/* initialize all IRQs as masked */
config->uart->imsc = 0U;
config->uart->icr = PL011_IMSC_MASK_ALL;
if (!data->sbsa)
{
config->uart->dmacr = 0U;
config->uart->cr &= ~(BIT(14) | BIT(15) | BIT(1));
config->uart->cr |= PL011_CR_RXE | PL011_CR_TXE;
}
up_irq_restore(i_flags);
return 0;
}
/***************************************************************************
* Private Data
***************************************************************************/
/* Serial driver UART operations */
static const struct uart_ops_s g_uart_ops =
{
.setup = pl011_setup,
.shutdown = pl011_shutdown,
.attach = pl011_attach,
.detach = pl011_detach,
.ioctl = pl011_ioctl,
.receive = pl011_receive,
.rxint = pl011_rxint,
.rxavailable = pl011_rxavailable,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
.rxflowcontrol = NULL,
#endif
.send = pl011_send,
.txint = pl011_txint,
.txready = pl011_txready,
.txempty = pl011_txempty,
};
/* This describes the state of the uart1 port. */
static struct pl011_uart_port_s g_uart1priv =
{
.data =
{
.baud_rate = CONFIG_UART1_BAUD,
.sbsa = false,
},
.config =
{
.uart = (volatile struct pl011_regs *)CONFIG_UART1_BASE,
.sys_clk_freq = 24000000,
},
.irq_num = CONFIG_UART1_IRQ,
.is_console = 1,
};
/* I/O buffers */
static char g_uart1rxbuffer[CONFIG_UART1_RXBUFSIZE];
static char g_uart1txbuffer[CONFIG_UART1_TXBUFSIZE];
static struct uart_dev_s 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,
};
/***************************************************************************
* Public Functions
***************************************************************************/
/***************************************************************************
* Name: pl011_earlyserialinit
*
* Description:
* see nuttx/serial/uart_pl011.h
*
***************************************************************************/
void pl011_earlyserialinit(void)
{
/* Enable the console UART. The other UARTs will be initialized if and
* when they are first opened.
*/
#ifdef CONSOLE_DEV
CONSOLE_DEV.isconsole = true;
pl011_setup(&CONSOLE_DEV);
#endif
}
/***************************************************************************
* Name: up_putc
*
* Description:
* Provide priority, low-level access to support OS debug
* writes
*
***************************************************************************/
int up_putc(int ch)
{
#ifdef CONSOLE_DEV
struct uart_dev_s *dev = &CONSOLE_DEV;
/* Check for LF */
if (ch == '\n')
{
/* Add CR */
pl011_send(dev, '\r');
}
pl011_send(dev, ch);
#endif
return ch;
}
/***************************************************************************
* Name: pl011_serialinit
*
* Description:
* see nuttx/serial/uart_pl011.h
*
***************************************************************************/
void pl011_serialinit(void)
{
#ifdef CONSOLE_DEV
int ret;
ret = uart_register("/dev/console", &CONSOLE_DEV);
if (ret < 0)
{
sinfo("error at register dev/console, ret =%d\n", ret);
}
ret = uart_register("/dev/ttyS0", &TTYS0_DEV);
if (ret < 0)
{
sinfo("error at register dev/ttyS0, ret =%d\n", ret);
}
#endif
}
#endif /* USE_SERIALDRIVER */