基于IMX6Q的uboot启动流程分析(4):uboot中的串口设备

基于IMX6Q的uboot启动流程分析(1):uboot入口函数
基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f
基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r
基于IMX6Q的uboot启动流程分析(4):uboot中的串口设备

第4章: uboot中的串口设备

本节主要是围绕uboot中的串口设备的初始化流程内容进行介绍。

4.1 uboot中串口相关结构体

4.1.1 serial_device

serial_device结构体用于描述串口设备,其定义在include/serial.h文件中,其内容为:

struct serial_device {
	/* enough bytes to match alignment of following func pointer */
	char	name[16];

	int	(*start)(void);  //串口初始化
	int	(*stop)(void); 
	void(*setbrg)(void); //硬件设置
	int	(*getc)(void);   //获取字符
	int	(*tstc)(void);	
	void(*putc)(const char c);  //发送字符
	void(*puts)(const char *s); //发送字符串
#if CONFIG_POST & CONFIG_SYS_POST_UART
	void(*loop)(int);
#endif
	struct serial_device	*next;
};

4.2 设置IOMUX

init_sequence_f初始化序列中,board_early_init_f函数中有关于串口IOMUX设置,其实现函数为setup_iomux_uart,函数内容为:

static void setup_iomux_uart(void)
{
	SETUP_IOMUX_PADS(uart1_pads);
}

4.3 serial_init硬件初始化

在调用serial_init函数前,先调用init_sequence_f初始化序列中的init_baud_rate函数,从环境变量中获取波特率,其函数内容为:

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

init_sequence_f初始化序列中,serial_init函数用于初始化串口,函数定义在drivers/serial/serial.c文件中,其内容为:

int serial_init(void)
{
	gd->flags |= GD_FLG_SERIAL_READY;
	return get_current()->start();
}

serial_init函数中,分为两步:

  1. 调用get_current函数获取当前的使用的串口;
  2. 调用串口的start函数进行初始化。
4.3.1 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();
	else
		dev = serial_current;

	/* We must have a console device */
	if (!dev) {
#ifdef CONFIG_SPL_BUILD
		puts("Cannot find console\n");
		hang();
#else
		panic("Cannot find console\n");
#endif
	}

	return dev;
}

上述内容中,若uboot未完成重定位工作,将使用默认提供串口设备;若已经完成重定位,则使用已有的串口。

通过default_serial_console函数来获取默认的串口设备,在IMX6Q中使用的是drivers/serial/serial_mxc.c文件中的default_serial_console函数,其内容为:

__weak struct serial_device *default_serial_console(void)
{
	return &mxc_serial_drv;
}

上述代码中,返回的设备结构体信息为:

static struct serial_device mxc_serial_drv = {
	.name	= "mxc_serial",
	.start	= mxc_serial_init,
	.stop	= NULL,
	.setbrg	= mxc_serial_setbrg,
	.putc	= mxc_serial_putc,
	.puts	= default_serial_puts,
	.getc	= mxc_serial_getc,
	.tstc	= mxc_serial_tstc,
};

通过get_current函数,将mxc_serial_drv作为默认的串口设备,后续对其进行初始化。

4.3.2 start函数

uboot串口通过设备结构体成员函数start进行初始化操作,mxc_serial_drv串口设备的成员函数start由上得知,调用mxc_serial_init函数,其内容为:

static int mxc_serial_init(void)
{
	_mxc_serial_init(mxc_base, false);

	serial_setbrg();

	return 0;
}

上述代码中,调用函数为serial_setbrg进行硬件初始化,定义在drivers/serial/serial.c文件,其内容为:

void serial_setbrg(void)
{
	get_current()->setbrg();
}

函数最终调用的是串口设备结构体函数成员setbrg,IMX6Q串口调用mxc_serial_setbrg函数,其内容为:

static void mxc_serial_setbrg(void)
{
	u32 clk = imx_get_uartclk();

	if (!gd->baudrate)
		gd->baudrate = CONFIG_BAUDRATE;

	_mxc_serial_setbrg(mxc_base, clk, gd->baudrate, false);
}

