diff --git a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c index 6aef0b4a..4542bd00 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c @@ -198,6 +198,61 @@ err_axi_cbb: return ret; } +/** + * @brief ether_conf_eee - Init and configure EEE LPI in the MAC + * + * Algorithm: + * 1) Check if EEE is requested to be enabled/disabled. + * 2) If enabled, then check if current PHY speed/mode supports EEE. + * 3) If PHY supports, then enable the Tx LPI timers in MAC and set EEE active. + * 4) Else disable the Tx LPI timers in MAC and set EEE inactive. + * + * @param[in] pdata: Pointer to driver private data structure + * @param[in] tx_lpi_enable: Flag to indicate Tx LPI enable (1) or disable (0) + * + * @note MAC and PHY need to be initialized. + * + * @retval OSI_ENABLE if EEE is enabled/active + * @retval OSI_DISABLE if EEE is disabled/inactive + */ +int ether_conf_eee(struct ether_priv_data *pdata, unsigned int tx_lpi_enable) +{ + int ret = 0; + struct phy_device *phydev = pdata->phydev; + unsigned int enable = tx_lpi_enable; + + if (tx_lpi_enable) { + /* phy_init_eee() returns 0 if EEE is supported by the PHY */ + if (phy_init_eee(phydev, OSI_ENABLE)) { + /* PHY does not support EEE, disable it in MAC */ + enable = OSI_DISABLE; + } else { + /* PHY supports EEE, if link is up enable EEE */ + enable = (unsigned int)phydev->link; + } + } + + /* Enable EEE */ + ret = osi_configure_eee(pdata->osi_core, + enable, + pdata->tx_lpi_timer); + /* Return current status of EEE based on OSI API success/failure */ + if (ret != 0) { + if (enable) { + dev_warn(pdata->dev, "Failed to enable EEE\n"); + ret = OSI_DISABLE; + } else { + dev_warn(pdata->dev, "Failed to disable EEE\n"); + ret = OSI_ENABLE; + } + } else { + /* EEE enabled/disabled successfully as per enable flag */ + ret = enable; + } + + return ret; +} + /** * @brief Adjust link call back * @@ -215,6 +270,7 @@ static void ether_adjust_link(struct net_device *dev) struct phy_device *phydev = pdata->phydev; int new_state = 0, speed_changed = 0; unsigned long val; + unsigned int eee_enable = OSI_DISABLE; if (phydev == NULL) { return; @@ -273,6 +329,13 @@ static void ether_adjust_link(struct net_device *dev) "failed to do pad caliberation\n"); } } + + /* Configure EEE if it is enabled */ + if (pdata->eee_enabled && pdata->tx_lpi_enabled) { + eee_enable = OSI_ENABLE; + } + + pdata->eee_active = ether_conf_eee(pdata, eee_enable); } /** @@ -1157,6 +1220,27 @@ static int ether_therm_init(struct ether_priv_data *pdata) } #endif /* THERMAL_CAL */ +/** + * @brief Initialize default EEE LPI configurations + * + * Algorithm: Update the defalt configuration/timers for EEE LPI in driver + * private data structure + * + * @param[in] pdata: OSD private data. + */ +static inline void ether_init_eee_params(struct ether_priv_data *pdata) +{ + if (pdata->hw_feat.eee_sel) { + pdata->eee_enabled = OSI_ENABLE; + } else { + pdata->eee_enabled = OSI_DISABLE; + } + + pdata->tx_lpi_enabled = pdata->eee_enabled; + pdata->eee_active = OSI_DISABLE; + pdata->tx_lpi_timer = OSI_DEFAULT_TX_LPI_TIMER; +} + /** * @brief Call back to handle bring up of Ethernet interface * @@ -1280,6 +1364,9 @@ static int ether_open(struct net_device *dev) goto err_r_irq; } + /* Init EEE configuration */ + ether_init_eee_params(pdata); + /* Start the MAC */ osi_start_mac(pdata->osi_core); diff --git a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h index 97f281d6..4036160d 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h +++ b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h @@ -308,6 +308,14 @@ struct ether_priv_data { bool clks_enable; /** Delayed work queue to read RMON counters periodically */ struct delayed_work ether_stats_work; + /** Flag to check if EEE LPI is enabled for the MAC */ + unsigned int eee_enabled; + /** Flag to check if EEE LPI is active currently */ + unsigned int eee_active; + /** Flag to check if EEE LPI is enabled for MAC transmitter */ + unsigned int tx_lpi_enabled; + /** Time (usec) MAC waits to enter LPI after Tx complete */ + unsigned int tx_lpi_timer; }; /** @@ -381,4 +389,5 @@ int ether_handle_hwtstamp_ioctl(struct ether_priv_data *pdata, struct ifreq *ifr); int ether_handle_priv_ts_ioctl(struct ether_priv_data *pdata, struct ifreq *ifr); +int ether_conf_eee(struct ether_priv_data *pdata, unsigned int tx_lpi_enable); #endif /* ETHER_LINUX_H */ diff --git a/drivers/net/ethernet/nvidia/nvethernet/ethtool.c b/drivers/net/ethernet/nvidia/nvethernet/ethtool.c index ebbf2682..df1130f2 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ethtool.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ethtool.c @@ -197,6 +197,14 @@ static const struct eqos_stats eqos_mmc[] = { EQOS_MMC_STAT(mmc_rx_fifo_overflow), EQOS_MMC_STAT(mmc_rx_vlan_frames_gb), EQOS_MMC_STAT(mmc_rx_watchdog_error), + EQOS_MMC_STAT(mmc_rx_receive_error), + EQOS_MMC_STAT(mmc_rx_ctrl_frames_g), + + /* LPI */ + EQOS_MMC_STAT(mmc_tx_lpi_usec_cntr), + EQOS_MMC_STAT(mmc_tx_lpi_tran_cntr), + EQOS_MMC_STAT(mmc_rx_lpi_usec_cntr), + EQOS_MMC_STAT(mmc_rx_lpi_tran_cntr), /* IPv4 */ EQOS_MMC_STAT(mmc_rx_ipv4_gd), @@ -700,6 +708,211 @@ static int ether_get_coalesce(struct net_device *dev, return 0; } +/* + * @brief Get current EEE configuration in MAC/PHY + * + * Algorithm: This function is invoked by kernel when user request to get + * current EEE parameters. The function invokes the PHY framework to fill + * the supported & advertised EEE modes, as well as link partner EEE modes + * if it is available. + * + * @param[in] ndev: Net device data. + * @param[in] cur_eee: Pointer to struct ethtool_eee + * + * @note MAC and PHY need to be initialized. + * + * @retval 0 on Success. + * @retval -ve on Failure + */ +static int ether_get_eee(struct net_device *ndev, + struct ethtool_eee *cur_eee) +{ + int ret; + struct ether_priv_data *pdata = netdev_priv(ndev); + struct phy_device *phydev = pdata->phydev; + + if (!pdata->hw_feat.eee_sel) { + return -EOPNOTSUPP; + } + + if (!netif_running(ndev)) { + netdev_err(pdata->ndev, "interface not up\n"); + return -EINVAL; + } + + ret = phy_ethtool_get_eee(phydev, cur_eee); + if (ret) { + netdev_warn(pdata->ndev, "Cannot get PHY EEE config\n"); + return ret; + } + + cur_eee->eee_enabled = pdata->eee_enabled; + cur_eee->tx_lpi_enabled = pdata->tx_lpi_enabled; + cur_eee->eee_active = pdata->eee_active; + cur_eee->tx_lpi_timer = pdata->tx_lpi_timer; + + return ret; +} + +/** + * @brief Helper routing to validate EEE configuration requested via ethtool + * + * Algorithm: Check for invalid combinations of ethtool_eee fields. If any + * invalid combination found, override it. + * + * @param[in] ndev: Net device data. + * @param[in] eee_req: Pointer to struct ethtool_eee configuration requested + * + * @retval none + */ +static inline void validate_eee_conf(struct net_device *ndev, + struct ethtool_eee *eee_req, + struct ethtool_eee cur_eee) +{ + /* These are the invalid combinations that can be requested. + * EEE | Tx LPI | Rx LPI + *---------------------- + * 0 | 0 | 1 + * 0 | 1 | 0 + * 0 | 1 | 1 + * 1 | 0 | 0 + * + * These combinations can be entered from a state where either EEE was + * enabled or disabled originally. Hence decide next valid state based + * on whether EEE has toggled or not. + */ + if (!eee_req->eee_enabled && !eee_req->tx_lpi_enabled && + eee_req->advertised) { + if (eee_req->eee_enabled != cur_eee.eee_enabled) { + netdev_warn(ndev, "EEE off. Set Rx LPI off\n"); + eee_req->advertised = OSI_DISABLE; + } else { + netdev_warn(ndev, "Rx LPI on. Set EEE on\n"); + eee_req->eee_enabled = OSI_ENABLE; + } + } + + if (!eee_req->eee_enabled && eee_req->tx_lpi_enabled && + !eee_req->advertised) { + if (eee_req->eee_enabled != cur_eee.eee_enabled) { + netdev_warn(ndev, "EEE off. Set Tx LPI off\n"); + eee_req->tx_lpi_enabled = OSI_DISABLE; + } else { + /* phy_init_eee will fail if Rx LPI advertisement is + * disabled. Hence change the adv back to enable, + * so that Tx LPI will be set. + */ + netdev_warn(ndev, "Tx LPI on. Set EEE & Rx LPI on\n"); + eee_req->eee_enabled = OSI_ENABLE; + eee_req->advertised = eee_req->supported; + } + } + + if (!eee_req->eee_enabled && eee_req->tx_lpi_enabled && + eee_req->advertised) { + if (eee_req->eee_enabled != cur_eee.eee_enabled) { + netdev_warn(ndev, "EEE off. Set Tx & Rx LPI off\n"); + eee_req->tx_lpi_enabled = OSI_DISABLE; + eee_req->advertised = OSI_DISABLE; + } else { + netdev_warn(ndev, "Tx & Rx LPI on. Set EEE on\n"); + eee_req->eee_enabled = OSI_ENABLE; + } + } + + if (eee_req->eee_enabled && !eee_req->tx_lpi_enabled && + !eee_req->advertised) { + if (eee_req->eee_enabled != cur_eee.eee_enabled) { + netdev_warn(ndev, "EEE on. Set Tx & Rx LPI on\n"); + eee_req->tx_lpi_enabled = OSI_ENABLE; + eee_req->advertised = eee_req->supported; + } else { + netdev_warn(ndev, "Tx,Rx LPI off. Set EEE off\n"); + eee_req->eee_enabled = OSI_DISABLE; + } + } +} + +/** + * @brief Set current EEE configuration + * + * Algorithm: This function is invoked by kernel when user request to Set + * current EEE parameters. + * + * @param[in] ndev: Net device data. + * @param[in] eee_req: Pointer to struct ethtool_eee + * + * @note MAC and PHY need to be initialized. + * + * @retval 0 on Success. + * @retval -ve on Failure + */ +static int ether_set_eee(struct net_device *ndev, + struct ethtool_eee *eee_req) +{ + struct ether_priv_data *pdata = netdev_priv(ndev); + struct phy_device *phydev = pdata->phydev; + struct ethtool_eee cur_eee; + + if (!pdata->hw_feat.eee_sel) { + return -EOPNOTSUPP; + } + + if (!netif_running(ndev)) { + netdev_err(pdata->ndev, "interface not up\n"); + return -EINVAL; + } + + if (ether_get_eee(ndev, &cur_eee)) { + return -EOPNOTSUPP; + } + + /* Validate args + * 1. Validate the tx lpi timer for acceptable range */ + if (cur_eee.tx_lpi_timer != eee_req->tx_lpi_timer) { + if (eee_req->tx_lpi_timer == 0) { + pdata->tx_lpi_timer = OSI_DEFAULT_TX_LPI_TIMER; + } else if (eee_req->tx_lpi_timer <= OSI_MAX_TX_LPI_TIMER && + eee_req->tx_lpi_timer >= OSI_MIN_TX_LPI_TIMER && + !(eee_req->tx_lpi_timer % OSI_MIN_TX_LPI_TIMER)) { + pdata->tx_lpi_timer = eee_req->tx_lpi_timer; + } else { + netdev_err(ndev, "Tx LPI timer has to be < %u usec " + "in %u usec steps\n", OSI_MAX_TX_LPI_TIMER, + OSI_MIN_TX_LPI_TIMER); + return -EINVAL; + } + } + + /* 2. Override invalid combinations of requested configuration */ + validate_eee_conf(ndev, eee_req, cur_eee); + + /* First store the requested & validated EEE configuration */ + pdata->eee_enabled = eee_req->eee_enabled; + pdata->tx_lpi_enabled = eee_req->tx_lpi_enabled; + pdata->tx_lpi_timer = eee_req->tx_lpi_timer; + pdata->eee_active = eee_req->eee_active; + + /* If EEE adv has changed, inform PHY framework. PHY will + * restart ANEG and the ether_adjust_link callback will take care of + * enabling Tx LPI as needed. + */ + if (cur_eee.advertised != eee_req->advertised) { + return phy_ethtool_set_eee(phydev, eee_req); + } + + /* If no advertisement change, and only local Tx LPI changed, then + * configure the MAC right away. + */ + if (cur_eee.tx_lpi_enabled != eee_req->tx_lpi_enabled) { + eee_req->eee_active = ether_conf_eee(pdata, + eee_req->tx_lpi_enabled); + pdata->eee_active = eee_req->eee_active; + } + + return 0; +} + /** * @brief This function is invoked by kernel when user request to set * pmt parameters for remote wakeup or magic wakeup @@ -812,6 +1025,8 @@ static const struct ethtool_ops ether_ethtool_ops = { .set_coalesce = ether_set_coalesce, .get_wol = ether_get_wol, .set_wol = ether_set_wol, + .get_eee = ether_get_eee, + .set_eee = ether_set_eee, }; void ether_set_ethtool_ops(struct net_device *ndev)