imx6 uart分析

 
本文主要记录:
  1、uart设备注册
  2、uart驱动注册
  3、上层应用调用
有些地方理解的还不是很透彻,希望指正。

1、uart设备注册过程 MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board") /* Maintainer: Freescale Semiconductor, Inc. */ .boot_params = MX6_PHYS_OFFSET + 0x100, .fixup = fixup_mxc_board, .map_io = mx6_map_io, .init_irq = mx6_init_irq, .init_machine = mx6_sabresd_board_init, ------------------------+ .timer = &mx6_sabresd_timer, | .reserve = mx6q_sabresd_reserve, | MACHINE_END | | static void __init mx6_sabresd_board_init(void) <-----------------------+ { int i; int ret; struct clk *clko, *clko2; struct clk *new_parent; int rate; if (cpu_is_mx6q()) mxc_iomux_v3_setup_multiple_pads(mx6q_sabresd_pads, ----+ ARRAY_SIZE(mx6q_sabresd_pads)); | else if (cpu_is_mx6dl()) { | mxc_iomux_v3_setup_multiple_pads(mx6dl_sabresd_pads, | ARRAY_SIZE(mx6dl_sabresd_pads)); | } | ... ... | mx6q_sabresd_init_uart(); ------------------------|----+ ... ... | | } | | //设置IOMUX寄存器,将有功能复用的引脚设置为UART功能 | | static iomux_v3_cfg_t mx6q_sabresd_pads[] = { <---------------+ | ... ... | /* UART1 for debug */ | MX6Q_PAD_CSI0_DAT10__UART1_TXD, | MX6Q_PAD_CSI0_DAT11__UART1_RXD, | | /* UART2*/ | MX6Q_PAD_EIM_D26__UART2_TXD, | MX6Q_PAD_EIM_D27__UART2_RXD, | | /* UART3 for gps */ | MX6Q_PAD_EIM_D24__UART3_TXD, | MX6Q_PAD_EIM_D25__UART3_RXD, | | /* UART4*/ | MX6Q_PAD_KEY_COL0__UART4_TXD, | MX6Q_PAD_KEY_ROW0__UART4_RXD, | | /* UART5*/ | MX6Q_PAD_KEY_COL1__UART5_TXD, | MX6Q_PAD_KEY_ROW1__UART5_RXD, | ... ... | }; | | //uart初始化 | static inline void mx6q_sabresd_init_uart(void) <----------------+ { imx6q_add_imx_uart(0, NULL); imx6q_add_imx_uart(1, NULL); imx6q_add_imx_uart(2, NULL); imx6q_add_imx_uart(3, NULL); imx6q_add_imx_uart(4, NULL); } #define imx6q_add_imx_uart(id, pdata) \ imx_add_imx_uart_1irq(&imx6q_imx_uart_data[id], pdata) // 注册为平台设备 struct platform_device *__init imx_add_imx_uart_1irq( const struct imx_imx_uart_1irq_data *data, const struct imxuart_platform_data *pdata) { struct resource res[] = { { .start = data->iobase, .end = data->iobase + data->iosize - 1, .flags = IORESOURCE_MEM, }, { .start = data->irq, .end = data->irq, .flags = IORESOURCE_IRQ, }, }; return imx_add_platform_device("imx-uart", data->id, res, ARRAY_SIZE(res), pdata, sizeof(*pdata)); | } | | V static inline struct platform_device *imx_add_platform_device( const char *name, int id, const struct resource *res, unsigned int num_resources, const void *data, size_t size_data) { return imx_add_platform_device_dmamask( name, id, res, num_resources, data, size_data, 0); } 2、驱动注册 kernel/drivers/tty/serial/imx.c static int __init imx_serial_init(void) { int ret; printk(KERN_INFO "Serial: IMX driver\n"); ret = uart_register_driver(&imx_reg); ----------------------------+ if (ret) | | return ret; +----------------------------+ | //注册uart驱动 | | ret = platform_driver_register(&serial_imx_driver); | | if (ret != 0) | | | uart_unregister_driver(&imx_reg); | | | +--------------|-------|----+ return 0; | | | } | | | | | | static struct uart_driver imx_reg = { <----------------------+ | | .owner = THIS_MODULE, | | .driver_name = DRIVER_NAME, // IMX-uart | | .dev_name = DEV_NAME, // ttymxc | | .major = SERIAL_IMX_MAJOR, // 207 | | .minor = MINOR_START, // 16 | | .nr = ARRAY_SIZE(imx_ports), | | .cons = IMX_CONSOLE, | | }; | | | | // kernel/drivers/tty/serial/serial_core.c | | int uart_register_driver(struct uart_driver *drv) <----------+ | { | struct tty_driver *normal; | int i, retval; | | BUG_ON(drv->state); | //分配uart_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; | //所有的串口只有一个tty_driver | normal = alloc_tty_driver(drv->nr); | if (!normal) | goto out_kfree; | | drv->tty_driver = normal; | | normal->owner = drv->owner; | 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; | /* tty_driver和uart_driver结构体关联, | * 当驱动和设备匹配之后,调用probe函数, | * | */ | 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; | port->close_delay = 500; /* .5 seconds */ | port->closing_wait = 30000; /* 30 seconds */ | tasklet_init(&state->tlet, uart_tasklet_action, | (unsigned long)state); | } | | retval = tty_register_driver(normal); ------------------------+ | if (retval >= 0) | | return retval; | | | | put_tty_driver(normal); | | out_kfree: | | kfree(drv->state); | | out: | | return -ENOMEM; | | } | | // kernel/drivers/tty/tty_io.c | | int tty_register_driver(struct tty_driver *driver) <-------------------+ | { | int error; | int i; | dev_t dev; | void **p = NULL; | struct device *d; | | if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) { | p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL); | if (!p) | return -ENOMEM; | } | | 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); | /* 为设备分配主设备号和次设备号 | * 主设备号207,次设备号从16开始 | * 所以注册的串口设备次设备号是16,17,18,19... | */ | error = register_chrdev_region(dev, driver->num, driver->name); | } | if (error < 0) { | kfree(p); | return error; | } | | if (p) { | driver->ttys = (struct tty_struct **)p; | driver->termios = (struct ktermios **)(p + driver->num); | } else { | driver->ttys = NULL; | driver->termios = NULL; | } | | cdev_init(&driver->cdev, &tty_fops); | driver->cdev.owner = driver->owner; | error = cdev_add(&driver->cdev, dev, driver->num); | if (error) { | unregister_chrdev_region(dev, driver->num); | driver->ttys = NULL; | driver->termios = NULL; | kfree(p); | return error; | } | | 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; | } | } | } | proc_tty_register_driver(driver); | driver->flags |= TTY_DRIVER_INSTALLED; | return 0; | | err: | for (i--; i >= 0; i--) | tty_unregister_device(driver, i); | | mutex_lock(&tty_mutex); | list_del(&driver->tty_drivers); | mutex_unlock(&tty_mutex); | | unregister_chrdev_region(dev, driver->num); | driver->ttys = NULL; | driver->termios = NULL; | kfree(p); | return error; | } | | static struct platform_driver serial_imx_driver = { <----------------+ .probe = serial_imx_probe, .remove = serial_imx_remove, .suspend = serial_imx_suspend, .resume = serial_imx_resume, .driver = { .name = "imx-uart", .owner = THIS_MODULE, }, }; //当设备和驱动匹配之后,会调用probe函数,其中就指定了读写函数 static int serial_imx_probe(struct platform_device *pdev) { ... ... sport->port.ops = &imx_pops; -------+ ... ... | //将初始化用到的uart_driver和imx_port关联起来 | //最终就将操作imx_pops结构体定义的函数与上层应用关联起来 | //初始化的时候,就已经将uart_driver与tty_driver关联 | ret = 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, | .set_termios = imx_set_termios, | .type = imx_type, | .release_port = imx_release_port, | .request_port = imx_request_port, | .config_port = imx_config_port, | .verify_port = imx_verify_port, | #if defined(CONFIG_CONSOLE_POLL) | .poll_get_char = imx_poll_get_char, | .poll_put_char = imx_poll_put_char, | #endif | }; | V 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; 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; } state->uart_port = uport; state->pm_state = -1; uport->cons = drv->cons; uport->state = state; /* * 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); } uart_configure_port(drv, state, uport); /* * Register the port whether it's detected or not. This allows * setserial to be used to alter this ports parameters. */ tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev); if (likely(!IS_ERR(tty_dev))) { device_set_wakeup_capable(tty_dev, 1); } else { printk(KERN_ERR "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; } 3、 上层调用 kernel/drivers/tty/tty_io.c static const struct file_operations tty_fops = { .llseek = no_llseek, .read = tty_read, .write = tty_write, .poll = tty_poll, .unlocked_ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, .open = tty_open, .release = tty_release, .fasync = tty_fasync, }; // kernel/drivers/tty/tty_io.c static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct inode *inode = file->f_path.dentry->d_inode; struct tty_struct *tty = file_tty(file); struct tty_ldisc *ld; ssize_t ret; if (tty_paranoia_check(tty, inode, "tty_write")) return -EIO; if (!tty || !tty->ops->write || (test_bit(TTY_IO_ERROR, &tty->flags))) return -EIO; /* Short term debug to catch buggy drivers */ if (tty->ops->write_room == NULL) printk(KERN_ERR "tty driver %s lacks a write_room method.\n", tty->driver->name); ld = tty_ldisc_ref_wait(tty); if (!ld->ops->write) ret = -EIO; else ret = do_tty_write(ld->ops->write, tty, file, buf, count); tty_ldisc_deref(ld); | return ret; | } | V static inline ssize_t do_tty_write( ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), struct tty_struct *tty, struct file *file, const char __user *buf, size_t count) { ssize_t ret, written = 0; unsigned int chunk; ret = tty_write_lock(tty, file->f_flags & O_NDELAY); if (ret < 0) return ret; chunk = 2048; if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) chunk = 65536; if (count < chunk) chunk = count; /* write_buf/write_cnt is protected by the atomic_write_lock mutex */ if (tty->write_cnt < chunk) { unsigned char *buf_chunk; if (chunk < 1024) chunk = 1024; buf_chunk = kmalloc(chunk, GFP_KERNEL); if (!buf_chunk) { ret = -ENOMEM; goto out; } kfree(tty->write_buf); tty->write_cnt = chunk; tty->write_buf = buf_chunk; } /* Do the write .. */ for (;;) { size_t size = count; if (size > chunk) size = chunk; ret = -EFAULT; if (copy_from_user(tty->write_buf, buf, size)) break; ret = write(tty, file, tty->write_buf, size); -----------+ if (ret <= 0) | break; | written += ret; | buf += ret; | count -= ret; | if (!count) | break; | ret = -ERESTARTSYS; | if (signal_pending(current)) | break; | cond_resched(); | } | if (written) { | struct inode *inode = file->f_path.dentry->d_inode; | inode->i_mtime = current_fs_time(inode->i_sb); | ret = written; | } | out: | tty_write_unlock(tty); | return ret; | } | V //kernel/drivers/tty/n_tty.c static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) { const unsigned char *b = buf; DECLARE_WAITQUEUE(wait, current); int c; ssize_t retval = 0; /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */ if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) { retval = tty_check_change(tty); if (retval) return retval; } /* Write out any echoed characters that are still pending */ process_echoes(tty); add_wait_queue(&tty->write_wait, &wait); while (1) { set_current_state(TASK_INTERRUPTIBLE); if (signal_pending(current)) { retval = -ERESTARTSYS; break; } if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) { retval = -EIO; break; } if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { while (nr > 0) { ssize_t num = process_output_block(tty, b, nr); if (num < 0) { if (num == -EAGAIN) break; retval = num; goto break_out; } b += num; nr -= num; if (nr == 0) break; c = *b; if (process_output(c, tty) < 0) break; b++; nr--; } if (tty->ops->flush_chars) tty->ops->flush_chars(tty); } else { while (nr > 0) { c = tty->ops->write(tty, b, nr); -------------------+ if (c < 0) { | retval = c; | goto break_out; | } | if (!c) | break; | b += c; | nr -= c; | } | } | if (!nr) | break; | if (file->f_flags & O_NONBLOCK) { | retval = -EAGAIN; | break; | } | schedule(); | } | break_out: | __set_current_state(TASK_RUNNING); | remove_wait_queue(&tty->write_wait, &wait); | if (b - buf != nr && tty->fasync) | set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); | return (b - buf) ? b - buf : retval; | } | // kernel/drivers/tty/serial/imx.c | static void | imx_console_write(struct console *co, const char *s, unsigned int count) <-+ { struct imx_port *sport = imx_ports[co->index]; struct imx_port_ucrs old_ucr; unsigned int ucr1; unsigned long flags; int locked = 1; local_irq_save(flags); if (sport->port.sysrq) locked = 0; else spin_lock(&sport->port.lock); /* * First, save UCR1/2/3 and then disable interrupts */ imx_port_ucrs_save(&sport->port, &old_ucr); ucr1 = old_ucr.ucr1; if (cpu_is_mx1()) ucr1 |= MX1_UCR1_UARTCLKEN; ucr1 |= UCR1_UARTEN; ucr1 &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN); writel(ucr1, sport->port.membase + UCR1); writel(old_ucr.ucr2 | UCR2_TXEN, sport->port.membase + UCR2); uart_console_write(&sport->port, s, count, imx_console_putchar); /* * Finally, wait for transmitter to become empty * and restore UCR1/2/3 */ while (!(readl(sport->port.membase + USR2) & USR2_TXDC)); imx_port_ucrs_restore(&sport->port, &old_ucr); if (locked) spin_unlock(&sport->port.lock); local_irq_restore(flags); }

 

posted @ 2016-04-13 08:44  SuperTao1024  阅读(5448)  评论(0编辑  收藏  举报