嵌入式Linux——网卡驱动(4):移植DM9000C厂家驱动到JZ2440

2019-07-12 23:09发布

简介:本文主要介绍如何将厂家提供的网卡驱动程序dm9dev9000c.c移植到我们的开发板JZ2440中。而本文将会从硬件方面入手,具体介绍如何修改厂家提供的驱动,以使其适应本开发板。 一.下面我们介绍一下我们的开发环境: 开发板:JZ2440(CPU为S3C2440) Linux版本: 2.6.22.6 二.介绍完开发环境,我们就从硬件入手先了解在本开发板中DM9000C是如何接线的,以及这样接线的目的。     我们先看一下在开发板中他的电路图: 而为了让大家可以对他们的连线一目了然,我画了一个简单的连线图(我只画了比较重要的一些连线): 注:#表示低电平有效。 从图中可以看到,我将连线分为了四类,分别为: 1. 数据传输线 2. 片选引脚线 3. 中断引脚线 4. 地址/数据命令线         下面我们一一对其介绍,我们首先看数据传输线,通过数据传输线我们可以看出我们开发板上所使用的DM9000C的数据宽度为16bit。从我们以前学的知识我们知道一个设备的数据宽度为16bit,那么他对应的地址总线宽度也应该是16bit。而在我们的电路图中我们却没有找到16位宽的地址总线,而只找到了与DM9000C芯片CMD引脚相连的LADDR2引脚。而通过我们前面在NAND Flash中学的知识我们知道,如果这个芯片只找到数据引脚而没有找到地址引脚,那么这个芯片的数据引脚就和地址引脚复用了。同时我们也知道如果出现引脚复用,那么就一定有一个引脚,通过设置该引脚进而设置芯片输入输出的是地址或者数据。而在本芯片中,这个引脚就是上面的地址/数据命令线所连接CMD引脚。当该引脚为高电平时这16根数据线为传输数据。而如果该引脚为低电平时,这16根数据线传输的是地址数据。     同时我们看到DM9000C的片选引脚是和2440的nGCS4相连。而通过看DM9000C的芯片手册知道他的片选是低电平有效的,而如何选中他们,即如何将该引脚拉为低电平。我们看2440 的数据手册的内存管理这章可以知道,当我们的操作地址在0x20000000到0x28000000之间时,片选nGCS4为低电平。这样DM9000C的片选也就是低电平了。如图所示: 上图中红 {MOD}边框圈起来的就是nGCS4所对应的地址范围,即0x20000000~0x28000000 下面我们讲最后一连线:中断引脚线。我们知道DM9000C接收是通过中断通知的。当有数据到来时,通过触发中断,在中断处理函数中,我们做对数据接收的处理。而这个中断引脚对应到2440中对应的是外部中断7,如下图:   我们在上一篇文章:嵌入式Linux——网卡驱动(3):结合硬件分析厂家提供驱动代码dm9dev9000c.c对厂家提供的驱动程序dm9dev9000c.c进行了分析。而通过上面文章的分析我们知道厂家在驱动程序中已经为我们做好了网络驱动上层的协议,而我们所要做的就是结合自己的开发板,来对厂家所没有做的关于物理层相关的设置。同时我们也要想,厂家的驱动程序不是为我们一块开发板所写的。而是为所有要用这块芯片的开发板所写。所以厂商会尽自己所能的使这个驱动程序适应更多的开发板,使得各个开发板只要注意他们最基本的差异就好,即基地址,位宽,复用引脚,中断引脚以及时间要求。而设置好这些我们就可以试着将厂家提供的驱动程序在自己的开发板上运行了。 三. 下面我们就开始我们第三部分,通过读厂家提供的程序,发现其中的不足,对其进行修改。 还是要提醒大家,我们可以只关心与物理层相关的设置,而不用去关心他上层的设计。因为上层的协议厂家已经帮我们做好了。而我们的方法就是找与物理层相关的设置,并看他与什么相关,而这个相关量的设置是否正确。下面我们按着这个思路去读程序。 我们直接从probe函数分析(由于在上一篇文章中对程序已经分析了所以在本文中不做详细分析)。 struct net_device * __init dmfe_probe(void) { dev= alloc_etherdev(sizeof(struct board_info)); /* 分配一个net_device结构体 */         ·········· err = dmfe_probe1(dev); /* 设置net_device结构体 */         ·········· err = register_netdev(dev); /* 注册一个net_device结构体 */ } 我们知道在设置net_device结构体中做硬件相关的设置。所以我们进入dmfe_probe1(dev)函数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); id_val = inb(iobase + 4); /* 读厂家ID的低字节 */ outb(DM9KS_VID_H, iobase); id_val |= inb(iobase + 4) << 8; /* 读厂家ID的高字节 */ outb(DM9KS_PID_L, iobase); id_val |= inb(iobase + 4) << 16; /* 读设备ID的低字节 */ outb(DM9KS_PID_H, iobase); id_val |= inb(iobase + 4) << 24; /* 读设备ID的高字节 */ if (id_val == DM9KS_ID || id_val == DM9010_ID) { /* 将读到的与芯片的比较,如果一样则继续运行 */ /* Request IO from system */ if(!request_region(iobase, 2, dev->name)) /* 分配网络设配器所占内存 */ return -ENODEV; printk(KERN_ERR" I/O: %x, VID: %x ",iobase, id_val); dm9000_found = TRUE; /* Allocated board information structure */ memset(dev->priv, 0, sizeof(struct board_info)); /* 设置net_device结构体的私有数据大小为board_info大小 */ db = (board_info_t *)dev->priv; /* 将board_info结构体放到dev的私有数据中 */ dmfe_dev = dev; db->io_addr = iobase; /* 设置芯片命令寄存器的基地址 */ db->io_data = iobase + 4; /* 设置芯片数据寄存器的基地址,而此处为什么加4,是因为CMD引脚与地址laddr2引脚相连 */ db->chip_revision = ior(db, DM9KS_CHIPR); /* 获取芯片的版本信息,CHIPR为芯片版本寄存器地址为2CH */ 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; /* 设置IO基地址 */ dev->irq = irq; /* 设置中断序列号,在本程序中使用外部中断7,这在后面设置 */ 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|NETIF_F_SG; #endif /* 设置MII总线 */ db->mii.dev = dev; /* 设置MII接口 */ db->mii.mdio_read = mdio_read; /* MII方式读函数 */ db->mii.mdio_write = mdio_write; /* MII方式写函数 */ db->mii.phy_id = 1; db->mii.phy_id_mask = 0x1F; db->mii.reg_num_mask = 0x1F; }//end of if() iobase += 0x10; }while(!dm9000_found && iobase <= DM9KS_MAX_IO); return dm9000_found ? 0:-ENODEV; }   通过观察电路图我们发现我们的开发板没有接EEPROM,所以我将上面的与EEPROM的部分代码删了。下面我们分析代码,首先我们看到outb(DM9KS_VID_L, iobase)语句,我们知道他的功能是将寄存器DM9KS_VID_L中的值写入到基地址iobase中。而基地址是与物理硬件相关的。所以我们要看看他是否是我们在前面介绍的基地址0x20000000。如果是我们继续分析,如果不是,我们对其进行修改。我们观察代码发现:   static int iobase = DM9KS_MIN_IO; 而DM9KS_MIN_IO为: #define DM9KS_MIN_IO 0x300 显然这个不是我们想要的基地址,所以我们要修改他为0x20000000,同时我们知道在内核中不会识别物理地址,所以要对他进行地址重映射,所以我们在入口函数中加 /* 设置基地址,对iobase进行重映射 */ iobase = (int)ioremap(0x20000000,1024); /* 第二个参数1024根据你所用空间大小而设,这里其实没有用到这么大的空间 */ 既然在入口函数中加了ioremap那么我们就应该在出口函数中加iounmapiounmap((void *)iobase); 我们会发现设置完基地址后,很多函数设置都会用到与其相关的设置如: db->io_addr = iobase; db->io_data = iobase + 4; dev->base_addr = iobase; 他们都用到了iobase,由于我们在前面设置了,所以这里就可以直接使用了。 我们接着看代码,看看那些还是那些与2440硬件相关的设置,下一个我们会看到: dev->irq = irq; 我们开发板的外部中断7与DM9000C的INT引脚相连。我们看看这里的irq设置为什么。 我们会看到: static int irq = DM9KS_IRQ; 而DM9KS_IRQ为: #define DM9KS_IRQ 3我们外部中断7为51 。所以不匹配。我们将这里改为: /* 设置irq,设置中断号 */ irq = IRQ_EINT7; 而设置完这些就是该net_device结构体回调函数的设置了 下面我们进主要的三个函数:open函数,接收函数,以及中断函数(其中包括接收函数)。我们知道当我们连接好DM9000C在开发板上用ifconfig命令时,会打开open函数。所以我们先从open函数开始分析: 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,0,dev->name,dev)) /* 注册中断函数 */ return -EAGAIN; /* Initilize DM910X board,初始化DM9000 */ dmfe_init_dm9000(dev); /* Init driver variable,初始化驱动变量 */ db->reset_counter = 0; db->reset_tx_timeout = 0; db->cont_rx_pkt_cnt = 0; /* check link state and media speed,检测连接状态和设备速度 */ db->Speed =10; i=0; do { reg_nsr = ior(db,DM9KS_NSR); if(reg_nsr & 0x40) /* link OK!! */ { /* wait for detected Speed */ mdelay(200); reg_nsr = ior(db,DM9KS_NSR); if(reg_nsr & 0x80) db->Speed =10; else db->Speed =100; break; } i++; mdelay(1); }while(i<3000); /* wait 3 second */ /* set and active a timer process,设置并使能时间处理函数 */ init_timer(&db->timer); db->timer.expires = DMFE_TIMER_WUT; db->timer.data = (unsigned long)dev; db->timer.function = &dmfe_timer; add_timer(&db->timer); //Move to DM9000 initiallization was finished. netif_start_queue(dev); return 0; } 我们第一个看到的就是申请中断函数: request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev) 而他的第一个参数我们在前面已经设置了为外部中断7,而第二个参数为中断处理函数,我们会在后面分析,而第三个参数是中断触发方式,其中0表示: #define IRQT_NOEDGE (0) /* 0 表示没有边沿 */ 显然这个不是我们想要的我们需要上升沿触发,所以这里我们改第三个参数为:IRQT_RISING第四个参数为中断名,这个没有要求。第五个参数为设备ID即为这个设备。   改完这个我们发现没有与硬件2440 相关的设置了。那么我们看下一个发送函数。   static int dmfe_start_xmit(struct sk_buff *skb, struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; char * data_ptr; int i, tmplen; u16 MDWAH, MDWAL; DMFE_DBUG(0, "dmfe_start_xmit", 0); if (db->chip_revision != 0x1A) { if(db->Speed == 10) {if (db->tx_pkt_cnt >= 1) return 1;} else {if (db->tx_pkt_cnt >= 2) return 1;} }else if (db->tx_pkt_cnt >= 2) return 1; /* packet counting,发送数据包加一 */ db->tx_pkt_cnt++; db->stats.tx_packets++; /* 发送数据包加1 */ db->stats.tx_bytes+=skb->len; /* 发送数据长度加数据的长度 */ if (db->chip_revision != 0x1A) { if (db->Speed == 10) {if (db->tx_pkt_cnt >= 1) netif_stop_queue(dev);} /* 停止接收队列 */ else {if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev);} }else if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev); /* Disable all interrupt */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* 关闭所有中断 */ MDWAH = ior(db,DM9KS_MDWAH); /* 读内存数据写地址寄存器高字节 */ MDWAL = ior(db,DM9KS_MDWAL); /* 读内存数据写地址寄存器低字节 */ /* Set TX length to reg. 0xfc & 0xfd */ iow(db, DM9KS_TXPLL, (skb->len & 0xff)); /* 将数据包长度低字节写入传输数据包长度低字节寄存器 */ iow(db, DM9KS_TXPLH, (skb->len >> 8) & 0xff); /* 将数据包长度高字节写入传输数据包长度高字节寄存器 */ /* Move data to TX SRAM */ data_ptr = (char *)skb->data; /* 将sk_buff中数据的地址赋值给SRAM */ outb(DM9KS_MWCMD, db->io_addr); /* 操作内存数据写命令寄存器,向发送SRAM中写数据 */ switch(db->io_mode) /* 选择IO模式,为16bit或者8bit */ { case DM9KS_BYTE_MODE: for (i = 0; i < skb->len; i++) outb((data_ptr[i] & 0xff), db->io_data); break; case DM9KS_WORD_MODE: tmplen = (skb->len + 1) / 2; /* 计算发送长度 */ for (i = 0; i < tmplen; i++) /* 向SRAM中写入数据 */ outw(((u16 *)data_ptr)[i], db->io_data); break; case DM9KS_DWORD_MODE: tmplen = (skb->len + 3) / 4; for (i = 0; i< tmplen; i++) outl(((u32 *)data_ptr)[i], db->io_data); break; } /* Saved the time stamp,保存时间戳 */ dev->trans_start = jiffies; /* 写入发送数据包的时间戳 */ db->cont_rx_pkt_cnt =0; /* Free this SKB,释放sk_buff */ dev_kfree_skb(skb); /* Re-enable interrupt ,重新开启全部中断*/ iow(db, DM9KS_IMR, DM9KS_REGFF); return 0; } 我们可以看到这里只有db->io_mode这个设置传输位宽的与2440的硬件相关,而其他的并无相关。所以我们要看一下他的设置是否正确。而我们在dmfe_init_dm9000函数中找到了对位宽的设置:   /* I/O mode */ db->io_mode = ior(db, DM9KS_ISR) >> 6; /* ISR bit7:6 keeps I/O mode */ 通过上面的程序我们知道,这个位宽是通过读芯片中断状态寄存器的bit[7]获得的。而芯片可以自己感知这是几位的位宽。所以不用我们自己修改。 那么我们就接着看接收中断处理函数吧。 static void dmfe_interrupt(int irq, void *dev_id, struct pt_regs *regs) {     ········· spin_lock(&db->lock); /* 对临界资源加锁 */ /* Save previous register address */ reg_save = inb(db->io_addr); /* 保存寄存器基地址 */ /* Disable all interrupt */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* 关闭所有中断 */ /* Got DM9000/DM9010 interrupt status */ int_status = ior(db, DM9KS_ISR); /* 读中断状态寄存器,获得中断状态*/ iow(db, DM9KS_ISR, int_status); /* 向中断状态寄存器获得中断位置写1,清除该中断 */ /* Link status change */ if (int_status & DM9KS_LINK_INTR) /* 判断连接状态*/ { netif_stop_queue(dev); /* 停止队列 */ for(i=0; i<500; i++) /*wait link OK, waiting time =0.5s */ { phy_read(db,0x1); if(phy_read(db,0x1) & 0x4) /*Link OK*/ { /* wait for detected Speed */ for(i=0; i<200;i++) udelay(1000); /* set media speed */ if(phy_read(db,0)&0x2000) db->Speed =100; else db->Speed =10; break; } udelay(1000); } netif_wake_queue(dev); /* 唤醒队列 */ //printk("[INTR]i=%d speed=%d ",i, (int)(db->Speed)); } /* 接收数据包 */ if (int_status & DM9KS_RX_INTR) dmfe_packet_receive(dev); /* 接收数据包函数 */ /* Trnasmit Interrupt check */ if (int_status & DM9KS_TX_INTR) /* 判断传输是否完成 */ dmfe_tx_done(0); if (db->cont_rx_pkt_cnt>=CONT_RX_PKT_CNT) { iow(db, DM9KS_IMR, 0xa2); } else { /* Re-enable interrupt mask */ iow(db, DM9KS_IMR, DM9KS_REGFF); /* 使能全部中断 */ } /* Restore previous register address */ outb(reg_save, db->io_addr); /* 恢复中断处理前中断寄存器地址 */ spin_unlock(&db->lock); /* 对临界资源解锁 */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) return IRQ_HANDLED; #endif } 我们会发现只有db->io_addr与2440的物理层相关,而这个在我们当初设置iobase时就已经设置了。所以这里可以用前面的设置而不用再为其做新的设置。 那么我们进入中断处理程序中的接收函数static void dmfe_packet_receive(struct net_device *dev) { ··············· do { /*保存内存数据读地址寄存器的值*/ MDRAH=ior(db, DM9KS_MDRAH); /* 读内存数据寄存器高字节数据 */ MDRAL=ior(db, DM9KS_MDRAL); /* 读内存数据寄存器低字节数据 */ /* 从接收SRAM中读数据,读取该指令之后,指向内部SRAM的读指针不变。DM9000A开始预取SRAM中数据到内部数据缓冲中 */ ior(db, DM9KS_MRCMDX); /* 读内存数据预取读命令寄存器 */ rxbyte = inb(db->io_data); /* 获取最新的更新信息 */ /* 数据包准备好,准备接收数据长度和状态 */ GoodPacket = TRUE; outb(DM9KS_MRCMD, db->io_addr); /* 向寄存器发送读命令 */ /* Read packet status & length */ switch (db->io_mode) /* 选择IO模式有8bit,16bit和32bit */ { case DM9KS_BYTE_MODE: *ptr = inb(db->io_data) + (inb(db->io_data) << 8); *(ptr+1) = inb(db->io_data) + (inb(db->io_data) << 8); break; case DM9KS_WORD_MODE: *ptr = inw(db->io_data); *(ptr+1) = inw(db->io_data); /* 将数据地址传给SRAM指针 */ break; case DM9KS_DWORD_MODE: tmpdata = inl(db->io_data); *ptr = tmpdata; *(ptr+1) = tmpdata >> 16; break; default: break; } /* Packet status check,包状态检测 */ if (rx.desc.status & 0xbf) /* 检查接收状态是否出错 */ { GoodPacket = FALSE; if (rx.desc.status & 0x01) { db->stats.rx_fifo_errors++; printk(KERN_INFO" "); /* 检查FIFO是否出错 */ } if (rx.desc.status & 0x02) { db->stats.rx_crc_errors++; printk(KERN_INFO" ");/* 检查CRC是否出错 */ } if (rx.desc.status & 0x80) /* 检查包长度是否出错 */ { db->stats.rx_length_errors++; printk(KERN_INFO" "); } if (rx.desc.status & 0x08)/* 检查物理层是否出错 */ printk(KERN_INFO" "); } if (!GoodPacket) /* 如果不是正确的包,就丢掉从新接收 */ { // drop this packet!!! switch (db->io_mode) { case DM9KS_BYTE_MODE: for (i=0; iio_data); break; case DM9KS_WORD_MODE: tmplen = (rx.desc.length + 1) / 2; for (i = 0; i < tmplen; i++) inw(db->io_data); break; case DM9KS_DWORD_MODE: tmplen = (rx.desc.length + 3) / 4; for (i = 0; i < tmplen; i++) inl(db->io_data); break; } continue;/*next the packet*/ } skb = dev_alloc_skb(rx.desc.length+4); /* 分配sk_buff */ /* Move data from DM9000 */ skb->dev = dev; skb_reserve(skb, 2); rdptr = (u8*)skb_put(skb, rx.desc.length - 4); /* Read received packet from RX SARM,从接收SRAM中读取接收的包数据 */ switch (db->io_mode) { case DM9KS_BYTE_MODE: for (i=0; iio_data); break; case DM9KS_WORD_MODE: tmplen = (rx.desc.length + 1) / 2; for (i = 0; i < tmplen; i++) ((u16 *)rdptr)[i] = inw(db->io_data); /* 读取包数据 */ break; case DM9KS_DWORD_MODE: tmplen = (rx.desc.length + 3) / 4; for (i = 0; i < tmplen; i++) ((u32 *)rdptr)[i] = inl(db->io_data); break; /* Pass to upper layer */ skb->protocol = eth_type_trans(skb,dev); /* 通知上层协议栈处理 */ #ifdef CHECKSUM if((rxbyte&0xe0)==0) /* receive packet no checksum fail */ skb->ip_summed = CHECKSUM_UNNECESSARY; #endif netif_rx(skb); /* 发送sk_buff */ dev->last_rx=jiffies; /* 设置时间戳 */ db->stats.rx_packets++; /* 更新统计数据包个数 */ db->stats.rx_bytes += rx.desc.length;/* 更新统计数据包长度 */ db->cont_rx_pkt_cnt++; if (db->cont_rx_pkt_cnt>=CONT_RX_PKT_CNT) { dmfe_tx_done(0); break; } } }while((rxbyte & 0x01) == DM9KS_PKT_RDY); DMFE_DBUG(0, "[END]dmfe_packet_receive()", 0); } 我们从中找到与2440设置相关的为:db->io_data,db->io_addr,db->io_mode。而这些我们都已经在前面设置过了。所以我们就不在修改了。 做完这些我们该做一些2440专属的设置 我们通过设置2440内存管理这章的总线宽度和等待控制寄存器(BWSCON)的bit[16:17],bit[18],bit[19]。他们的相关设置为: volatile unsigned long *bwscon; unsigned long val; /* 映射,设置寄存器 */ bwscon=ioremap(0x48000000,4); //0x48000000为bwscon的物理地址 /* *bit[19]=0 ST4 0 = Not using UB/LB (The pins are dedicated nWBE[3:0]) *bit[18]=0 ws4 0 = WAIT disable //dm9000c网卡芯片没有接等待信号 *bit[17:16]=1 dw4 01 = 16-bit //dm9000c网卡是16位模式 */ val=*bwscon; val &=~(0xf<<16); val |=(1<<16); *bwscon=val; iounmap(bwscon);//释放映射的地址 好了,做到现在我们修改的工作就大致完成了。那么我们接下来就可以编译这个驱动了 。看是否可以通过。 我们很多时候是会有编译错误的。因为我们做修改用到的一些函数我们并没有将他们的头文件包含进来。所以我们要包含这些头文件: #include #include #include #include #include 同时需要我们注意的一点是,   if((db->chip_revision!=0x1A) || ((chip_info&(1<<5))!=0) || ((chip_info&(1<<2))!=1)) return -ENODEV; 由于我们芯片的版本与上面的检测版本不匹配,所以我们将这句话注释掉。 现在我们发现我们的驱动程序可以编译通过了。 其实我们程序中还有一点瑕疵 那就是我们程序中关于时间的设置。我们现在的驱动程序可以编译通过并能放在内核中运行是因为uboot帮我们做了时间相关的设置。而如果我们想编写一个不依赖于uboot的网卡驱动我们就要自己设置时间参数,而这些时间参数在2440手册内存管理部分的BANKCON4(0x48000014)。详细的参数为: 而在DM9000C中对应的部分为: 参考上图,得出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)需要维持多长时间)
所以相应的程序为: volatile unsigned long *bankcon4; unsigned long val; /* 映射,设置寄存器 */ bankcon4=ioremap(0x48000014,4); /* *bit[14:13]=0 地址信号发出后多久发片选信号 00 = 0 clock *bit[12:11]=0 片选信号发出后多久发出读信号 00 = 0 cloc ​*bit[10:8]=1 读/写信号脉冲宽度 001 = 2 clocks *bit[7:6]=1 读信号消失后,地址信号和片选信号还要维持多久 01 = 1 clock *bit[5:4]=0 片选信号消失多久后地址信号消失 00 = 0 clock *bit[1:0]=0 正常模式 00 = normal (1 data) */ *bankcon4=(1<<8)|(1<<6); iounmap(bankcon4); 做完这些我们就改好了一个不依赖于uboot的网卡驱动了。 四.下面我们将它放入内核代替原有的DM9000的驱动。 1) 把dm9dev9000c.c放到内核的drivers/net目录下,来替换原来内核的DM9000C

2) 修改drivers/net/Makefile 

    把
    obj-$(CONFIG_DM9000) += dm9000.o
    改为
    obj-$(CONFIG_DM9000) += dm9dev9000c.o 3) make uImage     并将新内核拷贝到文件系统目录下 4) 使用新内核启动

    启动后用:ifconfig eth0 192.168.2.107       ping其他机器:ping 192.168.2.1       如果可以ping通就表示驱动修改好了。   参考文章: 27.Linux-DM9000C网卡移植(详解)
linux内核移植-DM9000C移植笔记
移植DM9000C驱动程序之确定相异性
移植驱动到内核学习笔记1-----DM9000C驱动
linux-2.6.32在mini2440开发板上移植DM9000 网卡驱动