T507 以太网---yt8531

一、硬件确认

1.1、phy硬件

二、驱动配置

2.1、驱动位置

linux-4.9/drivers/net/ethernet/allwinner

2.1.1 gmac驱动

路径 :linux-4.9/drivers/net/ethernet/allwinner/sunxi-gmac.h

复制代码
/*
 * linux/drivers/net/ethernet/allwinner/sunxi_gmac.h
 *
 * Copyright © 2016-2018, fuzhaoke
 *		Author: fuzhaoke <fuzhaoke@allwinnertech.com>
 *
 * This file is provided under a dual BSD/GPL license.  When using or
 * redistributing this file, you may do so under either license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 */
#ifndef __SUNXI_GETH_H__
#define __SUNXI_GETH_H__

#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/module.h>
#include <linux/init.h>

/* GETH_FRAME_FILTER  register value */
#define GETH_FRAME_FILTER_PR	0x00000001	/* Promiscuous Mode */
#define GETH_FRAME_FILTER_HUC	0x00000002	/* Hash Unicast */
#define GETH_FRAME_FILTER_HMC	0x00000004	/* Hash Multicast */
#define GETH_FRAME_FILTER_DAIF	0x00000008	/* DA Inverse Filtering */
#define GETH_FRAME_FILTER_PM	0x00000010	/* Pass all multicast */
#define GETH_FRAME_FILTER_DBF	0x00000020	/* Disable Broadcast frames */
#define GETH_FRAME_FILTER_SAIF	0x00000100	/* Inverse Filtering */
#define GETH_FRAME_FILTER_SAF	0x00000200	/* Source Address Filter */
#define GETH_FRAME_FILTER_HPF	0x00000400	/* Hash or perfect Filter */
#define GETH_FRAME_FILTER_RA	0x80000000	/* Receive all mode */

/* Default tx descriptor */
#define TX_SINGLE_DESC0		0x80000000
#define TX_SINGLE_DESC1		0x63000000

/* Default rx descriptor */
#define RX_SINGLE_DESC0		0x80000000
#define RX_SINGLE_DESC1		0x83000000

typedef union {
	struct {
		/* TDES0 */
		unsigned int deferred:1;	/* Deferred bit (only half-duplex) */
		unsigned int under_err:1;	/* Underflow error */
		unsigned int ex_deferral:1;	/* Excessive deferral */
		unsigned int coll_cnt:4;	/* Collision count */
		unsigned int vlan_tag:1;	/* VLAN Frame */
		unsigned int ex_coll:1;		/* Excessive collision */
		unsigned int late_coll:1;	/* Late collision */
		unsigned int no_carr:1;		/* No carrier */
		unsigned int loss_carr:1;	/* Loss of collision */
		unsigned int ipdat_err:1;	/* IP payload error */
		unsigned int frm_flu:1;		/* Frame flushed */
		unsigned int jab_timeout:1;	/* Jabber timeout */
		unsigned int err_sum:1;		/* Error summary */
		unsigned int iphead_err:1;	/* IP header error */
		unsigned int ttss:1;		/* Transmit time stamp status */
		unsigned int reserved0:13;
		unsigned int own:1;		/* Own bit. CPU:0, DMA:1 */
	} tx;

	/* bits 5 7 0 | Frame status
	 * ----------------------------------------------------------
	 *      0 0 0 | IEEE 802.3 Type frame (length < 1536 octects)
	 *      1 0 0 | IPv4/6 No CSUM errorS.
	 *      1 0 1 | IPv4/6 CSUM PAYLOAD error
	 *      1 1 0 | IPv4/6 CSUM IP HR error
	 *      1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS
	 *      0 0 1 | IPv4/6 unsupported IP PAYLOAD
	 *      0 1 1 | COE bypassed.. no IPv4/6 frame
	 *      0 1 0 | Reserved.
	 */
	struct {
		/* RDES0 */
		unsigned int chsum_err:1;	/* Payload checksum error */
		unsigned int crc_err:1;		/* CRC error */
		unsigned int dribbling:1;	/* Dribble bit error */
		unsigned int mii_err:1;		/* Received error (bit3) */
		unsigned int recv_wt:1;		/* Received watchdog timeout */
		unsigned int frm_type:1;	/* Frame type */
		unsigned int late_coll:1;	/* Late Collision */
		unsigned int ipch_err:1;	/* IPv header checksum error (bit7) */
		unsigned int last_desc:1;	/* Laset descriptor */
		unsigned int first_desc:1;	/* First descriptor */
		unsigned int vlan_tag:1;	/* VLAN Tag */
		unsigned int over_err:1;	/* Overflow error (bit11) */
		unsigned int len_err:1;		/* Length error */
		unsigned int sou_filter:1;	/* Source address filter fail */
		unsigned int desc_err:1;	/* Descriptor error */
		unsigned int err_sum:1;		/* Error summary (bit15) */
		unsigned int frm_len:14;	/* Frame length */
		unsigned int des_filter:1;	/* Destination address filter fail */
		unsigned int own:1;		/* Own bit. CPU:0, DMA:1 */
	#define RX_PKT_OK		0x7FFFB77C
	#define RX_LEN			0x3FFF0000
	} rx;

	unsigned int all;
} desc0_u;

typedef union {
	struct {
		/* TDES1 */
		unsigned int buf1_size:11;	/* Transmit buffer1 size */
		unsigned int buf2_size:11;	/* Transmit buffer2 size */
		unsigned int ttse:1;		/* Transmit time stamp enable */
		unsigned int dis_pad:1;		/* Disable pad (bit23) */
		unsigned int adr_chain:1;	/* Second address chained */
		unsigned int end_ring:1;	/* Transmit end of ring */
		unsigned int crc_dis:1;		/* Disable CRC */
		unsigned int cic:2;		/* Checksum insertion control (bit27:28) */
		unsigned int first_sg:1;	/* First Segment */
		unsigned int last_seg:1;	/* Last Segment */
		unsigned int interrupt:1;	/* Interrupt on completion */
	} tx;

	struct {
		/* RDES1 */
		unsigned int buf1_size:11;	/* Received buffer1 size */
		unsigned int buf2_size:11;	/* Received buffer2 size */
		unsigned int reserved1:2;
		unsigned int adr_chain:1;	/* Second address chained */
		unsigned int end_ring:1;		/* Received end of ring */
		unsigned int reserved2:5;
		unsigned int dis_ic:1;		/* Disable interrupt on completion */
	} rx;

	unsigned int all;
} desc1_u;

typedef struct dma_desc {
	desc0_u desc0;
	desc1_u desc1;
	/* The address of buffers */
	unsigned int	desc2;
	/* Next desc's address */
	unsigned int	desc3;
} __attribute__((packed)) dma_desc_t;

enum rx_frame_status { /* IPC status */
	good_frame = 0,
	discard_frame = 1,
	csum_none = 2,
	llc_snap = 4,
};

enum tx_dma_irq_status {
	tx_hard_error = 1,
	tx_hard_error_bump_tc = 2,
	handle_tx_rx = 3,
};

struct geth_extra_stats {
	/* Transmit errors */
	unsigned long tx_underflow;
	unsigned long tx_carrier;
	unsigned long tx_losscarrier;
	unsigned long vlan_tag;
	unsigned long tx_deferred;
	unsigned long tx_vlan;
	unsigned long tx_jabber;
	unsigned long tx_frame_flushed;
	unsigned long tx_payload_error;
	unsigned long tx_ip_header_error;

	/* Receive errors */
	unsigned long rx_desc;
	unsigned long sa_filter_fail;
	unsigned long overflow_error;
	unsigned long ipc_csum_error;
	unsigned long rx_collision;
	unsigned long rx_crc;
	unsigned long dribbling_bit;
	unsigned long rx_length;
	unsigned long rx_mii;
	unsigned long rx_multicast;
	unsigned long rx_gmac_overflow;
	unsigned long rx_watchdog;
	unsigned long da_rx_filter_fail;
	unsigned long sa_rx_filter_fail;
	unsigned long rx_missed_cntr;
	unsigned long rx_overflow_cntr;
	unsigned long rx_vlan;

	/* Tx/Rx IRQ errors */
	unsigned long tx_undeflow_irq;
	unsigned long tx_process_stopped_irq;
	unsigned long tx_jabber_irq;
	unsigned long rx_overflow_irq;
	unsigned long rx_buf_unav_irq;
	unsigned long rx_process_stopped_irq;
	unsigned long rx_watchdog_irq;
	unsigned long tx_early_irq;
	unsigned long fatal_bus_error_irq;

	/* Extra info */
	unsigned long threshold;
	unsigned long tx_pkt_n;
	unsigned long rx_pkt_n;
	unsigned long poll_n;
	unsigned long sched_timer_n;
	unsigned long normal_irq_n;
};

int sunxi_mdio_read(void *,  int, int);
int sunxi_mdio_write(void *, int, int, unsigned short);
int sunxi_mdio_reset(void *);
void sunxi_set_link_mode(void *iobase, int duplex, int speed);
void sunxi_int_disable(void *);
int sunxi_int_status(void *, struct geth_extra_stats *x);
int sunxi_mac_init(void *, int txmode, int rxmode);
void sunxi_set_umac(void *, unsigned char *, int);
void sunxi_mac_enable(void *);
void sunxi_mac_disable(void *);
void sunxi_tx_poll(void *);
void sunxi_int_enable(void *);
void sunxi_start_rx(void *, unsigned long);
void sunxi_start_tx(void *, unsigned long);
void sunxi_stop_tx(void *);
void sunxi_stop_rx(void *);
void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high);
void sunxi_set_filter(void *iobase, unsigned long flags);
void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause);
void sunxi_mac_loopback(void *iobase, int enable);

void desc_buf_set(struct dma_desc *p, unsigned long paddr, int size);
void desc_set_own(struct dma_desc *p);
void desc_init_chain(struct dma_desc *p, unsigned long paddr,  unsigned int size);
void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert);
void desc_init(struct dma_desc *p);
int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x);
int desc_buf_get_len(struct dma_desc *desc);
int desc_buf_get_addr(struct dma_desc *desc);
int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x);
int desc_get_own(struct dma_desc *desc);
int desc_get_tx_ls(struct dma_desc *desc);
int desc_rx_frame_len(struct dma_desc *desc);

