串口执行流程,首先是/common/board_f.c中
先调用

void board_init_f(ulong boot_flags)

该函数是汇编代码进入C代码前在_main中调用的地址重定向前的设备初始化函数,其中调用

initcall_run_list(init_sequence_f)

来执行init_sequence_f数组中的相关初始化函数,该数组的定义也在本文件中,其定义如下

static init_fnc_t init_sequence_f[]

该序列中关于串口的函数主要有

init_baud_rate,      /* initialze baudrate settings */
serial_init,        /* serial communications setup */

第一个是初始化初始化波特率,注意这里的初始化波特率并不是设置串口波特率,而是先查找环境变量区看是否存在波特率,若不存在根据宏定义来将波特率保存在全局数据(global data)其函数实现如下:

static int init_baud_rate(void)
{gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);return 0;
}

第二个函数为串口初始化,主要关于硬件初始化,其实现函数如下:

int serial_init(void)
{return get_current()->start();
}

该函数位于driver/serial/serial.c中,其中get_current()函数实现如下:

static struct serial_device *get_current(void)
{struct serial_device *dev;if (!(gd->flags & GD_FLG_RELOC))dev = default_serial_console();else if (!serial_current)dev = default_serial_console();elsedev = serial_current;/* We must have a console device */if (!dev) {#ifdef CONFIG_SPL_BUILDputs("Cannot find console\n");hang();
#elsepanic("Cannot find console\n");
#endif}return dev;
}

该函数位于本文件中,其大致实现过程为,若第一次初始化串口(即在重定向前初始化串口),则将设备执行默认串口default_serial_console,若在重定向后再次初始化串口,则查找当前串口设备。其中default_serial_console实现函数如下:

__weak struct serial_device *default_serial_console(void)
{#if CONFIG_CONS_INDEX == 1return &eserial1_device;
#elif CONFIG_CONS_INDEX == 2return &eserial2_device;
#elif CONFIG_CONS_INDEX == 3return &eserial3_device;
#elif CONFIG_CONS_INDEX == 4return &eserial4_device;
#elif CONFIG_CONS_INDEX == 5return &eserial5_device;
#elif CONFIG_CONS_INDEX == 6return &eserial6_device;
#else
#error "Bad CONFIG_CONS_INDEX."
#endif
}

其中在全志的config配置文件即sun50iw101.h中定义了CONFIG_CONS_INDEX为1,故我们使用eserial1_device这个设备,继续查找eserial1_device的函数如下:

#if defined(CONFIG_SYS_NS16550_COM1)
DECLARE_ESERIAL_FUNCTIONS(1);
struct serial_device eserial1_device =INIT_ESERIAL_STRUCTURE(1, "eserial0");
#endif
#if defined(CONFIG_SYS_NS16550_COM2)
DECLARE_ESERIAL_FUNCTIONS(2);
struct serial_device eserial2_device =INIT_ESERIAL_STRUCTURE(2, "eserial1");
#endif
#if defined(CONFIG_SYS_NS16550_COM3)
DECLARE_ESERIAL_FUNCTIONS(3);
struct serial_device eserial3_device =INIT_ESERIAL_STRUCTURE(3, "eserial2");
#endif
#if defined(CONFIG_SYS_NS16550_COM4)
DECLARE_ESERIAL_FUNCTIONS(4);
struct serial_device eserial4_device =INIT_ESERIAL_STRUCTURE(4, "eserial3");
#endif
#if defined(CONFIG_SYS_NS16550_COM5)
DECLARE_ESERIAL_FUNCTIONS(5);
struct serial_device eserial5_device =INIT_ESERIAL_STRUCTURE(5, "eserial4");
#endif
#if defined(CONFIG_SYS_NS16550_COM6)
DECLARE_ESERIAL_FUNCTIONS(6);
struct serial_device eserial6_device =INIT_ESERIAL_STRUCTURE(6, "eserial5");
#endif

该文件位于/drivers/serial/serial_ns16550.c中,由上面代码可以看出该串口设备通过INIT_ESERIAL_STRUCTURE(1, “eserial0”);来初始化,继续查找这个函数如下:

#define INIT_ESERIAL_STRUCTURE(port, __name) {   \.name  = __name,          \.start = eserial##port##_init,        \.stop  = NULL,                \.setbrg    = eserial##port##_setbrg,  \.getc  = eserial##port##_getc,        \.tstc  = eserial##port##_tstc,        \.putc  = eserial##port##_putc,        \.puts  = eserial##port##_puts,        \
}

可以看出它是一个宏定义,定义了该结构体内相关的函数与名称等。由此我们可以得出串口初始化函数中的get_current()得到的为INIT_ESERIAL_STRUCTURE(1, eserial0) 的结构体,由于初始化调用的是get_current()->start(),故实际运行的函数为eserial##port##_init()这个函数(其中##为连接符的作用即eserial##1##_init等效于eserial1_init)我们继续查找eserial##port##_init()这个函数,经过查找其实现过程如下:

#define DECLARE_ESERIAL_FUNCTIONS(port) \static int  eserial##port##_init(void) \{ \int clock_divisor; \clock_divisor = calc_divisor(serial_ports[port-1]); \NS16550_init(serial_ports[port-1], clock_divisor); \return 0 ; \} \static void eserial##port##_setbrg(void) \{ \serial_setbrg_dev(port); \} \static int  eserial##port##_getc(void) \{ \return serial_getc_dev(port); \} \static int  eserial##port##_tstc(void) \{ \return serial_tstc_dev(port); \} \static void eserial##port##_putc(const char c) \{ \serial_putc_dev(port, c); \} \static void eserial##port##_puts(const char *s) \{ \serial_puts_dev(port, s); \}

也在本文件下,可以看出其实现过程是先调用clock_divisor = calc_divisor(serial_ports[port-1]);来配置时钟,该函数的实现过程如下:

static int calc_divisor (NS16550_t port)
{#ifdef CONFIG_OMAP1510/* If can't cleanly clock 115200 set div to 1 */if ((CONFIG_SYS_NS16550_CLK == 12000000) && (gd->baudrate == 115200)) {port->osc_12m_sel = OSC_12M_SEL;   /* enable 6.5 * divisor */return (1);               /* return 1 for base divisor */}port->osc_12m_sel = 0;          /* clear if previsouly set */
#endif
#ifdef CONFIG_OMAP1610/* If can't cleanly clock 115200 set div to 1 */if ((CONFIG_SYS_NS16550_CLK == 48000000) && (gd->baudrate == 115200)) {return (26);       /* return 26 for base divisor */}
#endif#define MODE_X_DIV 16/* Compute divisor value. Normally, we should simply return:*   CONFIG_SYS_NS16550_CLK) / MODE_X_DIV / gd->baudrate* but we need to round that value by adding 0.5.* Rounding is especially important at high baud rates.*/return (CONFIG_SYS_NS16550_CLK + (gd->baudrate * (MODE_X_DIV / 2))) /(MODE_X_DIV * gd->baudrate);
}

也位于当前文件下,由于CONFIG_OMAP1510与CONFIG_OMAP1610没有定义所以实际运行的为#define MODE_X_DIV 16下面的函数CONFIG_SYS_NS16550_CLK 在sun50iw1p1.h中有定义为24M,gd->baudrate在之前board_f文件中初始化波特率时,已对其赋值,因此可直接计算出此时的分频系数。
然后我们再考虑NS16550_init(serial_ports[port-1], clock_divisor);这个函数,该函数是直接对外设寄存器进行串口初始化,其实现过程如下:

