/* ========================================================================= * 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_eee.c * @brief: Driver functions. */ #include "DWC_ETH_QOS_yheader.h" /* Clause 22 registers to access clause 45 register set */ #define MMD_CTRL_REG 0x0D /* MMD Access Control Register */ #define MMD_ADDR_DATA_REG 0x0E /* MMD Access Address Data Register */ /* MMD Access Control register fields */ #define MMD_CTRL_FUNC_ADDR 0x0000 /* address */ #define MMD_CTRL_FUNC_DATA_NOINCR 0x4000 /* data, no post increment */ #define MMD_CTRL_FUNC_DATA_INCR_ON_RDWT 0x8000 /* data, post increment on reads & writes */ #define MMD_CTRL_FUNC_DATA_INCR_ON_WT 0xC000 /* data, post increment on writes only */ /* Clause 45 expansion register */ #define CL45_PCS_EEE_ABLE 0x14 /* EEE Capability register */ #define CL45_ADV_EEE_REG 0x3C /* EEE advertisement */ #define CL45_AN_EEE_LPABLE_REG 0x3D /* EEE Link Partner ability reg */ #define CL45_CLK_STOP_EN_REG 0x0 /* Clock Stop enable reg */ /* Clause 45 expansion registers fields */ #define CL45_LP_ADV_EEE_STATS_1000BASE_T 0x0004 /* LP EEE capabilities status */ #define CL45_CLK_STOP_EN 0x400 /* Enable xMII Clock Stop */ void DWC_ETH_QOS_enable_eee_mode(struct DWC_ETH_QOS_prv_data *pdata) { struct DWC_ETH_QOS_tx_wrapper_descriptor *tx_desc_data = NULL; hw_interface_t *hw_if = &(pdata->hw_if); int tx_idle = 0, qInx; DBGPR_EEE("-->DWC_ETH_QOS_enable_eee_mode\n"); for (qInx = 0; qInx < pdata->tx_queue_cnt; qInx++) { tx_desc_data = GET_TX_WRAPPER_DESC(qInx); if ((tx_desc_data->dirty_tx == tx_desc_data->cur_tx) && (pdata->tx_path_in_lpi_mode == false)) { tx_idle = 1; } else { tx_idle = 0; break; } } if (tx_idle) hw_if->set_eee_mode(); DBGPR_EEE("<--DWC_ETH_QOS_enable_eee_mode\n"); } void DWC_ETH_QOS_disable_eee_mode(struct DWC_ETH_QOS_prv_data *pdata) { hw_interface_t *hw_if = &(pdata->hw_if); DBGPR_EEE("-->DWC_ETH_QOS_disable_eee_mode\n"); hw_if->reset_eee_mode(); del_timer_sync(&pdata->eee_ctrl_timer); pdata->tx_path_in_lpi_mode = false; DBGPR_EEE("-->DWC_ETH_QOS_disable_eee_mode\n"); } /*! * \brief API to control EEE mode. * * \details This function will move the MAC transmitter in LPI mode * if there is no data transfer and MAC is not already in LPI state. * * \param[in] data - data hook * * \return void */ static void DWC_ETH_QOS_eee_ctrl_timer(struct timer_list *t) { struct DWC_ETH_QOS_prv_data *pdata = from_timer(pdata, t, eee_ctrl_timer); DBGPR_EEE("-->DWC_ETH_QOS_eee_ctrl_timer\n"); DWC_ETH_QOS_enable_eee_mode(pdata); DBGPR_EEE("<--DWC_ETH_QOS_eee_ctrl_timer\n"); } static void DWC_ETH_QOS_mmd_phy_indirect(struct mii_bus *bus, int regAddr, int devAddr, int phyAddr) { /* Write the desired MMD devAddr */ bus->write(bus, phyAddr, MMD_CTRL_REG, devAddr); /* Write the desired MMD regAddr */ bus->write(bus, phyAddr, MMD_ADDR_DATA_REG, regAddr); /* Select the Function : DATA with no post increment */ bus->write(bus, phyAddr, MMD_CTRL_REG, (devAddr | MMD_CTRL_FUNC_DATA_NOINCR)); } /*! * \brief API to read data from the MMD registers. * * \details This function will read data from the MMD(clause 45) registers * using clause 22 registers. The procedure to read MMD registers is, * 1. Write the desired MMD device addr into reg 13 * 2. Write the desired MMD reg addr into reg 14 * 3. Select the desired Function - MMD data command by writing in reg 13 * 4. Read the content of the MMD's selected reg through reg 14 * * \param[in] bus - the target MII bus * \param[in] regAddr - desired MMD reg addr to be read * \param[in] devAddr - desired MMD address * \param[in] phyAddr - PHY addr/id on the MII bus * * \return integer */ static int DWC_ETH_QOS_phy_read_mmd_indirect(struct mii_bus *bus, int regAddr, int devAddr, int phyAddr) { uint32_t ret; DBGPR_EEE("-->DWC_ETH_QOS_phy_read_mmd_indirect\n"); DWC_ETH_QOS_mmd_phy_indirect(bus, regAddr, devAddr, phyAddr); /* read the content of the MMD's selected register */ ret = bus->read(bus, phyAddr, MMD_ADDR_DATA_REG); DBGPR_EEE("<--DWC_ETH_QOS_phy_read_mmd_indirect\n"); return ret; } /*! * \brief API to write data into the MMD registers. * * \details This function will write data into MMD(clause 45) registers * using clause 22 registers. The procedure to write MMD registers is, * 1. Write the desired MMD device addr into reg 13 * 2. Write the desired MMD reg addr into reg 14 * 3. Select the desired Function - MMD data command by writing in reg 13 * 4. Write the data into MMD's selected reg through reg 14 * * \param[in] bus - the target MII bus * \param[in] regAddr - desired MMD reg addr to be written * \param[in] devAddr - desired MMD address * \param[in] phyAddr - PHY addr/id on the MII bus * \param[in] data - data to write into the MMD register * * \return void */ static void DWC_ETH_QOS_phy_write_mmd_indirect(struct mii_bus *bus, int regAddr, int devAddr, int phyAddr, uint32_t data) { DBGPR_EEE("-->DWC_ETH_QOS_phy_write_mmd_indirect\n"); DWC_ETH_QOS_mmd_phy_indirect(bus, regAddr, devAddr, phyAddr); /* Write the data into MMD's selected register */ bus->write(bus, phyAddr, MMD_ADDR_DATA_REG, data); DBGPR_EEE("<--DWC_ETH_QOS_phy_write_mmd_indirect\n"); } #if 0 #define MDIO_EEE_100TX 0x0002 /* EEE is supported for 100BASE-TX */ #define MDIO_EEE_1000T 0x0004 /* EEE is supported for 1000BASE-T */ #define MDIO_EEE_10GT 0x0008 /* EEE is supported for 10GBASE-T */ #define MDIO_EEE_1000KX 0x0010 /* EEE is supported for 1000BASE-KX */ #define MDIO_EEE_10GKX4 0x0020 /* EEE is supported for 10GBASE-KX4 */ #define MDIO_EEE_10GKR 0x0040 /* EEE is supported for 10GBASE KR */ /* A small helper function that translates MMD EEE Capability (3.20) bits * to ethtool supported settings. * */ static uint32_t DWC_ETH_QOS_mmd_eee_cap_to_ethtool_sup_t(u16 eee_cap) { uint32_t supported = 0; if (eee_cap & MDIO_EEE_100TX) supported |= SUPPORTED_100baseT_Full; if (eee_cap & MDIO_EEE_1000T) supported |= SUPPORTED_1000baseT_Full; if (eee_cap & MDIO_EEE_10GT) supported |= SUPPORTED_10000baseT_Full; if (eee_cap & MDIO_EEE_1000KX) supported |= SUPPORTED_1000baseKX_Full; if (eee_cap & MDIO_EEE_10GKX4) supported |= SUPPORTED_10000baseKX4_Full; if (eee_cap & MDIO_EEE_10GKR) supported |= SUPPORTED_10000baseKR_Full; return supported; } /* A small helper function that translates the MMD EEE Advertisment (7.60) * and MMD EEE Link Partner Ability (7.61) bits to ethtool advertisement * settings. * */ static inline uint32_t DWC_ETH_QOS_mmd_eee_adv_to_ethtool_adv_t(u16 eee_adv) { uint32_t adv = 0; if (eee_adv & MDIO_EEE_100TX) adv |= ADVERTISED_100baseT_Full; if (eee_adv & MDIO_EEE_1000T) adv |= ADVERTISED_1000baseT_Full; if (eee_adv & MDIO_EEE_10GT) adv |= ADVERTISED_10000baseT_Full; if (eee_adv & MDIO_EEE_1000KX) adv |= ADVERTISED_1000baseKX_Full; if (eee_adv & MDIO_EEE_10GKX4) adv |= ADVERTISED_10000baseKX4_Full; if (eee_adv & MDIO_EEE_10GKR) adv |= ADVERTISED_10000baseKR_Full; return adv; } #endif /*! * \brief API to initialize and check EEE mode. * * \details This function checks if the EEE is supported by * looking at the MMD registes and it also programs the MMD * register 3.0 setting the "Clock stop enable" bit if required. * * \param[in] phydev - pointer to target phy_device structure * \param[in] clk_stop_enable - PHY may stop the clock during LPI * * \return integer * * \retval zero if EEE is supported else return -ve number. */ static int DWC_ETH_QOS_phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) { int ret = -EPROTONOSUPPORT; DBGPR_EEE("-->DWC_ETH_QOS_phy_init_eee\n"); /* According to 802.3az,the EEE is supported only in full duplex-mode. * Also EEE feature is active when core is operating with MII, GMII, * SGMII or RGMII. */ if ((phydev->duplex == DUPLEX_FULL) && ((phydev->interface == PHY_INTERFACE_MODE_MII) || (phydev->interface == PHY_INTERFACE_MODE_GMII) || (phydev->interface == PHY_INTERFACE_MODE_SGMII) || (phydev->interface == PHY_INTERFACE_MODE_RGMII))) { int eee_lp, eee_cap, eee_adv; //uint32_t cap,lp , adv; int status;//, idx; /* Read phy status to properly get the right settings */ status = phy_read_status(phydev); if (status) return status; /* First check if the EEE ability is supported */ eee_cap = DWC_ETH_QOS_phy_read_mmd_indirect(phydev->mdio.bus, CL45_PCS_EEE_ABLE, MDIO_MMD_PCS, phydev->mdio.addr); if (eee_cap < 0) return eee_cap; /* cap = DWC_ETH_QOS_mmd_eee_cap_to_ethtool_sup_t(eee_cap); if (!cap) goto eee_exit; */ /* check whether link Partner support EEE or not */ eee_lp = DWC_ETH_QOS_phy_read_mmd_indirect(phydev->mdio.bus, CL45_AN_EEE_LPABLE_REG, MDIO_MMD_AN, phydev->mdio.addr); if (eee_lp < 0) return eee_lp; eee_adv = DWC_ETH_QOS_phy_read_mmd_indirect(phydev->mdio.bus, CL45_ADV_EEE_REG, MDIO_MMD_AN, phydev->mdio.addr); if (eee_adv < 0) return eee_adv; /* //TODO:check this adv = DWC_ETH_QOS_mmd_eee_adv_to_ethtool_adv_t(eee_adv); lp = DWC_ETH_QOS_mmd_eee_adv_to_ethtool_adv_t(eee_lp); idx = phy_find_setting(phydev->speed, phydev->duplex); if ((lp & adv & settings[idx].setting)) goto eee_exit; */ if (clk_stop_enable) { /* Configure the PHY to stop receiving xMII * clock while it is signaling LPI. */ int val = DWC_ETH_QOS_phy_read_mmd_indirect(phydev->mdio.bus, CL45_CLK_STOP_EN_REG, MDIO_MMD_PCS, phydev->mdio.addr); if (val < 0) return val; val |= CL45_CLK_STOP_EN; DWC_ETH_QOS_phy_write_mmd_indirect(phydev->mdio.bus, CL45_CLK_STOP_EN_REG, MDIO_MMD_PCS, phydev->mdio.addr, val); } ret = 0; /* EEE supported */ } DBGPR_EEE("<--DWC_ETH_QOS_phy_init_eee\n"); //eee_exit: return ret; } /*! * \brief API to initialize EEE mode. * * \details This function enables the LPI state and start the timer * to verify whether the tx path can enter in LPI state if * a. GMAC supports EEE mode & * b. phy can also manage EEE. * * \param[in] pdata - pointer to private data structure * * \return bool * * \retval true on success & false on failure. */ bool DWC_ETH_QOS_eee_init(struct DWC_ETH_QOS_prv_data *pdata) { hw_interface_t *hw_if = &(pdata->hw_if); bool ret = false; DBGPR_EEE("-->DWC_ETH_QOS_eee_init\n"); /* HW supports the EEE feature */ if (pdata->hw_feat.eee_sel) { #ifndef DWC_ETH_QOS_CUSTOMIZED_EEE_TEST /* check if the PHY supports EEE */ if (DWC_ETH_QOS_phy_init_eee(pdata->phydev, 1)) goto phy_eee_failed; #endif /* DWC_ETH_QOS_CUSTOMIZED_EEE_TEST */ if (!pdata->eee_active) { pdata->eee_active = 1; timer_setup(&pdata->eee_ctrl_timer, DWC_ETH_QOS_eee_ctrl_timer, 0); mod_timer(&pdata->eee_ctrl_timer, DWC_ETH_QOS_LPI_TIMER(DWC_ETH_QOS_DEFAULT_LPI_TIMER)); add_timer(&pdata->eee_ctrl_timer); hw_if->set_eee_timer(DWC_ETH_QOS_DEFAULT_LPI_LS_TIMER, DWC_ETH_QOS_DEFAULT_LPI_TWT_TIMER); if (pdata->use_lpi_tx_automate) hw_if->set_lpi_tx_automate(); } else { /* When EEE has been already initialized we have to modify * the PLS bit in MAC_LPI_Control_Status reg according to * PHY link status. * */ hw_if->set_eee_pls(pdata->phydev->link); } DBGPR_EEE("EEE initialized\n"); ret = true; } DBGPR_EEE("<--DWC_ETH_QOS_eee_init\n"); #ifndef DWC_ETH_QOS_CUSTOMIZED_EEE_TEST phy_eee_failed: #endif return ret; } #define MAC_LPS_TLPIEN 0x00000001 #define MAC_LPS_TLPIEX 0x00000002 #define MAC_LPS_RLPIEN 0x00000004 #define MAC_LPS_RLPIEX 0x00000008 void DWC_ETH_QOS_handle_eee_interrupt(struct DWC_ETH_QOS_prv_data *pdata) { hw_interface_t *hw_if = &(pdata->hw_if); uint32_t lpi_status; DBGPR_EEE("-->DWC_ETH_QOS_handle_eee_interrupt\n"); lpi_status = hw_if->get_lpi_status(); DBGPR_EEE("MAC_LPI_Control_Status = %#x\n", lpi_status); if (lpi_status & MAC_LPS_TLPIEN) { pdata->tx_path_in_lpi_mode = 1; pdata->xstats.tx_path_in_lpi_mode_irq_n++; DBGPR_EEE("MAC Transmitter has entered the LPI state\n"); } if (lpi_status & MAC_LPS_TLPIEX) { pdata->tx_path_in_lpi_mode = 0; pdata->xstats.tx_path_exit_lpi_mode_irq_n++; DBGPR_EEE("MAC Transmitter has exited the LPI state\n"); } if (lpi_status & MAC_LPS_RLPIEN) { pdata->xstats.rx_path_in_lpi_mode_irq_n++; DBGPR_EEE("MAC Receiver has entered the LPI state\n"); } if (lpi_status & MAC_LPS_RLPIEX) { pdata->xstats.rx_path_exit_lpi_mode_irq_n++; DBGPR_EEE("MAC Receiver has exited the LPI state\n"); } DBGPR_EEE("<--DWC_ETH_QOS_handle_eee_interrupt\n"); }