Linux UART驱动分析

1. 介绍

8250是IBM PC及兼容机使用的一种串口芯片; 16550是一种带先进先出(FIFO)功能的8250系列串口芯片; 16550A则是16550的升级版本, 修复了FIFO相关BUG, 也是目前比较常见的串口芯片.

本文介绍的是Xilinx UART 驱动分析, 因为没有找到其datasheet, 硬件操作部分分析16550的实现.

Xilinx UART驱动主要由drivers/tty/serial/xilinx_uartps.c来实现
其相关配置和基本信息可参考<Zynq UART>

2. 结构体

uart_driverconsole结构变量, 以及实现了uart_ops函数操作集定义如下图所示

static struct uart_driver cdns_uart_uart_driver = {
.owner = THIS_MODULE,
.driver_name = CDNS_UART_NAME, /* xuartps */
.dev_name = CDNS_UART_TTY_NAME, /* ttyPS */
.major = CDNS_UART_MAJOR, /* 0, 注册时动态分配 */
.minor = CDNS_UART_MINOR, /* 0, 注册时动态分配*/
.nr = CDNS_UART_NR_PORTS, /* 2 */
#ifdef CONFIG_SERIAL_XILINX_PS_UART_CONSOLE
.cons = &cdns_uart_console, /* ttyPS */
#endif
}; static struct console cdns_uart_console = {
.name = CDNS_UART_TTY_NAME, /* ttyPS */
.write = cdns_uart_console_write,
.device = uart_console_device,
.setup = cdns_uart_console_setup,
.flags = CON_PRINTBUFFER,
.index = -, /* 由cmdline指定(e.g. console=ttyPS ) */
.data = &cdns_uart_uart_driver,
}; static const struct uart_ops cdns_uart_ops = {
.set_mctrl = cdns_uart_set_mctrl,
.get_mctrl = cdns_uart_get_mctrl,
.start_tx = cdns_uart_start_tx,
.stop_tx = cdns_uart_stop_tx,
.stop_rx = cdns_uart_stop_rx,
.tx_empty = cdns_uart_tx_empty,
.break_ctl = cdns_uart_break_ctl,
.set_termios = cdns_uart_set_termios,
.startup = cdns_uart_startup,
.shutdown = cdns_uart_shutdown,
.pm = cdns_uart_pm,
.type = cdns_uart_type,
.verify_port = cdns_uart_verify_port,
.request_port = cdns_uart_request_port,
.release_port = cdns_uart_release_port,
.config_port = cdns_uart_config_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = cdns_uart_poll_get_char,
.poll_put_char = cdns_uart_poll_put_char,
#endif
};

3. 初始化

模块入口为cdns_uart_init
首先注册UART驱动

uart_register_driver(&cdns_uart_uart_driver);

随后又注册platform驱动

platform_driver_register(&cdns_uart_platform_driver);

其中cdns_uart_platform_driver定义如下

static const struct of_device_id cdns_uart_of_match[] = {
{ .compatible = "xlnx,xuartps", },
{ .compatible = "cdns,uart-r1p8", },
{ .compatible = "cdns,uart-r1p12", .data = &zynqmp_uart_def },
{ .compatible = "xlnx,zynqmp-uart", .data = &zynqmp_uart_def },
{}
}; static struct platform_driver cdns_uart_platform_driver = {
.probe = cdns_uart_probe,
.remove = cdns_uart_remove,
.driver = {
.name = CDNS_UART_NAME,
.of_match_table = cdns_uart_of_match,
.pm = &cdns_uart_dev_pm_ops,
},
};

而在arch/arm/boot/dts/zynq-7000.dtsi中, 定义了uart设备树相关信息

uart0: serial@e0000000 {
compatible = "xlnx,xuartps", "cdns,uart-r1p8";
status = "disabled";
clocks = <&clkc >, <&clkc >;
clock-names = "uart_clk", "pclk";
reg = <0xE0000000 0x1000>;
interrupts = < >;
}; uart1: serial@e0001000 {
compatible = "xlnx,xuartps", "cdns,uart-r1p8";
status = "disabled";
clocks = <&clkc >, <&clkc >;
clock-names = "uart_clk", "pclk";
reg = <0xE0001000 0x1000>;
interrupts = < >;
};

关于设备树, 可参考<Linux设备树解析>
从文章中我们知道内核会将设备树解析为platform_device, 匹配后则会调用cdns_uart_probe
下面以uart0驱动probe分析一下该函数