int sunxi_mac_reset(void *iobase, void (*mdelay)(int), int n);
int sunxi_geth_register(void *iobase, int version, unsigned int div);

#if defined(CONFIG_SUNXI_EPHY)
extern int ephy_is_enable(void);
#endif

#if defined(CONFIG_ARCH_SUN8IW3) \
	|| defined(CONFIG_ARCH_SUN9IW1) \
	|| defined(CONFIG_ARCH_SUN7I)
#define HW_VERSION	0
#else
#define HW_VERSION	1
#endif

#endif

路径:linux-4.9/drivers/net/ethernet/allwinner/sunxi-gmac.c

复制代码
/*
 * linux/drivers/net/ethernet/allwinner/sunxi_gmac.c
 *
 * Copyright © 2016-2018, fuzhaoke
 *		Author: fuzhaoke <fuzhaoke@allwinnertech.com>
 *
 * This file is provided under a dual BSD/GPL license.  When using or
 * redistributing this file, you may do so under either license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 */
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/mii.h>
#include <linux/gpio.h>
#include <linux/crc32.h>
#include <linux/skbuff.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <linux/err.h>
#include <linux/scatterlist.h>
#include <linux/regulator/consumer.h>
#include <linux/of_net.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include <linux/sunxi-sid.h>
#include <linux/sunxi-gpio.h>
#include "sunxi-gmac.h"

#define DMA_DESC_RX	256
#define DMA_DESC_TX	256
#define BUDGET		(dma_desc_rx / 4)
#define TX_THRESH	(dma_desc_tx / 4)

#define HASH_TABLE_SIZE	64
#define MAX_BUF_SZ	(SZ_2K - 1)

#define POWER_CHAN_NUM	3

#undef PKT_DEBUG
#undef DESC_PRINT

#define circ_cnt(head, tail, size) (((head) > (tail)) ? \
					((head) - (tail)) : \
					((head) - (tail)) & ((size) - 1))

#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size))

#define circ_inc(n, s) (((n) + 1) % (s))

#define GETH_MAC_ADDRESS "00:00:00:00:00:00"
static char *mac_str = GETH_MAC_ADDRESS;
module_param(mac_str, charp, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)");

static int rxmode = 1;
module_param(rxmode, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(rxmode, "DMA threshold control value");

static int txmode = 1;
module_param(txmode, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(txmode, "DMA threshold control value");

static int pause = 0x400;
module_param(pause, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(pause, "Flow Control Pause Time");

#define TX_TIMEO	5000
static int watchdog = TX_TIMEO;
module_param(watchdog, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds");

static int dma_desc_rx = DMA_DESC_RX;
module_param(dma_desc_rx, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(watchdog, "The number of receive's descriptors");

static int dma_desc_tx = DMA_DESC_TX;
module_param(dma_desc_tx, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(watchdog, "The number of transmit's descriptors");

/* - 0: Flow Off
 * - 1: Rx Flow
 * - 2: Tx Flow
 * - 3: Rx & Tx Flow
 */
static int flow_ctrl;
module_param(flow_ctrl, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]");

static unsigned long tx_delay;
module_param(tx_delay, ulong, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(tx_delay, "Adjust transmit clock delay, value: 0~7");

static unsigned long rx_delay;
module_param(rx_delay, ulong, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(rx_delay, "Adjust receive clock delay, value: 0~31");

/* whether using ephy_clk */
static int g_use_ephy_clk;
static int g_phy_addr;

struct geth_priv {
	struct dma_desc *dma_tx;
	struct sk_buff **tx_sk;
	unsigned int tx_clean;
	unsigned int tx_dirty;
	dma_addr_t dma_tx_phy;

	unsigned long buf_sz;

	struct dma_desc *dma_rx;
	struct sk_buff **rx_sk;
	unsigned int rx_clean;
	unsigned int rx_dirty;
	dma_addr_t dma_rx_phy;

	struct net_device *ndev;
	struct device *dev;
	struct napi_struct napi;

	struct geth_extra_stats xstats;

	struct mii_bus *mii;
	int link;
	int speed;
	int duplex;
#define INT_PHY 0
#define EXT_PHY 1
	int phy_ext;
	int phy_interface;

	void __iomem *base;
	void __iomem *base_phy;
	struct clk *geth_clk;
	struct clk *ephy_clk;
	struct pinctrl *pinctrl;

	struct regulator *gmac_power[POWER_CHAN_NUM];
	bool is_suspend;
	int phyrst;
	u8  rst_active_low;
	/* definition spinlock */
	spinlock_t lock;
	spinlock_t tx_lock;

	/* resume work */
	struct work_struct eth_work;
	struct regulator *avcc;
};

static u64 geth_dma_mask = DMA_BIT_MASK(32);

void sunxi_udelay(int n)
{
	udelay(n);
}

static int geth_stop(struct net_device *ndev);
static int geth_open(struct net_device *ndev);
static void geth_tx_complete(struct geth_priv *priv);
static void geth_rx_refill(struct net_device *ndev);

#ifdef CONFIG_GETH_ATTRS
static ssize_t adjust_bgs_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	int value = 0;
	u32 efuse_value;
	struct net_device *ndev = to_net_dev(dev);
	struct geth_priv *priv = netdev_priv(ndev);

	if (priv->phy_ext == INT_PHY) {
		value = readl(priv->base_phy) >> 28;
		if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
			pr_err("get PHY efuse fail!\n");
		else
#if defined(CONFIG_ARCH_SUN50IW2)
			value = value - ((efuse_value >> 24) & 0x0F);
#else
			pr_warn("miss config come from efuse!\n");
#endif
	}

	return sprintf(buf, "bgs: %d\n", value);
}

static ssize_t adjust_bgs_write(struct device *dev, struct device_attribute *attr,
				const char *buf, size_t count)
{
	unsigned int out = 0;
	struct net_device *ndev = to_net_dev(dev);
	struct geth_priv *priv = netdev_priv(ndev);
	u32 clk_value = readl(priv->base_phy);
	u32 efuse_value;

	out = simple_strtoul(buf, NULL, 10);

	if (priv->phy_ext == INT_PHY) {
		clk_value &= ~(0xF << 28);
		if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
			pr_err("get PHY efuse fail!\n");
		else
#if defined(CONFIG_ARCH_SUN50IW2)
			clk_value |= (((efuse_value >> 24) & 0x0F) + out) << 28;
#else
			pr_warn("miss config come from efuse!\n");
#endif
	}

	writel(clk_value, priv->base_phy);

	return count;
}

static struct device_attribute adjust_reg[] = {
	__ATTR(adjust_bgs, 0664, adjust_bgs_show, adjust_bgs_write),
};

static int geth_create_attrs(struct net_device *ndev)
{
	int j, ret;

	for (j = 0; j < ARRAY_SIZE(adjust_reg); j++) {
		ret = device_create_file(&ndev->dev, &adjust_reg[j]);
		if (ret)
			goto sysfs_failed;
	}
	goto succeed;

sysfs_failed:
	while (j--)
		device_remove_file(&ndev->dev, &adjust_reg[j]);
succeed:
	return ret;
}
#endif

#ifdef DEBUG
static void desc_print(struct dma_desc *desc, int size)
{
#ifdef DESC_PRINT
	int i;

	for (i = 0; i < size; i++) {
		u32 *x = (u32 *)(desc + i);

		pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n",
			i, (unsigned long)(&desc[i]),
			x[0], x[1], x[2], x[3]);
	}
	pr_info("\n");
#endif
}
#endif

static ssize_t gphy_test_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct net_device *ndev = dev_get_drvdata(dev);

	if (!dev) {
		pr_err("Argment is invalid\n");
		return 0;
	}

	if (!ndev) {
		pr_err("Net device is null\n");
		return 0;
	}

	return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n"
			"0 - Normal Mode\n"
			"1 - Transmit Jitter Test\n"
			"2 - Transmit Jitter Test(MASTER mode)\n"
			"3 - Transmit Jitter Test(SLAVE mode)\n"
			"4 - Transmit Distortion Test\n\n");
}

static ssize_t gphy_test_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct net_device *ndev = dev_get_drvdata(dev);
	struct geth_priv *priv = netdev_priv(ndev);
	u16 value = 0;
	int ret = 0;
	u16 data = 0;

	if (!dev) {
		pr_err("Argument is invalid\n");
		return count;
	}

	if (!ndev) {
		pr_err("Net device is null\n");
		return count;
	}

	data = sunxi_mdio_read(priv->base, g_phy_addr, MII_CTRL1000);

	ret = kstrtou16(buf, 0, &value);
	if (ret)
		return ret;

	if (value >= 0 && value <= 4) {
		data &= ~(0x7 << 13);
		data |= value << 13;
		sunxi_mdio_write(priv->base, g_phy_addr, MII_CTRL1000, data);
		pr_info("Set MII_CTRL1000(0x09) Reg: 0x%x\n", data);
	} else {
		pr_info("unknown value (%d)\n", value);
	}

	return count;
}

static DEVICE_ATTR(gphy_test, 0664, gphy_test_show, gphy_test_store);

static int geth_power_on(struct geth_priv *priv)
{
	int value;
	int i;

	value = readl(priv->base_phy);
	if (priv->phy_ext == INT_PHY) {
		value |= (1 << 15);
		value &= ~(1 << 16);
		value |= (3 << 17);
		printk("INT_PHY, power up\n");
	} else {
		printk("EXT_PHY, regulator up,bit 16 18 set\n");
		value &= ~(1 << 15);
		/*
		value &= ~(1 << 16);
		value &= ~(3 << 17);
		value |= (1 << 17);
		*/
		for (i = 0; i < POWER_CHAN_NUM; i++) {
			if (IS_ERR_OR_NULL(priv->gmac_power[i]))
				continue;
			if (regulator_enable(priv->gmac_power[i]) != 0) {
				pr_err("gmac-power%d enable error\n", i);
				return -EINVAL;
			}
		}
	}

	writel(value, priv->base_phy);

	return 0;
}

static void geth_power_off(struct geth_priv *priv)
{
	int value;
	int i;

	if (priv->phy_ext == INT_PHY) {
		value = readl(priv->base_phy);
		value |= (1 << 16);
		writel(value, priv->base_phy);
	} else {
		for (i = 0; i < POWER_CHAN_NUM; i++) {
			if (IS_ERR_OR_NULL(priv->gmac_power[i]))
				continue;
			regulator_disable(priv->gmac_power[i]);
		}
	}
}

/* PHY interface operations */
static int geth_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
{
	struct net_device *ndev = bus->priv;
	struct geth_priv *priv = netdev_priv(ndev);

	return (int)sunxi_mdio_read(priv->base,  phyaddr, phyreg);
}

static int geth_mdio_write(struct mii_bus *bus, int phyaddr,
			   int phyreg, u16 data)
{
	struct net_device *ndev = bus->priv;
	struct geth_priv *priv = netdev_priv(ndev);

	sunxi_mdio_write(priv->base, phyaddr, phyreg, data);

	return 0;
}

static int geth_mdio_reset(struct mii_bus *bus)
{
	struct net_device *ndev = bus->priv;
	struct geth_priv *priv = netdev_priv(ndev);

	return sunxi_mdio_reset(priv->base);
}

static void geth_adjust_link(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct phy_device *phydev = ndev->phydev;
	unsigned long flags;
	int new_state = 0;

	if (!phydev)
		return;
	printk("geth_adjust_link link %d, phydev->speeds %d , priv->speed %d\n", phydev->link, phydev->speed, priv->speed);
	spin_lock_irqsave(&priv->lock, flags);
	if (phydev->link) {
		/* Now we make sure that we can be in full duplex mode.
		 * If not, we operate in half-duplex mode.
		 */
		if (phydev->duplex != priv->duplex) {
			new_state = 1;
			priv->duplex = phydev->duplex;
		}
		/* Flow Control operation */
		if (phydev->pause)
			sunxi_flow_ctrl(priv->base, phydev->duplex,
					flow_ctrl, pause);

		printk("geth_adjust_link phydev->speeds %d , %d\n", phydev->speed, priv->speed);
		if (phydev->speed != priv->speed) {
			new_state = 1;
			priv->speed = phydev->speed;
		}

		if (priv->link == 0) {
			new_state = 1;
			priv->link = phydev->link;
		}

		if (new_state){
			printk("sunxi_set_link_mode  %p , %d, %d\n", priv->base, priv->duplex, priv->speed);
			sunxi_set_link_mode(priv->base, priv->duplex, priv->speed);
		}

#ifdef LOOPBACK_DEBUG
		phydev->state = PHY_FORCING;
#endif

	} else if (priv->link != phydev->link) {
		new_state = 1;
		priv->link = 0;
		priv->speed = 0;
		priv->duplex = -1;
	}

	if (new_state)
		phy_print_status(phydev);

	spin_unlock_irqrestore(&priv->lock, flags);
}

#define AT803X_REG_CHIP_CONFIG			0x1f
#define AT803X_BT_BX_REG_SEL			0x8000
#define AT803X_RG_AUDO_MDET				0x800B
#define AT803X_BX1000_RGMII_75			0x0003
#define AT803X_CONFIG_AUTO_NEGOTIATION		BIT(12)


static int at803x_mode_config(struct phy_device *phydev){

	int ccr;

	phy_write(phydev, AT803X_REG_CHIP_CONFIG,  AT803X_BT_BX_REG_SEL);

	ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
	printk("1120 at803x_parse_dt AT803X_REG_CHIP_CONFIG 0x%x\n", ccr);	 
	return 0;

}


static int ar8031_fix_up(struct phy_device *phydev)
{
	unsigned short val;

	printk("ar8031 0528 2126 fix up, 125MHz\n");

	/* Ar8031 phy SmartEEE feature cause link status generates glitch,
	 * which cause ethernet link down/up issue, so disable SmartEEE
	*/
	phy_write(phydev, 0xd, 0x3);
	phy_write(phydev, 0xe, 0x805d);
	phy_write(phydev, 0xd, 0x4003);
	val = phy_read(phydev, 0xe);
	val &= ~(0x1 << 8);
	phy_write(phydev, 0xe, val);
	printk("ar8031 0x805d 0x%x\n", val);
#if 1
	/* To enable AR8031 ouput a 125MHz clk from CLK_25M */
	phy_write(phydev, 0xd, 0x7);
	phy_write(phydev, 0xe, 0x8016);
	phy_write(phydev, 0xd, 0x4007);
	val = phy_read(phydev, 0xe);
	printk("ar8031 0x8016 default 0x%x\n", val);
	val &= 0xffe3;
	val |= 0x18;
/*
#define AT803X_CLK_OUT_STRENGTH_MASK		GENMASK(8, 7) //bit8 bit7
#define AT803X_CLK_OUT_STRENGTH_FULL		0
#define AT803X_CLK_OUT_STRENGTH_HALF		1
#define AT803X_CLK_OUT_STRENGTH_QUARTER		2


	val &= 0xFE7F;
*/

	
	phy_write(phydev, 0xe, val);
	printk("ar8031 0x8016 0x%x\n", val);

	/* Introduce tx clock delay */
	/*phy_write(phydev, 0x1d, 0x5);
	val = phy_read(phydev, 0x1e);
	val |= 0x0100;
	phy_write(phydev, 0x1e, val);*/

	#define AT803X_DEBUG_REG_1F			0x1F
	#define AT803X_DEBUG_PLL_ON			BIT(2)
	#define AT803X_DEBUG_RGMII_1V8			BIT(3)


	#define AT803X_DEBUG_ADDR			0x1D
	#define AT803X_DEBUG_DATA			0x1E
	
	phy_write(phydev, 0x1d, 0x1F);
	val = phy_read(phydev, 0x1e);
	val |= AT803X_DEBUG_PLL_ON ;
	phy_write(phydev, 0x1e, val);
#endif	
	/*check phy power*/
	//val = phy_read(phydev, 0x0);
	//if (val & BMCR_PDOWN)
		//phy_write(phydev, 0x0, (val & ~BMCR_PDOWN));

	at803x_mode_config(phydev);

	return 0;
}




static int geth_phy_init(struct net_device *ndev)
{
	int value;
	struct mii_bus *new_bus;
	struct geth_priv *priv = netdev_priv(ndev);
	struct phy_device *phydev = ndev->phydev;

	printk("geth_phy_init 1\n");

	if (priv->is_suspend && phydev)
		goto resume;

	/* Fixup the phy interface type */
	if (priv->phy_ext == INT_PHY) {
		priv->phy_interface = PHY_INTERFACE_MODE_MII;
	} else {
		/* If config gpio to reset the phy device, we should reset it */
		if (gpio_is_valid(priv->phyrst)) {
			gpio_direction_output(priv->phyrst,
					priv->rst_active_low);
			msleep(50);
			gpio_direction_output(priv->phyrst,
					!priv->rst_active_low);
			msleep(50);
		}
	}

	new_bus = mdiobus_alloc();
	if (!new_bus) {
		netdev_err(ndev, "Failed to alloc new mdio bus\n");
		return -ENOMEM;
	}

	new_bus->name = dev_name(priv->dev);
	new_bus->read = &geth_mdio_read;
	new_bus->write = &geth_mdio_write;
	new_bus->reset = &geth_mdio_reset;
	snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, 0);

	new_bus->parent = priv->dev;
	new_bus->priv = ndev;

	if (mdiobus_register(new_bus)) {
		pr_err("%s: Cannot register as MDIO bus\n", new_bus->name);
		goto reg_fail;
	}

	priv->mii = new_bus;

	{
		int addr;

		for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
			struct phy_device *phydev_tmp = mdiobus_get_phy(new_bus, addr);

			if (phydev_tmp && (phydev_tmp->phy_id != 0x00)) {
				phydev = phydev_tmp;
				g_phy_addr = addr;
				break;
			}
		}
	}

	if (!phydev) {
		netdev_err(ndev, "No PHY found!\n");
		goto err;
	}else{
		netdev_err(ndev, "PHY found!\n");
	}


	phy_write(phydev, MII_BMCR, BMCR_RESET);
	while (BMCR_RESET & phy_read(phydev, MII_BMCR))
		msleep(30);

	netdev_err(ndev, "MII_BMCR poweron , MII_BMCR 0x%x\n", phy_read(phydev, MII_BMCR));

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

	netdev_err(ndev, "1534 MII_BMCR 0x%x\n", phy_read(phydev, MII_BMCR));
	phydev->irq = PHY_POLL;

	//ar8031_fix_up(phydev);
	netdev_err(ndev, "~BMCR_PDOWN 1411\n");

	value = phy_connect_direct(ndev, phydev, &geth_adjust_link, priv->phy_interface);
	if (value) {
		netdev_err(ndev, "Could not attach to PHY\n");
		goto err;
	} else {
		netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n",
			    ndev->name, phydev->interface, phydev->phy_id,
			    phydev->mdio.addr, "poll", dev_name(&phydev->mdio.dev));
	}
	netdev_err(ndev, "phy_connect_direct\n");
	phydev->supported &= PHY_GBIT_FEATURES;
	phydev->advertising = phydev->supported;

resume:
	if (priv->phy_ext == INT_PHY) {
		/* EPHY Initial */
		phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
		phy_write(phydev, 0x12, 0x4824); /* Disable APS */
		phy_write(phydev, 0x1f, 0x0200); /* switchto page 2 */
		phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
		phy_write(phydev, 0x1f, 0x0600); /* switchto page 6 */
		phy_write(phydev, 0x14, 0x708F); /* PHYAFE TX optimization */
		phy_write(phydev, 0x19, 0x0000);
		phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */
		phy_write(phydev, 0x15, 0x1530);
		phy_write(phydev, 0x1f, 0x0800); /* switch to page 8 */
		phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
		phy_write(phydev, 0x1f, 0x0100); /* switchto page 1 */
		/* reg 0x17 bit3,set 0 to disable iEEE */
		phy_write(phydev, 0x17, phy_read(phydev, 0x17) & (~(1<<3)));
		phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
	}
	if (priv->is_suspend)
		phy_init_hw(phydev);

	return 0;

err:
	mdiobus_unregister(new_bus);
reg_fail:
	mdiobus_free(new_bus);

	return -EINVAL;
}

static int geth_phy_release(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct phy_device *phydev = ndev->phydev;
	int value = 0;

	/* Stop and disconnect the PHY */
	if (phydev)
		phy_stop(phydev);

	priv->link = PHY_DOWN;
	priv->speed = 0;
	priv->duplex = -1;

	if (priv->is_suspend)
		return 0;

	if (phydev) {
		value = phy_read(phydev, MII_BMCR);
		phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));
		phy_disconnect(phydev);
		ndev->phydev = NULL;
	}

	if (priv->mii) {
		mdiobus_unregister(priv->mii);
		priv->mii->priv = NULL;
		mdiobus_free(priv->mii);
		priv->mii = NULL;
	}

	return 0;
}

static void geth_rx_refill(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct dma_desc *desc;
	struct sk_buff *sk = NULL;
	dma_addr_t paddr;

	while (circ_space(priv->rx_clean, priv->rx_dirty, dma_desc_rx) > 0) {
		int entry = priv->rx_clean;

		/* Find the dirty's desc and clean it */
		desc = priv->dma_rx + entry;

		if (priv->rx_sk[entry] == NULL) {
			sk = netdev_alloc_skb_ip_align(ndev, priv->buf_sz);

			if (unlikely(sk == NULL))
				break;

			priv->rx_sk[entry] = sk;
			paddr = dma_map_single(priv->dev, sk->data,
					       priv->buf_sz, DMA_FROM_DEVICE);
			desc_buf_set(desc, paddr, priv->buf_sz);
		}

		/* sync memery */
		wmb();
		desc_set_own(desc);
		priv->rx_clean = circ_inc(priv->rx_clean, dma_desc_rx);
	}
}

/* geth_dma_desc_init - initialize the RX/TX descriptor list
 * @ndev: net device structure
 * Description: initialize the list for dma.
 */
static int geth_dma_desc_init(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);
	unsigned int buf_sz;

	priv->rx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_rx,
				GFP_KERNEL);
	if (!priv->rx_sk)
		return -ENOMEM;

	priv->tx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_tx,
				GFP_KERNEL);
	if (!priv->tx_sk)
		goto tx_sk_err;

	/* Set the size of buffer depend on the MTU & max buf size */
	buf_sz = MAX_BUF_SZ;

	priv->dma_tx = dma_alloc_coherent(priv->dev,
					dma_desc_tx *
					sizeof(struct dma_desc),
					&priv->dma_tx_phy,
					GFP_KERNEL);
	if (!priv->dma_tx)
		goto dma_tx_err;

	priv->dma_rx = dma_alloc_coherent(priv->dev,
					dma_desc_rx *
					sizeof(struct dma_desc),
					&priv->dma_rx_phy,
					GFP_KERNEL);
	if (!priv->dma_rx)
		goto dma_rx_err;

	priv->buf_sz = buf_sz;

	return 0;

dma_rx_err:
	dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
			  priv->dma_tx, priv->dma_tx_phy);
dma_tx_err:
	kfree(priv->tx_sk);
tx_sk_err:
	kfree(priv->rx_sk);

	return -ENOMEM;
}

static void geth_free_rx_sk(struct geth_priv *priv)
{
	int i;

	for (i = 0; i < dma_desc_rx; i++) {
		if (priv->rx_sk[i] != NULL) {
			struct dma_desc *desc = priv->dma_rx + i;

			dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
					 desc_buf_get_len(desc),
					 DMA_FROM_DEVICE);
			dev_kfree_skb_any(priv->rx_sk[i]);
			priv->rx_sk[i] = NULL;
		}
	}
}

static void geth_free_tx_sk(struct geth_priv *priv)
{
	int i;

	for (i = 0; i < dma_desc_tx; i++) {
		if (priv->tx_sk[i] != NULL) {
			struct dma_desc *desc = priv->dma_tx + i;

			if (desc_buf_get_addr(desc))
				dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
						 desc_buf_get_len(desc),
						 DMA_TO_DEVICE);
			dev_kfree_skb_any(priv->tx_sk[i]);
			priv->tx_sk[i] = NULL;
		}
	}
}

static void geth_free_dma_desc(struct geth_priv *priv)
{
	/* Free the region of consistent memory previously allocated for the DMA */
	dma_free_coherent(priv->dev, dma_desc_tx * sizeof(struct dma_desc),
			  priv->dma_tx, priv->dma_tx_phy);
	dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
			  priv->dma_rx, priv->dma_rx_phy);

	kfree(priv->rx_sk);
	kfree(priv->tx_sk);
}

#ifdef CONFIG_PM
static int geth_select_gpio_state(struct pinctrl *pctrl, char *name)
{
	int ret = 0;
	struct pinctrl_state *pctrl_state = NULL;

	pctrl_state = pinctrl_lookup_state(pctrl, name);
	if (IS_ERR(pctrl_state)) {
		pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n",
						name, pctrl_state);
		return -EINVAL;
	}

	ret = pinctrl_select_state(pctrl, pctrl_state);
	if (ret < 0)
		pr_err("gmac pinctrl_select_state(%s) failed! return %d\n",
						name, ret);

	return ret;
}

static int geth_suspend(struct device *dev)
{
	struct net_device *ndev = dev_get_drvdata(dev);
	struct geth_priv *priv = netdev_priv(ndev);

	cancel_work_sync(&priv->eth_work);

	if (!ndev || !netif_running(ndev))
		return 0;

	priv->is_suspend = true;

	spin_lock(&priv->lock);
	netif_device_detach(ndev);
	spin_unlock(&priv->lock);

	geth_stop(ndev);

	if (priv->phy_ext == EXT_PHY)
		geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP);

	return 0;
}

static void geth_resume_work(struct work_struct *work)
{
	struct geth_priv *priv = container_of(work, struct geth_priv, eth_work);
	struct net_device *ndev = priv->ndev;
	int ret = 0;

	if (!netif_running(ndev))
		return;

	if (priv->phy_ext == EXT_PHY)
		geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT);

	spin_lock(&priv->lock);
	netif_device_attach(ndev);
	spin_unlock(&priv->lock);

#if defined(CONFIG_SUNXI_EPHY)
	if (!ephy_is_enable()) {
		pr_info("[geth_resume] ephy is not enable, waiting...\n");
		msleep(2000);
		if (!ephy_is_enable()) {
			netdev_err(ndev, "Wait for ephy resume timeout.\n");
			return;
		}
	}
#endif

	ret = geth_open(ndev);
	if (!ret)
		priv->is_suspend = false;
}

static void geth_resume(struct device *dev)
{
	struct net_device *ndev = dev_get_drvdata(dev);
	struct geth_priv *priv = netdev_priv(ndev);

	schedule_work(&priv->eth_work);
}

static int geth_freeze(struct device *dev)
{
	return 0;
}

static int geth_restore(struct device *dev)
{
	return 0;
}

static const struct dev_pm_ops geth_pm_ops = {
	.complete = geth_resume,
	.prepare = geth_suspend,
	.suspend = NULL,
	.resume = NULL,
	.freeze = geth_freeze,
	.restore = geth_restore,
};
#else
static const struct dev_pm_ops geth_pm_ops;
#endif /* CONFIG_PM */

#define sunxi_get_soc_chipid(x) {}
static void geth_chip_hwaddr(u8 *addr)
{
#define MD5_SIZE	16
#define CHIP_SIZE	16

	struct crypto_ahash *tfm;
	struct ahash_request *req;
	struct scatterlist sg;
	u8 result[MD5_SIZE];
	u8 chipid[CHIP_SIZE];
	int i = 0;
	int ret = -1;

	memset(chipid, 0, sizeof(chipid));
	memset(result, 0, sizeof(result));

	sunxi_get_soc_chipid((u8 *)chipid);

	tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC);
	if (IS_ERR(tfm)) {
		pr_err("Failed to alloc md5\n");
		return;
	}

	req = ahash_request_alloc(tfm, GFP_KERNEL);
	if (!req)
		goto out;

	ahash_request_set_callback(req, 0, NULL, NULL);

	ret = crypto_ahash_init(req);
	if (ret) {
		pr_err("crypto_ahash_init() failed\n");
		goto out;
	}

	sg_init_one(&sg, chipid, sizeof(chipid) - 1);
	ahash_request_set_crypt(req, &sg, result, sizeof(chipid) - 1);
	ret = crypto_ahash_update(req);
	if (ret) {
		pr_err("crypto_ahash_update() failed for id\n");
		goto out;
	}

	ret = crypto_ahash_final(req);
	if (ret) {
		pr_err("crypto_ahash_final() failed for result\n");
		goto out;
	}

	ahash_request_free(req);

	/* Choose md5 result's [0][2][4][6][8][10] byte as mac address */
	for (i = 0; i < ETH_ALEN; i++)
		addr[i] = result[2 * i];
	addr[0] &= 0xfe; /* clear multicast bit */
	addr[0] |= 0x02; /* set local assignment bit (IEEE802) */

out:
	crypto_free_ahash(tfm);
}

static void geth_check_addr(struct net_device *ndev, unsigned char *mac)
{
	int i;
	char *p = mac;

	if (!is_valid_ether_addr(ndev->dev_addr)) {
		for (i = 0; i < ETH_ALEN; i++, p++)
			ndev->dev_addr[i] = simple_strtoul(p, &p, 16);

		if (!is_valid_ether_addr(ndev->dev_addr))
			geth_chip_hwaddr(ndev->dev_addr);

		if (!is_valid_ether_addr(ndev->dev_addr)) {
			random_ether_addr(ndev->dev_addr);
			pr_warn("%s: Use random mac address\n", ndev->name);
		}
	}
}

