Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

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

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

#include "yt8614-phy.h"

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

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

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

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


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

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

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


/* no need to change below */

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

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

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

int phy_yt8531_led_fixup(struct mii_bus *bus, int addr);
int yt8511_config_out_125m(struct mii_bus *bus, int phy_id);

#if ( LINUX_VERSION_CODE > KERNEL_VERSION(5,0,0) )
int genphy_config_init(struct phy_device *phydev)
{
	int ret;

	//printk (KERN_INFO "yzhang..read phyaddr=%d, phyid=%08x\n",phydev->mdio.addr, phydev->phy_id);

	if(phydev->phy_id == 0x4f51e91b)
	{
		//printk (KERN_INFO "yzhang..get YT8511, abt to set 125m clk out, phyaddr=%d, phyid=%08x\n",phydev->mdio.addr, phydev->phy_id);
		ret = yt8511_config_out_125m(phydev->mdio.bus, phydev->mdio.addr);
		//printk (KERN_INFO "yzhang..8511 set 125m clk out, reg=%#04x\n",phydev->mdio.bus->read(phydev->mdio.bus,phydev->mdio.addr,0x1f)/*double check as delay*/);
		if (ret<0)
			printk (KERN_INFO "yzhang..failed to set 125m clk out, ret=%d\n",ret);

		phy_yt8531_led_fixup(phydev->mdio.bus, phydev->mdio.addr);
	}
	return  genphy_read_abilities(phydev);
}
#endif

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
static int ytphy_config_init(struct phy_device *phydev)
{
	return 0;
}
#endif

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

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

	val = phy_read(phydev, REG_DEBUG_DATA);

	return val;
}

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

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

	ret = phy_write(phydev, REG_DEBUG_DATA, val);

	return ret;
}

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

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

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

	val |= YT8512_CONFIG_PLL_REFCLK_SEL_EN;

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

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

	val |= YT8512_CONTROL1_RMII_EN;

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

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

	val |= YT_SOFTWARE_RESET;
	ret = phy_write(phydev, MII_BMCR, val);

	return ret;
}

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_LED1_BT_ON_EN, val);

	return ret;
}

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

	ret = yt8512_clk_init(phydev);
	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 ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
		speed = -1;
#else
		speed = SPEED_UNKNOWN;
#endif
		break;
	}

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

	return 0;
}
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#else
#if 0
int yt8521_soft_reset(struct phy_device *phydev)
{
	int ret;

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

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

	return 0;
}
#else
/* qingsong feedback 2 genphy_soft_reset will cause problem.
 * and this is the reduction version
 */
int yt8521_soft_reset(struct phy_device *phydev)
{
	int ret, val;

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

	ret = genphy_soft_reset(phydev);
	if (ret < 0)
		return ret;

	return 0;
}
#endif

#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;
}

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);
    printk("yt8511_config_dis_txdelay..phy txdelay, val=%#08x\n",val);

    return ret;
}

int phy_yt8531_led_fixup(struct mii_bus *bus, int addr)
{
	//printk("%s in\n", __func__);

	ytphy_mii_wr_ext(bus, addr, 0xa00d, 0x670);
	ytphy_mii_wr_ext(bus, addr, 0xa00e, 0x2070);
	ytphy_mii_wr_ext(bus, addr, 0xa00f, 0x7e);

	return 0;
}

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

	mdelay(100);
	ret = ytphy_mii_wr_ext(bus, addr, 0xa012, 0xd0);

	mdelay(100);
	val = ytphy_mii_rd_ext(bus, addr, 0xa012);

	if(val != 0xd0)
	{
		printk("yt8511_config_out_125m error\n");
		return -1;
	}

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

	val &= (~BIT(15));

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

	/* enable RXC clock when no wire plug */
	val = ytphy_mii_rd_ext(bus, addr, 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, addr, 0xc, val);
	//printk("yt8511_config_out_125m, phy clk out, val=%#08x\n",val);

