一、环境介绍

我适配的设备使用的是 RJ45 网口,phy 和 soc 之间的接口为 RGMII;

linux 版本:4.1.15

soc:imx6q 

二、硬件检查

1. 检查电源;

VDDL = DVDDL = AVDDL = 1.1V;由 32 脚 LX 输出;
AVDD33 检查是否为 3.3V 供电;
DVDD_RGMII 电压是否符合下表,由 36、37 脚来选择电压;

2. 检查时钟;

检查晶振能否正常输出 25Mhz 的信号;

3. 检查接线;

MDI、RGMII、MDIO、MDC 等是否正确连接;

4. 检查 phy 地址;

28、17 脚没有外部上拉的情况下,默认使用的是内部下拉,即值为 0;

 三、软件调试

1. phy id

phy 匹配过程大致如下,可以在 get_phy_device 函数里面判断是否正确读取 phy id;

fec_probe
  fec_enet_mii_init
    of_mdiobus_register  Loop over the child nodes and register a phy_device for each one
	  of_mdiobus_register_phy
		get_phy_device ---get_phy_id(phy_device_create匹配phy_driver_register)   ---- phy_device_create
			              phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);

可以在 u-boot 处,进入命令行模式,用 mii info 命令来查看是否正确获取 phy id,如果没有获取,多半是 phy 硬件有问题;

正常情况下,在 u-boot 时候,可以获取两个 phy id,一个广播地址 00,一个 phy 地址;
对于 YT8531S 来说,操作广播地址与操作 phy 地址没差别;

硬件复位后,需要延时 100 ms,才能进行 MDIO 读写;

 在 drivers/net/ethernet/freescale/fec_main.c 的 fec_reset_phy 函数尾部加一个 100ms 延时;

static void fec_reset_phy(struct platform_device *pdev)
{
        int err, phy_reset;
        int msec = 1;
        struct device_node *np = pdev->dev.of_node;

        if (!np)
                return;

        err = of_property_read_u32(np, "phy-reset-duration", &msec);
        /* A sane reset duration should not be longer than 1s */
        if (!err && msec > 1000)
                msec = 1;

        phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);
        if (!gpio_is_valid(phy_reset))
                return;

        err = devm_gpio_request_one(&pdev->dev, phy_reset,
                                    GPIOF_OUT_INIT_LOW, "phy-reset");
        if (err) {
                dev_err(&pdev->dev, "failed to get phy-reset-gpios: %d\n", err);
                return;
        }
        msleep(msec);
        gpio_set_value(phy_reset, 1);
        msleep(100);
}

2. ENET_TX_CLK

对于 imx6q,使用 RGMII 的时候,还需要一个 125M 的时钟;
将 phy 的外部寄存器 0xA012 改为 0x50 即可;

3. dts 和 driver

fec 节点如下:

&fec {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_enet>;
	phy-mode = "rgmii";
	phy-reset-gpios = <&gpio1 25 GPIO_ACTIVE_LOW>;
	phy-reset-duration = <2>;
	fsl,magic-packet;
	status = "okay";
};

pinctrl_enet: enetgrp {
	fsl,pins = <
		MX6QDL_PAD_ENET_MDIO__ENET_MDIO		0x1b0b0
		MX6QDL_PAD_ENET_MDC__ENET_MDC		0x1b0b0
		MX6QDL_PAD_RGMII_TXC__RGMII_TXC		0x1b0b0
		MX6QDL_PAD_RGMII_TD0__RGMII_TD0		0x1b0b0
		MX6QDL_PAD_RGMII_TD1__RGMII_TD1		0x1b0b0
		MX6QDL_PAD_RGMII_TD2__RGMII_TD2		0x1b0b0
		MX6QDL_PAD_RGMII_TD3__RGMII_TD3		0x1b0b0
		MX6QDL_PAD_RGMII_TX_CTL__RGMII_TX_CTL	0x1b0b0
		MX6QDL_PAD_ENET_REF_CLK__ENET_TX_CLK	0x1b0b0
		MX6QDL_PAD_RGMII_RXC__RGMII_RXC		0x1b0b0
		MX6QDL_PAD_RGMII_RD0__RGMII_RD0		0x1b0b0
		MX6QDL_PAD_RGMII_RD1__RGMII_RD1		0x1b0b0
		MX6QDL_PAD_RGMII_RD2__RGMII_RD2		0x1b0b0
		MX6QDL_PAD_RGMII_RD3__RGMII_RD3		0x1b0b0
		MX6QDL_PAD_RGMII_RX_CTL__RGMII_RX_CTL	0x1b0b0

		/* Phy reset */
		MX6QDL_PAD_ENET_CRS_DV__GPIO1_IO25		0x000b0
	>;

 motorcomm.c 修改如下:

/*
 * drivers/net/phy/motorcomm.c
 *
 * Driver for Motorcomm PHYs
 *
 * Author: Leilei Zhao <leilei.zhao@motorcomm.com>
 *
 * Copyright (c) 2019 Motorcomm, Inc.
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 *
 * Support : Motorcomm Phys:
 *      Giga phys: yt8511, yt8521
 *      100/10 Phys : yt8512, yt8512b, yt8510
 *      Automotive 100Mb Phys : yt8010
 *      Automotive 100/10 hyper range Phys: yt8510
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/motorcomm_phy.h>
#include <linux/of.h>
#include <linux/clk.h>
#ifndef LINUX_VERSION_CODE
#include <linux/version.h>
#else
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#endif
/*for wol, 20210604*/
#include <linux/netdevice.h>

#include "yt8614-phy.h"

/**** configuration section begin ***********/

/* if system depends on ethernet packet to restore from sleep, please define this macro to 1
 * otherwise, define it to 0.
 */
#define SYS_WAKEUP_BASED_ON_ETH_PKT     0

/* to enable system WOL of phy, please define this macro to 1
 * otherwise, define it to 0.
 * NOTE: this macro will need macro SYS_WAKEUP_BASED_ON_ETH_PKT to set to 1
 */
#define YTPHY_ENABLE_WOL        0

/* some GMAC need clock input from PHY, for eg., 125M, please enable this macro
 * by degault, it is set to 0
 */
#define GMAC_CLOCK_INPUT_NEEDED 0


#define YT8521_PHY_MODE_FIBER   1 //fiber mode only
#define YT8521_PHY_MODE_UTP     2 //utp mode only
#define YT8521_PHY_MODE_POLL    3 //fiber and utp, poll mode

/* please make choice according to system design
 * for Fiber only system, please define YT8521_PHY_MODE_CURR 1
 * for UTP only system, please define YT8521_PHY_MODE_CURR 2
 * for combo system, please define YT8521_PHY_MODE_CURR 3 
 */
#define YT8521_PHY_MODE_CURR    2

/**** configuration section end ***********/


/* no need to change below */

#if (YTPHY_ENABLE_WOL)
#undef SYS_WAKEUP_BASED_ON_ETH_PKT
#define SYS_WAKEUP_BASED_ON_ETH_PKT     1
#endif

/* workaround for 8521 fiber 100m mode */
static int link_mode_8521 = 0; //0: no link; 1: utp; 32: fiber. traced that 1000m fiber uses 32.
static int link_mode_8614[4] = {0}; //0: no link; 1: utp; 32: fiber. traced that 1000m fiber uses 32.

/* for multiple port phy, base phy address */
static unsigned int yt_mport_base_phy_addr = 0xff; //0xff: invalid;
static unsigned int yt_mport_base_phy_addr_8614 = 0xff; //0xff: invalid;


static int ytphy_read_ext(struct phy_device *phydev, u32 regnum)
{
    int ret;
    int val;

    ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
    if (ret < 0)
        return ret;

    val = phy_read(phydev, REG_DEBUG_DATA);

    return val;
}

static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val)
{
    int ret;

    ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
    if (ret < 0)
        return ret;

    ret = phy_write(phydev, REG_DEBUG_DATA, val);

    return ret;
}


int yt8521_soft_reset(struct phy_device *phydev)
{
    int ret;
    int val;

    ytphy_write_ext(phydev, 0xa000, 0);
    ret = genphy_soft_reset(phydev);
    if (ret < 0)
        return ret;

    ytphy_write_ext(phydev, 0xa000, 2);
    ret = genphy_soft_reset(phydev);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    ret = ytphy_write_ext(phydev, 0xA012, 0x50);
    if (ret < 0)
        return ret;

    return 0;
}

#if (YTPHY_ENABLE_WOL)
static int ytphy_switch_reg_space(struct phy_device *phydev, int space)
{
    int ret;

    if (space == YTPHY_REG_SPACE_UTP) {
        ret = ytphy_write_ext(phydev, 0xa000, 0);
    } else {
        ret = ytphy_write_ext(phydev, 0xa000, 2);
    }

    return ret;
}

static int ytphy_wol_en_cfg(struct phy_device *phydev, ytphy_wol_cfg_t wol_cfg)
{
    int ret = 0;
    int val = 0;

    val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG);
    if (val < 0)
        return val;

    if (wol_cfg.enable) {
        val |= YTPHY_WOL_CFG_EN;

        if (wol_cfg.type == YTPHY_WOL_TYPE_LEVEL) {
            val &= ~YTPHY_WOL_CFG_TYPE;
            val &= ~YTPHY_WOL_CFG_INTR_SEL;
        } else if (wol_cfg.type == YTPHY_WOL_TYPE_PULSE) {
            val |= YTPHY_WOL_CFG_TYPE;
            val |= YTPHY_WOL_CFG_INTR_SEL;

            if (wol_cfg.width == YTPHY_WOL_WIDTH_84MS) {
                val &= ~YTPHY_WOL_CFG_WIDTH1;
                val &= ~YTPHY_WOL_CFG_WIDTH2;
            } else if (wol_cfg.width == YTPHY_WOL_WIDTH_168MS) {
                val |= YTPHY_WOL_CFG_WIDTH1;
                val &= ~YTPHY_WOL_CFG_WIDTH2;
            } else if (wol_cfg.width == YTPHY_WOL_WIDTH_336MS) {
                val &= ~YTPHY_WOL_CFG_WIDTH1;
                val |= YTPHY_WOL_CFG_WIDTH2;
            } else if (wol_cfg.width == YTPHY_WOL_WIDTH_672MS) {
                val |= YTPHY_WOL_CFG_WIDTH1;
                val |= YTPHY_WOL_CFG_WIDTH2;
            }
        }
    } else {
        val &= ~YTPHY_WOL_CFG_EN;
        val &= ~YTPHY_WOL_CFG_INTR_SEL;
    }

    ret = ytphy_write_ext(phydev, YTPHY_WOL_CFG_REG, val);
    if (ret < 0)
        return ret;

    return 0;
}

static void ytphy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
{
    int val = 0;

    wol->supported = WAKE_MAGIC;
    wol->wolopts = 0;

    val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG);
    if (val < 0)
        return;

    if (val & YTPHY_WOL_CFG_EN)
        wol->wolopts |= WAKE_MAGIC;

    return;
}

static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
{
    int ret, pre_page, val;
    ytphy_wol_cfg_t wol_cfg;
    struct net_device *p_attached_dev = phydev->attached_dev;

    memset(&wol_cfg, 0, sizeof(ytphy_wol_cfg_t));
    pre_page = ytphy_read_ext(phydev, 0xa000);
    if (pre_page < 0)
        return pre_page;

    /* Switch to phy UTP page */
    ret = ytphy_switch_reg_space(phydev, YTPHY_REG_SPACE_UTP);
    if (ret < 0)
        return ret;

    if (wol->wolopts & WAKE_MAGIC) {
        
        /* Enable the WOL interrupt */
        val = phy_read(phydev, YTPHY_UTP_INTR_REG);
        val |= YTPHY_WOL_INTR;
        ret = phy_write(phydev, YTPHY_UTP_INTR_REG, val);
        if (ret < 0)
            return ret;

        /* Set the WOL config */
        wol_cfg.enable = 1; //enable
        wol_cfg.type = YTPHY_WOL_TYPE_PULSE;
        wol_cfg.width = YTPHY_WOL_WIDTH_672MS;
        ret = ytphy_wol_en_cfg(phydev, wol_cfg);
        if (ret < 0)
            return ret;

        /* Store the device address for the magic packet */
        ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR2,
                ((p_attached_dev->dev_addr[0] << 8) |
                 p_attached_dev->dev_addr[1]));
        if (ret < 0)
            return ret;
        ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR1,
                ((p_attached_dev->dev_addr[2] << 8) |
                 p_attached_dev->dev_addr[3]));
        if (ret < 0)
            return ret;
        ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR0,
                ((p_attached_dev->dev_addr[4] << 8) |
                 p_attached_dev->dev_addr[5]));
        if (ret < 0)
            return ret;
    } else {
        wol_cfg.enable = 0; //disable
        wol_cfg.type = YTPHY_WOL_TYPE_MAX;
        wol_cfg.width = YTPHY_WOL_WIDTH_MAX;
        ret = ytphy_wol_en_cfg(phydev, wol_cfg);
        if (ret < 0)
            return ret;
    }

    /* Recover to previous register space page */
    ret = ytphy_switch_reg_space(phydev, pre_page);
    if (ret < 0)
        return ret;

    return 0;
}
#endif /*(YTPHY_ENABLE_WOL)*/

static int yt8521_config_init(struct phy_device *phydev)
{
    int ret;
    int val;

    phydev->irq = PHY_POLL;

    ytphy_write_ext(phydev, 0xa000, 0);
    ret = genphy_config_init(phydev);
    if (ret < 0)
        return ret;

    /* disable auto sleep */
    val = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1);
    if (val < 0)
        return val;

    val &= (~BIT(YT8521_EN_SLEEP_SW_BIT));

    ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, val);
    if (ret < 0)
        return ret;

    /* enable RXC clock when no wire plug */
    ret = ytphy_write_ext(phydev, 0xa000, 0);
    if (ret < 0)
        return ret;

    val = ytphy_read_ext(phydev, 0xc);
    if (val < 0)
        return val;
    val &= ~(1 << 12);
    ret = ytphy_write_ext(phydev, 0xc, val);
    if (ret < 0)
        return ret;

    //printk("8521 init call out...\n");
    return ret;
}

