Linux 驱动开发 五十一:I.MX6U UART 驱动分析

通过对 NXP 维护 Linux4.1.15 源码进行分析。

一、确定驱动源码

思路:通过 imx6ull.dtsi 设备树文件,找到 UART 中 compatible 属性值,在驱动源码中查找相匹配驱动。

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

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 驱动文件,此文 件为 drivers/tty/serial/imx.c,在此文件中可以找到如下内容:

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 */ }
};
MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);

通过以上信息可以确定,drivers/tty/serial/imx.c 文件为 I.MX6U 驱动源码。

二、驱动加载函数分析

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;
}

imx_serial_init 函数功能:

1、调用 uart_register_driver 函数注册 imx_reg 串口驱动。

2、调用 platform_driver_register 函数注册 serial_imx_driver platform驱动。

三、imx_reg 初始化状态

在这里插入图片描述

四、serial_imx_driver 初始化状态

在这里插入图片描述

五、uart_register_driver(&imx_reg)分析

源码如下:

/**
 *	uart_register_driver - register a driver with the uart core layer 向uart核心层注册一个驱动程序
 *	@drv: low level driver structure 底层驱动结构
 *
 *	Register a uart driver with the core driver.  We in turn register
 *	with the tty layer, and initialise the core driver per-port state.
 * 用核心驱动注册一个uart驱动。
 * 我们依次向tty层注册,并初始化每个端口的核心驱动程序状态。
 *
 *	We have a proc file in /proc/tty/driver which is named after the
 *	normal driver. 我们在 /proc/tty/driver 中有一个 proc 文件,它以普通驱动程序命名。
 *
 *	drv->port should be NULL, and the per-port structures should be
 *	registered using uart_add_one_port after this call has succeeded.
 * drv->port 应为 NULL,并且在此调用成功后,应使用 uart_add_one_port 注册每个端口的结构。
 */
int uart_register_driver(struct uart_driver *drv)
{
	struct tty_driver *normal;
	int i, retval;

	BUG_ON(drv->state);

	/*
	 * Maybe we should be using a slab cache for this, especially if
	 * we have a large number of ports to handle.
	 * 也许我们应该使用一个slab缓存,特别是当我们有大量的端口要处理的时候。
	 */
	drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
	if (!drv->state)
		goto out;

	normal = alloc_tty_driver(drv->nr);
	if (!normal)
		goto out_kfree;

	drv->tty_driver = normal;

	normal->driver_name	= drv->driver_name;
	normal->name		= drv->dev_name;
	normal->major		= drv->major;
	normal->minor_start	= drv->minor;
	normal->type		= TTY_DRIVER_TYPE_SERIAL;
	normal->subtype		= SERIAL_TYPE_NORMAL;
	normal->init_termios	= tty_std_termios;
	normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
	normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
	normal->flags		= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
	normal->driver_state    = drv;
	tty_set_operations(normal, &uart_ops);

	/*
	 * Initialise the UART state(s).
	 */
	for (i = 0; i < drv->nr; i++) {
		struct uart_state *state = drv->state + i;
		struct tty_port *port = &state->port;

		tty_port_init(port);
		port->ops = &uart_port_ops;
	}

	retval = tty_register_driver(normal);
	if (retval >= 0)
		return retval;

	for (i = 0; i < drv->nr; i++)
		tty_port_destroy(&drv->state[i].port);
	put_tty_driver(normal);
out_kfree:
	kfree(drv->state);
out:
	return -ENOMEM;
}

1、分析1

drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if (!drv->state)
	goto out;

drv:为 uart_register_driver 传入参数指针,也就是 imx_reg 变量首地址。

drv->state:就是 imx_reg.state。

drv->nr:就是 imx_reg.nr,此字段保存芯片 uart 个数。

以上代码主要作用是依照 uart 个数分配空间,并将首地址赋值给 imx_reg.state
在这里插入图片描述

2、分析2

normal = alloc_tty_driver(drv->nr);
if (!normal)
	goto out_kfree;

drv->tty_driver = normal;

normal->driver_name	= drv->driver_name;
normal->name		= drv->dev_name;
normal->major		= drv->major;
normal->minor_start	= drv->minor;
normal->type		= TTY_DRIVER_TYPE_SERIAL;
normal->subtype		= SERIAL_TYPE_NORMAL;
normal->init_termios	= tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags		= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state    = drv;
tty_set_operations(normal, &uart_ops);
void tty_set_operations(struct tty_driver *driver,
			const struct tty_operations *op)
{
	driver->ops = op;
};
static const struct tty_operations uart_ops = {
	.open		= uart_open,
	.close		= uart_close,
	.write		= uart_write,
	.put_char	= uart_put_char,
	.flush_chars	= uart_flush_chars,
	.write_room	= uart_write_room,
	.chars_in_buffer= uart_chars_in_buffer,
	.flush_buffer	= uart_flush_buffer,
	.ioctl		= uart_ioctl,
	.throttle	= uart_throttle,
	.unthrottle	= uart_unthrottle,
	.send_xchar	= uart_send_xchar,
	.set_termios	= uart_set_termios,
	.set_ldisc	= uart_set_ldisc,
	.stop		= uart_stop,
	.start		= uart_start,
	.hangup		= uart_hangup,
	.break_ctl	= uart_break_ctl,
	.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
	.proc_fops	= &uart_proc_fops,
#endif
	.tiocmget	= uart_tiocmget,
	.tiocmset	= uart_tiocmset,
	.get_icount	= uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
	.poll_init	= uart_poll_init,
	.poll_get_char	= uart_poll_get_char,
	.poll_put_char	= uart_poll_put_char,
#endif
};

