210学习日记(15)_移植DM9000
2012-10-29 19:17
211 查看
210学习日记(15)
--移植DM9000网卡驱动
我想,大家学习到这里,写了一些裸板程序,对S5PV210应该算是比较了解了吧,而且已经写出了一个比较满意的bootloader来了,肯定是想上系统玩玩驱动了吧!
我在"Tiny210学习日记(1)"中提过,要在从内核官网上面下载的纯净的Linux系统上面玩驱动,那么你要不得支持网卡,要不得有MTD分区,因为我们要挂接系统。
接下来说说我是怎么一步一步移植DM9000网卡驱动的:
(在看以下具体操作步骤之前,强烈建议大家把韦东山视频里面关于dm9000的类容和在一起,拉通看一次,讲得太详细了)
1.当自己写好的bootloader成功引导内核时,查看内核的启动信息,发现没有任何网卡相关的启动信息;
注意:
问:这里是使用的官方提供的默认的配置文件s5pv210_defconfig去配置编译内核的。可是将编译出的 zImage烧写进开发板,启动的时候,发现串口无任何启动信息输出,这是为什么呢?
答:千万不要怀疑是我提供的bootloader有错误,从而不能够启动内核。而真正的原因是,我们需要 在make menuconfig中配置一下串口,选为串口0:(当然,大家得根据自己使用串口的具体情况)
Location:
-> System Type
(0) S3C UART to use for low-level messages
2.在make menuconfig中添加上DM9000选项,然后重新编译内核,下载新内核到开发板,观察启动信息,发现只打印了一句关于DM9000的信息,类容如下:
dm9000 Ethernet Driver, V1.31
3.分析内核内核自带的DM9000网卡的驱动程序dm9000.c,发现它是采用总线设备驱动模型写的,于是我怀疑网卡驱动的probe函数是不是没有被调用呢?于是加打印语句验证,果然没有被调用。从而说明没有注册平台设备资源。
4.
问:既然发现没有注册平台设备资料,那就自己去注册一个呗。那么需要添加些什么样的资源呢?
答:分析dm9000.c的probe函数,发现以下几行代码:
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
一看platform_get_resource()函数,那太熟悉了,获得资源(即获得基地址和中断号),条件反射,我们就应该注册平台资源(提供基地址和中断号)。当然,我们也可以像而且视频里面移植网卡的那样,直接在dm9000.c的初始化函数中ioremap()一下基地址,然后再赋值给对应的基地址变量(我相信这种方法,大家在听了二期视频和阅读完这章日志后,自己能够搞定)。我采用注册平台资源的方式。
问:既然选择了注册平台资源,那么在哪里去注册呢?
答:我们可以单独写一个注册平台资源的程序,然后编译进内核。但是为了省事情,我梦直接在arch\arm\
mach-s5pv210\mach-smdkv210.c中注册就好了。内容如下:
static struct resource dm9000_resources[] = {
[0] = {
.start = 0x88000000, /* 用于向DM9000发地址,后面有解释 */
.end = 0x88000000 + 3, /* 这里想加多少都可以,只要大于等于3就行 */
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x88000000 + 4, /* 用于向DM9000发数据,后面有解释 */
.end = 0x88000000 + 7, /* 这里想加多少都可以,只要大于等于3就行 */
.flags = IORESOURCE_MEM,
},
[2] = {
.start = IRQ_EINT(7), /* 中断号 */
.end = IRQ_EINT(7),
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
},
};
static struct dm9000_plat_data dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM, /*位宽是16位,无EEPROM */
.dev_addr = { 0x08, 0x90, 0x00, 0xa0, 0x02, 0x10 }, /* MAC地址 */
};
struct platform_device tiny210_device_dm9000 = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(dm9000_resources),
.resource = dm9000_resources,
.dev = {
.platform_data = &dm9000_platdata,
},
};
问:如何知道基地址是多少呢?如何区分基地址是用于向DM9000发地址,还是发数据呢?
答:看Tiny210的原理图得知,DM9000的CS引脚接到了s5pv210的Xm0CSn1管脚,则对应于Bank 1,地址范围为0x8800_0000 到0x8FFF_FFFF,那么我们的基地址到底该设为多少呢?
我们先来看一下dm9000.c中是怎么来用这些地址的吧,以未经过修改的复位函数为例子:
static void dm9000_reset(board_info_t * db)
{
dev_dbg(db->dev, "resetting device\n"); /* 打印信息 */
/* RESET device */
writeb(DM9000_NCR, db->io_addr); /* 向DM9000发寄存器地址 */
udelay(200);
writeb(NCR_RST, db->io_data); /* 往上述寄存器写入数据 */
udelay(200);
}
从上面的复位函数中发现,我们只使用到了基地址(即注册的地址的平台资源的开始的第一地址),这也就.end
= 0x88000000 + 3可以加大于等于3的任意地址的原因了,因为只用到了开始的四个字节,后面的无用。
另外,从上面的复位函数中看出,是向一个地址发送一个数据,很像SDRAM的操作。但是原理图上面发现,只用一个地址,即ADDR2(站在S5PV210的角度),那么是不是DM9000里面只有0和1两个地址呢?
看过视频和DM9000芯片手册的人一定知道,其实DM9000是地址线和数据线复用,通过DM9000上面的CMD引脚来区分(0为地址,1为数据),而该引脚和S5PV210的ADDR2连接。
所以,绕了那么远,说了那么多,最终就是告诉大家,发出的地址信息里面,只有ADDR2上面的地址信号(1或者0)有用,因此,如果是作为发地址的基地址的话,在0x8800_0000 到0x8FFF_FFFF里面(用的是Xm0CSn1,即Bank 1),任意一个能够让ADDR2输出低电平的地址就行;如果是作为发数据的基地址的话,在0x8800_0000 到0x8FFF_FFFF里面(用的是Xm0CSn1,即Bank 1),任意一个能够让ADDR2输出高电平的地址就行;
注意:
这里让ADDR2输出对应电平信号(高或低),而加一个偏移地址(如0x88000000 + 4中的4),有两种算法,因为在S5PV210中的基地址有两种对齐方式,我会在下面进行讲解。
5.由于在我的bootloader里面没有初始化Bank 1的相关类容(如位宽,时序等),而且为了让网卡驱动程序独立于其他任何程序,我们得做些初始化操作,类容如下:
static void __init tiny210_dm9000_set(void)
{
SROM_BW = ioremap(0xE8000000,4);
SROM_BC1 = ioremap(0xE8000008,4);
MP0_1CON = ioremap(0xE02002E0,4);
*SROM_BC1 = ((0<<28)|(0<<24)|(5<<16)|(0<<12)|(0<<8)|(0<<4)|(0<<0)); /* 设置时序,视频已详细 讲解了 */
*SROM_BW &= ~(0xf << 4);
*SROM_BW |= (0x1 << 4) | (1<<5); /* 位宽为16位,基地址按字节对齐(第5位很重要) */
*MP0_1CON |= 0x2<<4; /* 设置对应GPIO用于Bank 1的片选 */
}
既然上面我已经打开了arch\arm\mach-s5pv210\mach-smdkv210.c,就在该文件的入口函数添加该初始化函数吧(即在smdkv210_machine_init函数里添加tiny210_dm9000_set),当然也可以把该初始化函数设置在网卡驱动的入口函数里面。
问:初始化函数中说SROM_BW寄存器的bit5很重要,为什么呢?
答:还记得在4中讲的偏移值有两种算法吧!而这个算法就和该bit5息息相关,即:
当bit5设置为1时:(基地址按字节对齐)
开发板发出的地址信息 网卡收到的地址信息
A0 ------ A0
A1 ------ A1
A2 ------ A2
... ------ ....
所以,在这种情况下,和2440的设置基地址没有任何差别,开发板的A2就对应DM9000的CMD。上面注册平台资源就是属于这种情况,所以可以加4。
当bit5设置为0时:(基地址按半字对齐)
开发板发出的地址信息 网卡收到的地址信息
A0 ------ 无
A1 ------ A0
A2 ------ A1
... ------ ....
所以,在这种情况下,开发板的A0地址是无效的,那么A3才对应CMD,所以偏移值应该加8之类的值。(在我共享的dm9000的驱动中"half_word"文件中的网卡驱动中,就是用的这种方法)
6.在这里我得向大家道歉一下,我上传上传的驱动由于个人整理时的疏忽,编译时有个错误,如下:
drivers/net/dm9000.c:1612: error: 'struct dm9000_plat_data' has no member named 'param_addr'
怎么解决?说没有这个成员,我就给它添加一个这样的成员呗。在include\linux\dm9000.h中的dm9000_plat_data结构体中添加unsigned char
param_addr[6]成员。接下来,我就说说该成员的作用吧:
在dm9000.c的probe函数里面添加了如下代码:
if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src = "param data";
memcpy(ndev->dev_addr, pdata->param_addr, 6);
}
该部分代码就是设置MAC地址的,当然大家也可以按照视频里面的方法进行设置,这里就不多说了。
7.到这里为止,自我感觉已经移植得差不多了,编译一下,实验一下吧,看看能行不?果然就ok了。
注意:
1).在共享的dm9000的驱动中"byte"文件中的驱动,对应基地址按字节对齐的方法;
2).在共享的dm9000的驱动中"half_word"文件中的驱动,对应基地址按字对齐的方法;
3).再在这里解释一下使用"half_word"的方法时,为什么修改了dm9000_reset函数,类容如下:
static void dm9000_reset(board_info_t *db)
{
dev_dbg(db->dev, "resetting device\n");
iow(db, DM9000_GPCR, 0x0f); /* 设置GPIO寄存器作为输出,(类似于单片机对IO端口的控制) */
iow(db, DM9000_GPR, 0); /* 往GPIO寄存器写值,清除PWER_DOWN信号,使能PHY */
iow(db, DM9000_NCR, 3); /* 复位 */
do {
udelay(100);
} while (ior(db, DM9000_NCR) & 0x1); /* 等待复位成功 */
iow(db, DM9000_NCR, 0);
iow(db, DM9000_NCR, 3); /* 再次服务 */
do {
udelay(100);
} while (ior(db, DM9000_NCR) & 0x1); /* 等待复位成功 */
if ((ior(db, DM9000_PIDL) != 0) || (ior(db, DM9000_PIDH) != 0x90)) /* 读ID */
printk(KERN_INFO "ERROR : resetting ");
}
注:
如有问题,请到韦东山LINUX视频讨论群里面,我们一起讨论学习,或者加我QQ:317312379
--移植DM9000网卡驱动
我想,大家学习到这里,写了一些裸板程序,对S5PV210应该算是比较了解了吧,而且已经写出了一个比较满意的bootloader来了,肯定是想上系统玩玩驱动了吧!
我在"Tiny210学习日记(1)"中提过,要在从内核官网上面下载的纯净的Linux系统上面玩驱动,那么你要不得支持网卡,要不得有MTD分区,因为我们要挂接系统。
接下来说说我是怎么一步一步移植DM9000网卡驱动的:
(在看以下具体操作步骤之前,强烈建议大家把韦东山视频里面关于dm9000的类容和在一起,拉通看一次,讲得太详细了)
1.当自己写好的bootloader成功引导内核时,查看内核的启动信息,发现没有任何网卡相关的启动信息;
注意:
问:这里是使用的官方提供的默认的配置文件s5pv210_defconfig去配置编译内核的。可是将编译出的 zImage烧写进开发板,启动的时候,发现串口无任何启动信息输出,这是为什么呢?
答:千万不要怀疑是我提供的bootloader有错误,从而不能够启动内核。而真正的原因是,我们需要 在make menuconfig中配置一下串口,选为串口0:(当然,大家得根据自己使用串口的具体情况)
Location:
-> System Type
(0) S3C UART to use for low-level messages
2.在make menuconfig中添加上DM9000选项,然后重新编译内核,下载新内核到开发板,观察启动信息,发现只打印了一句关于DM9000的信息,类容如下:
dm9000 Ethernet Driver, V1.31
3.分析内核内核自带的DM9000网卡的驱动程序dm9000.c,发现它是采用总线设备驱动模型写的,于是我怀疑网卡驱动的probe函数是不是没有被调用呢?于是加打印语句验证,果然没有被调用。从而说明没有注册平台设备资源。
4.
问:既然发现没有注册平台设备资料,那就自己去注册一个呗。那么需要添加些什么样的资源呢?
答:分析dm9000.c的probe函数,发现以下几行代码:
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
一看platform_get_resource()函数,那太熟悉了,获得资源(即获得基地址和中断号),条件反射,我们就应该注册平台资源(提供基地址和中断号)。当然,我们也可以像而且视频里面移植网卡的那样,直接在dm9000.c的初始化函数中ioremap()一下基地址,然后再赋值给对应的基地址变量(我相信这种方法,大家在听了二期视频和阅读完这章日志后,自己能够搞定)。我采用注册平台资源的方式。
问:既然选择了注册平台资源,那么在哪里去注册呢?
答:我们可以单独写一个注册平台资源的程序,然后编译进内核。但是为了省事情,我梦直接在arch\arm\
mach-s5pv210\mach-smdkv210.c中注册就好了。内容如下:
static struct resource dm9000_resources[] = {
[0] = {
.start = 0x88000000, /* 用于向DM9000发地址,后面有解释 */
.end = 0x88000000 + 3, /* 这里想加多少都可以,只要大于等于3就行 */
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x88000000 + 4, /* 用于向DM9000发数据,后面有解释 */
.end = 0x88000000 + 7, /* 这里想加多少都可以,只要大于等于3就行 */
.flags = IORESOURCE_MEM,
},
[2] = {
.start = IRQ_EINT(7), /* 中断号 */
.end = IRQ_EINT(7),
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
},
};
static struct dm9000_plat_data dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM, /*位宽是16位,无EEPROM */
.dev_addr = { 0x08, 0x90, 0x00, 0xa0, 0x02, 0x10 }, /* MAC地址 */
};
struct platform_device tiny210_device_dm9000 = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(dm9000_resources),
.resource = dm9000_resources,
.dev = {
.platform_data = &dm9000_platdata,
},
};
问:如何知道基地址是多少呢?如何区分基地址是用于向DM9000发地址,还是发数据呢?
答:看Tiny210的原理图得知,DM9000的CS引脚接到了s5pv210的Xm0CSn1管脚,则对应于Bank 1,地址范围为0x8800_0000 到0x8FFF_FFFF,那么我们的基地址到底该设为多少呢?
我们先来看一下dm9000.c中是怎么来用这些地址的吧,以未经过修改的复位函数为例子:
static void dm9000_reset(board_info_t * db)
{
dev_dbg(db->dev, "resetting device\n"); /* 打印信息 */
/* RESET device */
writeb(DM9000_NCR, db->io_addr); /* 向DM9000发寄存器地址 */
udelay(200);
writeb(NCR_RST, db->io_data); /* 往上述寄存器写入数据 */
udelay(200);
}
从上面的复位函数中发现,我们只使用到了基地址(即注册的地址的平台资源的开始的第一地址),这也就.end
= 0x88000000 + 3可以加大于等于3的任意地址的原因了,因为只用到了开始的四个字节,后面的无用。
另外,从上面的复位函数中看出,是向一个地址发送一个数据,很像SDRAM的操作。但是原理图上面发现,只用一个地址,即ADDR2(站在S5PV210的角度),那么是不是DM9000里面只有0和1两个地址呢?
看过视频和DM9000芯片手册的人一定知道,其实DM9000是地址线和数据线复用,通过DM9000上面的CMD引脚来区分(0为地址,1为数据),而该引脚和S5PV210的ADDR2连接。
所以,绕了那么远,说了那么多,最终就是告诉大家,发出的地址信息里面,只有ADDR2上面的地址信号(1或者0)有用,因此,如果是作为发地址的基地址的话,在0x8800_0000 到0x8FFF_FFFF里面(用的是Xm0CSn1,即Bank 1),任意一个能够让ADDR2输出低电平的地址就行;如果是作为发数据的基地址的话,在0x8800_0000 到0x8FFF_FFFF里面(用的是Xm0CSn1,即Bank 1),任意一个能够让ADDR2输出高电平的地址就行;
注意:
这里让ADDR2输出对应电平信号(高或低),而加一个偏移地址(如0x88000000 + 4中的4),有两种算法,因为在S5PV210中的基地址有两种对齐方式,我会在下面进行讲解。
5.由于在我的bootloader里面没有初始化Bank 1的相关类容(如位宽,时序等),而且为了让网卡驱动程序独立于其他任何程序,我们得做些初始化操作,类容如下:
static void __init tiny210_dm9000_set(void)
{
SROM_BW = ioremap(0xE8000000,4);
SROM_BC1 = ioremap(0xE8000008,4);
MP0_1CON = ioremap(0xE02002E0,4);
*SROM_BC1 = ((0<<28)|(0<<24)|(5<<16)|(0<<12)|(0<<8)|(0<<4)|(0<<0)); /* 设置时序,视频已详细 讲解了 */
*SROM_BW &= ~(0xf << 4);
*SROM_BW |= (0x1 << 4) | (1<<5); /* 位宽为16位,基地址按字节对齐(第5位很重要) */
*MP0_1CON |= 0x2<<4; /* 设置对应GPIO用于Bank 1的片选 */
}
既然上面我已经打开了arch\arm\mach-s5pv210\mach-smdkv210.c,就在该文件的入口函数添加该初始化函数吧(即在smdkv210_machine_init函数里添加tiny210_dm9000_set),当然也可以把该初始化函数设置在网卡驱动的入口函数里面。
问:初始化函数中说SROM_BW寄存器的bit5很重要,为什么呢?
答:还记得在4中讲的偏移值有两种算法吧!而这个算法就和该bit5息息相关,即:
当bit5设置为1时:(基地址按字节对齐)
开发板发出的地址信息 网卡收到的地址信息
A0 ------ A0
A1 ------ A1
A2 ------ A2
... ------ ....
所以,在这种情况下,和2440的设置基地址没有任何差别,开发板的A2就对应DM9000的CMD。上面注册平台资源就是属于这种情况,所以可以加4。
当bit5设置为0时:(基地址按半字对齐)
开发板发出的地址信息 网卡收到的地址信息
A0 ------ 无
A1 ------ A0
A2 ------ A1
... ------ ....
所以,在这种情况下,开发板的A0地址是无效的,那么A3才对应CMD,所以偏移值应该加8之类的值。(在我共享的dm9000的驱动中"half_word"文件中的网卡驱动中,就是用的这种方法)
6.在这里我得向大家道歉一下,我上传上传的驱动由于个人整理时的疏忽,编译时有个错误,如下:
drivers/net/dm9000.c:1612: error: 'struct dm9000_plat_data' has no member named 'param_addr'
怎么解决?说没有这个成员,我就给它添加一个这样的成员呗。在include\linux\dm9000.h中的dm9000_plat_data结构体中添加unsigned char
param_addr[6]成员。接下来,我就说说该成员的作用吧:
在dm9000.c的probe函数里面添加了如下代码:
if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src = "param data";
memcpy(ndev->dev_addr, pdata->param_addr, 6);
}
该部分代码就是设置MAC地址的,当然大家也可以按照视频里面的方法进行设置,这里就不多说了。
7.到这里为止,自我感觉已经移植得差不多了,编译一下,实验一下吧,看看能行不?果然就ok了。
注意:
1).在共享的dm9000的驱动中"byte"文件中的驱动,对应基地址按字节对齐的方法;
2).在共享的dm9000的驱动中"half_word"文件中的驱动,对应基地址按字对齐的方法;
3).再在这里解释一下使用"half_word"的方法时,为什么修改了dm9000_reset函数,类容如下:
static void dm9000_reset(board_info_t *db)
{
dev_dbg(db->dev, "resetting device\n");
iow(db, DM9000_GPCR, 0x0f); /* 设置GPIO寄存器作为输出,(类似于单片机对IO端口的控制) */
iow(db, DM9000_GPR, 0); /* 往GPIO寄存器写值,清除PWER_DOWN信号,使能PHY */
iow(db, DM9000_NCR, 3); /* 复位 */
do {
udelay(100);
} while (ior(db, DM9000_NCR) & 0x1); /* 等待复位成功 */
iow(db, DM9000_NCR, 0);
iow(db, DM9000_NCR, 3); /* 再次服务 */
do {
udelay(100);
} while (ior(db, DM9000_NCR) & 0x1); /* 等待复位成功 */
if ((ior(db, DM9000_PIDL) != 0) || (ior(db, DM9000_PIDH) != 0x90)) /* 读ID */
printk(KERN_INFO "ERROR : resetting ");
}
注:
如有问题,请到韦东山LINUX视频讨论群里面,我们一起讨论学习,或者加我QQ:317312379
相关文章推荐
- 210学习日记(1)--遇到的问题
- 初始学习TQ210的uboot移植第一天55
- java学习日记_15:面向对象之形式参数为类时的调用:07.02
- 2015.07.26 STC15单片机学习日记-储存器结构
- u-boot移植到mini2440,增加DM9000驱动的学习笔记
- 210学习日记(3)_支持串口
- Zynq-Linux移植学习笔记之15-用户APP直接访问PL物理地址
- 2015.07.28 STC15单片机学习日记--NRF24L01 6通道调试
- 210学习日记(4)_printf的实现
- C#学习日记15----引用类型 之 string类型用法总结
- 2015.08.01 STC15单片机学习日记-并行I/O口
- 黑马程序员_java学习日记num15
- 210学习日记(5)_简单命令的实现.doc
- u-boot移植日记 (经典文章转载,学习中)
- 210学习日记(7)_支持NAND
- OK6410-A开发板学习-⑤uboot移植(3)DM9000 网卡驱动移植
- 210学习日记(8)_支持DDR
- web前端学习日记15------angularjs京东购物车继续
- 黑马程序员--交通灯管理系统--java学习日记15(7K)
- 【笨鸟先飞】Java重新学习日记15--设计模式之观察者模式