Linux驱动分析之Uart驱动

简介: 之前对Uart驱动的整体架构做了介绍,现在来分析具体的驱动程序。我们以NXP 的 IMX6来进行分析。

前言

之前对Uart驱动的整体架构做了介绍,现在来分析具体的驱动程序。我们以NXP 的 IMX6来进行分析。


Uart驱动分析

内核:4.20

芯片:NXP IMX6

下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析。

(1) 装载和卸载函数

//dts匹配表staticconststructof_device_idimx_uart_dt_ids[] = {
  { .compatible="fsl,imx6q-uart", .data=&imx_uart_devdata[IMX6Q_UART], },
  { .compatible="fsl,imx53-uart", .data=&imx_uart_devdata[IMX53_UART], },
  { .compatible="fsl,imx1-uart", .data=&imx_uart_devdata[IMX1_UART], },
  { .compatible="fsl,imx21-uart", .data=&imx_uart_devdata[IMX21_UART], },
  { /* sentinel */ }
};
staticstructuart_driverimx_uart_uart_driver= {
  .owner=THIS_MODULE,
  .driver_name=DRIVER_NAME,
  .dev_name=DEV_NAME, //设备节点名  .major=SERIAL_IMX_MAJOR, //主设备号  .minor=MINOR_START, //次设备号  .nr=ARRAY_SIZE(imx_uart_ports), //串口数  .cons=IMX_CONSOLE,
};
staticstructplatform_driverimx_uart_platform_driver= {
  .probe=imx_uart_probe, //driver和device匹配后回调  .remove=imx_uart_remove,
  .id_table=imx_uart_devtype,
  .driver= {
    .name="imx-uart",
    .of_match_table=imx_uart_dt_ids,
    .pm=&imx_uart_pm_ops,
  },
};
//加载函数staticint__initimx_uart_init(void)
{
//注册uart_driverintret=uart_register_driver(&imx_uart_uart_driver);
//注册platform_driverret=platform_driver_register(&imx_uart_platform_driver);
returnret;
}
//卸载函数staticvoid__exitimx_uart_exit(void)
{
//注销uart_driver和platform_driverplatform_driver_unregister(&imx_uart_platform_driver);
uart_unregister_driver(&imx_uart_uart_driver);
}
module_init(imx_uart_init);
module_exit(imx_uart_exit);

image.gif

上面真正回调probe的是匹配platform_driver, 而不是uart_driver。所以我们会看到调用了uart_register_driverplatform_driver_register

uart_register_driver是为了向uart核心层注册。


(2) probe()函数

