ns16550串口驱动

之前学习的STM32F103的串口时,特点是:没有FIFO、参考手册写的很棒。

这里主要介绍一下a20的串口模块,之前把printf重定向分析了个大概,没有具体到ns16550的驱动,今天还是网上找了一些资料。发现a20貌似采用的是SISDA-16550-UART的IP参考手册地址:http://www.cs.indiana.edu/classes/b649-sjoh/reading/SISDA-16550-UART.pdf,参考手册的最后还给出了怎么与arm架构、AVR架构集成的例子,再次想起来前面分析的a20串口驱动是移植的ns16550串口驱动,a20很可能集成的就是SISDA-16550-UART。

所以这里就分析一下NS16550的驱动,分析过程中会与STM32F103的串口模块对比。

从大的方面说起,ns16550模块存在FIFO、MODEM,这些是stm32f103不存在的。与stm32共同的特点是中断、DMA。

再分析一下寄存器结构,a20参考手册


刚开始看的时候UART_RBR、UART_THR、UART_DLL的偏移地址竟然都是0x00,再看看代码中如何定义结构体

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
};
可以看出结构体根本没有对UART_THR、UART_DLL的定义,其实紧接着就是对偏移地址重复的寄存器定义

#define thr rbr
#define iir fcr
#define dll rbr
#define dlm ier
寄存器定义也找到了,那么问题来了,由于收发寄存器偏移地址一样,怎么读写呢?想写的时候它到底是发送寄存器还是接受寄存器?(没办法,新手想的就是这,还停留在STM32F103串口的经验中),看看参考手册吧


主要是对接收数据的说明和开启FIFO的介绍,刚开始看的时候,理解为:只有LCR的DR位被置1的时候,读RBR才有意义。所以想着直接设置LCR的DR位为1然后就能读RBR。这个想法是把UART_RBR、UART_THR、UART_DLL看做是BANK,就像内存BANK,通过高位来选择片选从而映射到不同内存BANK的地址。怎么验证呢?先看看手册吧,看了LCR寄存器的描述之后更郁闷了,压根没有DR位的定义。无语了,再来看看收发函数的代码吧

char NS16550_getc(NS16550_t com_port)
{
	while ((serial_in(&com_port->lsr) & UART_LSR_DR) == 0) {
#if !defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_TTY)
		extern void usbtty_poll(void);
		usbtty_poll();
#endif
		WATCHDOG_RESET();
	}
	return serial_in(&com_port->rbr);
}
我去,无语了,while ((serial_in(&com_port->lsr) & UART_LSR_DR) == 0)。怎么是LSR,再看看参考手册中LSR的描述,果然有DR位的描述,无语了,手册错误了。

OK继续,while ((serial_in(&com_port->lsr) & UART_LSR_DR) == 0)的意思是UART_LSR_DR为0就一直循环,而且循环体里根本没有对UART_LSR_DR位的操作,无语了,解释为由硬件逻辑自动置1吗?原来理解的直接设置LCR的DR位为1然后就能读RBR的想法是错误的。这里再次响起STM32F103的参考手册中的约定,有直接置1、硬件逻辑自动置1、通过写a位来置1b位(虽然写a位,单a位不存在是否置的意义)、通过写入数据序列来置1某位。这就是参考手册为什么会有版本号的原因。

继续吧,同理可以看一下THR寄存器的描述,跟RBR寄存器的描述差不多一样。再看看DLL的描述


这里说的LCR的DLAB位也是自动置1吗?还是看看代码吧(一般在串口的初始化中设置时钟,所以看初始化代码)

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 */
}
其中与LCR有关的是serial_out(UART_LCR_BKSE | UART_LCRVAL, &com_port->lcr);其中#define UART_LCR_BKSE 0x80 /* Bank select enable */

很明白对LCR的DLAB位置1,但是为什么是UART_LCR_BKSE,而且#define UART_LCR_DLAB0x80 /* Divisor latch access bit *。想说既然UART_LCR_BKSE与UART_LCR_DLAB相等,而且此处的意思好像就是设置DLAB位(因为下面有两句serial_out(0, &com_port->dll);serial_out(0, &com_port->dlm);很符合参考手册上说的:DLAB位置1之后才能设置DLL)。总结一下就是LCR的DLAB位要软件置1。


上面是uart寄存器结构体的理解。再来看看ns16550.c文件吧,一共5个函数。

void NS16550_init(NS16550_t com_port, int baud_divisor)

void NS16550_reinit(NS16550_t com_port, int baud_divisor)

void NS16550_putc(NS16550_t com_port, char c)

char NS16550_getc(NS16550_t com_port)

int NS16550_tstc(NS16550_t com_port)

虽然不都是叶子函数,但是其调用的子函数WATCHDOG_RESET();也难分析,有了刚才对结构体的分析,感觉这几个函数就不用说了。

只想说说刚才的UART_LCR_DLAB与UART_LCR_BKSE,整个项目中两处很值得注意

<arch/arm/armv7/sunxi/early_print.c>中

void uart_init(void) {

	/* select dll dlh */
	writel(UART_LCR_DLAB, UART_LCR(UART));
	/* set baudrate */
	writel(0, UART_DLH(UART));
	writel(BAUD_115200, UART_DLL(UART));
	/* set line control */
	writel(LC_8_N_1, UART_LCR(UART));

	uart_initialized = 1;
}

还有void NS16550_init(NS16550_t com_port, int baud_divisor)中,在这两个函数中UART_LCR_DLAB都是为了设置时钟,虽然这两处都不会被编译。

最后还要说一下不要忘了uart的时钟与GPIO配置,这两部分已经在前面分析过的s_init中完成。

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值