YT8531S PHY光电转换驱动

// SPDX-License-Identifier: GPL-2.0+
/*
 * drivers/net/phy/motorcomm.c
 *
 * Driver for Motorcomm PHYs
 *
 * Author: yinghong.zhang<yinghong.zhang@motor-comm.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, yt8531, yt8614, yt8618
 *        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/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 feature */
#include <linux/netdevice.h>

#define YT_LINUX_MAJOR             2
#define YT_LINUX_MINOR          2
#define YT_LINUX_SUBVERSION     21925
#define YT_LINUX_VERSIONID      "2.2.21925"

/********************************************
 **** 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     1

/* to enable system WOL feature of phy, please define this macro to 1
 * otherwise, define it to 0.
 */
#define YTPHY_WOL_FEATURE_ENABLE        0

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

/* the max number of yt8521 chip on pcb board
 * the most case is only 1 chip per board, but
 * by default, we support up to 8.
 */
#define YTPHY_BOARD_MAX_NUM_OF_CHIP_8521    8
#define YTPHY_BOARD_MAX_NUM_OF_CHIP_8614    4

/* for YT8531 package A xtal init config */
#define YTPHY8531A_XTAL_INIT                (0)

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

/* no need to change below */
#define MOTORCOMM_PHY_ID_MASK           0xffffffff

#define PHY_ID_YT8010                   0x00000309
#define PHY_ID_YT8010AS                 0x4f51eb19
#define PHY_ID_YT8011                   0x4f51eb01
#define PHY_ID_YT8510                   0x00000109
#define PHY_ID_YT8511                   0x0000010a
#define PHY_ID_YT8512                   0x00000128
#define PHY_ID_YT8522                   0x4f51e928
#define PHY_ID_YT8521                   0x0000011a
#define PHY_ID_YT8531S                  0x4f51e91a
#define PHY_ID_YT8531                   0x4f51e91b
#define PHY_ID_YT8614                   0x4f51e899
#define PHY_ID_YT8614Q                  0x4f51e8a9
#define PHY_ID_YT8618                   0x0000e889
#define PHY_ID_YT8821                   0x4f51ea19

#define REG_PHY_SPEC_STATUS             0x11
#define REG_DEBUG_ADDR_OFFSET           0x1e
#define REG_DEBUG_DATA                  0x1f
#define REG_MII_MMD_CTRL                  0x0D    /* MMD access control register */
#define REG_MII_MMD_DATA                  0x0E    /* MMD access data register    */

#define YT8512_EXTREG_LED0              0x40c0
#define YT8512_EXTREG_LED1              0x40c3

#define YT8512_EXTREG_SLEEP_CONTROL1    0x2027

#define YT_SOFTWARE_RESET               0x8000

#define YT8512_LED0_ACT_BLK_IND         0x1000
#define YT8512_LED0_DIS_LED_AN_TRY      0x0001
#define YT8512_LED0_BT_BLK_EN           0x0002
#define YT8512_LED0_HT_BLK_EN           0x0004
#define YT8512_LED0_COL_BLK_EN          0x0008
#define YT8512_LED0_BT_ON_EN            0x0010
#define YT8512_LED1_BT_ON_EN            0x0010
#define YT8512_LED1_TXACT_BLK_EN        0x0100
#define YT8512_LED1_RXACT_BLK_EN        0x0200
#define YT8512_SPEED_MODE               0xc000
#define YT8512_DUPLEX                   0x2000

#define YT8512_SPEED_MODE_BIT           14
#define YT8512_DUPLEX_BIT               13
#define YT8512_EN_SLEEP_SW_BIT          15

#define YT8522_TX_CLK_DELAY              0x4210
#define YT8522_ANAGLOG_IF_CTRL           0x4008
#define YT8522_DAC_CTRL                  0x2057
#define YT8522_INTERPOLATOR_FILTER_1     0x14
#define YT8522_INTERPOLATOR_FILTER_2     0x15
#define YT8522_EXTENDED_COMBO_CTRL_1     0x4000

#define YT8521_EXTREG_SLEEP_CONTROL1    0x27
#define YT8521_EN_SLEEP_SW_BIT          15

#define YTXXXX_SPEED_MODE               0xc000
#define YTXXXX_DUPLEX                   0x2000
#define YTXXXX_SPEED_MODE_BIT           14
#define YTXXXX_DUPLEX_BIT               13
#define YTXXXX_LINK_STATUS_BIT          10

/* based on yt8521 wol feature config register */
#define YTPHY_UTP_INTR_REG              0x12
/* WOL Feature Event Interrupt Enable */
#define YTPHY_WOL_FEATURE_INTR          BIT(6)

/* Magic Packet MAC address registers */
#define YTPHY_WOL_FEATURE_MACADDR2_4_MAGIC_PACKET    0xa007
#define YTPHY_WOL_FEATURE_MACADDR1_4_MAGIC_PACKET    0xa008
#define YTPHY_WOL_FEATURE_MACADDR0_4_MAGIC_PACKET    0xa009

#define YTPHY_WOL_FEATURE_REG_CFG               0xa00a
#define YTPHY_WOL_FEATURE_TYPE_CFG              BIT(0)    /* WOL TYPE Config */
#define YTPHY_WOL_FEATURE_ENABLE_CFG            BIT(3)    /* WOL Enable Config */
#define YTPHY_WOL_FEATURE_INTR_SEL_CFG          BIT(6)    /* WOL Event Interrupt Enable Config */
#define YTPHY_WOL_FEATURE_WIDTH1_CFG            BIT(1)    /* WOL Pulse Width Config */
#define YTPHY_WOL_FEATURE_WIDTH2_CFG            BIT(2)    /* WOL Pulse Width Config */

#define YTPHY_REG_SPACE_UTP             0
#define YTPHY_REG_SPACE_FIBER           2

enum ytphy_wol_feature_trigger_type_e {
    YTPHY_WOL_FEATURE_PULSE_TRIGGER,
    YTPHY_WOL_FEATURE_LEVEL_TRIGGER,
    YTPHY_WOL_FEATURE_TRIGGER_TYPE_MAX
};

enum ytphy_wol_feature_pulse_width_e {
    YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH,
    YTPHY_WOL_FEATURE_336MS_PULSE_WIDTH,
    YTPHY_WOL_FEATURE_168MS_PULSE_WIDTH,
    YTPHY_WOL_FEATURE_84MS_PULSE_WIDTH,
    YTPHY_WOL_FEATURE_PULSE_WIDTH_MAX
};

struct ytphy_wol_feature_cfg {
    bool enable;
    int type;
    int width;
};

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

struct yt8xxx_priv {
    u8 polling_mode;
    u8 chip_mode; 
};

/* polling mode */
#define YT_PHY_MODE_FIBER           1 //fiber mode only
#define YT_PHY_MODE_UTP             2 //utp mode only
#define YT_PHY_MODE_POLL            (YT_PHY_MODE_FIBER | YT_PHY_MODE_UTP)

/* support automatically check polling mode for yt8521
 * 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 YTPHY_861X_ABC_VER              0
#if (YTPHY_861X_ABC_VER)
static int yt8614_get_port_from_phydev(struct phy_device *phydev);
#endif

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
static int ytphy_config_init(struct phy_device *phydev)
{
    int val;

    val = phy_read(phydev, 3);

    return 0;
}
#endif


#if (KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE)
static inline void phy_lock_mdio_bus(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    mutex_lock(&phydev->bus->mdio_lock);
#else
    mutex_lock(&phydev->mdio.bus->mdio_lock);
#endif
}

static inline void phy_unlock_mdio_bus(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    mutex_unlock(&phydev->bus->mdio_lock);
#else
    mutex_unlock(&phydev->mdio.bus->mdio_lock);
#endif
}
#endif

#if (KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE)
static inline int __phy_read(struct phy_device *phydev, u32 regnum)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct mii_bus *bus = phydev->bus;
    int addr = phydev->addr;
    return bus->read(bus, phydev->addr, regnum);
#else
    struct mii_bus *bus = phydev->mdio.bus;
    int addr = phydev->mdio.addr;
#endif
    return bus->read(bus, addr, regnum);
}

static inline int __phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct mii_bus *bus = phydev->bus;
    int addr = phydev->addr;
#else
    struct mii_bus *bus = phydev->mdio.bus;
    int addr = phydev->mdio.addr;
#endif
    return bus->write(bus, addr, regnum, val);
}
#endif

static int ytphy_read_ext(struct phy_device *phydev, u32 regnum)
{
    int ret;
    
    phy_lock_mdio_bus(phydev);
    ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
    if (ret < 0)
        goto err_handle;

    ret = __phy_read(phydev, REG_DEBUG_DATA);
    if (ret < 0)
        goto err_handle;
    
err_handle:
    phy_unlock_mdio_bus(phydev);
    return ret;
}

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

    phy_lock_mdio_bus(phydev);
    ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
    if (ret < 0)
        goto err_handle;

    ret = __phy_write(phydev, REG_DEBUG_DATA, val);
    if (ret < 0)
        goto err_handle;
    
err_handle:
    phy_unlock_mdio_bus(phydev);
    return ret;
}

__attribute__((unused)) static int ytphy_read_mmd(struct phy_device* phydev, u16 device, u16 reg)
{
    int val;

    phy_lock_mdio_bus(phydev);

    __phy_write(phydev, REG_MII_MMD_CTRL, device);
    __phy_write(phydev, REG_MII_MMD_DATA, reg);
    __phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
    val = __phy_read(phydev, REG_MII_MMD_DATA);
    if (val < 0) {
        dev_err(&phydev->mdio.dev, "error read mmd device(%u) reg (%u)\n", device, reg);
        goto err_handle;
    }

err_handle:
    phy_unlock_mdio_bus(phydev);

    return val;
}

__attribute__((unused)) static int ytphy_write_mmd(struct phy_device* phydev, u16 device, u16 reg, u16 value)
{
    int ret = 0;

    phy_lock_mdio_bus(phydev);

    __phy_write(phydev, REG_MII_MMD_CTRL, device);
    __phy_write(phydev, REG_MII_MMD_DATA, reg);
    __phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
    __phy_write(phydev, REG_MII_MMD_DATA, value);

    phy_unlock_mdio_bus(phydev);
    
    return ret;
}


static int ytphy_soft_reset(struct phy_device *phydev)
{
    int ret = 0, val = 0;

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

    ret = phy_write(phydev, MII_BMCR, val | BMCR_RESET);
    if (ret < 0)
        return ret;

    return ret;
}


#if (YTPHY8531A_XTAL_INIT)
static int yt8531a_xtal_init(struct phy_device *phydev)
{
    int ret = 0;
    int val = 0;
    bool state = false;

    msleep(50);

    do {
        ret = ytphy_write_ext(phydev, 0xa012, 0x88);
        if (ret < 0)
            return ret;

        msleep(100);

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

        usleep_range(10000, 20000);
    } while (val != 0x88);

    ret = ytphy_write_ext(phydev, 0xa012, 0xc8);
    if (ret < 0)
        return ret;

    return ret;
}
#endif

#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8010_soft_reset(struct phy_device *phydev)
{
    ytphy_soft_reset(phydev);

    return 0;
}

static int yt8010AS_soft_reset(struct phy_device *phydev)
{
    int ret = 0;

    /* sgmii */
    ytphy_write_ext(phydev, 0xe, 1);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xe, 0);
        return ret;
    }

    /* utp */
    ytphy_write_ext(phydev, 0xe, 0);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0)
        return ret;

    return 0;
}
#endif

