/* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 *
 * This file contain content copied from Synopsis driver,
 * provided under the license below
 */
/* =========================================================================
 * The Synopsys DWC ETHER QOS Software Driver and documentation (hereinafter
 * "Software") is an unsupported proprietary work of Synopsys, Inc. unless
 * otherwise expressly agreed to in writing between Synopsys and you.
 *
 * The Software IS NOT an item of Licensed Software or Licensed Product under
 * any End User Software License Agreement or Agreement for Licensed Product
 * with Synopsys or any supplement thereto.  Permission is hereby granted,
 * free of charge, to any person obtaining a copy of this software annotated
 * with this license and the Software, to deal in the Software without
 * restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * =========================================================================
 */

/*!@file: DWC_ETH_QOS_mdio.c
 * @brief: Driver functions.
 */
#include "DWC_ETH_QOS_yheader.h"
#include "DWC_ETH_QOS_ipa.h"

/*!
 * \brief read MII PHY register, function called by the driver alone
 *
 * \details Read MII registers through the API read_phy_reg where the
 * related MAC registers can be configured.
 *
 * \param[in] pdata - pointer to driver private data structure.
 * \param[in] phyaddr - the phy address to read
 * \param[in] phyreg - the phy regiester id to read
 * \param[out] phydata -
 *		pointer to the value that is read from the phy registers
 *
 * \return int
 *
 * \retval  0 - successfully read data from register
 * \retval -1 - error occurred
 * \retval  1 - if the feature is not defined.
 */

INT DWC_ETH_QOS_mdio_read_direct(struct DWC_ETH_QOS_prv_data *pdata,
				 int phyaddr, int phyreg, int *phydata)
{
	struct hw_if_struct *hw_if = &pdata->hw_if;
	int phy_reg_read_status;

	DBGPR_MDIO("--> DWC_ETH_QOS_mdio_read_direct\n");

	if (hw_if->read_phy_regs) {
		phy_reg_read_status =
		    hw_if->read_phy_regs(phyaddr, phyreg, phydata);
	} else {
		phy_reg_read_status = 1;
		pr_alert("%s: hw_if->read_phy_regs not defined", DEV_NAME);
	}

	DBGPR_MDIO("<-- DWC_ETH_QOS_mdio_read_direct\n");

	return phy_reg_read_status;
}

/*!
 * \brief write MII PHY register, function called by the driver alone
 *
 * \details Writes MII registers through the API write_phy_reg where the
 * related MAC registers can be configured.
 *
 * \param[in] pdata - pointer to driver private data structure.
 * \param[in] phyaddr - the phy address to write
 * \param[in] phyreg - the phy regiester id to write
 * \param[out] phydata - actual data to be written into the phy registers
 *
 * \return void
 *
 * \retval  0 - successfully read data from register
 * \retval -1 - error occurred
 * \retval  1 - if the feature is not defined.
 */

INT DWC_ETH_QOS_mdio_write_direct(struct DWC_ETH_QOS_prv_data *pdata,
				  int phyaddr, int phyreg, int phydata)
{
	struct hw_if_struct *hw_if = &pdata->hw_if;
	int phy_reg_write_status;

	DBGPR_MDIO("--> DWC_ETH_QOS_mdio_write_direct\n");

	if (hw_if->write_phy_regs) {
		phy_reg_write_status =
		    hw_if->write_phy_regs(phyaddr, phyreg, phydata);
	} else {
		phy_reg_write_status = 1;
		pr_alert("%s: hw_if->write_phy_regs not defined", DEV_NAME);
	}

	DBGPR_MDIO("<-- DWC_ETH_QOS_mdio_write_direct\n");

	return phy_reg_write_status;
}

/*!
 * \brief write MII MMD register, function called by the driver alone
 *
 * \details Writes MII MMD registers through the API write_phy_reg where the
 * related MAC registers can be configured.
 *
 * \param[in] pdata - pointer to driver private data structure.
 * \param[in] phyaddr - the phy address to write
 * \param[in] phyreg - the phy regiester id to write
 * \param[out] phydata - actual data to be written into the phy registers
 *
 */
void DWC_ETH_QOS_mdio_mmd_register_write_direct(struct DWC_ETH_QOS_prv_data *pdata,
				 int phyaddr, int devaddr, int offset, u16 phydata)
{
	int write_data = 0;
	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_ADDR_OFFSET,
				devaddr);

	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_DATAPORT,
				offset);

	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_ADDR_OFFSET,
				(devaddr | 1 << 14));

	write_data = phydata;
	EMACDBG("Writing 0x%x to Dev address 0x%x ,  MMD register offset 0x%x",phydata,devaddr, offset);
	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_DATAPORT,
				write_data);

	return;
}
/*!
 * \brief Read MII MMD register, function called by the driver alone
 *
 * \details Reads MII MMD registers through the API write_phy_reg where the
 * related MAC registers can be configured.
 *
 * \param[in] pdata - pointer to driver private data structure.
 * \param[in] phyaddr - the phy address to write
 * \param[in] phyreg - the phy regiester id to write
 * \param[out] phydata - actual data to be written into the phy registers
 *
 */
void DWC_ETH_QOS_mdio_mmd_register_read_direct(struct DWC_ETH_QOS_prv_data *pdata,
				 int phyaddr, int devaddr, int offset, u16 *phydata)
{
	int read_data = 0;
	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_ADDR_OFFSET,
				devaddr);

	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_DATAPORT,
				offset);

	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_ADDR_OFFSET,
				(devaddr | 1 << 14));

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_PORT_DATAPORT,
				&read_data);
	memcpy((void *)phydata,(void *)&read_data, sizeof(u16));

	//EMACDBG("Read 0x%x from Dev address 0x%x ,  MMD register offset 0x%x",*phydata,devaddr, offset);

	return;
}

/*!
 * \brief read MII PHY register.
 *
 * \details Read MII registers through the API read_phy_reg where the
 * related MAC registers can be configured.
 *
 * \param[in] bus - points to the mii_bus structure
 * \param[in] phyaddr - the phy address to write
 * \param[in] phyreg - the phy register offset to write
 *
 * \return int
 *
 * \retval  - value read from given phy register
 */

