DM9000C网卡移植

一、DM9000C原理图

如下图所示:

DM9000C原理图

#:表示低电平有效

  • SD0~15: 16位数据线,有CMD引脚决定访问类型
  • CMD: 命令线,当CMD为高,表示SD 传输的是数据,CMD为低表示传输的是地址
  • INT: 中断引脚,接在2440的GPF7脚上
  • IOR#: 读引脚,接在2440的nOE脚上
  • IOW#: 写引脚,接在2440的nWE脚上
  • CS#: 片选,放在2440的bank4的片选上面

1、2440的bank4地址区间

bank4地址区间

bank4的区间位于: 0X20000000~0X28000000,当我们访问这个区间的地址,内存控制器便会使能网卡DM9000C的使能脚,所以我们的DM9000C的io基地址=0X20000000

其中DM9000C的CMD引脚接在bank4的LADDR2上面,所以会有以下情况出现:

当在地址0X2000 0000上读写数据时,表示读写的数据是DM9000C的地址

访问的地址0X2000 0004上读写数据时,表示读写的数据是DM9000C的数据

2、DM9000C收发过程

  • 当DM9000C收到外部的数据后,会暂存到内部地址中,然后产生一个上升沿中断,等待2440读取数据

  • 当DM9000C将2440的数据转发出去后,也会产生一个上升沿中断给2440

如下图所示,DM9000C的中断引脚位于pin34脚

DM9000C中断引脚

2440检测引脚

接在2440的GPF7引脚上,使用的中断为EINT7

二、厂家提供的DM9000C源码

一般来说一个公司想推他的网卡芯片,厂家肯定比我们更加清楚怎么操作这个芯片,所以一般都会提供一个驱动程序,我们只需要在这个基础上修改即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifdef MODULE

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Davicom DM9000/DM9010 ISA/uP Fast Ethernet Driver");
......

/* Description:
when user used insmod to add module, system invoked init_module()
to initilize and register.
*/
int __init init_module(void)
{
switch(mode) {
case DM9KS_10MHD:
case DM9KS_100MHD:
case DM9KS_10MFD:
case DM9KS_100MFD:
media_mode = mode;
break;
default:
media_mode = DM9KS_AUTO;
}
dmfe_dev = dmfe_probe();//初始化DM9000C硬件和设置net_device结构体的成员 下一步会讲
if(IS_ERR(dmfe_dev))
return PTR_ERR(dmfe_dev);
return 0;
}
/* Description:
when user used rmmod to delete module, system invoked clean_module()
to un-register DEVICE.
*/
void __exit cleanup_module(void)
{
......
}
#endif

这里可以发现它的init_module()入口函数前有个条件编译,而该编译条件之前是未定义的,所以这里应该注释掉 “#ifdef MODULE”“#endif”

关于入口出口函数名,这里建议修改函数名并利用如下宏修饰函数名,避免与内核的其它函数重名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Description: 
when user used insmod to add module, system invoked init_module()
to initilize and register.
*/
int __init dm9000c_init(void)
{
......
}
/* Description:
when user used rmmod to delete module, system invoked clean_module()
to un-register DEVICE.
*/
void __exit dm9000c_exit(void)
{
......
}

module_init(dm9000c_init);
module_exit(dm9000c_exit);

三、修改驱动的硬件相异性(设置基地址,寄存器,中断等)

1、初始化DM9000C硬件

通过入口程序发现会调用dmfe_probe()

dm9000c_init()

​ -> dmfe_probe()

其中dmfe_probe()函数如下:

1
2
3
4
5
6
7
8
9
10
11
struct net_device * __init dmfe_probe(void)
{
struct net_device *dev;

dev= alloc_etherdev(sizeof(struct board_info));//分配一个net_device结构体
... ...
err = dmfe_probe1(dev);//调用dmfe_probe1()函数
... ...
err = register_netdev(dev);//向内核注册net_device结构体
... ...
}

显然dmfe_probe1()函数就是用来初始化DM9000C硬件和设置net_device结构体的成员用的

2、dmfe_probe1()函数