#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
static int yt8010_aneg_done(struct phy_device *phydev)
{
    int val = 0;

    val = phy_read(phydev, 0x1);
    val = phy_read(phydev, 0x1);

    return (val < 0) ? val : (val & BMSR_LSTATUS);
}
#endif

static int yt8010_config_init(struct phy_device *phydev)
{
    int val;
    
    phydev->autoneg = AUTONEG_DISABLE;

    ytphy_write_ext(phydev, 0x1023, 0xfc00);
    ytphy_write_ext(phydev, 0x101d, 0x12c0);
    val = ytphy_read_ext(phydev, 0x1000);
    val &= ~(1 << 7);
    ytphy_write_ext(phydev, 0x1000, val);
    ytphy_write_ext(phydev, 0x101d, 0x12c0);
    ytphy_write_ext(phydev, 0x101e, 0x1900);
    ytphy_write_ext(phydev, 0x101f, 0x1900);
    ytphy_write_ext(phydev, 0x4083, 0x4327);
    ytphy_write_ext(phydev, 0x4082, 0xc20);
    ytphy_soft_reset(phydev);
        
    return 0;
}

static int yt8010_config_aneg(struct phy_device *phydev)
{
    phydev->speed = SPEED_100;
    return 0;
}

static int yt8010_read_status(struct phy_device *phydev)
{
    int ret = 0;

    ret = genphy_update_link(phydev);
    if (ret)
        return ret;

    /* for 8010, no definition mii reg 0x04, 0x11, here force 100/full */
    phydev->speed = SPEED_100;
    phydev->duplex = DUPLEX_FULL;

    return 0;
}

static int yt8010AS_config_init(struct phy_device *phydev)
{
    phydev->autoneg = AUTONEG_DISABLE;

    ytphy_write_ext(phydev, 0x1009, 0x0);

    return 0;
}

static int yt8011_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct device *dev = &phydev->dev;
#else
    struct device *dev = &phydev->mdio.dev;
#endif
    struct yt8xxx_priv *priv;
    int chip_config;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    phydev->priv = priv;

    /* ext reg 0x9030 bit0 
     * 0 = chip works in RGMII mode; 1 = chip works in SGMII mode 
     */
    chip_config = ytphy_read_ext(phydev, 0x9030);
    priv->chip_mode = chip_config & 0x1;
    
    return 0;
}

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
#else
static int yt8011_soft_reset(struct phy_device *phydev)
{
    struct yt8xxx_priv *priv = phydev->priv;

    /* utp */
    ytphy_write_ext(phydev, 0x9000, 0x0);
    ytphy_soft_reset(phydev);

    if (priv->chip_mode)    /* sgmii */
    {
        ytphy_write_ext(phydev, 0x9000, 0x8000);
        ytphy_soft_reset(phydev);
        
        /* restore utp space */ 
        ytphy_write_ext(phydev, 0x9000, 0x0);
    }

    return 0;
}
#endif

static int yt8011_config_aneg(struct phy_device *phydev)
{
    phydev->speed = SPEED_1000;
    
    return 0;
}

#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
static int yt8011_aneg_done(struct phy_device *phydev)
{
    int link_utp = 0;

    /* UTP */
    ytphy_write_ext(phydev, 0x9000, 0);
    link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
    
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n", __func__, phydev->addr, link_utp);
#else
    netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n", __func__, phydev->mdio.addr, link_utp);
#endif

    return !!(link_utp);
}
#endif

/* #define YT8011_RGMII_DVDDIO_3V3 */
/* #define YT8011_RGMII_DVDDIO_2V5 */
/* #define YT8011_RGMII_DVDDIO_1V8 */

static int yt8011_config_init(struct phy_device *phydev)
{
    struct yt8xxx_priv *priv = phydev->priv;
    
    phydev->autoneg = AUTONEG_DISABLE;

    /* UTP */
    ytphy_write_ext(phydev, 0x9000, 0x0);
    
    ytphy_write_ext(phydev, 0x1008, 0x2119);
    ytphy_write_ext(phydev, 0x1092, 0x712);
    ytphy_write_ext(phydev, 0x90bc, 0x6661);
    ytphy_write_ext(phydev, 0x90b9, 0x620b);
    ytphy_write_ext(phydev, 0x2001, 0x6418);
    ytphy_write_ext(phydev, 0x1019, 0x3712);
    ytphy_write_ext(phydev, 0x101a, 0x3713);
    ytphy_write_ext(phydev, 0x2015, 0x1012);
    ytphy_write_ext(phydev, 0x2005, 0x810);    
    ytphy_write_ext(phydev, 0x2013, 0xff06);
    ytphy_write_ext(phydev, 0x1000, 0x0028);
    ytphy_write_ext(phydev, 0x1002, 0x3800);
    ytphy_write_ext(phydev, 0x1090, 0x1012);
    ytphy_write_ext(phydev, 0x1091, 0x1013);
    ytphy_write_ext(phydev, 0x1100, 0x2020);
    ytphy_write_mmd(phydev, 0x7, 0x1f42, 0x0016);
    ytphy_write_ext(phydev, 0x1088, 0x002b);
    ytphy_write_ext(phydev, 0x1088, 0x022b);
    ytphy_write_ext(phydev, 0x1088, 0x020b);
    ytphy_write_ext(phydev, 0x3008, 0x143);
    ytphy_write_ext(phydev, 0x3009, 0x1918);
    
    if (!(priv->chip_mode))    /* rgmii config */
    {
#if defined (YT8011_RGMII_DVDDIO_3V3)
        ytphy_write_ext(phydev, 0x9000, 0x8000);
        ytphy_write_ext(phydev, 0x0062, 0x0000);
        ytphy_write_ext(phydev, 0x9000, 0x0000);
        ytphy_write_ext(phydev, 0x9031, 0xb200);
        ytphy_write_ext(phydev, 0x903b, 0x0040);
        ytphy_write_ext(phydev, 0x903e, 0x3b3b);
        ytphy_write_ext(phydev, 0x903c, 0xf);
        ytphy_write_ext(phydev, 0x903d, 0x1000);
        ytphy_write_ext(phydev, 0x9038, 0x0000);
#elif defined (YT8011_RGMII_DVDDIO_2V5)
        ytphy_write_ext(phydev, 0x9000, 0x8000);
        ytphy_write_ext(phydev, 0x0062, 0x0000);
        ytphy_write_ext(phydev, 0x9000, 0x0000);
        ytphy_write_ext(phydev, 0x9031, 0xb200);
        ytphy_write_ext(phydev, 0x9111, 0x5);
        ytphy_write_ext(phydev, 0x9114, 0x3939);
        ytphy_write_ext(phydev, 0x9112, 0xf);
        ytphy_write_ext(phydev, 0x9110, 0x0);
        ytphy_write_ext(phydev, 0x9113, 0x10);
        ytphy_write_ext(phydev, 0x903d, 0x2);
#elif defined (YT8011_RGMII_DVDDIO_1V8)
        ytphy_write_ext(phydev, 0x9000, 0x8000);
        ytphy_write_ext(phydev, 0x0062, 0x0000);
        ytphy_write_ext(phydev, 0x9000, 0x0000);
        ytphy_write_ext(phydev, 0x9031, 0xb200);
        ytphy_write_ext(phydev, 0x9116, 0x6);
        ytphy_write_ext(phydev, 0x9119, 0x3939);
        ytphy_write_ext(phydev, 0x9117, 0xf);
        ytphy_write_ext(phydev, 0x9115, 0x0);
        ytphy_write_ext(phydev, 0x9118, 0x20);
        ytphy_write_ext(phydev, 0x903d, 0x3);
#endif
    }
    
    ytphy_soft_reset(phydev);
    
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif

    return 0;
}

static int ytxxxx_automotive_adjust_status(struct phy_device *phydev, int val)
{
    int speed_mode;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    int speed = -1;
#else
    int speed = SPEED_UNKNOWN;
#endif

    speed_mode = (val & YTXXXX_SPEED_MODE) >> YTXXXX_SPEED_MODE_BIT;
    switch (speed_mode) {
    case 1:
        speed = SPEED_100;
        break;
    case 2:
        speed = SPEED_1000;
        break;
    default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
        speed = -1;
#else
        speed = SPEED_UNKNOWN;
#endif
        break;
    }

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

    return 0;
}

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

    /* UTP */
    ret = ytphy_write_ext(phydev, 0x9000, 0x0);
    if (ret < 0)
        return ret;
    
    val = phy_read(phydev, REG_PHY_SPEC_STATUS);
    if (val < 0)
        return val;
    
    link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
    if (link) {
        link_utp = 1;
        ytxxxx_automotive_adjust_status(phydev, val);
    } else {
        link_utp = 0;
    }

    if (link_utp) {
        if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
                __func__, phydev->addr, (unsigned int)val);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
                __func__, phydev->mdio.addr, (unsigned int)val);
