linux驱动 6.UART驱动初始化

1.简介

UART驱动是我们每个人都会遇到的,输出log都少不了UART,这个是我们和系统给交流的窗口。下面我们在看一下解析一下这个驱动的code.
软件:linux 4.14.98
硬件: imx8q
UART总体驱动框图如下图所示:
在这里插入图片描述

2.设备树

在这里以lpuart0为例,通过设备树(device tree)的相关代码,查看该设备是怎么配置的。废话不多少,上代码:
file:fsl-imx8qxp-mek.dtsi

#include "fsl-imx8qxp.dtsi"

/ {
	model = "Freescale i.MX8QXP MEK";
	compatible = "fsl,imx8qxp-mek", "fsl,imx8qxp";

	chosen {
		bootargs = "console=ttyLP0,115200 earlycon=lpuart32,0x5a060000,115200";
		stdout-path = &lpuart0;
	};

pinctrl_lpuart0: lpuart0grp {
    fsl,pins = <
        SC_P_UART0_RX_ADMA_UART0_RX	0x06000020
        SC_P_UART0_TX_ADMA_UART0_TX	0x06000020
                >;
 };
........................
&pd_dma_lpuart0 {
	debug_console;
};
&lpuart0 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lpuart0>;
	status = "okay";
};
.............................

file:fsl-imx8dx.dtsi中有对lpuart0的定义

 .....................................
/ {
   model = "Freescale i.MX8DX";
    compatible = "fsl,imx8dx", "fsl,imx8qxp";
    interrupt-parent = <&gic>;
    #address-cells = <2>;
    #size-cells = <2>;

	pmu {
	interrupt-affinity = <&A35_0>, <&A35_1>;
	};

	aliases {
		csi0 = &mipi_csi_0;
		dpu0 = &dpu1;
		ethernet0 = &fec1;
		ethernet1 = &fec2;
		dsi_phy0 = &mipi_dsi_phy1;
		dsi_phy1 = &mipi_dsi_phy2;
		mipi_dsi0 = &mipi_dsi1;
		mipi_dsi1 = &mipi_dsi2;
		ldb0 = &ldb1;
		ldb1 = &ldb2;
		isi0 = &isi_0;
		isi1 = &isi_1;
		isi2 = &isi_2;
		isi3 = &isi_3;
		isi4 = &isi_4;
		isi5 = &isi_5;
		isi6 = &isi_6;
		isi7 = &isi_7;
		serial0 = &lpuart0;
		serial1 = &lpuart1;
		serial2 = &lpuart2;
		serial3 = &lpuart3;
		mmc0 = &usdhc1;
		mmc1 = &usdhc2;
		mmc2 = &usdhc3;
		can0 = &flexcan1;
		can1 = &flexcan2;
		can2 = &flexcan3;
		i2c1 = &i2c_rpbus_1;
		i2c5 = &i2c_rpbus_5;
		i2c12 = &i2c_rpbus_12;
		i2c13 = &i2c_rpbus_13;
		i2c14 = &i2c_rpbus_14;
		i2c15 = &i2c_rpbus_15;
	};
..............................................
 
  	pd_dma_lpuart0: PD_DMA_UART0 {
       reg = <SC_R_UART_0>;
       #power-domain-cells = <0>;
       power-domains = <&pd_dma>;
       wakeup-irq = <345>;
       };
   .........................
   lpuart0: serial@5a060000 {
		compatible = "fsl,imx8qm-lpuart";
		reg = <0x0 0x5a060000 0x0 0x1000>;
		interrupts = <GIC_SPI 345 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-parent = <&wu>;
		clocks = <&clk IMX8QXP_UART0_CLK>,
			 <&clk IMX8QXP_UART0_IPG_CLK>;
		clock-names = "per", "ipg";
		assigned-clocks = <&clk IMX8QXP_UART0_CLK>;
		assigned-clock-rates = <80000000>;
		power-domains = <&pd_dma_lpuart0>;
		status = "disabled";
	};

在这一部分定义了lpuart设备相关的寄存器,管脚等参数。

2.TTY 层初始化

2.1线路规程初始化

线路规程初始化,是在console_init()函数中实现的。
kernel/printk/printk.c :void __init console_init(void)

/*
 * Initialize the console device. This is called *early*, so
 * we can't necessarily depend on lots of kernel help here.
 * Just do some early initializations, and do the complex setup
 * later.
 */
void __init console_init(void)
{
        initcall_t *call;

        /* Setup the default TTY line discipline. */
        n_tty_init();

        /*
         * set up the console device so that later boot sequences can
         * inform about problems etc..
         */
        call = __con_initcall_start;
        while (call < __con_initcall_end) {
                (*call)();
                call++;
        }
}

drivers/tty/n_tty.c:n_tty_init()函数

void __init n_tty_init(void)
{
        tty_register_ldisc(N_TTY, &n_tty_ops);
}

drivers/tty/tty_ldisc.c: tty_register_ldisc(N_TTY, &n_tty_ops);

/**
 *      tty_register_ldisc      -       install a line discipline
 *      @disc: ldisc number
 *      @new_ldisc: pointer to the ldisc object
 *
 *      Installs a new line discipline into the kernel. The discipline
 *      is set up as unreferenced and then made available to the kernel
 *      from this point onwards.
 *
 *      Locking:
 *              takes tty_ldiscs_lock to guard against ldisc races
 */

int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
        unsigned long flags;
        int ret = 0;

        if (disc < N_TTY || disc >= NR_LDISCS)
                return -EINVAL;

        raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
        tty_ldiscs[disc] = new_ldisc;
        new_ldisc->num = disc;
        new_ldisc->refcount = 0;
        raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);

        return ret;
}
EXPORT_SYMBOL(tty_register_ldisc);