#if 0
	/* for customer, please enable it based on demand.
	 * configure to master
	 */	
	val = bus->read(bus, phy_id, 0x9/*master/slave config reg*/);
	val |= (0x3<<11); //to be manual config and force to be master
	ret = bus->write(bus, phy_id, 0x9, val); //take effect until phy soft reset
	if (ret < 0)
		return ret;

	printk("yt8511_config_out_125m, phy to be master, val=%#08x\n",val);
#endif

    return ret;
}

EXPORT_SYMBOL(yt8511_config_out_125m);

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

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

	return ret;
}
#endif /*GMAC_CLOCK_INPUT_NEEDED*/

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

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

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

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

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

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

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

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

	return 0;
}

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

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

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

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

	return;
}

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

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

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

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

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

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

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

	return 0;
}

#endif /*(YTPHY_ENABLE_WOL)*/

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

	phydev->irq = PHY_POLL;

	ytphy_write_ext(phydev, 0xa000, 0);
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
	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 */
	ret = ytphy_write_ext(phydev, 0xa000, 0);
	if (ret < 0)
		return ret;

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

	printk (KERN_INFO "yt8521_config_init, 8521 init call out.\n");
	return ret;
}

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

	duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT;
	speed_mode = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT;
	switch (speed_mode) {
	case 0:
		if (is_utp)
			speed = SPEED_10;
		break;
	case 1:
		speed = SPEED_100;
		break;
	case 2:
		speed = SPEED_1000;
		break;
	case 3:
		break;
	default:
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
		speed = -1;
#else
		speed = SPEED_UNKNOWN;
#endif
		break;
	}

	phydev->speed = speed;
	phydev->duplex = duplex;
	//printk (KERN_INFO "yt8521_adjust_status call out,regval=0x%04x,mode=%s,speed=%dm...\n", val,is_utp?"utp":"fiber", phydev->speed);

	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.
 */
int yt8521_aneg_done (struct phy_device *phydev)
{

	//printk("yt8521_aneg_done callin,speed=%dm,linkmoded=%d\n", phydev->speed,link_mode_8521);

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

#if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) )
	return genphy_aneg_done(phydev);
#else
	return 1;
#endif
}

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

#if (YT8521_PHY_MODE_CURR != YT8521_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(YT8521_LINK_STATUS_BIT));
	if (link) {
		link_utp = 1;
		link_mode_8521 = 1;
		yt8521_adjust_status(phydev, val, 1);
	} else {
		link_utp = 0;
	}
#endif //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER)

#if (YT8521_PHY_MODE_CURR != YT8521_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.
	//printk (KERN_INFO "yt8521_read_status, fiber status=%04x,macbase=0x%08lx\n", val,(unsigned long)phydev->attached_dev);

	/* 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(YT8521_LINK_STATUS_BIT));
	if((link) && (yt8521_fiber_latch_val != yt8521_fiber_curr_val))
	{
		link = 0;
		printk (KERN_INFO "yt8521_read_status, fiber link down detect,latch=%04x,curr=%04x\n", yt8521_fiber_latch_val,yt8521_fiber_curr_val);
	}
	
	if (link) {
		link_fiber = 1;
		yt8521_adjust_status(phydev, val, 0);
		link_mode_8521 = 32; //fiber mode


	} else {
		link_fiber = 0;
	}
#endif //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP)

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

#if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER)
	if (link_utp) {
		ytphy_write_ext(phydev, 0xa000, 0);
	}
#endif

	//printk (KERN_INFO "yzhang..8521 read status call out,link=%d,linkmode=%d\n", phydev->link, link_mode_8521 );
	return 0;
}

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

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

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

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

	ytphy_write_ext(phydev, 0xa000, 0);

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

	return 0;
}

int yt8521_resume(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)				
	int value;
	int ret;
	
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
	mutex_lock(&phydev->lock);
#else
	/* no need lock/unlock in 4.19 */
#endif

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

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

	/* enable RXC clock when no wire plug */
	value = ytphy_read_ext(phydev, 0xc);
	if (value < 0)
		return value;
	value &= ~(1 << 12);
	ret = ytphy_write_ext(phydev, 0xc, value);
	if (ret < 0)
		return ret;

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