#endif
        phydev->link = 1;
    } else {
        if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif

        phydev->link = 0;
    }

    if (link_utp)
        ytphy_write_ext(phydev, 0x9000, 0x0);

    return 0;
}

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

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

    val |= YT8512_LED0_ACT_BLK_IND;

    mask = YT8512_LED0_DIS_LED_AN_TRY | YT8512_LED0_BT_BLK_EN |
    YT8512_LED0_HT_BLK_EN | YT8512_LED0_COL_BLK_EN |
    YT8512_LED0_BT_ON_EN;
    val &= ~mask;

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

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

    val |= YT8512_LED1_BT_ON_EN;

    mask = YT8512_LED1_TXACT_BLK_EN | YT8512_LED1_RXACT_BLK_EN;
    val &= ~mask;

    ret = ytphy_write_ext(phydev, YT8512_EXTREG_LED1, val);

    return ret;
}

static int yt8512_config_init(struct phy_device *phydev)
{
    int ret;
    int val;
    
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
    ret = ytphy_config_init(phydev);
#else
    ret = genphy_config_init(phydev);
#endif
    if (ret < 0)
        return ret;

    ret = yt8512_led_init(phydev);

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

    val &= (~BIT(YT8512_EN_SLEEP_SW_BIT));

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

    return ret;
}

static int yt8512_read_status(struct phy_device *phydev)
{
    int ret;
    int val;
    int speed, speed_mode, duplex;

    ret = genphy_update_link(phydev);
    if (ret)
        return ret;

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

    duplex = (val & YT8512_DUPLEX) >> YT8512_DUPLEX_BIT;
    speed_mode = (val & YT8512_SPEED_MODE) >> YT8512_SPEED_MODE_BIT;
    switch (speed_mode) {
    case 0:
        speed = SPEED_10;
        break;
    case 1:
        speed = SPEED_100;
        break;
    case 2:
    case 3:
    default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
        speed = -1;
#else
        speed = SPEED_UNKNOWN;
#endif
        break;
    }

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

    return 0;
}

static int yt8522_read_status(struct phy_device *phydev)
{
    int val;
    int speed, speed_mode, duplex;

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

    if ((val & BIT(10)) >> YTXXXX_LINK_STATUS_BIT) {    /* link up */
        duplex = (val & BIT(13)) >> YTXXXX_DUPLEX_BIT;
        speed_mode = (val & (BIT(15) | BIT(14))) >> YTXXXX_SPEED_MODE_BIT;
        switch (speed_mode) {
        case 0:
            speed = SPEED_10;
            break;
        case 1:
            speed = SPEED_100;
            break;
        case 2:
        case 3:
        default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
            speed = -1;
#else
            speed = SPEED_UNKNOWN;
#endif
            break;
        }

        phydev->link = 1;
        phydev->speed = speed;
        phydev->duplex = duplex;

        return 0;
    }

    phydev->link = 0;
    
    return 0;
}

static int yt8522_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct device *dev = &phydev->dev;
#else
    struct device *dev = &phydev->mdio.dev;
#endif
    struct yt8xxx_priv *priv;
    int chip_config;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    phydev->priv = priv;

    chip_config = ytphy_read_ext(phydev, YT8522_EXTENDED_COMBO_CTRL_1);

    priv->chip_mode = ((chip_config & BIT(3)) >> 3);
    
    return 0;
}

static int yt8522_config_init(struct phy_device *phydev)
{
    int ret;
    int val;
    
    struct yt8xxx_priv *priv = phydev->priv;
    
    if (!priv->chip_mode) {    /* UTP */
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
        ret = ytphy_config_init(phydev);
#else
        ret = genphy_config_init(phydev);
#endif
        if (ret < 0)
            return ret;

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

        val = ytphy_write_ext(phydev, YT8522_ANAGLOG_IF_CTRL, 0xbf2a);
        if (val < 0)
            return val;

        val = ytphy_write_ext(phydev, YT8522_DAC_CTRL, 0x297f);
        if (val < 0)
            return val;

        val = ytphy_write_ext(phydev, YT8522_INTERPOLATOR_FILTER_1, 0x1FE);
        if (val < 0)
            return val;

        val = ytphy_write_ext(phydev, YT8522_INTERPOLATOR_FILTER_2, 0x1FE);
        if (val < 0)
            return val;

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

        val &= (~BIT(YT8512_EN_SLEEP_SW_BIT));

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

    return 0;
}

static int yt8521_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct device *dev = &phydev->dev;
#else
    struct device *dev = &phydev->mdio.dev;
#endif
    struct yt8xxx_priv *priv;
    int chip_config;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    phydev->priv = priv;

    chip_config = ytphy_read_ext(phydev, 0xa001);

    priv->chip_mode = chip_config & 0x7;
    switch (priv->chip_mode) {
    case 1:    //fiber<>rgmii
    case 4:
    case 5:
        priv->polling_mode = YT_PHY_MODE_FIBER;
        break;
    case 2:    //utp/fiber<>rgmii
    case 6:
    case 7:
        priv->polling_mode = YT_PHY_MODE_POLL;
        break;
    case 3:    //utp<>sgmii
    case 0:    //utp<>rgmii
    default:
        priv->polling_mode = YT_PHY_MODE_UTP;
        break;
    }
    
    return 0;
}

#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8521_soft_reset(struct phy_device *phydev)
{
    int val;
    struct yt8xxx_priv *priv = phydev->priv;

    if (priv->polling_mode == YT_PHY_MODE_UTP) {
        ytphy_write_ext(phydev, 0xa000, 0);
        ytphy_soft_reset(phydev);
    }

    if (priv->polling_mode == YT_PHY_MODE_FIBER) {
        ytphy_write_ext(phydev, 0xa000, 2);
        ytphy_soft_reset(phydev);

        ytphy_write_ext(phydev, 0xa000, 0);
    }

    if (priv->polling_mode == YT_PHY_MODE_POLL) {
        val = ytphy_read_ext(phydev, 0xa001);
        ytphy_write_ext(phydev, 0xa001, (val & ~0x8000));

        ytphy_write_ext(phydev, 0xa000, 0);
        ytphy_soft_reset(phydev);
    }

    return 0;
}
#endif

#if GMAC_CLOCK_INPUT_NEEDED
static int ytphy_mii_rd_ext(struct mii_bus *bus, int phy_id, u32 regnum)
{
    int ret;
    int val;

    ret = bus->write(bus, phy_id, REG_DEBUG_ADDR_OFFSET, regnum);
    if (ret < 0)
        return ret;

    val = bus->read(bus, phy_id, REG_DEBUG_DATA);

    return val;
}

static int ytphy_mii_wr_ext(struct mii_bus *bus
                int phy_id,
                u32 regnum,
                u16 val)
{
    int ret;

    ret = bus->write(bus, phy_id, REG_DEBUG_ADDR_OFFSET, regnum);
    if (ret < 0)
        return ret;

    ret = bus->write(bus, phy_id, REG_DEBUG_DATA, val);

    return ret;
}

static int yt8511_config_dis_txdelay(struct mii_bus *bus, int phy_id)
{
    int ret;
    int val;

    /* disable auto sleep */
    val = ytphy_mii_rd_ext(bus, phy_id, 0x27);
    if (val < 0)
        return val;

    val &= (~BIT(15));

    ret = ytphy_mii_wr_ext(bus, phy_id, 0x27, val);
    if (ret < 0)
        return ret;

    /* enable RXC clock when no wire plug */
    val = ytphy_mii_rd_ext(bus, phy_id, 0xc);
    if (val < 0)
        return val;

    /* ext reg 0xc b[7:4]
     * Tx Delay time = 150ps * N - 250ps
     */
    val &= ~(0xf << 4);
    ret = ytphy_mii_wr_ext(bus, phy_id, 0xc, val);

    return ret;
}

static int yt8511_config_out_125m(struct mii_bus *bus, int phy_id)
{
    int ret;
    int val;

    /* disable auto sleep */
    val = ytphy_mii_rd_ext(bus, phy_id, 0x27);
    if (val < 0)
        return val;

    val &= (~BIT(15));

    ret = ytphy_mii_wr_ext(bus, phy_id, 0x27, val);
    if (ret < 0)
        return ret;

    /* enable RXC clock when no wire plug */
    val = ytphy_mii_rd_ext(bus, phy_id, 0xc);
    if (val < 0)
        return val;

    /* ext reg 0xc.b[2:1]
     * 00-----25M from pll;
     * 01---- 25M from xtl;(default)
     * 10-----62.5M from pll;
     * 11----125M from pll(here set to this value)
     */
    val |= (3 << 1);
    ret = ytphy_mii_wr_ext(bus, phy_id, 0xc, val);

#ifdef YT_8511_INIT_TO_MASTER
    /* for customer, please enable it based on demand.
     * configure to master
     */

    /* master/slave config reg*/
    val = bus->read(bus, phy_id, 0x9);
    /* to be manual config and force to be master */
    val |= (0x3<<11);
    /* take effect until phy soft reset */
    ret = bus->write(bus, phy_id, 0x9, val);
    if (ret < 0)
        return ret;
#endif

    return ret;
}

static int yt8511_config_init(struct phy_device *phydev)
{
    int ret;

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
    ret = ytphy_config_init(phydev);
#else
    ret = genphy_config_init(phydev);
#endif

#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif

    return ret;
}
#endif /* GMAC_CLOCK_INPUT_NEEDED */

#if (YTPHY_WOL_FEATURE_ENABLE)
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_feature_enable_cfg(struct phy_device *phydev,
            struct ytphy_wol_feature_cfg wol_cfg)
{
    int ret = 0;
    int val = 0;

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

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

    if (wol_cfg.type == YTPHY_WOL_FEATURE_LEVEL_TRIGGER) {
        val &= ~YTPHY_WOL_FEATURE_TYPE_CFG;
        val &= ~YTPHY_WOL_FEATURE_INTR_SEL_CFG;
    } else if (wol_cfg.type == YTPHY_WOL_FEATURE_PULSE_TRIGGER) {
        val |= YTPHY_WOL_FEATURE_TYPE_CFG;
        val |= YTPHY_WOL_FEATURE_INTR_SEL_CFG;

        if (wol_cfg.width == YTPHY_WOL_FEATURE_84MS_PULSE_WIDTH) {
            val &= ~YTPHY_WOL_FEATURE_WIDTH1_CFG;
            val &= ~YTPHY_WOL_FEATURE_WIDTH2_CFG;
        } else if (wol_cfg.width == YTPHY_WOL_FEATURE_168MS_PULSE_WIDTH) {
            val |= YTPHY_WOL_FEATURE_WIDTH1_CFG;
            val &= ~YTPHY_WOL_FEATURE_WIDTH2_CFG;
        } else if (wol_cfg.width == YTPHY_WOL_FEATURE_336MS_PULSE_WIDTH) {
            val &= ~YTPHY_WOL_FEATURE_WIDTH1_CFG;
            val |= YTPHY_WOL_FEATURE_WIDTH2_CFG;
        } else if (wol_cfg.width == YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH) {
            val |= YTPHY_WOL_FEATURE_WIDTH1_CFG;
            val |= YTPHY_WOL_FEATURE_WIDTH2_CFG;
        }
    }
    } else {
        val &= ~YTPHY_WOL_FEATURE_ENABLE_CFG;
        val &= ~YTPHY_WOL_FEATURE_INTR_SEL_CFG;
    }

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

    return 0;
}