static INT DWC_ETH_QOS_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
{
	struct net_device *dev = bus->priv;
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	int phydata;

	DBGPR_MDIO("--> DWC_ETH_QOS_mdio_read: phyaddr = %d, phyreg = %d\n",
		   phyaddr, phyreg);

	if (hw_if->read_phy_regs)
		hw_if->read_phy_regs(phyaddr, phyreg, &phydata);
	else
		pr_alert("%s: hw_if->read_phy_regs not defined", DEV_NAME);

	DBGPR_MDIO("<-- DWC_ETH_QOS_mdio_read: phydata = %#x\n", phydata);

	return phydata;
}

/*!
 * \brief API to write MII PHY register
 *
 * \details This API is expected to write MII registers with the value being
 * passed as the last argument which is done in write_phy_regs API
 * called by this function.
 *
 * \param[in] bus - points to the mii_bus structure
 * \param[in] phyaddr - the phy address to write
 * \param[in] phyreg - the phy register offset to write
 * \param[in] phydata - the register value to write with
 *
 * \return 0 on success and -ve number on failure.
 */

static INT DWC_ETH_QOS_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg,
				  u16 phydata)
{
	struct net_device *dev = bus->priv;
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	INT ret = Y_SUCCESS;

	DBGPR_MDIO("--> DWC_ETH_QOS_mdio_write\n");

	if (hw_if->write_phy_regs) {
		hw_if->write_phy_regs(phyaddr, phyreg, phydata);
	} else {
		ret = -1;
		pr_alert("%s: hw_if->write_phy_regs not defined", DEV_NAME);
	}

	DBGPR_MDIO("<-- DWC_ETH_QOS_mdio_write\n");

	return ret;
}

/*!
 * \brief API to reset PHY
 *
 * \details This API is issue soft reset to PHY core and waits
 * until soft reset completes.
 *
 * \param[in] bus - points to the mii_bus structure
 *
 * \return 0 on success and -ve number on failure.
 */

static INT DWC_ETH_QOS_mdio_reset(struct mii_bus *bus)
{
	struct net_device *dev = bus->priv;
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	INT phydata;

	DBGPR_MDIO("-->DWC_ETH_QOS_mdio_reset: phyaddr : %d\n", pdata->phyaddr);

	if (pdata->res_data->early_eth_en)
		return 0;

#if 0 /* def DWC_ETH_QOS_CONFIG_PGTEST */
	pr_alert("PHY Programming for Autoneg disable\n");
	hw_if->read_phy_regs(pdata->phyaddr, MII_BMCR, &phydata);
	phydata &= ~(1 << 12);
	hw_if->write_phy_regs(pdata->phyaddr, MII_BMCR, phydata);
#endif

	hw_if->read_phy_regs(pdata->phyaddr, MII_BMCR, &phydata);

	if (phydata < 0)
		return 0;

	/* issue soft reset to PHY */
	phydata |= BMCR_RESET;
	hw_if->write_phy_regs(pdata->phyaddr, MII_BMCR, phydata);

	/* wait until software reset completes */
	do {
		hw_if->read_phy_regs(pdata->phyaddr, MII_BMCR, &phydata);
	} while ((phydata >= 0) && (phydata & BMCR_RESET));

#if 0 /* def DWC_ETH_QOS_CONFIG_PGTEST */
	pr_alert("PHY Programming for Loopback\n");
	hw_if->read_phy_regs(pdata->phyaddr, MII_BMCR, &phydata);
	phydata |= (1 << 14);
	hw_if->write_phy_regs(pdata->phyaddr, MII_BMCR, phydata);
#endif

	DBGPR_MDIO("<--DWC_ETH_QOS_mdio_reset\n");

	return 0;
}

/*!
 * \details This function is invoked by other functions to get the PHY register
 * dump. This function is used during development phase for debug purpose.
 *
 * \param[in] pdata – pointer to private data structure.
 *
 * \return 0
 */

void dump_phy_registers(struct DWC_ETH_QOS_prv_data *pdata)
{
	int phydata = 0;

	pr_alert("\n************* PHY Reg dump *************************\n");
	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_BMCR, &phydata);
	pr_alert(
	    "Phy Control Reg(Basic Mode Control Reg) (%#x) = %#x\n",
	     MII_BMCR, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_BMSR, &phydata);
	pr_alert(
		"Phy Status Reg(Basic Mode Status Reg) (%#x) = %#x\n",
		MII_BMSR, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_PHYSID1,
				     &phydata);
	pr_alert(
		"Phy Id (PHYS ID 1) (%#x)= %#x\n",
		MII_PHYSID1, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_PHYSID2,
				     &phydata);
	pr_alert(
		"Phy Id (PHYS ID 2) (%#x)= %#x\n",
		MII_PHYSID2, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_ADVERTISE,
				     &phydata);
	pr_alert(
		"Auto-nego Adv (Advertisement Control Reg)(%#x) = %#x\n",
		MII_ADVERTISE, phydata);

	/* read Phy Control Reg */
	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_LPA,
				     &phydata);
	pr_alert(
		"Auto-nego Lap (Link Partner Ability Reg)(%#x)= %#x\n",
		MII_LPA, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_EXPANSION,
				     &phydata);
	pr_alert(
		"Auto-nego Exp (Extension Reg)(%#x) = %#x\n",
		MII_EXPANSION, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
				     DWC_ETH_QOS_AUTO_NEGO_NP, &phydata);
	pr_alert(
		"Auto-nego Np (%#x) = %#x\n",
	    DWC_ETH_QOS_AUTO_NEGO_NP, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_ESTATUS,
				     &phydata);
	pr_alert(
		"Extended Status Reg (%#x) = %#x\n", MII_ESTATUS, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_CTRL1000,
				     &phydata);
	pr_alert(
		"1000 Ctl Reg (1000BASE-T Control Reg)(%#x) = %#x\n",
		MII_CTRL1000, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_STAT1000,
				     &phydata);
	pr_alert(
		"1000 Sts Reg (1000BASE-T Status)(%#x) = %#x\n",
		MII_STAT1000, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, DWC_ETH_QOS_PHY_CTL,
				     &phydata);
	pr_alert(
		"PHY Ctl Reg (%#x) = %#x\n", DWC_ETH_QOS_PHY_CTL, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
				     DWC_ETH_QOS_PHY_STS, &phydata);
	pr_alert(
		"PHY Sts Reg (%#x) = %#x\n", DWC_ETH_QOS_PHY_STS, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
				     DWC_ETH_QOS_PHY_INTR_STATUS, &phydata);
	pr_alert(
		"PHY Intr Status Reg (%#x) = %#x\n", DWC_ETH_QOS_PHY_INTR_STATUS, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
					 DWC_ETH_QOS_PHY_INTR_EN, &phydata);
	pr_alert(
		"PHY Intr EN Reg (%#x) = %#x\n", DWC_ETH_QOS_PHY_INTR_EN, phydata);

	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, DWC_ETH_QOS_PHY_SMART_SPEED, &phydata);
	pr_alert( "Smart Speed Reg (%#x) = %#x\n", DWC_ETH_QOS_PHY_SMART_SPEED, phydata);

	if ((pdata->phydev->phy_id & pdata->phydev->drv->phy_id_mask) == MICREL_PHY_ID) {
		int i = 0;
		u16 mmd_phydata = 0;
		for(i=0;i<=8;i++){
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR, i, &mmd_phydata);
			EMACDBG("Read %#x from offset %#x", mmd_phydata, i);
		}
	}

	pr_alert("\n****************************************************\n");
}