#if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER)
	ytphy_write_ext(phydev, 0xa000, 0);
#endif

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

	return 0;
}


#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#else
int yt8618_soft_reset(struct phy_device *phydev)
{
	int ret;

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

	return 0;
}

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

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

	/* qsgmii */
	ytphy_write_ext(phydev, 0xa000, 2);
	ret = genphy_soft_reset(phydev);
	if (ret < 0) {
		ytphy_write_ext(phydev, 0xa000, 0); //back to utp mode
		return ret;
	}

	/* sgmii */
	ytphy_write_ext(phydev, 0xa000, 3);
	ret = genphy_soft_reset(phydev);
	if (ret < 0) {
		ytphy_write_ext(phydev, 0xa000, 0); //back to utp mode
		return ret;
	}

	return 0;
}

#endif

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

	phydev->irq = PHY_POLL;

	if(0xff == yt_mport_base_phy_addr)
		/* by default, we think the first phy should be the base phy addr. for mul */
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
	{
		yt_mport_base_phy_addr = phydev->addr;
	}else if (yt_mport_base_phy_addr > phydev->addr) { 
		printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%d, cur=%d\n", yt_mport_base_phy_addr, phydev->addr);
	}
#else
	{
		yt_mport_base_phy_addr = phydev->mdio.addr;
	}else if (yt_mport_base_phy_addr > phydev->mdio.addr) { 
		printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%d, cur=%d\n", yt_mport_base_phy_addr, phydev->mdio.addr);
	}
#endif	

	ytphy_write_ext(phydev, 0xa000, 0);
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
	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 ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
	if((phydev->addr > yt_mport_base_phy_addr) && ((2 == phydev->addr - yt_mport_base_phy_addr) || (5 == phydev->addr - yt_mport_base_phy_addr)))
#else
	if((phydev->mdio.addr > yt_mport_base_phy_addr) && ((2 == phydev->mdio.addr - yt_mport_base_phy_addr) || (5 == phydev->mdio.addr - yt_mport_base_phy_addr)))
#endif
	{
		ret = ytphy_write_ext(phydev, 0x44, 0x2929);
		if (ret < 0)
			return ret;
	}

	val = phy_read(phydev, MII_BMCR);
	phy_write(phydev, MII_BMCR, val | BMCR_RESET);

	printk (KERN_INFO "yt8618_config_init call out.\n");
	return ret;
}

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

	phydev->irq = PHY_POLL;

	if(0xff == yt_mport_base_phy_addr_8614)
		/* by default, we think the first phy should be the base phy addr. for mul */
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
	{
		yt_mport_base_phy_addr_8614 = (unsigned int)phydev->addr;
	}else if (yt_mport_base_phy_addr_8614 > (unsigned int)phydev->addr) { 
		printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%u, cur=%d\n", yt_mport_base_phy_addr_8614, phydev->addr);
	}
#else
	{
		yt_mport_base_phy_addr_8614 = (unsigned int)phydev->mdio.addr;
	}else if (yt_mport_base_phy_addr_8614 > (unsigned int)phydev->mdio.addr) { 
		printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%u, cur=%d\n", yt_mport_base_phy_addr_8614, phydev->mdio.addr);
	}
#endif	
	return ret;
}

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#define yt8614_get_port_from_phydev(phydev) ((0xff == yt_mport_base_phy_addr_8614) && (yt_mport_base_phy_addr_8614 <= (phydev)->addr) ? 0 : (unsigned int)((phydev)->addr) - yt_mport_base_phy_addr_8614)
#else
#define yt8614_get_port_from_phydev(phydev) ((0xff == yt_mport_base_phy_addr_8614) && (yt_mport_base_phy_addr_8614 <= (phydev)->mdio.addr) ? 0 : (unsigned int)((phydev)->mdio.addr) - yt_mport_base_phy_addr_8614)
#endif

int yt8618_aneg_done (struct phy_device *phydev)
{

	return genphy_aneg_done(phydev);
}

