rtems网络移植-实现网卡MDIO通信
本博文适用于bbb的bsp移植。
在本人早些时间写的一篇短博文中提到为rtems移植网络驱动的想法:bbb上rtems的tcp/ip协议移植的想法
其中提到驱动移植主要是移植底层的驱动代码,那么现在有很多系统源码都支持bbb板的网卡,比如android for bbb,ubuntu,redhat等。这些系统的确包含驱动源码,但由于系统过于繁琐,设备树过于庞大,导致移植代码的难度增加不少。
但是却忽略了一个更为普遍的系统:uboot
uboot是绝对支持bbb板的网卡,因为各大linux/unix系统的启动都依靠uboot,而很多开发者都依靠uboot来从服务器加载系统,因此uboot的开发团队绝对会紧跟嵌入式硬件的脚步,可以说uboot对于各种cpu、开发板的支持是最可靠、最快速的了。
而且uboot相比其他系统更为精简,代码移植难度小。
因此接下来开始移植uboot的驱动,整个开发周期应该会持续一个月左右,在过程中,大家有什么问题或者本人有什么疏忽的请留言。
首先查看bbb板上处理器和网卡的连接方式:
要明确一点,bbb板的am3359处理器是自带MAC。
上图可以看出,处理器和网卡的连接方式是GMII方式,同时加上MDIO和网卡的控制寄存器进行通信。
首先弄明白uboot如何进行mdio通信。
查看uboot源码,首先查看网卡的注册和初始化方式:
uboot在start.s启动后首先调用的关于硬件初始化的函数是,eth_initialize函数:
源码:/net/eth.c
int eth_initialize(void)
{
int num_devices = 0;
struct udevice *dev;
eth_common_init();
/*
* Devices need to write the hwaddr even if not started so that Linux
* will have access to the hwaddr that u-boot stored for the device.
* This is accomplished by attempting to probe each device and calling
* their write_hwaddr() operation.
*/
uclass_first_device(UCLASS_ETH, &dev);
if (!dev) {
printf("No ethernet found.\n");
bootstage_error(BOOTSTAGE_ID_NET_ETH_START);
} else {
char *ethprime = getenv("ethprime");
struct udevice *prime_dev = NULL;
if (ethprime)
prime_dev = eth_get_dev_by_name(ethprime);
if (prime_dev) {
eth_set_dev(prime_dev);
eth_current_changed();
} else {
eth_set_dev(NULL);
}
bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT);
do {
if (num_devices)
printf(", ");
printf("eth%d: %s", dev->seq, dev->name);
if (ethprime && dev == prime_dev)
printf(" [PRIME]");
eth_write_hwaddr(dev);
uclass_next_device(&dev);
num_devices++;
} while (dev);
putc('\n');
}
return num_devices;
}
函数中调用了eth_common_init函数,该函数实现如下:
static void eth_common_init(void)
{
bootstage_mark(BOOTSTAGE_ID_NET_ETH_START);
#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
miiphy_init();
#endif
#ifdef CONFIG_PHYLIB
phy_init();
#endif
/*
* If board-specific initialization exists, call it.
* If not, call a CPU-specific one
*/
if (board_eth_init != __def_eth_init) {
if (board_eth_init(gd->bd) < 0)
printf("Board Net Initialization Failed\n");
} else if (cpu_eth_init != __def_eth_init) {
if (cpu_eth_init(gd->bd) < 0)
printf("CPU Net Initialization Failed\n");
} else {
#ifndef CONFIG_DM_ETH
printf("Net Initialization Skipped\n");
#endif
}
}
其中miiphy_init函数是注册mii设备,让其加入链表。
接着是调用phy_init函数,该函数实现了对于网卡设备的注册和初始化:
int phy_init(void)
{
#ifdef CONFIG_PHY_AQUANTIA
phy_aquantia_init();
#endif
#ifdef CONFIG_PHY_ATHEROS
phy_atheros_init();
#endif
#ifdef CONFIG_PHY_BROADCOM
phy_broadcom_init();
#endif
#ifdef CONFIG_PHY_CORTINA
phy_cortina_init();
#endif
#ifdef CONFIG_PHY_DAVICOM
phy_davicom_init();
#endif
#ifdef CONFIG_PHY_ET1011C
phy_et1011c_init();
#endif
#ifdef CONFIG_PHY_LXT
phy_lxt_init();
#endif
#ifdef CONFIG_PHY_MARVELL
phy_marvell_init();
#endif
#ifdef CONFIG_PHY_MICREL
phy_micrel_init();
#endif
#ifdef CONFIG_PHY_NATSEMI
phy_natsemi_init();
#endif
#ifdef CONFIG_PHY_REALTEK
phy_realtek_init();
#endif
#ifdef CONFIG_PHY_SMSC
phy_smsc_init();
#endif
#ifdef CONFIG_PHY_TERANETICS
phy_teranetics_init();
#endif
#ifdef CONFIG_PHY_VITESSE
phy_vitesse_init();
#endif
return 0;
}
这里网卡lan8710是smsc公司的,选择phy_smsc_init函数进行编译:
该函数实现如下:
/drivers/net/phy/smsc.c
#include <miiphy.h>
/* This code does not check the partner abilities. */
static int smsc_parse_status(struct phy_device *phydev)
{
int mii_reg;
mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
if (mii_reg & (BMSR_100FULL | BMSR_100HALF))
phydev->speed = SPEED_100;
else
phydev->speed = SPEED_10;
if (mii_reg & (BMSR_10FULL | BMSR_100FULL))
phydev->duplex = DUPLEX_FULL;
else
phydev->duplex = DUPLEX_HALF;
return 0;
}
static int smsc_startup(struct phy_device *phydev)
{
genphy_update_link(phydev);
smsc_parse_status(phydev);
return 0;
}
static struct phy_driver lan8700_driver = {
.name = "SMSC LAN8700",
.uid = 0x0007c0c0,
.mask = 0xffff0,
.features = PHY_BASIC_FEATURES,
.config = &genphy_config_aneg,
.startup = &smsc_startup,
.shutdown = &genphy_shutdown,
};
static struct phy_driver lan911x_driver = {
.name = "SMSC LAN911x Internal PHY",
.uid = 0x0007c0d0,
.mask = 0xffff0,
.features = PHY_BASIC_FEATURES,
.config = &genphy_config_aneg,
.startup = &smsc_startup,
.shutdown = &genphy_shutdown,
};
static struct phy_driver lan8710_driver = {
.name = "SMSC LAN8710/LAN8720",
.uid = 0x0007c0f0,
.mask = 0xffff0,
.features = PHY_BASIC_FEATURES,
.config = &genphy_config_aneg,
.startup = &genphy_startup,
.shutdown = &genphy_shutdown,
};
int phy_smsc_init(void)
{
phy_register(&lan8710_driver);
phy_register(&lan911x_driver);
phy_register(&lan8700_driver);
return 0;
}
可以看到对于lan8710a网卡进行了phy_register,也就是把网卡设备加入了链表。
接着回到eth_common_init函数,往下执行board_eth_init函数:
该函数定义在board.c文件中,包括了bbb板的具体的配置过程,非常重要
具体实现如下:
int board_eth_init(bd_t *bis)
{
int rv, n = 0;
uint8_t mac_addr[6];
uint32_t mac_hi, mac_lo;
__maybe_unused struct am335x_baseboard_id header;
/* try reading mac address from efuse */
mac_lo = readl(&cdev->macid0l);
mac_hi = readl(&cdev->macid0h);
mac_addr[0] = mac_hi & 0xFF;
mac_addr[1] = (mac_hi & 0xFF00) >> 8;
mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
mac_addr[4] = mac_lo & 0xFF;
mac_addr[5] = (mac_lo & 0xFF00) >> 8;
#if (defined(CONFIG_DRIVER_TI_CPSW) && !defined(CONFIG_SPL_BUILD)) || \
(defined(CONFIG_SPL_ETH_SUPPORT) && defined(CONFIG_SPL_BUILD))
if (!getenv("ethaddr")) {
printf("<ethaddr> not set. Validating first E-fuse MAC\n");
if (is_valid_ethaddr(mac_addr))
eth_setenv_enetaddr("ethaddr", mac_addr);
}
#ifdef CONFIG_DRIVER_TI_CPSW
mac_lo = readl(&cdev->macid1l);
mac_hi = readl(&cdev->macid1h);
mac_addr[0] = mac_hi & 0xFF;
mac_addr[1] = (mac_hi & 0xFF00) >> 8;
mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
mac_addr[4] = mac_lo & 0xFF;
mac_addr[5] = (mac_lo & 0xFF00) >> 8;
if (!getenv("eth1addr")) {
if (is_valid_ethaddr(mac_addr))
eth_setenv_enetaddr("eth1addr", mac_addr);
}
if (read_eeprom(&header) < 0)
puts("Could not get board ID.\n");
if (board_is_bone(&header) || board_is_bone_lt(&header) ||
board_is_idk(&header)) {
writel(MII_MODE_ENABLE, &cdev->miisel);
cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =
PHY_INTERFACE_MODE_MII;
} else {
writel((RGMII_MODE_ENABLE | RGMII_INT_DELAY), &cdev->miisel);
cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =
PHY_INTERFACE_MODE_RGMII;
}
rv = cpsw_register(&cpsw_data);
if (rv < 0)
printf("Error %d registering CPSW switch\n", rv);
else
n += rv;
#endif
/*
*
* CPSW RGMII Internal Delay Mode is not supported in all PVT
* operating points. So we must set the TX clock delay feature
* in the AR8051 PHY. Since we only support a single ethernet
* device in U-Boot, we only do this for the first instance.
*/
#define AR8051_PHY_DEBUG_ADDR_REG 0x1d
#define AR8051_PHY_DEBUG_DATA_REG 0x1e
#define AR8051_DEBUG_RGMII_CLK_DLY_REG 0x5
#define AR8051_RGMII_TX_CLK_DLY 0x100
if (board_is_evm_sk(&header) || board_is_gp_evm(&header)) {
const char *devname;
devname = miiphy_get_current_dev();
miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_ADDR_REG,
AR8051_DEBUG_RGMII_CLK_DLY_REG);
miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_DATA_REG,
AR8051_RGMII_TX_CLK_DLY);
}
#endif
#if defined(CONFIG_USB_ETHER) && \
(!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_USBETH_SUPPORT))
if (is_valid_ethaddr(mac_addr))
eth_setenv_enetaddr("usbnet_devaddr", mac_addr);
rv = usb_eth_initialize(bis);
if (rv < 0)
printf("Error %d registering USB_ETHER\n", rv);
else
n += rv;
#endif
return n;
}
分析该函数:
首先读取mac的地址,然后将其设置为环境变量,便于在命令行下查看
往下使能了MII模式,writel(MII_MODE_ENABLE, &cdev->miisel),调用这个系统io函数,将MII_MODE_ENABLE这个宏定义对应的值写入&cdev->miisel这个寄存器中,而寄存器的地址为CTRL_DEVICE_BASE+miisel,也就是CTRL_DEVICE_BASE这个基本地址加上miisel偏移量,CTRL_DEVICE_BASE这个宏的值是0x4A101000,定义在arch/arm/include/asm/arch-am33xx/hardware_am33xx.h文件中。
MII_MODE_ENABLE这个值的定义在cpu.h中:
#define MII_MODE_ENABLE (GMII1_SEL_MII | GMII2_SEL_MII)
#define GMII1_SEL_MII 0x0
#define GMII2_SEL_MII 0x0
使能后board_eth_init继续调用cpsw_register函数,在这个函数中,会进行与网卡mdio通信,查看网卡寄存器中的网卡ID。
具体函数在/drivers/net/phy/phy.c文件中:
int __weak get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id)
{
int phy_reg;
/* Grab the bits from PHYIR1, and put them
* in the upper half */
phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);
if (phy_reg < 0)
return -EIO;
*phy_id = (phy_reg & 0xffff) << 16;
/* Grab the bits from PHYIR2, and put them in the lower half */
phy_reg = bus->read(bus, addr, devad, MII_PHYSID2);
if (phy_reg < 0)
return -EIO;
*phy_id |= (phy_reg & 0xffff);
return 0;
}
该网卡存放ID的寄存器为PHYSID1和PHYSID2,因此就需要读取这两个寄存器。
实现读取寄存器的具体命令是:bus->read(bus, addr, devad, MII_PHYSID1)
这个总线读取函数其实就是CPSW_mdio_read函数,实现如下:
文件位于:/drivers/netcpsw.c
static int cpsw_mdio_read(struct mii_dev *bus, int phy_id,
int dev_addr, int phy_reg)
{
int data;
u32 reg;
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
wait_for_user_access();
reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |
(phy_id << 16));
__raw_writel(reg, &mdio_regs->user[0].access);
reg = wait_for_user_access();
data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;
return data;
}
在这个函数中首先调用wait_for_user_access函数:
static inline u32 wait_for_user_access(void)
{
u32 reg = 0;
int timeout = MDIO_TIMEOUT;
while (timeout-- &&
((reg = __raw_readl(&mdio_regs->user[0].access)) & USERACCESS_GO))
udelay(10);
if (timeout == -1) {
printf("wait_for_user_access Timeout\n");
return -ETIMEDOUT;
}
return reg;
}
该函数的作用就是不断读取网卡寄存器的某个值,判断是否能够进行access,如果可以返回非0值,否则返回负数。
然后回到上面的函数,调用__raw_writel函数对寄存器进行写值,值为reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) | (phy_id << 16)); 表示要进行通信和读取寄存器了,让网卡做好准备。
寄存器地址为&mdio_regs->user[0].access,写完后,继续调用reg = wait_for_user_access(); 读取寄存器的值。
然后将返回的reg值进行判断:data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;
如果reg & USERACCESS_ACK是正数,也就是网卡寄存器做出了回应,表示数据是对的,那么就reg & USERACCESS_DATA,将其赋值给data。否则data赋值为-1,表示没有数据读取到。
读取完寄存器后返回到get_phy_id函数,phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);
这个时候phy_reg的值就是读取的寄存器的值了
然后接着读下一个寄存器:phy_reg = bus->read(bus, addr, devad, MII_PHYSID2);
因为网卡id一个寄存器放不下,分为两个寄存器。两个寄存器都读到后,进行简单的运算就可得到完整的网卡ID了。
以上就是第一步的工作,有什么欠妥的地方请各位大神指正。以后基本每周更新一篇本周的进展。
posted on 2016-11-04 00:30 sichenzhao 阅读(627) 评论(0) 编辑 收藏 举报