I.MX6ULL_Linux_驱动篇(50)linux UART驱动

串口是很常用的一个外设,在 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 驱动。

Linux 下 UART 驱动框架

uart_driver 注册与注销

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

cpp 复制代码
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,此函数原型如下:

cpp 复制代码
int uart_register_driver(struct uart_driver *drv)

函数参数和返回值含义如下:

drv: 要注册的 uart_driver。

返回值: 0,成功;负值,失败。

注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数,

函数原型如下:

cpp 复制代码
void uart_unregister_driver(struct uart_driver *drv)

函数参数和返回值含义如下:

drv: 要注销的 uart_driver。

返回值: 无。

uart_port 的添加与移除

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

cpp 复制代码
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 函数,函数原型如下:

cpp 复制代码
int 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 函数,函数原型如下:

cpp 复制代码
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

函数参数和返回值含义如下:

drv:要卸载的 port 所对应的 uart_driver。

uport: 要卸载的 uart_port。

返回值: 0,成功;负值,失败。

uart_ops 实现

在上面讲解 uart_port 的时候说过, uart_port 中的 ops 成员变量很重要,因为 ops 包含了针对 UART 具体的驱动函数, Linux 系统收发数据最终调用的都是 ops 中的函数。 ops 是 uart_ops

类型的结构体指针变量, uart_ops 定义在 include/linux/serial_core.h 文件中,内容如下:

cpp 复制代码
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 驱动文件是如何编写的。

I.MX6U UART 驱动分析

UART 的 platform 驱动框架

打开 imx6ull.dtsi 文件,找到 UART3 对应的子节点,子节点内容如下所示:

cpp 复制代码
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,imx6quar"和"fsl,imx21-uart"。在 linux 源码中搜索这三个值即可找到对应的 UART 驱动文件,此文

件为 drivers/tty/serial/imx.c,在此文件中可以找到如下内容:

cpp 复制代码
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。

uart_driver 初始化

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

cpp 复制代码
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 };
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 结构体内容如下所示(有缩减):

cpp 复制代码
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;
245 };

第 217 行, uart_port 成员变量 port。

接下来看一下 serial_imx_probe 函数,函数内容如下:

cpp 复制代码
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。

imx_pops 结构体变量

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

cpp 复制代码
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 接口。

相关推荐
耶啵奶膘4 分钟前
uniapp-是否删除
linux·前端·uni-app
2401_850410831 小时前
文件系统和日志管理
linux·运维·服务器
XMYX-02 小时前
使用 SSH 蜜罐提升安全性和记录攻击活动
linux·ssh
二十雨辰4 小时前
[linux]docker基础
linux·运维·docker
饮浊酒4 小时前
Linux操作系统 ------(3.文本编译器Vim)
linux·vim
lihuhelihu4 小时前
第3章 CentOS系统管理
linux·运维·服务器·计算机网络·ubuntu·centos·云计算
矛取矛求5 小时前
Linux系统性能调优技巧
linux
One_Blanks5 小时前
渗透测试-Linux基础(1)
linux·运维·安全
Perishell5 小时前
无人机避障——大疆与Airsim中的角速度信息订阅获取
linux·动态规划·无人机
爱吃喵的鲤鱼5 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++