static void DWC_ETH_QOS_request_phy_wol(struct DWC_ETH_QOS_prv_data *pdata)
{
	pdata->phy_wol_supported = 0;
	pdata->phy_wol_wolopts = 0;

	/* Check if phydev is valid*/
	/* Check and enable Wake-on-LAN functionality in PHY*/
	if (pdata->phydev) {
		struct ethtool_wolinfo wol = {.cmd = ETHTOOL_GWOL};
		wol.supported = 0;
		wol.wolopts= 0;

		phy_ethtool_get_wol(pdata->phydev, &wol);
		pdata->phy_wol_supported = wol.supported;

		/* Try to enable supported Wake-on-LAN features in PHY*/
		if (wol.supported) {

			device_set_wakeup_capable(&pdata->pdev->dev, 1);

			wol.cmd = ETHTOOL_SWOL;
			wol.wolopts = wol.supported;

			if (!phy_ethtool_set_wol(pdata->phydev, &wol)){
				pdata->phy_wol_wolopts = wol.wolopts;

				enable_irq_wake(pdata->phy_irq);

				device_set_wakeup_enable(&pdata->pdev->dev, 1);
				EMACDBG("Enabled WoL[0x%x] in %s\n", wol.wolopts,
						 pdata->phydev->drv->name);
				pdata->wol_enabled = 1;
			}
		}
	}
}

/*!
 * \brief API to enable or disable PHY hibernation mode
 *
 * \details Write to PHY debug registers at 0x0B bit[15]
 *
 * \param[in] pdata - pointer to platform data, mode
 * enable or disable values.
 *
 * \return void
 *
 * \retval none
 */
static void DWC_ETH_QOS_set_phy_hibernation_mode(struct DWC_ETH_QOS_prv_data *pdata,
								uint mode)
{
	u32 phydata = 0;
	EMACDBG("Enter\n");

	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_PHY_DEBUG_PORT_ADDR_OFFSET,
				DWC_ETH_QOS_PHY_HIB_CTRL);
	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
				&phydata);

	EMACDBG("value read 0x%x\n", phydata);

	phydata = ((phydata & DWC_ETH_QOS_PHY_HIB_CTRL_PS_HIB_EN_WR_MASK)
			   | ((DWC_ETH_QOS_PHY_HIB_CTRL_PS_HIB_EN_MASK & mode) << 15));
	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
				phydata);

	DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_PHY_DEBUG_PORT_ADDR_OFFSET,
				DWC_ETH_QOS_PHY_HIB_CTRL);
	DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
				DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
				&phydata);

	EMACDBG("Exit value written 0x%x\n", phydata);
}

/*!
 * \brief API to enable or disable RX/TX delay in PHY.
 *
 * \details Write to PHY debug registers at 0x0 and 0x5
 * offsets to enable and disable Rx/Tx delay
 *
 * \param[in] pdata - pointer to platform data, rx/tx delay
 * enable or disable values.
 *
 * \return void
 *
 * \retval none
 */