int cdns_uart_probe(struct platform_device *pdev)
{
int id, irq;
struct uart_port *port;
struct resource *res;
struct cdns_uart *cdns_uart_data; /* 分配驱动私有数据结构体 */
cdns_uart_data = devm_kzalloc(&pdev->dev, sizeof(*cdns_uart_data), GFP_KERNEL); /* 从dts获取时钟(clocks), pclk=40, uart_clk=23 */
cdns_uart_data->pclk = devm_clk_get(&pdev->dev, "pclk");
cdns_uart_data->uartclk = devm_clk_get(&pdev->dev, "uart_clk");
/* 准备时钟源 */
clk_prepare(cdns_uart_data->pclk);
clk_prepare(cdns_uart_data->uartclk); /* 从dts获取编址(reg), start=0xE0000000,end=0xE0001000 */
platform_get_resource(pdev, IORESOURCE_MEM, ); /* 从dts获取中断(interrupts), 中断号为27 !!! */
platform_get_irq(pdev, ); /* 获取设备编号, 此处为0 */
id = of_alias_get_id(pdev->dev.of_node, "serial"); /* 初始化uart端口 */
port = cdns_uart_get_port(id); /* 设置uart端口硬件相关参数 */
port->mapbase = res->start;
port->irq = irq;
port->dev = &pdev->dev;
port->uartclk = clk_get_rate(cdns_uart_data->uartclk);
port->private_data = cdns_uart_data;
cdns_uart_data->port = port;
platform_set_drvdata(pdev, port); /* 添加uart端口 */
uart_add_one_port(&cdns_uart_uart_driver, port);
} static struct uart_port cdns_uart_port[CDNS_UART_NR_PORTS]; /* 2 */
struct uart_port *cdns_uart_get_port(int id)
{
struct uart_port *port; /* 获取本地定义的uart_port结构体变量 */
port = &cdns_uart_port[id]; spin_lock_init(&port->lock);
port->membase = NULL;
port->irq = ;
port->type = PORT_UNKNOWN; /* 会在config_port中设置为PORT_XUARTPS */
port->iotype = UPIO_MEM32; /* 串口接口寄存器的地址类型 */
port->flags = UPF_BOOT_AUTOCONF; /* 该标志会使uart_add_one_port调用config_port */
port->ops = &cdns_uart_ops; /* 即前面定义的uart_ops函数操作集 */
port->fifosize = CDNS_UART_FIFO_SIZE; /* 64 */
port->line = id; /* 0 */
port->dev = NULL;
return port;
}

4. 16550介绍

16550寄存器信息如下

Linux UART驱动分析

RBF定义如下

Linux UART驱动分析

THR定义如下

Linux UART驱动分析

IER定义如下

Linux UART驱动分析

IIR定义如下

Linux UART驱动分析

FCR定义如下

Linux UART驱动分析

LCR定义如下

Linux UART驱动分析

MCR定义如下

Linux UART驱动分析

LSR定义如下

Linux UART驱动分析

MSR定义如下

Linux UART驱动分析

SCR定义如下

Linux UART驱动分析

5. 硬件操作实现

这里分析8250/16550对uart_ops的实现serial8250_pops
主要代码位于drivers/tty/serial/8250/8250_port.c

tx_empty: serial8250_tx_empty
读取并判断LSR的第THRE、TEMT位是否为1

set_mctrl: serial8250_set_mctrl
将位设置(RTS、DTR、OUT1、OUT2、LOOP)写入MCR

get_mctrl: serial8250_get_mctrl
读取MSR, 即Modem Interface的当前状态

stop_tx: serial8250_stop_tx
禁用IER的THRI/ETBEI位

start_tx: serial8250_start_tx
启用IER的THRI/ETBEI位; 当LSR的THRE位为1, 通过操作THR将circ_buf的数据搬运至UART

stop_rx: serial8250_stop_rx
禁用IER的RLSI/ELSI和RDI/ERBFI位

enable_ms: serial8250_enable_ms
启用IER的MSI/EDSSI

break_ctl: serial8250_break_ctl
启动或者禁用LCR的SBC/SetBreak位

startup: serial8250_startup
1. 设置FCR清空FIFO缓冲区, 清空中断寄存器(LSR、RX、IIR、MSR), 初始化相关寄存器
2. 调用uart_8250_ops::setup_irq(univ8250_setup_irq)
3. 设置MCR寄存器
4. 为TX/RX请求DMA通道

univ8250_setup_irq
serial_link_irq_chain
request_irq
serial8250_interrupt
dw8250_handle_irq
/* 即uart_port::handle_irq */
serial8250_handle_irq
handle_rx_dma(Running here???)
serial8250_rx_dma
/* uart_8250_port::uart_8250_dma::rx_dma */
__dma_rx_complete
tty_insert_flip_string
/* 将数据插入接收数据缓冲区 */
tty_flip_buffer_push
/* 将数据搬至线路规程层 */
tty_schedule_flip
flush_to_ldisc
serial8250_rx_chars
serial8250_read_char
uart_insert_char
tty_insert_flip_char
/* 将数据插入接收数据缓冲区 */
tty_flip_buffer_push
/* 将数据搬至线路规程层 */
tty_schedule_flip
flush_to_ldisc

shutdown: serial8250_shutdown
初始化寄存器(...), 注销中断处理程序(???)

set_termios: serial8250_set_termios
设置相关寄存器(...)

set_ldisc: serial8250_set_ldisc
如果没有设置了Modem状态, 则禁用IER的MSI位

pm: serial8250_pm
休眠(???)

type: serial8250_type
获取硬件名称

release_port: serial8250_release_port
释放端口占用物理资源, 如Memory, I/O

request_port: serial8250_request_port
请求物理资源

config_port: serial8250_config_port
按照传入参数配置端口

verify_port: serial8250_verify_port
校验端口配置是否有效

参考:
<Xinu uart-ns16550>
<AXI UART 16550 v2.0>
<XPS 16550 UART v3.00>
<dw_apb_uart Databook>
<Serial UART information>

上一篇:ARM-Linux S5PV210 UART驱动(6)----platform device的添加


下一篇:调试exynos4412—ARM嵌入式Linux—LEDS/GPIO驱动之二