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的区间位于: 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脚
接在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" ); ...... 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(); if (IS_ERR(dmfe_dev)) return PTR_ERR(dmfe_dev); return 0 ; } 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 int __init dm9000c_init (void ) { ...... } 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)); ... ... err = dmfe_probe1(dev); ... ... err = register_netdev(dev); ... ... }
显然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 ; 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 ); do { outb(DM9KS_VID_L, iobase); id_val = inb(iobase + 4 ); 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 ); ...... }
并在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 ; 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 ); 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; ... ... }
5、修改中断名
在init函数中,修改中断名,将irq改为IRQ_EINT7
1 2 3 4 5 6 void __init dm9000c_init (void ) { iobase = (int )ioremap(0x20000000 ,4096 ); 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) { ...... 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; dev->base_addr = iobase; dev->irq = irq; dev->open = &dmfe_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|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 ...... }
接着修改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 ); 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] 硬件设置
设置ST4=0,不使用UB/LB(UB/LB:表示高字节与低字节数据是否分开传输)
设置WS4=0,其中WAIT引脚为PE4,而我们DM9000C没有引脚接入PE4,所以禁止
设置DW4=0x01,我们的DM9000C的数据线为16位
2、设置BANKCON4控制寄存器
设置这些时序之前,首先来看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; unsigned long *BANKCON4; iobase =(int )ioremap(0x20000000 ,4096 ); irq=IRQ_EINT7; BWSCON = ioremap(0x48000000 ,4 ); BANKCON4 = ioremap(0x48000014 ,4 ); *BWSCON &=~(0XFF <<16 ); *BWSCON |= (0x01 <<16 ); *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驱动程序:
把dm9dev9000c.c放到内核的drivers/net目录下,来替换原来内核的DM9000C
修改内核中的makefile,位于drivers/net/Makefile
把
1 obj-$(CONFIG_DM9000) += dm9000.o
改为
1 obj-$(CONFIG_DM9000) += dm9dev9000c.o
make uImage
如下图,说明新的驱动已编译进内核
使用新内核启动
1 2 ifconfig eth0 192.168.2.107 ping 192.168.2.1
如下图,可以ping通,说明移植成功