blob: f3b94c84368581eb5773088e9fdf5ebabbcbf308 [file] [log] [blame]
/* ns16550.c - NS16550D serial driver */
#define DT_DRV_COMPAT ns16550
/*
* Copyright (c) 2010, 2012-2015 Wind River Systems, Inc.
* Copyright (c) 2020-2022 Intel Corp.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief NS16550 Serial Driver
*
* This is the driver for the Intel NS16550 UART Chip used on the PC 386.
* It uses the SCCs in asynchronous mode only.
*
* Before individual UART port can be used, uart_ns16550_port_init() has to be
* called to setup the port.
*/
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/types.h>
#include <zephyr/init.h>
#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/pm/policy.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/spinlock.h>
#include <zephyr/irq.h>
#if defined(CONFIG_PINCTRL)
#include <zephyr/drivers/pinctrl.h>
#endif
#include <zephyr/drivers/serial/uart_ns16550.h>
#define INST_HAS_PCP_HELPER(inst) DT_INST_NODE_HAS_PROP(inst, pcp) ||
#define INST_HAS_DLF_HELPER(inst) DT_INST_NODE_HAS_PROP(inst, dlf) ||
#define UART_NS16550_PCP_ENABLED \
(DT_INST_FOREACH_STATUS_OKAY(INST_HAS_PCP_HELPER) 0)
#define UART_NS16550_DLF_ENABLED \
(DT_INST_FOREACH_STATUS_OKAY(INST_HAS_DLF_HELPER) 0)
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(pcie)
BUILD_ASSERT(IS_ENABLED(CONFIG_PCIE), "NS16550(s) in DT need CONFIG_PCIE");
#include <zephyr/drivers/pcie/pcie.h>
#endif
/* register definitions */
#define REG_THR 0x00 /* Transmitter holding reg. */
#define REG_RDR 0x00 /* Receiver data reg. */
#define REG_BRDL 0x00 /* Baud rate divisor (LSB) */
#define REG_BRDH 0x01 /* Baud rate divisor (MSB) */
#define REG_IER 0x01 /* Interrupt enable reg. */
#define REG_IIR 0x02 /* Interrupt ID reg. */
#define REG_FCR 0x02 /* FIFO control reg. */
#define REG_LCR 0x03 /* Line control reg. */
#define REG_MDC 0x04 /* Modem control reg. */
#define REG_LSR 0x05 /* Line status reg. */
#define REG_MSR 0x06 /* Modem status reg. */
#define REG_SCR 0x07 /* Scratchpad. */
#define REG_DLF 0xC0 /* Divisor Latch Fraction */
#define REG_PCP 0x200 /* PRV_CLOCK_PARAMS (Apollo Lake) */
/* equates for interrupt enable register */
#define IER_RXRDY 0x01 /* receiver data ready */
#define IER_TBE 0x02 /* transmit bit enable */
#define IER_LSR 0x04 /* line status interrupts */
#define IER_MSI 0x08 /* modem status interrupts */
/* equates for interrupt identification register */
#define IIR_MSTAT 0x00 /* modem status interrupt */
#define IIR_NIP 0x01 /* no interrupt pending */
#define IIR_THRE 0x02 /* transmit holding register empty interrupt */
#define IIR_RBRF 0x04 /* receiver buffer register full interrupt */
#define IIR_LS 0x06 /* receiver line status interrupt */
#define IIR_MASK 0x07 /* interrupt id bits mask */
#define IIR_ID 0x06 /* interrupt ID mask without NIP */
#define IIR_FE 0xC0 /* FIFO mode enabled */
#define IIR_CH 0x0C /* Character timeout*/
/* equates for FIFO control register */
#define FCR_FIFO 0x01 /* enable XMIT and RCVR FIFO */
#define FCR_RCVRCLR 0x02 /* clear RCVR FIFO */
#define FCR_XMITCLR 0x04 /* clear XMIT FIFO */
/* equates for Apollo Lake clock control register (PRV_CLOCK_PARAMS) */
#define PCP_UPDATE 0x80000000 /* update clock */
#define PCP_EN 0x00000001 /* enable clock output */
/*
* Per PC16550D (Literature Number: SNLS378B):
*
* RXRDY, Mode 0: When in the 16450 Mode (FCR0 = 0) or in
* the FIFO Mode (FCR0 = 1, FCR3 = 0) and there is at least 1
* character in the RCVR FIFO or RCVR holding register, the
* RXRDY pin (29) will be low active. Once it is activated the
* RXRDY pin will go inactive when there are no more charac-
* ters in the FIFO or holding register.
*
* RXRDY, Mode 1: In the FIFO Mode (FCR0 = 1) when the
* FCR3 = 1 and the trigger level or the timeout has been
* reached, the RXRDY pin will go low active. Once it is acti-
* vated it will go inactive when there are no more characters
* in the FIFO or holding register.
*
* TXRDY, Mode 0: In the 16450 Mode (FCR0 = 0) or in the
* FIFO Mode (FCR0 = 1, FCR3 = 0) and there are no charac-
* ters in the XMIT FIFO or XMIT holding register, the TXRDY
* pin (24) will be low active. Once it is activated the TXRDY
* pin will go inactive after the first character is loaded into the
* XMIT FIFO or holding register.
*
* TXRDY, Mode 1: In the FIFO Mode (FCR0 = 1) when
* FCR3 = 1 and there are no characters in the XMIT FIFO, the
* TXRDY pin will go low active. This pin will become inactive
* when the XMIT FIFO is completely full.
*/
#define FCR_MODE0 0x00 /* set receiver in mode 0 */
#define FCR_MODE1 0x08 /* set receiver in mode 1 */
/* RCVR FIFO interrupt levels: trigger interrupt with this bytes in FIFO */
#define FCR_FIFO_1 0x00 /* 1 byte in RCVR FIFO */
#define FCR_FIFO_4 0x40 /* 4 bytes in RCVR FIFO */
#define FCR_FIFO_8 0x80 /* 8 bytes in RCVR FIFO */
#define FCR_FIFO_14 0xC0 /* 14 bytes in RCVR FIFO */
/*
* UART NS16750 supports 64 bytes FIFO, which can be enabled
* via the FCR register
*/
#define FCR_FIFO_64 0x20 /* Enable 64 bytes FIFO */
/* FIFO Depth. */
#if defined(CONFIG_UART_NS16550_VARIANT_NS16750)
#define UART_FIFO_DEPTH (64)
#elif defined(CONFIG_UART_NS16550_VARIANT_NS16950)
#define UART_FIFO_DEPTH (128)
#else
#define UART_FIFO_DEPTH (16)
#endif
/* FIFO Half Depth. */
#define UART_FIFO_HALF_DEPTH (UART_FIFO_DEPTH / 2)
/* constants for line control register */
#define LCR_CS5 0x00 /* 5 bits data size */
#define LCR_CS6 0x01 /* 6 bits data size */
#define LCR_CS7 0x02 /* 7 bits data size */
#define LCR_CS8 0x03 /* 8 bits data size */
#define LCR_2_STB 0x04 /* 2 stop bits */
#define LCR_1_STB 0x00 /* 1 stop bit */
#define LCR_PEN 0x08 /* parity enable */
#define LCR_PDIS 0x00 /* parity disable */
#define LCR_EPS 0x10 /* even parity select */
#define LCR_SP 0x20 /* stick parity select */
#define LCR_SBRK 0x40 /* break control bit */
#define LCR_DLAB 0x80 /* divisor latch access enable */
/* constants for the modem control register */
#define MCR_DTR 0x01 /* dtr output */
#define MCR_RTS 0x02 /* rts output */
#define MCR_OUT1 0x04 /* output #1 */
#define MCR_OUT2 0x08 /* output #2 */
#define MCR_LOOP 0x10 /* loop back */
#define MCR_AFCE 0x20 /* auto flow control enable */
/* constants for line status register */
#define LSR_RXRDY 0x01 /* receiver data available */
#define LSR_OE 0x02 /* overrun error */
#define LSR_PE 0x04 /* parity error */
#define LSR_FE 0x08 /* framing error */
#define LSR_BI 0x10 /* break interrupt */
#define LSR_EOB_MASK 0x1E /* Error or Break mask */
#define LSR_THRE 0x20 /* transmit holding register empty */
#define LSR_TEMT 0x40 /* transmitter empty */
/* Transfer Error */
#define UART_PASS 0 /* Transfer completed */
#define UART_ERROR_CANCELED -ECANCELED /* Operation was canceled */
#define UART_DRIVER_ERROR -ENOTSUP /* Unsupported error */
/* Transfer Status */
#define UART_TRANSFER_SUCCESS 0 /* Transfer success */
#define UART_TRANSFER_FAILED -EPERM /* Transfer failed */
/* SCR bit to indicate updated status for LSR. */
#define UART_SCR_STATUS_UPDATE BIT(0)
/* constants for modem status register */
#define MSR_DCTS 0x01 /* cts change */
#define MSR_DDSR 0x02 /* dsr change */
#define MSR_DRI 0x04 /* ring change */
#define MSR_DDCD 0x08 /* data carrier change */
#define MSR_CTS 0x10 /* complement of cts */
#define MSR_DSR 0x20 /* complement of dsr */
#define MSR_RI 0x40 /* complement of ring signal */
#define MSR_DCD 0x80 /* complement of dcd */
#define THR(dev) (get_port(dev) + REG_THR * reg_interval(dev))
#define RDR(dev) (get_port(dev) + REG_RDR * reg_interval(dev))
#define BRDL(dev) (get_port(dev) + REG_BRDL * reg_interval(dev))
#define BRDH(dev) (get_port(dev) + REG_BRDH * reg_interval(dev))
#define IER(dev) (get_port(dev) + REG_IER * reg_interval(dev))
#define IIR(dev) (get_port(dev) + REG_IIR * reg_interval(dev))
#define FCR(dev) (get_port(dev) + REG_FCR * reg_interval(dev))
#define LCR(dev) (get_port(dev) + REG_LCR * reg_interval(dev))
#define MDC(dev) (get_port(dev) + REG_MDC * reg_interval(dev))
#define LSR(dev) (get_port(dev) + REG_LSR * reg_interval(dev))
#define MSR(dev) (get_port(dev) + REG_MSR * reg_interval(dev))
#define SCR(dev) (get_port(dev) + REG_SCR * reg_interval(dev))
#define DLF(dev) (get_port(dev) + REG_DLF)
#define PCP(dev) (get_port(dev) + REG_PCP)
#define IIRC(dev) (((struct uart_ns16550_dev_data *)(dev)->data)->iir_cache)
#if CONFIG_UART_ASYNC_API
/* Macro to get tx semamphore */
#define GET_TX_SEM(dev) \
(((const struct uart_ns16550_device_config *) \
dev->config)->tx_sem)
/* Macro to get rx sempahore */
#define GET_RX_SEM(dev) \
(((const struct uart_ns16550_device_config *) \
dev->config)->rx_sem)
/**
* UART asynchronous transfer structure.
*/
struct uart_ns16550_transfer_t {
uint8_t *data; /**< Pre-allocated write or read buffer. */
uint32_t data_len; /**< Number of bytes to transfer. */
/** Transfer callback
*
* @param[in] data Callback user data.
* @param[in] UART_PASS on success,negative value for possible
* errors.
* @param[in] status UART module status.To be interpreted with
* intel_uart_status_t for different error bits.
* @param[in] len Length of the UART transfer if successful, 0
* otherwise.
*/
void (*callback)(void *data, int error, uint32_t status, uint32_t len);
void *callback_data; /**< Callback identifier. */
};
#endif /* CONFIG_UART_ASYNC_API */
/* device config */
struct uart_ns16550_device_config {
union {
DEVICE_MMIO_ROM;
uint32_t port;
};
uint32_t sys_clk_freq;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API)
uart_irq_config_func_t irq_config_func;
#endif
#if UART_NS16550_PCP_ENABLED
uint32_t pcp;
#endif
uint8_t reg_interval;
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(pcie)
struct pcie_dev *pcie;
#endif
#if defined(CONFIG_PINCTRL)
const struct pinctrl_dev_config *pincfg;
#endif
#ifdef CONFIG_UART_ASYNC_API
struct k_sem *tx_sem;
struct k_sem *rx_sem;
#endif
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
bool io_map;
#endif
};
/** Device data structure */
struct uart_ns16550_dev_data {
DEVICE_MMIO_RAM;
struct uart_config uart_config;
struct k_spinlock lock;
uint8_t fifo_size;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
uint8_t iir_cache; /**< cache of IIR since it clears when read */
uart_irq_callback_user_data_t cb; /**< Callback function pointer */
void *cb_data; /**< Callback function arg */
#endif
#if UART_NS16550_DLF_ENABLED
uint8_t dlf; /**< DLF value */
#endif
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) && defined(CONFIG_PM)
bool tx_stream_on;
#endif
#ifdef CONFIG_UART_ASYNC_API
struct uart_event evt;
uart_callback_t async_cb;
void *async_user_data;
struct uart_ns16550_transfer_t *rx_transfer;
struct uart_ns16550_transfer_t *tx_transfer;
uint32_t write_pos;
uint32_t read_pos;
#endif
};
static void ns16550_outbyte(const struct uart_ns16550_device_config *cfg,
uintptr_t port, uint8_t val)
{
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
if (cfg->io_map) {
if (IS_ENABLED(CONFIG_UART_NS16550_ACCESS_WORD_ONLY)) {
sys_out32(val, port);
} else {
sys_out8(val, port);
}
} else {
#else
{
#endif
/* MMIO mapped */
if (IS_ENABLED(CONFIG_UART_NS16550_ACCESS_WORD_ONLY)) {
sys_write32(val, port);
} else {
sys_write8(val, port);
}
}
}
static uint8_t ns16550_inbyte(const struct uart_ns16550_device_config *cfg,
uintptr_t port)
{
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
if (cfg->io_map) {
if (IS_ENABLED(CONFIG_UART_NS16550_ACCESS_WORD_ONLY)) {
return sys_in32(port);
} else {
return sys_in8(port);
}
} else {
#else
{
#endif
/* MMIO mapped */
if (IS_ENABLED(CONFIG_UART_NS16550_ACCESS_WORD_ONLY)) {
return sys_read32(port);
} else {
return sys_read8(port);
}
}
return 0;
}
#if UART_NS16550_PCP_ENABLED
static void ns16550_outword(const struct uart_ns16550_device_config *cfg,
uintptr_t port, uint32_t val)
{
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
if (cfg->io_map) {
sys_out32(val, port);
} else {
#else
{
#endif
/* MMIO mapped */
sys_write32(val, port);
}
}
static uint32_t ns16550_inword(const struct uart_ns16550_device_config *cfg,
uintptr_t port)
{
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
if (cfg->io_map) {
return sys_in32(port);
}
#endif
/* MMIO mapped */
return sys_read32(port);
}
#endif
static inline uint8_t reg_interval(const struct device *dev)
{
const struct uart_ns16550_device_config *config = dev->config;
return config->reg_interval;
}
static const struct uart_driver_api uart_ns16550_driver_api;
static inline uintptr_t get_port(const struct device *dev)
{
uintptr_t port;
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
const struct uart_ns16550_device_config *config = dev->config;
if (config->io_map) {
port = config->port;
} else {
#else
{
#endif
port = DEVICE_MMIO_GET(dev);
}
return port;
}
static void set_baud_rate(const struct device *dev, uint32_t baud_rate, uint32_t pclk)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
uint32_t divisor; /* baud rate divisor */
uint8_t lcr_cache;
if ((baud_rate != 0U) && (pclk != 0U)) {
/*
* calculate baud rate divisor. a variant of
* (uint32_t)(pclk / (16.0 * baud_rate) + 0.5)
*/
divisor = ((pclk + (baud_rate << 3))
/ baud_rate) >> 4;
/* set the DLAB to access the baud rate divisor registers */
lcr_cache = ns16550_inbyte(dev_cfg, LCR(dev));
ns16550_outbyte(dev_cfg, LCR(dev), LCR_DLAB | lcr_cache);
ns16550_outbyte(dev_cfg, BRDL(dev), (unsigned char)(divisor & 0xff));
ns16550_outbyte(dev_cfg, BRDH(dev), (unsigned char)((divisor >> 8) & 0xff));
/* restore the DLAB to access the baud rate divisor registers */
ns16550_outbyte(dev_cfg, LCR(dev), lcr_cache);
dev_data->uart_config.baudrate = baud_rate;
}
}
static int uart_ns16550_configure(const struct device *dev,
const struct uart_config *cfg)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
uint8_t mdc = 0U;
uint32_t pclk = 0U;
/* temp for return value if error occurs in this locked region */
int ret = 0;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
#if defined(CONFIG_PINCTRL)
if (dev_cfg->pincfg != NULL) {
pinctrl_apply_state(dev_cfg->pincfg, PINCTRL_STATE_DEFAULT);
}
#endif
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
dev_data->iir_cache = 0U;
#endif
#if UART_NS16550_DLF_ENABLED
ns16550_outbyte(dev_cfg, DLF(dev), dev_data->dlf);
#endif
#if UART_NS16550_PCP_ENABLED
uint32_t pcp = dev_cfg->pcp;
if (pcp) {
pcp |= PCP_EN;
ns16550_outbyte(dev_cfg, PCP(dev), pcp & ~PCP_UPDATE);
ns16550_outbyte(dev_cfg, PCP(dev), pcp | PCP_UPDATE);
}
#endif
/*
* set clock frequency from clock_frequency property if valid,
* otherwise, get clock frequency from clock manager
*/
if (dev_cfg->sys_clk_freq != 0U) {
pclk = dev_cfg->sys_clk_freq;
} else {
if (!device_is_ready(dev_cfg->clock_dev)) {
ret = -EINVAL;
goto out;
}
clock_control_get_rate(dev_cfg->clock_dev, dev_cfg->clock_subsys,
&pclk);
}
set_baud_rate(dev, cfg->baudrate, pclk);
/* Local structure to hold temporary values to pass to ns16550_outbyte() */
struct uart_config uart_cfg;
switch (cfg->data_bits) {
case UART_CFG_DATA_BITS_5:
uart_cfg.data_bits = LCR_CS5;
break;
case UART_CFG_DATA_BITS_6:
uart_cfg.data_bits = LCR_CS6;
break;
case UART_CFG_DATA_BITS_7:
uart_cfg.data_bits = LCR_CS7;
break;
case UART_CFG_DATA_BITS_8:
uart_cfg.data_bits = LCR_CS8;
break;
default:
ret = -ENOTSUP;
goto out;
}
switch (cfg->stop_bits) {
case UART_CFG_STOP_BITS_1:
uart_cfg.stop_bits = LCR_1_STB;
break;
case UART_CFG_STOP_BITS_2:
uart_cfg.stop_bits = LCR_2_STB;
break;
default:
ret = -ENOTSUP;
goto out;
}
switch (cfg->parity) {
case UART_CFG_PARITY_NONE:
uart_cfg.parity = LCR_PDIS;
break;
case UART_CFG_PARITY_EVEN:
uart_cfg.parity = LCR_EPS;
break;
default:
ret = -ENOTSUP;
goto out;
}
dev_data->uart_config = *cfg;
/* data bits, stop bits, parity, clear DLAB */
ns16550_outbyte(dev_cfg, LCR(dev),
uart_cfg.data_bits | uart_cfg.stop_bits | uart_cfg.parity);
mdc = MCR_OUT2 | MCR_RTS | MCR_DTR;
#if defined(CONFIG_UART_NS16550_VARIANT_NS16750) || \
defined(CONFIG_UART_NS16550_VARIANT_NS16950)
if (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RTS_CTS) {
mdc |= MCR_AFCE;
}
#endif
ns16550_outbyte(dev_cfg, MDC(dev), mdc);
/*
* Program FIFO: enabled, mode 0 (set for compatibility with quark),
* generate the interrupt at 8th byte
* Clear TX and RX FIFO
*/
ns16550_outbyte(dev_cfg, FCR(dev),
FCR_FIFO | FCR_MODE0 | FCR_FIFO_8 | FCR_RCVRCLR | FCR_XMITCLR
#ifdef CONFIG_UART_NS16550_VARIANT_NS16750
| FCR_FIFO_64
#endif
);
if ((ns16550_inbyte(dev_cfg, IIR(dev)) & IIR_FE) == IIR_FE) {
#ifdef CONFIG_UART_NS16550_VARIANT_NS16750
dev_data->fifo_size = 64;
#elif defined(CONFIG_UART_NS16550_VARIANT_NS16950)
dev_data->fifo_size = 128;
#else
dev_data->fifo_size = 16;
#endif
} else {
dev_data->fifo_size = 1;
}
/* clear the port */
ns16550_inbyte(dev_cfg, RDR(dev));
/* disable interrupts */
ns16550_outbyte(dev_cfg, IER(dev), 0x00);
out:
k_spin_unlock(&dev_data->lock, key);
return ret;
};
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
static int uart_ns16550_config_get(const struct device *dev,
struct uart_config *cfg)
{
struct uart_ns16550_dev_data *data = dev->data;
cfg->baudrate = data->uart_config.baudrate;
cfg->parity = data->uart_config.parity;
cfg->stop_bits = data->uart_config.stop_bits;
cfg->data_bits = data->uart_config.data_bits;
cfg->flow_ctrl = data->uart_config.flow_ctrl;
return 0;
}
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
/**
* @brief Initialize individual UART port
*
* This routine is called to reset the chip in a quiescent state.
*
* @param dev UART device struct
*
* @return 0 if successful, failed otherwise
*/
static int uart_ns16550_init(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config *dev_cfg = dev->config;
int ret;
ARG_UNUSED(dev_cfg);
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(pcie)
if (dev_cfg->pcie) {
struct pcie_bar mbar;
if (dev_cfg->pcie->bdf == PCIE_BDF_NONE) {
return -EINVAL;
}
pcie_probe_mbar(dev_cfg->pcie->bdf, 0, &mbar);
pcie_set_cmd(dev_cfg->pcie->bdf, PCIE_CONF_CMDSTAT_MEM, true);
device_map(DEVICE_MMIO_RAM_PTR(dev), mbar.phys_addr, mbar.size,
K_MEM_CACHE_NONE);
} else
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(pcie) */
{
#if defined(CONFIG_UART_NS16550_ACCESS_IOPORT) || defined(CONFIG_UART_NS16550_SIMULT_ACCESS)
/* Map directly from DTS */
if (!dev_cfg->io_map) {
#else
{
#endif
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
}
}
ret = uart_ns16550_configure(dev, &data->uart_config);
if (ret != 0) {
return ret;
}
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
dev_cfg->irq_config_func(dev);
#endif
return 0;
}
/**
* @brief Poll the device for input.
*
* @param dev UART device struct
* @param c Pointer to character
*
* @return 0 if a character arrived, -1 if the input buffer if empty.
*/
static int uart_ns16550_poll_in(const struct device *dev, unsigned char *c)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int ret = -1;
k_spinlock_key_t key = k_spin_lock(&data->lock);
if ((ns16550_inbyte(dev_cfg, LSR(dev)) & LSR_RXRDY) != 0) {
/* got a character */
*c = ns16550_inbyte(dev_cfg, RDR(dev));
ret = 0;
}
k_spin_unlock(&data->lock, key);
return ret;
}
/**
* @brief Output a character in polled mode.
*
* Checks if the transmitter is empty. If empty, a character is written to
* the data register.
*
* If the hardware flow control is enabled then the handshake signal CTS has to
* be asserted in order to send a character.
*
* @param dev UART device struct
* @param c Character to send
*/
static void uart_ns16550_poll_out(const struct device *dev,
unsigned char c)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
while ((ns16550_inbyte(dev_cfg, LSR(dev)) & LSR_THRE) == 0) {
}
ns16550_outbyte(dev_cfg, THR(dev), c);
k_spin_unlock(&data->lock, key);
}
/**
* @brief Check if an error was received
*
* @param dev UART device struct
*
* @return one of UART_ERROR_OVERRUN, UART_ERROR_PARITY, UART_ERROR_FRAMING,
* UART_BREAK if an error was detected, 0 otherwise.
*/
static int uart_ns16550_err_check(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
int check = (ns16550_inbyte(dev_cfg, LSR(dev)) & LSR_EOB_MASK);
k_spin_unlock(&data->lock, key);
return check >> 1;
}
#if CONFIG_UART_INTERRUPT_DRIVEN
/**
* @brief Fill FIFO with data
*
* @param dev UART device struct
* @param tx_data Data to transmit
* @param size Number of bytes to send
*
* @return Number of bytes sent
*/
static int uart_ns16550_fifo_fill(const struct device *dev,
const uint8_t *tx_data,
int size)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int i;
k_spinlock_key_t key = k_spin_lock(&data->lock);
for (i = 0; (i < size) && (i < data->fifo_size); i++) {
ns16550_outbyte(dev_cfg, THR(dev), tx_data[i]);
}
k_spin_unlock(&data->lock, key);
return i;
}
/**
* @brief Read data from FIFO
*
* @param dev UART device struct
* @param rxData Data container
* @param size Container size
*
* @return Number of bytes read
*/
static int uart_ns16550_fifo_read(const struct device *dev, uint8_t *rx_data,
const int size)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int i;
k_spinlock_key_t key = k_spin_lock(&data->lock);
for (i = 0; (i < size) && (ns16550_inbyte(dev_cfg, LSR(dev)) & LSR_RXRDY) != 0; i++) {
rx_data[i] = ns16550_inbyte(dev_cfg, RDR(dev));
}
k_spin_unlock(&data->lock, key);
return i;
}
/**
* @brief Enable TX interrupt in IER
*
* @param dev UART device struct
*/
static void uart_ns16550_irq_tx_enable(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) && defined(CONFIG_PM)
struct uart_ns16550_dev_data *const dev_data = dev->data;
if (!dev_data->tx_stream_on) {
dev_data->tx_stream_on = true;
uint8_t num_cpu_states;
const struct pm_state_info *cpu_states;
num_cpu_states = pm_state_cpu_get_all(0U, &cpu_states);
/*
* Power state to be disabled. Some platforms have multiple
* states and need to be given a constraint set according to
* different states.
*/
for (uint8_t i = 0U; i < num_cpu_states; i++) {
pm_policy_state_lock_get(cpu_states[i].state, PM_ALL_SUBSTATES);
}
}
#endif
ns16550_outbyte(dev_cfg, IER(dev), ns16550_inbyte(dev_cfg, IER(dev)) | IER_TBE);
k_spin_unlock(&data->lock, key);
}
/**
* @brief Disable TX interrupt in IER
*
* @param dev UART device struct
*/
static void uart_ns16550_irq_tx_disable(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) & (~IER_TBE));
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) && defined(CONFIG_PM)
struct uart_ns16550_dev_data *const dev_data = dev->data;
if (dev_data->tx_stream_on) {
dev_data->tx_stream_on = false;
uint8_t num_cpu_states;
const struct pm_state_info *cpu_states;
num_cpu_states = pm_state_cpu_get_all(0U, &cpu_states);
/*
* Power state to be enabled. Some platforms have multiple
* states and need to be given a constraint release according
* to different states.
*/
for (uint8_t i = 0U; i < num_cpu_states; i++) {
pm_policy_state_lock_put(cpu_states[i].state, PM_ALL_SUBSTATES);
}
}
#endif
k_spin_unlock(&data->lock, key);
}
/**
* @brief Check if Tx IRQ has been raised
*
* @param dev UART device struct
*
* @return 1 if an IRQ is ready, 0 otherwise
*/
static int uart_ns16550_irq_tx_ready(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
k_spinlock_key_t key = k_spin_lock(&data->lock);
int ret = ((IIRC(dev) & IIR_ID) == IIR_THRE) ? 1 : 0;
k_spin_unlock(&data->lock, key);
return ret;
}
/**
* @brief Check if nothing remains to be transmitted
*
* @param dev UART device struct
*
* @return 1 if nothing remains to be transmitted, 0 otherwise
*/
static int uart_ns16550_irq_tx_complete(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
int ret = ((ns16550_inbyte(dev_cfg, LSR(dev)) & (LSR_TEMT | LSR_THRE))
== (LSR_TEMT | LSR_THRE)) ? 1 : 0;
k_spin_unlock(&data->lock, key);
return ret;
}
/**
* @brief Enable RX interrupt in IER
*
* @param dev UART device struct
*/
static void uart_ns16550_irq_rx_enable(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
ns16550_outbyte(dev_cfg, IER(dev), ns16550_inbyte(dev_cfg, IER(dev)) | IER_RXRDY);
k_spin_unlock(&data->lock, key);
}
/**
* @brief Disable RX interrupt in IER
*
* @param dev UART device struct
*/
static void uart_ns16550_irq_rx_disable(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) & (~IER_RXRDY));
k_spin_unlock(&data->lock, key);
}
/**
* @brief Check if Rx IRQ has been raised
*
* @param dev UART device struct
*
* @return 1 if an IRQ is ready, 0 otherwise
*/
static int uart_ns16550_irq_rx_ready(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
k_spinlock_key_t key = k_spin_lock(&data->lock);
int ret = ((IIRC(dev) & IIR_ID) == IIR_RBRF) ? 1 : 0;
k_spin_unlock(&data->lock, key);
return ret;
}
/**
* @brief Enable error interrupt in IER
*
* @param dev UART device struct
*/
static void uart_ns16550_irq_err_enable(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) | IER_LSR);
k_spin_unlock(&data->lock, key);
}
/**
* @brief Disable error interrupt in IER
*
* @param dev UART device struct
*
* @return 1 if an IRQ is ready, 0 otherwise
*/
static void uart_ns16550_irq_err_disable(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) & (~IER_LSR));
k_spin_unlock(&data->lock, key);
}
/**
* @brief Check if any IRQ is pending
*
* @param dev UART device struct
*
* @return 1 if an IRQ is pending, 0 otherwise
*/
static int uart_ns16550_irq_is_pending(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
k_spinlock_key_t key = k_spin_lock(&data->lock);
int ret = (!(IIRC(dev) & IIR_NIP)) ? 1 : 0;
k_spin_unlock(&data->lock, key);
return ret;
}
/**
* @brief Update cached contents of IIR
*
* @param dev UART device struct
*
* @return Always 1
*/
static int uart_ns16550_irq_update(const struct device *dev)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&data->lock);
IIRC(dev) = ns16550_inbyte(dev_cfg, IIR(dev));
k_spin_unlock(&data->lock, key);
return 1;
}
/**
* @brief Set the callback function pointer for IRQ.
*
* @param dev UART device struct
* @param cb Callback function pointer.
*/
static void uart_ns16550_irq_callback_set(const struct device *dev,
uart_irq_callback_user_data_t cb,
void *cb_data)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
dev_data->cb = cb;
dev_data->cb_data = cb_data;
k_spin_unlock(&dev_data->lock, key);
}
#ifdef CONFIG_UART_ASYNC_API
static bool is_async_write_complete(const struct device *dev)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
struct uart_ns16550_transfer_t *transfer = dev_data->tx_transfer;
return dev_data->write_pos >= transfer->data_len;
}
static bool is_async_read_complete(const struct device *dev)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
struct uart_ns16550_transfer_t *transfer = dev_data->rx_transfer;
return dev_data->read_pos >= transfer->data_len;
}
static void ns16550_write_transfer(const struct device *dev,
struct uart_ns16550_transfer_t *write_transfer)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int count;
if (!write_transfer) {
return;
}
if (is_async_write_complete(dev)) {
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) & (~IER_TBE));
/*
* At this point the FIFOs are empty, but the shift
* register still is transmitting the last 8 bits.
* So if we were to read LSR, it would say the device
* is still busy.
* Use the SCR Bit 0 to indicate an irq tx is complete.
*/
ns16550_outbyte(dev_cfg, SCR(dev), ns16550_inbyte(dev_cfg, SCR(dev))
| UART_SCR_STATUS_UPDATE);
if (write_transfer->callback) {
write_transfer->callback(write_transfer->callback_data,
UART_PASS, UART_TRANSFER_SUCCESS,
dev_data->write_pos);
write_transfer->callback = NULL;
}
return;
}
/*
* If we are starting the transfer then the TX FIFO is empty.
* In that case we set 'count' variable to UART_FIFO_DEPTH
* in order to take advantage of the whole FIFO capacity.
*/
count = (dev_data->write_pos == 0)
? UART_FIFO_DEPTH
: UART_FIFO_HALF_DEPTH;
while (count-- && !is_async_write_complete(dev)) {
ns16550_outbyte(dev_cfg, THR(dev),
write_transfer->data[dev_data->write_pos++]);
}
/*
* Change the threshold level to trigger an interrupt when the
* TX buffer is empty.
*/
if (is_async_write_complete(dev)) {
ns16550_outbyte(dev_cfg, FCR(dev), ns16550_inbyte(dev_cfg, FCR(dev)) |
(ns16550_inbyte(dev_cfg, IER(dev)) | IER_TBE));
}
}
static void ns16550_read_transfer(const struct device *dev,
struct uart_ns16550_transfer_t *read_transfer)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int lsr;
if (!read_transfer) {
return;
}
/*
* Copy data from RX FIFO to xfer buffer as long as the xfer
* has not completed and we have data in the RX FIFO.
*/
while (!is_async_read_complete(dev)) {
lsr = ns16550_inbyte(dev_cfg, LSR(dev));
/*
* A break condition may cause a line status
* interrupt to follow very closely after a
* char timeout interrupt, but reading the lsr
* effectively clears the pending interrupts so
* we issue here the callback
* instead, otherwise we would miss it.
* NOTE: Returned len is 0 for now, this might
* change in the future.
*/
if (lsr & LSR_EOB_MASK) {
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) &
~(IER_RXRDY | IER_LSR));
if (read_transfer->callback) {
read_transfer->callback(read_transfer
->callback_data,
UART_DRIVER_ERROR,
lsr & LSR_EOB_MASK, 0);
read_transfer->callback = NULL;
}
return;
}
if (lsr & LSR_RXRDY) {
read_transfer->data[dev_data->read_pos++] =
ns16550_inbyte(dev_cfg, THR(dev));
} else {
/* No more data in the RX FIFO. */
break;
}
}
if (is_async_read_complete(dev)) {
/*
* Disable both 'Receiver Data Available' and
* 'Receiver Line Status' interrupts.
*/
ns16550_outbyte(dev_cfg, IER(dev), ns16550_inbyte(dev_cfg, IER(dev)) &
~(IER_RXRDY | IER_LSR));
if (read_transfer->callback) {
read_transfer->callback(read_transfer->callback_data,
UART_PASS, UART_TRANSFER_SUCCESS,
dev_data->read_pos);
read_transfer->callback = NULL;
}
}
}
static void ns16550_line_status(const struct device *dev, uint32_t line_status,
struct uart_ns16550_transfer_t *read_transfer)
{
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
if (!line_status) {
return;
}
if (read_transfer) {
ns16550_outbyte(dev_cfg, IER(dev), ns16550_inbyte(dev_cfg, IER(dev)) &
~(IER_RXRDY | IER_LSR));
if (read_transfer->callback) {
/*
* Return the number of bytes read
* a zero as a line status error
* was detected.
*/
read_transfer->callback(read_transfer->callback_data,
UART_DRIVER_ERROR,
line_status, 0);
read_transfer->callback = NULL;
}
}
}
static void uart_ns16550_callback(const struct device *dev)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
struct uart_ns16550_transfer_t *write_transfer = dev_data->tx_transfer;
struct uart_ns16550_transfer_t *read_transfer = dev_data->rx_transfer;
uint8_t interrupt_id = ns16550_inbyte(dev_cfg, IIR(dev)) & IIR_MASK;
uint32_t line_status;
/*
* Interrupt ID priority levels (from highest to lowest):
* 1: IIR_LS
* 2: IIR_RBRF and IIR_CH
* 3: IIR_THRE
*/
switch (interrupt_id) {
/* Spurious interrupt */
case IIR_NIP:
break;
case IIR_LS:
line_status = ns16550_inbyte(dev_cfg, LSR(dev)) & LSR_EOB_MASK;
ns16550_line_status(dev, line_status, read_transfer);
break;
case IIR_CH:
case IIR_RBRF:
ns16550_read_transfer(dev, read_transfer);
break;
case IIR_THRE:
ns16550_write_transfer(dev, write_transfer);
break;
default:
/* Unhandled interrupt occurred,disable uart interrupts.
* and report error.
*/
if (read_transfer && read_transfer->callback) {
ns16550_outbyte(dev_cfg, IER(dev), ns16550_inbyte(dev_cfg, IER(dev)) &
~(IER_RXRDY | IER_LSR));
read_transfer->callback(read_transfer->callback_data,
UART_DRIVER_ERROR,
UART_TRANSFER_FAILED, 0);
read_transfer->callback = NULL;
}
if (write_transfer && write_transfer->callback) {
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) & (~IER_TBE));
write_transfer->callback(write_transfer->callback_data,
UART_DRIVER_ERROR,
UART_TRANSFER_FAILED, 0);
write_transfer->callback = NULL;
}
}
}
#endif /* CONFIG_UART_ASYNC_API */
/**
* @brief Interrupt service routine.
*
* This simply calls the callback function, if one exists.
*
* @param arg Argument to ISR.
*/
static void uart_ns16550_isr(const struct device *dev)
{
struct uart_ns16550_dev_data * const dev_data = dev->data;
if (dev_data->cb) {
dev_data->cb(dev, dev_data->cb_data);
#ifdef CONFIG_UART_ASYNC_API
} else {
uart_ns16550_callback(dev);
#endif
}
#ifdef CONFIG_UART_NS16550_WA_ISR_REENABLE_INTERRUPT
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
uint8_t cached_ier = ns16550_inbyte(dev_cfg, IER(dev));
ns16550_outbyte(dev_cfg, IER(dev), 0U);
ns16550_outbyte(dev_cfg, IER(dev), cached_ier);
#endif
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
#ifdef CONFIG_UART_NS16550_LINE_CTRL
/**
* @brief Manipulate line control for UART.
*
* @param dev UART device struct
* @param ctrl The line control to be manipulated
* @param val Value to set the line control
*
* @return 0 if successful, failed otherwise
*/
static int uart_ns16550_line_ctrl_set(const struct device *dev,
uint32_t ctrl, uint32_t val)
{
struct uart_ns16550_dev_data *data = dev->data;
const struct uart_ns16550_device_config *const dev_cfg = dev->config;
uint32_t mdc, chg, pclk = 0U;
k_spinlock_key_t key;
if (dev_cfg->sys_clk_freq != 0U) {
pclk = dev_cfg->sys_clk_freq;
} else {
if (device_is_ready(dev_cfg->clock_dev)) {
clock_control_get_rate(dev_cfg->clock_dev, dev_cfg->clock_subsys, &pclk);
}
}
switch (ctrl) {
case UART_LINE_CTRL_BAUD_RATE:
set_baud_rate(dev, val, pclk);
return 0;
case UART_LINE_CTRL_RTS:
case UART_LINE_CTRL_DTR:
key = k_spin_lock(&data->lock);
mdc = ns16550_inbyte(dev_cfg, MDC(dev));
if (ctrl == UART_LINE_CTRL_RTS) {
chg = MCR_RTS;
} else {
chg = MCR_DTR;
}
if (val) {
mdc |= chg;
} else {
mdc &= ~(chg);
}
ns16550_outbyte(dev_cfg, MDC(dev), mdc);
k_spin_unlock(&data->lock, key);
return 0;
}
return -ENOTSUP;
}
#endif /* CONFIG_UART_NS16550_LINE_CTRL */
#ifdef CONFIG_UART_NS16550_DRV_CMD
/**
* @brief Send extra command to driver
*
* @param dev UART device struct
* @param cmd Command to driver
* @param p Parameter to the command
*
* @return 0 if successful, failed otherwise
*/
static int uart_ns16550_drv_cmd(const struct device *dev, uint32_t cmd,
uint32_t p)
{
#if UART_NS16550_DLF_ENABLED
if (cmd == CMD_SET_DLF) {
struct uart_ns16550_dev_data * const dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
dev_data->dlf = p;
ns16550_outbyte(dev_cfg, DLF(dev), dev_data->dlf);
k_spin_unlock(&dev_data->lock, key);
return 0;
}
#endif
return -ENOTSUP;
}
#endif /* CONFIG_UART_NS16550_DRV_CMD */
#ifdef CONFIG_UART_ASYNC_API
static uint32_t uart_ns16550_decode_line_err(uint32_t line_err)
{
uint32_t zephyr_line_err = 0;
if (line_err & LSR_OE) {
zephyr_line_err = UART_ERROR_OVERRUN;
}
if (line_err & LSR_PE) {
zephyr_line_err = UART_ERROR_PARITY;
}
if (line_err & LSR_FE) {
zephyr_line_err = UART_ERROR_FRAMING;
}
if (line_err & LSR_BI) {
zephyr_line_err = UART_BREAK;
}
return zephyr_line_err;
}
static void uart_ns16550_irq_tx_common_cb(void *data, int err, uint32_t status,
uint32_t len)
{
const struct device *dev = data;
struct uart_ns16550_dev_data *dev_data = dev->data;
if (dev_data->async_cb) {
dev_data->async_user_data = (void *)(uintptr_t)
uart_ns16550_decode_line_err(status);
dev_data->async_cb(dev, &dev_data->evt, dev_data->async_user_data);
}
k_sem_give(GET_TX_SEM(dev));
}
static void uart_ns16550_irq_rx_common_cb(void *data, int err, uint32_t status,
uint32_t len)
{
const struct device *dev = data;
struct uart_ns16550_dev_data *dev_data = dev->data;
if (dev_data->async_cb) {
dev_data->async_user_data = (void *)(uintptr_t)
uart_ns16550_decode_line_err(status);
dev_data->async_cb(dev, &dev_data->evt, dev_data->async_user_data);
}
k_sem_give(GET_RX_SEM(dev));
}
static int uart_ns16550_async_callback_set(const struct device *dev,
uart_callback_t cb,
void *user_data)
{
struct uart_ns16550_dev_data *dev_data = dev->data;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
dev_data->async_cb = cb;
dev_data->async_user_data = user_data;
k_spin_unlock(&dev_data->lock, key);
return 0;
}
static int uart_ns16550_write_buffer_async(const struct device *dev,
const uint8_t *tx_buf,
size_t tx_buf_size, int32_t timeout)
{
struct uart_ns16550_dev_data *dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int ret;
__ASSERT(tx_buf != NULL, "");
__ASSERT(tx_buf_size != 0, "");
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
if (k_sem_take(GET_TX_SEM(dev), K_NO_WAIT)) {
ret = -EBUSY;
goto out;
}
if (dev_data->async_cb) {
dev_data->evt.type = UART_TX_DONE;
}
dev_data->tx_transfer->data = (uint8_t *)tx_buf;
dev_data->tx_transfer->data_len = tx_buf_size;
dev_data->tx_transfer->callback = uart_ns16550_irq_tx_common_cb;
dev_data->tx_transfer->callback_data = (void *)dev;
dev_data->write_pos = 0;
/* Set threshold. */
ns16550_outbyte(dev_cfg, FCR(dev), (FCR_FIFO | FCR_FIFO_8));
/* Enable TX holding reg empty interrupt. */
ns16550_outbyte(dev_cfg, IER(dev), (ns16550_inbyte(dev_cfg, IER(dev)) | IER_TBE));
ret = 0;
out:
k_spin_unlock(&dev_data->lock, key);
return ret;
}
static int uart_ns16550_write_abort_async(const struct device *dev)
{
struct uart_ns16550_dev_data *dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
struct uart_ns16550_transfer_t *transfer = dev_data->tx_transfer;
int ret;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
if (dev_data->async_cb) {
dev_data->evt.type = UART_TX_ABORTED;
}
/* No ongoing write transaction to be terminated. */
if (transfer == NULL) {
ret = -EIO;
goto out;
}
/* Disable TX holding reg empty interrupt. */
ns16550_outbyte(dev_cfg, IER(dev), ns16550_inbyte(dev_cfg, IER(dev)) & (~IER_TBE));
if (transfer) {
if (transfer->callback) {
transfer->callback(transfer->callback_data,
UART_ERROR_CANCELED,
UART_TRANSFER_FAILED,
dev_data->write_pos);
}
dev_data->tx_transfer->callback = NULL;
dev_data->write_pos = 0;
}
ret = 0;
out:
k_spin_unlock(&dev_data->lock, key);
return ret;
}
static int uart_ns16550_read_buffer_async(const struct device *dev,
uint8_t *rx_buf,
size_t rx_buf_size,
int32_t timeout)
{
__ASSERT(rx_buf != NULL, "");
__ASSERT(rx_buf_size != 0, "");
struct uart_ns16550_dev_data *dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
int ret;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
if (k_sem_take(GET_RX_SEM(dev), K_NO_WAIT)) {
ret = -EBUSY;
goto out;
}
if (dev_data->async_cb) {
dev_data->evt.type = UART_RX_RDY;
}
dev_data->rx_transfer->data = rx_buf;
dev_data->rx_transfer->data_len = rx_buf_size;
dev_data->rx_transfer->callback = uart_ns16550_irq_rx_common_cb;
dev_data->rx_transfer->callback_data = (void *)dev;
dev_data->read_pos = 0;
/* Set threshold. */
ns16550_outbyte(dev_cfg, FCR(dev), (FCR_FIFO | FCR_FIFO_8));
/*
* Enable both 'Receiver Data Available' and 'Receiver
* Line Status' interrupts.
*/
ns16550_outbyte(dev_cfg, IER(dev),
(ns16550_inbyte(dev_cfg, IER(dev)) | IER_RXRDY | IER_LSR));
ret = 0;
out:
k_spin_unlock(&dev_data->lock, key);
return ret;
}
static int uart_ns16550_read_disable_async(const struct device *dev)
{
struct uart_ns16550_dev_data *dev_data = dev->data;
const struct uart_ns16550_device_config * const dev_cfg = dev->config;
struct uart_ns16550_transfer_t *transfer = dev_data->rx_transfer;
int ret;
k_spinlock_key_t key = k_spin_lock(&dev_data->lock);
if (dev_data->async_cb) {
dev_data->evt.type = UART_RX_DISABLED;
}
/* No ongoing read transaction to be terminated. */
if (transfer == NULL) {
ret = -EIO;
goto out;
}
/*
* Disable both 'Receiver Data Available' and 'Receiver Line Status'
* interrupts.
*/
ns16550_outbyte(dev_cfg, IER(dev),
ns16550_inbyte(dev_cfg, IER(dev)) & ~(IER_RXRDY | IER_LSR));
if (transfer) {
if (transfer->callback) {
transfer->callback(transfer->callback_data,
UART_ERROR_CANCELED,
UART_TRANSFER_FAILED,
dev_data->read_pos);
transfer->callback = NULL;
}
dev_data->read_pos = 0;
}
ret = 0;
out:
k_spin_unlock(&dev_data->lock, key);
return ret;
}
static int uart_ns16550_read_buf_rsp(const struct device *dev, uint8_t *buf,
size_t len)
{
ARG_UNUSED(dev);
ARG_UNUSED(buf);
ARG_UNUSED(len);
return -ENOTSUP;
}
#endif /* CONFIG_UART_ASYNC_API */
static const struct uart_driver_api uart_ns16550_driver_api = {
.poll_in = uart_ns16550_poll_in,
.poll_out = uart_ns16550_poll_out,
.err_check = uart_ns16550_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
.configure = uart_ns16550_configure,
.config_get = uart_ns16550_config_get,
#endif
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
.fifo_fill = uart_ns16550_fifo_fill,
.fifo_read = uart_ns16550_fifo_read,
.irq_tx_enable = uart_ns16550_irq_tx_enable,
.irq_tx_disable = uart_ns16550_irq_tx_disable,
.irq_tx_ready = uart_ns16550_irq_tx_ready,
.irq_tx_complete = uart_ns16550_irq_tx_complete,
.irq_rx_enable = uart_ns16550_irq_rx_enable,
.irq_rx_disable = uart_ns16550_irq_rx_disable,
.irq_rx_ready = uart_ns16550_irq_rx_ready,
.irq_err_enable = uart_ns16550_irq_err_enable,
.irq_err_disable = uart_ns16550_irq_err_disable,
.irq_is_pending = uart_ns16550_irq_is_pending,
.irq_update = uart_ns16550_irq_update,
.irq_callback_set = uart_ns16550_irq_callback_set,
#endif
#ifdef CONFIG_UART_ASYNC_API
.callback_set = uart_ns16550_async_callback_set,
.tx = uart_ns16550_write_buffer_async,
.tx_abort = uart_ns16550_write_abort_async,
.rx_enable = uart_ns16550_read_buffer_async,
.rx_disable = uart_ns16550_read_disable_async,
.rx_buf_rsp = uart_ns16550_read_buf_rsp,
#endif
#ifdef CONFIG_UART_NS16550_LINE_CTRL
.line_ctrl_set = uart_ns16550_line_ctrl_set,
#endif
#ifdef CONFIG_UART_NS16550_DRV_CMD
.drv_cmd = uart_ns16550_drv_cmd,
#endif
};
#define UART_NS16550_IRQ_FLAGS_SENSE0(n) 0
#define UART_NS16550_IRQ_FLAGS_SENSE1(n) DT_INST_IRQ(n, sense)
#define UART_NS16550_IRQ_FLAGS(n) \
_CONCAT(UART_NS16550_IRQ_FLAGS_SENSE, DT_INST_IRQ_HAS_CELL(n, sense))(n)
/* not PCI(e) */
#define UART_NS16550_IRQ_CONFIG_PCIE0(n) \
static void irq_config_func##n(const struct device *dev) \
{ \
ARG_UNUSED(dev); \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
uart_ns16550_isr, DEVICE_DT_INST_GET(n), \
UART_NS16550_IRQ_FLAGS(n)); \
irq_enable(DT_INST_IRQN(n)); \
}
/* PCI(e) with auto IRQ detection */
#define UART_NS16550_IRQ_CONFIG_PCIE1(n) \
static void irq_config_func##n(const struct device *dev) \
{ \
BUILD_ASSERT(DT_INST_IRQN(n) == PCIE_IRQ_DETECT, \
"Only runtime IRQ configuration is supported"); \
BUILD_ASSERT(IS_ENABLED(CONFIG_DYNAMIC_INTERRUPTS), \
"NS16550 PCIe requires dynamic interrupts"); \
const struct uart_ns16550_device_config *dev_cfg = dev->config;\
unsigned int irq = pcie_alloc_irq(dev_cfg->pcie->bdf); \
if (irq == PCIE_CONF_INTR_IRQ_NONE) { \
return; \
} \
pcie_connect_dynamic_irq(dev_cfg->pcie->bdf, irq, \
DT_INST_IRQ(n, priority), \
(void (*)(const void *))uart_ns16550_isr, \
DEVICE_DT_INST_GET(n), \
UART_NS16550_IRQ_FLAGS(n)); \
pcie_irq_enable(dev_cfg->pcie->bdf, irq); \
}
#ifdef CONFIG_UART_NS16550_ACCESS_IOPORT
#define REG_INIT(n) \
.port = DT_INST_REG_ADDR(n), \
.io_map = true,
#else
#define REG_INIT_PCIE1(n)
#define REG_INIT_PCIE0(n) DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)),
#define DEV_REG_PORT_IO_1(n) \
.port = DT_INST_REG_ADDR(n),
#define DEV_REG_PORT_IO_0(n) \
_CONCAT(REG_INIT_PCIE, DT_INST_ON_BUS(n, pcie))(n)
#ifdef CONFIG_UART_NS16550_SIMULT_ACCESS
#define DEV_IO_INIT(n) \
.io_map = DT_INST_PROP(n, io_mapped),
#else
#define DEV_IO_INIT(n)
#endif
#define REG_INIT(n) \
_CONCAT(DEV_REG_PORT_IO_, DT_INST_PROP(n, io_mapped))(n) \
DEV_IO_INIT(n)
#endif
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
#define DEV_CONFIG_IRQ_FUNC_INIT(n) \
.irq_config_func = irq_config_func##n,
#define UART_NS16550_IRQ_FUNC_DECLARE(n) \
static void irq_config_func##n(const struct device *dev);
#define UART_NS16550_IRQ_FUNC_DEFINE(n) \
_CONCAT(UART_NS16550_IRQ_CONFIG_PCIE, DT_INST_ON_BUS(n, pcie))(n)
#else
/* !CONFIG_UART_INTERRUPT_DRIVEN */
#define DEV_CONFIG_IRQ_FUNC_INIT(n)
#define UART_NS16550_IRQ_FUNC_DECLARE(n)
#define UART_NS16550_IRQ_FUNC_DEFINE(n)
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
#if UART_NS16550_PCP_ENABLED
#define DEV_CONFIG_PCP_INIT(n) .pcp = DT_INST_PROP_OR(n, pcp, 0),
#else
#define DEV_CONFIG_PCP_INIT(n)
#endif
#define DEV_CONFIG_PCIE0(n)
#define DEV_CONFIG_PCIE1(n) DEVICE_PCIE_INST_INIT(n, pcie)
#define DEV_CONFIG_PCIE_INIT(n) \
_CONCAT(DEV_CONFIG_PCIE, DT_INST_ON_BUS(n, pcie))(n)
#define DEV_DECLARE_PCIE0(n)
#define DEV_DECLARE_PCIE1(n) DEVICE_PCIE_INST_DECLARE(n)
#define DEV_PCIE_DECLARE(n) \
_CONCAT(DEV_DECLARE_PCIE, DT_INST_ON_BUS(n, pcie))(n)
#define DEV_DATA_FLOW_CTRL0 UART_CFG_FLOW_CTRL_NONE
#define DEV_DATA_FLOW_CTRL1 UART_CFG_FLOW_CTRL_RTS_CTS
#define DEV_DATA_FLOW_CTRL(n) \
_CONCAT(DEV_DATA_FLOW_CTRL, DT_INST_PROP_OR(n, hw_flow_control, 0))
#define DEV_DATA_DLF0(n)
#define DEV_DATA_DLF1(n) \
.dlf = DT_INST_PROP(n, dlf),
#define DEV_DATA_DLF_INIT(n) \
_CONCAT(DEV_DATA_DLF, DT_INST_NODE_HAS_PROP(n, dlf))(n)
#ifdef CONFIG_UART_ASYNC_API
#define DEV_ASYNC_DECLARE(n) \
static K_SEM_DEFINE(uart_##n##_tx_sem, 1, 1); \
static K_SEM_DEFINE(uart_##n##_rx_sem, 1, 1); \
static struct uart_ns16550_transfer_t tx_##n; \
static struct uart_ns16550_transfer_t rx_##n;
#else
#define DEV_ASYNC_DECLARE(n)
#endif
#ifdef CONFIG_UART_ASYNC_API
#define DEV_CONFIG_ASYNC_INIT(n) \
.tx_sem = &uart_##n##_tx_sem, \
.rx_sem = &uart_##n##_rx_sem,
#else
#define DEV_CONFIG_ASYNC_INIT(n)
#endif
#ifdef CONFIG_UART_ASYNC_API
#define DEV_DATA_ASYNC_INIT(n) \
.tx_transfer = &tx_##n, \
.rx_transfer = &rx_##n,
#else
#define DEV_DATA_ASYNC_INIT(n)
#endif
#define UART_NS16550_DEVICE_INIT(n) \
UART_NS16550_IRQ_FUNC_DECLARE(n); \
DEV_PCIE_DECLARE(n); \
DEV_ASYNC_DECLARE(n); \
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, pinctrl_0), \
(PINCTRL_DT_INST_DEFINE(n))); \
static const struct uart_ns16550_device_config uart_ns16550_dev_cfg_##n = { \
REG_INIT(n) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, clock_frequency), ( \
.sys_clk_freq = DT_INST_PROP(n, clock_frequency), \
.clock_dev = NULL, \
.clock_subsys = NULL, \
), ( \
.sys_clk_freq = 0, \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
.clock_subsys = (clock_control_subsys_t) DT_INST_PHA(\
0, clocks, clkid), \
) \
) \
DEV_CONFIG_IRQ_FUNC_INIT(n) \
DEV_CONFIG_PCP_INIT(n) \
.reg_interval = (1 << DT_INST_PROP(n, reg_shift)), \
DEV_CONFIG_ASYNC_INIT(n) \
DEV_CONFIG_PCIE_INIT(n) \
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, pinctrl_0), \
(.pincfg = PINCTRL_DT_DEV_CONFIG_GET(DT_DRV_INST(n)),)) \
}; \
static struct uart_ns16550_dev_data uart_ns16550_dev_data_##n = { \
.uart_config.baudrate = DT_INST_PROP_OR(n, current_speed, 0), \
.uart_config.parity = UART_CFG_PARITY_NONE, \
.uart_config.stop_bits = UART_CFG_STOP_BITS_1, \
.uart_config.data_bits = UART_CFG_DATA_BITS_8, \
.uart_config.flow_ctrl = DEV_DATA_FLOW_CTRL(n), \
DEV_DATA_DLF_INIT(n) \
DEV_DATA_ASYNC_INIT(n) \
}; \
DEVICE_DT_INST_DEFINE(n, &uart_ns16550_init, NULL, \
&uart_ns16550_dev_data_##n, &uart_ns16550_dev_cfg_##n, \
PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \
&uart_ns16550_driver_api); \
UART_NS16550_IRQ_FUNC_DEFINE(n)
DT_INST_FOREACH_STATUS_OKAY(UART_NS16550_DEVICE_INIT)