static void set_phy_rx_tx_delay(struct DWC_ETH_QOS_prv_data *pdata,
								uint rx_delay, uint tx_delay)
{
	EMACDBG("Enter\n");

	if ((pdata->phydev->phy_id & pdata->phydev->drv->phy_id_mask) == MICREL_PHY_ID) {
		u16 phydata = 0;
		u16 rx_clk = 0;
		if (pdata->emac_hw_version_type == EMAC_HW_v2_3_1) {
			if(!pdata->io_macro_tx_mode_non_id){
				EMACDBG("No PHY delay settings required for ID mode for "
						"EMAC core version 2.3.1 \n");
				return;
			}
			rx_clk = 22;
			/* RX_CLK to 0*/
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x8,&phydata);
			phydata &= ~(0x1F<<5);
			phydata |= (rx_clk << 5);
			DWC_ETH_QOS_mdio_mmd_register_write_direct(pdata, pdata->phyaddr,
						DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x8,phydata);

			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
						DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x8,&phydata);
			EMACDBG("Read 0x%x from offset 0x8\n",phydata);
		} else {
			rx_clk = 0x1F;
			/* RX_CLK to 0*/
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x8,&phydata);
			phydata &= ~(0x1F);
			phydata |= rx_clk;
			DWC_ETH_QOS_mdio_mmd_register_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x8,phydata);

			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x8,&phydata);
			EMACDBG("Read 0x%x from offset 0x8\n",phydata);
			phydata = 0;

		if (pdata->emac_hw_version_type == EMAC_HW_v2_1_2
			|| pdata->emac_hw_version_type == EMAC_HW_v2_1_1) {
			u16 tx_clk = 0xE;
			/* Provide TX_CLK  delay of -0.06nsec */
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
						DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR, 0x8, &phydata);
			phydata |= (tx_clk << 5);
			DWC_ETH_QOS_mdio_mmd_register_write_direct(pdata, pdata->phyaddr,
						DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR, 0x8, phydata);

			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
						DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR, 0x8, &phydata);
			EMACDBG("Read 0x%x from offset 0x8\n",phydata);
			phydata = 0;
		}

		/*RXD0 = 15,RXD1 = 15,RXD2 = 0,RXD3 = 2*/
		DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x5,&phydata);
		phydata &= ~(0xFF);
		if (pdata->emac_hw_version_type == EMAC_HW_v2_1_2 ||
			pdata->emac_hw_version_type == EMAC_HW_v2_1_1)
			phydata |= ((0x2 << 12) | (0x2 << 8) | (0x2 << 4) | 0x2);
		else
			/* Default settings for EMAC_HW_v2_1_0 */
			phydata |= ((0x0 << 12) | (0x0 << 8) | (0x0 << 4) | 0x0);

			DWC_ETH_QOS_mdio_mmd_register_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x5,phydata);
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x5,&phydata);
			EMACDBG("Read 0x%x from offset 0x5\n",phydata);
			phydata = 0;

			/*RX_CTL to 9*/
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x4,&phydata);
		phydata &= ~(0xF << 4);
		if (pdata->emac_hw_version_type == EMAC_HW_v2_1_2 ||
			pdata->emac_hw_version_type == EMAC_HW_v2_1_1)
			phydata |= (0x2 << 4);
		else
			/* Default settings for EMAC_HW_v2_1_0 */
			phydata |= (0x0 << 4);
		DWC_ETH_QOS_mdio_mmd_register_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x4,phydata);
			DWC_ETH_QOS_mdio_mmd_register_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_MICREL_PHY_DEBUG_MMD_DEV_ADDR,0x4,&phydata);
			EMACDBG("Read 0x%x from offset 0x4\n",phydata);
			phydata = 0;
		}
	} else {
		/* Default values are for PHY AR8035 */
		int phydata = 0;
		DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_PHY_DEBUG_PORT_ADDR_OFFSET,
					DWC_ETH_QOS_PHY_TX_DELAY);
		DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
					&phydata);
		phydata = ((phydata & DWC_ETH_QOS_PHY_TX_DELAY_WR_MASK) |
					((tx_delay & DWC_ETH_QOS_PHY_TX_DELAY_MASK) << 8));
		DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
					phydata);
		EMACDBG("Setting TX delay %#x in PHY\n", phydata);
		DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_PHY_DEBUG_PORT_ADDR_OFFSET,
					DWC_ETH_QOS_PHY_RX_DELAY);
		DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
					&phydata);
		phydata = ((phydata & DWC_ETH_QOS_PHY_RX_DELAY_WR_MASK) |
				((rx_delay & DWC_ETH_QOS_PHY_RX_DELAY_MASK) << 15));
		DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr,
					DWC_ETH_QOS_PHY_DEBUG_PORT_DATAPORT,
					phydata);
		EMACDBG("Setting RX delay %#x in PHY\n", phydata);
	}

	EMACDBG("Exit\n");
}

/*!
 * \brief Determine whether or not to enable or disable
 * RX/TX delay in PHY.
 *
 * \details Determine whether or not to enable or disable
 * the RX/TX delay using the phy interface mode info and
 * speed.
 *
 * \param[in] pdata - pointer to platform data
 *
 * \return void
 *
 * \retval none
 */

static void configure_phy_rx_tx_delay(struct DWC_ETH_QOS_prv_data *pdata)
{
	EMACDBG("Enter\n");
	if ((pdata->emac_hw_version_type == EMAC_HW_v2_3_1)
		&& (pdata->io_macro_phy_intf == RMII_MODE)) {
		EMACDBG("phy rx tx delay setting not required for RMII mode for 2.3.1\n");
		return;
	}

	switch (pdata->speed) {
	case SPEED_1000:
		if (pdata->io_macro_tx_mode_non_id) {
			/* Settings for Non-ID mode */
			set_phy_rx_tx_delay(pdata, ENABLE_RX_DELAY, ENABLE_TX_DELAY);
		} else {
			/* Settings for RGMII ID mode.
			Not applicable for EMAC core version 2.1.0, 2.1.2 and 2.1.1 */
			if (pdata->emac_hw_version_type != EMAC_HW_v2_1_0 &&
				pdata->emac_hw_version_type != EMAC_HW_v2_1_2 &&
				pdata->emac_hw_version_type != EMAC_HW_v2_1_1)
				set_phy_rx_tx_delay(pdata, DISABLE_RX_DELAY, DISABLE_TX_DELAY);
		}
		break;

	case SPEED_100:
	case SPEED_10:
		if (pdata->emac_hw_version_type == EMAC_HW_v2_1_0 ||
			pdata->emac_hw_version_type == EMAC_HW_v2_1_2) {
			if (pdata->io_macro_tx_mode_non_id)
				set_phy_rx_tx_delay(pdata, DISABLE_RX_DELAY, ENABLE_TX_DELAY);
		} else {

			if (pdata->io_macro_tx_mode_non_id ||
				pdata->io_macro_phy_intf == MII_MODE) {
				/* Settings for Non-ID mode or MII mode */
				set_phy_rx_tx_delay(pdata, DISABLE_RX_DELAY, ENABLE_TX_DELAY);
			} else {
				/* Settings for RGMII ID mode */
				/* Not applicable for EMAC core version 2.1.0, 2.1.2 and 2.1.1 */
				if (pdata->emac_hw_version_type != EMAC_HW_v2_1_0 &&
					pdata->emac_hw_version_type != EMAC_HW_v2_1_2 &&
					pdata->emac_hw_version_type != EMAC_HW_v2_1_1)
					set_phy_rx_tx_delay(pdata, DISABLE_RX_DELAY, DISABLE_TX_DELAY);
			}
		}
		break;
	}
	EMACDBG("Exit\n");
}