在初始化过程中,主要有以下三个工作:

  1. 获取串口时钟

    通过imx_get_uartclk函数来获取串口时钟,实际调用get_uart_clk函数,定义在arch/arm/mach-imx/mx6/clock.c文件中,内容为:

    static u32 get_uart_clk(void)
    {
    	u32 reg, uart_podf;
    	u32 freq = decode_pll(PLL_USBOTG, MXC_HCLK) / 6; /* static divider */
    	reg = __raw_readl(&imx_ccm->cscdr1);
    
    	if (is_mx6sl() || is_mx6sx() || is_mx6dqp() || is_mx6ul() ||
    	    is_mx6sll() || is_mx6ull()) {
    		if (reg & MXC_CCM_CSCDR1_UART_CLK_SEL)
    			freq = MXC_HCLK;
    	}
    
    	reg &= MXC_CCM_CSCDR1_UART_CLK_PODF_MASK;
    	uart_podf = reg >> MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET;
    
    	return freq / (uart_podf + 1);
    }
    
  2. 再次获取波特率

    获取波特率代码为:

    	if (!gd->baudrate)
    		gd->baudrate = CONFIG_BAUDRATE;
    

    若环境变量没有设置波特率,即init_baud_rate阶段获取的值为0,则通过宏CONFIG_BAUDRATE设置波特率。

  3. 串口初始化

    在串口初始化前,已经进行获取到了时钟与波特率,调用_mxc_serial_setbrg函数实现硬件初始化,其内容为:

    static void _mxc_serial_setbrg(struct mxc_uart *base, unsigned long clk,
    			       unsigned long baudrate, bool use_dte)
    {
    	u32 tmp;
    
    	tmp = RFDIV << UFCR_RFDIV_SHF;
    	if (use_dte)
    		tmp |= UFCR_DCEDTE;
    	else
    		tmp |= (TXTL << UFCR_TXTL_SHF) | (RXTL << UFCR_RXTL_SHF);
    	writel(tmp, &base->fcr);
    
    	writel(0xf, &base->bir);
    	writel(clk / (2 * baudrate), &base->bmr);
    
    	writel(UCR2_WS | UCR2_IRTS | UCR2_RXEN | UCR2_TXEN | UCR2_SRST,
    	       &base->cr2);
    	writel(UCR1_UARTEN, &base->cr1);
    }
    

4.4 console第一阶段初始化

init_sequence_f初始化序列中,调用console_init_f函数进行console第一阶段初始化,定义在common/console.c文件中,其内容为:

int console_init_f(void)
{
	gd->have_console = 1;

	console_update_silent();

	print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);

	return 0;
}

console在第一阶段初始化时,将gd->have_console变量置为1,代表已经有console;

在IMX6Q中,没有设置slient,则console_update_silent为空;

在IMX6Q中,PRE_CONSOLE_BUFFER没有使用,则print_pre_console_buffer函数同样为空。

**经过IOMUX设置、serila_init硬件初始化以及console第一阶段初始化后,uboot串口在重定位前所有的初始化已经完成。**其中最重要的是,通过get_current获取到串口设备mxc_serial_drv,为重定位后串口初始化提供函数接口,但此时的串口不能使用putc\get函数。

4.5 serial_initializ函数

uboot重定位后,调用关于串口第一个函数是serial_initializ,调用在init_sequence_r初始化列表中,定义在drivers/serial/serial.c文件中,函数的内容为:

int serial_initialize(void)
{
	atmel_serial_initialize();
	mcf_serial_initialize();
	mpc85xx_serial_initialize();
	mxc_serial_initialize();
	ns16550_serial_initialize();
	pl01x_serial_initialize();
	pxa_serial_initialize();
	sh_serial_initialize();
	mtk_serial_initialize();
	xen_debug_serial_initialize();

	serial_assign(default_serial_console()->name);

	return 0;
}

函数主要有两个工作,第一个工作是进一步初始化串口,在IMX6Q中调用mxc_serial_initialize函数;第二个工作是,调用serial_assign(default_serial_console()->name)根据串口name属性,将其设置为当前使用的串口。

下面详细介绍第一个工作内容,mxc_serial_initialize函数为:

void mxc_serial_initialize(void)
{
	serial_register(&mxc_serial_drv);
}

函数中调用serial_register,注册串口mxc_serial_drv设备,其部分内容为:

void serial_register(struct serial_device *dev)
{
	//...
	dev->next = serial_devices;
	serial_devices = dev;
}

上述内容中,首先将mxc_serial_drv设置插入串口设备列表,然后将其赋值给全局变量serial_devices

至此,uboot重定位后,完成mxc_serial_drv串口设备注册,下面需要串口基本输入输出等功能。

4.6 stdio_add_devices函数

init_sequence_r初始化序列中,调用stdio_add_devices函数执行stdio设备的注册,函数在common/stdio.c文件中定义。stdio设备层工作在serail层之上,但stdio设备并不限于serial设备,比如标准输出设备可能包括键盘、显示器,但我们这里只讨论stdio中的serail设备。

stdio_add_devices函数通过调用serial_stdio_init函数实现串口相关初始化,其定义在drivers/serial/serial.c文件中,内容为:

void serial_stdio_init(void)
{
	struct stdio_dev dev;
	struct serial_device *s = serial_devices;

	while (s) {
		memset(&dev, 0, sizeof(dev));

		strcpy(dev.name, s->name);
		dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;

		dev.start = serial_stub_start;
		dev.stop = serial_stub_stop;
		dev.putc = serial_stub_putc;
		dev.puts = serial_stub_puts;
		dev.getc = serial_stub_getc;
		dev.tstc = serial_stub_tstc;
		dev.priv = s;

		stdio_register(&dev);

		s = s->next;
	}
}

上述内容得知,主要有以下几点工作:

  1. 通过serial_devices全局变量获取mxc_serial_drv串口设备;
  2. 开辟stdio设备;
  3. 通过串口设备为stdio设备赋值变量;
  4. 调用stdio_register函数注册stdio设备。

下面主要介绍第三点工作stdio设备的如何通过串口进行输入输出。

  • 字符输出putc

    stdio设备调用函数serial_stub_putc实现字符输出,函数内容为:

    static void serial_stub_putc(struct stdio_dev *sdev, const char ch)
    {
    	struct serial_device *dev = sdev->priv;
    
    	dev->putc(ch);
    }
    

    从上述内容得知,实际调用mxc_serial_drv设备的putc函数,即mxc_serial_putc,其内容为:

    static void mxc_serial_putc(const char c)
    {
    	/* If \n, also do \r */
    	if (c == '\n')
    		serial_putc('\r');
    
    	writel(c, &mxc_base->txd);
    
    	/* wait for transmitter to be ready */
    	while (!(readl(&mxc_base->ts) & UTS_TXEMPTY))
    		WATCHDOG_RESET();
    }
    

    调用写函数,将需要发送的字符写道mxc_base->txd寄存器中,并检查是否发送完成。

  • 字符串输出puts

    stdio设备调用函数serial_stub_puts实现字符串输出,函数内容为:

    static void serial_stub_puts(struct stdio_dev *sdev, const char *str)
    {
    	struct serial_device *dev = sdev->priv;
    
    	dev->puts(str);
    }
    

    从上述内容得知,实际调用mxc_serial_drv设备的puts函数,即default_serial_puts,其内容为:

    void default_serial_puts(const char *s)
    {
    	struct serial_device *dev = get_current();
    	while (*s)
    		dev->putc(*s++);
    }
    

    从函数内容得知,发送字符串,就是调用putc循环发送字符。

  • 字符输入getc

    stdio设备调用函数serial_stub_getc实现字符输入,函数内容为:

    static int stdio_serial_getc(struct stdio_dev *dev)
    {
    	return serial_getc();
    }
    

    调用serial_getc函数,其内容为:

    int serial_getc(void)
    {
    	return get_current()->getc();
    }
    

    实际调用mxc_serial_drv设备的getc函数,即mxc_serial_getc,其内容为:

    static int mxc_serial_getc(void)
    {
    	while (readl(&mxc_base->ts) & UTS_RXEMPTY)
    		WATCHDOG_RESET();
    	return (readl(&mxc_base->rxd) & URXD_RX_DATA); /* mask out status from upper word */
    }
    

    调用读函数,将mxc_base->rxd寄存器数据读取出来,并检测是否接受完毕。

  • 输入检测tstc

    stdio设备调用函数serial_stub_tstc实现检测串口是否有输入,函数内容为:

    static int stdio_serial_tstc(struct stdio_dev *dev)
    {
    	return serial_tstc();
    }
    

    实际调用mxc_serial_drv设备的tstc函数,即mxc_serial_tstc,其内容为:

    static int one_time_rx_line_always_low_workaround_needed = 1;
    static int mxc_serial_tstc(void)
    {
    	/* If receive fifo is empty, return false */
    	if (readl(&mxc_base->ts) & UTS_RXEMPTY)
    		return 0;
    
    	/* Empty RX FIFO if receiver is stuck because of RXD line being low */
    	if (one_time_rx_line_always_low_workaround_needed) {
    		one_time_rx_line_always_low_workaround_needed = 0;
    		if (!(readl(&mxc_base->sr2) & USR2_RDR)) {
    			while (!(readl(&mxc_base->ts) & UTS_RXEMPTY)) {
    				(void) readl(&mxc_base->rxd);
    			}
    			return 0;
    		}
    	}
    
    	return 1;
    }
    

    调用读函数,读取mxc_base->ts寄存器中的FIFO状态,判断是否有输入。

    one_time_rx_line_always_low_workaround_neededmxc_serial_drv设备特有的检测,pl011串口驱动则是直接读取FIFO的状态寄存器来判断。

