blob: 9ee36cde259400ba4ea40ffcffeebcf0ea25a676 [file] [log] [blame]
/* 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");
}