前言
之前对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);
上面真正回调probe的是匹配platform_driver, 而不是uart_driver。所以我们会看到调用了uart_register_driver 和 platform_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); }
上面其实主要是寄存器配置,中断申请,最后添加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, .poll_init=imx_uart_poll_init, .poll_get_char=imx_uart_poll_get_char, .poll_put_char=imx_uart_poll_put_char, };
上面的操作函数都是对具体芯片(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&"!=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 --- 串口发送
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; } }
使用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; }
接收中断就是将收到的数据发送给tty核心层,让它去进行缓存。
总结
上面芯片相关的可以跳着看,我们主要是去看Uart驱动的套路。学习驱动就是在学习套路,掌握了套路,它们就会变成模板了。可以和之前的《Linux驱动分析之Uart驱动架构》一起看。