1.串口是很常用的一个外设,在 Linux 下通常通过串口和其他设备或传感器进行通信。电平的不同,串口分为 TTL 和 RS232。
RS232、RS485 以及 GPS 模块接口通通连接到了 I.MX6U 的 UART3 接口上。
2.linux uart 驱动框架
同 I2C、SPI 一样,Linux 也提供了串口驱动框架。
我们只需要按照相应的串口框架编写驱动程序即可。
串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,而且这个驱动也已经由 NXP 官方已经编写好了。
我们真正要做的就是在设备树中添加所要使用的串口节点信息。
当系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttymxcX(X=0….n)文件。
虽然串口驱动不需要我们去写,但是串口驱动框架我们还是需要了解的,uart_driver 结构体表示 UART 驱动。
uart_driver 定义在 include/linux/serial_core.h 文件中。
uart_driver 结构体
struct uart_driver {
struct module *owner; /* 模块所属者 */
const char *driver_name; /* 驱动名字 */
const char *dev_name; /* 设备名字 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
int nr; /* 设备数 */
struct console *cons; /* 控制台 */
/*
*these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};
每个串口驱动都需要定义一个 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。
返回值:无。
uart_port 定义在 include/linux/serial_core.h
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 函数,函数原型如下:
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
Linux 系统收发数据最终调用的都是 ops 中的函数
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*throttle)(struct uart_port *);
I.MX6U 嵌入式 Linux 驱动开发指南
void (*unthrottle)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
(*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
/*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *);
/*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *);
/*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
第 1987~1988 行,从设备树中获取 I.MX 系列 SOC UART 外设寄存器首地址,对于
I.MX6ULL 的 UART3 来说就是 0X021EC000。得到寄存器首地址以后对其进行内存映射,得到
对应的虚拟地址。
我们要做的就是在设备树中添加 UART3 对应的设备节点即可。打开 imx6ull-alientek-emmc.dts
文件,在此文件中只有 UART1 对应的 uart1 节点,并没有 UART3 对应的节点,因此我们可以
参考 uart1 节点创建 uart3 节点
iomuxc
pinctrl_uart3: uart3grp {
fsl,pins = <
MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0X1b0b1
MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0X1b0b1
>;
};
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};