Linux驅動分析之Uart驅動

喜歡就關注我們吧!

前言

    之前對Uart驅動的整體架構做了介紹,現在來分析具體的驅動程序。我們以NXP 的 IMX6來進行分析。

Uart驅動分析

內核:4.20

芯片:NXP IMX6

下面的代碼分析主要都在註釋中,會按照驅動中函數的執行順序分析。

//dts匹配表
static const struct of_device_id imx_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 */ }
};


static struct uart_driver imx_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,
};


static struct platform_driver imx_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,
  },
};
//加載函數
static int __init imx_uart_init(void)
{
    //註冊uart_driver
  int ret = uart_register_driver(&imx_uart_uart_driver);


  //註冊platform_driver
  ret = platform_driver_register(&imx_uart_platform_driver);
  return ret;
}
//卸載函數
static void __exit imx_uart_exit(void)
{
    //註銷uart_driver和platform_driver
  platform_driver_unregister(&imx_uart_platform_driver);
  uart_unregister_driver(&imx_uart_uart_driver);
}


module_init(imx_uart_init);
module_exit(imx_uart_exit);

上面真正回調probe的是匹配platform_driver, 而不是uart_driver。所以我們會看到調用了uart_register_driver 和 platform_driver_register

uart_register_driver是爲了向uart核心層註冊。

(2) probe()函數

static int imx_uart_probe(struct platform_device *pdev)
{
  struct imx_port *sport; //nxp對uart_port進行了封裝,添加自己的成員
  void __iomem *base;
  int ret = 0;
  u32 ucr1;
  struct resource *res;
  int txirq, rxirq, rtsirq;
    //分配內存,並清0
  sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
  if (!sport)
    return -ENOMEM;
    //解析設備樹,保存到imx_port
  ret = imx_uart_probe_dt(sport, pdev);
  if (ret > 0)
    imx_uart_probe_pdata(sport, pdev);
  else if (ret < 0)
    return ret;
    //省略....
    //獲取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))
    return PTR_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);
    //下面都是對寄存器的配置,可以查看datasheet
  ucr1 = 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) {


    u32 ufcr = 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 {
    u32 ucr3 = UCR3_DSR;
    u32 ufcr = 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_port
  imx_uart_ports[sport->port.line] = sport;
  platform_set_drvdata(pdev, sport);
   //關聯uart_driver和uart_port
  return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}

上面其實主要是寄存器配置,中斷申請,最後添加port。對裸機程序熟悉的,應該能很輕鬆的理解,因爲我們不是爲了針對某款芯片,所以寄存器配置可以忽略,主要還是爲了理解Uart的驅動框架。

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

static const struct uart_ops imx_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
};

上面的操作函數都是對具體芯片(IMX)的寄存器進行配置。需要根據具體的芯片手冊來進行實現。我們簡單看幾個函數。

  • imx_uart_set_termios --- 配置串口

static void
imx_uart_set_termios(struct uart_port *port, struct ktermios *termios,
         struct ktermios *old)
{
  struct imx_port *sport = (struct imx_port *)port;
  unsigned long flags;
  u32 ucr2, old_ucr1, old_ucr2, ufcr;
  unsigned int baud, quot;
  unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8;
  unsigned long div;
  unsigned long num, denom;
  uint64_t tdiv64;


    //設置數據位
  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;
  else
    ucr2 = 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);
}

應用層是通過struct termios來設置串口,傳到底層就是struct ktermios。通過解析設置參數,然後配置對應的寄存器。

  • imx_uart_start_tx --- 串口發送

static void imx_uart_start_tx(struct uart_port *port)
{
  struct imx_port *sport = (struct imx_port *)port;
  u32 ucr1;
    //判斷是否有高優先級數據和環形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中斷,關閉DMA
      ucr1 = 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;
  }
}

使用Tx中斷進行發送或DMA進行發送。

  • imx_uart_rxint --- Rx中斷處理函數

static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
{
  struct imx_port *sport = dev_id;
  unsigned int rx, flg, ignored = 0;
  struct tty_port *port = &sport->port.state->port;


  spin_lock(&sport->port.lock);


  while (imx_uart_readl(sport, USR2) & USR2_RDR) {
    u32 usr2;


    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)
      goto out;
    //添加到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核心層
  return IRQ_HANDLED;
}

接收中斷就是將收到的數據發送給tty核心層,讓它去進行緩存。

總結

    上面芯片相關的可以跳着看,我們主要是去看Uart驅動的套路。學習驅動就是在學習套路,掌握了套路,它們就會變成模板了。可以和之前的《Linux驅動分析之Uart驅動架構》一起看。

精彩推薦

Linux驅動分析之Uart驅動架構

如何提高C編程能力

必知必會的TCP/IP知識

好文!必須在看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章