static int yt8521_adjust_status(struct phy_device *phydev, int val, int is_utp)
{
    int speed_mode, duplex;
    int speed = SPEED_UNKNOWN;

    //printk("8521 status adjust call in...\n");
    duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT;
    speed_mode = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT;
    switch (speed_mode) {
    case 0:
        if (is_utp)
            speed = SPEED_10;
        break;
    case 1:
        speed = SPEED_100;
        break;
    case 2:
        speed = SPEED_1000;
        break;
    case 3:
        break;
    default:
        speed = SPEED_UNKNOWN;

        break;
    }

    phydev->speed = speed;
    phydev->duplex = duplex;

    //printk("8521 status adjust call out,regval=0x%04x,mode=%s,speed=%dm...\n", val,is_utp?"utp":"fiber", phydev->speed);
    return 0;
}

int yt8521_aneg_done (struct phy_device *phydev)
{
    //printk("phy..YT8521 AN_done callin,speed=%dm,linkmoded=%d\n", phydev->speed,link_mode_8521);

    if ((32 == link_mode_8521) && (SPEED_100 == phydev->speed))
    {
        return 1/*link_mode_8521*/;
    }

    return genphy_aneg_done(phydev);
}

static int yt8521_read_status(struct phy_device *phydev)
{
    int ret;
    volatile int val;
    volatile int link;
    int link_utp = 0;

    /* reading UTP */
    ret = ytphy_write_ext(phydev, 0xa000, 0);
    if (ret < 0)
        return ret;

    val = phy_read(phydev, REG_PHY_SPEC_STATUS);
    if (val < 0)
        return val;

    link = val & (BIT(YT8521_LINK_STATUS_BIT));
    if (link) {
        link_utp = 1;
        link_mode_8521 = 1;
        yt8521_adjust_status(phydev, val, 1);
    } else {
        link_utp = 0;
    }

    if (link_utp) {
        phydev->link = 1;
    } else {
        phydev->link = 0;
        link_mode_8521 = 0;
    }

    if (link_utp) {
        ytphy_write_ext(phydev, 0xa000, 0);
    }

    //printk("8521 read status call out, link=%d, linkmode=%d\n", phydev->link, link_mode_8521 );

    return 0;
}

int yt8521_suspend(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)              
    int value;

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    mutex_lock(&phydev->lock);
#else
    /* no need lock in 4.19 */
#endif

    ytphy_write_ext(phydev, 0xa000, 0);
    value = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);

    ytphy_write_ext(phydev, 0xa000, 2);
    value = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);

    ytphy_write_ext(phydev, 0xa000, 0);

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    mutex_unlock(&phydev->lock);
#else
    /* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/               

    return 0;
}

int yt8521_resume(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)              
    int value;

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    mutex_lock(&phydev->lock);
#else
    /* no need lock/unlock in 4.19 */
#endif

    ytphy_write_ext(phydev, 0xa000, 0);
    value = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);

    ytphy_write_ext(phydev, 0xa000, 2);
    value = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);

    ytphy_write_ext(phydev, 0xa000, 0);

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
    mutex_unlock(&phydev->lock);
#else
    /* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/               

    return 0;
}

static struct phy_driver ytphy_drvs[] = {
    {
        /* same as 8521 */
        .phy_id         = PHY_ID_YT8531S,
        .name           = "YT8531S Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
        .flags          = PHY_POLL,
        .soft_reset     = yt8521_soft_reset,
        .config_aneg    = genphy_config_aneg,
        .aneg_done      = yt8521_aneg_done,
        .config_init    = yt8521_config_init,
        .read_status    = yt8521_read_status,
        .suspend        = yt8521_suspend,
        .resume         = yt8521_resume,
#if (YTPHY_ENABLE_WOL)
        .get_wol        = &ytphy_get_wol,
        .set_wol        = &ytphy_set_wol,
#endif                
    } 
};

module_phy_driver(ytphy_drvs);

MODULE_DESCRIPTION("Motorcomm PHY driver");
MODULE_AUTHOR("Leilei Zhao");
MODULE_LICENSE("GPL");

static struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
    { PHY_ID_YT8010, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8510, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8511, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8512, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8512B, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8521, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8531S, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8531, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8618, MOTORCOMM_MPHY_ID_MASK },
    { PHY_ID_YT8614, MOTORCOMM_MPHY_ID_MASK_8614 },
    { }
};

MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);

Logo

更多推荐