static void ytphy_wol_feature_get(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_FEATURE_REG_CFG);
    if (val < 0)
        return;

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

    //return;
}

static int ytphy_wol_feature_set(struct phy_device *phydev,
                struct ethtool_wolinfo *wol)
{
    int ret, curr_reg_space, val;
    struct ytphy_wol_feature_cfg wol_cfg;
    struct net_device *p_attached_dev = phydev->attached_dev;

    memset(&wol_cfg, 0, sizeof(struct ytphy_wol_feature_cfg));
    curr_reg_space = ytphy_read_ext(phydev, 0xa000);
    if (curr_reg_space < 0)
        return curr_reg_space;

    /* 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 feature interrupt */
        val = phy_read(phydev, YTPHY_UTP_INTR_REG);
        val |= YTPHY_WOL_FEATURE_INTR;
        ret = phy_write(phydev, YTPHY_UTP_INTR_REG, val);
        if (ret < 0)
            return ret;

        /* Set the WOL feature config */
        wol_cfg.enable = true;
        wol_cfg.type = YTPHY_WOL_FEATURE_PULSE_TRIGGER;
        wol_cfg.width = YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH;
        ret = ytphy_wol_feature_enable_cfg(phydev, wol_cfg);
        if (ret < 0)
            return ret;

        /* Store the device address for the magic packet */
        ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR2_4_MAGIC_PACKET,
            ((p_attached_dev->dev_addr[0] << 8) |
            p_attached_dev->dev_addr[1]));
        if (ret < 0)
            return ret;
        ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR1_4_MAGIC_PACKET,
            ((p_attached_dev->dev_addr[2] << 8) |
            p_attached_dev->dev_addr[3]));
        if (ret < 0)
            return ret;
        ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR0_4_MAGIC_PACKET,
            ((p_attached_dev->dev_addr[4] << 8) |
            p_attached_dev->dev_addr[5]));
        if (ret < 0)
            return ret;
    } else {
        wol_cfg.enable = false;
        wol_cfg.type = YTPHY_WOL_FEATURE_TRIGGER_TYPE_MAX;
        wol_cfg.width = YTPHY_WOL_FEATURE_PULSE_WIDTH_MAX;
        ret = ytphy_wol_feature_enable_cfg(phydev, wol_cfg);
        if (ret < 0)
            return ret;
    }

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

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

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

    struct yt8xxx_priv *priv = phydev->priv;

#if (YTPHY_WOL_FEATURE_ENABLE)
    struct ethtool_wolinfo wol;

    /* set phy wol enable */
    memset(&wol, 0x0, sizeof(struct ethtool_wolinfo));
    wol.wolopts |= WAKE_MAGIC;
    ytphy_wol_feature_set(phydev, &wol);
#endif

    phydev->irq = PHY_POLL;

    ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
    ret = ytphy_config_init(phydev);
#else
    ret = genphy_config_init(phydev);
#endif
    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 */
    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;

#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode = %d, polling mode = %d\n",
        __func__, phydev->addr, priv->chip_mode, priv->polling_mode);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode = %d, polling mode = %d\n",
        __func__, phydev->mdio.addr, priv->chip_mode, priv->polling_mode);
#endif
    return ret;
}

/* for fiber mode, there is no 10M speed mode and
 * this function is for this purpose.
 */
static int ytxxxx_adjust_status(struct phy_device *phydev, int val, int is_utp)
{
    int speed_mode, duplex;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    int speed = -1;
#else
    int speed = SPEED_UNKNOWN;
#endif

    if (is_utp)
        duplex = (val & YTXXXX_DUPLEX) >> YTXXXX_DUPLEX_BIT;
    else
        duplex = 1;
    speed_mode = (val & YTXXXX_SPEED_MODE) >> YTXXXX_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:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
        speed = -1;
#else
        speed = SPEED_UNKNOWN;
#endif
        break;
    }

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

    return 0;
}

/* for fiber mode, when speed is 100M, there is no definition for
 * autonegotiation, and this function handles this case and return
 * 1 per linux kernel's polling.
 */
static int yt8521_aneg_done(struct phy_device *phydev)
{
    int link_fiber = 0, link_utp = 0;

    /* reading Fiber */
    ytphy_write_ext(phydev, 0xa000, 2);
    link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));

    /* reading UTP */
    ytphy_write_ext(phydev, 0xa000, 0);
    if (!link_fiber)
        link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
    
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_fiber: %d, link_utp: %d\n",
        __func__, phydev->addr, link_fiber, link_utp);
#else
    netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_fiber: %d, link_utp: %d\n",
        __func__, phydev->mdio.addr, link_fiber, link_utp);
#endif
    return !!(link_fiber | link_utp);
}

static int yt8521_read_status(struct phy_device *phydev)
{
    int ret;
    int val;
    int yt8521_fiber_latch_val;
    int yt8521_fiber_curr_val;
    int link;
    int link_utp = 0, link_fiber = 0;
    struct yt8xxx_priv *priv = phydev->priv;

    if(priv->polling_mode != YT_PHY_MODE_FIBER) {
        /* 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(YTXXXX_LINK_STATUS_BIT));
        if (link) {
            link_utp = 1;
            ytxxxx_adjust_status(phydev, val, 1);
        } else {
            link_utp = 0;
        }
    }

    if (priv->polling_mode != YT_PHY_MODE_UTP) {
        /* reading Fiber */
        ret = ytphy_write_ext(phydev, 0xa000, 2);
        if (ret < 0)
            return ret;

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

        //note: below debug information is used to check multiple PHy ports.

        /* for fiber, from 1000m to 100m, there is not link down from 0x11,
         * and check reg 1 to identify such case this is important for Linux
         * kernel for that, missing linkdown event will cause problem.
         */
        yt8521_fiber_latch_val = phy_read(phydev, MII_BMSR);
        yt8521_fiber_curr_val = phy_read(phydev, MII_BMSR);
        link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
        if (link && yt8521_fiber_latch_val != yt8521_fiber_curr_val) {
            link = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)            
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
                __func__, phydev->addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
                __func__, phydev->mdio.addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val);
#endif
        }

        if (link) {
            link_fiber = 1;
            ytxxxx_adjust_status(phydev, val, 0);
        } else {
            link_fiber = 0;
        }
    }

    if (link_utp || link_fiber) {
        if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n",
                __func__, phydev->addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n",
                __func__, phydev->mdio.addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val);
#endif
        phydev->link = 1;
    } else {
        if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
        phydev->link = 0;
    }

    if (priv->polling_mode != YT_PHY_MODE_FIBER) {    //utp or combo
        if (link_fiber)
            ytphy_write_ext(phydev, 0xa000, 2);
        if (link_utp)
            ytphy_write_ext(phydev, 0xa000, 0);
    }
    return 0;
}

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

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

    return 0;
}

static int yt8521_resume(struct phy_device *phydev)
{
    int value, ret;

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

    value &= (~BIT(YT8521_EN_SLEEP_SW_BIT));

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

#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    mutex_lock(&phydev->lock);
#else
    /* no need lock/unlock in 4.19 */
#endif
    struct yt8xxx_priv *priv = phydev->priv;

    if (priv->polling_mode != YT_PHY_MODE_FIBER) {
        ytphy_write_ext(phydev, 0xa000, 0);
        value = phy_read(phydev, MII_BMCR);
        phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
    }

    if (priv->polling_mode != YT_PHY_MODE_UTP) {
        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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    mutex_unlock(&phydev->lock);
#else
    /* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/

    return 0;
}

static int yt8531S_config_init(struct phy_device *phydev)
{
    int ret = 0;

#if (YTPHY8531A_XTAL_INIT)
    ret = yt8531a_xtal_init(phydev);
    if (ret < 0)
        return ret;
#endif

    ret = yt8521_config_init(phydev);

    return ret;
}

static int yt8531_config_init(struct phy_device *phydev)
{
    int ret = 0;

#if (YTPHY8531A_XTAL_INIT)
    ret = yt8531a_xtal_init(phydev);
    if (ret < 0)
        return ret;
#endif

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
    ret = ytphy_config_init(phydev);
#else
    ret = genphy_config_init(phydev);
#endif
    if (ret < 0)
        return ret;

    return 0;
}

static int yt8614_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct device *dev = &phydev->dev;
#else
    struct device *dev = &phydev->mdio.dev;
#endif
    struct yt8xxx_priv *priv;
    int chip_config;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    phydev->priv = priv;

    chip_config = ytphy_read_ext(phydev, 0xa007);

    priv->chip_mode = chip_config & 0xf;
    switch (priv->chip_mode) {
    case 8:        //4'b1000, Fiber x4 + Copper x4
    case 12:    //4'b1100, QSGMII x1 + Combo x4 mode;
    case 13:    //4'b1101, QSGMII x1 + Combo x4 mode;
        priv->polling_mode = (YT_PHY_MODE_FIBER | YT_PHY_MODE_UTP);
        break;
    case 14:    //4'b1110, QSGMII x1 + SGMII(MAC) x4 mode;
    case 11:    //4'b1011, QSGMII x1 + Fiber x4 mode;
        priv->polling_mode = YT_PHY_MODE_FIBER; 
        break;
    case 9:        //4'b1001, Reserved.
    case 10:    //4'b1010, QSGMII x1 + Copper x4 mode
    case 15:    //4'b1111, SGMII(PHY) x4 + Copper x4 mode
    default:
        priv->polling_mode = YT_PHY_MODE_UTP; 
        break;
    }
    
    return 0;
}

static int yt8614Q_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    struct device *dev = &phydev->dev;
#else
    struct device *dev = &phydev->mdio.dev;
#endif
    struct yt8xxx_priv *priv;
    int chip_config;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    phydev->priv = priv;

    chip_config = ytphy_read_ext(phydev, 0xa007);

    priv->chip_mode = chip_config & 0xf;
    switch (priv->chip_mode) {
    case 0x1:    //4'b0001, QSGMII to 1000BASE-X or 100BASE-FX x 4
    default:
        priv->polling_mode = YT_PHY_MODE_FIBER; 
        break;
    }
    
    return 0;
}

static int yt8614Q_config_aneg(struct phy_device *phydev)
{
    //do nothing
    
    return 0;
}

#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8618_soft_reset(struct phy_device *phydev)
{
    int ret;

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

    return 0;
}

static int yt8614_soft_reset(struct phy_device *phydev)
{
    int ret;

    /* qsgmii */
    ytphy_write_ext(phydev, 0xa000, 2);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    /* sgmii */
    ytphy_write_ext(phydev, 0xa000, 3);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    /* utp */
    ytphy_write_ext(phydev, 0xa000, 0);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0)
        return ret;

    return 0;
}