static void geth_clk_enable(struct geth_priv *priv)
{
	int phy_interface = 0;
	u32 clk_value;
	u32 efuse_value;

	if (clk_prepare_enable(priv->geth_clk)){
		pr_err("try to enable geth_clk failed!\n");
	}else{
		printk("priv->geth_clk enable  rate %p\n", priv->geth_clk);
	}

	
	if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk)
			&& !IS_ERR_OR_NULL(priv->ephy_clk)) {
		if (clk_prepare_enable(priv->ephy_clk))
			pr_err("try to enable ephy_clk failed!\n");
	}

	phy_interface = priv->phy_interface;

	clk_value = readl(priv->base_phy);
	if (phy_interface == PHY_INTERFACE_MODE_RGMII)
		clk_value |= 0x00000004;
	else
		clk_value &= (~0x00000004);

	
	printk("phy_interface 0x%x\n",phy_interface);

	clk_value &= (~0x00002003);
	if (phy_interface == PHY_INTERFACE_MODE_RGMII
			|| phy_interface == PHY_INTERFACE_MODE_GMII){
			printk(" PHY_INTERFACE_MODE_RGMII CLK125M to RGMII\n");
		clk_value |= 0x00000002;
	}
	else if (phy_interface == PHY_INTERFACE_MODE_RMII){
		printk(" PHY_INTERFACE_MODE_RMII\n");
		clk_value |= 0x00002001;
	}

	if (priv->phy_ext == INT_PHY) {
		if (0 != sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value))
			pr_err("get PHY efuse fail!\n");
		else
#if defined(CONFIG_ARCH_SUN50IW2)
			clk_value |= (((efuse_value >> 24) & 0x0F) + 3) << 28;
#else
			pr_warn("miss config come from efuse!\n");
#endif
	}

	/* Adjust Tx/Rx clock delay */
	clk_value &= ~(0x07 << 10);
	clk_value |= ((tx_delay & 0x07) << 10);
	clk_value &= ~(0x1F << 5);
	clk_value |= ((rx_delay & 0x1F) << 5);

	writel(clk_value, priv->base_phy);
}

static void geth_clk_disable(struct geth_priv *priv)
{
	if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk)
			&& !IS_ERR_OR_NULL(priv->ephy_clk))
		clk_disable_unprepare(priv->ephy_clk);

	clk_disable_unprepare(priv->geth_clk);
}

static void geth_tx_err(struct geth_priv *priv)
{
	netif_stop_queue(priv->ndev);

	sunxi_stop_tx(priv->base);

	geth_free_tx_sk(priv);
	memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
	desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
	priv->tx_dirty = 0;
	priv->tx_clean = 0;
	sunxi_start_tx(priv->base, priv->dma_tx_phy);

	priv->ndev->stats.tx_errors++;
	netif_wake_queue(priv->ndev);
}

static inline void geth_schedule(struct geth_priv *priv)
{
	if (likely(napi_schedule_prep(&priv->napi))) {
		sunxi_int_disable(priv->base);
		__napi_schedule(&priv->napi);
	}
}

static irqreturn_t geth_interrupt(int irq, void *dev_id)
{
	struct net_device *ndev = (struct net_device *)dev_id;
	struct geth_priv *priv = netdev_priv(ndev);
	int status;

	if (unlikely(!ndev)) {
		pr_err("%s: invalid ndev pointer\n", __func__);
		return IRQ_NONE;
	}

	status = sunxi_int_status(priv->base, (void *)(&priv->xstats));

	if (likely(status == handle_tx_rx))
		geth_schedule(priv);
	else if (unlikely(status == tx_hard_error_bump_tc))
		netdev_info(ndev, "Do nothing for bump tc\n");
	else if (unlikely(status == tx_hard_error))
		geth_tx_err(priv);
	else
		netdev_info(ndev, "Do nothing.....\n");

	return IRQ_HANDLED;
}

extern int gpioInit(struct platform_device *pdev, const char* name);


static int geth_open(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct platform_device *pdev = container_of(priv->dev, struct platform_device, dev);
	int ret = 0;


	printk("geth_open \n");
	ret = geth_dma_desc_init(ndev);
	if (ret) {
		ret = -EINVAL;
		return ret;
	}

		/******phy reset, that can be discovered *****/
	printk("geth_probe reset set high, gpioInit, RXCK will start\n");
	//gpioInit(pdev, "phy_reset");

	ret = geth_power_on(priv);
	if (ret) {
		netdev_err(ndev, "Power on is failed\n");
		ret = -EINVAL;
	}

	geth_clk_enable(priv);

	//netif_carrier_off(ndev);

	ret = geth_phy_init(ndev);
	if (ret)
		goto err;

	ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000);
	if (ret) {
		netdev_err(ndev, "Initialize hardware error\n");
		goto desc_err;
	}else{
		netdev_err(ndev, "Initialize hardware success\n");
	}

	sunxi_mac_init(priv->base, txmode, rxmode);
	sunxi_set_umac(priv->base, ndev->dev_addr, 0);
/*
	if (!priv->is_suspend) {
		ret = geth_dma_desc_init(ndev);
		if (ret) {
			ret = -EINVAL;
			goto desc_err;
		}
	}
*/
	memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
	memset(priv->dma_rx, 0, dma_desc_rx * sizeof(struct dma_desc));

	desc_init_chain(priv->dma_rx, (unsigned long)priv->dma_rx_phy, dma_desc_rx);
	desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);

	priv->rx_clean = 0;
	priv->rx_dirty = 0;
	priv->tx_clean = 0;
	priv->tx_dirty = 0;
	geth_rx_refill(ndev);

	/* Extra statistics */
	memset(&priv->xstats, 0, sizeof(struct geth_extra_stats));

	if (ndev->phydev)
		phy_start(ndev->phydev);

	sunxi_start_rx(priv->base, (unsigned long)((struct dma_desc *)
		       priv->dma_rx_phy + priv->rx_dirty));
	sunxi_start_tx(priv->base, (unsigned long)((struct dma_desc *)
		       priv->dma_tx_phy + priv->tx_clean));

	/* Enable the Rx/Tx */
	sunxi_mac_enable(priv->base);

	netif_carrier_on(ndev);

	napi_enable(&priv->napi);
	netif_start_queue(ndev);

	/* Enable the Rx/Tx */
//	sunxi_mac_enable(priv->base);

	return 0;

desc_err:
	geth_phy_release(ndev);
err:
	geth_clk_disable(priv);
	if (priv->is_suspend)
		napi_enable(&priv->napi);

	geth_power_off(priv);

power_err:
	geth_free_dma_desc(priv);

	return ret;
}

static int geth_stop(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);

	netif_stop_queue(ndev);
	napi_disable(&priv->napi);

	netif_carrier_off(ndev);

	/* Release PHY resources */
	geth_phy_release(ndev);

	/* Disable Rx/Tx */
	sunxi_mac_disable(priv->base);

	geth_clk_disable(priv);
	geth_power_off(priv);

	netif_tx_lock_bh(ndev);
	/* Release the DMA TX/RX socket buffers */
	geth_free_rx_sk(priv);
	geth_free_tx_sk(priv);
	netif_tx_unlock_bh(ndev);

	/* Ensure that hareware have been stopped */
	if (!priv->is_suspend)
		geth_free_dma_desc(priv);

	return 0;
}

static void geth_tx_complete(struct geth_priv *priv)
{
	unsigned int entry = 0;
	struct sk_buff *skb = NULL;
	struct dma_desc *desc = NULL;
	int tx_stat;

	spin_lock(&priv->tx_lock);
	while (circ_cnt(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > 0) {
		entry = priv->tx_clean;
		desc = priv->dma_tx + entry;

		/* Check if the descriptor is owned by the DMA. */
		if (desc_get_own(desc))
			break;

		/* Verify tx error by looking at the last segment */
		if (desc_get_tx_ls(desc)) {
			tx_stat = desc_get_tx_status(desc, (void *)(&priv->xstats));

			if (likely(!tx_stat))
				priv->ndev->stats.tx_packets++;
			else
				priv->ndev->stats.tx_errors++;
		}

		dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
				 desc_buf_get_len(desc), DMA_TO_DEVICE);

		skb = priv->tx_sk[entry];
		priv->tx_sk[entry] = NULL;
		desc_init(desc);

		/* Find next dirty desc */
		priv->tx_clean = circ_inc(entry, dma_desc_tx);

		if (unlikely(skb == NULL))
			continue;

		dev_kfree_skb(skb);
	}

	if (unlikely(netif_queue_stopped(priv->ndev)) &&
	    circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
	    TX_THRESH) {
		netif_wake_queue(priv->ndev);
	}
	spin_unlock(&priv->tx_lock);
}

static netdev_tx_t geth_xmit(struct sk_buff *skb, struct net_device *ndev)
{
	struct geth_priv  *priv = netdev_priv(ndev);
	unsigned int entry;
	struct dma_desc *desc, *first;
	unsigned int len, tmp_len = 0;
	int i, csum_insert;
	int nfrags = skb_shinfo(skb)->nr_frags;
	dma_addr_t paddr;

	spin_lock(&priv->tx_lock);
	if (unlikely(circ_space(priv->tx_dirty, priv->tx_clean,
	    dma_desc_tx) < (nfrags + 1))) {
		if (!netif_queue_stopped(ndev)) {
			netdev_err(ndev, "%s: BUG! Tx Ring full when queue awake\n", __func__);
			netif_stop_queue(ndev);
		}
		spin_unlock(&priv->tx_lock);

		return NETDEV_TX_BUSY;
	}

	csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL);
	entry = priv->tx_dirty;
	first = priv->dma_tx + entry;
	desc = priv->dma_tx + entry;

	len = skb_headlen(skb);
	priv->tx_sk[entry] = skb;

#ifdef PKT_DEBUG
	printk("======TX PKT DATA: ============\n");
	/* dump the packet */
	print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
		       16, 1, skb->data, 64, true);
#endif

	/* Every desc max size is 2K */
	while (len != 0) {
		desc = priv->dma_tx + entry;
		tmp_len = ((len > MAX_BUF_SZ) ?  MAX_BUF_SZ : len);

		paddr = dma_map_single(priv->dev, skb->data, tmp_len, DMA_TO_DEVICE);
		if (dma_mapping_error(priv->dev, paddr)) {
			dev_kfree_skb(skb);
			return -EIO;
		}
		desc_buf_set(desc, paddr, tmp_len);
		/* Don't set the first's own bit, here */
		if (first != desc) {
			priv->tx_sk[entry] = NULL;
			desc_set_own(desc);
		}

		entry = circ_inc(entry, dma_desc_tx);
		len -= tmp_len;
	}

	for (i = 0; i < nfrags; i++) {
		const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];

		len = skb_frag_size(frag);
		desc = priv->dma_tx + entry;
		paddr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE);
		if (dma_mapping_error(priv->dev, paddr)) {
			dev_kfree_skb(skb);
			return -EIO;
		}

		desc_buf_set(desc, paddr, len);
		desc_set_own(desc);
		priv->tx_sk[entry] = NULL;
		entry = circ_inc(entry, dma_desc_tx);
	}

	ndev->stats.tx_bytes += skb->len;
	priv->tx_dirty = entry;
	desc_tx_close(first, desc, csum_insert);

	desc_set_own(first);
	spin_unlock(&priv->tx_lock);

	if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) <=
			(MAX_SKB_FRAGS + 1)) {
		netif_stop_queue(ndev);
		if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
				TX_THRESH)
			netif_wake_queue(ndev);
	}

#ifdef DEBUG
	printk("=======TX Descriptor DMA: 0x%08llx\n", priv->dma_tx_phy);
	printk("Tx pointor: dirty: %d, clean: %d\n", priv->tx_dirty, priv->tx_clean);
	desc_print(priv->dma_tx, dma_desc_tx);
#endif
	sunxi_tx_poll(priv->base);
	geth_tx_complete(priv);

	return NETDEV_TX_OK;
}

static int geth_rx(struct geth_priv *priv, int limit)
{
	unsigned int rxcount = 0;
	unsigned int entry;
	struct dma_desc *desc;
	struct sk_buff *skb;
	int status;
	int frame_len;

	while (rxcount < limit) {
		entry = priv->rx_dirty;
		desc = priv->dma_rx + entry;

		if (desc_get_own(desc))
			break;

		rxcount++;
		priv->rx_dirty = circ_inc(priv->rx_dirty, dma_desc_rx);

		/* Get length & status from hardware */
		frame_len = desc_rx_frame_len(desc);
		status = desc_get_rx_status(desc, (void *)(&priv->xstats));

		netdev_dbg(priv->ndev, "Rx frame size %d, status: %d\n",
			   frame_len, status);

		skb = priv->rx_sk[entry];
		if (unlikely(!skb)) {
			netdev_err(priv->ndev, "Skb is null\n");
			priv->ndev->stats.rx_dropped++;
			break;
		}

#ifdef PKT_DEBUG
		printk("======RX PKT DATA: ============\n");
		/* dump the packet */
		print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
				16, 1, skb->data, 64, true);
#endif

		if (status == discard_frame) {
			netdev_dbg(priv->ndev, "Get error pkt\n");
			priv->ndev->stats.rx_errors++;
			continue;
		}

		if (unlikely(status != llc_snap))
			frame_len -= ETH_FCS_LEN;

		priv->rx_sk[entry] = NULL;

		skb_put(skb, frame_len);
		dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
				 desc_buf_get_len(desc), DMA_FROM_DEVICE);

		skb->protocol = eth_type_trans(skb, priv->ndev);

		skb->ip_summed = CHECKSUM_UNNECESSARY;
		napi_gro_receive(&priv->napi, skb);

		priv->ndev->stats.rx_packets++;
		priv->ndev->stats.rx_bytes += frame_len;
	}

#ifdef DEBUG
	if (rxcount > 0) {
		printk("======RX Descriptor DMA: 0x%08llx=\n", priv->dma_rx_phy);
		printk("RX pointor: dirty: %d, clean: %d\n", priv->rx_dirty, priv->rx_clean);
		desc_print(priv->dma_rx, dma_desc_rx);
	}
#endif
	geth_rx_refill(priv->ndev);

	return rxcount;
}

static int geth_poll(struct napi_struct *napi, int budget)
{
	struct geth_priv *priv = container_of(napi, struct geth_priv, napi);
	int work_done = 0;

	geth_tx_complete(priv);
	work_done = geth_rx(priv, budget);

	if (work_done < budget) {
		napi_complete(napi);
		sunxi_int_enable(priv->base);
	}

	return work_done;
}

static int geth_change_mtu(struct net_device *ndev, int new_mtu)
{
	int max_mtu;

	if (netif_running(ndev)) {
		pr_err("%s: must be stopped to change its MTU\n", ndev->name);
		return -EBUSY;
	}

	max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);

	if ((new_mtu < 46) || (new_mtu > max_mtu)) {
		pr_err("%s: invalid MTU, max MTU is: %d\n", ndev->name, max_mtu);
		return -EINVAL;
	}

	ndev->mtu = new_mtu;
	netdev_update_features(ndev);

	return 0;
}

static netdev_features_t geth_fix_features(struct net_device *ndev,
					   netdev_features_t features)
{
	return features;
}

static void geth_set_rx_mode(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);
	unsigned int value = 0;

	pr_debug("%s: # mcasts %d, # unicast %d\n",
		 __func__, netdev_mc_count(ndev), netdev_uc_count(ndev));

	spin_lock(&priv->lock);
	if (ndev->flags & IFF_PROMISC) {
		value = GETH_FRAME_FILTER_PR;
	} else if ((netdev_mc_count(ndev) > HASH_TABLE_SIZE) ||
		   (ndev->flags & IFF_ALLMULTI)) {
		value = GETH_FRAME_FILTER_PM;	/* pass all multi */
		sunxi_hash_filter(priv->base, ~0UL, ~0UL);
	} else if (!netdev_mc_empty(ndev)) {
		u32 mc_filter[2];
		struct netdev_hw_addr *ha;

		/* Hash filter for multicast */
		value = GETH_FRAME_FILTER_HMC;

		memset(mc_filter, 0, sizeof(mc_filter));
		netdev_for_each_mc_addr(ha, ndev) {
			/* The upper 6 bits of the calculated CRC are used to
			 *  index the contens of the hash table
			 */
			int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26;
			/* The most significant bit determines the register to
			 * use (H/L) while the other 5 bits determine the bit
			 * within the register.
			 */
			mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
		}
		sunxi_hash_filter(priv->base, mc_filter[0], mc_filter[1]);
	}

	/* Handle multiple unicast addresses (perfect filtering)*/
	if (netdev_uc_count(ndev) > 16) {
		/* Switch to promiscuous mode is more than 8 addrs are required */
		value |= GETH_FRAME_FILTER_PR;
	} else {
		int reg = 1;
		struct netdev_hw_addr *ha;

		netdev_for_each_uc_addr(ha, ndev) {
			sunxi_set_umac(priv->base, ha->addr, reg);
			reg++;
		}
	}

#ifdef FRAME_FILTER_DEBUG
	/* Enable Receive all mode (to debug filtering_fail errors) */
	value |= GETH_FRAME_FILTER_RA;
#endif
	sunxi_set_filter(priv->base, value);
	spin_unlock(&priv->lock);
}

static void geth_tx_timeout(struct net_device *ndev)
{
	struct geth_priv *priv = netdev_priv(ndev);

	geth_tx_err(priv);
}

static int geth_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
{
	if (!netif_running(ndev))
		return -EINVAL;

	if (!ndev->phydev)
		return -EINVAL;

	return phy_mii_ioctl(ndev->phydev, rq, cmd);
}

/* Configuration changes (passed on by ifconfig) */
static int geth_config(struct net_device *ndev, struct ifmap *map)
{
	if (ndev->flags & IFF_UP)	/* can't act on a running interface */
		return -EBUSY;

	/* Don't allow changing the I/O address */
	if (map->base_addr != ndev->base_addr) {
		pr_warn("%s: can't change I/O address\n", ndev->name);
		return -EOPNOTSUPP;
	}

	/* Don't allow changing the IRQ */
	if (map->irq != ndev->irq) {
		pr_warn("%s: can't change IRQ number %d\n", ndev->name, ndev->irq);
		return -EOPNOTSUPP;
	}

	return 0;
}

static int geth_set_mac_address(struct net_device *ndev, void *p)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct sockaddr *addr = p;

	if (!is_valid_ether_addr(addr->sa_data))
		return -EADDRNOTAVAIL;

	memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len);
	sunxi_set_umac(priv->base, ndev->dev_addr, 0);

	return 0;
}

int geth_set_features(struct net_device *ndev, netdev_features_t features)
{
	struct geth_priv *priv = netdev_priv(ndev);

	if (features & NETIF_F_LOOPBACK && netif_running(ndev))
		sunxi_mac_loopback(priv->base, 1);
	else
		sunxi_mac_loopback(priv->base, 0);

	return 0;
}

#ifdef CONFIG_NET_POLL_CONTROLLER
/* Polling receive - used by NETCONSOLE and other diagnostic tools
 * to allow network I/O with interrupts disabled.
 */
static void geth_poll_controller(struct net_device *dev)
{
	disable_irq(dev->irq);
	geth_interrupt(dev->irq, dev);
	enable_irq(dev->irq);
}
#endif

static const struct net_device_ops geth_netdev_ops = {
	.ndo_init = NULL,
	.ndo_open = geth_open,
	.ndo_start_xmit = geth_xmit,
	.ndo_stop = geth_stop,
	.ndo_change_mtu = geth_change_mtu,
	.ndo_fix_features = geth_fix_features,
	.ndo_set_rx_mode = geth_set_rx_mode,
	.ndo_tx_timeout = geth_tx_timeout,
	.ndo_do_ioctl = geth_ioctl,
	.ndo_set_config = geth_config,
#ifdef CONFIG_NET_POLL_CONTROLLER
	.ndo_poll_controller = geth_poll_controller,
#endif
	.ndo_set_mac_address = geth_set_mac_address,
	.ndo_set_features = geth_set_features,
};

