喜欢就关注我们吧!
前言之前对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 voidimx_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知识好文!必须在看