这个tty_ldisc初始化到这一步就暂时放在一边,待到后面通过open函数中再来调用此处已注册的的tty_disc线路规程。

2.2 tty_driver相关部分的初始化

tty_driver的初始化从lpuart_serial_init()函数看起。
“drivers/tty/serial/fsl_lpuart.c” 这个文件代码有点多,现在我们只看自己需要的代码:从module初始化和退出的函数开始看起

static int __init lpuart_serial_init(void)
{
        int ret = uart_register_driver(&lpuart_reg);

        if (ret)
                return ret;

        ret = platform_driver_register(&lpuart_driver);
        if (ret)
                uart_unregister_driver(&lpuart_reg);

        return ret;
}

static void __exit lpuart_serial_exit(void)
{
        platform_driver_unregister(&lpuart_driver);
        uart_unregister_driver(&lpuart_reg);
}

我们看初始化函数**:int ret = uart_register_driver(&lpuart_reg);**
其中 lpuart_reg的定义如下:

static struct uart_driver lpuart_reg = {
        .owner          = THIS_MODULE,
        .driver_name    = DRIVER_NAME,
        .dev_name       = DEV_NAME,
        .nr             = ARRAY_SIZE(lpuart_ports),
        .cons           = LPUART_CONSOLE,
};

其中各个变量的定义:
........................
#ifdef MODULE
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
#else
#define THIS_MODULE ((struct module *)0)
#endif
...............................
#define DRIVER_NAME     "fsl-lpuart"
#define DEV_NAME        "ttyLP"
#define LPUART_CONSOLE  (&lpuart_console)
....................................
static struct console lpuart_console = {
        .name           = DEV_NAME,
        .write          = lpuart_console_write,
        .device         = uart_console_device,
        .setup          = lpuart_console_setup,
        .flags          = CON_PRINTBUFFER,
        .index          = -1,
        .data           = &lpuart_reg,
};
.......................................

然后转到drivers/tty/serial/serial_core.c,查看函数uart_register_driver(),函数的定义:

/**
 *      uart_register_driver - register a driver with the uart core layer
 *      @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.
 *
 *      We have a proc file in /proc/tty/driver which is named after the
 *      normal driver.
 *
 *      drv->port should be NULL, and the per-port structures should be
 *      registered using uart_add_one_port after this call has succeeded.
 */
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.
         */
        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;
}

下面我们看一下 tty_register_driver(normal);这这里申请dev结构体并添加driver。

/*
 * Called by a tty driver to register itself.
 */
int tty_register_driver(struct tty_driver *driver)
{
        int error;
        int i;
        dev_t dev;
        struct device *d;

        if (!driver->major) {
                error = alloc_chrdev_region(&dev, driver->minor_start,
                                                driver->num, driver->name);
                if (!error) {
                        driver->major = MAJOR(dev);
                        driver->minor_start = MINOR(dev);
                }
        } else {
                dev = MKDEV(driver->major, driver->minor_start);
                error = register_chrdev_region(dev, driver->num, driver->name);
        }
        if (error < 0)
                goto err;

        if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
                error = tty_cdev_add(driver, dev, 0, driver->num);
                if (error)
                        goto err_unreg_char;
        }

        mutex_lock(&tty_mutex);
        list_add(&driver->tty_drivers, &tty_drivers);
        mutex_unlock(&tty_mutex);

        if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
                for (i = 0; i < driver->num; i++) {
                        d = tty_register_device(driver, i, NULL);
                        if (IS_ERR(d)) {
                                error = PTR_ERR(d);
                                goto err_unreg_devs;
                        }
                }
        }
        proc_tty_register_driver(driver);
        driver->flags |= TTY_DRIVER_INSTALLED;
        return 0;