static int geth_check_if_running(struct net_device *ndev)
{
	if (!netif_running(ndev))
		return -EBUSY;
	return 0;
}

static int geth_get_sset_count(struct net_device *netdev, int sset)
{
	int len;

	switch (sset) {
	case ETH_SS_STATS:
		len = 0;
		return len;
	default:
		return -EOPNOTSUPP;
	}
}

static int geth_ethtool_getsettings(struct net_device *ndev,
				    struct ethtool_cmd *cmd)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct phy_device *phy = ndev->phydev;
	int rc;

	if (phy == NULL) {
		netdev_err(ndev, "%s: %s: PHY is not registered\n",
		       __func__, ndev->name);
		return -ENODEV;
	}

	if (!netif_running(ndev)) {
		pr_err("%s: interface is disabled: we cannot track "
		       "link speed / duplex setting\n", ndev->name);
		return -EBUSY;
	}

	cmd->transceiver = XCVR_INTERNAL;
	spin_lock_irq(&priv->lock);
	rc = phy_ethtool_gset(phy, cmd);
	spin_unlock_irq(&priv->lock);

	return rc;
}

static int geth_ethtool_setsettings(struct net_device *ndev,
				    struct ethtool_cmd *cmd)
{
	struct geth_priv *priv = netdev_priv(ndev);
	struct phy_device *phy = ndev->phydev;
	int rc;

	spin_lock(&priv->lock);
	rc = phy_ethtool_sset(phy, cmd);
	spin_unlock(&priv->lock);

	return rc;
}

static void geth_ethtool_getdrvinfo(struct net_device *ndev,
				    struct ethtool_drvinfo *info)
{
	strlcpy(info->driver, "sunxi_geth", sizeof(info->driver));

#define DRV_MODULE_VERSION "SUNXI Gbgit driver V1.1"

	strcpy(info->version, DRV_MODULE_VERSION);
	info->fw_version[0] = '\0';
}

static const struct ethtool_ops geth_ethtool_ops = {
	.begin = geth_check_if_running,
	.get_settings = geth_ethtool_getsettings,
	.set_settings = geth_ethtool_setsettings,
	.get_link = ethtool_op_get_link,
	.get_pauseparam = NULL,
	.set_pauseparam = NULL,
	.get_ethtool_stats = NULL,
	.get_strings = NULL,
	.get_wol = NULL,
	.set_wol = NULL,
	.get_sset_count = geth_get_sset_count,
	.get_drvinfo = geth_ethtool_getdrvinfo,
};

/* config hardware resource */
static int geth_hw_init(struct platform_device *pdev)
{
	struct net_device *ndev = platform_get_drvdata(pdev);
	struct geth_priv *priv = netdev_priv(ndev);
	struct device_node *np = pdev->dev.of_node;
	int ret = 0;
	struct resource *res;
	u32 value;
	struct gpio_config cfg;
	const char *gmac_power;
	char power[20];
	int i;

#ifdef CONFIG_SUNXI_EXT_PHY
	priv->phy_ext = EXT_PHY;
#else
	priv->phy_ext = INT_PHY;
#endif

	/* config memery resource */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (unlikely(!res)) {
		pr_err("%s: ERROR: get gmac memory failed", __func__);
		return -ENODEV;
	}
	printk("sunxi-gmac geth_hw_init\n");
	priv->base = devm_ioremap_resource(&pdev->dev, res);
	if (!priv->base) {
		pr_err("%s: ERROR: gmac memory mapping failed", __func__);
		return -ENOMEM;
	}
	printk("sunxi-gmac geth_hw_init base 0x%x\n", priv->base);
	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (unlikely(!res)) {
		pr_err("%s: ERROR: get phy memory failed", __func__);
		ret = -ENODEV;
		goto mem_err;
	}

	priv->base_phy = devm_ioremap_resource(&pdev->dev, res);
	if (unlikely(!priv->base_phy)) {
		pr_err("%s: ERROR: phy memory mapping failed", __func__);
		ret = -ENOMEM;
		goto mem_err;
	}

	/* config IRQ */
	ndev->irq = platform_get_irq_byname(pdev, "gmacirq");
	if (ndev->irq == -ENXIO) {
		pr_err("%s: ERROR: MAC IRQ not found\n", __func__);
		ret = -ENXIO;
		goto irq_err;
	}

	ret = request_irq(ndev->irq, geth_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev);
	if (unlikely(ret < 0)) {
		pr_err("Could not request irq %d, error: %d\n", ndev->irq, ret);
		goto irq_err;
	}

	/* config clock */
	priv->geth_clk = of_clk_get_by_name(np, "gmac");
	if (unlikely(!priv->geth_clk || IS_ERR(priv->geth_clk))) {
		pr_err("Get gmac clock failed!\n");
		ret = -EINVAL;
		goto clk_err;
	}

	if (INT_PHY == priv->phy_ext) {
		priv->ephy_clk = of_clk_get_by_name(np, "ephy");
		if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
			pr_err("Get ephy clock failed!\n");
			ret = -EINVAL;
			goto clk_err;
		}
	}
#if defined(CONFIG_ARCH_SUN8IW12) || defined(CONFIG_ARCH_SUN50IW9)
	else {
		if (!of_property_read_u32(np, "use_ephy25m", &g_use_ephy_clk)
				&& g_use_ephy_clk) {
			priv->ephy_clk = of_clk_get_by_name(np, "ephy");
			if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
				pr_err("Get ephy clk failed!\n");
				ret = -EINVAL;
				goto clk_err;
			}
		}
	}
#endif

	/* config power regulator */
	if (EXT_PHY == priv->phy_ext) {
		for (i = 0; i < POWER_CHAN_NUM; i++) {
			snprintf(power, 15, "gmac-power%d", i);
			ret = of_property_read_string(np, power, &gmac_power);
			if (ret) {
				priv->gmac_power[i] = NULL;
				pr_info("gmac-power%d: NULL\n", i);
				continue;
			}
			priv->gmac_power[i] = regulator_get(NULL, gmac_power);
			if (IS_ERR(priv->gmac_power[i])) {
				pr_err("gmac-power%d get error!\n", i);
				ret = -EINVAL;
				goto clk_err;
			}
		}
	}
	/* config other parameters */
	priv->phy_interface = of_get_phy_mode(np);
	if (priv->phy_interface != PHY_INTERFACE_MODE_MII &&
	    priv->phy_interface != PHY_INTERFACE_MODE_RGMII &&
	    priv->phy_interface != PHY_INTERFACE_MODE_RMII) {
		pr_err("Not support phy type!\n");
		priv->phy_interface = PHY_INTERFACE_MODE_MII;
	}

	if (!of_property_read_u32(np, "tx-delay", &value))
		tx_delay = value;

	if (!of_property_read_u32(np, "rx-delay", &value))
		rx_delay = value;

	/* config pinctrl */
	if (EXT_PHY == priv->phy_ext) {
		priv->phyrst = of_get_named_gpio_flags(np, "phy-rst", 0, (enum of_gpio_flags *)&cfg);
		priv->rst_active_low = (cfg.data == OF_GPIO_ACTIVE_LOW) ? 1 : 0;

		if (gpio_is_valid(priv->phyrst)) {
			if (gpio_request(priv->phyrst, "phy-rst") < 0) {
				pr_err("gmac gpio request fail!\n");
				ret = -EINVAL;
				goto pin_err;
			}
		}

		priv->pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
		if (IS_ERR_OR_NULL(priv->pinctrl)) {
			pr_err("gmac pinctrl error!\n");
			priv->pinctrl = NULL;
			ret = -EINVAL;
			goto pin_err;
		}
	}

	return 0;

pin_err:
	if (EXT_PHY == priv->phy_ext) {
		for (i = 0; i < POWER_CHAN_NUM; i++) {
			if (IS_ERR_OR_NULL(priv->gmac_power[i]))
				continue;
			regulator_put(priv->gmac_power[i]);
		}
	}
clk_err:
	free_irq(ndev->irq, ndev);
irq_err:
	devm_iounmap(&pdev->dev, priv->base_phy);
mem_err:
	devm_iounmap(&pdev->dev, priv->base);

	return ret;
}

static void geth_hw_release(struct platform_device *pdev)
{
	struct net_device *ndev = platform_get_drvdata(pdev);
	struct geth_priv *priv = netdev_priv(ndev);
	int i;

	devm_iounmap(&pdev->dev, (priv->base_phy));
	devm_iounmap(&pdev->dev, priv->base);
	free_irq(ndev->irq, ndev);
	if (priv->geth_clk)
		clk_put(priv->geth_clk);

	if (EXT_PHY == priv->phy_ext) {
		for (i = 0; i < POWER_CHAN_NUM; i++) {
			if (IS_ERR_OR_NULL(priv->gmac_power[i]))
				continue;
			regulator_put(priv->gmac_power[i]);
		}

		if (!IS_ERR_OR_NULL(priv->pinctrl))
			devm_pinctrl_put(priv->pinctrl);

		if (gpio_is_valid(priv->phyrst))
			gpio_free(priv->phyrst);
	}

	if (!IS_ERR_OR_NULL(priv->ephy_clk))
		clk_put(priv->ephy_clk);
}

extern int gpioInit(struct platform_device *pdev, const char* name);

/**
 * geth_probe
 * @pdev: platform device pointer
 * Description: the driver is initialized through platform_device.
 */
static int geth_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct net_device *ndev = NULL;
	struct geth_priv *priv;
	struct gpio_desc *gpiod_reset;

#ifdef CONFIG_OF
	pdev->dev.dma_mask = &geth_dma_mask;
	pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
