[Linux驱动篇]Uart驱动分析

前言

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

Uart驱动分析

内核:4.20

芯片:NXP IMX6

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

(1) 装载和卸载函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//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()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 — 配置串口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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 — 串口发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void imx_uart_start_tx(struct uart_port *port)
{
  struct imx_port *sport = (struct imx_port *)port;
  u32 ucr1;
    //判断是否有高优先级数据和环形buffer是否有数据
  if (!sport-&gt;port.x_char &amp;&amp; uart_circ_empty(&amp;port-&gt;state-&gt;xmit))
    return;
    //省略......
    //没有开启DMA,则使用Tx中断
  if (!sport-&gt;dma_is_enabled) {
            //触发Tx中断
    ucr1 = imx_uart_readl(sport, UCR1);
    imx_uart_writel(sport, ucr1 | UCR1_TXMPTYEN, UCR1);
  }
 
 
  if (sport-&gt;dma_is_enabled) {
    if (sport-&gt;port.x_char) {
      //有高优先级的数据要发送,则使用Tx中断,关闭DMA
      ucr1 = imx_uart_readl(sport, UCR1);
      ucr1 &amp;= ~UCR1_TXDMAEN;
      ucr1 |= UCR1_TXMPTYEN;
      imx_uart_writel(sport, ucr1, UCR1);
      return;
    }
    //环形buffer有数据,并且串口没有停止,则使用DMA进行发送
    if (!uart_circ_empty(&amp;port-&gt;state-&gt;xmit) &amp;&amp;
        !uart_tx_stopped(port))
      imx_uart_dma_tx(sport); //DMA发送
    return;
  }
}

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

  • imx_uart_rxint — Rx中断处理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 = &amp;sport-&gt;port.state-&gt;port;
 
 
  spin_lock(&amp;sport-&gt;port.lock);
 
 
  while (imx_uart_readl(sport, USR2) &amp; USR2_RDR) {
    u32 usr2;
 
 
    flg = TTY_NORMAL;
    sport-&gt;port.icount.rx++;
 
 
    rx = imx_uart_readl(sport, URXD0);
 
 
    usr2 = imx_uart_readl(sport, USR2);
    if (usr2 &amp; USR2_BRCD) {
      imx_uart_writel(sport, USR2_BRCD, USR2);
      if (uart_handle_break(&amp;sport-&gt;port))
        continue;
    }
 
 
            //省略......
 
 
    if (sport-&gt;port.ignore_status_mask &amp; URXD_DUMMY_READ)
      goto out;
    //添加到tty核心层
    if (tty_insert_flip_char(port, rx, flg) == 0)
      sport-&gt;port.icount.buf_overrun++;
  }
 
 
out:
  spin_unlock(&amp;sport-&gt;port.lock);
  tty_flip_buffer_push(port); //push给tty核心层
  return IRQ_HANDLED;
}

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

总结

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