static int yt8614Q_soft_reset(struct phy_device *phydev)
{
    int ret;

    /* qsgmii */
    ytphy_write_ext(phydev, 0xa000, 2);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    /* sgmii */
    ytphy_write_ext(phydev, 0xa000, 3);
    ret = ytphy_soft_reset(phydev);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    return 0;
}
#endif

static int yt8618_config_init(struct phy_device *phydev)
{
    int ret;
    int val;
    unsigned int retries = 12;
#if (YTPHY_861X_ABC_VER)
    int port = 0;
#endif

    phydev->irq = PHY_POLL;

#if (YTPHY_861X_ABC_VER)
    port = yt8614_get_port_from_phydev(phydev);
#endif

    ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
    ret = ytphy_config_init(phydev);
#else
    ret = genphy_config_init(phydev);
#endif
    if (ret < 0)
        return ret;

    /* for utp to optimize signal */
    ret = ytphy_write_ext(phydev, 0x41, 0x33);
    if (ret < 0)
        return ret;
    ret = ytphy_write_ext(phydev, 0x42, 0x66);
    if (ret < 0)
        return ret;
    ret = ytphy_write_ext(phydev, 0x43, 0xaa);
    if (ret < 0)
        return ret;
    ret = ytphy_write_ext(phydev, 0x44, 0xd0d);
    if (ret < 0)
        return ret;

#if (YTPHY_861X_ABC_VER)
    if ((port == 2) || (port == 5)) {
        ret = ytphy_write_ext(phydev, 0x57, 0x2929);
        if (ret < 0)
            return ret;
    }
#endif

    val = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, val | BMCR_RESET);
    do {
        msleep(50);
        ret = phy_read(phydev, MII_BMCR);
        if (ret < 0)
            return ret;
    } while ((ret & BMCR_RESET) && --retries);
    if (ret & BMCR_RESET)
        return -ETIMEDOUT;

    /* for QSGMII optimization */
    ytphy_write_ext(phydev, 0xa000, 0x02);

    ret = ytphy_write_ext(phydev, 0x3, 0x4F80);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }
    ret = ytphy_write_ext(phydev, 0xe, 0x4F80);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    ytphy_write_ext(phydev, 0xa000, 0);

#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif
    return ret;
}

#if (YTPHY_861X_ABC_VER)
static int yt8614_get_port_from_phydev(struct phy_device *phydev)
{
    int tmp = ytphy_read_ext(phydev, 0xa0ff);
    int phy_addr = 0;

#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    phy_addr = (unsigned int)phydev->addr;
#else
    phy_addr = (unsigned int)phydev->mdio.addr;
#endif

    if ((phy_addr - tmp) < 0) {
        ytphy_write_ext(phydev, 0xa0ff, phy_addr);
        tmp = phy_addr;
    }

    return (phy_addr - tmp);
}
#endif

static int yt8614_config_init(struct phy_device *phydev)
{
    int ret = 0;
    int val;
    unsigned int retries = 12;
#if (YTPHY_861X_ABC_VER)
    int port = 0;
#endif
    struct yt8xxx_priv *priv = phydev->priv;

    phydev->irq = PHY_POLL;


#if (YTPHY_861X_ABC_VER)
    port = yt8614_get_port_from_phydev(phydev);
#endif

    ytphy_write_ext(phydev, 0xa000, 0);

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
    ret = ytphy_config_init(phydev);
#else
    ret = genphy_config_init(phydev);
#endif
    if (ret < 0)
        return ret;

    /* for utp to optimize signal */
    ret = ytphy_write_ext(phydev, 0x41, 0x33);
    if (ret < 0)
        return ret;
    ret = ytphy_write_ext(phydev, 0x42, 0x66);
    if (ret < 0)
        return ret;
    ret = ytphy_write_ext(phydev, 0x43, 0xaa);
    if (ret < 0)
        return ret;
    ret = ytphy_write_ext(phydev, 0x44, 0xd0d);
    if (ret < 0)
        return ret;

#if (YTPHY_861X_ABC_VER)
    if (port == 2) {
        ret = ytphy_write_ext(phydev, 0x57, 0x2929);
        if (ret < 0)
            return ret;
    }
#endif

    /* soft reset to take config effect */
    val = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, val | BMCR_RESET);
    do {
        msleep(50);
        ret = phy_read(phydev, MII_BMCR);
        if (ret < 0)
            return ret;
    } while ((ret & BMCR_RESET) && --retries);
    if (ret & BMCR_RESET)
        return -ETIMEDOUT;

    /* for QSGMII optimization */
    ytphy_write_ext(phydev, 0xa000, 0x02);
    ret = ytphy_write_ext(phydev, 0x3, 0x4F80);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }
    ret = ytphy_write_ext(phydev, 0xe, 0x4F80);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    /* for SGMII optimization */
    ytphy_write_ext(phydev, 0xa000, 0x03);
    ret = ytphy_write_ext(phydev, 0x3, 0x2420);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }
    ret = ytphy_write_ext(phydev, 0xe, 0x24a0);
    if (ret < 0) {
        ytphy_write_ext(phydev, 0xa000, 0);
        return ret;
    }

    /* back up to utp*/
    ytphy_write_ext(phydev, 0xa000, 0);

#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->addr, priv->chip_mode, priv->polling_mode);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->mdio.addr, priv->chip_mode, priv->polling_mode);
#endif
    return ret;
}

static int yt8614Q_config_init(struct phy_device *phydev)
{
    struct yt8xxx_priv *priv = phydev->priv;

    phydev->irq = PHY_POLL;

    ytphy_write_ext(phydev, 0xa056, 0x7);

#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->addr, priv->chip_mode, priv->polling_mode);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->mdio.addr, priv->chip_mode, priv->polling_mode);
#endif
    return 0;
}

static int yt8618_aneg_done(struct phy_device *phydev)
{
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
    return genphy_aneg_done(phydev);
#else
    return 1;
#endif
}

static int yt8614_aneg_done(struct phy_device *phydev)
{
    int link_fiber = 0, link_utp = 0;
    struct yt8xxx_priv *priv = phydev->priv;

    if (priv->polling_mode & YT_PHY_MODE_FIBER) {
        /* reading Fiber */
        ytphy_write_ext(phydev, 0xa000, 3);
        link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
    }
    
    if (priv->polling_mode & YT_PHY_MODE_UTP) {
        /* reading UTP */
        ytphy_write_ext(phydev, 0xa000, 0);
        link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
    }

    return !!(link_fiber | link_utp);
}

static int yt8614Q_aneg_done(struct phy_device *phydev)
{
    int link_fiber = 0;
    struct yt8xxx_priv *priv = phydev->priv;

    if (priv->polling_mode & YT_PHY_MODE_FIBER) {
        /* reading Fiber */
        ytphy_write_ext(phydev, 0xa000, 3);
        link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
    }
    
    return !!(link_fiber);
}

static int yt8614_read_status(struct phy_device *phydev)
{
    int ret;
    int val, yt8614_fiber_latch_val, yt8614_fiber_curr_val;
    int link;
    int link_utp = 0, link_fiber = 0;
    struct yt8xxx_priv *priv = phydev->priv;

    if (priv->polling_mode & YT_PHY_MODE_UTP) {
        /* switch to utp and reading regs  */
        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(YTXXXX_LINK_STATUS_BIT));
        if (link) {
            link_utp = 1;
            /* here is same as 8521 and re-use the function; */
            ytxxxx_adjust_status(phydev, val, 1);
        } else {
            link_utp = 0;
        }
    }

    if (priv->polling_mode & YT_PHY_MODE_FIBER) {
        /* reading Fiber/sgmii */
        ret = ytphy_write_ext(phydev, 0xa000, 3);
        if (ret < 0)
            return ret;

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

        /* for fiber, from 1000m to 100m, there is not link down from 0x11,
         * and check reg 1 to identify such case
         */
        yt8614_fiber_latch_val = phy_read(phydev, MII_BMSR);
        yt8614_fiber_curr_val = phy_read(phydev, MII_BMSR);
        link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
        if (link && yt8614_fiber_latch_val != yt8614_fiber_curr_val) {
            link = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
                __func__, phydev->addr, yt8614_fiber_latch_val, yt8614_fiber_curr_val);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
                __func__, phydev->mdio.addr, yt8614_fiber_latch_val, yt8614_fiber_curr_val);
#endif
        }

        if (link) {
            link_fiber = 1;
            ytxxxx_adjust_status(phydev, val, 0);
        } else {
            link_fiber = 0;
        }
    }

    if (link_utp || link_fiber) {
        if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media %s\n",
                __func__, phydev->addr, (link_utp && link_fiber) ? "both UTP and Fiber" : (link_utp ? "UTP" : "Fiber"));
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media %s\n",
                __func__, phydev->mdio.addr, (link_utp && link_fiber) ? "both UTP and Fiber" : (link_utp ? "UTP" : "Fiber"));
#endif
        phydev->link = 1;
    } else {
        if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
        phydev->link = 0;
    }

    if (priv->polling_mode & YT_PHY_MODE_UTP) {
        if (link_utp)
            ytphy_write_ext(phydev, 0xa000, 0);
    }
    return 0;
}

