整体框架
串口驱动隶属于linux tty驱动框架的一部分, 其整体框架可参考下图,方便有个直观印象。

前言
1、SPI,I2C驱动框架分为控制器驱动和设备驱动,然后芯片原厂负责编写控制器驱动和设备树,然后普通的驱动工程师编写/适配设备驱动和相应的设备树文件。
2、对于UART来说他没有所谓的设备驱动,只有控制器驱动,控制器驱动是由芯片原厂提供,如果不是在芯片原厂工作,一般需要编写uart驱动。如果是在芯片原厂需要编写uart驱动层,只需要编写uart硬件驱动层,Linux内核已经提供了串口核心层、tty层。
根据整体框架图。针对uart链路进行分析,以串口read和write为例,梳理整体流程如下
write流程

流程问题1:
为什么用户对文件操作后就是直接进入tty层,而不是直接进入到硬件驱动层呢?
tty文件存在其自身的字符接口:
drivers/tty/tty_io.c文件
static const struct file_operations tty_fops
static const struct file_operations console_fops
static const struct file_operations hung_up_tty_fops
注册tty文件操作的接口函数为
drivers/tty/tty_io.c文件
static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
unsigned int index, unsigned int count)
上一层调用该函数的接口只有两个
drivers/tty/tty_io.c文件
struct device *tty_register_device_attr(struct tty_driver *driver,
unsigned index, struct device *device,
void *drvdata,
const struct attribute_group **attr_grp)
int tty_register_device(struct tty_driver *driver)
情况1 - tty_cdev_add直接调用
tty_cdev_add在tty_register_driver存在直接调用情况
tty_register_driver内部tty_cdev_add函数调用存在条件判断如下:

而flags值在uart_register_driver函数中的赋值情况如下,并不满足条件

因此,硬件驱动层tegra_uart_init函数调用的uart_register_driver并未完成对tty文件操作的注册。
总体调用关系如下

情况2 - tty_register_device_attr和tty_register_device间接调用
tty_register_device_attr函数在两处被调用:
tty_io.c----tty_register_device
tty_port.c---tty_port_register_device_attr
tty_io.c----tty_register_device也是因flags不满足条件在tty层tty_register_driver函数中未进行调用

流程可归纳为

tty_port.c---tty_port_register_device_attr则是在串口核心层uart_add_one_port中调用,层层追溯至硬件驱动层的tegra_uart_probe()函数中,该函数是在platform驱动框架下匹配设备树后执行。
调用流程归纳如下:

小结
1、用户对串口文件操作后直接进入tty层的原因在于驱动初始化时,将串口文件操作接口赋值为了tty文件操作函数,而不是不同的字符设备文件操作函数,使得打开串口文件时相当于打开了tty设备,所以直接进入到tty层。
2、在串口初始化时,文件操作接口赋值实际上有三次机会,但由于flags初值的原因,导致前两次在tty层并未直接完成赋值,这是因为串口属于硬件设备,需要等待硬件驱动完成后才能获得其文件节点,然后再进行文件操作接口赋值。