rt-thread-official/bsp/zynqmp-a53-dfzu2eg/drivers/drv_uart.c

347 lines
10 KiB
C

/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-04-11 liYony the first version
*/
#include <rthw.h>
#include <rtdevice.h>
#include <drv_uart.h>
#include "board.h"
#include "zynqmp_uart.h"
#define ZYNQMP_UART_DEVICE_DEFAULT(base, irq, clk) {{ \
.ops = &_zynqmp_ops, \
.config = RT_SERIAL_CONFIG_DEFAULT \
}, \
.hw_base = base, \
.irqno = irq, \
.in_clk = clk \
}
struct zynqmp_uart_device
{
struct rt_serial_device device;
rt_ubase_t hw_base;
rt_uint32_t irqno;
rt_uint32_t in_clk;
};
static void _uart_set_fifo_threshold(rt_ubase_t base, rt_uint8_t trigger_level)
{
rt_uint32_t reg_triger;
/* Assert validates the input arguments */
RT_ASSERT(base != RT_NULL);
RT_ASSERT(trigger_level <= (rt_uint8_t)XUARTPS_RXWM_MASK);
reg_triger = ((rt_uint32_t)trigger_level) & (rt_uint32_t)XUARTPS_RXWM_MASK;
/*
* Write the new value for the FIFO control register to it such that the
* threshold is changed
*/
writel(reg_triger, base + XUARTPS_RXWM_OFFSET);
}
static void _uart_set_interrupt_mask(rt_ubase_t base, rt_uint32_t mask)
{
rt_uint32_t temp_mask = mask;
RT_ASSERT(base != RT_NULL);
temp_mask &= (rt_uint32_t)XUARTPS_IXR_MASK;
writel(temp_mask, base + XUARTPS_IER_OFFSET);
writel((~temp_mask), base + XUARTPS_IDR_OFFSET);
}
static rt_err_t _uart_baudrate_init(rt_ubase_t base, struct serial_configure *cfg, rt_uint32_t in_clk)
{
rt_uint32_t iter_baud_div; /* Iterator for available baud divisor values */
rt_uint32_t brgr_value; /* Calculated value for baud rate generator */
rt_uint32_t calc_baudrate; /* Calculated baud rate */
rt_uint32_t baud_error; /* Diff between calculated and requested baud rate */
rt_uint32_t best_brgr = 0U; /* Best value for baud rate generator */
rt_uint8_t best_baud_div = 0U; /* Best value for baud divisor */
rt_uint32_t best_error = 0xFFFFFFFFU;
rt_uint32_t percent_error;
rt_uint32_t mode_reg;
rt_uint32_t input_clk;
rt_uint32_t temp_reg;
/* Asserts validate the input arguments */
RT_ASSERT(base != RT_NULL);
RT_ASSERT(cfg->baud_rate <= (rt_uint32_t)XUARTPS_MAX_RATE);
RT_ASSERT(cfg->baud_rate >= (rt_uint32_t)XUARTPS_MIN_RATE);
/*
* Make sure the baud rate is not impossilby large.
* Fastest possible baud rate is Input Clock / 2.
*/
if ((cfg->baud_rate * 2) > in_clk)
{
return -RT_EINVAL;
}
/* Check whether the input clock is divided by 8 */
mode_reg = readl(base + XUARTPS_MR_OFFSET);
input_clk = in_clk;
if (mode_reg & XUARTPS_MR_CLKSEL)
{
input_clk = in_clk / 8;
}
/*
* Determine the Baud divider. It can be 4to 254.
* Loop through all possible combinations
*/
for (iter_baud_div = 4; iter_baud_div < 255; iter_baud_div++)
{
/* Calculate the value for BRGR register */
brgr_value = input_clk / (cfg->baud_rate * (iter_baud_div + 1));
/* Calculate the baud rate from the BRGR value */
calc_baudrate = input_clk / (brgr_value * (iter_baud_div + 1));
/* Avoid unsigned integer underflow */
if (cfg->baud_rate > calc_baudrate)
{
baud_error = cfg->baud_rate - calc_baudrate;
}
else
{
baud_error = calc_baudrate - cfg->baud_rate;
}
/* Find the calculated baud rate closest to requested baud rate. */
if (best_error > baud_error)
{
best_brgr = brgr_value;
best_baud_div = iter_baud_div;
best_error = baud_error;
}
}
/* Make sure the best error is not too large. */
percent_error = (best_error * 100) / cfg->baud_rate;
if (XUARTPS_MAX_BAUD_ERROR_RATE < percent_error)
{
return -RT_EINVAL;
}
/* Disable TX and RX to avoid glitches when setting the baud rate. */
temp_reg = (((readl(base + XUARTPS_CR_OFFSET)) & ((rt_uint32_t)(~XUARTPS_CR_EN_DIS_MASK))) |
((rt_uint32_t)XUARTPS_CR_RX_DIS | (rt_uint32_t)XUARTPS_CR_TX_DIS));
writel(temp_reg, base + XUARTPS_CR_OFFSET);
/* Set the baud rate divisor */
writel(best_brgr, base + XUARTPS_BAUDGEN_OFFSET);
writel(best_baud_div, base + XUARTPS_BAUDDIV_OFFSET);
/* RX and TX SW reset */
writel(XUARTPS_CR_TXRST | XUARTPS_CR_RXRST, base + XUARTPS_CR_OFFSET);
/* Enable device */
temp_reg = (((readl(base + XUARTPS_CR_OFFSET)) & ((rt_uint32_t)(~XUARTPS_CR_EN_DIS_MASK))) |
((rt_uint32_t)XUARTPS_CR_RX_EN | (rt_uint32_t)XUARTPS_CR_TX_EN));
writel(temp_reg, base + XUARTPS_CR_OFFSET);
return RT_EOK;
}
static rt_err_t zynqmp_uart_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
struct zynqmp_uart_device *uart = (struct zynqmp_uart_device *)serial;
RT_ASSERT(uart != RT_NULL);
if (_uart_baudrate_init(uart->hw_base, cfg, uart->in_clk) != RT_EOK)
{
return -RT_ERROR;
}
rt_uint32_t mode_reg = 0U;
/* Set the parity mode */
mode_reg = readl(uart->hw_base + XUARTPS_MR_OFFSET);
/* Mask off what's already there */
mode_reg &= (~((rt_uint32_t)XUARTPS_MR_CHARLEN_MASK |
(rt_uint32_t)XUARTPS_MR_STOPMODE_MASK |
(rt_uint32_t)XUARTPS_MR_PARITY_MASK));
switch (cfg->data_bits)
{
case DATA_BITS_6:
mode_reg |= (rt_uint32_t)XUARTPS_MR_CHARLEN_6_BIT;
break;
case DATA_BITS_7:
mode_reg |= (rt_uint32_t)XUARTPS_MR_CHARLEN_7_BIT;
break;
case DATA_BITS_8:
mode_reg |= (rt_uint32_t)XUARTPS_MR_CHARLEN_8_BIT;
break;
default:
mode_reg |= (rt_uint32_t)XUARTPS_MR_CHARLEN_8_BIT;
break;
}
switch (cfg->stop_bits)
{
case STOP_BITS_1:
mode_reg |= (rt_uint32_t)XUARTPS_MR_STOPMODE_1_BIT;
break;
case STOP_BITS_2:
mode_reg |= (rt_uint32_t)XUARTPS_MR_STOPMODE_2_BIT;
break;
default:
mode_reg |= (rt_uint32_t)XUARTPS_MR_STOPMODE_1_BIT;
break;
}
switch (cfg->parity)
{
case PARITY_NONE:
mode_reg |= (rt_uint32_t)XUARTPS_MR_PARITY_NONE;
break;
case PARITY_ODD:
mode_reg |= (rt_uint32_t)XUARTPS_MR_PARITY_ODD;
break;
case PARITY_EVEN:
mode_reg |= (rt_uint32_t)XUARTPS_MR_PARITY_EVEN;
break;
default:
mode_reg |= (rt_uint32_t)XUARTPS_MR_PARITY_NONE;
break;
}
/* Write the mode register out */
writel(mode_reg, uart->hw_base + XUARTPS_MR_OFFSET);
/* Set the RX FIFO trigger at 8 data bytes. */
writel(0x08U, uart->hw_base + XUARTPS_RXWM_OFFSET);
/* Set the RX timeout to 1, which will be 4 character time */
writel(0x01U, uart->hw_base + XUARTPS_RXTOUT_OFFSET);
/* Disable all interrupts, polled mode is the default */
writel(XUARTPS_IXR_MASK, uart->hw_base + XUARTPS_IDR_OFFSET);
return RT_EOK;
}
static rt_err_t zynqmp_uart_control(struct rt_serial_device *serial, int cmd, void *arg)
{
struct zynqmp_uart_device *uart = (struct zynqmp_uart_device *)serial;
RT_ASSERT(uart != RT_NULL);
switch (cmd)
{
case RT_DEVICE_CTRL_CLR_INT:
/* Disable the UART Interrupt */
rt_hw_interrupt_mask(uart->irqno);
_uart_set_interrupt_mask(uart->hw_base, 0U);
break;
case RT_DEVICE_CTRL_SET_INT:
/* Enable the UART Interrupt */
_uart_set_fifo_threshold(uart->hw_base, 1);
rt_hw_interrupt_umask(uart->irqno);
_uart_set_interrupt_mask(uart->hw_base, XUARTPS_IXR_RXOVR);
break;
}
return RT_EOK;
}
static int zynqmp_uart_putc(struct rt_serial_device *serial, char c)
{
struct zynqmp_uart_device *uart = (struct zynqmp_uart_device *)serial;
RT_ASSERT(uart != RT_NULL);
/* Wait until there is space in TX FIFO */
while ((readl(uart->hw_base + XUARTPS_SR_OFFSET) &
XUARTPS_SR_TXFULL) == XUARTPS_SR_TXFULL)
{
;
}
/* Write the byte into the TX FIFO */
writel((rt_uint32_t)c, uart->hw_base + XUARTPS_FIFO_OFFSET);
return 1;
}
static int zynqmp_uart_getc(struct rt_serial_device *serial)
{
struct zynqmp_uart_device *uart = (struct zynqmp_uart_device *)serial;
RT_ASSERT(uart != RT_NULL);
/* Wait until there is data */
if ((readl(uart->hw_base + XUARTPS_SR_OFFSET) &
XUARTPS_SR_RXEMPTY) == XUARTPS_SR_RXEMPTY)
{
return -1;
}
int ch = readl(uart->hw_base + XUARTPS_FIFO_OFFSET);
return ch;
}
static const struct rt_uart_ops _zynqmp_ops =
{
zynqmp_uart_configure,
zynqmp_uart_control,
zynqmp_uart_putc,
zynqmp_uart_getc,
};
#ifdef BSP_USING_UART0
static struct zynqmp_uart_device _uart0_device =
ZYNQMP_UART_DEVICE_DEFAULT(ZYNQMP_UART0_BASE, ZYNQMP_UART0_IRQNUM, ZYNQMP_UART0_CLK_FREQ_HZ);
#endif
static void rt_hw_uart_isr(int irqno, void *param)
{
struct zynqmp_uart_device *uart = (struct zynqmp_uart_device *)param;
RT_ASSERT(uart != RT_NULL);
struct rt_serial_device *serial = &(uart->device);
rt_uint32_t isr_status;
isr_status = readl(uart->hw_base + XUARTPS_IMR_OFFSET);
isr_status &= readl(uart->hw_base + XUARTPS_ISR_OFFSET);
if (isr_status & (rt_uint32_t)XUARTPS_IXR_RXOVR)
{
writel(XUARTPS_IXR_RXOVR, uart->hw_base + XUARTPS_ISR_OFFSET);
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);
}
}
int rt_hw_uart_init(void)
{
struct zynqmp_uart_device *uart = RT_NULL;
#ifdef BSP_USING_UART0
uart = &_uart0_device;
_uart0_device.hw_base = (rt_size_t)rt_ioremap((void*)_uart0_device.hw_base, ZYNQMP_UART0_SIZE);
/* register UART0 device */
rt_hw_serial_register(&uart->device, "uart0",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
rt_hw_interrupt_install(uart->irqno, rt_hw_uart_isr, uart, "uart0");
#endif
return 0;
}