zephyr/drivers/serial/uart_apbuart.c

565 lines
15 KiB
C

/*
* Copyright (c) 2019-2020 Cobham Gaisler AB
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT gaisler_apbuart
#include <zephyr/drivers/uart.h>
#include <zephyr/irq.h>
#include <zephyr/sys/time_units.h>
#include <errno.h>
/* APBUART registers
*
* Offset | Name | Description
* ------ | ------ | ----------------------------------------
* 0x0000 | data | UART data register
* 0x0004 | status | UART status register
* 0x0008 | ctrl | UART control register
* 0x000c | scaler | UART scaler register
* 0x0010 | debug | UART FIFO debug register
*/
struct apbuart_regs {
/** @brief UART data register
*
* Bit | Name | Description
* ------ | ------ | ----------------------------------------
* 7-0 | data | Holding register or FIFO
*/
uint32_t data; /* 0x0000 */
/** @brief UART status register
*
* Bit | Name | Description
* ------ | ------ | ----------------------------------------
* 31-26 | RCNT | Receiver FIFO count
* 25-20 | TCNT | Transmitter FIFO count
* 10 | RF | Receiver FIFO full
* 9 | TF | Transmitter FIFO full
* 8 | RH | Receiver FIFO half-full
* 7 | TH | Transmitter FIFO half-full
* 6 | FE | Framing error
* 5 | PE | Parity error
* 4 | OV | Overrun
* 3 | BR | Break received
* 2 | TE | Transmitter FIFO empty
* 1 | TS | Transmitter shift register empty
* 0 | DR | Data ready
*/
uint32_t status; /* 0x0004 */
/** @brief UART control register
*
* Bit | Name | Description
* ------ | ------ | ----------------------------------------
* 31 | FA | FIFOs available
* 14 | SI | Transmitter shift register empty interrupt enable
* 13 | DI | Delayed interrupt enable
* 12 | BI | Break interrupt enable
* 11 | DB | FIFO debug mode enable
* 10 | RF | Receiver FIFO interrupt enable
* 9 | TF | Transmitter FIFO interrupt enable
* 8 | EC | External clock
* 7 | LB | Loop back
* 6 | FL | Flow control
* 5 | PE | Parity enable
* 4 | PS | Parity select
* 3 | TI | Transmitter interrupt enable
* 2 | RI | Receiver interrupt enable
* 1 | TE | Transmitter enable
* 0 | RE | Receiver enable
*/
uint32_t ctrl; /* 0x0008 */
/** @brief UART scaler register
*
* Bit | Name | Description
* ------ | ------ | ----------------------------------------
* 11-0 | RELOAD | Scaler reload value
*/
uint32_t scaler; /* 0x000c */
/** @brief UART FIFO debug register
*
* Bit | Name | Description
* ------ | ------ | ----------------------------------------
* 7-0 | data | Holding register or FIFO
*/
uint32_t debug; /* 0x0010 */
};
/* APBUART register bits. */
/* Control register */
#define APBUART_CTRL_FA (1 << 31)
#define APBUART_CTRL_DB (1 << 11)
#define APBUART_CTRL_RF (1 << 10)
#define APBUART_CTRL_TF (1 << 9)
#define APBUART_CTRL_LB (1 << 7)
#define APBUART_CTRL_FL (1 << 6)
#define APBUART_CTRL_PE (1 << 5)
#define APBUART_CTRL_PS (1 << 4)
#define APBUART_CTRL_TI (1 << 3)
#define APBUART_CTRL_RI (1 << 2)
#define APBUART_CTRL_TE (1 << 1)
#define APBUART_CTRL_RE (1 << 0)
/* Status register */
#define APBUART_STATUS_RF (1 << 10)
#define APBUART_STATUS_TF (1 << 9)
#define APBUART_STATUS_RH (1 << 8)
#define APBUART_STATUS_TH (1 << 7)
#define APBUART_STATUS_FE (1 << 6)
#define APBUART_STATUS_PE (1 << 5)
#define APBUART_STATUS_OV (1 << 4)
#define APBUART_STATUS_BR (1 << 3)
#define APBUART_STATUS_TE (1 << 2)
#define APBUART_STATUS_TS (1 << 1)
#define APBUART_STATUS_DR (1 << 0)
/* For APBUART implemented without FIFO */
#define APBUART_STATUS_HOLD_REGISTER_EMPTY (1 << 2)
struct apbuart_dev_cfg {
struct apbuart_regs *regs;
int interrupt;
};
struct apbuart_dev_data {
int usefifo;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
uart_irq_callback_user_data_t cb;
void *cb_data;
#endif
};
/*
* This routine waits for the TX holding register or TX FIFO to be ready and
* then it writes a character to the data register.
*/
static void apbuart_poll_out(const struct device *dev, unsigned char x)
{
const struct apbuart_dev_cfg *config = dev->config;
struct apbuart_dev_data *data = dev->data;
volatile struct apbuart_regs *regs = (void *) config->regs;
if (data->usefifo) {
/* Transmitter FIFO full flag is available. */
while (regs->status & APBUART_STATUS_TF) {
;
}
} else {
/*
* Transmitter "hold register empty" AKA "FIFO empty" flag is
* available.
*/
while (!(regs->status & APBUART_STATUS_HOLD_REGISTER_EMPTY)) {
;
}
}
regs->data = x & 0xff;
}
static int apbuart_poll_in(const struct device *dev, unsigned char *c)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
if ((regs->status & APBUART_STATUS_DR) == 0) {
return -1;
}
*c = regs->data & 0xff;
return 0;
}
static int apbuart_err_check(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
const uint32_t status = regs->status;
int err = 0;
if (status & APBUART_STATUS_FE) {
err |= UART_ERROR_FRAMING;
}
if (status & APBUART_STATUS_PE) {
err |= UART_ERROR_PARITY;
}
if (status & APBUART_STATUS_OV) {
err |= UART_ERROR_OVERRUN;
}
if (status & APBUART_STATUS_BR) {
err |= UART_BREAK;
}
return err;
}
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
static int get_baud(volatile struct apbuart_regs *const regs)
{
unsigned int core_clk_hz;
unsigned int scaler;
scaler = regs->scaler;
core_clk_hz = sys_clock_hw_cycles_per_sec();
/* Calculate baud rate from generator "scaler" number */
return core_clk_hz / ((scaler + 1) * 8);
}
static void set_baud(volatile struct apbuart_regs *const regs, uint32_t baud)
{
unsigned int core_clk_hz;
unsigned int scaler;
if (baud == 0) {
return;
}
core_clk_hz = sys_clock_hw_cycles_per_sec();
/* Calculate Baud rate generator "scaler" number */
scaler = (core_clk_hz / (baud * 8)) - 1;
/* Set new baud rate by setting scaler */
regs->scaler = scaler;
}
static int apbuart_configure(const struct device *dev,
const struct uart_config *cfg)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
uint32_t ctrl = 0;
uint32_t newctrl = 0;
switch (cfg->parity) {
case UART_CFG_PARITY_NONE:
break;
case UART_CFG_PARITY_EVEN:
newctrl |= APBUART_CTRL_PE;
break;
case UART_CFG_PARITY_ODD:
newctrl |= APBUART_CTRL_PE | APBUART_CTRL_PS;
break;
default:
return -ENOTSUP;
}
if (cfg->stop_bits != UART_CFG_STOP_BITS_1) {
return -ENOTSUP;
}
if (cfg->data_bits != UART_CFG_DATA_BITS_8) {
return -ENOTSUP;
}
switch (cfg->flow_ctrl) {
case UART_CFG_FLOW_CTRL_NONE:
break;
case UART_CFG_FLOW_CTRL_RTS_CTS:
newctrl |= APBUART_CTRL_FL;
break;
default:
return -ENOTSUP;
}
set_baud(regs, cfg->baudrate);
ctrl = regs->ctrl;
ctrl &= ~(APBUART_CTRL_PE | APBUART_CTRL_PS | APBUART_CTRL_FL);
regs->ctrl = ctrl | newctrl;
return 0;
}
static int apbuart_config_get(const struct device *dev, struct uart_config *cfg)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
const uint32_t ctrl = regs->ctrl;
cfg->parity = UART_CFG_PARITY_NONE;
if (ctrl & APBUART_CTRL_PE) {
if (ctrl & APBUART_CTRL_PS) {
cfg->parity = UART_CFG_PARITY_ODD;
} else {
cfg->parity = UART_CFG_PARITY_EVEN;
}
}
cfg->flow_ctrl = UART_CFG_FLOW_CTRL_NONE;
if (ctrl & APBUART_CTRL_FL) {
cfg->flow_ctrl = UART_CFG_FLOW_CTRL_RTS_CTS;
}
cfg->baudrate = get_baud(regs);
cfg->data_bits = UART_CFG_DATA_BITS_8;
cfg->stop_bits = UART_CFG_STOP_BITS_1;
return 0;
}
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static void apbuart_isr(const struct device *dev);
static int apbuart_fifo_fill(const struct device *dev, const uint8_t *tx_data,
int size)
{
const struct apbuart_dev_cfg *config = dev->config;
struct apbuart_dev_data *data = dev->data;
volatile struct apbuart_regs *regs = (void *) config->regs;
int i;
if (data->usefifo) {
/* Transmitter FIFO full flag is available. */
for (
i = 0;
(i < size) && !(regs->status & APBUART_STATUS_TF);
i++
) {
regs->data = tx_data[i];
}
return i;
}
for (i = 0; (i < size) && (regs->status & APBUART_STATUS_TE); i++) {
regs->data = tx_data[i];
}
return i;
}
static int apbuart_fifo_read(const struct device *dev, uint8_t *rx_data,
const int size)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
int i;
for (i = 0; (i < size) && (regs->status & APBUART_STATUS_DR); i++) {
rx_data[i] = regs->data & 0xff;
}
return i;
}
static void apbuart_irq_tx_enable(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
struct apbuart_dev_data *data = dev->data;
volatile struct apbuart_regs *regs = (void *) config->regs;
unsigned int key;
if (data->usefifo) {
/* Enable the FIFO level interrupt */
regs->ctrl |= APBUART_CTRL_TF;
return;
}
regs->ctrl |= APBUART_CTRL_TI;
/*
* The "TI" interrupt is an edge interrupt. It fires each time the TX
* holding register (or FIFO if implemented) moves from non-empty to
* empty.
*
* When the APBUART is implemented _without_ FIFO, the TI interrupt is
* the only TX interrupt we have. When the APBUART is implemented
* _with_ FIFO, the TI will fire on each TX byte.
*/
regs->ctrl |= APBUART_CTRL_TI;
/* Fire the first "TI" edge interrupt to get things going. */
key = irq_lock();
apbuart_isr(dev);
irq_unlock(key);
}
static void apbuart_irq_tx_disable(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
regs->ctrl &= ~(APBUART_CTRL_TF | APBUART_CTRL_TI);
}
static int apbuart_irq_tx_ready(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
struct apbuart_dev_data *data = dev->data;
volatile struct apbuart_regs *regs = (void *) config->regs;
if (data->usefifo) {
return !(regs->status & APBUART_STATUS_TF);
}
return !!(regs->status & APBUART_STATUS_TE);
}
static int apbuart_irq_tx_complete(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
return !!(regs->status & APBUART_STATUS_TS);
}
static void apbuart_irq_rx_enable(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
regs->ctrl |= APBUART_CTRL_RI;
}
static void apbuart_irq_rx_disable(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
regs->ctrl &= ~APBUART_CTRL_RI;
}
static int apbuart_irq_rx_ready(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
volatile struct apbuart_regs *regs = (void *) config->regs;
return !!(regs->status & APBUART_STATUS_DR);
}
static int apbuart_irq_is_pending(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
struct apbuart_dev_data *data = dev->data;
volatile struct apbuart_regs *regs = (void *) config->regs;
uint32_t status = regs->status;
uint32_t ctrl = regs->ctrl;
if ((ctrl & APBUART_CTRL_RI) && (status & APBUART_STATUS_DR)) {
return 1;
}
if (data->usefifo) {
/* TH is the TX FIFO half-empty flag */
if (status & APBUART_STATUS_TH) {
return 1;
}
} else {
if ((ctrl & APBUART_CTRL_TI) && (status & APBUART_STATUS_TE)) {
return 1;
}
}
return 0;
}
static int apbuart_irq_update(const struct device *dev)
{
return 1;
}
static void apbuart_irq_callback_set(const struct device *dev,
uart_irq_callback_user_data_t cb,
void *cb_data)
{
struct apbuart_dev_data *const dev_data = dev->data;
dev_data->cb = cb;
dev_data->cb_data = cb_data;
}
static void apbuart_isr(const struct device *dev)
{
struct apbuart_dev_data *const dev_data = dev->data;
if (dev_data->cb) {
dev_data->cb(dev, dev_data->cb_data);
}
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
static int apbuart_init(const struct device *dev)
{
const struct apbuart_dev_cfg *config = dev->config;
struct apbuart_dev_data *data = dev->data;
volatile struct apbuart_regs *regs = (void *) config->regs;
const uint32_t APBUART_DEBUG_MASK = APBUART_CTRL_DB | APBUART_CTRL_FL;
uint32_t dm;
uint32_t ctrl;
ctrl = regs->ctrl;
data->usefifo = !!(ctrl & APBUART_CTRL_FA);
/* NOTE: CTRL_FL has reset value 0. CTRL_DB has no reset value. */
dm = ctrl & APBUART_DEBUG_MASK;
if (dm == APBUART_DEBUG_MASK) {
/* Debug mode enabled so assume APBUART already initialized. */
;
} else {
regs->ctrl = APBUART_CTRL_TE | APBUART_CTRL_RE;
}
regs->status = 0;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
irq_connect_dynamic(config->interrupt,
0, (void (*)(const void *))apbuart_isr, dev, 0);
irq_enable(config->interrupt);
#endif
return 0;
}
/* Driver API defined in uart.h */
static const struct uart_driver_api apbuart_driver_api = {
.poll_in = apbuart_poll_in,
.poll_out = apbuart_poll_out,
.err_check = apbuart_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
.configure = apbuart_configure,
.config_get = apbuart_config_get,
#endif
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
.fifo_fill = apbuart_fifo_fill,
.fifo_read = apbuart_fifo_read,
.irq_tx_enable = apbuart_irq_tx_enable,
.irq_tx_disable = apbuart_irq_tx_disable,
.irq_tx_ready = apbuart_irq_tx_ready,
.irq_rx_enable = apbuart_irq_rx_enable,
.irq_rx_disable = apbuart_irq_rx_disable,
.irq_tx_complete = apbuart_irq_tx_complete,
.irq_rx_ready = apbuart_irq_rx_ready,
.irq_is_pending = apbuart_irq_is_pending,
.irq_update = apbuart_irq_update,
.irq_callback_set = apbuart_irq_callback_set,
#endif
};
#define APBUART_INIT(index) \
static const struct apbuart_dev_cfg apbuart##index##_config = { \
.regs = (struct apbuart_regs *) \
DT_INST_REG_ADDR(index), \
IF_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN, \
(.interrupt = DT_INST_IRQN(index),)) \
}; \
\
static struct apbuart_dev_data apbuart##index##_data = { \
.usefifo = 0, \
}; \
\
DEVICE_DT_INST_DEFINE(index, \
&apbuart_init, \
NULL, \
&apbuart##index##_data, \
&apbuart##index##_config, \
PRE_KERNEL_1, \
CONFIG_SERIAL_INIT_PRIORITY, \
&apbuart_driver_api);
DT_INST_FOREACH_STATUS_OKAY(APBUART_INIT)