staticintimx_uart_probe(structplatform_device*pdev)
{
structimx_port*sport; //nxp对uart_port进行了封装,添加自己的成员void__iomem*base;
intret=0;
u32ucr1;
structresource*res;
inttxirq, rxirq, rtsirq;
//分配内存,并清0sport=devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
if (!sport)
return-ENOMEM;
//解析设备树,保存到imx_portret=imx_uart_probe_dt(sport, pdev);
if (ret>0)
imx_uart_probe_pdata(sport, pdev);
elseif (ret<0)
returnret;
//省略....//获取IO资源,并映射res=platform_get_resource(pdev, IORESOURCE_MEM, 0);
base=devm_ioremap_resource(&pdev->dev, res);
//省略....//获取RX,TX,RTS中断号rxirq=platform_get_irq(pdev, 0);
txirq=platform_get_irq(pdev, 1);
rtsirq=platform_get_irq(pdev, 2);
//填充imx_port结构体sport->port.dev=&pdev->dev;
sport->port.mapbase=res->start; //映射地址sport->port.membase=base; //物理地址sport->port.type=PORT_IMX,
sport->port.iotype=UPIO_MEM;
sport->port.irq=rxirq; //接收中断sport->port.fifosize=32;
sport->port.ops=&imx_uart_pops; //串口操作函数sport->port.rs485_config=imx_uart_rs485_config; //485配置sport->port.flags=UPF_BOOT_AUTOCONF;
timer_setup(&sport->timer, imx_uart_timeout, 0); //设置定时器sport->gpios=mctrl_gpio_init(&sport->port, 0);
if (IS_ERR(sport->gpios))
returnPTR_ERR(sport->gpios);
//获取IPG时钟sport->clk_ipg=devm_clk_get(&pdev->dev, "ipg");
//省略....//获取PER时钟sport->clk_per=devm_clk_get(&pdev->dev, "per");
//省略....sport->port.uartclk=clk_get_rate(sport->clk_per);
//使能IPG时钟ret=clk_prepare_enable(sport->clk_ipg);
//省略....//读取寄存器值sport->ucr1=readl(sport->port.membase+UCR1);
sport->ucr2=readl(sport->port.membase+UCR2);
sport->ucr3=readl(sport->port.membase+UCR3);
sport->ucr4=readl(sport->port.membase+UCR4);
sport->ufcr=readl(sport->port.membase+UFCR);
uart_get_rs485_mode(&pdev->dev, &sport->port.rs485);
//省略....imx_uart_rs485_config(&sport->port, &sport->port.rs485);
//下面都是对寄存器的配置,可以查看datasheetucr1=imx_uart_readl(sport, UCR1);
ucr1&=~(UCR1_ADEN|UCR1_TRDYEN|UCR1_IDEN|UCR1_RRDYEN|UCR1_TXMPTYEN|UCR1_RTSDEN);
imx_uart_writel(sport, ucr1, UCR1);
if (!imx_uart_is_imx1(sport) &&sport->dte_mode) {
u32ufcr=imx_uart_readl(sport, UFCR);
if (!(ufcr&UFCR_DCEDTE))
imx_uart_writel(sport, ufcr|UFCR_DCEDTE, UFCR);
imx_uart_writel(sport,
IMX21_UCR3_RXDMUXSEL|UCR3_ADNIMP|UCR3_DSR,
UCR3);
  } else {
u32ucr3=UCR3_DSR;
u32ufcr=imx_uart_readl(sport, UFCR);
if (ufcr&UFCR_DCEDTE)
imx_uart_writel(sport, ufcr&~UFCR_DCEDTE, UFCR);
if (!imx_uart_is_imx1(sport))
ucr3|=IMX21_UCR3_RXDMUXSEL|UCR3_ADNIMP;
imx_uart_writel(sport, ucr3, UCR3);
  }
clk_disable_unprepare(sport->clk_ipg);
//申请中断if (txirq>0) { //开启tx中断ret=devm_request_irq(&pdev->dev, rxirq, imx_uart_rxint, 0,
dev_name(&pdev->dev), sport);
//省略.....ret=devm_request_irq(&pdev->dev, txirq, imx_uart_txint, 0,
dev_name(&pdev->dev), sport);
//省略.....ret=devm_request_irq(&pdev->dev, rtsirq, imx_uart_rtsint, 0,
dev_name(&pdev->dev), sport);
//省略.....  } else { //不开tx中断ret=devm_request_irq(&pdev->dev, rxirq, imx_uart_int, 0,
dev_name(&pdev->dev), sport);
//省略.....  }
//保存imx_portimx_uart_ports[sport->port.line] =sport;
platform_set_drvdata(pdev, sport);
//关联uart_driver和uart_portreturnuart_add_one_port(&imx_uart_uart_driver, &sport->port);
}

image.gif

上面其实主要是寄存器配置,中断申请,最后添加port。对裸机程序熟悉的,应该能很轻松的理解,因为我们不是为了针对某款芯片,所以寄存器配置可以忽略,主要还是为了理解Uart的驱动框架。


(3) 串口操作函数(uart_ops)

staticconststructuart_opsimx_uart_pops= {
  .tx_empty=imx_uart_tx_empty,
  .set_mctrl=imx_uart_set_mctrl,
  .get_mctrl=imx_uart_get_mctrl,
  .stop_tx=imx_uart_stop_tx,
  .start_tx=imx_uart_start_tx,
  .stop_rx=imx_uart_stop_rx,
  .enable_ms=imx_uart_enable_ms,
  .break_ctl=imx_uart_break_ctl,
  .startup=imx_uart_startup,
  .shutdown=imx_uart_shutdown,
  .flush_buffer=imx_uart_flush_buffer,
  .set_termios=imx_uart_set_termios, //对串口进行配置  .type=imx_uart_type,
  .config_port=imx_uart_config_port,
  .verify_port=imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL)  .poll_init=imx_uart_poll_init,
  .poll_get_char=imx_uart_poll_get_char,
  .poll_put_char=imx_uart_poll_put_char,
#endif};

image.gif