/*!
 * \brief Function to set RGMII clock and enable bus
 * scaling.
 *
 * \details Function to set RGMII clock and enable bus
 * scaling.
 *
 * \param[in] pdata - pointer to platform data
 *
 * \return void
 *
 * \retval Y_SUCCESS on success and Y_FAILURE on failure.
 */
void DWC_ETH_QOS_set_clk_and_bus_config(struct DWC_ETH_QOS_prv_data *pdata, int speed)
{
	EMACDBG("Enter\n");

	switch (pdata->io_macro_phy_intf) {
	case RGMII_MODE:
		switch (speed) {

		case SPEED_1000:
			pdata->rgmii_clk_rate = RGMII_1000_NOM_CLK_FREQ;
			break;

		case SPEED_100:
			if (pdata->io_macro_tx_mode_non_id)
				pdata->rgmii_clk_rate = RGMII_NON_ID_MODE_100_LOW_SVS_CLK_FREQ;
			else
				pdata->rgmii_clk_rate = RGMII_ID_MODE_100_LOW_SVS_CLK_FREQ;
			break;

		case SPEED_10:
			if (pdata->io_macro_tx_mode_non_id)
				pdata->rgmii_clk_rate = RGMII_NON_ID_MODE_10_LOW_SVS_CLK_FREQ;
			else
				pdata->rgmii_clk_rate = RGMII_ID_MODE_10_LOW_SVS_CLK_FREQ;
			break;
		}
		break;

	case RMII_MODE:
		switch (speed) {
		case SPEED_100:
			pdata->rgmii_clk_rate = RMII_100_LOW_SVS_CLK_FREQ;
			break;

		case SPEED_10:
			pdata->rgmii_clk_rate = RMII_10_LOW_SVS_CLK_FREQ;
			break;
		}
		break;

	case MII_MODE:
		switch (speed) {
		case SPEED_100:
			pdata->rgmii_clk_rate = MII_100_LOW_SVS_CLK_FREQ;
			break;
		case SPEED_10:
			pdata->rgmii_clk_rate = MII_10_LOW_SVS_CLK_FREQ;
			break;
		}
		break;
	}

	switch (speed) {
		case SPEED_1000:
			pdata->vote_idx = VOTE_IDX_1000MBPS;
			break;
		case SPEED_100:
			pdata->vote_idx = VOTE_IDX_100MBPS;
			break;
		case SPEED_10:
			pdata->vote_idx = VOTE_IDX_10MBPS;
			break;
		case 0:
			pdata->vote_idx = VOTE_IDX_0MBPS;
			pdata->rgmii_clk_rate = 0;
			break;
	}

	if (pdata->bus_hdl) {
		if (msm_bus_scale_client_update_request(pdata->bus_hdl, pdata->vote_idx))
			WARN_ON(1);
	}

	if (pdata->res_data->rgmii_clk)
		clk_set_rate(pdata->res_data->rgmii_clk, pdata->rgmii_clk_rate);

	EMACDBG("Exit\n");

}
/*!
 * \brief Function to configure IO macro and DLL settings.
 *
 * \details Function to configure IO macro and DLL settings based
 * on speed and phy interface.
 *
 * \param[in] pdata - pointer to platform data
 *
 * \return void
 *
 * \retval Y_SUCCESS on success and Y_FAILURE on failure.
 */

static inline int DWC_ETH_QOS_configure_io_macro_dll_settings(
			struct DWC_ETH_QOS_prv_data *pdata)
{
	int ret = Y_SUCCESS;

	EMACDBG("Enter\n");

#ifndef DWC_ETH_QOS_EMULATION_PLATFORM
	DWC_ETH_QOS_rgmii_io_macro_dll_reset(pdata);

	/* For RGMII ID mode with internal delay*/
	if (pdata->io_macro_phy_intf == RGMII_MODE && !pdata->io_macro_tx_mode_non_id) {
		EMACDBG("Initialize and configure SDCC DLL\n");
		ret = DWC_ETH_QOS_rgmii_io_macro_sdcdc_init(pdata);
		if (ret < 0) {
			EMACERR("DLL init failed \n");
			return ret;
		}
		if (pdata->speed == SPEED_1000) {
			ret = DWC_ETH_QOS_rgmii_io_macro_sdcdc_config(pdata);
			if (ret < 0) {
				EMACERR("DLL config failed \n");
				return ret;
			}
		}
	} else {
		/* For RGMII Non ID (i.e external delay), RMII and MII modes set DLL bypass */
		DWC_ETH_QOS_sdcc_set_bypass_mode();
	}
#endif
	DWC_ETH_QOS_rgmii_io_macro_init(pdata);

	EMACDBG("Exit\n");
	return ret;
}


/*!
 * \brief Set link parameters for QCA8337.
 *
 * \details This function configures the MAC in 1000
 * Mbps full duplex mode.
 * \param[in] dev - pointer to net_device structure
 *
 * \return 0 on success
 */

static int DWC_ETH_QOS_config_qca_link(struct DWC_ETH_QOS_prv_data* pdata)
{
	struct hw_if_struct *hw_if = &pdata->hw_if;
	int ret = Y_SUCCESS;

	EMACDBG("Enter\n");

	if (pdata->io_macro_phy_intf == RMII_MODE ||
			pdata->io_macro_phy_intf == MII_MODE) {
		hw_if->set_mii_speed_100();
		hw_if->set_full_duplex();
		pdata->vote_idx = VOTE_IDX_100MBPS;
		pdata->speed = SPEED_100;
	} else {
		/* Default setting is for RGMII 1000 Mbps full duplex mode*/
		hw_if->set_gmii_speed();
		hw_if->set_full_duplex();
		pdata->vote_idx = VOTE_IDX_1000MBPS;
		pdata->speed = SPEED_1000;
	}
	pdata->duplex = 1;
	EMACDBG("EMAC configured to speed = %d and full duplex = %d\n",
			pdata->speed,
			pdata->duplex);
	EMACDBG("pdata->interface = %d\n", pdata->interface);
	pdata->oldlink = 1;
	pdata->oldduplex = 1;

	/* Set RGMII clock and bus scale request based on link speed and phy mode */
	DWC_ETH_QOS_set_clk_and_bus_config(pdata, pdata->speed);

	ret = DWC_ETH_QOS_configure_io_macro_dll_settings(pdata);

	EMACDBG("Exit\n");
	return ret;
}

/*!
 * \brief API to adjust link parameters.
 *
 * \details This function will be called by PAL to inform the driver
 * about various link parameters like duplex and speed. This function
 * will configure the MAC based on link parameters.
 *
 * \param[in] dev - pointer to net_device structure
 *
 * \return void
 */

void DWC_ETH_QOS_adjust_link(struct net_device *dev)
{
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	struct phy_device *phydev = pdata->phydev;
	//unsigned long flags;
	int new_state = 0;
	int ret = 0;

	if (!phydev)
		return;

	if (pdata->oldlink == -1 && !phydev->link) {
		pdata->oldlink = phydev->link;
		return;
	}

	DBGPR_MDIO(
		"-->DWC_ETH_QOS_adjust_link. address %d link %d\n",
		phydev->mdio.addr, phydev->link);

	//spin_lock_irqsave(&pdata->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 != pdata->oldduplex) {
			new_state = 1;
			if (phydev->duplex) {
				hw_if->set_full_duplex();
			} else {
				hw_if->set_half_duplex();
#ifdef DWC_ETH_QOS_CERTIFICATION_PKTBURSTCNT_HALFDUPLEX
				/* For Synopsys testing and debugging only */
				{
					UINT phydata;

					/* setting 'Assert CRS on transmit' */
					phydata = 0;
					DWC_ETH_QOS_mdio_read_direct(
						pdata, pdata->phyaddr,
						DWC_ETH_QOS_PHY_CTL, &phydata);
					phydata |= (1 << 11);
					DWC_ETH_QOS_mdio_write_direct(
						pdata, pdata->phyaddr,
						DWC_ETH_QOS_PHY_CTL, phydata);
				}
#endif
			}
			pdata->oldduplex = phydev->duplex;
		}

		/* FLOW ctrl operation */
		if (phydev->pause || phydev->asym_pause) {
			if (pdata->flow_ctrl != pdata->oldflow_ctrl)
				DWC_ETH_QOS_configure_flow_ctrl(pdata);
		}

		if (phydev->speed != pdata->speed) {
			new_state = 1;
			switch (phydev->speed) {
			case SPEED_1000:
				hw_if->set_gmii_speed();
				break;
			case SPEED_100:
				hw_if->set_mii_speed_100();
				break;
			case SPEED_10:
				hw_if->set_mii_speed_10();
				break;
			}
			pdata->speed = phydev->speed;

			EMACDBG("Bypass mode read from device tree = %d\n",
					pdata->io_macro_tx_mode_non_id);

			/* Set PHY delays here */
			configure_phy_rx_tx_delay(pdata);

			/* Set RGMII clock and bus scale request based on link speed and phy mode */
			if (pdata->io_macro_phy_intf != RMII_MODE) {
				DWC_ETH_QOS_set_clk_and_bus_config(pdata, pdata->speed);

				ret = DWC_ETH_QOS_configure_io_macro_dll_settings(pdata);
				if (ret < 0) {
					EMACERR("Failed to configure IO macro and DLL settings\n");
					return;
				}
			}
		}

		if (!pdata->oldlink || (pdata->oldlink == -1)) {
			new_state = 1;
			pdata->oldlink = 1;
			netif_carrier_on(dev);
		}
	} else if (pdata->oldlink) {
		netif_carrier_off(dev);
		new_state = 1;
		pdata->oldlink = 0;
		pdata->speed = 0;
		pdata->oldduplex = -1;
	}

	if (new_state) {
		phy_print_status(phydev);

#ifdef CONFIG_MSM_BOOT_TIME_MARKER
		if ((phydev->link == 1) && !pdata->print_kpi) {
			place_marker("M - Ethernet is Ready.Link is UP");
			pdata->print_kpi = 1;
		}
#endif

		if (pdata->phy_intr_en && !pdata->wol_enabled)
			DWC_ETH_QOS_request_phy_wol(pdata);

		if (pdata->ipa_enabled && netif_running(dev)) {
			if (phydev->link == 1)
				 DWC_ETH_QOS_ipa_offload_event_handler(pdata, EV_PHY_LINK_UP);
			else if (phydev->link == 0)
				DWC_ETH_QOS_ipa_offload_event_handler(pdata, EV_PHY_LINK_DOWN);
		}

		if (phydev->link == 1)
			pdata->hw_if.start_mac_tx_rx();
		else if (phydev->link == 0 && pdata->io_macro_phy_intf != RMII_MODE)
			DWC_ETH_QOS_set_clk_and_bus_config(pdata, SPEED_10);
	}

	/* At this stage, it could be need to setup the EEE or adjust some
	 * MAC related HW registers.
	 */
	pdata->eee_enabled = DWC_ETH_QOS_eee_init(pdata);

	//spin_unlock_irqrestore(&pdata->lock, flags);

	DBGPR_MDIO("<--DWC_ETH_QOS_adjust_link\n");
}

bool DWC_ETH_QOS_is_phy_link_up(struct DWC_ETH_QOS_prv_data *pdata)
{
	/* PHY driver initializes phydev->link=1.
	 * So, phydev->link is 1 even on booup with no PHY connected.
	 * phydev->link is valid only after adjust_link is called once.
	 * Use (pdata->oldlink != -1) to indicate phy link is not up */
	return pdata->always_on_phy ? 1 :
		((pdata->oldlink != -1) && pdata->phydev && pdata->phydev->link);
}

/*!
 * \brief API to initialize PHY.
 *
 * \details This function will initializes the driver's PHY state and attaches
 * the PHY to the MAC driver.
 *
 * \param[in] dev - pointer to net_device structure
 *
 * \return integer
 *
 * \retval 0 on success & negative number on failure.
 */