void NS16550_init(NS16550_t com_port, int baud_divisor)
{#if (defined(CONFIG_SPL_BUILD) && defined(CONFIG_OMAP34XX))/** On some OMAP3 devices when UART3 is configured for boot mode before* SPL starts only THRE bit is set. We have to empty the transmitter* before initialization starts.*/if ((serial_in(&com_port->lsr) & (UART_LSR_TEMT | UART_LSR_THRE))== UART_LSR_THRE) {serial_out(UART_LCR_DLAB, &com_port->lcr);serial_out(baud_divisor & 0xff, &com_port->dll);serial_out((baud_divisor >> 8) & 0xff, &com_port->dlm);serial_out(UART_LCRVAL, &com_port->lcr);serial_out(0, &com_port->mdr1);}
#endifwhile (!(serial_in(&com_port->lsr) & UART_LSR_TEMT));serial_out(CONFIG_SYS_NS16550_IER, &com_port->ier);
#if defined(CONFIG_OMAP) || defined(CONFIG_AM33XX) || \defined(CONFIG_TI81XX) || defined(CONFIG_AM43XX)serial_out(0x7, &com_port->mdr1); /* mode select reset TL16C750*/
#endifserial_out(UART_LCR_BKSE | UART_LCRVAL, &com_port->lcr);serial_out(0, &com_port->dll);serial_out(0, &com_port->dlm);serial_out(UART_LCRVAL, &com_port->lcr);serial_out(UART_MCRVAL, &com_port->mcr);serial_out(UART_FCRVAL, &com_port->fcr);serial_out(UART_LCR_BKSE | UART_LCRVAL, &com_port->lcr);serial_out(baud_divisor & 0xff, &com_port->dll);serial_out((baud_divisor >> 8) & 0xff, &com_port->dlm);serial_out(UART_LCRVAL, &com_port->lcr);
#if (defined(CONFIG_OMAP) && !defined(CONFIG_OMAP3_ZOOM2)) || \defined(CONFIG_AM33XX) || defined(CONFIG_SOC_DA8XX) || \defined(CONFIG_TI81XX) || defined(CONFIG_AM43XX)/* /16 is proper to hit 115200 with 48MHz */serial_out(0, &com_port->mdr1);
#endif /* CONFIG_OMAP */
#if defined(CONFIG_K2HK_EVM)serial_out(UART_REG_VAL_PWREMU_MGMT_UART_ENABLE, &com_port->regC);
#endif
}

该函数位于driver/serial/ns16550.c中,其中实际使用的只有两个函数,serial_in(x),与serial_out(x,y),其中serial_in(x)函数表示从串口寄存器x中读取数据返回,serial_out(x,y)则是向串口寄存器y中写入x,这两个函数的实现实际上就是两个宏定义,指向标准IO输入输出的宏定义,如下:

#define serial_out(x, y) writeb(x, y)
#define serial_in(y)        readb(y)

它们也位于本文件下。
下面说下各寄存器的地址,首先是void NS16550_init(NS16550_t com_port, int baud_divisor)输入函数com_port的地址,该函数的调用函数为NS16550_init(serial_ports[port-1], clock_divisor); 所以,地址为serial_ports[port-1],的地址,serial_ports的定义为:

static NS16550_t serial_ports[6] = {#ifdef CONFIG_SYS_NS16550_COM1(NS16550_t)CONFIG_SYS_NS16550_COM1,
#elseNULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM2(NS16550_t)CONFIG_SYS_NS16550_COM2,
#elseNULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM3(NS16550_t)CONFIG_SYS_NS16550_COM3,
#elseNULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM4(NS16550_t)CONFIG_SYS_NS16550_COM4,
#elseNULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM5(NS16550_t)CONFIG_SYS_NS16550_COM5,
#elseNULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM6(NS16550_t)CONFIG_SYS_NS16550_COM6
#elseNULL
#endif
};

该定义位于driver/serial/serial_ns16550.c中,可以看出初始地址为CONFIG_SYS_NS16550_COM1,而CONFIG_SYS_NS16550_COM1的定义位于
configs/sun50iw1p1.h中,故对于不同的板子,如果串口基地址不一样,只要在配置文件中将自己板子的地址配置进去即可。

关于为什么选择ns16550的通用函数,以及在函数调用时怎么选择的它