drv->tty_driver:就是 imx_reg.tty_driver。

以上源码主要作用为,为 imx_reg.tty_driver 分配空间,并进行初始化
在这里插入图片描述

3、分析3

/*
 * Initialise the UART state(s).
 */
for (i = 0; i < drv->nr; i++) {
	struct uart_state *state = drv->state + i;
	struct tty_port *port = &state->port;
	tty_port_init(port);
	port->ops = &uart_port_ops;
}
static const struct tty_port_operations uart_port_ops = {
	.activate	= uart_port_activate,
	.shutdown	= uart_port_shutdown,
	.carrier_raised = uart_carrier_raised,
	.dtr_rts	= uart_dtr_rts,
};

以上代码主要作用是为 imx_reg.state.port 赋值
在这里插入图片描述

4、分析4

retval = tty_register_driver(normal);
if (retval >= 0)
	return retval;

以上代码主要作用注册 tty_driver 驱动

5、总结

uart_register_driver(&imx_reg) 主要作用如下:

1、初始化 imx_reg 变量。

2、注册 tty_driver 驱动。

六、platform_driver_register(&serial_imx_driver) 分析

platform_driver_register(&serial_imx_driver) 主要作用注册 platform 驱动,不进行详细分析。

七、serial_imx_probe 分析

当 UART 驱动和设备匹配成功以后,serial_imx_probe 函数将被执行。

static int serial_imx_probe(struct platform_device *pdev)
{
	struct imx_port *sport;
	void __iomem *base;
	int ret = 0;
	struct resource *res;
	int txirq, rxirq, rtsirq;

	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;
	
    // 从设备树中获取 I.MX 系列 SOC UART 外设寄存器首地址
	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
	sport->port.dev = &pdev->dev;
	sport->port.mapbase = res->start;
	sport->port.membase = base;
	sport->port.type = PORT_IMX,
	sport->port.iotype = UPIO_MEM;
	sport->port.irq = rxirq;
	sport->port.fifosize = 32;
	sport->port.ops = &imx_pops;	// imx_pops 就是 I.MX6ULL 最底层的驱动函数集合
	sport->port.rs485_config = imx_rs485_config;
	sport->port.rs485.flags =
		SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
	sport->port.flags = UPF_BOOT_AUTOCONF;
	init_timer(&sport->timer);
	sport->timer.function = imx_timeout;
	sport->timer.data     = (unsigned long)sport;

	sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
	if (IS_ERR(sport->clk_ipg)) {
		ret = PTR_ERR(sport->clk_ipg);
		dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);
		return ret;
	}

	sport->clk_per = devm_clk_get(&pdev->dev, "per");
	if (IS_ERR(sport->clk_per)) {
		ret = PTR_ERR(sport->clk_per);
		dev_err(&pdev->dev, "failed to get per clk: %d\n", ret);
		return ret;
	}

	sport->port.uartclk = clk_get_rate(sport->clk_per);
	if (sport->port.uartclk > IMX_MODULE_MAX_CLK_RATE) {
		ret = clk_set_rate(sport->clk_per, IMX_MODULE_MAX_CLK_RATE);
		if (ret < 0) {
			dev_err(&pdev->dev, "clk_set_rate() failed\n");
			return ret;
		}
	}
	sport->port.uartclk = clk_get_rate(sport->clk_per);

	/*
	 * Allocate the IRQ(s) i.MX1 has three interrupts whereas later
	 * chips only have one interrupt.
	 */
    // 申请中断
	if (txirq > 0) {
		ret = devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,
				       dev_name(&pdev->dev), sport);
		if (ret)
			return ret;

		ret = devm_request_irq(&pdev->dev, txirq, imx_txint, 0,
				       dev_name(&pdev->dev), sport);
		if (ret)
			return ret;
	} else {
		ret = devm_request_irq(&pdev->dev, rxirq, imx_int, 0,
				       dev_name(&pdev->dev), sport);
		if (ret)
			return ret;
	}

	imx_ports[sport->port.line] = sport;

	platform_set_drvdata(pdev, sport);

    // 向 uart_driver 添加 uart_port
	return uart_add_one_port(&imx_reg, &sport->port);
}
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
};

imx_pops 中的函数基本都是和 I.MX6ULL 的 UART 寄存器打交道的。

八、串口驱动使用

I.MX6U 的 UART 驱动 NXP 已经编写好了,所以不需要我们编写。

我们要做的就是在设备树中添加 UART3 对应的设备节点即可。

1、检查引脚是否使用

pinctrl_uart2: uart2grp {
	fsl,pins = <
		MX6UL_PAD_UART2_TX_DATA__UART2_DCE_TX	0x1b0b1
		MX6UL_PAD_UART2_RX_DATA__UART2_DCE_RX	0x1b0b1
		MX6UL_PAD_UART3_RX_DATA__UART2_DCE_RTS	0x1b0b1
		MX6UL_PAD_UART3_TX_DATA__UART2_DCE_CTS	0x1b0b1
	>;
};

通过查找设备树,pinctrl_uart2 中使用 UART3 引脚。因此,需要注释掉。

2、在设备树中添加引脚复用

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

3、设备树中增加uart3节点

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

4、测试

添加前 Linux 启动日志如下:
在这里插入图片描述
编译设备树:

onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$ make dtbs
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  DTC     arch/arm/boot/dts/imx6ull-lq-evk.dtb
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$

添加后 Linux 启动日志如下:
在这里插入图片描述

九、app程序

《待完成》

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值