全志D1开发(十二)网络驱动之网络硬件PHY初始化

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2022年5月3日20:24:19 评论 944

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);
    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);
    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,内核中提供的初始化流程基本都能满足,当然最终还是要根据实际情况做出选择。

gewenbin
  • 本文由 发表于 2022年5月3日20:24:19
  • 转载请务必保留本文链接:http://www.databusworld.cn/10717.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: