zoukankan      html  css  js  c++  java
  • 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          = -1,                      /* 由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 23>, <&clkc 40>;
        clock-names = "uart_clk", "pclk";
        reg = <0xE0000000 0x1000>;
        interrupts = <0 27 4>;
    };
    
    uart1: serial@e0001000 {
        compatible = "xlnx,xuartps", "cdns,uart-r1p8";
        status = "disabled";
        clocks = <&clkc 24>, <&clkc 41>;
        clock-names = "uart_clk", "pclk";
        reg = <0xE0001000 0x1000>;
        interrupts = <0 50 4>;
    };

    关于设备树, 可参考<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, 0);
    
        /* 从dts获取中断(interrupts), 中断号为27 !!! */
        platform_get_irq(pdev, 0);
    
        /* 获取设备编号, 此处为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      = 0;
        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寄存器信息如下

    RegisterAddressMap

    RBF定义如下

    RBF

    THR定义如下

    THR

    IER定义如下

    IER

    IIR定义如下

    IIR

    FCR定义如下

    FCR

    LCR定义如下

    LCR

    MCR定义如下

    MCR

    LSR定义如下

    LSR

    MSR定义如下

    MSR

    SCR定义如下

    SCR

    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>

  • 相关阅读:
    SharePoint 2013 图文开发系列之自定义字段
    SharePoint 2013 图文开发系列之Visual Studio 创建母版页
    SharePoint 2013 图文开发系列之代码定义列表
    SharePoint 2013 图文开发系列之计时器任务
    SharePoint 2013 图文开发系列之应用程序页
    SharePoint 2013 图文开发系列之事件接收器
    SharePoint 2013 图文开发系列之可视化WebPart
    SharePoint 2013 图文开发系列之WebPart
    SharePoint 2013 对二进制大型对象(BLOB)进行爬网
    SharePoint 2013 状态机工作流之日常报销示例
  • 原文地址:https://www.cnblogs.com/hzl6255/p/9560138.html
Copyright © 2011-2022 走看看