Linux RS232/485/GPS 驱动实验

一、Linux 下 UART 驱动框架(本实验驱动厂商已写好,只需看原理图改设备树就行)

1、uart_driver 注册与注销

uart_driver 结构体表示 UART 驱动,uart_driver 定义在 include/linux/serial_core.h 文件中,内容如下:

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。
返回值:无。

2、uart_port 的添加与移除

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

struct uart_port {
	spinlock_t lock; /* port lock */
	unsigned long iobase; /* in/out[bwl] */
	unsigned char __iomem *membase; /* read/write[bwl] */
	......
	const struct uart_ops *ops;
	unsigned int custom_divisor;
	unsigned int line; /* port index */
	unsigned int minor;
	resource_size_t mapbase; /* for ioremap */
	resource_size_t mapsize;
	struct device *dev; /* parent device */
	......
};

ops 包含了串口的具体驱动函数。每个 UART 都有一个 uart_port,那么 uart_port 是怎么和 uart_driver 结合起来的呢?这里要用到uart_add_one_port 函数,函数原型如下:

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)

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

3、uart_ops 实现

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

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 *);
	void (*unthrottle)(struct uart_port *);
	void (*send_xchar)(struct uart_port *, char ch);
	void (*stop_rx)(struct uart_port *);
	void (*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
};

二、I.MX6U UART 驱动分析

1、UART 的 platform 驱动框架

uart3: serial@021ec000 {
	compatible = "fsl,imx6ul-uart","fsl,imx6q-uart", "fsl,imx21-uart";
	reg = <0x021ec000 0x4000>;
	interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6UL_CLK_UART3_IPG>,
	<&clks IMX6UL_CLK_UART3_SERIAL>;
	clock-names = "ipg", "per";
	dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
	dma-names = "rx", "tx";
	status = "disabled";
};

compatible 属性,这里一共有三个值:“fsl,imx6ul-uart”、“fsl,imx6q-uar”和“fsl,imx21-uart”。在 linux 源码中搜索这三个值即可找到对应的 UART 驱动文件。

static struct platform_device_id imx_uart_devtype[] = {
	{
	.name = "imx1-uart",
	.driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX1_UART],
	}, {
	.name = "imx21-uart",
	.driver_data = (kernel_ulong_t)
	&imx_uart_devdata[IMX21_UART],
	}, {
	.name = "imx6q-uart",
	.driver_data = (kernel_ulong_t)
	&imx_uart_devdata[IMX6Q_UART],
	}, {
	/* sentinel */
	}
};
MODULE_DEVICE_TABLE(platform, imx_uart_devtype);

static const struct of_device_id imx_uart_dt_ids[] = {
	{ .compatible = "fsl,imx6q-uart", .data =&imx_uart_devdata[IMX6Q_UART], },
	{ .compatible = "fsl,imx1-uart", .data =&imx_uart_devdata[IMX1_UART], },
	{ .compatible = "fsl,imx21-uart", .data =&imx_uart_devdata[IMX21_UART], },
	{ /* sentinel */ }
};
......
static struct platform_driver serial_imx_driver = {
	.probe = serial_imx_probe,
	.remove = serial_imx_remove,
	
	.suspend = serial_imx_suspend,
	.resume = serial_imx_resume,
	.id_table = imx_uart_devtype,
	.driver = {
		.name = "imx-uart",
		.of_match_table = imx_uart_dt_ids,
	},
};

static int __init imx_serial_init(void)
{
	int ret = uart_register_driver(&imx_reg);
	
	if (ret)
		return ret;

	ret = platform_driver_register(&serial_imx_driver);
	if (ret != 0)
		uart_unregister_driver(&imx_reg);
		
	return ret;
}

static void __exit imx_serial_exit(void)
{
	platform_driver_unregister(&serial_imx_driver);
	uart_unregister_driver(&imx_reg);
}

module_init(imx_serial_init);
module_exit(imx_serial_exit);

2、uart_driver 初始化

static struct uart_driver imx_reg = {
	.owner = THIS_MODULE,
	.driver_name = DRIVER_NAME,
	.dev_name = DEV_NAME,
	.major = SERIAL_IMX_MAJOR,
	.minor = MINOR_START,
	.nr = ARRAY_SIZE(imx_ports),
	.cons = IMX_CONSOLE,
};

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 结构体内容如下所示(有缩减):

struct imx_port {
	struct uart_port port;   //uart_port 成员变量 port。
	struct timer_list timer;
	unsigned int old_status;
	unsigned int have_rtscts:1;
	unsigned int dte_mode:1;
	unsigned int irda_inv_rx:1;
	unsigned int irda_inv_tx:1;
	unsigned short trcv_delay; /* transceiver delay */
	......
	unsigned long flags;
};

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

static int serial_imx_probe(struct platform_device *pdev)
{
	struct imx_port *sport			//定义一个 imx_port 类型的结构体指针变量 sport。
	.....
	
	sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
	if (!sport)
		return -ENOMEM;
	
	ret = serial_imx_probe_dt(sport, pdev);
	if (ret > 0)
		serial_imx_probe_pdata(sport, pdev);
	else if (ret < 0)
		return ret;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);		//获取硬件寄存器地址
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);
	
	rxirq = platform_get_irq(pdev, 0);		//获取中断信息。
	txirq = platform_get_irq(pdev, 1);
	rtsirq = platform_get_irq(pdev, 2)
	.....

	sport->port.ops = &imx_pops;

	.....
	return uart_add_one_port(&imx_reg, &sport->port);// 向 uart_driver 添加 uart_port,在这里就是向 imx_reg 添加 sport->port
}

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

static struct uart_ops imx_pops = {
	.tx_empty = imx_tx_empty,
	.set_mctrl = imx_set_mctrl,
	.get_mctrl = imx_get_mctrl,
	.stop_tx = imx_stop_tx,
	.start_tx = imx_start_tx,
	.stop_rx = imx_stop_rx,
	.enable_ms = imx_enable_ms,
	.break_ctl = imx_break_ctl,
	.startup = imx_startup,
	.shutdown = imx_shutdown,
	.flush_buffer = imx_flush_buffer,
	.set_termios = imx_set_termios,
	.type = imx_type,
	.config_port = imx_config_port,
	.verify_port = imx_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
	.poll_init = imx_poll_init,
	.poll_get_char = imx_poll_get_char,
	.poll_put_char = imx_poll_put_char,
	#endif
};	

三、硬件原理图分析

1、RS232 原理图

Linux RS232/485/GPS 驱动实验_第1张图片

2、UART3 IO 节点创建

pinctrl_uart3: uart3grp {
	fsl,pins = <
		MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0X1b0b1
		MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0X1b0b1
	>;
};

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

3、添加 uart3 节点

默认情况下 imx6ull-alientek-emmc.dts 中只有 uart1 和 uart2 这两个节点

Linux RS232/485/GPS 驱动实验_第2张图片
I.MX6U-ALPHA 开发板上没有用到 UART2,而且 UART2
默认用到了 UART3 的 IO,因此需要将 uart2 这个节点删除掉,然后加上 UART3 对应的 uart3,
uart3 节点内容如下:

&uart3 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart3>;
	status = "okay";
};

四、移植 minicom

参考正点原子手册

你可能感兴趣的:(Linux,驱动以及裸机,linux,驱动开发)