ledsup-083a用的网络芯片是rtl8019as,但是在移植u-boot到开发板的时候,网络的移植出了一点问题。所以想看看rtl8019as这个网卡是如何工作的,很不幸的是,正如网络上很多人说的那样,rtl8019的文档及其的烂,真的不是一般的烂。基本上看过它的文档,跟没看过没什么区别。现将网络同步时钟的网卡工作过程详细讲述一下。
2、CR的位定义如下:
时钟内部寄存器7,6bit是用来选寄存器的页的,设置完之后,对01~20的寄存器的操作
设置5-3bit位下列值时有不同的功能:
设置RD2RD1RD0=000->表示启动网卡的remote读功能,此时可以从BNRY开始通过DMA从RAM中读出数据
设置RD2RD1RD0=010->表示启动网卡的remote写功能,此时可以从TPSR开始通过DMA写入数据到RAM中去
设置RD2RD1RD0=100->表示启动网卡的向外传输数据功能,当bit2被置位,则网卡启动传输,开始向网络传输
具体如下图:
3、RSR:接收状态寄存器
了解网卡的工作流程,分析分析驱动程序是再好不过的方式了。
网上流传的rtl8019as的驱动还是比较多版本的,这里用的是U-boot-1.1.6中的rtl8019as的驱动:
//定义Page的地址
#define RTL8019_REG_00 (RTL8019_BASE + 0x00<<1)
#define RTL8019_REG_01 (RTL8019_BASE + 0x01<<1)
#define RTL8019_REG_02 (RTL8019_BASE + 0x02<<1)
#define RTL8019_REG_03 (RTL8019_BASE + 0x03<<1)
#define RTL8019_REG_04 (RTL8019_BASE + 0x04<<1)
#define RTL8019_REG_05 (RTL8019_BASE + 0x05<<1)
#define RTL8019_REG_06 (RTL8019_BASE + 0x06<<1)
#define RTL8019_REG_07 (RTL8019_BASE + 0x07<<1)
#define RTL8019_REG_08 (RTL8019_BASE + 0x08<<1)
#define RTL8019_REG_09 (RTL8019_BASE + 0x09<<1)
#define RTL8019_REG_0a (RTL8019_BASE + 0x0a<<1)
#define RTL8019_REG_0b (RTL8019_BASE + 0x0b<<1)
#define RTL8019_REG_0c (RTL8019_BASE + 0x0c<<1)
#define RTL8019_REG_0d (RTL8019_BASE + 0x0d<<1)
#define RTL8019_REG_0e (RTL8019_BASE + 0x0e<<1)
#define RTL8019_REG_0f (RTL8019_BASE + 0x0f<<1)
#define RTL8019_REG_10 (RTL8019_BASE + 0x10<<1)
#define RTL8019_REG_1f (RTL8019_BASE + 0x1f<<1)
//将物理寄存器映射为各种功能寄存器,方便理解
#define RTL8019_COMMAND RTL8019_REG_00 //CR寄存器
#define RTL8019_PAGESTART RTL8019_REG_01 //接收缓冲启始位置
#define RTL8019_PAGESTOP RTL8019_REG_02 //接收缓冲结束位置
#define RTL8019_BOUNDARY RTL8019_REG_03 //Boundary寄存器
#define RTL8019_TRANSMITSTATUS RTL8019_REG_04 //传输状态寄存器
#define RTL8019_TRANSMITPAGE RTL8019_REG_04 //传输开始page
#define RTL8019_TRANSMITBYTECOUNT0 RTL8019_REG_05 //传输字节数低8位
#define RTL8019_NCR RTL8019_REG_05 //冲突次数
#define RTL8019_TRANSMITBYTECOUNT1 RTL8019_REG_06 //传输字节数高8位
#define RTL8019_INTERRUPTSTATUS RTL8019_REG_07 //中断状态寄存器
#define RTL8019_CURRENT RTL8019_REG_07 //CURRENT寄存器
#define RTL8019_REMOTESTARTADDRESS0 RTL8019_REG_08 //接收数据起始地址低8位
#define RTL8019_CRDMA0 RTL8019_REG_08
#define RTL8019_REMOTESTARTADDRESS1 RTL8019_REG_09 //接收数据起始地址高8位
#define RTL8019_CRDMA1 RTL8019_REG_09
#define RTL8019_REMOTEBYTECOUNT0 RTL8019_REG_0a //接收数据字节大小低8位
#define RTL8019_REMOTEBYTECOUNT1 RTL8019_REG_0b //接收数据字节大小高8位
#define RTL8019_RECEIVESTATUS RTL8019_REG_0c //接收状态寄存器
#define RTL8019_RECEIVECONFIGURATION RTL8019_REG_0c //接收配置寄存器
#define RTL8019_TRANSMITCONFIGURATION RTL8019_REG_0d //发送接收寄存器
#define RTL8019_FAE_TALLY RTL8019_REG_0d
#define RTL8019_DATACONFIGURATION RTL8019_REG_0e //数据配置寄存器
#define RTL8019_CRC_TALLY RTL8019_REG_0e
#define RTL8019_INTERRUPTMASK RTL8019_REG_0f //中断屏蔽寄存器
#define RTL8019_MISS_PKT_TALLY RTL8019_REG_0f
#define RTL8019_PHYSICALADDRESS0 RTL8019_REG_01 //MAC地址0
#define RTL8019_PHYSICALADDRESS1 RTL8019_REG_02
#define RTL8019_PHYSICALADDRESS2 RTL8019_REG_03
#define RTL8019_PHYSICALADDRESS3 RTL8019_REG_04
#define RTL8019_PHYSICALADDRESS4 RTL8019_REG_05
#define RTL8019_PHYSICALADDRESS5 RTL8019_REG_06
#define RTL8019_MULTIADDRESS0 RTL8019_REG_08 //多播地址配置
#define RTL8019_MULTIADDRESS1 RTL8019_REG_09
#define RTL8019_MULTIADDRESS2 RTL8019_REG_0a
#define RTL8019_MULTIADDRESS3 RTL8019_REG_0b
#define RTL8019_MULTIADDRESS4 RTL8019_REG_0c
#define RTL8019_MULTIADDRESS5 RTL8019_REG_0d
#define RTL8019_MULTIADDRESS6 RTL8019_REG_0e
#define RTL8019_MULTIADDRESS7 RTL8019_REG_0f
#define RTL8019_DMA_DATA RTL8019_REG_10 //DMA传输寄存器(读出/写入)
#define RTL8019_RESET RTL8019_REG_1f //NIC复位寄存器
//定义一些用来设置寄存器的值
#define RTL8019_PAGE0 0x22 //0010 0010
#define RTL8019_PAGE1 0x62 //0110 0010
#define RTL8019_PAGE0DMAWRITE 0x12 //0001 0010
#define RTL8019_PAGE2DMAWRITE 0x92 //1001 0010
#define RTL8019_REMOTEDMAWR 0x12 //0001 0010
#define RTL8019_REMOTEDMARD 0x0A //0000 1010
#define RTL8019_ABORTDMAWR 0x32 //0011 0010
#define RTL8019_ABORTDMARD 0x2A //0010 1010
#define RTL8019_PAGE0STOP 0x21 //0010 0001
#define RTL8019_PAGE1STOP 0x61 //0110 0001
#define RTL8019_TRANSMIT 0x26 //0010 0110
#define RTL8019_TXINPROGRESS 0x04 //0000 0100
#define RTL8019_SEND 0x1A //0001 1010
#define RTL8019_PSTART 0x4c //0100 1100
#define RTL8019_PSTOP 0x80 //1000 0000
#define RTL8019_TPSTART 0x40 //0100 0000
static void eth_reset (void)
{
unsigned char ucTemp;
/* reset NIC */
//向reset寄存器中写入任何值就可以完成NIC的reset
ucTemp = get_reg (RTL8019_RESET);
put_reg (RTL8019_RESET, ucTemp);
//清除所有的中断位
put_reg (RTL8019_INTERRUPTSTATUS, 0xff);
udelay (2000); /* wait for 2ms */
}
int eth_init (bd_t * bd)
{
//初始化NIC
eth_reset ();
put_reg (RTL8019_COMMAND, RTL8019_PAGE0STOP);//选择page0,停机状态
put_reg (RTL8019_DATACONFIGURATION, 0x48);//设置为loopback模式,且DMA为8位模式
put_reg (RTL8019_REMOTEBYTECOUNT0, 0x00);//清除接收数据寄存器
put_reg (RTL8019_REMOTEBYTECOUNT1, 0x00);
put_reg (RTL8019_RECEIVECONFIGURATION, 0x00); //瞎写
put_reg (RTL8019_TRANSMITPAGE, RTL8019_TPSTART);//传输起始page设为Page40
put_reg (RTL8019_TRANSMITCONFIGURATION, 0x02);//传输配置,loopback模式(跟DCR配对,均需设置)
put_reg (RTL8019_PAGESTART, RTL8019_PSTART);//接收数据起始page设置为Page4c
put_reg (RTL8019_BOUNDARY, RTL8019_PSTART);//Boundary初始化为page4c
put_reg (RTL8019_PAGESTOP, RTL8019_PSTOP);//接收数据边界页为Page80
put_reg (RTL8019_INTERRUPTSTATUS, 0xff);//清除所有中断寄存位
put_reg (RTL8019_INTERRUPTMASK, 0x11); //只允许第0位和第4位中断
put_reg (RTL8019_COMMAND, RTL8019_PAGE1STOP);//选择page1,停机状态
put_reg (RTL8019_PHYSICALADDRESS0, bd->bi_enetaddr[0]);//设置MAC地址
put_reg (RTL8019_PHYSICALADDRESS1, bd->bi_enetaddr[1]);
put_reg (RTL8019_PHYSICALADDRESS2, bd->bi_enetaddr[2]);
put_reg (RTL8019_PHYSICALADDRESS3, bd->bi_enetaddr[3]);
put_reg (RTL8019_PHYSICALADDRESS4, bd->bi_enetaddr[4]);
put_reg (RTL8019_PHYSICALADDRESS5, bd->bi_enetaddr[5]);
put_reg (RTL8019_MULTIADDRESS0, 0x00);//设置多播地址(设置的值是什么意思呢??)
put_reg (RTL8019_MULTIADDRESS1, 0x00);
put_reg (RTL8019_MULTIADDRESS2, 0x00);
put_reg (RTL8019_MULTIADDRESS3, 0x00);
put_reg (RTL8019_MULTIADDRESS4, 0x00);
put_reg (RTL8019_MULTIADDRESS5, 0x00);
put_reg (RTL8019_MULTIADDRESS6, 0x00);
put_reg (RTL8019_MULTIADDRESS7, 0x00);
put_reg (RTL8019_CURRENT, RTL8019_PSTART);//设置CURRENT寄存器为Page4c
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);//选择Page0,且NIC开机(处于开机状态,使其能够接收数据)
put_reg (RTL8019_TRANSMITCONFIGURATION, 0xe0);//设置传输模式为1110 0000
//正常模式(非loopback模式),CRC使能
return 0;
}
static unsigned char nic_to_pc (void)
{//返回接收操作完成之后的CURRENT寄存器值
//该函数完成的功能是从boundary寄存器中存储的地址开始接收一帧的数据
unsigned char rec_head_status;
unsigned char next_packet_pointer;
unsigned char packet_length0;
unsigned char packet_length1;
unsigned short rxlen = 0;
unsigned int i = 4;
unsigned char current_point;
unsigned char *addr;
/*
* The RTL8019's first 4B is packet status,page of next packet
* and packet length(2B).So we receive the fist 4B.
*/
//rtl8019as的帧的头4Byte如上所述:
//第一字节为status
//第二字节为下一个帧的起始Page
//第3,4字节为本帧的长度,以字节为单位
put_reg (RTL8019_REMOTESTARTADDRESS1, get_reg (RTL8019_BOUNDARY));
put_reg (RTL8019_REMOTESTARTADDRESS0, 0x00);//因为实际接收地址是boundary<<8,所以低8位地址恒定为0
put_reg (RTL8019_REMOTEBYTECOUNT1, 0x00);
put_reg (RTL8019_REMOTEBYTECOUNT0, 0x04);//从接收缓冲区中接收4字节的数据
put_reg (RTL8019_COMMAND, RTL8019_REMOTEDMARD);//在CR中设置接收位为001(Remote Read)
rec_head_status = get_reg (RTL8019_DMA_DATA);//读第一个字节
next_packet_pointer = get_reg (RTL8019_DMA_DATA);//读第二个字节
packet_length0 = get_reg (RTL8019_DMA_DATA);//第三个字节
packet_length1 = get_reg (RTL8019_DMA_DATA);//第四个字节
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);//设置CR为Page0
/*Packet length is in two 8bit registers */
rxlen = packet_length1;
rxlen = (((rxlen < < 8) & 0xff00) + packet_length0);
rxlen -= 4;
if (rxlen > PKTSIZE_ALIGN + PKTALIGN)
printf ("packet too big!\n");
/*Receive the packet */
put_reg (RTL8019_REMOTESTARTADDRESS0, 0x04);//配置DMA的读取起始地址(减去了4个字节)
put_reg (RTL8019_REMOTESTARTADDRESS1, get_reg (RTL8019_BOUNDARY));
put_reg (RTL8019_REMOTEBYTECOUNT0, (rxlen & 0xff));//配置DAM读取长度
put_reg (RTL8019_REMOTEBYTECOUNT1, ((rxlen >> 8) & 0xff));
put_reg (RTL8019_COMMAND, RTL8019_REMOTEDMARD);//设置CR为READ状态
//这里是8位DMA的情况,16位DMA的情况需要做调整,每次能够读出16位的数据
for (addr = (unsigned char *) NetRxPackets[0], i = rxlen; i > 0; i--)
*addr++ = get_reg (RTL8019_DMA_DATA);
/* Pass the packet up to the protocol layers. */
NetReceive (NetRxPackets[0], rxlen);//将接收到的数据交给上层协议处理
while (!(get_reg (RTL8019_INTERRUPTSTATUS)) & 0x40); /* wait for the op. */
//等待DMA操作结束(RDC位被自动置位的时候表示DMA操作结束)
/*
* To test whether the packets are all received,get the
* location of current point
*/
put_reg (RTL8019_COMMAND, RTL8019_PAGE1);
current_point = get_reg (RTL8019_CURRENT);
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);
put_reg (RTL8019_BOUNDARY, next_packet_pointer);//将下一帧的起始页数写入到boundary中
//这里难道不需要考虑boundary>80的情况吗????
return current_point;
}
/* Get a data block via Ethernet */
extern int eth_rx (void)
{
unsigned char temp, current_point;
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);//选取Page0,开机状态
while (1) {
temp = get_reg (RTL8019_INTERRUPTSTATUS);
if (temp & 0x90) {
/*中断寄存器为90表示接收数据太快,接收缓冲被用完了,此时由于循环RAM的缘故,CURRENT=BOUNDARY*/
put_reg (RTL8019_COMMAND, RTL8019_PAGE0STOP);//停机
udelay (2000);
put_reg (RTL8019_REMOTEBYTECOUNT0, 0);
put_reg (RTL8019_REMOTEBYTECOUNT1, 0);
put_reg (RTL8019_TRANSMITCONFIGURATION, 2);//进入内部loopback模式
do {
//反复读取缓冲中的值,直到boundary==current(注意这时current_point是不变的!!)
current_point = nic_to_pc ();
} while (get_reg (RTL8019_BOUNDARY) != current_point);
put_reg (RTL8019_TRANSMITCONFIGURATION, 0xe0);//推出loopback模式,设置传输配置寄存器为普通模式
}
if (temp & 0x1) {//如果有帧被顺利的接收到,会自动给bit0置位
/*packet received */
do {
//反复读取直到NIC收到的数据全部被CPU接收
put_reg (RTL8019_INTERRUPTSTATUS, 0x01);//清除bit0
current_point = nic_to_pc ();
} while (get_reg (RTL8019_BOUNDARY) != current_point);//这个条件对么?还是应该用burndary+1 == current_point来判断???
}
if (!(temp & 0x1))//如果没有数据收到则退出
return 0;
/* done and exit. */
}
}
/* Send a data block via Ethernet.发送 */
extern int eth_send (volatile void *packet, int length)
{
volatile unsigned char *p;
unsigned int pn;
pn = length;
p = (volatile unsigned char *) packet;
while (get_reg (RTL8019_COMMAND) == RTL8019_TRANSMIT);//等待上一次传输结束
put_reg (RTL8019_REMOTESTARTADDRESS0, 0);//设置传输起始地址
put_reg (RTL8019_REMOTESTARTADDRESS1, RTL8019_TPSTART);
put_reg (RTL8019_REMOTEBYTECOUNT0, (pn & 0xff));//设置传输长度
put_reg (RTL8019_REMOTEBYTECOUNT1, ((pn >> 8) & 0xff));
put_reg (RTL8019_COMMAND, RTL8019_REMOTEDMAWR);//标明处于WRITE状态,TPX为0
while (pn > 0) {
put_reg (RTL8019_DMA_DATA, *p++);
pn--;
}
pn = length;
//如果数据长度小于60个字节时,则自动以0补全
while (pn < 60) {
/*Padding */
put_reg (RTL8019_DMA_DATA, 0);
pn++;
}
while (!(get_reg (RTL8019_INTERRUPTSTATUS)) & 0x40);//等待DMA操作结束(发送结束)
put_reg (RTL8019_INTERRUPTSTATUS, 0x40);清除相应中断位
put_reg (RTL8019_TRANSMITPAGE, RTL8019_TPSTART);复位传输起始page
put_reg (RTL8019_TRANSMITBYTECOUNT0, (pn & 0xff));设置传输字节大小
put_reg (RTL8019_TRANSMITBYTECOUNT1, ((pn >> 8 & 0xff)));
put_reg (RTL8019_COMMAND, RTL8019_TRANSMIT);//哦,恍然大悟
//此处是启动NIC通过网卡向外发送数据,上面的REMOTEDMAWR是使NIC进入DMA状态,以便从CPU传数据到发送缓冲中去。
return 0;
}
最终,网络同步授时LED时钟将会正确获取网络NTP服务器的时间信息,并显示出年、月、日、时、分、秒、星期等信息,具体农历的计算请参考我们发布的时钟农历计算方法。