static int yt8614Q_read_status(struct phy_device *phydev)
{
    int ret;
    int val, yt8614Q_fiber_latch_val, yt8614Q_fiber_curr_val;
    int link;
    int link_fiber = 0;
    struct yt8xxx_priv *priv = phydev->priv;

    if (priv->polling_mode & YT_PHY_MODE_FIBER) {
        /* reading Fiber/sgmii */
        ret = ytphy_write_ext(phydev, 0xa000, 3);
        if (ret < 0)
            return ret;

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

        /* for fiber, from 1000m to 100m, there is not link down from 0x11,
         * and check reg 1 to identify such case
         */
        yt8614Q_fiber_latch_val = phy_read(phydev, MII_BMSR);
        yt8614Q_fiber_curr_val = phy_read(phydev, MII_BMSR);
        link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
        if (link && yt8614Q_fiber_latch_val != yt8614Q_fiber_curr_val) {
            link = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
                __func__, phydev->addr, yt8614Q_fiber_latch_val, yt8614Q_fiber_curr_val);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
                __func__, phydev->mdio.addr, yt8614Q_fiber_latch_val, yt8614Q_fiber_curr_val);
#endif
        }

        if (link) {
            link_fiber = 1;
            ytxxxx_adjust_status(phydev, val, 0);
        } else {
            link_fiber = 0;
        }
    }

    if (link_fiber) {
        if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media Fiber\n",
                __func__, phydev->addr);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media Fiber\n",
                __func__, phydev->mdio.addr);
#endif
        phydev->link = 1;
    } else {
        if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
        phydev->link = 0;
    }

    return 0;
}

static int yt8618_read_status(struct phy_device *phydev)
{
    int ret;
    /* maybe for 8614 yt8521_fiber_latch_val, yt8521_fiber_curr_val; */
    int val;
    int link;
    int link_utp = 0, link_fiber = 0;

    /* switch to utp and reading regs  */
    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(YTXXXX_LINK_STATUS_BIT));
    if (link) {
        link_utp = 1;
        ytxxxx_adjust_status(phydev, val, 1);
    } else {
        link_utp = 0;
    }

    if (link_utp || link_fiber)
        phydev->link = 1;
    else
        phydev->link = 0;

    return 0;
}

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

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    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);

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

    return 0;
}

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

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    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);

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

    return 0;
}

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

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    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, 3);
    value = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);

    ytphy_write_ext(phydev, 0xa000, 0);

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

    return 0;
}

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

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

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

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

    return 0;
}

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

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    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, 3);
    value = phy_read(phydev, MII_BMCR);
    phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);

    ytphy_write_ext(phydev, 0xa000, 0);

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

    return 0;
}

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

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

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

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

    return 0;
}

#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8821_soft_reset(struct phy_device *phydev)
{
    int ret, val;

    val = ytphy_read_ext(phydev, 0xa001);
    ytphy_write_ext(phydev, 0xa001, (val & ~0x8000));

    ytphy_write_ext(phydev, 0xa000, 0);
    ret = ytphy_soft_reset(phydev);

    return ret;
}
#endif

static int yt8821_init(struct phy_device *phydev)
{
    int ret = 0;

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

    ret = ytphy_write_ext(phydev, 0x34e, 0x8080);
    if (ret < 0)
        return ret;
    
    ret = ytphy_write_ext(phydev, 0x4d2, 0x5200);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x4d3, 0x5200);
    if (ret < 0)
        return ret;
    
    ret = ytphy_write_ext(phydev, 0x372, 0x5a3c);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x374, 0x7c6c);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x336, 0xaa0a);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x340, 0x3022);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x36a, 0x8000);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x4b3, 0x7711);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x4b5, 0x2211);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x56, 0x20);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x56, 0x3f);
    if (ret < 0)
        return ret;

    ret = ytphy_write_ext(phydev, 0x97, 0x380c);
    if (ret < 0)
        return ret;

    /* soft reset */
    ytphy_soft_reset(phydev);
    
    return ret;
}

#define YT8821_CHIP_MODE_AUTO_BX2500_SGMII     (1)
#define YT8821_CHIP_MODE_FORCE_BX2500         (0)
#define YT8821_CHIP_MODE_UTP_TO_FIBER_FORCE (0)
static int yt8821_config_init(struct phy_device *phydev)
{
    int ret, val;

    phydev->irq = PHY_POLL;
    
    val = ytphy_read_ext(phydev, 0xa001);
#if (YT8821_CHIP_MODE_AUTO_BX2500_SGMII)            //ext reg 0xa001 bit2:0 3'b000
    val &= ~(BIT(0));
    val &= ~(BIT(1));
    val &= ~(BIT(2));
    ret = ytphy_write_ext(phydev, 0xa001, val);
    if (ret < 0)
        return ret;
#elif (YT8821_CHIP_MODE_FORCE_BX2500)                //ext reg 0xa001 bit2:0 3'b001
    val |= BIT(0);
    val &= ~(BIT(1));
    val &= ~(BIT(2));
    ret = ytphy_write_ext(phydev, 0xa001, val);
    if (ret < 0)
        return ret;
#elif (YT8821_CHIP_MODE_UTP_TO_FIBER_FORCE)            //ext reg 0xa001 bit2:0 3'b101
    val |= BIT(0);
    val &= ~(BIT(1));
    val |= BIT(2);
    ret = ytphy_write_ext(phydev, 0xa001, val);
    if (ret < 0)
        return ret;
#endif

    ret = yt8821_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;
    
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
    netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif

    return ret;
}

static int yt8821_aneg_done(struct phy_device *phydev)
{
    int link_utp = 0;

    /* reading UTP */
    ytphy_write_ext(phydev, 0xa000, 0);
    link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(10)));
    
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
    netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
        __func__, phydev->addr, link_utp);
#else
    netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
        __func__, phydev->mdio.addr, link_utp);
#endif

    return !!(link_utp);
}
static int yt8821_adjust_status(struct phy_device *phydev, int val)
{
    int speed_mode, duplex;
    int speed_mode_bit15_14, speed_mode_bit9;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
    int speed = -1;
#else
    int speed = SPEED_UNKNOWN;
#endif

    duplex = (val & YT8512_DUPLEX) >> YTXXXX_DUPLEX_BIT;

    /* Bit9-Bit15-Bit14 speed mode 100---2.5G; 010---1000M; 001---100M; 000---10M */
    speed_mode_bit15_14 = (val & YTXXXX_SPEED_MODE) >> YTXXXX_SPEED_MODE_BIT;
    speed_mode_bit9 = (val & BIT(9)) >> 9;
    speed_mode = (speed_mode_bit9 << 2) | speed_mode_bit15_14;
    switch (speed_mode) {
    case 0:
        speed = SPEED_10;
        break;
    case 1:
        speed = SPEED_100;
        break;
    case 2:
        speed = SPEED_1000;
        break;
    case 4:
        speed = SPEED_2500;
        break;
    default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
        speed = -1;
#else
        speed = SPEED_UNKNOWN;
#endif
        break;
    }

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

    return 0;
}

static int yt8821_read_status(struct phy_device *phydev)
{
    int ret;
    int val;
    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(YTXXXX_LINK_STATUS_BIT));
    if (link) {
        link_utp = 1;
        yt8821_adjust_status(phydev, val);    /* speed(2500), duplex */
    } else {
        link_utp = 0;
    }

    if (link_utp) {
        if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
                __func__, phydev->addr, (unsigned int)val);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
                __func__, phydev->mdio.addr, (unsigned int)val);
#endif
        phydev->link = 1;
    } else {
        if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
            netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif

        phydev->link = 0;
    }

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

#if (KERNEL_VERSION(5, 0, 21) < LINUX_VERSION_CODE)
static int yt8821_get_features(struct phy_device *phydev)
{
    linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported, 1);
    return genphy_read_abilities(phydev);
}
#endif

static int yt8821_suspend(struct phy_device *phydev)
{
    int value = 0;
    int wol_enabled = 0;

#if (YTPHY_WOL_FEATURE_ENABLE)
    value = phy_read(phydev, YTPHY_UTP_INTR_REG);
    wol_enabled = value & YTPHY_WOL_FEATURE_INTR;
#endif

    if (!wol_enabled)
    {
        value = phy_read(phydev, MII_BMCR);
        phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
    }

    return 0;
}

static int yt8821_resume(struct phy_device *phydev)
{
    int value;
    
    value = phy_read(phydev, MII_BMCR);
    value &= ~BMCR_PDOWN;
    value &= ~BMCR_ISOLATE;
    
    phy_write(phydev, MII_BMCR, value);
    
    return 0;
}