int yt8614_aneg_done (struct phy_device *phydev)
{
	int port = yt8614_get_port_from_phydev(phydev);
	
	/*it should be used for 8614 fiber*/
	if((32 == link_mode_8614[port]) && (SPEED_100 == phydev->speed))
	{
		return 1;
	}

	return genphy_aneg_done(phydev);
}

static int yt8614_read_status(struct phy_device *phydev)
{
        //int i;
	int ret;
	volatile int val, yt8614_fiber_latch_val, yt8614_fiber_curr_val;
	volatile int link;
	int link_utp = 0, link_fiber = 0;
	int port = yt8614_get_port_from_phydev(phydev);

#if (YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER)
	/* 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(YT8521_LINK_STATUS_BIT));
	if (link) {
		link_utp = 1;
		// here is same as 8521 and re-use the function;
		yt8521_adjust_status(phydev, val, 1);  
	} else {
		link_utp = 0;
	}
#endif //(YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER)

#if (YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_UTP)
	/* 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;
	
	//printk (KERN_INFO "yzhang..8614 read fiber status=%04x,macbase=0x%08lx\n", val,(unsigned long)phydev->attached_dev);

	/* 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(YT8521_LINK_STATUS_BIT));
	if((link) && (yt8614_fiber_latch_val != yt8614_fiber_curr_val))
	{
		link = 0;
		printk (KERN_INFO "yt8614_read_status, fiber link down detect,latch=%04x,curr=%04x\n", yt8614_fiber_latch_val,yt8614_fiber_curr_val);
	}
	
	if (link) {
		link_fiber = 1;
		yt8521_adjust_status(phydev, val, 0);
		link_mode_8614[port] = 32; //fiber mode


	} else {
		link_fiber = 0;
	}
#endif //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP)

	if (link_utp || link_fiber) {
		phydev->link = 1;
	} else {
		phydev->link = 0;
		link_mode_8614[port] = 0;
	}

#if (YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER)
	if (link_utp) {
		ytphy_write_ext(phydev, 0xa000, 0);
	}
#endif
	//printk (KERN_INFO "yt8614_read_status call out,link=%d,linkmode=%d\n", phydev->link, link_mode_8614[port] );

	return 0;
}

static int yt8618_read_status(struct phy_device *phydev)
{
	int ret;
	volatile int val; //maybe for 8614 yt8521_fiber_latch_val, yt8521_fiber_curr_val;
	volatile 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(YT8521_LINK_STATUS_BIT));
	if (link) {
		link_utp = 1;
		yt8521_adjust_status(phydev, val, 1);
	} else {
		link_utp = 0;
	}

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

	return 0;
}

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

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

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

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

	return 0;
}

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

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

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

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

	return 0;
}

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

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

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

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

	ytphy_write_ext(phydev, 0xa000, 0);

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

	return 0;
}

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

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

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

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

	ytphy_write_ext(phydev, 0xa000, 0);

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

	return 0;
}


static struct phy_driver ytphy_drvs[] = {
	{
		.phy_id         = PHY_ID_YT8010,
		.name           = "YT8010 Automotive Ethernet",
		.phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features       = PHY_BASIC_FEATURES,
		.flags          = PHY_HAS_INTERRUPT,
#endif		
		.config_aneg    = yt8010_config_aneg,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
		.config_init	= ytphy_config_init,
#else
		.config_init	= genphy_config_init,
#endif
		.read_status    = genphy_read_status,
	}, {
		.phy_id		= PHY_ID_YT8510,
		.name		= "YT8510 100/10Mb Ethernet",
		.phy_id_mask	= MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features	= PHY_BASIC_FEATURES,
		.flags			= PHY_HAS_INTERRUPT,
#endif		
		.config_aneg	= genphy_config_aneg,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
		.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,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features	= PHY_GBIT_FEATURES,
		.flags			= PHY_HAS_INTERRUPT,
#endif		
		.config_aneg	= genphy_config_aneg,
#if GMAC_CLOCK_INPUT_NEEDED
		.config_init	= yt8511_config_init,
#else
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
		.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 Ethernet",
		.phy_id_mask	= MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features	= PHY_BASIC_FEATURES,
		.flags			= PHY_HAS_INTERRUPT,
#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_YT8512B,
		.name		= "YT8512B Ethernet",
		.phy_id_mask	= MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features	= PHY_BASIC_FEATURES,
		.flags			= PHY_HAS_INTERRUPT,
#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_YT8521,
        .name           = "YT8521 Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
        .features       = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
#endif
        .flags          = PHY_POLL,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#else
		.soft_reset	= yt8521_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
#if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) )
        .aneg_done	= yt8521_aneg_done,
#endif
        .config_init    = yt8521_config_init,
        .read_status    = yt8521_read_status,
        .suspend        = yt8521_suspend,
        .resume         = yt8521_resume,
#if (YTPHY_ENABLE_WOL)
		.get_wol		= &ytphy_get_wol,
		.set_wol		= &ytphy_set_wol,
#endif                
        },{
		/* same as 8521 */
        .phy_id         = PHY_ID_YT8531S,
        .name           = "YT8531S Ethernet",
        .phy_id_mask    = MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
        .features       = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
#endif
        .flags          = PHY_POLL,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#else
		.soft_reset	= yt8521_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
#if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) )
        .aneg_done	= yt8521_aneg_done,
#endif
        .config_init    = yt8521_config_init,
        .read_status    = yt8521_read_status,
        .suspend        = yt8521_suspend,
        .resume         = yt8521_resume,
#if (YTPHY_ENABLE_WOL)
		.get_wol		= &ytphy_get_wol,
		.set_wol		= &ytphy_set_wol,
#endif                
        }, {
        /* same as 8511 */
		.phy_id		= PHY_ID_YT8531,
		.name		= "YT8531 Gigabit Ethernet",
		.phy_id_mask	= MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features	= PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
		.flags			= PHY_HAS_INTERRUPT,
#endif		
		.config_aneg	= genphy_config_aneg,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
		.config_init	= ytphy_config_init,
#else
		.config_init	= genphy_config_init,
#endif
		.read_status	= genphy_read_status,
		.suspend	= genphy_suspend,
		.resume		= genphy_resume,
#if (YTPHY_ENABLE_WOL)
		.get_wol		= &ytphy_get_wol,
		.set_wol		= &ytphy_set_wol,
#endif                
	}, {
        .phy_id         = PHY_ID_YT8618,
        .name           = "YT8618 Ethernet",
        .phy_id_mask    = MOTORCOMM_MPHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
        .features       = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
#endif
        .flags          = PHY_POLL,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#else
		.soft_reset	= yt8618_soft_reset,
#endif
        .config_aneg    = genphy_config_aneg,
#if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) )
        .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_MPHY_ID_MASK_8614,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
		.features		= PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
#endif
		.flags			= PHY_POLL,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
#else
		.soft_reset = yt8614_soft_reset,
#endif
		.config_aneg	= genphy_config_aneg,
#if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) )
		.aneg_done		= yt8614_aneg_done,
#endif
		.config_init	= yt8614_config_init,
		.read_status	= yt8614_read_status,
		.suspend		= yt8614_suspend,
		.resume 		= yt8614_resume,
		}, 
};

#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) )
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)
{
	printk("motorcomm phy register\n");
	return ytphy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}

static void __exit ytphy_exit(void)
{
	printk("motorcomm phy unregister\n");
	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_YT8512B, MOTORCOMM_PHY_ID_MASK },
	{ PHY_ID_YT8521, MOTORCOMM_PHY_ID_MASK },
	{ PHY_ID_YT8531S, MOTORCOMM_PHY_ID_8531_MASK },
	{ PHY_ID_YT8531, MOTORCOMM_PHY_ID_8531_MASK },
	{ PHY_ID_YT8618, MOTORCOMM_MPHY_ID_MASK },
	{ PHY_ID_YT8614, MOTORCOMM_MPHY_ID_MASK_8614 },
	{ }
};

MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);