err_unreg_devs:
        for (i--; i >= 0; i--)
                tty_unregister_device(driver, i);

        mutex_lock(&tty_mutex);
        list_del(&driver->tty_drivers);
        mutex_unlock(&tty_mutex);

err_unreg_char:
        unregister_chrdev_region(dev, driver->num);
err:
        return error;
}
EXPORT_SYMBOL(tty_register_driver);

从上述代码中我们可以看到,tty driver 的参数是从uart_driver中来的,这就相当于在tty_driver外面就加了一层uart_driver。而uart_driver中的部分字段和tty_driver中的部分字段数值是一样的。
tty_set_operations(normal, &uart_ops);指定了tty_driver的操作集tty_operation为uart_ops。

3 Uart driver 初始化

Uart driver初始化中有个重要的架构体uart_state,uart_state中有两个重要的变量,其中包含uart_port、tty_port。tty_port在之前uart_register_driver()中进行了初始化。而uart_port是在另一个probe函数中进行的。

下面我们看platform_driver_register()函数

ret = platform_driver_register(&lpuart_driver);

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \
        __platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
                                        struct module *);

对这个函数我们就不细讲了,现在看一下,lpuart_driver 结构体。

static struct platform_driver lpuart_driver = {
        .probe          = lpuart_probe,
        .remove         = lpuart_remove,
        .driver         = {
                .name   = "fsl-lpuart",
                .of_match_table = lpuart_dt_ids,
                .pm     = SERIAL_LPUART_PM_OPS,
        },
};

下面看lpuart_probe函数

static int lpuart_probe(struct platform_device *pdev)
{
        const struct of_device_id *of_id = of_match_device(lpuart_dt_ids,
                                                           &pdev->dev);
        const struct lpuart_soc_data *sdata = of_id->data;
        struct device_node *np = pdev->dev.of_node;
        struct lpuart_port *sport;
        struct resource *res;
        unsigned long cr_32;
        unsigned char cr_8;
        int ret;

        sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
        if (!sport)
                return -ENOMEM;

        ret = of_alias_get_id(np, "serial");
        if (ret < 0) {
                dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);
                return ret;
        }
        if (ret >= ARRAY_SIZE(lpuart_ports)) {
                dev_err(&pdev->dev, "serial%d out of range\n", ret);
                return -EINVAL;
        }
        sport->port.line = ret;
        sport->dma_eeop = of_device_is_compatible(np, "fsl,imx8qm-lpuart");

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        sport->port.membase = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(sport->port.membase))
                return PTR_ERR(sport->port.membase);

        sport->regbase = sport->port.membase;
        sport->port.membase += sdata->reg_off;
        sport->port.mapbase = res->start + sdata->reg_off;
        sport->port.dev = &pdev->dev;
        sport->port.type = PORT_LPUART;
        ret = platform_get_irq(pdev, 0);
        if (ret < 0) {
                dev_err(&pdev->dev, "cannot obtain irq\n");
                return ret;
        }
        sport->port.irq = ret;
        sport->port.iotype = sdata->iotype;
        if (lpuart_is_32(sport))
                sport->port.ops = &lpuart32_pops;
        else
                sport->port.ops = &lpuart_pops;
        sport->port.flags = UPF_BOOT_AUTOCONF;

        if (!lpuart_is_32(sport))
                sport->port.rs485_config = lpuart_config_rs485;

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

        ret = clk_prepare_enable(sport->ipg_clk);
        if (ret) {
                dev_err(&pdev->dev, "failed to enable uart ipg clk: %d\n", ret);
                return ret;
        }
        ret = clk_prepare_enable(sport->per_clk);
        if (ret) {
                clk_disable_unprepare(sport->ipg_clk);
                dev_err(&pdev->dev, "failed to enable uart clk: %d\n", ret);
                return ret;
        }
        if (sport->per_clk)
                sport->port.uartclk = clk_get_rate(sport->per_clk);
        else
                sport->port.uartclk = clk_get_rate(sport->ipg_clk);

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

        platform_set_drvdata(pdev, &sport->port);

        /* Disable interrupts before request irq */
        if (lpuart_is_32(sport)) {
                cr_32 = lpuart32_read(&sport->port, UARTCTRL);
                cr_32 &= ~(UARTCTRL_TIE | UARTCTRL_TCIE | UARTCTRL_RIE | UARTCTRL_ILIE);
                lpuart32_write(&sport->port, cr_32, UARTCTRL);
        } else {
                cr_8 = readb(sport->port.membase + UARTCR2);
                cr_8 &= ~(UARTCR2_TIE | UARTCR2_TCIE | UARTCR2_RIE | UARTCR2_ILIE);
                writeb(cr_8, sport->port.membase + UARTCR2);
        }

        if (lpuart_is_32(sport)) {
                lpuart_reg.cons = LPUART32_CONSOLE;
                ret = devm_request_irq(&pdev->dev, sport->port.irq, lpuart32_int, 0,
                                        DRIVER_NAME, sport);
        } else {
                lpuart_reg.cons = LPUART_CONSOLE;
                ret = devm_request_irq(&pdev->dev, sport->port.irq, lpuart_int, 0,
                                        DRIVER_NAME, sport);
        }

        if (ret)
                goto failed_irq_request;

        pm_runtime_use_autosuspend(&pdev->dev);
        pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT);
        pm_runtime_set_active(&pdev->dev);
        pm_runtime_enable(&pdev->dev);

        ret = uart_add_one_port(&lpuart_reg, &sport->port);
        if (ret)
                goto failed_attach_port;

        ret = lpuart_hw_reset(sport);
        if (ret)
                goto failed_attach_port;

        sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx");
        if (!sport->dma_tx_chan)
                dev_info(sport->port.dev, "NO DMA tx channel, run at cpu mode\n");

        sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx");
        if (!sport->dma_rx_chan)
                dev_info(sport->port.dev, "NO DMA rx channel, run at cpu mode\n");

        if (!lpuart_is_32(sport) &&
                of_property_read_bool(np, "linux,rs485-enabled-at-boot-time")) {
                sport->port.rs485.flags |= SER_RS485_ENABLED;
                sport->port.rs485.flags |= SER_RS485_RTS_ON_SEND;
                writeb(UARTMODEM_TXRTSE, sport->port.membase + UARTMODEM);
        }

        return 0;

failed_attach_port:
        pm_runtime_disable(&pdev->dev);
        pm_runtime_set_suspended(&pdev->dev);
        pm_runtime_dont_use_autosuspend(&pdev->dev);
failed_irq_request:
        clk_disable_unprepare(sport->per_clk);
        clk_disable_unprepare(sport->ipg_clk);
        return ret;
}

在这里我们重点看一下ret = uart_add_one_port(&lpuart_reg, &sport->port)函数这个就是我们要添加的串口,比如你有三个串口就是添加三个。一个 uart driver可以有多个uart_state。

 *      @uport: uart port structure to use for this port.
 *
 *      This allows the driver to register its own uart_port structure
 *      with the core driver.  The main purpose is to allow the low
 *      level uart drivers to expand uart_port, rather than having yet
 *      more levels of structures.
 */
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
        struct uart_state *state;
        struct tty_port *port;
        int ret = 0;
        struct device *tty_dev;
        int num_groups;

        BUG_ON(in_interrupt());

        if (uport->line >= drv->nr)
                return -EINVAL;

        state = drv->state + uport->line;
        port = &state->port;

        mutex_lock(&port_mutex);
        mutex_lock(&port->mutex);
        if (state->uart_port) {
                ret = -EINVAL;
                goto out;
        }

        /* Link the port to the driver state table and vice versa */
        atomic_set(&state->refcount, 1);
        init_waitqueue_head(&state->remove_wait);
        state->uart_port = uport;
        uport->state = state;

        state->pm_state = UART_PM_STATE_UNDEFINED;
        uport->cons = drv->cons;
        uport->minor = drv->tty_driver->minor_start + uport->line;
        uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
                                drv->tty_driver->name_base + uport->line);
        if (!uport->name) {
                ret = -ENOMEM;
                goto out;
        }

        /*
         * If this port is a console, then the spinlock is already
         * initialised.
         */
        if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
                spin_lock_init(&uport->lock);
                lockdep_set_class(&uport->lock, &port_lock_key);
        }
        if (uport->cons && uport->dev)
                of_console_check(uport->dev->of_node, uport->cons->name, uport->line);

        uart_configure_port(drv, state, uport);

        port->console = uart_console(uport);

        num_groups = 2;
        if (uport->attr_group)
                num_groups++;

        uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
                                    GFP_KERNEL);
        if (!uport->tty_groups) {
                ret = -ENOMEM;
                goto out;
        }
        uport->tty_groups[0] = &tty_dev_attr_group;
        if (uport->attr_group)
                uport->tty_groups[1] = uport->attr_group;

        /*
         * Register the port whether it's detected or not.  This allows
         * setserial to be used to alter this port's parameters.
         */
        tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
                        uport->line, uport->dev, port, uport->tty_groups);
        if (likely(!IS_ERR(tty_dev))) {
                device_set_wakeup_capable(tty_dev, 1);
        } else {
                dev_err(uport->dev, "Cannot register tty device on line %d\n",
                       uport->line);
        }

        /*
         * Ensure UPF_DEAD is not set.
         */
        uport->flags &= ~UPF_DEAD;

 out:
        mutex_unlock(&port->mutex);
        mutex_unlock(&port_mutex);

        return ret;
}

这样uart的初始化,就基本上结束了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值