Linux驱动.之 tty uart串口驱动框架(一)

原文链接:https://blog.youkuaiyun.com/weixin_55796564/article/details/122639335

目录
1、裸机开发
2、tty驱动框架

一、裸机开发
在这里插入图片描述
串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合。

16.1 UART串口简介
串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。
UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图 7.5.13.1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。
在这里插入图片描述
UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认),1.5或2位。串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒),常用的波特率有9600、19200、38400、57600以及115200等。

在设置好数据格式及传输速率之后,UART负责完成数据的串并转换,而信号的传输则由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有RS232、RS422、RS485等,它们定义了接口不同的电气特性,如RS-232是单端输入输出,而RS-422/485为差分输入输出等。
RS232接口标准出现较早,可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准,本章主要介绍针对RS-232标准的UART串口通信。
RS-232标准的串口最常见的接口类型为DB9,样式如图 7.5.13.2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过USB转串口线(图 7.5.13.3)来实现与外部设备的串口通信。
在这里插入图片描述
DB9接口定义以及各引脚功能说明如图 7.5.13.4所示,我们一般只用到其中的2(RXD)、3(TXD)、5(GND)引脚,其他引脚在普通串口模式下一般不使用,如果大家想了解,可以自行百度下。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
串口是很常用的一个外设,在Linux下通常通过串口和其他设备或传感器进行通信,根据电平的不同,串口分为TTL和RS232。不管是什么样的接口电平,其驱动程序都是一样的,通过外接RS485这样的芯片就可以将串口转换为RS485信号,正点原子的I.MX6U-ALPHA开发板就是这么做的。对于正点原子的I.MX6U-ALPHA开发板而言, RS232、RS485以及GPS模块接口通通连接到了I.MX6U的UART3接口上,因此这些外设最终都归结为UART3的串口驱动。本章我们就来学习一下如何驱动I.MX6U-ALPHA开发板上的UART3串口,进而实现RS232、RS485以及GSP驱动。

63.1 Linux下UART驱动框架
1、uart_driver注册与注销
同I2C、SPI一样,Linux也提供了串口驱动框架,我们只需要按照相应的串口框架编写驱动程序即可。串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,而且这个驱动也已经由NXP官方已经编写好了,我们真正要做的就是在设备树中添加所要使用的串口节点信息。当系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttymxcX(X=0….n)文件。
虽然串口驱动不需要我们去写,但是串口驱动框架我们还是需要了解的,uart_driver结构体表示UART驱动,uart_driver定义在include/linux/serial_core.h文件中,内容如下:

示例代码63.1.1 uart_driver结构体
295 struct uart_driver {
296     struct module		*owner;    			/* 模块所属者 	*/
297     const char    		*driver_name;   	/* 驱动名字 		*/
298     const char      	*dev_name;      	/* 设备名字 		*/
299     int          		major;             	/* 主设备号 		*/
300     int          		minor;             	/* 次设备号 		*/
301     int          		nr;               	/* 设备数 		*/
302     struct console  	*cons;      		/* 控制台 		*/
303 
304     /*
305      * these are private; the low level driver should not
306      * touch these; they should be initialised to NULL
307      */
308     struct uart_state   *state;     
309     struct tty_driver   *tty_driver;
310 };

每个串口驱动都需要定义一个uart_driver,加载驱动的时候通过uart_register_driver函数向系统注册这个uart_driver,此函数原型如下:

2、uart_port的添加与移除
uart_port表示一个具体的port,uart_port定义在include/linux/serial_core.h文件,内容如下(有省略):

示例代码63.1.2 uart_port结构体
117 struct uart_port {
118     spinlock_t      	lock;         		/* port lock 			*/
119     unsigned long    	iobase;         	/* in/out[bwl] 		*/
120     unsigned char __iomem   *membase; 	/* read/write[bwl] 	*/
......
235     const struct uart_ops   *ops;
236     unsigned int        custom_divisor;
237     unsigned int        line;           	/* port index 			*/
238     unsigned int        minor;
239     resource_size_t     mapbase;        	/* for ioremap 		*/
240     resource_size_t     mapsize;
241     struct device       *dev;           	/* parent device 		*/
......
250 };

uart_port中最主要的就是第235行的ops,ops包含了串口的具体驱动函数,这个我们稍后再看。每个UART都有一个uart_port,那么uart_port是怎么和uart_driver结合起来的呢?这里要用到uart_add_one_port函数,函数原型如下:

nt uart_add_one_port(struct uart_driver *drv,struct uart_port *uport)

