通过二十、Linux驱动之移植DM9000C网卡驱动(上)对厂家提供的网卡驱动程序dm9dev9000c.c的分析,下面将该网卡驱动移植到JZ2440开发板上(内核版本为linux-2.6.22.6)。
1. 硬件分析
本人使用的开发板是JZ2440,CPU为S3C2440A,与DM9000C芯片连接如下:
SD0~15:16位地址、数据线,由CMD引脚决定访问类型。
CMD:命令线,当CMD为高表示传输的是数据,CMD为低表示传输的是地址。
INT:中断引脚,接在2440的GPF7脚上。
IOR#:读引脚,接在2440的nOE脚上。
IOW#:写引脚,接在2440的nWE脚上。
CS#:片选,接在2440的bank4的片选上面。
1.1 位宽选择
DM9000C支持两种位宽模式,8位宽和16位宽。选择不同的模式引脚接法不同。如下:
通过选择DM9000的21号引脚是接上拉电阻还是浮空输入(低电平)来选择位宽模式,对于JZ2440开发板上的DM9000C的21号引脚浮空,选择的是16位宽模式。
1.2 BANK与片选信号
首先看到有16根数据线(地址线复用),这些数据线同样还连接到了开发板上的其他芯片(如SDRAM、NOR FLASH、NAND FLASH),当想要去读写DM9000C或者操作其他芯片时都在这些I/O引脚上发出信号,我们如何让他们之间不会互相干扰,正确控制自己想要控制的芯片呢?
S3C2440A有一个存储控制器,如下图:
接在地址线或数据线上的芯片可能有很多,不同的芯片都会有CE片选引脚,可以手动控制该引脚来选择操作的芯片。
S3C2440A帮我们省去了这一操作,S3C2440A通过将可寻址地址(物理地址)划分为8个内存块(每个内存块128MB,8*128MB=(2^27)B,所以S3C2440A地址线共有27根(LADDR0~26)),每个内存块都有一个片选信号(nGCS0~nGCS7),具体每一部分物理地址范围如上图。以第5个内存块物理地址0x2000 0000-0x2800 0000为例,当CPU读写该范围内的物理地址时,CPU会自动将nGCS4引脚拉低,挂在该片选信号线上的芯片就会被选中,本节所讲的DM9000就是挂在该内存块上,所以当CPU发出0x2000 0000-0x2800 0000的物理地址数据时,DM9000就会被选中。从这里也可以看出此时的DM9000C芯片基地址为0x2000 0000。
1.3 地址数据线共用
对于DM9000C芯片来说,读写地址与数据使用的是同一组16个I/O引脚,DM9000C芯片如何区分CPU读写的是地址还是数据呢?
可以看到原理图中DM9000C的第32引脚CMD命令信号线连到CPU的地址线LADDR2上,也就是说当CPU发出物理地址(0x2000 0000-0x2800 0000)|0x4时,此时CPU再发出的数据线信号对于DM9000C来说实际是地址信号。例如:
1. 当在地址0X2000 0000上读写数据时,表示读写的数据是DM9000C的地址
2. 当访问的地址0X2000 0004上读写数据时,表示读写的数据是DM9000C的数据
1.4 中断信号
DM9000C芯片的34号引脚INT中断信号引脚连接到2440的GPF7,也就是接到CPU的外部中断线7上。当DM9000C收到外部的数据后,会暂存到内部地址中,然后产生一个上升沿中断,等待2440读取数据;当DM9000C将2440的数据转发出去后,也会产生一个上升沿中断给2440。
1.5 总结
1. DM9000C芯片物理基地址应设为0x20000000
2. 中断号为EINT7
3. 16位总线宽度
2. 修改代码
2.1 修改代码为标准驱动格式
1. 添加宏定义“#define MODULE 1”如下:
2. 修改入口出口函数名,并增加模块装载卸载函数,如下:
2.2 修改硬件相关代码
2.2.1 修改iobase
iobase是一个全局static变量,也就是网卡的基地址(这里是虚拟地址),例如,在dmfe_probe1()函数中有如下代码:
/*
#define DM9KS_VID_L 0x28
#define DM9KS_VID_H 0x29
#define DM9KS_PID_L 0x2A
#define DM9KS_PID_H 0x2B
*/
int __init dmfe_probe1(struct net_device *dev)
{
... ...
/*获取ID*/
outb(DM9KS_VID_L, iobase); //地址总线输出iobase,数据总线输出0x28
id_val = inb(iobase + 4); //地址总线输出iobase+4,读入数据总线数据
outb(DM9KS_VID_H, iobase); //地址总线输出iobase,数据总线输出0x29
id_val |= inb(iobase + 4) << 8; //地址总线输出iobase+4,读入数据总线数据
outb(DM9KS_PID_L, iobase); //地址总线输出iobase,数据总线输出0x2A
id_val |= inb(iobase + 4) << 16; //地址总线输出iobase+4,读入数据总线数据
outb(DM9KS_PID_H, iobase); //地址总线输出iobase,数据总线输出0x2B
id_val |= inb(iobase + 4) << 24; //地址总线输出iobase+4,读入数据总线数据
... ...
}
由硬件分析可知,我们的iobase的物理地址应设为0x20000000。
1. 当CPU地址总线输出iobase即0x20000000(物理地址)时,DM9000C的cmd引脚为低电平,此时CPU数据总线输出的0x28对于DM9000来说表示读0x28地址
2. 当CPU地址总线输出iobase+4即0x20000004(物理地址)时,DM9000C的cmd引脚为高电平,此时CPU数据总线输入输出对应读写DM9000C。
依次读地址也就是寄存器0x28、0x29、0x2A、0x2B,得到厂家ID和设备ID。所以需要我们根据硬件给它重新赋值(将实际物理地址转换为虚拟地址),在入口函数dm9dev9000c_init中添加代码如下:
iobase = (int)ioremap(0x20000000, 4096); //重映射物理基地址0x2000 0000
在出口函数dm9dev9000c_exit中添加代码如下:
iounmap(iobase);
2.2.2 去掉版本判断
去掉下面dmfe_probe1()函数中这行芯片版本判断代码:
2.2.3 修改中断
在dm9dev9000c_init函数中,添加修改中断号代码,将irq改为IRQ_EINT7,如下:
irq = IRQ_EINT7; //设置中断号为外部中断7
在上一节二十、Linux驱动之移植DM9000C网卡驱动(上)中分析dmfe_open()函数时提到,申请中断函数时默认的中断触发方式设置不对,应该改为“IRQF_TRIGGER_RISING”,上升沿触发,dmfe_open()函数中修改代码如下:
2.2.4 位宽(这里只做分析,不需要修改代码)
在dmfe_start_xmit()函数中有如下代码:
/*
#define DM9KS_DWORD_MODE 1
#define DM9KS_BYTE_MODE 2
#define DM9KS_WORD_MODE 0
*/
static int dmfe_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
... ...
switch(db->io_mode) //选择位宽模式
{
case DM9KS_BYTE_MODE: //8-bit位宽模式
for (i = 0; i < skb->len; i++)
outb((data_ptr[i] & 0xff), db->io_data);
break;
case DM9KS_WORD_MODE: //16-bit位宽模式
tmplen = (skb->len + 1) / 2;
for (i = 0; i < tmplen; i++)
outw(((u16 *)data_ptr)[i], db->io_data);
break;
case DM9KS_DWORD_MODE: //32-bit位宽模式
tmplen = (skb->len + 3) / 4;
for (i = 0; i< tmplen; i++)
outl(((u32 *)data_ptr)[i], db->io_data);
break;
}
... ...
}
在硬件分析1.1中,我们分析了硬件上如何选择DM9000C位宽模式,如何让CPU知道我们选择了哪种位宽模式呢?在dmfe_init_dm9000()函数中有如下代码:
这里通过读取DM9KS_ISR(宏定义为0xfe)中断状态寄存器第7位来读取位宽模式,我们可以从DM9000CEP芯片手册中看到:
2.2.4 设置2440相关寄存器
1. 设置BWSCON总线宽度控制寄存器
因为DM9000C网卡接在2440的BANK4上,所以要设置2440的BANK4的硬件位宽、时序等。S3C2440A芯片手册有如下:
可得如下几点信息:
1. 设置ST4=0,不使用UB/LB(表示高字节与低字节数据是否分开传输)。
2. 设置WS4=0,其中WAIT引脚为PE4,而我们DM9000C没有引脚接入PE4,所以禁止。
3. 设置DW4=0x01,我们的DM9000C的数据线为16位。
4. BWSCON寄存器物理基地址为0x48000000。
2. 设置BANKCON4控制寄存器
还需要设置BANKCON4控制寄存器。S3C2440A芯片手册有如下:
S3C2440A芯片手册有如下:
可得如下几点信息:
1. 设置Tacs=0,因为CS和CMD可以同时结束(bank4地址(也就是CMD)稳定多久后,CS片选才启动)。
2. 设置Tcos=T1=0(CS片选后,多久才能使能读写)。
3. 设置Tacc=T2>=10ns=1,表示2个时钟 (读写使能后,多久才能访问数据) 。
4. 设置Tcoh=T4>=3ns=1,表示1个时钟 ,因为当DM9000C的写信号取消后,数据线上的数据还需要至少3ns才消失(nOE读写取消后,片选需要维持多长时间)。
5. 设置Tcah=0,因为 CS和CMD可以同时结束 (CS片选取消后,地址(也就是CMD)需要维持多长时间)。
6. 设置PMC为0,正常模式。
7. BANKCON4控制寄存器物理基地址为0x48000014。
对于该部分硬件相关的代码在dm9dev9000c_init()函数中添加如下红框部分:
dm9dev9000c_init()函数完整修改代码如下:
int __init dm9dev9000c_init(void)
{
volatile unsigned long *bwscon; // 0x48000000
volatile unsigned long *bankcon4; // 0x48000014
unsigned long val;
iobase = (int)ioremap(0x20000000, 1024); //重映射基地址
irq = IRQ_EINT7; //修改中断号为IRQ_EINT7
/* 设置S3C2440的memory controller */
bwscon = ioremap(0x48000000, 4);
bankcon4 = ioremap(0x48000014, 4);
/* DW4[17:16]: 1,16bit位宽
* WS4[18] : 0,WAIT disable
* ST4[19] : 0,不使用UB/LB
*/
val = *bwscon;
val &= ~(0xf<<16);
val |= (1<<16);
*bwscon = val;
//Tacc[10:8] : 设为1
//Tcoh[7:6] : 设为1
//*bankcon4 = (1<<8)|(1<<6); /* 对于DM9000C可以设Tacc为1, 对于DM9000E,Tacc要设大一点,比如最大值7 */
*bankcon4 = (7<<8)|(1<<6); /* MINI2440使用DM9000E,Tacc要设大一点 */
iounmap(bwscon);
iounmap(bankcon4);
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;
}
2.2.5 添加头文件
添加相关头文件,代码如下:
#include <asm/delay.h>
#include <asm/irq.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/arch-s3c2410/regs-mem.h>
这样代码就修改完成了,就下来进行编译测试。
3. 测试
内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10
1. 将dm9dev9000c.c拷贝到内核的linux-2.6.22.6/drivers/net目录里。
2. 修改该目录里的Makefile,执行命令如下(我的内核放在/work/system/下):
vi /work/system/linux-2.6.22.6/drivers/net/Makefile (修改如下图)
3. 重新编译内核,下载启动,就可以正常使用网卡了。
make uImage
如下挂载网络文件系统启动,说明移植成功了。