您的位置:首页 > 其它

CS8900 U-boot 网卡驱动分析

2010-07-07 19:02 375 查看
【概述】
本文描述了CS8900网卡在u-boot环境下的相关操作,包括初始化,数据发送/接收等过程的解析等。

【环境描述】

硬件平台:EBD2410开发板(S3C2410+CS8900A)
U-boot版本:V1.1.4

【其他描述】
在include/configs/smdk2410.h中,定义如下:
#define CONFIG_DRIVER_CS8900 1
#define CS8900_BASE 0x19000300
#define CS8900_BUS16 1

u-boot中对网卡驱动需要实现如下函数:
int eth_init (bd_t * bd);

int eth_rx (void);

eth_send (volatile void *packet, int length);

void eth_halt (void);

在此主要分析drivers/cs8900.c这个文件。

参考文档:
CS8900 Datasheet 《CS8900A.pdf》

【地址映射相关】
CS8900默认工作于IO空间访问方式,其IO空间的首地址默认为0x300,而在u-boot中也是通过I/O空间对芯片进行访问,因此我们可以宏定义中,有#define CS8900_BASE 0x19000300

【通过I/O空间访问CS8900的内部寄存器】
方法很简单,通过往PacketPage Pointer Port中写入寄存器的地址,其中最高位表示是否工作于自增模式;然后访问PacketPage Data Ports 0 and 1来进行读或写,相关代码如下所示:

static unsigned short get_reg (int regno)
{
CS8900_PPTR = regno;
return (unsigned short) CS8900_PDATA;
}

static void put_reg (int regno, unsigned short val)
{
CS8900_PPTR = regno;
CS8900_PDATA = val;
}
其中:

#define CS8900_PPTR *(volatile CS8900_REG *)(CS8900_BASE+0x05*CS8900_OFF)
#define CS8900_PDATA *(volatile CS8900_REG *)(CS8900_BASE+0x06*CS8900_OFF)

#ifdef CS8900_BUS16

#define CS8900_OFF 0x02
......

【软件复位CS8900】
通过往SelfCTL寄存器的RESET位置一进行软复位,复位后CS8900A将进行内部一系列操作,此时不要对其进行任何访问操作,典型的时间为10ms,一段时间后,软件通过读取Self Status Register的INITD位来确定芯片是否复位操作完成。参考如下函数:

static void eth_reset (void)
{
int tmo;
unsigned short us;

/* reset NIC */
put_reg (PP_SelfCTL, get_reg (PP_SelfCTL) | PP_SelfCTL_Reset);

/* wait for 200ms */
udelay (200000);
/* Wait until the chip is reset */

tmo = get_timer (0) + 1 * CFG_HZ;
while ((((us = get_reg_init_bus (PP_SelfSTAT)) & PP_SelfSTAT_InitD) == 0)
&& tmo < get_timer (0))
/*NOP*/;
}

【对CS8900A的初始化】
通过eth_init()函数进行,这个也是u-boot网络驱动中要求实现的函数:

int eth_init (bd_t * bd)
{

/* verify chip id */
if (get_reg_init_bus (PP_ChipID) != 0x630e) {
printf ("CS8900 Ethernet chip not found?!/n");
return 0;
}

eth_reset ();
/* set the ethernet address */
put_reg (PP_IA + 0, bd->bi_enetaddr[0] | (bd->bi_enetaddr[1] << 8));
put_reg (PP_IA + 2, bd->bi_enetaddr[2] | (bd->bi_enetaddr[3] << 8));
put_reg (PP_IA + 4, bd->bi_enetaddr[4] | (bd->bi_enetaddr[5] << 8));

eth_reginit ();
return 0;
}
首先验证芯片ID,随后通过软件复位芯片(参看前面),随后初始化其MAC地址,最后完成对相关寄存器的初始化。

其中:
#define PP_IA 0x0158 /* Individual address (MAC) */

eth_reginit()初始化了哪些寄存器呢?我们看看:

static void eth_reginit (void)
{
/* receive only error free packets addressed to this card */
put_reg (PP_RxCTL, PP_RxCTL_IA | PP_RxCTL_Broadcast | PP_RxCTL_RxOK);
/* do not generate any interrupts on receive operations */
put_reg (PP_RxCFG, 0);
/* do not generate any interrupts on transmit operations */
put_reg (PP_TxCFG, 0);
/* do not generate any interrupts on buffer operations */
put_reg (PP_BufCFG, 0);
/* enable transmitter/receiver mode */
put_reg (PP_LineCTL, PP_LineCTL_Rx | PP_LineCTL_Tx);
}

PP_RxCTL(Receiver Control):用来设置接收包的类型。此刻我们设置她:
a:仅能接收正确的包(CRC正确,且包大小大于64小于1518字节)
b:目标地址匹配模式为IndibidualA模式(即不是IAHashA模式)

c:能接收广播地址

PP_RxCFG(Receiver Configuration):用来设置接收到的帧如何传递给主机,以及能产生何种类型的中断。此刻我们设置她:

a:正常接收模式(不使用DMA及Stream模式)

b:接收到的数据中不包括CRC

c:不产生任何中断

PP_TxCFG(Transmit Configuration):用来设置产生中断的情况。此刻我们设置她:

不产生任何中断

PP_BufCFG(Buffer Configuration):设置有关跟buffer有关的中断类型。此刻我们设置她:

不产生任何中断

PP_LineCTL(Line Control):设置相关物理层与MAC层的相关配置。此刻我们设置她:

