1. 网卡PHY操作集
和SD卡驱动讲解一样,我们不会对全志D1的MAC和PHY寄存器做深入的讲解,同样只关心通用的处理流程和一些必要的设置思想,具体的细节请查阅数据手册和源码。
SylixOS下对PHY的操作是通过 PHY_DRV_FUNC 这个数据结构来定义操作集的:
typedef struct phy_drv_func { FUNCPTR PHYF_pfuncRead; /* phy read function */ FUNCPTR PHYF_pfuncWrite; /* phy write function */ FUNCPTR PHYF_pfuncLinkDown; /* phy status down function */ FUNCPTR PHYF_pfuncLinkSetHook; /* mii phy link set hook func */ } PHY_DRV_FUNC;
- PHYF_pfuncRead:MDIO读PHY寄存器操作。
- PHYF_pfuncWrite:MDIO写PHY寄存器操作。
- PHYF_pfuncLinkDown:PHY状态改变操作,比如网络连接断开和连接上都会调用这个接口做处理。
- PHYF_pfuncLinkSetHook:在自协商之前的回调接口。
同样定义一个全局变量来表示PHY操作集,并使用对应的函数初始化:
static int emac_mdio_write(struct phy_dev *phy, u8 reg, u16 val) { struct emac_eth_dev *emac = &emac_dev; u32 mii_cmd; int addr = phy->PHY_ucPhyAddr; mii_cmd = (reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) & MDIO_CMD_MII_PHY_REG_ADDR_MASK; mii_cmd |= (addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) & MDIO_CMD_MII_PHY_ADDR_MASK; /* * The EMAC clock is either 200 or 300 MHz, so we need a divider * of 128 to get the MDIO frequency below the required 2.5 MHz. */ mii_cmd |= MDIO_CMD_MII_CLK_CSR_DIV_128 << MDIO_CMD_MII_CLK_CSR_SHIFT; mii_cmd |= MDIO_CMD_MII_WRITE; mii_cmd |= MDIO_CMD_MII_BUSY; writel(val, emac->mac_reg + EMAC_MII_DATA); writel(mii_cmd, emac->mac_reg + EMAC_MII_CMD); while(readl(emac->mac_reg + EMAC_MII_CMD) & MDIO_CMD_MII_BUSY); return 0; } static int emac_mdio_read(struct phy_dev *phy, u8 reg, u16 *val) { struct emac_eth_dev *emac = &emac_dev; u32 mii_cmd; int addr = phy->PHY_ucPhyAddr; mii_cmd = (reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) & MDIO_CMD_MII_PHY_REG_ADDR_MASK; mii_cmd |= (addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) & MDIO_CMD_MII_PHY_ADDR_MASK; /* * The EMAC clock is either 200 or 300 MHz, so we need a divider * of 128 to get the MDIO frequency below the required 2.5 MHz. */ mii_cmd |= MDIO_CMD_MII_CLK_CSR_DIV_128 << MDIO_CMD_MII_CLK_CSR_SHIFT; mii_cmd |= MDIO_CMD_MII_BUSY; writel(mii_cmd, emac->mac_reg + EMAC_MII_CMD); while(readl(emac->mac_reg + EMAC_MII_CMD) & MDIO_CMD_MII_BUSY); *val = readl(emac->mac_reg + EMAC_MII_DATA) & 0xffff; return 0; } static void sun8i_adjust_link(struct emac_eth_dev *emac, struct phy_dev *phydev) { u32 v; v = readl(emac->mac_reg + EMAC_CTL0); if (phydev->PHY_uiPhyAbilityFlags & MII_PHY_FD) v |= EMAC_CTL0_FULL_DUPLEX; else v &= ~EMAC_CTL0_FULL_DUPLEX; v &= ~EMAC_CTL0_SPEED_MASK; switch (phydev->PHY_uiPhySpeed) { case 1000 * 1000000: v |= EMAC_CTL0_SPEED_1000; break; case 100 * 1000000: v |= EMAC_CTL0_SPEED_100; break; case 10 * 1000000: v |= EMAC_CTL0_SPEED_10; break; } writel(v, emac->mac_reg + EMAC_CTL0); } static int emac_link_status(struct netdev *pnetdev) { struct emac_eth_dev *emac = &emac_dev; struct phy_dev *phy = &emac_phy; printk("PHY EMAC - Link is %s", (phy->PHY_usPhyStatus & MII_SR_LINK_STATUS) ? "UP" : "DOWN"); printk(" - %dM(%s)\r\n", phy->PHY_uiPhySpeed / (1000 * 1000), phy->PHY_pcPhyMode); if (phy->PHY_usPhyStatus & MII_SR_LINK_STATUS) { sun8i_adjust_link(emac, phy); netdev_set_linkup(pnetdev, 1, phy->PHY_uiPhySpeed); } else { netdev_set_linkup(pnetdev, 0, 0); } return 0; } static int emac_link_set(struct phy_dev *phydev) { u16 reg; emac_mdio_write(phydev, MIIM_RTL8211F_PAGE_SELECT, 0xd08); /* enable TX-delay for rgmii-id and rgmii-txid, otherwise disable it */ emac_mdio_read(phydev, 0x11, ®); reg |= MIIM_RTL8211F_TX_DELAY; emac_mdio_write(phydev, 0x11, reg); /* enable RX-delay for rgmii-id and rgmii-rxid, otherwise disable it */ emac_mdio_read(phydev, 0x15, ®); reg |= MIIM_RTL8211F_RX_DELAY; emac_mdio_write(phydev, 0x15, reg); /* restore to default page 0 */ emac_mdio_write(phydev, MIIM_RTL8211F_PAGE_SELECT, 0x0); /* Set green LED for Link, yellow LED for Active */ emac_mdio_write(phydev, MIIM_RTL8211F_PAGE_SELECT, 0xd04); emac_mdio_write(phydev, 0x10, 0x617f); emac_mdio_write(phydev, MIIM_RTL8211F_PAGE_SELECT, 0x0); return 0; } static struct phy_drv_func emac_phy_func = { .PHYF_pfuncWrite = (FUNCPTR)emac_mdio_write, .PHYF_pfuncRead = (FUNCPTR)emac_mdio_read, .PHYF_pfuncLinkDown = (FUNCPTR)emac_link_status, .PHYF_pfuncLinkSetHook = (FUNCPTR)emac_link_set, };
全志D1的网卡驱动硬件设置同样基本是参考的uboot驱动代码,读写PHY寄存器没啥好说的,就是操作控制器相应的寄存器设置好PHY地址和寄存器偏移然后读写数据就行。emac_link_status 中调用了 netdev_set_linkup 来通知内核网络连接状态和速度,原型如下:
int netdev_set_linkup(netdev_t *netdev, int linkup, UINT64 speed);
- netdev:网络设备。
- linkup:1表示连接上,0表示连接断开。
- speed:连接速度,一般为十兆/百兆/千兆中的一个。
在 emac_link_status 中同时还会根据当前状态打印网络连接状态信息,方便从串口上直观的看到。emac_link_set 函数中的实现是从uboot中拿过来的,最主要的就是使能发送和接收的delay操作,如果不设置会导致发送的数据异常。
2. 网卡PHY初始化
SylixOS中通过 PHY_DEV 数据结构来描述一个PHY设备:
typedef struct phy_dev { LW_LIST_LINE PHY_node; /* Device Header */ PHY_DRV_FUNC *PHY_pPhyDrvFunc; VOID *PHY_pvMacDrv; /* Mother Mac Driver Control */ UINT32 PHY_uiPhyFlags; /* PHY flag bits */ UINT32 PHY_uiPhyAbilityFlags; /* PHY flag bits */ UINT32 PHY_uiPhyANFlags; /* Auto Negotiation flags */ UINT32 PHY_uiPhyLinkMethod; /* Whether to force link mode */ UINT32 PHY_uiLinkDelay; /* Delay time to wait for Link */ UINT32 PHY_uiSpinDelay; /* Delay time of Spinning Reg */ UINT32 PHY_uiTryMax; /* Max Try times */ UINT16 PHY_usPhyStatus; /* Record Status of PHY */ UINT8 PHY_ucPhyAddr; /* Address of a PHY */ UINT32 PHY_uiPhyID; /* Phy ID */ UINT32 PHY_uiPhyIDMask; /* Phy ID MASK */ UINT32 PHY_uiPhySpeed; CHAR PHY_pcPhyMode[16]; /* Link Mode description */ } PHY_DEV;
一样有很多成员,介绍其中几个比较重要的:
- PHY_pPhyDrvFunc:PHY驱动操作集。
- PHY_pvMacDrv:PHY驱动私有数据,不能为空。
- PHY_ucPhyAddr:PHY地址,本次网络驱动中,查看原理图得知PHY地址为0x1。
- PHY_uiPhyID:PHY芯片ID,本次网络采用rtl8211f PHY,ID为0xc916001c。
- PHY_uiPhyIDMask:PHY芯片ID掩码,一般设置为0xffffffff。
- PHY_uiLinkDelay:PHY等待操作延时,单位为ms,一般10。
- PHY_uiTryMax:PHY操作超时重试次数,一般300。
- PHY_uiPhyFlags:PHY初始化属性,根据实际需求来设置。
PHY_uiPhyFlags常用的属性如下:
- MII_PHY_AUTO:使用自协商机制。
- MII_PHY_GMII_TYPE:PHY和MAC为GMII类接口。
- MII_PHY_1000T_HD:支持千兆半双工。
- MII_PHY_1000T_FD:支持千兆全双工。
- MII_PHY_100:支持百兆。
- MII_PHY_10:支持十兆。
- MII_PHY_FD:支持全双工。
- MII_PHY_HD:支持半双工。
- MII_PHY_MONITOR:使用PHY驱动内部定时器线程定时检测PHY状态。
初始化好PHY_DEV 数据结构后,就可以使用 API_MiiPhyInit 接口来初始化PHY驱动,其原型如下:
LW_API INT API_MiiPhyInit(PHY_DEV *pPhyDev);
只有一个参数,就是 PHY_DEV,我们同样定义一个全局变量表示PHY设备,本网卡驱动PHY初始化代码如下:
static struct phy_dev emac_phy; static int sun8i_mdio_init(struct netdev *pnetdev) { struct phy_dev *phy = &emac_phy; phy->PHY_pPhyDrvFunc = &emac_phy_func; phy->PHY_pvMacDrv = pnetdev; phy->PHY_ucPhyAddr = RTL8211F_PHY_ADDR; phy->PHY_uiPhyID = RTL8211F_PHY_ID; phy->PHY_uiPhyIDMask = 0xffffffff; phy->PHY_uiTryMax = 300; phy->PHY_uiLinkDelay = 10; phy->PHY_uiPhyFlags = MII_PHY_AUTO | MII_PHY_GMII_TYPE | MII_PHY_1000T_HD | MII_PHY_1000T_FD | MII_PHY_100 | MII_PHY_10 | MII_PHY_FD | MII_PHY_HD | MII_PHY_MONITOR; API_MiiPhyInit(phy); return 0; }
在实际的网卡驱动中,如果内核提供的标准MII PHY初始化流程不满足需求的话,可以自己直接在驱动中编写PHY初始化流程,而不一定非要用内核中的MII PHY框架,对于大部分PHY,内核中提供的初始化流程基本都能满足,当然最终还是要根据实际情况做出选择。
评论