static struct phy_driver ytphy_drvs[] = {
    {
        .phy_id         = PHY_ID_YT8010,
        .name           = "YT8010 100M Automotive Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_BASIC_FEATURES,
        .flags          = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset     = yt8010_soft_reset,
#endif
        .config_aneg    = yt8010_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done      = yt8010_aneg_done,
#endif
        .config_init    = yt8010_config_init,
        .read_status    = yt8010_read_status,
    }, {
        .phy_id         = PHY_ID_YT8010AS,
        .name           = "YT8010AS 100M Automotive Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_BASIC_FEATURES,
        .flags          = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset     = yt8010AS_soft_reset,
#endif
        .config_aneg    = yt8010_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done      = yt8010_aneg_done,
#endif
        .config_init    = yt8010AS_config_init,
        .read_status    = yt8010_read_status,
    }, {
        .phy_id         = PHY_ID_YT8011,
        .name           = "YT8011 Automotive Gigabit Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_GBIT_FEATURES,
        .flags          = PHY_POLL,
        .probe            = yt8011_probe,
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset     = yt8011_soft_reset,
#endif
        .config_aneg    = yt8011_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done      = yt8011_aneg_done,
#endif
        .config_init    = yt8011_config_init,
        .read_status    = yt8011_read_status,
    }, {
        .phy_id         = PHY_ID_YT8510,
        .name           = "YT8510 100/10Mb Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_BASIC_FEATURES,
        .flags          = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = ytphy_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
        .config_init    = ytphy_config_init,
#else
        .config_init    = genphy_config_init,
#endif
        .read_status    = genphy_read_status,
    }, {
        .phy_id         = PHY_ID_YT8511,
        .name           = "YT8511 Gigabit Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_GBIT_FEATURES,
        .flags          = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = ytphy_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
#if GMAC_CLOCK_INPUT_NEEDED
        .config_init    = yt8511_config_init,
#else
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
        .config_init    = ytphy_config_init,
#else
        .config_init    = genphy_config_init,
#endif
#endif
        .read_status    = genphy_read_status,
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
    }, {
        .phy_id         = PHY_ID_YT8512,
        .name           = "YT8512 100/10Mb Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_BASIC_FEATURES,
        .flags          = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = ytphy_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
        .config_init    = yt8512_config_init,
        .read_status    = yt8512_read_status,
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
    }, {
        .phy_id         = PHY_ID_YT8522,
        .name           = "YT8522 100/10Mb Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_BASIC_FEATURES,
        .flags          = PHY_POLL,
        .probe            = yt8522_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = ytphy_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
        .config_init    = yt8522_config_init,
        .read_status    = yt8522_read_status,
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
    }, {
        .phy_id         = PHY_ID_YT8521,
        .name           = "YT8521 Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
        .features       = PHY_GBIT_FEATURES,
        .flags          = PHY_POLL,
        .probe            = yt8521_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset     = yt8521_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done      = yt8521_aneg_done,
#endif
        .config_init    = yt8521_config_init,
        .read_status    = yt8521_read_status,
        .suspend        = yt8521_suspend,
        .resume         = yt8521_resume,
#if (YTPHY_WOL_FEATURE_ENABLE)
        .get_wol        = &ytphy_wol_feature_get,
        .set_wol        = &ytphy_wol_feature_set,
#endif
    }, {
        /* same as 8521 */
        .phy_id        = PHY_ID_YT8531S,
        .name          = "YT8531S Ethernet",
        .phy_id_mask   = MOTORCOMM_PHY_ID_MASK,
        .features      = PHY_GBIT_FEATURES,
        .flags         = PHY_POLL,
        .probe            = yt8521_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = yt8521_soft_reset,
#endif
        .config_aneg   = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done     = yt8521_aneg_done,
#endif
        .config_init   = yt8531S_config_init,
        .read_status   = yt8521_read_status,
        .suspend       = yt8521_suspend,
        .resume        = yt8521_resume,
#if (YTPHY_WOL_FEATURE_ENABLE)
        .get_wol       = &ytphy_wol_feature_get,
        .set_wol       = &ytphy_wol_feature_set,
#endif
    }, {
        /* same as 8511 */
        .phy_id        = PHY_ID_YT8531,
        .name          = "YT8531 Gigabit Ethernet",
        .phy_id_mask   = MOTORCOMM_PHY_ID_MASK,
        .features      = PHY_GBIT_FEATURES,
        .flags         = PHY_POLL,
        .config_aneg   = genphy_config_aneg,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = ytphy_soft_reset,
#endif
        .config_init   = yt8531_config_init,
        .read_status   = genphy_read_status,
        .suspend       = genphy_suspend,
        .resume        = genphy_resume,
#if (YTPHY_WOL_FEATURE_ENABLE)
        .get_wol       = &ytphy_wol_feature_get,
        .set_wol       = &ytphy_wol_feature_set,
#endif
    }, {
        .phy_id        = PHY_ID_YT8618,
        .name          = "YT8618 Ethernet",
        .phy_id_mask   = MOTORCOMM_PHY_ID_MASK,
        .features      = PHY_GBIT_FEATURES,
        .flags         = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = yt8618_soft_reset,
#endif
        .config_aneg   = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done     = yt8618_aneg_done,
#endif
        .config_init   = yt8618_config_init,
        .read_status   = yt8618_read_status,
        .suspend       = yt8618_suspend,
        .resume        = yt8618_resume,
    },
    {
        .phy_id        = PHY_ID_YT8614,
        .name          = "YT8614 Ethernet",
        .phy_id_mask   = MOTORCOMM_PHY_ID_MASK,
        .features      = PHY_GBIT_FEATURES,
        .flags         = PHY_POLL,
        .probe         = yt8614_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = yt8614_soft_reset,
#endif
        .config_aneg   = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done     = yt8614_aneg_done,
#endif
        .config_init   = yt8614_config_init,
        .read_status   = yt8614_read_status,
        .suspend       = yt8614_suspend,
        .resume        = yt8614_resume,
    },
    {
        .phy_id        = PHY_ID_YT8614Q,
        .name          = "YT8614Q Ethernet",
        .phy_id_mask   = MOTORCOMM_PHY_ID_MASK,
        .features      = PHY_GBIT_FEATURES,
        .flags         = PHY_POLL,
        .probe         = yt8614Q_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = yt8614Q_soft_reset,
#endif
        .config_aneg   = yt8614Q_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done     = yt8614Q_aneg_done,
#endif
        .config_init   = yt8614Q_config_init,
        .read_status   = yt8614Q_read_status,
        .suspend       = yt8614Q_suspend,
        .resume        = yt8614Q_resume,
    },
    {
        .phy_id        = PHY_ID_YT8821,
        .name          = "YT8821 2.5Gbps Ethernet",
        .phy_id_mask   = MOTORCOMM_PHY_ID_MASK,
#if (KERNEL_VERSION(5, 1, 0) > LINUX_VERSION_CODE)
        .features      = PHY_GBIT_FEATURES,
#endif
        .flags         = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
        .soft_reset    = yt8821_soft_reset,
#endif
        .config_aneg   = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
        .aneg_done     = yt8821_aneg_done,
#endif
#if (KERNEL_VERSION(5, 0, 21) < LINUX_VERSION_CODE)
        .get_features  = yt8821_get_features,
#endif
        .config_init   = yt8821_config_init,
#if (YTPHY_WOL_FEATURE_ENABLE)
        .set_wol       = &ytphy_wol_feature_set,
        .get_wol       = &ytphy_wol_feature_get,
#endif
        .read_status   = yt8821_read_status,
        .suspend       = yt8821_suspend,
        .resume           = yt8821_resume,
    },
};

#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
static int ytphy_drivers_register(struct phy_driver *phy_drvs, int size)
{
    int i, j;
    int ret;

    for (i = 0; i < size; i++) {
        ret = phy_driver_register(&phy_drvs[i]);
        if (ret)
            goto err;
    }

    return 0;

err:
        for (j = 0; j < i; j++)
            phy_driver_unregister(&phy_drvs[j]);

    return ret;
}

static void ytphy_drivers_unregister(struct phy_driver *phy_drvs, int size)
{
    int i;

    for (i = 0; i < size; i++)
        phy_driver_unregister(&phy_drvs[i]);
}

static int __init ytphy_init(void)
{
    return ytphy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}

static void __exit ytphy_exit(void)
{
    ytphy_drivers_unregister(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}

module_init(ytphy_init);
module_exit(ytphy_exit);
#else
/* for linux 4.x */
module_phy_driver(ytphy_drvs);
#endif

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_YT8521, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8531S, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8531, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8618, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8614, MOTORCOMM_PHY_ID_MASK },
    { PHY_ID_YT8821, MOTORCOMM_PHY_ID_MASK },
    { }
};

MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);

Makefile

# Makefile for Linux PHY drivers and MDIO bus drivers

libphy-y            := phy.o phy_device.o mdio_bus.o mdio_device.o
libphy-$(CONFIG_SWPHY)        += swphy.o

obj-$(CONFIG_PHYLIB)        += libphy.o

obj-$(CONFIG_MDIO_BCM_IPROC)    += mdio-bcm-iproc.o
obj-$(CONFIG_MDIO_BCM_UNIMAC)    += mdio-bcm-unimac.o
obj-$(CONFIG_MDIO_BITBANG)    += mdio-bitbang.o
obj-$(CONFIG_MDIO_BUS_MUX)    += mdio-mux.o
obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC)    += mdio-mux-bcm-iproc.o
obj-$(CONFIG_MDIO_BUS_MUX_GPIO)    += mdio-mux-gpio.o
obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
obj-$(CONFIG_MDIO_CAVIUM)    += mdio-cavium.o
obj-$(CONFIG_MDIO_GPIO)        += mdio-gpio.o
obj-$(CONFIG_MDIO_HISI_FEMAC)    += mdio-hisi-femac.o
obj-$(CONFIG_MDIO_MOXART)    += mdio-moxart.o
obj-$(CONFIG_MDIO_OCTEON)    += mdio-octeon.o
obj-$(CONFIG_MDIO_SUN4I)    += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER)    += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE)    += mdio-xgene.o

obj-$(CONFIG_AMD_PHY)        += amd.o
obj-$(CONFIG_AQUANTIA_PHY)    += aquantia.o
obj-$(CONFIG_AT803X_PHY)    += at803x.o
obj-$(CONFIG_BCM63XX_PHY)    += bcm63xx.o
obj-$(CONFIG_BCM7XXX_PHY)    += bcm7xxx.o
obj-$(CONFIG_BCM87XX_PHY)    += bcm87xx.o
obj-$(CONFIG_BCM_CYGNUS_PHY)    += bcm-cygnus.o
obj-$(CONFIG_BCM_NET_PHYLIB)    += bcm-phy-lib.o
obj-$(CONFIG_BROADCOM_PHY)    += broadcom.o
obj-$(CONFIG_CICADA_PHY)    += cicada.o
obj-$(CONFIG_DAVICOM_PHY)    += davicom.o
obj-$(CONFIG_DP83640_PHY)    += dp83640.o
obj-$(CONFIG_DP83848_PHY)    += dp83848.o
obj-$(CONFIG_DP83867_PHY)    += dp83867.o
obj-$(CONFIG_FIXED_PHY)        += fixed_phy.o
obj-$(CONFIG_ICPLUS_PHY)    += icplus.o
obj-$(CONFIG_INTEL_XWAY_PHY)    += intel-xway.o
obj-$(CONFIG_LSI_ET1011C_PHY)    += et1011c.o
obj-$(CONFIG_LXT_PHY)        += lxt.o
obj-$(CONFIG_MARVELL_PHY)    += marvell.o
obj-$(CONFIG_MICREL_KS8995MA)    += spi_ks8995.o
obj-$(CONFIG_MICREL_PHY)    += micrel.o
obj-$(CONFIG_MICROCHIP_PHY)    += microchip.o
obj-$(CONFIG_MICROSEMI_PHY)    += mscc.o
obj-$(CONFIG_MOTORCOMM_PHY)    += motorcomm.o
obj-$(CONFIG_NATIONAL_PHY)    += national.o
obj-$(CONFIG_QSEMI_PHY)        += qsemi.o
obj-$(CONFIG_REALTEK_PHY)    += realtek.o
obj-$(CONFIG_SMSC_PHY)        += smsc.o
obj-$(CONFIG_STE10XP)        += ste10Xp.o
obj-$(CONFIG_TERANETICS_PHY)    += teranetics.o
obj-$(CONFIG_VITESSE_PHY)    += vitesse.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
 