允许发送和接收状态

【数据包发送】
数据包发送的过程如下:

1:主机向TxCMD寄存器中写数,通知即将发送数据

2:主机向TxLen寄存器中写数,告知要发送的数据长度

3:主机查询Bus Status寄存器中的Rdy4TX NOW位,查看CS8900是否准备好
4:若CS8900A已经准备好,主机开始不断向RTDATA寄存器写入数据长度的数据。

5:主机通过查询Transimitter Event寄存器中的TxOK位,可知数据包是否发送完毕。

函数体如下:

extern int eth_send (volatile void *packet, int length)
{
volatile unsigned short *addr;
int tmo;
unsigned short s;

retry:
/* initiate a transmit sequence */
CS8900_TxCMD = PP_TxCmd_TxStart_Full;
CS8900_TxLEN = length;

/* Test to see if the chip has allocated memory for the packet */
if ((get_reg (PP_BusSTAT) & PP_BusSTAT_TxRDY) == 0) {
/* Oops... this should not happen! */
#ifdef DEBUG
printf ("cs: unable to send packet; retrying.../n");
#endif
for (tmo = get_timer (0) + 5 * CFG_HZ; get_timer (0) < tmo;)
/*NOP*/;
eth_reset (); //若没有准备好就进行软重启,有必要吗?
eth_reginit ();
goto retry;
}

/* Write the contents of the packet */
/* assume even number of bytes */
for (addr = packet; length > 0; length -= 2)
CS8900_RTDATA = *addr++;

/* wait for transfer to succeed */
tmo = get_timer (0) + 5 * CFG_HZ;
while ((s = get_reg (PP_TER) & ~0x1F) == 0) {
if (get_timer (0) >= tmo)
break;
}

/* nothing */ ;
if ((s & (PP_TER_CRS | PP_TER_TxOK)) != PP_TER_TxOK) {
#ifdef DEBUG
printf ("/ntransmission error %#x/n", s);
#endif
}

return 0;
}

【数据包接收】
网络数据包的接收处理过程应该是中断模式的,不过在u-boot中的这个驱动没有这样设计,而是做成了一个函数,当需要接收数据的时候调用这个函,若此时有网络数据则接收,没有则直接返回而已。

数据接收的过程如下:

1:查看Receiver Event中数据位RXOK,看是置为。若没有置为则返回0,表示此时没有网络数据要接收。

2:连续两次读取RTDATA地址,获得接收状态以及数据长度。

3:根据获取的长度,连续读取RTDATA地址,以获得接收到的数据。

函数体如下所示:

extern int eth_rx (void)
{
int i;
unsigned short rxlen;
unsigned short *addr;
unsigned short status;

status = get_reg (PP_RER);

if ((status & PP_RER_RxOK) == 0)
return 0;

status = CS8900_RTDATA; /* stat */
rxlen = CS8900_RTDATA; /* len */

#ifdef DEBUG
if (rxlen > PKTSIZE_ALIGN + PKTALIGN)
printf ("packet too big!/n");
#endif
for (addr = (unsigned short *) NetRxPackets[0], i = rxlen >> 1; i > 0;
i--)
*addr++ = CS8900_RTDATA;
if (rxlen & 1)
*addr++ = CS8900_RTDATA;

/* Pass the packet up to the protocol layers. */
NetReceive (NetRxPackets[0], rxlen);

return rxlen;
}

【Halt CS8900】
此处实现的函数如下:
void eth_halt (void)
{
/* disable transmitter/receiver mode */
put_reg (PP_LineCTL, 0);

/* "shutdown" to show ChipID or kernel wouldn't find he cs8900 ... */
get_reg_init_bus (PP_ChipID);
}
原来比较简单,仅仅是禁止一起设置允许接收和发送的标志位而已。

【后续问题】
问题1:
关于映射地址0x19000000的确定,以及通过MEM以及IO空间分别访问CS8900的方法。

可能需要参考:

1:ISA总线规范

2:硬件原理图中的硬件连接

3:S3C2410 Datasheet中memory region的划分

4:AT91RM9200 Datasheet中对RAM/ROM设备的访问方法(2410的datasheet中貌似没有提及)

【疑问】
问题1:
对某些寄存器的读访问采用的是get_reg_init_bus()函数,而其他则采用get_reg()函数,而这两个函数大体一致,但get_reg_init_bus()函数在前面加入了如下:

static unsigned short get_reg_init_bus (int regno)
{
/* force 16 bit busmode */
volatile unsigned char c;

c = CS8900_BUS16_0;
c = CS8900_BUS16_1;
c = CS8900_BUS16_0;
c = CS8900_BUS16_1;
c = CS8900_BUS16_0;

CS8900_PPTR = regno;
return (unsigned short) CS8900_PDATA;
}

前面一连串的c=XXX不知有何用,被Eric注释掉后,也能正常工作

问题2:
在eth_init()函数中用以识别芯片型号的代码:

if (get_reg_init_bus (PP_ChipID) != 0x630e) {
printf ("CS8900 Ethernet chip not found?!/n");
return 0;
}

其值0x630e在datasheet中无法找到,见Datasheet P41

问题3:<已解答>
Address Filter Register有两个:

1: Logical Address Filter (hash table)

2: Individual Address(IEEE address)

其中第二个需要设置,第一个干啥用的?

<解答>
参考Receive Control寄存器中关于目的地址匹配模式的设置,其匹配模式有两种,一种为IAHashA,另一种为IndividualA,两种匹配模式分别对应于这两个寄存器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: