Cubietruck开发板SPL阶段的preloader_console_init()分析

本文详细解析了U-Boot SPL中串口初始化过程,包括preloader_console_init函数的作用及内部实现,串口初始化函数serial_init的具体流程,以及NS16550驱动与特定硬件平台UART间的联系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<common/spl/spl.c>中

/*
 * This requires UART clocks to be enabled.  In order for this to work the
 * caller must ensure that the gd pointer is valid.
 */
void preloader_console_init(void)
{
	gd->bd = &bdata;
	gd->baudrate = CONFIG_BAUDRATE;

	serial_init();		/* serial communications setup */

	gd->have_console = 1;

	puts("\nU-Boot SPL " PLAIN_VERSION " (" U_BOOT_DATE " - " \
			U_BOOT_TIME ")\n");
#ifdef CONFIG_SPL_DISPLAY_PRINT
	spl_display_print();
#endif
}
preloader_console_init()主要对gd的某些成员变量赋值或者说初始化(用于初始化串口)、

然后调用serial_init()初始化串口、最后如果串口初始化成功(serial_init()成功返回)打印一些相关信息。

下面主要分析一下串口的初始化也就是serial_init()函数,

/**
 * serial_init() - Initialize currently selected serial port
 *
 * This function initializes the currently selected serial port. This
 * usually involves setting up the registers of that particular port,
 * enabling clock and such. This function uses the get_current() call
 * to determine which port is selected.
 *
 * Returns 0 on success, negative on error.
 */
int serial_init(void)
{
	return get_current()->start();
}
很明白得到单签的串口并start;

/**
 * get_current() - Return pointer to currently selected serial port
 *
 * This function returns a pointer to currently selected serial port.
 * The currently selected serial port is altered by serial_assign()
 * function.
 *
 * In case this function is called before relocation or before any serial
 * port is configured, this function calls default_serial_console() to
 * determine the serial port. Otherwise, the configured serial port is
 * returned.
 *
 * Returns pointer to the currently selected serial port on success,
 * NULL on error.
 */
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;
}
分析!(gd->flags & GD_FLG_RELOC)为真,所以dev = default_serial_console();

搜索文本并分析makefile文件可知函数定义在<drivers/serial/serial_ns16550.c>中

__weak struct serial_device *default_serial_console(void)
{
#if CONFIG_CONS_INDEX == 1
	return &eserial1_device;
#elif CONFIG_CONS_INDEX == 2
	return &eserial2_device;
#elif CONFIG_CONS_INDEX == 3
	return &eserial3_device;
#elif CONFIG_CONS_INDEX == 4
	return &eserial4_device;
#elif CONFIG_CONS_INDEX == 5
	return &eserial5_device;
#elif CONFIG_CONS_INDEX == 6
	return &eserial6_device;
#else
#error "Bad CONFIG_CONS_INDEX."
#endif
}
由于本人新手,看到ns16550很奇怪,查看了一下对应的头文件中的说明

/*
 * NS16550 Serial Port
 * originally from linux source (arch/powerpc/boot/ns16550.h)
 *
 * Cleanup and unification
 * (C) 2009 by Detlev Zundel, DENX Software Engineering GmbH
 *
 * modified slightly to
 * have addresses as offsets from CONFIG_SYS_ISA_BASE
 * added a few more definitions
 * added prototypes for ns16550.c
 * reduced no of com ports to 2
 * modifications (c) Rob Taylor, Flying Pig Systems. 2000.
 *
 * added support for port on 64-bit bus
 * by Richard Danter (richard.danter@windriver.com), (C) 2005 Wind River Systems
 */
可知,这个是从linux中拿过来的东西,那么怎么可以用到这里呢?ns16550的寄存器跟

a20的uart寄存器一样吗??加入一样的话怎么移植??网上找了一下ns16550的寄存器,

跟这个a20的uart很相似。那么下面分析ns16550移植到这里的过程


回到default_serial_console(void)中,由于CONFIG_CONS_INDEX == 1为真,所以

return &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
分析INIT_ESERIAL_STRUCTURE(1, "eserial0");

/* Serial device descriptor */
#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,		\
}
其中的##是gnu的C扩展??貌似是哦,暂且当做是吧 大笑

继续分析

/* Multi serial device functions */
#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); \
	}

所以开始的get_current()->start();其实就是这里的

	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 int  eserial1_init(void) \
	{ \
		int clock_divisor; \
		clock_divisor = calc_divisor(serial_ports[0]); \
		NS16550_init(serial_ports[0], clock_divisor); \
		return 0 ; \
	} 