函数参数和返回值含义如下:
drv:此port对应的uart_driver。
uport:要添加到uart_driver中的port。
返回值:0,成功;负值,失败。

卸载UART驱动的时候也需要将uart_port从相应的uart_driver中移除,需要用到uart_remove_one_port函数,
函数原型如下:

int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

函数参数和返回值含义如下:
drv:要卸载的port所对应的uart_driver。
uport:要卸载的uart_port。
返回值:0,成功;负值,失败。

3、uart_ops实现
在上面讲解uart_port的时候说过,uart_port中的ops成员变量很重要,因为ops包含了针对UART具体的驱动函数,Linux系统收发数据最终调用的都是ops中的函数。ops是uart_ops类型的结构体指针变量,uart_ops定义在include/linux/serial_core.h文件中,内容如下:

示例代码63.1.3 uart_ops结构体
49 struct uart_ops {
50  unsigned int    (*tx_empty)(struct uart_port *);
51  void        (*set_mctrl)(struct uart_port *, unsigned int mctrl);
52  unsigned int    (*get_mctrl)(struct uart_port *);
53  void        (*stop_tx)(struct uart_port *);
54  void        (*start_tx)(struct uart_port *);
55  void        (*throttle)(struct uart_port *);
56  void        (*unthrottle)(struct uart_port *);
57  void        (*send_xchar)(struct uart_port *, char ch);
58  void        (*stop_rx)(struct uart_port *);
59  void        (*enable_ms)(struct uart_port *);
60  void        (*break_ctl)(struct uart_port *, int ctl);
61  int     	(*startup)(struct uart_port *);
62  void        (*shutdown)(struct uart_port *);
63  void        (*flush_buffer)(struct uart_port *);
64  void        (*set_termios)(struct uart_port *, struct ktermios *new,
65                     struct ktermios *old);
66  void        (*set_ldisc)(struct uart_port *, struct ktermios *);
67  void        (*pm)(struct uart_port *, unsigned int state,
68                unsigned int oldstate);
69 
70  /*
71   * Return a string describing the type of the port
72   */
73  const char  *(*type)(struct uart_port *);
74 
75  /*
76   * Release IO and memory resources used by the port.
77   * This includes iounmap if necessary.
78   */
79  void        (*release_port)(struct uart_port *);
80 
81  /*
82   * Request IO and memory resources used by the port.
83   * This includes iomapping the port if necessary.
84   */
85  int     (*request_port)(struct uart_port *);
86  void        (*config_port)(struct uart_port *, int);
87  int     (*verify_port)(struct uart_port *, struct serial_struct *);
88  int     (*ioctl)(struct uart_port *, unsigned int, unsigned long);
89 #ifdef CONFIG_CONSOLE_POLL
90  int     (*poll_init)(struct uart_port *);
91  void        (*poll_put_char)(struct uart_port *, unsigned char);
92  int     (*poll_get_char)(struct uart_port *);
93 #endif
94 };

UART驱动编写人员需要实现uart_ops,因为uart_ops是最底层的UART驱动接口,是实实在在的和UART寄存器打交道的。关于uart_ops结构体中的这些函数的具体含义请参考Documentation/serial/driver这个文档。
UART驱动框架大概就是这些,接下来我们理论联系实际,看一下NXP官方的UART驱动文件是如何编写的。

63.2 I.MX6U UART驱动分析

1、UART的platform驱动框架
打开imx6ull.dtsi文件,找到UART3对应的子节点,子节点内容如下所示:

示例代码63.2.1 uart3设备节点
1  uart3: serial@021ec000 {
2        compatible = "fsl,imx6ul-uart",
3                 "fsl,imx6q-uart", "fsl,imx21-uart";
4        reg = <0x021ec000 0x4000>;
5        interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
6        clocks = <&clks IMX6UL_CLK_UART3_IPG>,
7             <&clks IMX6UL_CLK_UART3_SERIAL>;
8        clock-names = "ipg", "per";
9        dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
10       dma-names = "rx", "tx";
11       status = "disabled";
12 };                 

重点看一下第2,3行的compatible属性,这里一共有三个值:“fsl,imx6ul-uart”、“fsl,imx6q-uar”和“fsl,imx21-uart”。在linux源码中搜索这三个值即可找到对应的UART驱动文件,此文件为drivers/tty/serial/imx.c,在此文件中可以找到如下内容:

示例代码63.2.2 UART platform驱动框架
267 static struct platform_device_id imx_uart_devtype[] = {
268     {
269         .name = "imx1-uart",
270         .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX1_UART],
271     }, {
272         .name = "imx21-uart",
273         .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX21_UART],
274     }, {
275         .name = "imx6q-uart",
276         .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX6Q_UART],
277     }, {
278         /* sentinel */
279     }
280 };
281 MODULE_DEVICE_TABLE(platform, imx_uart_devtype);
282 
283 static const struct of_device_id imx_uart_dt_ids[] = {
284     { .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
285     { .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
286     { .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
287     { /* sentinel */ }
288 };
......
2071 static struct platform_driver serial_imx_driver = {
2072    .probe    	= serial_imx_probe,
2073    .remove  	= serial_imx_remove,
2074 
2075    .suspend 	= serial_imx_suspend,
2076    .resume  	= serial_imx_resume,
2077    .id_table	= imx_uart_devtype,
2078    .driver   	= {
2079        .name 	= "imx-uart",
2080        .of_match_table = imx_uart_dt_ids,
2081    },
2082 };
2083 
2084 static int __init imx_serial_init(void)
2085 {
2086    int ret = uart_register_driver(&imx_reg);
2087 
2088    if (ret)
2089        return ret;
2090 
2091    ret = platform_driver_register(&serial_imx_driver);
2092    if (ret != 0)
2093        uart_unregister_driver(&imx_reg);
2094 
2095    return ret;
2096 }
2097 
2098 static void __exit imx_serial_exit(void)
2099 {
2100    platform_driver_unregister(&serial_imx_driver);
2101    uart_unregister_driver(&imx_reg);
2102 }
2103 
2104 module_init(imx_serial_init);
2105 module_exit(imx_serial_exit);          

可以看出I.MX6U的UART本质上是一个platform驱动,第267~280行,imx_uart_devtype为传统匹配表。
第283~288行,设备树所使用的匹配表,第284行的compatible属性值为“fsl,imx6q-uart”。
第2071~2082行,platform驱动框架结构体serial_imx_driver。
第2084~2096行,驱动入口函数,第2086行调用uart_register_driver函数向Linux内核注册uart_driver,在这里就是imx_reg。
第2098~2102行,驱动出口函数,第2101行调用uart_unregister_driver函数注销掉前面注册的uart_driver,也就是imx_reg。

2、uart_driver初始化
在imx_serial_init函数中向Linux内核注册了imx_reg,imx_reg就是uart_driver类型的结构体变量,imx_reg定义如下:

示例代码63.2.3 imx_reg结构体变量
1836 static struct uart_driver imx_reg = {
1837    .owner          	= THIS_MODULE,
1838    .driver_name    	= DRIVER_NAME,
1839    .dev_name       	= DEV_NAME,
1840    .major          	= SERIAL_IMX_MAJOR,
1841    .minor          	= MINOR_START,
1842    .nr             	= ARRAY_SIZE(imx_ports),
1843    .cons           	= IMX_CONSOLE,
1844 };        

3、uart_port初始化与添加
当UART设备和驱动匹配成功以后serial_imx_probe函数就会执行,此函数的重点工作就是初始化uart_port,然后将其添加到对应的uart_driver中。在看serial_imx_probe函数之前先来看一下imx_port结构体,imx_port是NXP为I.MX系列SOC定义的一个设备结构体,此结构体内部就包含了uart_port成员变量,imx_port结构体内容如下所示(有缩减):

示例代码63.2.4 imx_port结构体
216 struct imx_port {
217     struct uart_port  	port;
218     struct timer_list  	timer;
219     unsigned int        	old_status;
220     unsigned int        	have_rtscts:1;
221     unsigned int        	dte_mode:1;
222     unsigned int        	irda_inv_rx:1;
223     unsigned int        	irda_inv_tx:1;
224     unsigned short      	trcv_delay; /* transceiver delay */
243     unsigned long       	flags;

第217行,uart_port成员变量port。

接下来看一下serial_imx_probe函数,函数内容如下:
示例代码63.2.5 serial_imx_probe函数

1969 static int serial_imx_probe(struct platform_device *pdev)
1970 {
1971    struct imx_port *sport;
1972    void __iomem *base;
1973    int ret = 0;
1974    struct resource *res;
1975    int txirq, rxirq, rtsirq;
1976 
1977    sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
1978    if (!sport)
1979        return -ENOMEM;
1980 
1981    ret = serial_imx_probe_dt(sport, pdev);
1982    if (ret > 0)
1983        serial_imx_probe_pdata(sport, pdev);
1984    else if (ret < 0)
1985        return ret;
1986 
1987    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1988    base = devm_ioremap_resource(&pdev->dev, res);
1989    if (IS_ERR(base))
1990        return PTR_ERR(base);
1991 
1992    rxirq = platform_get_irq(pdev, 0);
1993    txirq = platform_get_irq(pdev, 1);
1994    rtsirq = platform_get_irq(pdev, 2);
1995 
1996    sport->port.dev = &pdev->dev;
1997    sport->port.mapbase = res->start;
1998    sport->port.membase = base;
1999    sport->port.type = PORT_IMX,
2000    sport->port.iotype = UPIO_MEM;
2001    sport->port.irq = rxirq;
2002    sport->port.fifosize = 32;
2003    sport->port.ops = &imx_pops;
2004    sport->port.rs485_config = imx_rs485_config;
2005    sport->port.rs485.flags =
2006        SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
2007    sport->port.flags = UPF_BOOT_AUTOCONF;
2008    init_timer(&sport->timer);
2009    sport->timer.function = imx_timeout;
2010    sport->timer.data     = (unsigned long)sport;
2011 
2012    sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
2013    if (IS_ERR(sport->clk_ipg)) {
2014        ret = PTR_ERR(sport->clk_ipg);
2015        dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);
2016        return ret;
2017    }
2018 
2019    sport->clk_per = devm_clk_get(&pdev->dev, "per");
2020    if (IS_ERR(sport->clk_per)) {
2021        ret = PTR_ERR(sport->clk_per);
2022        dev_err(&pdev->dev, "failed to get per clk: %d\n", ret);
2023        return ret;
2024    }
2025 
2026    sport->port.uartclk = clk_get_rate(sport->clk_per);
2027    if (sport->port.uartclk > IMX_MODULE_MAX_CLK_RATE) {
2028        ret = clk_set_rate(sport->clk_per, IMX_MODULE_MAX_CLK_RATE);
2029        if (ret < 0) {
2030            dev_err(&pdev->dev, "clk_set_rate() failed\n");
2031            return ret;
2032        }
2033    }
2034    sport->port.uartclk = clk_get_rate(sport->clk_per);
2035 
2036    /*
2037     * Allocate the IRQ(s) i.MX1 has three interrupts whereas later
2038     * chips only have one interrupt.
2039     */
2040    if (txirq > 0) {
2041        ret = devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,
2042                       dev_name(&pdev->dev), sport);
2043        if (ret)
2044            return ret;
2045 
2046        ret = devm_request_irq(&pdev->dev, txirq, imx_txint, 0,
2047                       dev_name(&pdev->dev), sport);
2048        if (ret)
2049            return ret;
2050    } else {
2051        ret = devm_request_irq(&pdev->dev, rxirq, imx_int, 0,
2052                       dev_name(&pdev->dev), sport);
2053        if (ret)
2054            return ret;
2055    }
2056 
2057    imx_ports[sport->port.line] = sport;
2058 
2059    platform_set_drvdata(pdev, sport);
2060 
2061    return uart_add_one_port(&imx_reg, &sport->port);
2062 }

第1971行,定义一个imx_port类型的结构体指针变量sport。
第1977行,为sport申请内存。
第1987~1988行,从设备树中获取I.MX系列SOC UART外设寄存器首地址,对于I.MX6ULL的UART3来说就是0X021EC000。得到寄存器首地址以后对其进行内存映射,得到对应的虚拟地址。
第1992~1994行,获取中断信息。
第1996~2034行,初始化sport,我们重点关注的就是第2003行初始化sport的port成员变量,也就是设置uart_ops为imx_pops,imx_pops就是I.MX6ULL最底层的驱动函数集合,稍后再来看。
第2040~2055行,申请中断。
第2061行,使用uart_add_one_port向uart_driver添加uart_port,在这里就是向imx_reg添加sport->port。

4、imx_pops结构体变量
imx_pops就是uart_ops类型的结构体变量,保存了I.MX6ULL串口最底层的操作函数,imx_pops定义如下:

示例代码63.2.6 imx_pops结构体
1611 static struct uart_ops imx_pops = {
1612    .tx_empty   		= imx_tx_empty,
1613    .set_mctrl  		= imx_set_mctrl,
1614    .get_mctrl  		= imx_get_mctrl,
1615    .stop_tx    		= imx_stop_tx,
1616    .start_tx   		= imx_start_tx,
1617    .stop_rx    		= imx_stop_rx,
1618    .enable_ms  		= imx_enable_ms,
1619    .break_ctl  		= imx_break_ctl,
1620    .startup    		= imx_startup,
1621    .shutdown   		= imx_shutdown,
1622    .flush_buffer		= imx_flush_buffer,
1623    .set_termios 		= imx_set_termios,
1624    .type       		= imx_type,
1625    .config_port 		= imx_config_port,
1626    .verify_port 		= imx_verify_port,
1627 #if defined(CONFIG_CONSOLE_POLL)
1628    .poll_init   		= imx_poll_init,
1629    .poll_get_char  	= imx_poll_get_char,
1630    .poll_put_char  	= imx_poll_put_char,
1631 #endif
1632 };

imx_pops中的函数基本都是和I.MX6ULL的UART寄存器打交道的,这里就不去详细的分析了。简单的了解了I.MX6U的UART驱动以后我们再来学习一下,如何驱动正点原子I.MX6U-ALPHA开发板上的UART3接口。

63.3 硬件原理图分析
本实验要用到的I.MX6U的UART3接口,I.MX6U-ALPHA开发板上RS232、RS485和GPS这三个接口都连接到了UART3上,我们依次来看一下这三个模块的原理图。
1、RS232原理图
RS232原理图如图63.3.1所示:
在这里插入图片描述
从图63.3.1可以看出,RS232电平通过SP3232这个芯片来实现,RS232连接到了I.MX6U的UART3接口上,但是要通过JP1这个跳线帽设置。把JP1的1-3和2-4连接起来以后SP3232就和UART3连接到了一起。
2、RS485原理图
在这里插入图片描述
RS485采用SP3485这颗芯片来实现,RO为数据输出端,RI为数据输入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)。在图63.3.2中RE和DE经过一系列的电路,最终通过RS485_RX来控制,这样我们可以省掉一个RS485收发控制IO,将RS485完全当作一个串口来使用,方便我们写驱动。

3、GPS原理图
正点原子有一款GPS+北斗定位模块,型号为ATK1218-BD,I.MX6U-ALPHA开发板留出了这款GPS定位模块的接口,接口原理图如图63.3.3所示:
在这里插入图片描述
从图63.3.3可以看出,GPS模块用的也是UART3,因此UART3驱动成功以后就可以直接读取GPS模块数据了。

63.4 RS232驱动编写
前面我们已经说过了,I.MX6U的UART驱动NXP已经编写好了,所以不需要我们编写。我们要做的就是在设备树中添加UART3对应的设备节点即可。打开imx6ull-alientek-emmc.dts文件,在此文件中只有UART1对应的uart1节点,并没有UART3对应的节点,因此我们可以参考uart1节点创建uart3节点。
1、UART3 IO节点创建
UART3用到了UART3_TXD和UART3_RXD这两个IO,因此要先在iomuxc中创建UART3对应的pinctrl子节点,在iomuxc中添加如下内容:
设置设备树,管脚复用

示例代码63.4.1 UART3引脚pinctrl节点
1 pinctrl_uart3: uart3grp {
2     fsl,pins = <
3         MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX       0X1b0b1
4         MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX       0X1b0b1
5     >;
6 };

最后检查一下UART3_TX和UART3_RX这两个引脚有没有被用作其他功能,如果有的话要将其屏蔽掉,保证这两个IO只用作UART3,切记!!!

2、添加uart3节点
默认情况下imx6ull-alientek-emmc.dts中只有uart1和uart2这两个节点,如图63.4.1所示:
在这里插入图片描述
uart1是UART1的,在正点原子的I.MX6U-ALPHA开发板上没有用到UART2,而且UART2默认用到了UART3的IO,因此需要将uart2这个节点删除掉,然后加上UART3对应的uart3,uart3节点内容如下:

示例代码63.4.2 UART3对应的uart3节点

1 &uart3 {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_uart3>;
4     status = "okay";
5 };  

完成以后重新编译设备树并使用新的设备树启动Linux,如果设备树修改成功的话,系统启动以后就会生成一个名为“/dev/ttymxc2”的设备文件,ttymxc2就是UART3对应的设备文件,应用程序可以通过访问ttymxc2来实现对UART3的操作。

63.5 移植minicom

minicom类似我们常用的串口调试助手,是Linux下很常用的一个串口工具,将minicom移植到我们的开发板中,这样我们就可以借助minicom对串口进行读写操作。

1、移植ncurses
minicom需要用到ncurses,依次需要先移植ncurses,如果前面已经移植好了ncurses,那么这里就不需要再次移植了,只需要在编译minicom的时候指定ncurses库和头文件目录 即可。
首先在ubuntu中创建一个目录来存放我们要移植的文件,比如我在/home/zuozhongkai/linux/IMX6ULL目录下创建了一个名为“tool”的目录来存放所有的移植文件。然后下载ncurses源码,我们已经将ncurses源码放到了开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》ncurses-6.0.tar.gz,将ncurses-6.0.tar.gz拷贝到Ubuntu中创建的tool目录下,然后进行解压,解压命令如下:
tar -vxzf ncurses-6.0.tar.gz
解压完成以后就会生成一个名为“ncurses-6.0”的文件夹,此文件夹就是ncurese的源码文件夹。在tool目录下新建名为“ncurses”目录,用于保存ncurses编译结果,一切准备就绪以后就可以编译ncureses库了。进入到ncureses源码目录下,也就是刚刚解压出来的ncurses-6.0目录中,首先是配置ncureses,输入如下命令:
./configure --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/ncurses --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf --with-shared --without-profile --disable-stripping --without-progs --with-manpages --without-tests
configure就是配置脚本,–prefix用于指定编译结果的保存目录,这里肯定将编译结果保存到我们前面创建的“ncurses”目录中。–host用于指定编译器前缀,这里设置为 “arm-linux-gnueabihf”,–target用于指定目标,这里也设置为“arm-linux-gnueabihf”。配置命令写好以后点击回车键,等待配置完成,配置成功以后如图63.5.1所示:
在这里插入图片描述
图63.5.1 配置成功
配置成功以后输入“make”命令开始编译,编译成功以后如图63.5.2所示:
在这里插入图片描述
图63.5.2 编译成功
编译成功以后输入“make install”命令安装,安装的意思就是将编译出来的结果拷贝到–pfefix指定的目录里面去。安装成功以后如图63.5.3所示:
在这里插入图片描述
图63.5.3 安装成功
安装成功以后查看一下前面创建的“ncurses”文件夹,会发现里面多了一些东西,如图63.5.4所示:
在这里插入图片描述
图63.5.4 编译出来的结果
我们需要将图63.5.4中include、lib和share这三个目录中存放的文件分别拷贝到开发板根文件系统中的/usr/include、/usr/lib和/usr/share这三个目录中,如果哪个目录不存在的话请自行创建!!拷贝命令如下:
sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -rfa
sudo cp share/* /home/zuozhongkai/linux/nfs/rootfs/usr/share/ -rfa
sudo cp include/* /home/zuozhongkai/linux/nfs/rootfs/usr/include/ -rfa
然后在开发板根目录的/etc/profile(没有的话自己创建一个)文件中添加如下所示内容:

示例代码63.5.1 /etc/profile文件
1 #!/bin/sh
2 LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
3 export LD_LIBRARY_PATH
4 
5 export TERM=vt100
6 export TERMINFO=/usr/share/terminfo

2、移植minicom
继续移植minicom,获取minicom源码,我们已经放到了开发板光盘中了,路径为:1、例程源码-》7、第三方库源码-》minicom-2.7.1.tar.gz。将minicom-2.7.1.tar.gz拷贝到ubuntu中的/home/zuozhongkai/linux/IMX6ULL/tool目录下,然后在tool目录下新建一个名为“minicom”的子目录,用于存放minicom编译结果。一切准备好以后就可以编译minicom了,先解压minicom,命令如下:

tar -vxzf minicom-2.7.1.tar.gz
解压完成以后会生成一个叫做minicom-2.7.1的文件夹,这个就是minicom的源码,进入到此目录中,然后配置minicom,配置命令如下:
cd minicom-2.7.1/ //进入minicom源码目录
./configure CC=arm-linux-gnueabihf-gcc --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/
minicom --host=arm-linux-gnueabihf CPPFLAGS=-I/home/zuozhongkai/linux/IMX6ULL/tool/
ncurses/include LDFLAGS=-L/home/zuozhongkai/linux/IMX6ULL/tool/ncurses/lib -enable-cfg-dir=/etc/minicom //配置
CC表示要使用的gcc交叉编译器,–prefix指定编译出来的文件存放目录,肯定要存放到我们前面创建的minicom目录中。–host指定交叉编译器前缀,CPPFLAGS指定ncurses的头文件路径,LDFLAGS指定ncurses的库路径。
配置成功的话如图63.5.5所示:

配置成功以后执行如下命令编译并安装:
make
make install
编译安装完成以后,前面创建的minicom目录内容如图63.5.6所示:
在这里插入图片描述
图63.5.6 minicom安装编译结果
将minicom目录中bin子目录下的所有文件拷贝到开发板根目录中的/usr/bin目录下,命令如下:
sudo cp bin/* /home/zuozhongkai/linux/nfs/rootfs/usr/bin/
完成以后在开发板中输入“minicom -v”来查看minicom工作是否正常,结果如图63.5.7所示:
在这里插入图片描述
63.6.2 minicom设置
在开发板中输入“minicom -s”,打开minicom配置界面,然后选中“Serial port setup”,如图63.6.2.1所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
设置完以后按下回车键确认,确认完以后就可以设置其他的配置项。比如E设置波特率、数据位和停止位的、F设置硬件流控的,设置方法都一样,设置完以后如图63.6.2.4所示:
在这里插入图片描述

设置好以后就可以静静的等待GPS数据输出,GPS模块第一次启动可能需要几分钟搜星,等搜到卫星以后才会有定位数据输出。搜到卫星以后GPS模块输出的定位数据如图63.8.2.2所示:

app测试,在后面第二篇应用中,讲了,如何应用层,调用tty下的设备。
总结,tty驱动框架,只需要打开配置dts,生成设备节点,app层,调用read,write,进行配置外挂设备寄存器,读写数据即可。
和单片机类似,只需关心bufer缓存器是否有数据,底层内部soc驱动,由芯片商写好了。

下面是工作中,tty设备

OS_ARGS_CONSOLE :=console=ttyS2,115200n8
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

ttys2是串口控制台,ttyths1 是串口与mcu接口,imu
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

hardware/nvidia/soc/t23x/kernel-dts/tegra234-soc/tegra234-soc-uart.dtsi

tegra234-soc-uart.dtsi: Tegra234 soc dtsi file for UART instances

/ {
	aliases {
		serial0 = &uarta;
		serial1 = &uartb;
		serial2 = &uartc;
		serial3 = &uartd;
		serial4 = &uarte;
		serial5 = &uartf;
		serial7 = &uarth;
	};

	uarta: serial@3100000 {
		compatible = "nvidia,tegra194-hsuart";
		iommus = <&smmu_niso0 TEGRA_SID_NISO0_GPCDMA_0>;
		dma-coherent;
		reg = <0x0 0x03100000 0x0 0x10000>;
		reg-shift = <2>;
		interrupts = <0 TEGRA234_IRQ_UARTA 0x04>;
		nvidia,memory-clients = <14>;
		dmas = <&gpcdma 8>, <&gpcdma 8>;
		dma-names = "rx", "tx";
		clocks = <&bpmp_clks TEGRA234_CLK_UARTA>,
			<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
		clock-names = "serial", "parent";
		resets = <&bpmp_resets TEGRA234_RESET_UARTA>;
		reset-names = "serial";
		status = "disabled";
	};

	uartb: serial@3110000 {
		compatible = "nvidia,tegra194-hsuart";
		iommus = <&smmu_niso0 TEGRA_SID_NISO0_GPCDMA_0>;
		dma-coherent;
		reg = <0x0 0x03110000 0x0 0x10000>;
		reg-shift = <2>;
		interrupts = <0 TEGRA234_IRQ_UARTB 0x04>;
		nvidia,memory-clients = <14>;
		dmas = <&gpcdma 9>, <&gpcdma 9>;
		dma-names = "rx", "tx";
		clocks = <&bpmp_clks TEGRA234_CLK_UARTB>,
			<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
		clock-names = "serial", "parent";
		resets = <&bpmp_resets TEGRA234_RESET_UARTB>;
		reset-names = "serial";
		status = "disabled";
	};

	uartc: serial@c280000 {
		compatible = "nvidia,tegra194-hsuart";
		iommus = <&smmu_niso0 TEGRA_SID_NISO0_GPCDMA_0>;
		dma-coherent;
		reg = <0x0 0xc280000 0x0 0x10000>;
		reg-shift = <2>;
		interrupts = <0 TEGRA234_IRQ_UARTC 0x04>;
		nvidia,memory-clients = <14>;
		dmas = <&gpcdma 3>, <&gpcdma 3>;
		dma-names = "rx", "tx";
		clocks = <&bpmp_clks TEGRA234_CLK_UARTC>,
			<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
		clock-names = "serial", "parent";
		resets = <&bpmp_resets TEGRA234_RESET_UARTC>;
		reset-names = "serial";
		status = "disabled";
	};

hardware/nvidia/platform/t23x/automotive/kernel-dts/common/pct/tegra234-linux-linux-gos0.dtsi

	serial@c280000 {
		compatible = "nvidia,tegra20-uart";
		status = "okay";
		clock-frequency = <408000000>;
		/delete-property/ iommus;
		/delete-property/ dma-coherent;
		/delete-property/ nvidia,memory-clients;
		/delete-property/ dmas;
		/delete-property/ dma-names;
		/delete-property/ resets;
		/delete-property/ reset-names;
};

zeekr_cs1e/host/drive-linux_src/kernel/drive-linux/kernel/source/hardware/nvidia/platform/t23x/automotive/kernel-dts/p3663/common/tegra234-p3663-0001-linux-gos.dtsi

#include <dt-bindings/gpio/tegra234-gpio.h>
#include <dt-bindings/input/input.h>
#ifdef USE_FPGA_DTS
#include "../../../../sim/kernel-dts/tegra234-sim-fpga-vm1.dts"
#else
#include "tegra234-p3663-0001.dtsi"
#endif
#include "../../common/linux/tegra234-linux-gos.dtsi"
#include "../../common/linux/storage/tegra234-common-storage-linux-gos.dtsi"
#include "tegra234-usb-host-p3663-0001.dtsi"
#include "tegra234-p3663-0001-disable-nodes.dtsi"
#include "../../common/pct/tegra234-driveav-gos0.dtsi"
#include "tegra234-p3663-0001-desay.dtsi"

/ {
	/* I2C1: gen1_i2c */
	i2c@3160000 {
		status = "okay";
		MAX20087_0@28{
			compatible = "maxim,max20087_0";
			//interrupt-parent = <&tegra_main_gpio>;
			//interrupts = <TEGRA234_MAIN_GPIO(AD, 2) 1>; /* GMSLA_CAM_INT: rising edge sensitive */   /* ERROR: GP171 */
			status = "okay";
			reg = <0x28>;
		};
	};

	/* I2C2: gen2_i2c */
	i2c@c240000 {
		clock-frequency = <400000>;
		status = "okay";

		gpio_i2c_2_74: gpio@74 {
			compatible = "ti,tca9539";
			status = "okay";
			reg = <0x74>;
			gpio-controller;
			#gpio-cells = <2>;
			reset-gpios = <&tegra_main_gpio TEGRA234_MAIN_GPIO(Q, 3) GPIO_ACTIVE_LOW>;
		};
	};

	/* I2C3 */
	i2c@3180000 {
		status = "okay";
		MAX20087_2@28{
			compatible = "maxim,max20087_2";
			//interrupt-parent = <&tegra_main_gpio>;
			//interrupts = <TEGRA234_MAIN_GPIO(AC, 1) 1>; /* GMSLA_CAM_INT: rising edge sensitive */   /* ERROR: GP161 */
			status = "okay";
			reg = <0x28>;
		};
	};

	/* I2C4: dp_aux_ch1_i2c */
	i2c@3190000 {
		clock-frequency = <400000>;
		status = "okay";
		MAX20087_3@28{
			compatible = "maxim,max20087_3";
			//interrupt-parent = <&tegra_main_gpio>;
			//interrupts = <TEGRA234_MAIN_GPIO(AC, 0) 1>; /* GMSLA_CAM_INT: rising edge sensitive */   /* ERROR: GP161 */
			status = "okay";
			reg = <0x28>;
		};
	};

	spi0: spi@3210000 {
		status = "okay";
		spi@0 {
			compatible = "tegra-spidev";
			reg = <0>;
			spi-max-frequency = <50000000>; /* setting 50M frequency for security chip */
		};
	};

	spi2: spi@3230000 {
		compatible = "nvidia,tegra186-spi-slave";
		status = "okay";
		spi@0 {
			compatible = "tegra-spidev";
			reg = <0>;
			spi-max-frequency = <25000000>;
		};
	};

	serial@3110000 {
		status = "okay";
	};



	eqos_vm_irq_config: vm-irq-config {
		nvidia,num-vm-irqs = <1>;
		vm_irq1 {
			nvidia,num-vm-channels = <8>;
			nvidia,vm-channels = <0 1 2 3 4 5 6 7>;
		};
		/delete-node/ vm_irq2;
		/delete-node/ vm_irq3;
		/delete-node/ vm_irq4;
	};

	ethernet@2310000 {
		reg = <0x0 0x02310000 0x0 0x10000>,	/* EQOS Core base addr */
		      <0x0 0x02320000 0x0 0x10000>,	/* EQOS DMA base addr */
		      <0x0 0x023D0000 0x0 0x10000>;	/* MACSEC Base Register */
		reg-names = "mac-base", "dma_base", "macsec-base";
		ivc = <&tegra_hv 426>;
		nvidia,vm-irq-config = <&eqos_vm_irq_config>;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值