如下所示,这个iobase 变量就是我们DM9000C的io基地址0x20000000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __init dmfe_probe1(struct net_device *dev)
{
struct board_info *db; /* Point a board information structure */
u32 id_val;
u16 i, dm9000_found = FALSE;
u8 MAC_addr[6]={0x00,0x60,0x6E,0x33,0x44,0x55};
u8 HasEEPROM=0,chip_info;
DMFE_DBUG(0, "dmfe_probe1()",0);

/* Search All DM9000 serial NIC */
do {
outb(DM9KS_VID_L, iobase);//CMD引脚置0,写入要读的地址
id_val = inb(iobase + 4);//CMD置1,读出要读的数据
outb(DM9KS_VID_H, iobase);
id_val |= inb(iobase + 4) << 8;
outb(DM9KS_PID_L, iobase);
id_val |= inb(iobase + 4) << 16;
outb(DM9KS_PID_H, iobase);
id_val |= inb(iobase + 4) << 24;
... ...
}

iobase的作用:

如上图, 读一次DM9000C的VID低字节之前,需要先将地址赋为0x20000000,也就是将DM9000C的CMD置0,然后向0x20000000写入要读的DM9KS_VID_L地址值

最后再将地址+4,也就是赋为0x20000100,将CMD置1,然后读出0x20000100的值,也就是DM9000C的VID低字节

DM9000C的读写方式都是这样的,先将CMD置0,写入DM9000C的地址,然后再将CMD置1,来读写数据

3、重新初始化iobase

所以这里需要在init函数中便重新设置iobase 变量,其中iobase是int型

1
2
3
4
5
void __init dm9000c_init(void)
{
iobase = (int)ioremap(0x20000000,4096);//获取DM9000C基地址
......
}

并在exit出口函数中,添加iounmp()

1
2
3
4
5
void __exit dm9000c_exit(void)
{
......
iounmap(iobase);
}

4、屏蔽核对版本代码

如下所示为dmfe_probe1()函数中用来核对版本的代码,我们的DM9000C版本号不一样,所以要屏蔽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __init dmfe_probe1(struct net_device *dev)
{
struct board_info *db; /* Point a board information structure */
u32 id_val;
u16 i, dm9000_found = FALSE;
u8 MAC_addr[6]={0x00,0x60,0x6E,0x33,0x44,0x55};
u8 HasEEPROM=0,chip_info;
DMFE_DBUG(0, "dmfe_probe1()",0);

/* Search All DM9000 serial NIC */
do {
... ...

if (id_val == DM9KS_ID || id_val == DM9010_ID) {

... ...

chip_info = ior(db,0x43);
if((db->chip_revision!=0x1A) || ((chip_info&(1<<5))!=0) || ((chip_info&(1<<2))!=1)) return -ENODEV;//核对DM9000C版本,需要屏蔽

... ...
}

5、修改中断名

在init函数中,修改中断名,将irq改为IRQ_EINT7

1
2
3
4
5
6
void __init dm9000c_init(void)
{
iobase = (int)ioremap(0x20000000,4096);//获取DM9000C基地址
irq = IRQ_EINT7;//修改中断名
......
}

6、修改中断

当使用了register_netdev()注册了网卡驱动net_device后,在命令行中使用ifconfig就会进入net_device->open成员函数,该函数会进行申请中断、激活队列等操作

所以我们要修改open成员函数的申请中断函数,将触发中断改为“IRQT_RISING”,上升沿触发

通过dmfe_probe1()函数可以知道open成员函数为:dmfe_open()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int __init dmfe_probe1(struct net_device *dev)
{
......

/* Search All DM9000 serial NIC */
do {
......

if (id_val == DM9KS_ID || id_val == DM9010_ID) {

......

chip_info = ior(db,0x43);
if((db->chip_revision!=0x1A) || ((chip_info&(1<<5))!=0) || ((chip_info&(1<<2))!=1)) return -ENODEV;

/* driver system function */
dev->base_addr = iobase;
dev->irq = irq;
dev->open = &dmfe_open;//open成员函数
dev->hard_start_xmit = &dmfe_start_xmit;
dev->watchdog_timeo = 5*HZ;
dev->tx_timeout = dmfe_timeout;
dev->stop = &dmfe_stop;
dev->get_stats = &dmfe_get_stats;
dev->set_multicast_list = &dm9000_hash_table;
dev->do_ioctl = &dmfe_do_ioctl;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,28)
dev->ethtool_ops = &dmfe_ethtool_ops;
#endif
#ifdef CHECKSUM
//dev->features |= NETIF_F_IP_CSUM;
dev->features |= NETIF_F_IP_CSUM|NETIF_F_SG;
#endif
db->mii.dev = dev;
db->mii.mdio_read = mdio_read;
db->mii.mdio_write = mdio_write;
db->mii.phy_id = 1;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
db->mii.phy_id_mask = 0x1F;
db->mii.reg_num_mask = 0x1F;
#endif
//db->msg_enable =(debug == 0 ? DMFE_DEF_MSG_ENABLE : ((1 << debug) - 1));

......
}

接着修改open成员函数dmfe_open(),将触发中断改为“IRQT_RISING

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int dmfe_open(struct net_device *dev)
{
board_info_t *db = (board_info_t *)dev->priv;
u8 reg_nsr;
int i;
DMFE_DBUG(0, "dmfe_open", 0);

//修改为IRQT_RISING,即为1
//默认为IRQT_NOEDGE,即为0
if (request_irq(dev->irq,&dmfe_interrupt,IRQT_RISING,dev->name,dev))
return -EAGAIN;

......
}

四、设置2440的存储控制寄存器

这里需要设置2440的bank4的硬件位宽、时序。因为不同的硬件,涉及的数据收发都不同。

1、设置BWSCON总线宽度控制寄存器

这里我们只设置BANK4的内容,所以只有下面3个需要设置

注:BANK0的位宽由OM[1:0] 硬件设置

BWSCON寄存器

设置ST4=0,不使用UB/LB(UB/LB:表示高字节与低字节数据是否分开传输)

设置WS4=0,其中WAIT引脚为PE4,而我们DM9000C没有引脚接入PE4,所以禁止

设置DW4=0x01,我们的DM9000C的数据线为16位

2、设置BANKCON4控制寄存器

BANKCON4控制寄存器

设置这些时序之前,首先来看DM9000C芯片手册时序图和2440的时序图

DM9000C芯片手册时序图和2440的时序图

参考上图,得出BANKCON4 寄存器设置如下(HCLK=100MHZ,1个时钟等于10ns)

  • 设置Tacs=0,因为CS和CMD可以同时结束(bank4地址(也就是CMD)稳定多久后,CS片选才启动)
  • 设置Tcos=T1=0(CS片选后,多久才能使能读写)
  • 设置Tacc=T2>=10ns=1,表示2个时钟 (access cycle ,读写使能后,多久才能访问数据)
  • 设置Tcoh=T4>=3ns=1,表示1个时钟 ,因为当DM9000的写信号取消后,数据线上的数据还需要至少3ns才消失(nOE读写取消后,片选需要维持多长时间)
  • 设置Tcah=0,因为 CS和CMD可以同时结束 (CS片选取消后,地址(也就是CMD)需要维持多长时间)

代码如下,在init入口函数中设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int __init dm9000c_init(void)
{
unsigned long *BWSCON;//0x48000000
unsigned long *BANKCON4;//0x48000014

iobase =(int)ioremap(0x20000000,4096);//获取DM9000C基地址

irq=IRQ_EINT7;//设置中断名

/*设置硬件相关*/
BWSCON = ioremap(0x48000000,4);
BANKCON4 = ioremap(0x48000014,4);

/* ST4[19]:0=未使用UB/LB
* WS4[18]:0=WAIT禁止
* DW4[17:16]:01=16位
*/
*BWSCON &=~(0XFF<<16);
*BWSCON |= (0x01<<16);//关闭UB/LB,解止WAIT信号,设置16位宽数据

/*
* Tacs[14:13]: 发出片选信号之前,多长时间内要先发出地址信号
* DM9000C的片选信号和CMD信号可以同时发出,
* 所以它设为0
* Tcos[12:11]: 发出片选信号之后,多长时间才能发出读信号nOE
* DM9000C的T1>=0ns,
* 所以它设为0
* Tacc[10:8] : 读写信号的脉冲长度,
* DM9000C的T2>=10ns,
* 所以它设为1, 表示2个hclk周期,hclk=100MHz,就是20ns
* Tcoh[7:6] : 当读信号nOE变为高电平后,片选信号还要维持多长时间
* DM9000C进行写操作时, nWE变为高电平之后, 数据线上的数据还要维持最少3ns
* DM9000C进行读操作时, nOE变为高电平之后, 数据线上的数据在6ns之内会消失
* 我们取一个宽松值: 让片选信号在nOE放为高电平后,再维持10ns,
* 所以设为01
* Tcah[5:4] : 当片选信号变为高电平后, 地址信号还要维持多长时间
* DM9000C的片选信号和CMD信号可以同时出现,同时消失
* 所以设为0
* PMC[1:0] : 00-正常模式
*
*/
*BANKCON4 = (0x1<<8)|(0x1<<6);

/*后面不会用到,所以注销掉*/
iounmap(BWSCON);
iounmap(BANKCON4);
......

若DM9000C无法驱动,可能是Tacc时间太短,导致读取不到数据**,**可以将Tacc设大一点

五、编译测试

编译之前,首先添加该驱动需要的内核头文件:

1
2
3
4
5
#include <asm/delay.h>
#include <asm/irq.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/arch-s3c2410/regs-mem.h>

编译无误后,便开始测试DM9000C驱动程序:

    1. 把dm9dev9000c.c放到内核的drivers/net目录下,来替换原来内核的DM9000C
    1. 修改内核中的makefile,位于drivers/net/Makefile

    1
    obj-$(CONFIG_DM9000) += dm9000.o

    改为

    1
    obj-$(CONFIG_DM9000) += dm9dev9000c.o
    1. make uImage

    如下图,说明新的驱动已编译进内核

    新驱动

    1. 使用新内核启动
    1
    2
    ifconfig eth0 192.168.2.107
    ping 192.168.2.1

    如下图,可以ping通,说明移植成功

    命令截图