#endif
	printk("sunxi-gmac probe\n");

	


	ndev = alloc_etherdev(sizeof(struct geth_priv));
	if (!ndev) {
		dev_err(&pdev->dev, "could not allocate device.\n");
		return -ENOMEM;
	}
	SET_NETDEV_DEV(ndev, &pdev->dev);

	priv = netdev_priv(ndev);
	platform_set_drvdata(pdev, ndev);


	priv->avcc = regulator_get(&pdev->dev, "avcc");
	if (IS_ERR(priv->avcc)) {
		pr_err("[%s]:get geth avcc failed\n", __func__);
		priv->avcc = NULL;
	} else {
		ret = regulator_enable(priv->avcc);
		if (ret) {
			pr_err("[%s]:geth avcc enable failed!\n", __func__);
			//goto err_put_regulator;
		}else{
			pr_err("[%s]:geth avcc enable success!\n", __func__);
		}
	}

	/******phy reset, that can be discovered *****/
	printk("geth_probe reset set high, gpioInit\n");
	gpioInit(pdev, "phy_reset");

	/******phy reset, that can be discovered  end*****/

	/* Must set private data to pdev, before call it */
	ret = geth_hw_init(pdev);
	if (0 != ret) {
		pr_err("geth_hw_init fail!\n");
		goto hw_err;
	}
	/* setup the netdevice, fill the field of netdevice */
	ether_setup(ndev);
	ndev->netdev_ops = &geth_netdev_ops;
	netdev_set_default_ethtool_ops(ndev, &geth_ethtool_ops);
	ndev->base_addr = (unsigned long)priv->base;

	priv->ndev = ndev;
	priv->dev = &pdev->dev;

	/* TODO: support the VLAN frames */
	ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM |
				NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM;

	ndev->features |= ndev->hw_features;
	ndev->hw_features |= NETIF_F_LOOPBACK;
	ndev->priv_flags |= IFF_UNICAST_FLT;

	ndev->watchdog_timeo = msecs_to_jiffies(watchdog);

	netif_napi_add(ndev, &priv->napi, geth_poll,  BUDGET);

	spin_lock_init(&priv->lock);
	spin_lock_init(&priv->tx_lock);
	printk("sunxi-gmac probe 1\n");
	/* The last val is mdc clock ratio */
	sunxi_geth_register((void *)ndev->base_addr, HW_VERSION, 0x03);

	ret = register_netdev(ndev);
	if (ret) {
		netif_napi_del(&priv->napi);
		pr_err("Error: Register %s failed\n", ndev->name);
		goto reg_err;
	}

	/* Before open the device, the mac address should be set */
	geth_check_addr(ndev, mac_str);

#ifdef CONFIG_GETH_ATTRS
	geth_create_attrs(ndev);
#endif
	device_create_file(&pdev->dev, &dev_attr_gphy_test);

	device_enable_async_suspend(&pdev->dev);

#ifdef CONFIG_PM
	INIT_WORK(&priv->eth_work, geth_resume_work);
#endif
	printk("sunxi-gmac probe end\n");
	return 0;

reg_err:
	geth_hw_release(pdev);
hw_err:
	platform_set_drvdata(pdev, NULL);
	free_netdev(ndev);

	return ret;
}

static int geth_remove(struct platform_device *pdev)
{
	struct net_device *ndev = platform_get_drvdata(pdev);
	struct geth_priv *priv = netdev_priv(ndev);

	device_remove_file(&pdev->dev, &dev_attr_gphy_test);

	netif_napi_del(&priv->napi);
	unregister_netdev(ndev);
	geth_hw_release(pdev);
	platform_set_drvdata(pdev, NULL);
	free_netdev(ndev);

	return 0;
}


static const struct of_device_id geth_of_match[] = {
	{.compatible = "allwinner,sunxi-gmac" },
	{},
};


//MODULE_DEVICE_TABLE(of, geth_of_match);

static struct platform_driver geth_driver = {
	.probe	= geth_probe,
	.remove = geth_remove,
	.driver = {
		   .name = "sunxi-gmac",
		   .owner = THIS_MODULE,
		   .pm = &geth_pm_ops,
		   .of_match_table = geth_of_match,
	},
};
module_platform_driver(geth_driver);

/*
static int __init sunxi_gmac_init(void)
{
	printk("sunxi_gmac_init\n");
	return platform_driver_register(&geth_driver);
}

static void __exit sunxi_gmac_exit(void)
{
	printk("sunxi_gmac_exit\n");
	platform_driver_unregister(&geth_driver);
}

module_init(sunxi_gmac_init);
module_exit(sunxi_gmac_exit);


#ifndef MODULE
static int __init set_mac_addr(char *str)
{
	char *p = str;

	if (str && strlen(str))
		memcpy(mac_str, p, 18);

	return 0;
}
__setup("mac_addr=", set_mac_addr);
#endif
*/

MODULE_DESCRIPTION("Allwinner Gigabit Ethernet driver");
MODULE_AUTHOR("fuzhaoke <fuzhaoke@allwinnertech.com>");
MODULE_LICENSE("Dual BSD/GPL");

2.1.2 phy 驱动

路径:kernel/linux-4.9/drivers/net/phy/motorcomm.c

复制代码
// 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                (1)

/**** 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;
	static bool state = false;
#define YTPHY_WHILE_MAX_CNT  5
	static uint32_t max_cnt = YTPHY_WHILE_MAX_CNT;
	if (!state)
	{
		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;

			msleep(10);
		}while((0x88 != val) && (max_cnt-- != 0));
		
		ret = ytphy_write_ext(phydev, 0xa012, 0xd0);
		if (ret < 0)
			return ret;
		
		state = !state;
	}

	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);

2.2、使用config宏配置驱动

复制代码
--- a/kernel/linux-4.9/arch/arm64/configs/sun50iw9p1smp_longan_defconfig
+++ b/kernel/linux-4.9/arch/arm64/configs/sun50iw9p1smp_longan_defconfig
@@ -1387,6 +1387,7 @@ CONFIG_ETHERNET=y
 CONFIG_NET_VENDOR_ALLWINNER=y
 # CONFIG_SUN4I_EMAC is not set
 CONFIG_SUNXI_GMAC=y
+CONFIG_SUNXI_EXT_PHY=y
 # CONFIG_ALTERA_TSE is not set
 CONFIG_NET_VENDOR_AMAZON=y
 CONFIG_NET_VENDOR_AMD=y

2.3、设备树

(1)kernel/linux-4.9/arch/arm64/boot/dts/sunxi/sun50iw9p1-pinctrl.dtsi

复制代码
			gmac0_pins_a: gmac@0 {
				allwinner,pins = "PI0", "PI1", "PI2", "PI3",
						 "PI4", "PI5",  "PI7",
						 "PI8", "PI9", "PI10", "PI11",
						 "PI12", "PI13", "PI14", "PI15";
				allwinner,function = "gmac0";
				allwinner,muxsel = <2>;
				allwinner,drive = <3>;
				allwinner,pull = <0>;
			};

			gmac0_pins_b: gmac@1 {
				allwinner,pins = "PI0", "PI1", "PI2", "PI3",
						 "PI4", "PI5",  "PI7",
						 "PI8", "PI9", "PI10", "PI11",
						 "PI12", "PI13", "PI14", "PI15";
				allwinner,function = "io_disabled";
				allwinner,muxsel = <7>;
				allwinner,drive = <3>;
				allwinner,pull = <0>;
			};

/t507/configs/demo2.0/board.dts

复制代码
		gmac0:eth@05020000 {
			compatible = "allwinner,sunxi-gmac";
			
			reg = <0x0 0x05020000 0x0 0x10000>,
			      <0x0 0x03000030 0x0 0x4>;
			
			interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
			interrupt-names = "gmacirq";
			clocks = <&clk_gmac0>, <&clk_ephy_25m>;
			clock-names = "gmac", "ephy";
			device_type = "gmac0";
			pinctrl-0 = <&gmac0_pins_a>;
			pinctrl-1 = <&gmac0_pins_b>;
			pinctrl-names = "default", "sleep";
			
			phy-mode = "rgmii";
			tx-delay = <7>;
			rx-delay = <0>;
			use_ephy25m = <0>;
			phy-rst;
			gmac-power0;
			gmac-power1;
			gmac-power2;
			status = "okay";

			avcc-supply = <&reg_aldo3>;
			
			//qca,disable-smarteee = <1>;
			//qca,clk-out-frequency = <125000000>;
			//qca,clk-out-strength = <0>;

			//phy-rst = <&pio PI 6 1 1 1 1>;
			phy_reset = <&pio PI 6 1 1 1 1>;
		};

2.4、gmac驱动调试中遇到的问题

测试以太网时,第一次上电可以正常读取ID,识别到PHY

网口正常生成

但是无法获取IP,设置静态IP也无法ping通

确认PHY使能正常:

重新Up后,No PHY found!

加载失败则需要从以下三个方面进行分析:

(1)供电:PHY 一般无需单独的供电,GMAC 的 PIN BANK 在某些平台上需要单独供电,检查PIN 的供电是否正常

(2)时钟:主要检查 PHY 的 25M 时钟,如果是外部晶振,用示波器量晶振频率是否在 25Mhz 左 右,每款 PHY 对晶振的精确度要求不一致 PHY 的 datasheet 上会有相关说明;如果是内部 SOC25M 时钟供电,查看 SOC25M 对应的是哪个 PIN,用示波器量下 PIN 输出的时钟频率 是否在 25M 左右

(3)PHY-RST:确保对 PHY 在进行 MDIO 读写操作时需要 10ms 左右 rst 拉低以及 150ms 左右 rst 拉高操作

示波器测量后定位到无25兆时钟输出

使用外部晶振,硬件上焊接上电阻R204 R205,断开R207

使能25兆时钟后还是未找到PHY

reset:

配置情况:

为什么会识别不到phy,很奇怪???

对于此类问题,多半是引脚复用的问题。(由于是在脚本中配置了GPIO脚,导致排查了很久)

屏蔽此处GPIO的使用,该部分引脚是PHY的重要引脚

四、测试

网络设备

正常关闭和打开

联网测试:

iperf3测试:开发板于电脑网线连接,配置电脑端和设备端同一网段IP

Linux设备端

电脑端

相关推荐
FoldWinCard1 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计2 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1232 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质2 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX3 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.3 小时前
Linux系统管理
linux·运维
阿龙的工作记录3 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手4 小时前
Linux的基本指令
linux·运维·服务器
hkj88084 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动6 小时前
Linux编程笔记3【个人用】
linux·笔记