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 接口。

相关推荐
lyh13444 分钟前
【Ubuntu崩溃修复】
linux·运维·服务器
toradexsh23 分钟前
HDMI 显示器热插拔对应显示应用启停测试
arm·nxp·hdmi·imx8qm·hotplug
什么半岛铁盒1 小时前
【Linux系统】Linux环境变量:系统配置的隐形指挥官
linux
Lw老王要学习2 小时前
Linux容器篇、第一章_02Rocky9.5 系统下 Docker 的持久化操作与 Dockerfile 指令详解
linux·运维·docker·容器·云计算
橙子小哥的代码世界2 小时前
【大模型RAG】Docker 一键部署 Milvus 完整攻略
linux·docker·大模型·milvus·向量数据库·rag
倔强的石头1063 小时前
【Linux指南】用户与系统基础操作
linux·运维·服务器
云上艺旅3 小时前
centos升级内核
linux·运维·centos
kaikaile19953 小时前
centos开启samba服务
linux·运维·centos
云上艺旅3 小时前
centos部署k8s v1.33版本
linux·云原生·kubernetes·centos
好多知识都想学3 小时前
Centos 7 服务器部署多网站
linux·服务器·centos