Linux 串口驱动

Linux 串口驱动 一、Linux下UART驱动框架1、uart_driver 注册与注销同 I2C、SPI 一样Linux 也提供了串口驱动框架我们只需要按照相应的串口框架编写驱动程序即可。串口驱动没有什么主机端和设备端之分就只有一个串口驱动而且这个驱动也已经由 NXP 官方已经编写好了我们真正要做的就是在设备树中添加所要使用的串口节点信息。当系统启动以后串口驱动和设备匹配成功相应的串口就会被驱动起来生成/dev/ttymxcX (X0…n)文件。虽然串口驱动不需要我们去写但是串口驱动框架我们还是需要了解的uart_driver结构体表示 UART 驱动uart_driver定义在include/linux/serial_core.h文件中内容如下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此函数原型如下int uart_register_driver(struct uart_driver *drv)函数参数和返回值含义如下drv要注册的 uart_driver。返回值0成功负值失败。注销驱动的时候也需要注销掉前面注册的uart_driver需要用到uart_unregister_driver函数函数原型如下void uart_unregister_driver(struct uart_driver *drv)函数参数和返回值含义如下drv要注销的 uart_driver。返回值无。2、uart_port 的添加与移除uart_port表示一个具体的 portuart_port定义在include/linux/serial_core.h文件内容如下 (有省略)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 行的opsops包含了串口的具体驱动函数。每个 UART 都有一个uart_portuart_port通过uart_add_one_port和uart_driver绑定。函数原型int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)参数drv此 port 对应的uart_driver。uport要添加到uart_driver中的 port。返回值0 成功负值失败。卸载驱动移除 portint 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的ops成员是底层 UART 硬件操作接口Linux 收发数据最终调用 ops 内函数。ops 是uart_ops 类型的结构体指针变量uart_ops定义在include/linux/serial_core.h。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 * Request IO and memory resources used by the port. 82 * This includes iomapping the port if necessary. 83 */ 84 int (*request_port)(struct uart_port *); 85 void (*config_port)(struct uart_port *, int); 86 int (*verify_port)(struct uart_port *, struct serial_struct *); 87 int (*ioctl)(struct uart_port *, unsigned int, unsigned long); 88 #ifdef CONFIG_CONSOLE_POLL 89 int (*poll_init)(struct uart_port *); 90 void (*poll_put_char)(struct uart_port *, unsigned char); 91 int (*poll_get_char)(struct uart_port *); 92 #endif 93 };UART 驱动开发者需要实现uart_ops它是直接操作 UART 寄存器的底层接口。函数详细说明参考Documentation/serial/driver文档。UART 驱动框架讲解完毕接下来分析 NXP 官方 I.MX6U 的 UART 驱动源码。二、I.MX6U UART 驱动分析1、UART 的 platform 驱动框架打开imx6ull.dtsi文件找到 UART3 设备节点1 uart3: serial021ec000 { 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-uart、fsl,imx21-uart。在内核源码drivers/tty/serial/imx.c中找到对应的 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) 277 imx_uart_devdata[IMX6Q_UART], 278 }, { 279 /* sentinel */ 280 } 281 }; 282 MODULE_DEVICE_TABLE(platform, imx_uart_devtype); 283 static const struct of_device_id imx_uart_dt_ids[] { 284 { .compatible fsl,imx6q-uart, .data 285 imx_uart_devdata[IMX6Q_UART], }, 286 { .compatible fsl,imx21-uart, .data 287 imx_uart_devdata[IMX21_UART], }, 288 { /* sentinel */ } ...... 2071 static struct platform_driver serial_imx_driver { 2072 .probe serial_imx_probe, 2073 .remove serial_imx_remove, 2074 .suspend serial_imx_suspend, 2075 .resume serial_imx_resume, 2076 .id_table imx_uart_devtype, 2077 .driver { 2078 .name imx-uart, 2079 .of_match_table imx_uart_dt_ids, 2080 }, 2081 }; 2082 2083 static int __init imx_serial_init(void) 2084 { 2085 int ret uart_register_driver(imx_reg); 2086 if (ret) 2087 return ret; 2088 2089 ret platform_driver_register(serial_imx_driver); 2090 if (ret ! 0) 2091 uart_unregister_driver(imx_reg); 2092 return ret; 2093 } 2094 2095 static void __exit imx_serial_exit(void) 2096 { 2097 platform_driver_unregister(serial_imx_driver); 2098 uart_unregister_driver(imx_reg); 2099 } 2100 2101 module_init(imx_serial_init); 2102 module_exit(imx_serial_exit);第 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_regimx_reg 就是 uart_driver 类型的结构体变量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 结构体内容如下所示 (有缩减)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 函数函数内容如下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); 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 platform_set_drvdata(pdev, sport); 2059 2060 return uart_add_one_port(imx_reg, sport-port); 2061 }第 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_popsimx_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 定义如下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 #ifdef 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 寄存器打交道的这里就不去详细的分析了。三、驱动编写1、UART3 IO 节点创建UART3 用到了 UART3_TXD 和 UART3_RXD 这两个 IO因此要先在 iomuxc 中创建 UART3 对应的 pinctrl 子节点在 iomuxc 中添加如下内容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 这两个节点如下所示789 uart1 { 790 pinctrl-names default; 791 pinctrl-0 pinctrl_uart1; 792 status okay; 793 }; 794 795 uart2 { 796 pinctrl-names default; 797 pinctrl-0 pinctrl_uart2; 798 fsl,uart-has-rtscts; 799 /* for DTE mode, add below change */ 800 /* fsl,dte-mode; */ 801 /* pinctrl-0 pinctrl_uart2dte; */ 802 status okay; 803 };uart1 是 UART1 的UART2 默认用到了 UART3 的 IO因此需要将 uart2 这个节点删除掉然后加上 UART3 对应的 uart3uart3 节点内容如下1 uart3 { 2 pinctrl-names default; 3 pinctrl-0 pinctrl_uart3; 4 status okay; 5 };完成以后重新编译设备树并使用新的设备树启动 Linux如果设备树修改成功的话系统启动以后就会生成一个名为 /dev/ttymxc2 的设备文件ttymxc2 就是 UART3 对应的设备文件应用程序可以通过访问 ttymxc2 来实现对 UART3 的操作。觉得有帮助的话打赏一下呗。。