上面的操作函数都是对具体芯片(IMX)的寄存器进行配置。需要根据具体的芯片手册来进行实现。我们简单看几个函数。

    • imx_uart_set_termios --- 配置串口
    staticvoidimx_uart_set_termios(structuart_port*port, structktermios*termios,
    structktermios*old)
    {
    structimx_port*sport= (structimx_port*)port;
    unsignedlongflags;
    u32ucr2, old_ucr1, old_ucr2, ufcr;
    unsignedintbaud, quot;
    unsignedintold_csize=old?old->c_cflag&CSIZE : CS8;
    unsignedlongdiv;
    unsignedlongnum, denom;
    uint64_ttdiv64;
    //设置数据位while ((termios->c_cflag&CSIZE) !=CS7&&         (termios->c_cflag&CSIZE) !=CS8) {
    termios->c_cflag&=~CSIZE;
    termios->c_cflag|=old_csize;
    old_csize=CS8;
      }
    if ((termios->c_cflag&CSIZE) ==CS8)
    ucr2=UCR2_WS|UCR2_SRST|UCR2_IRTS;
    elseucr2=UCR2_SRST|UCR2_IRTS;
    //省略.....//设置停止位if (termios->c_cflag&CSTOPB)
    ucr2|=UCR2_STPB;
    if (termios->c_cflag&PARENB) {
    ucr2|=UCR2_PREN;
    if (termios->c_cflag&PARODD)
    ucr2|=UCR2_PROE;
      }
    del_timer_sync(&sport->timer);
    //设置波特率baud=uart_get_baud_rate(port, termios, old, 50, port->uartclk/16);
    quot=uart_get_divisor(port, baud);
    spin_lock_irqsave(&sport->port.lock, flags);
    //设置奇偶校验sport->port.read_status_mask=0;
    if (termios->c_iflag&INPCK)
    sport->port.read_status_mask|= (URXD_FRMERR|URXD_PRERR);
    if (termios->c_iflag& (BRKINT|PARMRK))
    sport->port.read_status_mask|=URXD_BRK;
    //省略.....//关闭中断old_ucr1=imx_uart_readl(sport, UCR1);
    imx_uart_writel(sport,
    old_ucr1&~(UCR1_TXMPTYEN|UCR1_RRDYEN|UCR1_RTSDEN),
    UCR1);
    old_ucr2=imx_uart_readl(sport, UCR2);
    imx_uart_writel(sport, old_ucr2&~UCR2_ATEN, UCR2);
    while (!(imx_uart_readl(sport, USR2) &USR2_TXDC))
    barrier();
    /* then, disable everything */imx_uart_writel(sport, old_ucr2&~(UCR2_TXEN|UCR2_RXEN|UCR2_ATEN), UCR2);
    old_ucr2&= (UCR2_TXEN|UCR2_RXEN|UCR2_ATEN);
    //计算波特率值div=sport->port.uartclk/ (baud*16);
    if (baud==38400&&quot!=div)
    baud=sport->port.uartclk/ (quot*16);
    div=sport->port.uartclk/ (baud*16);
    if (div>7)
    div=7;
    if (!div)
    div=1;
    rational_best_approximation(16*div*baud, sport->port.uartclk,
    1<<16, 1<<16, &num, &denom);
    tdiv64=sport->port.uartclk;
    tdiv64*=num;
    do_div(tdiv64, denom*16*div);
    tty_termios_encode_baud_rate(termios,
            (speed_t)tdiv64, (speed_t)tdiv64);
    num-=1;
    denom-=1;
    //对上面的设置写入到寄存器中ufcr=imx_uart_readl(sport, UFCR);
    ufcr= (ufcr& (~UFCR_RFDIV)) |UFCR_RFDIV_REG(div);
    imx_uart_writel(sport, ufcr, UFCR);
    imx_uart_writel(sport, num, UBIR);
    imx_uart_writel(sport, denom, UBMR);
    if (!imx_uart_is_imx1(sport))
    imx_uart_writel(sport, sport->port.uartclk/div/1000,
    IMX21_ONEMS);
    imx_uart_writel(sport, old_ucr1, UCR1);
    /* set the parity, stop bits and data size */imx_uart_writel(sport, ucr2|old_ucr2, UCR2);
    if (UART_ENABLE_MS(&sport->port, termios->c_cflag))
    imx_uart_enable_ms(&sport->port);
    spin_unlock_irqrestore(&sport->port.lock, flags);
    }

    image.gif

    应用层是通过struct termios来设置串口,传到底层就是struct ktermios。通过解析设置参数,然后配置对应的寄存器。

      • imx_uart_start_tx --- 串口发送
      staticvoidimx_uart_start_tx(structuart_port*port)
      {
      structimx_port*sport= (structimx_port*)port;
      u32ucr1;
      //判断是否有高优先级数据和环形buffer是否有数据if (!sport->port.x_char&&uart_circ_empty(&port->state->xmit))
      return;
      //省略......//没有开启DMA,则使用Tx中断if (!sport->dma_is_enabled) {
      //触发Tx中断ucr1=imx_uart_readl(sport, UCR1);
      imx_uart_writel(sport, ucr1|UCR1_TXMPTYEN, UCR1);
        }
      if (sport->dma_is_enabled) {
      if (sport->port.x_char) {
      //有高优先级的数据要发送,则使用Tx中断,关闭DMAucr1=imx_uart_readl(sport, UCR1);
      ucr1&=~UCR1_TXDMAEN;
      ucr1|=UCR1_TXMPTYEN;
      imx_uart_writel(sport, ucr1, UCR1);
      return;
          }
      //环形buffer有数据,并且串口没有停止,则使用DMA进行发送if (!uart_circ_empty(&port->state->xmit) &&!uart_tx_stopped(port))
      imx_uart_dma_tx(sport); //DMA发送return;
        }
      }

      image.gif

      使用Tx中断进行发送或DMA进行发送。

        • imx_uart_rxint --- Rx中断处理函数
        staticirqreturn_timx_uart_rxint(intirq, void*dev_id)
        {
        structimx_port*sport=dev_id;
        unsignedintrx, flg, ignored=0;
        structtty_port*port=&sport->port.state->port;
        spin_lock(&sport->port.lock);
        while (imx_uart_readl(sport, USR2) &USR2_RDR) {
        u32usr2;
        flg=TTY_NORMAL;
        sport->port.icount.rx++;
        rx=imx_uart_readl(sport, URXD0);
        usr2=imx_uart_readl(sport, USR2);
        if (usr2&USR2_BRCD) {
        imx_uart_writel(sport, USR2_BRCD, USR2);
        if (uart_handle_break(&sport->port))
        continue;
            }
        //省略......if (sport->port.ignore_status_mask&URXD_DUMMY_READ)
        gotoout;
        //添加到tty核心层if (tty_insert_flip_char(port, rx, flg) ==0)
        sport->port.icount.buf_overrun++;
          }
        out:
        spin_unlock(&sport->port.lock);
        tty_flip_buffer_push(port); //push给tty核心层returnIRQ_HANDLED;
        }

        image.gif

        接收中断就是将收到的数据发送给tty核心层,让它去进行缓存。


        总结

           上面芯片相关的可以跳着看,我们主要是去看Uart驱动的套路。学习驱动就是在学习套路,掌握了套路,它们就会变成模板了。可以和之前的《Linux驱动分析之Uart驱动架构》一起看。

        相关文章
        |
        3月前
        |
        存储 IDE Unix
        Linux 内核源代码情景分析(四)(上)
        Linux 内核源代码情景分析(四)
        26 1
        Linux 内核源代码情景分析(四)(上)
        |
        3月前
        |
        存储 Linux 块存储
        Linux 内核源代码情景分析(三)(下)
        Linux 内核源代码情景分析(三)
        32 4
        |
        3月前
        |
        Linux C语言
        深度探索Linux操作系统 —— 编译过程分析
        深度探索Linux操作系统 —— 编译过程分析
        26 2
        |
        3月前
        |
        存储 Unix Linux
        Linux 内核源代码情景分析(四)(下)
        Linux 内核源代码情景分析(四)
        23 2
        |
        2月前
        |
        存储 传感器 Linux
        STM32微控制器为何不适合运行Linux系统的分析
        总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
        191 0
        |
        3月前
        |
        存储 Unix Linux
        Linux 内核源代码情景分析(三)(上)
        Linux 内核源代码情景分析(三)
        31 1
        |
        2月前
        |
        Linux API
        Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
        Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
        |
        3月前
        |
        存储 算法 Unix
        Linux 内核源代码情景分析(四)(中)
        Linux 内核源代码情景分析(四)
        42 0
        |
        3月前
        |
        存储 算法 Unix
        Linux 内核源代码情景分析(三)(中)
        Linux 内核源代码情景分析(三)
        28 0
        |
        存储 Unix Linux
        浅入分析Linux
        Linux 操作系统必须完成的两个主要目的 与硬件部分交互, 为包含在硬件平台上的所有底层可编程部件提供服务 为运行在计算机系统上的应用程序(即所谓的用户空间)提供执行环境 一些操作系统运行所有的用户程序都直接与硬件部分进行交互, 比如典型的MS-DOS。
        997 0