static int DWC_ETH_QOS_init_phy(struct net_device *dev)
{
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
	struct phy_device *phydev = NULL;
	u32 phydata = 0;
	int ret = 0;

	DBGPR_MDIO("-->DWC_ETH_QOS_init_phy\n");

	pdata->oldlink = -1;
	pdata->speed = 0;
	pdata->oldduplex = -1;

	phydev = mdiobus_get_phy(pdata->mii, pdata->phyaddr);
	if (IS_ERR(phydev)) {
		pr_alert("%s: Could not attach to PHY\n", dev->name);
		return PTR_ERR(phydev);
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
	phydev->skip_sw_reset = true;
#endif
	ret = phy_connect_direct(dev, phydev, &DWC_ETH_QOS_adjust_link,
							pdata->interface);
	if (ret) {
		EMACERR("phy_connect_direct failed\n");
		return ret;
	}

	if (phydev->phy_id == 0) {
		pr_alert("%s: Invalid phy id\n", dev->name);
		phy_disconnect(phydev);
		return -ENODEV;
	}

#ifndef DWC_ETH_QOS_EMULATION_PLATFORM
	if ((pdata->enable_phy_intr && ((phydev->phy_id == ATH8031_PHY_ID)
		|| (phydev->phy_id == ATH8035_PHY_ID)
		|| ((phydev->phy_id & phydev->drv->phy_id_mask) == MICREL_PHY_ID)))) {
		pdata->phy_intr_en = true;
		EMACDBG("Phy interrupt enabled\n");
	} else
		EMACDBG("Phy polling enabled\n");
#endif


	if (pdata->interface == PHY_INTERFACE_MODE_GMII || pdata->interface == PHY_INTERFACE_MODE_RGMII) {
		phy_set_max_speed(phydev, SPEED_1000);
		/* Half duplex not supported */
		phydev->supported &= ~(SUPPORTED_10baseT_Half | SUPPORTED_100baseT_Half | SUPPORTED_1000baseT_Half);
	} else if ((pdata->interface == PHY_INTERFACE_MODE_MII) || (pdata->interface == PHY_INTERFACE_MODE_RMII)) {
		phy_set_max_speed(phydev, SPEED_100);
		/* Half duplex is not supported */
		phydev->supported &= ~(SUPPORTED_10baseT_Half | SUPPORTED_100baseT_Half);
	}
	phydev->advertising = phydev->supported;

	if (pdata->res_data->early_eth_en ) {
		phydev->autoneg = AUTONEG_DISABLE;
		phydev->speed = SPEED_100;
		phydev->duplex = DUPLEX_FULL;
		phydev->advertising = phydev->supported;
		phydev->advertising &= ~(SUPPORTED_1000baseT_Full);
		EMACDBG("Set max speed to SPEED_100 as early ethernet enabled\n");
	}

	pdata->phydev = phydev;

	/* Disable smart speed function for AR8035*/
	if (phydev->phy_id == ATH8035_PHY_ID) {
		DBGPR_MDIO("%s: attached to PHY (UID 0x%x) Link = %d\n", dev->name,
			   phydev->phy_id, phydev->link);

		DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, DWC_ETH_QOS_PHY_SMART_SPEED, &phydata);
		phydata &= ~(1<<5);

		DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr, DWC_ETH_QOS_PHY_SMART_SPEED, phydata);

		DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, MII_BMCR, &phydata);

		phydata |= (1 << 15);

		DWC_ETH_QOS_mdio_write_direct(pdata, pdata->phyaddr, MII_BMCR, phydata);

		DWC_ETH_QOS_mdio_read_direct(pdata, pdata->phyaddr, DWC_ETH_QOS_PHY_SMART_SPEED, &phydata);
		DBGPR_MDIO( "Smart Speed Reg (%#x) = %#x\n", DWC_ETH_QOS_PHY_SMART_SPEED, phydata);

		DWC_ETH_QOS_set_phy_hibernation_mode(pdata, 0);
	}

	if (pdata->phy_intr_en) {

		INIT_WORK(&pdata->emac_phy_work, DWC_ETH_QOS_defer_phy_isr_work);
		init_completion(&pdata->clk_enable_done);

		ret = request_irq(pdata->phy_irq, DWC_ETH_QOS_PHY_ISR,
						IRQF_SHARED, DEV_NAME, pdata);
		if (ret) {
			pr_alert("Unable to register PHY IRQ %d\n", pdata->phy_irq);
			return ret;
		}

		phydev->irq = PHY_IGNORE_INTERRUPT;
		phydev->interrupts =  PHY_INTERRUPT_ENABLED;

		if (!(phydev->drv->config_intr &&
			!phydev->drv->config_intr(phydev))){
			EMACERR("Failed to configure PHY interrupts");
			BUG();
		}
	}

	phy_start(pdata->phydev);

	DBGPR_MDIO("<--DWC_ETH_QOS_init_phy\n");

	return 0;
}

static bool DWC_ETH_QOS_phy_id_match(u32 phy_id)
{
	int i;
	EMACDBG("Enter\n");

	for (i = 0; i < ARRAY_SIZE(qca8337_phy_ids); i++) {
		if (phy_id == qca8337_phy_ids[i]) {
			pr_alert("qca8337: PHY_ID at %s: id:%08x\n", DEV_NAME, phy_id);
			return true;
		}
	}

	EMACDBG("Exit\n");
	return false;
}

/*!
 * \brief API to register mdio.
 *
 * \details This function will allocate mdio bus and register it
 * phy layer.
 *
 * \param[in] dev - pointer to net_device structure
 *
 * \return 0 on success and -ve on failure.
 */