首先为什么选择ns16550,我们在选择使用驱动中哪一种驱动时,要看自己板子芯片里的串口相关寄存器地址,自己的串口相关寄存器地址分布与哪款通用的一致,则应该就选择哪款,关于寄存器,自己板子的可以直接看数据手册,关于ns16550我们直接看其结构配置的结构体即可,其定义的结构体如下:

struct NS16550 {UART_REG(rbr);       /* 0 */UART_REG(ier);       /* 1 */UART_REG(fcr);       /* 2 */UART_REG(lcr);       /* 3 */UART_REG(mcr);       /* 4 */UART_REG(lsr);       /* 5 */UART_REG(msr);       /* 6 */UART_REG(spr);       /* 7 */
#ifdef CONFIG_SOC_DA8XXUART_REG(reg8);      /* 8 */UART_REG(reg9);      /* 9 */UART_REG(revid1);    /* A */UART_REG(revid2);    /* B */UART_REG(pwr_mgmt);  /* C */UART_REG(mdr1);      /* D */
#elseUART_REG(mdr1);        /* 8 */UART_REG(reg9);      /* 9 */UART_REG(regA);      /* A */UART_REG(regB);      /* B */UART_REG(regC);      /* C */UART_REG(regD);      /* D */UART_REG(regE);      /* E */UART_REG(uasr);      /* F */UART_REG(scr);       /* 10*/UART_REG(ssr);       /* 11*/UART_REG(reg12); /* 12*/UART_REG(osc_12m_sel);   /* 13*/
#endif
};

其位于include/ns16550.h中,如果自己设备的寄存器与以上类似,即可选择该驱动,当然,其余串口驱动有着不同寄存器顺序,这里就不一一赘述了。
如果通用驱动中没有与自己设备相符合的,那么你只能自己仿照的已有的写一个自己的了。

关于函数调用时怎么选择的这个串口,我们可以查看/drivers/serial/Makefile如下:

obj-y += serial.oobj-$(CONFIG_ALTERA_UART) += altera_uart.o
obj-$(CONFIG_ALTERA_JTAG_UART) += altera_jtag_uart.o
obj-$(CONFIG_ARM_DCC) += arm_dcc.o
obj-$(CONFIG_ATMEL_USART) += atmel_usart.o
obj-$(CONFIG_LPC32XX_HSUART) += lpc32xx_hsuart.o
obj-$(CONFIG_MCFUART) += mcfuart.o
obj-$(CONFIG_OPENCORES_YANU) += opencores_yanu.o
obj-$(CONFIG_SYS_NS16550) += ns16550.o
obj-$(CONFIG_S5P) += serial_s5p.o
obj-$(CONFIG_SYS_NS16550_SERIAL) += serial_ns16550.o
obj-$(CONFIG_IMX_SERIAL) += serial_imx.o
obj-$(CONFIG_KS8695_SERIAL) += serial_ks8695.o
obj-$(CONFIG_MAX3100_SERIAL) += serial_max3100.o
obj-$(CONFIG_MXC_UART) += serial_mxc.o
obj-$(CONFIG_PL010_SERIAL) += serial_pl01x.o
obj-$(CONFIG_PL011_SERIAL) += serial_pl01x.o
obj-$(CONFIG_PXA_SERIAL) += serial_pxa.o
obj-$(CONFIG_SA1100_SERIAL) += serial_sa1100.o
obj-$(CONFIG_S3C24X0_SERIAL) += serial_s3c24x0.o
obj-$(CONFIG_XILINX_UARTLITE) += serial_xuartlite.o
obj-$(CONFIG_SANDBOX_SERIAL) += sandbox.o
obj-$(CONFIG_SCIF_CONSOLE) += serial_sh.o
obj-$(CONFIG_ZYNQ_SERIAL) += serial_zynq.o
obj-$(CONFIG_BFIN_SERIAL) += serial_bfin.o
obj-$(CONFIG_FSL_LPUART) += serial_lpuart.o
obj-$(CONFIG_MXS_AUART) += mxs_auart.o
obj-$(CONFIG_ARC_SERIAL) += serial_arc.oifndef CONFIG_SPL_BUILD
obj-$(CONFIG_USB_TTY) += usbtty.o
endif

该MAKEFILE几乎将所有的驱动都链接编译了,但实际上,由于我们sun50iw1p1.h中仅定义了CONFIG_SYS_NS16550_SERIAL与CONFIG_SYS_NS16550,故其余的在是不会被编译链接的。

其余外设与此类似。

全志A64 U-BOOT起始串口初始化流程详解相关推荐

  1. U-Boot启动流程详解

    参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...

  2. 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  3. 杂志订阅管理系统c++_电池管理系统BMS功能安全开发流程详解

    点击上面 "电动知家"可以订阅哦! BMS功能安全开发流程详解 BMS和ISO26262 - BMS & ISO26262简介 BMS即Battery Management ...

  4. linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一

    [快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...

  5. Spring Boot 2.0 的配置详解(图文教程)

    本文来自作者 泥瓦匠 @ bysocket.com 在 GitChat 上分享 「Spring Boot 2.0 的配置详解(图文教程)」 编辑 | 哈比 Spring Boot 配置,包括自动配置和 ...

  6. mtk 电池曲线_mtk 电池驱动流程详解

    mtk 电池驱动流程详解 充电算法,充9S停1S 电池温度高于50,充电器电压为>6.5V 停止充电, 充电电压最大值是6500mV 最小值是4400mV 3.4V为开机电压,电压大于3.4V才 ...

  7. hadoop作业初始化过程详解(源码分析第三篇)

    (一)概述 我们在上一篇blog已经详细的分析了一个作业从用户输入提交命令到到达JobTracker之前的各个过程.在作业到达JobTracker之后初始化之前,JobTracker会通过submit ...

  8. 基于spark mllib_Spark高级分析指南 | 机器学习和分析流程详解(下)

    - 点击上方"中国统计网"订阅我吧!- 我们在Spark高级分析指南 | 机器学习和分析流程详解(上)快速介绍了一下不同的高级分析应用和用力,从推荐到回归.但这只是实际高级分析过程 ...

  9. [nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)...

    :由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...

最新文章

  1. javascript_core_01之数据类型与运算
  2. 重写equals方法的hashcode_Java equals 和 hashCode 的这几个问题可以说明白吗?
  3. Win10安装NodeJS
  4. 关于MySQL字符集和校对集问题
  5. 《数据库原理与应用》(第三版) 第7章 索引和视图 基础 习题参考答案
  6. js 短信验证码 6位数字
  7. CCF202006-1 线性分类器
  8. 机器学习中常见的距离公式
  9. 电子计算机第一代到第四代,从第一代电子计算机到第四代计算机的体系结构都是由运算器、控制器、存储器、输入设备和输出设备组成的,称为( )体系结构。...
  10. 一体机or复合机?企业文印设备该怎么选
  11. Android小项目:计算器
  12. Python 疾病诊断归一化
  13. linux 图形设计软件,Ubuntu下使用Blender 3D图形专业设计工具
  14. 2021年下半年系统集成项目管理工程师下午真题及答案解析
  15. Android 提升效率
  16. 数据文件offline online
  17. 编写python爬虫基础_0基础如何快速写python爬虫
  18. 基于JAVA共享汽车管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
  19. 什么是SDK,它是怎样威胁我们的隐私?
  20. BatchNorm的通俗解释

热门文章

  1. react解析md文件
  2. Java小程序简易多客户端聊天服务器
  3. 【学术相关】好文:新手小白如何准备考研?
  4. 医疗机器人在手术中的应用:如何借助人工智能技术提高手术安全性和效率
  5. 数据库.net3.5安装失败错误0X80070422
  6. 树莓派GPIO驱动ST7735S主控TFT液晶屏显示图片和文字
  7. 1147: 5005 奶牛计算器(-2进制)
  8. android 豆瓣图书api 二维码,根据ISBN获取豆瓣API提供的图书信息
  9. LVGL学习笔记7 - GD32平台优化
  10. No qualifying bean of type ‘com.suming.crowd.service.api.AdminService‘ available: expected at least