栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

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

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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 = ;
	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], },
	{  }
};
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)分析

源码如下:

int uart_register_driver(struct uart_driver *drv)
{
	struct tty_driver *normal;
	int i, retval;

	BUG_ON(drv->state);

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

	
	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
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: %dn", 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: %dn", 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() failedn");
			return ret;
		}
	}
	sport->port.uartclk = clk_get_rate(sport->clk_per);

	
    // 申请中断
	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程序

《待完成》

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/730507.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号