int DWC_ETH_QOS_mdio_register(struct net_device *dev)
{
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
	struct mii_bus *new_bus = NULL;
	int phyaddr = 0;
	unsigned short phy_detected = 0;
	int ret = Y_SUCCESS;
	int phy_reg_read_status, mii_status;
	u32 phy_id, phy_id1, phy_id2;
	u32 phydata = 0;

	DBGPR_MDIO("-->DWC_ETH_QOS_mdio_register\n");

	if (pdata->res_data->phy_addr != -1) {
		phy_reg_read_status =
		   DWC_ETH_QOS_mdio_read_direct(pdata, pdata->res_data->phy_addr, MII_BMSR,
										&mii_status);
		if (phy_reg_read_status == 0) {
			if (mii_status != 0x0000 && mii_status != 0xffff) {
				phy_detected = 1;
				phyaddr = pdata->res_data->phy_addr;
				EMACINFO("skip_phy_detection (phyaddr)%d\n", phyaddr);
				goto skip_phy_detection;
			} else
				EMACERR("Invlaid phy address specified in device tree\n");
		}
	}

	/* find the phy ID or phy address which is connected to our MAC */
	for (phyaddr = 0; phyaddr < 32; phyaddr++) {

		phy_reg_read_status =
		    DWC_ETH_QOS_mdio_read_direct(pdata, phyaddr, MII_BMSR,
						 &mii_status);
		if (phy_reg_read_status == 0) {
			if (mii_status != 0x0000 && mii_status != 0xffff) {
				pr_alert
				("%s: Phy detected at ID/ADDR %d\n",
				DEV_NAME, phyaddr);
				phy_detected = 1;
				break;
			}
		} else if (phy_reg_read_status < 0) {
			pr_alert(
			"%s: Error reading the phy register MII_BMSR for phy ID/ADDR %d\n",
			    DEV_NAME, phyaddr);
		}
	}

	if (!phy_detected) {
		pr_alert("%s: No phy could be detected\n", DEV_NAME);
		return -ENOLINK;
	}

	skip_phy_detection:

	pdata->phyaddr = phyaddr;
	pdata->bus_id = 0x1;
	pdata->phy_intr_en = false;
	pdata->always_on_phy = false;

	if(pdata->res_data->early_eth_en) {
		EMACDBG("Updated speed to 100 in emac\n");
		pdata->hw_if.set_mii_speed_100();

		phydata = BMCR_SPEED100;
		phydata |= BMCR_FULLDPLX;
		EMACDBG("Updated speed to 100 and autoneg disable\n");
		pdata->hw_if.write_phy_regs(pdata->phyaddr,
				MII_BMCR, phydata);
	}

	DBGPHY_REGS(pdata);

	new_bus = mdiobus_alloc();
	if (!new_bus) {
		pr_alert("Unable to allocate mdio bus\n");
		return -ENOMEM;
	}

	new_bus->name = "dwc_phy";
	new_bus->read = DWC_ETH_QOS_mdio_read;
	new_bus->write = DWC_ETH_QOS_mdio_write;
	new_bus->reset = DWC_ETH_QOS_mdio_reset;
	snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name,
		 pdata->bus_id);
	new_bus->priv = dev;
	new_bus->phy_mask = ~(1 << phyaddr);
	new_bus->parent = &pdata->pdev->dev;
	ret = mdiobus_register(new_bus);
	if (ret != 0) {
		pr_alert("%s: Cannot register as MDIO bus\n", new_bus->name);
		mdiobus_free(new_bus);
		return ret;
	}
	pdata->mii = new_bus;

	/* Check for QCA8337 phy chip id */
	phy_reg_read_status = DWC_ETH_QOS_mdio_read_direct(
	   pdata, phyaddr, MII_PHYSID1, &phy_id1);
	phy_reg_read_status = DWC_ETH_QOS_mdio_read_direct(
	   pdata, phyaddr, MII_PHYSID2, &phy_id2);
	if (phy_reg_read_status != 0) {
		EMACERR("unable to read phy id's: %d\n", phy_reg_read_status);
		goto err_out_phy_connect;
	}
	if (pdata->io_macro_phy_intf == RMII_MODE) {
		pdata->speed = SPEED_100; //Default speed
		DWC_ETH_QOS_set_clk_and_bus_config(pdata, pdata->speed);
		ret = DWC_ETH_QOS_configure_io_macro_dll_settings(pdata);
		if (ret < 0) {
			EMACERR("Failed to configure IO macro and DLL settings\n");
			goto err_out_phy_connect;
		}
	}
	phy_id = phy_id1 << 16;
	phy_id |= phy_id2;
	if (DWC_ETH_QOS_phy_id_match(phy_id) == true) {
		EMACDBG("QCA8337 detected\n");
		ret = DWC_ETH_QOS_config_qca_link(pdata);
		if (unlikely(ret)) {
			EMACERR("Failed to configure link in 1 Gbps/full duplex mode"
				" (error: %d)\n", ret);
			goto err_out_phy_connect;
		} else{
			pdata->always_on_phy = true;
			goto mdio_alloc_done;
		}
	}

	ret = DWC_ETH_QOS_init_phy(dev);
	if (unlikely(ret)) {
		pr_alert("Cannot attach to PHY (error: %d)\n", ret);
		goto err_out_phy_connect;
	}

 mdio_alloc_done:
	DBGPR_MDIO("<--DWC_ETH_QOS_mdio_register\n");

	return ret;

 err_out_phy_connect:
	DWC_ETH_QOS_mdio_unregister(dev);
	return ret;
}

/*!
 * \brief API to unregister mdio.
 *
 * \details This function will unregister mdio bus and free's the memory
 * allocated to it.
 *
 * \param[in] dev - pointer to net_device structure
 *
 * \return void
 */

void DWC_ETH_QOS_mdio_unregister(struct net_device *dev)
{
	struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);

	DBGPR_MDIO("-->DWC_ETH_QOS_mdio_unregister\n");

	if (pdata->phydev) {
		phy_stop(pdata->phydev);
		phy_disconnect(pdata->phydev);
		pdata->phydev = NULL;
	}

	mdiobus_unregister(pdata->mii);
	pdata->mii->priv = NULL;
	mdiobus_free(pdata->mii);
	pdata->mii = NULL;

	DBGPR_MDIO("<--DWC_ETH_QOS_mdio_unregister\n");
}