看到这里想到ns16550怎么与a20的uart联系上的呢?calc_divisor、NS16550_init

这两个函数都是ns16550的啊,所以只有通过serial_prots联系了,分析serial_ports吧

* Note: The port number specified in the functions is 1 based.
 *	 the array is 0 based.
 */
static NS16550_t serial_ports[6] = {
#ifdef CONFIG_SYS_NS16550_COM1
	(NS16550_t)CONFIG_SYS_NS16550_COM1,
#else
	NULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM2
	(NS16550_t)CONFIG_SYS_NS16550_COM2,
#else
	NULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM3
	(NS16550_t)CONFIG_SYS_NS16550_COM3,
#else
	NULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM4
	(NS16550_t)CONFIG_SYS_NS16550_COM4,
#else
	NULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM5
	(NS16550_t)CONFIG_SYS_NS16550_COM5,
#else
	NULL,
#endif
#ifdef CONFIG_SYS_NS16550_COM6
	(NS16550_t)CONFIG_SYS_NS16550_COM6
#else
	NULL
#endif
};
那么NS16550_t、CONFIG_SYS_NS16550_COM1又是什么呢?

#if !defined(CONFIG_SYS_NS16550_REG_SIZE) || (CONFIG_SYS_NS16550_REG_SIZE == 0)
#error "Please define NS16550 registers size."
#elif defined(CONFIG_SYS_NS16550_MEM32)
#define UART_REG(x) u32 x
#elif (CONFIG_SYS_NS16550_REG_SIZE > 0)
#define UART_REG(x)						   \
	unsigned char prepad_##x[CONFIG_SYS_NS16550_REG_SIZE - 1]; \
	unsigned char x;
#elif (CONFIG_SYS_NS16550_REG_SIZE < 0)
#define UART_REG(x)							\
	unsigned char x;						\
	unsigned char postpad_##x[-CONFIG_SYS_NS16550_REG_SIZE - 1];
#endif

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_DA8XX
	UART_REG(reg8);		/* 8 */
	UART_REG(reg9);		/* 9 */
	UART_REG(revid1);	/* A */
	UART_REG(revid2);	/* B */
	UART_REG(pwr_mgmt);	/* C */
	UART_REG(mdr1);		/* D */
#else
	UART_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
};

#define thr rbr
#define iir fcr
#define dll rbr
#define dlm ier

typedef struct NS16550 *NS16550_t;
NS16550_t是个结构体指针,对比ns16550的资料和a20的参考手册,

可知是ns16550寄存器的的结构体定义,

/* Serial & console */
#define CONFIG_SYS_NS16550
#define CONFIG_SYS_NS16550_SERIAL
/* ns16550 reg in the low bits of cpu reg */
#define CONFIG_SYS_NS16550_REG_SIZE	-4
#define CONFIG_SYS_NS16550_CLK		24000000
#define CONFIG_SYS_NS16550_COM1		SUNXI_UART0_BASE
#define CONFIG_SYS_NS16550_COM2		SUNXI_UART1_BASE
#define CONFIG_SYS_NS16550_COM3		SUNXI_UART2_BASE
#define CONFIG_SYS_NS16550_COM4		SUNXI_UART3_BASE
很明白了,所以(NS16550_t)CONFIG_SYS_NS16550_COM1其实就是

指定了一个SUNXI_UART0_BASE这个地址为NS16550类型的地址,这样

也就把ns16550跟a20的uart联系起来了。这里需要注意一下,NS16550的驱动

函数参数是CONFIG_SYS_NS16550_COM1 其实就是地址,所以要移植到其他

的芯片上的话只需要define这个地址就可以了(前提要求芯片的uart寄存器跟NS16550相似),

在这里define这地址的时候并不是直接define地址,而是SUNXI_UART0_BASE,其实相当于了

间接define地址(传递define、双层define),另外还要注意CONFIG_SYS_NS16550_REG_SIZE,貌似是大小端问题。

但是这里还想问:毕竟ns16550与a20的uart寄存器不是完全一样,只通过

define CONFIG_SYS_NS16550_COM1,ns16550的驱动就能完全使用吗?完全不用修改吗?


下面就是分析ns16550的驱动了,其实也就是把uart配置了什么样的模式

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);
	}
#endif

	while (!(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*/
#endif
	serial_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 */
}

分析的结果貌似是:115200、 8n1、开启fifo、禁收发中断

怪不得这个驱动可以直接用,超级简单的模式。


剩下的就是pritnf重定向、输出缓冲的问题了,再分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值