全志A64 U-BOOT起始串口初始化流程详解
串口执行流程,首先是/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起始串口初始化流程详解相关推荐
- U-Boot启动流程详解
参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...
- 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...
- 杂志订阅管理系统c++_电池管理系统BMS功能安全开发流程详解
点击上面 "电动知家"可以订阅哦! BMS功能安全开发流程详解 BMS和ISO26262 - BMS & ISO26262简介 BMS即Battery Management ...
- linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一
[快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...
- Spring Boot 2.0 的配置详解(图文教程)
本文来自作者 泥瓦匠 @ bysocket.com 在 GitChat 上分享 「Spring Boot 2.0 的配置详解(图文教程)」 编辑 | 哈比 Spring Boot 配置,包括自动配置和 ...
- mtk 电池曲线_mtk 电池驱动流程详解
mtk 电池驱动流程详解 充电算法,充9S停1S 电池温度高于50,充电器电压为>6.5V 停止充电, 充电电压最大值是6500mV 最小值是4400mV 3.4V为开机电压,电压大于3.4V才 ...
- hadoop作业初始化过程详解(源码分析第三篇)
(一)概述 我们在上一篇blog已经详细的分析了一个作业从用户输入提交命令到到达JobTracker之前的各个过程.在作业到达JobTracker之后初始化之前,JobTracker会通过submit ...
- 基于spark mllib_Spark高级分析指南 | 机器学习和分析流程详解(下)
- 点击上方"中国统计网"订阅我吧!- 我们在Spark高级分析指南 | 机器学习和分析流程详解(上)快速介绍了一下不同的高级分析应用和用力,从推荐到回归.但这只是实际高级分析过程 ...
- [nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)...
:由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...
最新文章
- javascript_core_01之数据类型与运算
- 重写equals方法的hashcode_Java equals 和 hashCode 的这几个问题可以说明白吗?
- Win10安装NodeJS
- 关于MySQL字符集和校对集问题
- 《数据库原理与应用》(第三版) 第7章 索引和视图 基础 习题参考答案
- js 短信验证码 6位数字
- CCF202006-1 线性分类器
- 机器学习中常见的距离公式
- 电子计算机第一代到第四代,从第一代电子计算机到第四代计算机的体系结构都是由运算器、控制器、存储器、输入设备和输出设备组成的,称为( )体系结构。...
- 一体机or复合机?企业文印设备该怎么选
- Android小项目:计算器
- Python 疾病诊断归一化
- linux 图形设计软件,Ubuntu下使用Blender 3D图形专业设计工具
- 2021年下半年系统集成项目管理工程师下午真题及答案解析
- Android 提升效率
- 数据文件offline online
- 编写python爬虫基础_0基础如何快速写python爬虫
- 基于JAVA共享汽车管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
- 什么是SDK,它是怎样威胁我们的隐私?
- BatchNorm的通俗解释
热门文章
- react解析md文件
- Java小程序简易多客户端聊天服务器
- 【学术相关】好文:新手小白如何准备考研?
- 医疗机器人在手术中的应用:如何借助人工智能技术提高手术安全性和效率
- 数据库.net3.5安装失败错误0X80070422
- 树莓派GPIO驱动ST7735S主控TFT液晶屏显示图片和文字
- 1147: 5005 奶牛计算器(-2进制)
- android 豆瓣图书api 二维码,根据ISBN获取豆瓣API提供的图书信息
- LVGL学习笔记7 - GD32平台优化
- No qualifying bean of type ‘com.suming.crowd.service.api.AdminService‘ available: expected at least