Kconfig

#
# PHY Layer Configuration
#

menuconfig PHYLIB
    tristate "PHY Device support and infrastructure"
    depends on NETDEVICES
    help
      Ethernet controllers are usually attached to PHY
      devices.  This option provides infrastructure for
      managing PHY devices.

if PHYLIB

config SWPHY
    bool

comment "MDIO bus device drivers"

config MDIO_BCM_IPROC
    tristate "Broadcom iProc MDIO bus controller"
    depends on ARCH_BCM_IPROC || COMPILE_TEST
    depends on HAS_IOMEM && OF_MDIO
    help
      This module provides a driver for the MDIO busses found in the
      Broadcom iProc SoC's.

config MDIO_BCM_UNIMAC
    tristate "Broadcom UniMAC MDIO bus controller"
    depends on HAS_IOMEM
    help
      This module provides a driver for the Broadcom UniMAC MDIO busses.
      This hardware can be found in the Broadcom GENET Ethernet MAC
      controllers as well as some Broadcom Ethernet switches such as the
      Starfighter 2 switches.

config MDIO_BITBANG
    tristate "Bitbanged MDIO buses"
    help
      This module implements the MDIO bus protocol in software,
      for use by low level drivers that export the ability to
      drive the relevant pins.

      If in doubt, say N.

config MDIO_BUS_MUX
    tristate
    depends on OF_MDIO
    help
      This module provides a driver framework for MDIO bus
      multiplexers which connect one of several child MDIO busses
      to a parent bus.  Switching between child busses is done by
      device specific drivers.

config MDIO_BUS_MUX_BCM_IPROC
    tristate "Broadcom iProc based MDIO bus multiplexers"
    depends on OF && OF_MDIO && (ARCH_BCM_IPROC || COMPILE_TEST)
    select MDIO_BUS_MUX
    default ARCH_BCM_IPROC
    help
      This module provides a driver for MDIO bus multiplexers found in
      iProc based Broadcom SoCs. This multiplexer connects one of several
      child MDIO bus to a parent bus. Buses could be internal as well as
      external and selection logic lies inside the same multiplexer.

config MDIO_BUS_MUX_GPIO
    tristate "GPIO controlled MDIO bus multiplexers"
    depends on OF_GPIO && OF_MDIO
    select MDIO_BUS_MUX
    help
      This module provides a driver for MDIO bus multiplexers that
      are controlled via GPIO lines.  The multiplexer connects one of
      several child MDIO busses to a parent bus.  Child bus
      selection is under the control of GPIO lines.

config MDIO_BUS_MUX_MMIOREG
    tristate "MMIO device-controlled MDIO bus multiplexers"
    depends on OF_MDIO && HAS_IOMEM
    select MDIO_BUS_MUX
    help
      This module provides a driver for MDIO bus multiplexers that
      are controlled via a simple memory-mapped device, like an FPGA.
      The multiplexer connects one of several child MDIO busses to a
      parent bus.  Child bus selection is under the control of one of
      the FPGA's registers.

      Currently, only 8-bit registers are supported.

config MDIO_CAVIUM
    tristate

config MDIO_GPIO
    tristate "GPIO lib-based bitbanged MDIO buses"
    depends on MDIO_BITBANG && GPIOLIB
    ---help---
      Supports GPIO lib-based MDIO busses.

      To compile this driver as a module, choose M here: the module
      will be called mdio-gpio.

config MDIO_HISI_FEMAC
    tristate "Hisilicon FEMAC MDIO bus controller"
    depends on HAS_IOMEM && OF_MDIO
    help
      This module provides a driver for the MDIO busses found in the
      Hisilicon SoC that have an Fast Ethernet MAC.

config MDIO_MOXART
        tristate "MOXA ART MDIO interface support"
        depends on ARCH_MOXART
        help
          This driver supports the MDIO interface found in the network
          interface units of the MOXA ART SoC

config MDIO_OCTEON
    tristate "Octeon and some ThunderX SOCs MDIO buses"
    depends on 64BIT
    depends on HAS_IOMEM
    select MDIO_CAVIUM
    help
      This module provides a driver for the Octeon and ThunderX MDIO
      buses. It is required by the Octeon and ThunderX ethernet device
      drivers on some systems.

config MDIO_SUN4I
    tristate "Allwinner sun4i MDIO interface support"
    depends on ARCH_SUNXI
    help
      This driver supports the MDIO interface found in the network
      interface units of the Allwinner SoC that have an EMAC (A10,
      A12, A10s, etc.)

config MDIO_THUNDER
    tristate "ThunderX SOCs MDIO buses"
    depends on 64BIT
    depends on PCI
    select MDIO_CAVIUM
    help
      This driver supports the MDIO interfaces found on Cavium
      ThunderX SoCs when the MDIO bus device appears as a PCI
      device.

config MDIO_XGENE
    tristate "APM X-Gene SoC MDIO bus controller"
    depends on ARCH_XGENE || COMPILE_TEST
    help
      This module provides a driver for the MDIO busses found in the
      APM X-Gene SoC's.

comment "MII PHY device drivers"

config AMD_PHY
    tristate "AMD PHYs"
    ---help---
      Currently supports the am79c874

config AQUANTIA_PHY
        tristate "Aquantia PHYs"
        ---help---
          Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405

config AT803X_PHY
    tristate "AT803X PHYs"
    ---help---
      Currently supports the AT8030 and AT8035 model

config BCM63XX_PHY
    tristate "Broadcom 63xx SOCs internal PHY"
    depends on BCM63XX
    select BCM_NET_PHYLIB
    ---help---
      Currently supports the 6348 and 6358 PHYs.

config BCM7XXX_PHY
    tristate "Broadcom 7xxx SOCs internal PHYs"
    select BCM_NET_PHYLIB
    ---help---
      Currently supports the BCM7366, BCM7439, BCM7445, and
      40nm and 65nm generation of BCM7xxx Set Top Box SoCs.

config BCM87XX_PHY
    tristate "Broadcom BCM8706 and BCM8727 PHYs"
    help
      Currently supports the BCM8706 and BCM8727 10G Ethernet PHYs.

config BCM_CYGNUS_PHY
    tristate "Broadcom Cygnus SoC internal PHY"
    depends on ARCH_BCM_CYGNUS || COMPILE_TEST
    depends on MDIO_BCM_IPROC
    select BCM_NET_PHYLIB
    ---help---
      This PHY driver is for the 1G internal PHYs of the Broadcom
      Cygnus Family SoC.

      Currently supports internal PHY's used in the BCM11300,
      BCM11320, BCM11350, BCM11360, BCM58300, BCM58302,
      BCM58303 & BCM58305 Broadcom Cygnus SoCs.

config BCM_NET_PHYLIB
    tristate

config BROADCOM_PHY
    tristate "Broadcom PHYs"
    select BCM_NET_PHYLIB
    ---help---
      Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
      BCM5481 and BCM5482 PHYs.

config CICADA_PHY
    tristate "Cicada PHYs"
    ---help---
      Currently supports the cis8204

config DAVICOM_PHY
    tristate "Davicom PHYs"
    ---help---
      Currently supports dm9161e and dm9131

config DP83848_PHY
    tristate "Texas Instruments DP83848 PHY"
    ---help---
      Supports the DP83848 PHY.

config DP83867_PHY
    tristate "Texas Instruments DP83867 Gigabit PHY"
    ---help---
      Currently supports the DP83867 PHY.

config FIXED_PHY
    tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
    depends on PHYLIB
    select SWPHY
    ---help---
      Adds the platform "fixed" MDIO Bus to cover the boards that use
      PHYs that are not connected to the real MDIO bus.

      Currently tested with mpc866ads and mpc8349e-mitx.

config ICPLUS_PHY
    tristate "ICPlus PHYs"
    ---help---
      Currently supports the IP175C and IP1001 PHYs.

config INTEL_XWAY_PHY
    tristate "Intel XWAY PHYs"
    ---help---
      Supports the Intel XWAY (former Lantiq) 11G and 22E PHYs.
      These PHYs are marked as standalone chips under the names
      PEF 7061, PEF 7071 and PEF 7072 or integrated into the Intel
      SoCs xRX200, xRX300, xRX330, xRX350 and xRX550.

config LSI_ET1011C_PHY
    tristate "LSI ET1011C PHY"
    ---help---
      Supports the LSI ET1011C PHY.

config LXT_PHY
    tristate "Intel LXT PHYs"
    ---help---
      Currently supports the lxt970, lxt971

config MARVELL_PHY
    tristate "Marvell PHYs"
    ---help---
      Currently has a driver for the 88E1011S

config MICREL_PHY
    tristate "Micrel PHYs"
    ---help---
      Supports the KSZ9021, VSC8201, KS8001 PHYs.

config MICROCHIP_PHY
    tristate "Microchip PHYs"
    help
      Supports the LAN88XX PHYs.

config MICROSEMI_PHY
    tristate "Microsemi PHYs"
    ---help---
      Currently supports the VSC8531 and VSC8541 PHYs

config MOTORCOMM_PHY
    tristate "Motorcomm PHYs"
    ---help---
      Supports the YT8010, YT8510, YT8511, YT8512 PHYs.

config NATIONAL_PHY
    tristate "National Semiconductor PHYs"
    ---help---
      Currently supports the DP83865 PHY.

config QSEMI_PHY
    tristate "Quality Semiconductor PHYs"
    ---help---
      Currently supports the qs6612

config REALTEK_PHY
    tristate "Realtek PHYs"
    ---help---
      Supports the Realtek 821x PHY.

config SMSC_PHY
    tristate "SMSC PHYs"
    ---help---
      Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs

config STE10XP
    tristate "STMicroelectronics STe10Xp PHYs"
    ---help---
      This is the driver for the STe100p and STe101p PHYs.

config TERANETICS_PHY
        tristate "Teranetics PHYs"
        ---help---
          Currently supports the Teranetics TN2020

config VITESSE_PHY
        tristate "Vitesse PHYs"
        ---help---
          Currently supports the vsc8244

config XILINX_GMII2RGMII
       tristate "Xilinx GMII2RGMII converter driver"
       ---help---
         This driver support xilinx GMII to RGMII IP core it provides
         the Reduced Gigabit Media Independent Interface(RGMII) between
         Ethernet physical media devices and the Gigabit Ethernet controller.

endif # PHYLIB

config MICREL_KS8995MA
    tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch"
    depends on SPI
 

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值