至此,uboot的串口stdio设备支持输入、输出以及输入检测功能。

4.7 console第二阶段初始化

在console第一阶段初始化过程中,只是设置console相关标志位,并未进行console实际的初始化。

console第二阶段初始化,通过调用console_init_r函数实现,定义在common/console.c文件中,其内容为:

 943 int console_init_r(void)
 944 {
 945         char *stdinname, *stdoutname, *stderrname;
 946         struct stdio_dev *inputdev = NULL, *outputdev = NULL, *errdev = NULL;
 947         int i;
 948         int iomux_err = 0;
 949         int flushpoint;
 950 
 951         /* update silent for env loaded from flash (initr_env) */
 952         if (console_update_silent())
 953                 flushpoint = PRE_CONSOLE_FLUSHPOINT1_SERIAL;
 954         else
 955                 flushpoint = PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL;
 956 
 957         /* set default handlers at first */
 958         gd->jt->getc  = serial_getc;
 959         gd->jt->tstc  = serial_tstc;
 960         gd->jt->putc  = serial_putc;
 961         gd->jt->puts  = serial_puts;
 962         gd->jt->printf = serial_printf;
 963 
 964         /* stdin stdout and stderr are in environment */
 965         /* scan for it */
 966         stdinname  = env_get("stdin");
 967         stdoutname = env_get("stdout");
 968         stderrname = env_get("stderr")SYS_CONSOLE_IS_IN_ENV;
 969 
 970         if (OVERWRITE_CONSOLE == 0) {   /* if not overwritten by config switch */
 971                 inputdev  = console_search_dev(DEV_FLAGS_INPUT,  stdinname);
 972                 outputdev = console_search_dev(DEV_FLAGS_OUTPUT, stdoutname);
 973                 errdev    = console_search_dev(DEV_FLAGS_OUTPUT, stderrname);
 974                 if (CONFIG_IS_ENABLED(CONSOLE_MUX)) {
 975                         iomux_err = iomux_doenv(stdin, stdinname);
 976                         iomux_err += iomux_doenv(stdout, stdoutname);
 977                         iomux_err += iomux_doenv(stderr, stderrname);
 978                         if (!iomux_err)
 979                                 /* Successful, so skip all the code below. */
 980                                 goto done;
 981                 }
 982         }
 983         /* if the devices are overwritten or not found, use default device */
 984         if (inputdev == NULL) {
 985                 inputdev  = console_search_dev(DEV_FLAGS_INPUT,  "serial");
 986         }
 987         if (outputdev == NULL) {
 988                 outputdev = console_search_dev(DEV_FLAGS_OUTPUT, "serial");
 989         }
 990         if (errdev == NULL) {
 991                 errdev    = console_search_dev(DEV_FLAGS_OUTPUT, "serial");
 992         }
 993         /* Initializes output console first */
 994         if (outputdev != NULL) {
 995                 /* need to set a console if not done above. */
 996                 console_doenv(stdout, outputdev);
 997         }
 998         if (errdev != NULL) {
 999                 /* need to set a console if not done above. */
1000                 console_doenv(stderr, errdev);
1001         }
1002         if (inputdev != NULL) {
1003                 /* need to set a console if not done above. */
1004                 console_doenv(stdin, inputdev);
1005         }
1006 
1007 done:
1008 
1009         if (!IS_ENABLED(CONFIG_SYS_CONSOLE_INFO_QUIET))
1010                 stdio_print_current_devices();
1011 
			 ...
1017 
1018         if (IS_ENABLED(CONFIG_SYS_CONSOLE_ENV_OVERWRITE)) {
1019                 /* set the environment variables (will overwrite previous env settings) */
1020                 for (i = 0; i < MAX_FILES; i++)
1021                         env_set(stdio_names[i], stdio_devices[i]->name);
1022         }
1023 
1024         gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */
1025 
1026         print_pre_console_buffer(flushpoint);
1027         return 0;
1028 }
  • 第958-962行,为console提供输入输出基本功能,其中serial_printf(其他函数同上)实现printf功能,其内容为:

    int serial_printf(const char *fmt, ...)
    {
    	va_list args;
    	uint i;
    	char printbuffer[CONFIG_SYS_PBSIZE];
    
    	va_start(args, fmt);
    
    	/* For this to work, printbuffer must be larger than
    	 * anything we ever want to print.
    	 */
    	i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);
    	va_end(args);
    
    	serial_puts(printbuffer);
    	return i;
    }
    

    函数首先调用vscnprintf函数处理需要打印的数据,然后调用串口的serial_puts输出函数,将处理完的数据进行打印。

  • 未分析完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值