我适配的设备使用的是 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
*
* 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
#include
#include
#include
#include
#include
#ifndef LINUX_VERSION_CODE
#include
#else
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#endif
/*for wol, 20210604*/
#include
#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);