576 lines
14 KiB
C
576 lines
14 KiB
C
/*
|
|
* Copyright (c) 2021 Telink Semiconductor
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "analog.h"
|
|
#include "clock.h"
|
|
|
|
#include <device.h>
|
|
#include <drivers/uart.h>
|
|
#include <drivers/pinmux.h>
|
|
#include <dt-bindings/pinctrl/b91-pinctrl.h>
|
|
|
|
|
|
/* Driver dts compatibility: telink,b91_uart */
|
|
#define DT_DRV_COMPAT telink_b91_uart
|
|
|
|
/* Get UART instance */
|
|
#define GET_UART(dev) ((volatile struct uart_b91_t *) \
|
|
((const struct uart_b91_config *)dev->config)->uart_addr)
|
|
|
|
/* Get UART configuration */
|
|
#define GET_CFG(dev) ((const struct uart_b91_config *)dev->config)
|
|
|
|
/* Get instance data */
|
|
#define DEV_DATA(dev) ((struct uart_b91_data *const)dev->data)
|
|
|
|
/* UART TX buffer count max value */
|
|
#define UART_TX_BUF_CNT ((uint8_t)8u)
|
|
|
|
/* Parity type */
|
|
#define UART_PARITY_NONE ((uint8_t)0u)
|
|
#define UART_PARITY_EVEN ((uint8_t)1u)
|
|
#define UART_PARITY_ODD ((uint8_t)2u)
|
|
|
|
/* Stop bits length */
|
|
#define UART_STOP_BIT_1 ((uint8_t)0u)
|
|
#define UART_STOP_BIT_1P5 BIT(4)
|
|
#define UART_STOP_BIT_2 BIT(5)
|
|
|
|
|
|
/* B91 UART registers structure */
|
|
struct uart_b91_t {
|
|
uint8_t data_buf[4];
|
|
uint16_t clk_div;
|
|
uint8_t ctrl0;
|
|
uint8_t ctrl1;
|
|
uint8_t ctrl2;
|
|
uint8_t ctrl3;
|
|
uint16_t rxtimeout;
|
|
uint8_t bufcnt;
|
|
uint8_t status;
|
|
uint8_t txrx_status;
|
|
uint8_t state;
|
|
};
|
|
|
|
/* B91 UART data structure */
|
|
struct uart_b91_data {
|
|
uint8_t tx_byte_index;
|
|
uint8_t rx_byte_index;
|
|
struct uart_config cfg;
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
|
uart_irq_callback_user_data_t callback;
|
|
void *cb_data;
|
|
#endif
|
|
};
|
|
|
|
/* B91 UART config structure */
|
|
struct uart_b91_config {
|
|
const uint32_t *pinctrl_list;
|
|
size_t pinctrl_list_size;
|
|
uint32_t uart_addr;
|
|
uint32_t baud_rate;
|
|
void (*pirq_connect)(void);
|
|
};
|
|
|
|
/* rxtimeout register enums */
|
|
enum {
|
|
UART_ERR_IRQ_MASK = BIT(15),
|
|
};
|
|
|
|
/* ctrl0 register enums */
|
|
enum {
|
|
UART_RX_IRQ_MASK = BIT(6),
|
|
UART_TX_IRQ_MASK = BIT(7),
|
|
};
|
|
|
|
/* ctrl3 register enums */
|
|
enum {
|
|
FLD_UART_RX_IRQ_TRIQ_LEV_OFFSET = 0,
|
|
FLD_UART_TX_IRQ_TRIQ_LEV_OFFSET = 4,
|
|
};
|
|
|
|
/* bufcnt register enums */
|
|
enum {
|
|
FLD_UART_RX_BUF_CNT_OFFSET = 0,
|
|
FLD_UART_TX_BUF_CNT_OFFSET = 4,
|
|
};
|
|
|
|
/* status register enums */
|
|
enum {
|
|
UART_IRQ_STATUS = BIT(3),
|
|
UART_RX_ERR_STATUS = BIT(7),
|
|
};
|
|
|
|
|
|
/* Get tx fifo count */
|
|
static inline uint8_t uart_b91_get_tx_bufcnt(volatile struct uart_b91_t *uart)
|
|
{
|
|
return (uart->bufcnt & FLD_UART_TX_BUF_CNT) >> FLD_UART_TX_BUF_CNT_OFFSET;
|
|
}
|
|
|
|
/* Get rx fifo count */
|
|
static inline uint8_t uart_b91_get_rx_bufcnt(volatile struct uart_b91_t *uart)
|
|
{
|
|
return (uart->bufcnt & FLD_UART_RX_BUF_CNT) >> FLD_UART_RX_BUF_CNT_OFFSET;
|
|
}
|
|
|
|
/* Check for prime */
|
|
static uint8_t uart_b91_is_prime(uint32_t n)
|
|
{
|
|
uint32_t i = 5;
|
|
|
|
if (n <= 3) {
|
|
return 1;
|
|
} else if ((n % 2 == 0) || (n % 3 == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 5; i * i < n; i += 6) {
|
|
if ((n % i == 0) || (n % (i + 2)) == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Calculate the best bit width */
|
|
static void uart_b91_cal_div_and_bwpc(uint32_t baudrate, uint32_t pclk,
|
|
uint16_t *divider, uint8_t *bwpc)
|
|
{
|
|
uint8_t i = 0, j = 0;
|
|
uint32_t primeInt = 0;
|
|
uint8_t primeDec = 0;
|
|
uint32_t D_intdec[13], D_int[13];
|
|
uint8_t D_dec[13];
|
|
|
|
primeInt = pclk / baudrate;
|
|
primeDec = 10 * pclk / baudrate - 10 * primeInt;
|
|
|
|
if (uart_b91_is_prime(primeInt)) {
|
|
primeInt += 1;
|
|
} else if (primeDec > 5) {
|
|
primeInt += 1;
|
|
if (uart_b91_is_prime(primeInt)) {
|
|
primeInt -= 1;
|
|
}
|
|
}
|
|
|
|
for (i = 3; i <= 15; i++) {
|
|
D_intdec[i - 3] = (10 * primeInt) / (i + 1);
|
|
D_dec[i - 3] = D_intdec[i - 3] - 10 * (D_intdec[i - 3] / 10);
|
|
D_int[i - 3] = D_intdec[i - 3] / 10;
|
|
}
|
|
|
|
/* find the max and min one decimation point */
|
|
uint8_t position_min = 0, position_max = 0;
|
|
uint32_t min = 0xffffffff, max = 0x00;
|
|
|
|
for (j = 0; j < 13; j++) {
|
|
if ((D_dec[j] <= min) && (D_int[j] != 0x01)) {
|
|
min = D_dec[j];
|
|
position_min = j;
|
|
}
|
|
if (D_dec[j] >= max) {
|
|
max = D_dec[j];
|
|
position_max = j;
|
|
}
|
|
}
|
|
|
|
if ((D_dec[position_min] < 5) && (D_dec[position_max] >= 5)) {
|
|
if (D_dec[position_min] < (10 - D_dec[position_max])) {
|
|
*bwpc = position_min + 3;
|
|
*divider = D_int[position_min] - 1;
|
|
} else {
|
|
*bwpc = position_max + 3;
|
|
*divider = D_int[position_max];
|
|
}
|
|
} else if ((D_dec[position_min] < 5) && (D_dec[position_max] < 5)) {
|
|
*bwpc = position_min + 3;
|
|
*divider = D_int[position_min] - 1;
|
|
} else {
|
|
*bwpc = position_max + 3;
|
|
*divider = D_int[position_max];
|
|
}
|
|
}
|
|
|
|
/* Initializes the UART instance */
|
|
static void uart_b91_init(volatile struct uart_b91_t *uart, uint16_t divider,
|
|
uint8_t bwpc, uint8_t parity, uint8_t stop_bit)
|
|
{
|
|
/* config clock */
|
|
divider = divider | FLD_UART_CLK_DIV_EN;
|
|
uart->ctrl0 = bwpc;
|
|
uart->clk_div = divider;
|
|
|
|
/* config parity */
|
|
if (parity) {
|
|
/* enable parity function */
|
|
uart->ctrl1 |= FLD_UART_PARITY_ENABLE;
|
|
|
|
if (parity == UART_PARITY_EVEN) {
|
|
/* enable even parity */
|
|
uart->ctrl1 &= (~FLD_UART_PARITY_POLARITY);
|
|
} else if (parity == UART_PARITY_ODD) {
|
|
/* enable odd parity */
|
|
uart->ctrl1 |= FLD_UART_PARITY_POLARITY;
|
|
}
|
|
} else {
|
|
uart->ctrl1 &= (~FLD_UART_PARITY_ENABLE); /* disable parity function */
|
|
}
|
|
|
|
/* stop bit config */
|
|
uart->ctrl1 &= (~FLD_UART_STOP_SEL);
|
|
uart->ctrl1 |= stop_bit;
|
|
}
|
|
|
|
/* API implementation: irq handler */
|
|
static void uart_b91_irq_handler(const struct device *dev)
|
|
{
|
|
#ifndef CONFIG_UART_INTERRUPT_DRIVEN
|
|
ARG_UNUSED(dev);
|
|
#else
|
|
struct uart_b91_data *data = DEV_DATA(dev);
|
|
|
|
if (data->callback != NULL) {
|
|
data->callback(dev, data->cb_data);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* API implementation: configure */
|
|
static int uart_b91_configure(const struct device *dev,
|
|
const struct uart_config *cfg)
|
|
{
|
|
uint16_t divider;
|
|
uint8_t bwpc;
|
|
uint8_t parity;
|
|
uint8_t stop_bits;
|
|
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
/* check parity */
|
|
if (cfg->parity == UART_CFG_PARITY_NONE) {
|
|
parity = UART_PARITY_NONE;
|
|
} else if (cfg->parity == UART_CFG_PARITY_ODD) {
|
|
parity = UART_PARITY_ODD;
|
|
} else if (cfg->parity == UART_CFG_PARITY_EVEN) {
|
|
parity = UART_PARITY_EVEN;
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* check stop bits */
|
|
if (cfg->stop_bits == UART_CFG_STOP_BITS_1) {
|
|
stop_bits = UART_STOP_BIT_1;
|
|
} else if (cfg->stop_bits == UART_CFG_STOP_BITS_1_5) {
|
|
stop_bits = UART_STOP_BIT_1P5;
|
|
} else if (cfg->stop_bits == UART_CFG_STOP_BITS_2) {
|
|
stop_bits = UART_STOP_BIT_2;
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* check flow control */
|
|
if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* UART configure */
|
|
uart_b91_cal_div_and_bwpc(cfg->baudrate, sys_clk.pclk * 1000 * 1000, ÷r, &bwpc);
|
|
uart_b91_init(uart, divider, bwpc, parity, stop_bits);
|
|
|
|
/* save configuration */
|
|
DEV_DATA(dev)->cfg = *cfg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* API implementation: config_get */
|
|
static int uart_b91_config_get(const struct device *dev,
|
|
struct uart_config *cfg)
|
|
{
|
|
*cfg = DEV_DATA(dev)->cfg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* API implementation: driver initialization */
|
|
static int uart_b91_driver_init(const struct device *dev)
|
|
{
|
|
uint16_t divider = 0u;
|
|
uint8_t bwpc = 0u;
|
|
const struct device *pinmux;
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
const struct uart_b91_config *cfg = GET_CFG(dev);
|
|
|
|
pinmux = DEVICE_DT_GET(DT_NODELABEL(pinmux));
|
|
if (!device_is_ready(pinmux)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
for (int i = 0; i < cfg->pinctrl_list_size; i++) {
|
|
pinmux_pin_set(pinmux, B91_PINMUX_GET_PIN(cfg->pinctrl_list[i]),
|
|
B91_PINMUX_GET_FUNC(cfg->pinctrl_list[i]));
|
|
}
|
|
|
|
uart_b91_cal_div_and_bwpc(cfg->baud_rate, sys_clk.pclk * 1000 * 1000, ÷r, &bwpc);
|
|
uart_b91_init(uart, divider, bwpc, UART_PARITY_NONE, UART_STOP_BIT_1);
|
|
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
|
cfg->pirq_connect();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* API implementation: poll_out */
|
|
static void uart_b91_poll_out(const struct device *dev, uint8_t c)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
struct uart_b91_data *data = DEV_DATA(dev);
|
|
|
|
while (uart_b91_get_tx_bufcnt(uart) >= UART_TX_BUF_CNT) {
|
|
};
|
|
|
|
uart->data_buf[data->tx_byte_index] = c;
|
|
data->tx_byte_index = (data->tx_byte_index + 1) % ARRAY_SIZE(uart->data_buf);
|
|
}
|
|
|
|
/* API implementation: poll_in */
|
|
static int uart_b91_poll_in(const struct device *dev, unsigned char *c)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
struct uart_b91_data *data = DEV_DATA(dev);
|
|
|
|
if (uart_b91_get_rx_bufcnt(uart) == 0) {
|
|
return -1;
|
|
}
|
|
|
|
*c = uart->data_buf[data->rx_byte_index];
|
|
data->rx_byte_index = (data->rx_byte_index + 1) % ARRAY_SIZE(uart->data_buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* API implementation: err_check */
|
|
static int uart_b91_err_check(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
return ((uart->status & UART_RX_ERR_STATUS) != 0) ? 1 : 0;
|
|
}
|
|
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
|
|
|
/* API implementation: fifo_fill */
|
|
static int uart_b91_fifo_fill(const struct device *dev,
|
|
const uint8_t *tx_data,
|
|
int size)
|
|
{
|
|
int i = 0;
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (uart_b91_get_rx_bufcnt(uart) != 0) {
|
|
break;
|
|
}
|
|
|
|
uart_b91_poll_out(dev, tx_data[i]);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/* API implementation: fifo_read */
|
|
static int uart_b91_fifo_read(const struct device *dev,
|
|
uint8_t *rx_data,
|
|
const int size)
|
|
{
|
|
int rx_count;
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
for (rx_count = 0; rx_count < size; rx_count++) {
|
|
if (uart_b91_get_rx_bufcnt(uart) == 0) {
|
|
break;
|
|
}
|
|
|
|
uart_b91_poll_in(dev, &rx_data[rx_count]);
|
|
}
|
|
|
|
return rx_count;
|
|
}
|
|
|
|
/* API implementation: irq_tx_enable */
|
|
static void uart_b91_irq_tx_enable(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
uart->ctrl3 = (uart->ctrl3 & (~FLD_UART_TX_IRQ_TRIQ_LEV)) |
|
|
BIT(FLD_UART_TX_IRQ_TRIQ_LEV_OFFSET);
|
|
uart->ctrl0 |= UART_TX_IRQ_MASK;
|
|
}
|
|
|
|
/* API implementation: irq_tx_disable */
|
|
static void uart_b91_irq_tx_disable(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
uart->ctrl0 &= ~UART_TX_IRQ_MASK;
|
|
}
|
|
|
|
/* API implementation: irq_tx_ready */
|
|
static int uart_b91_irq_tx_ready(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
return ((uart_b91_get_tx_bufcnt(uart) < UART_TX_BUF_CNT) &&
|
|
((uart->ctrl0 & UART_TX_IRQ_MASK) != 0)) ? 1 : 0;
|
|
}
|
|
|
|
/* API implementation: irq_tx_complete */
|
|
static int uart_b91_irq_tx_complete(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
return (uart_b91_get_tx_bufcnt(uart) == 0) ? 1 : 0;
|
|
}
|
|
|
|
/* API implementation: irq_rx_enable */
|
|
static void uart_b91_irq_rx_enable(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
uart->ctrl3 = (uart->ctrl3 & (~FLD_UART_RX_IRQ_TRIQ_LEV)) |
|
|
BIT(FLD_UART_RX_IRQ_TRIQ_LEV_OFFSET);
|
|
uart->ctrl0 |= UART_RX_IRQ_MASK;
|
|
}
|
|
|
|
/* API implementation: irq_rx_disable */
|
|
static void uart_b91_irq_rx_disable(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
uart->ctrl0 &= ~UART_RX_IRQ_MASK;
|
|
}
|
|
|
|
/* API implementation: irq_rx_ready */
|
|
static int uart_b91_irq_rx_ready(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
return (uart_b91_get_rx_bufcnt(uart) > 0) ? 1 : 0;
|
|
}
|
|
|
|
/* API implementation: irq_err_enable */
|
|
static void uart_b91_irq_err_enable(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
uart->rxtimeout |= UART_ERR_IRQ_MASK;
|
|
}
|
|
|
|
/* API implementation: irq_err_disable*/
|
|
static void uart_b91_irq_err_disable(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
uart->rxtimeout &= ~UART_ERR_IRQ_MASK;
|
|
}
|
|
|
|
/* API implementation: irq_is_pending */
|
|
static int uart_b91_irq_is_pending(const struct device *dev)
|
|
{
|
|
volatile struct uart_b91_t *uart = GET_UART(dev);
|
|
|
|
return ((uart->status & UART_IRQ_STATUS) != 0) ? 1 : 0;
|
|
}
|
|
|
|
/* API implementation: irq_update */
|
|
static int uart_b91_irq_update(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
/* nothing to be done */
|
|
return 1;
|
|
}
|
|
|
|
/* API implementation: irq_callback_set */
|
|
static void uart_b91_irq_callback_set(const struct device *dev,
|
|
uart_irq_callback_user_data_t cb,
|
|
void *cb_data)
|
|
{
|
|
struct uart_b91_data *data = DEV_DATA(dev);
|
|
|
|
data->callback = cb;
|
|
data->cb_data = cb_data;
|
|
}
|
|
|
|
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
|
|
|
static const struct uart_driver_api uart_b91_driver_api = {
|
|
.poll_in = uart_b91_poll_in,
|
|
.poll_out = uart_b91_poll_out,
|
|
.err_check = uart_b91_err_check,
|
|
.configure = uart_b91_configure,
|
|
.config_get = uart_b91_config_get,
|
|
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
|
.fifo_fill = uart_b91_fifo_fill,
|
|
.fifo_read = uart_b91_fifo_read,
|
|
.irq_tx_enable = uart_b91_irq_tx_enable,
|
|
.irq_tx_disable = uart_b91_irq_tx_disable,
|
|
.irq_tx_ready = uart_b91_irq_tx_ready,
|
|
.irq_tx_complete = uart_b91_irq_tx_complete,
|
|
.irq_rx_enable = uart_b91_irq_rx_enable,
|
|
.irq_rx_disable = uart_b91_irq_rx_disable,
|
|
.irq_rx_ready = uart_b91_irq_rx_ready,
|
|
.irq_err_enable = uart_b91_irq_err_enable,
|
|
.irq_err_disable = uart_b91_irq_err_disable,
|
|
.irq_is_pending = uart_b91_irq_is_pending,
|
|
.irq_update = uart_b91_irq_update,
|
|
.irq_callback_set = uart_b91_irq_callback_set,
|
|
#endif
|
|
};
|
|
|
|
|
|
#define UART_B91_INIT(n) \
|
|
\
|
|
static void uart_b91_irq_connect_##n(void); \
|
|
\
|
|
static const uint32_t uart_pins_##n[] = \
|
|
B91_PINMUX_DT_INST_GET_ARRAY(n, 0); \
|
|
\
|
|
static const struct uart_b91_config uart_b91_cfg_##n = \
|
|
{ \
|
|
.uart_addr = DT_INST_REG_ADDR(n), \
|
|
.baud_rate = DT_INST_PROP(n, current_speed), \
|
|
.pinctrl_list_size = ARRAY_SIZE(uart_pins_##n), \
|
|
.pinctrl_list = uart_pins_##n, \
|
|
.pirq_connect = uart_b91_irq_connect_##n \
|
|
}; \
|
|
\
|
|
static struct uart_b91_data uart_b91_data_##n; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, uart_b91_driver_init, \
|
|
NULL, \
|
|
&uart_b91_data_##n, \
|
|
&uart_b91_cfg_##n, \
|
|
PRE_KERNEL_1, \
|
|
CONFIG_SERIAL_INIT_PRIORITY, \
|
|
(void *)&uart_b91_driver_api); \
|
|
\
|
|
static void uart_b91_irq_connect_##n(void) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
|
|
uart_b91_irq_handler, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
\
|
|
riscv_plic_irq_enable(DT_INST_IRQN(n)); \
|
|
riscv_plic_set_priority(DT_INST_IRQN(n), DT_INST_IRQ(n, priority)); \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(UART_B91_INIT)
|