diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts index 2d56aa951fd28f..2939214f23ade4 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts @@ -175,8 +175,6 @@ rp1_target: &pcie2 { phy1: ethernet-phy@1 { reg = <0x1>; brcm,powerdown-enable; - eee-broken-1000t; - eee-broken-100tx; }; }; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi index e35d3abba20bc7..3afce9befb338f 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi @@ -166,8 +166,6 @@ rp1_target: &pcie2 { brcm,powerdown-enable; interrupt-parent = <&rp1_gpio>; interrupts = <37 IRQ_TYPE_LEVEL_LOW>; - eee-broken-1000t; - eee-broken-100tx; }; }; diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index f797244f576416..bb186807e33c7e 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -176,6 +176,10 @@ #define GEM_PCSANNPTX 0x021c /* PCS AN Next Page TX */ #define GEM_PCSANNPLP 0x0220 /* PCS AN Next Page LP */ #define GEM_PCSANEXTSTS 0x023c /* PCS AN Extended Status */ +#define GEM_RXLPI 0x0270 /* EEE RX LPI Transitions */ +#define GEM_RXLPITIME 0x0274 /* EEE RX LPI Time */ +#define GEM_TXLPI 0x0278 /* EEE TX LPI Transitions */ +#define GEM_TXLPITIME 0x027c /* EEE TX LPI Time */ #define GEM_DCFG1 0x0280 /* Design Config 1 */ #define GEM_DCFG2 0x0284 /* Design Config 2 */ #define GEM_DCFG3 0x0288 /* Design Config 3 */ @@ -258,6 +262,10 @@ #define MACB_MIIONRGMII_OFFSET 28 /* MII Usage on RGMII Interface */ #define MACB_MIIONRGMII_SIZE 1 +/* GEM specific NCR bitfields. */ +#define GEM_TXLPIEN_OFFSET 19 /* TX LPI Enable */ +#define GEM_TXLPIEN_SIZE 1 + /* Bitfields in NCFGR */ #define MACB_SPD_OFFSET 0 /* Speed */ #define MACB_SPD_SIZE 1 @@ -756,6 +764,7 @@ #define MACB_CAPS_MIIONRGMII 0x00000200 #define MACB_CAPS_NEED_TSUCLK 0x00000400 #define MACB_CAPS_QUEUE_DISABLE 0x00000800 +#define MACB_CAPS_EEE 0x00001000 #define MACB_CAPS_PCS 0x01000000 #define MACB_CAPS_HIGH_SPEED 0x02000000 #define MACB_CAPS_CLK_HW_CHG 0x04000000 @@ -1038,6 +1047,10 @@ struct gem_stats { u32 rx_ip_header_checksum_errors; u32 rx_tcp_checksum_errors; u32 rx_udp_checksum_errors; + u32 rx_lpi_transitions; + u32 rx_lpi_time; + u32 tx_lpi_transitions; + u32 tx_lpi_time; }; /* Describes the name and offset of an individual statistic register, as @@ -1137,6 +1150,10 @@ static const struct gem_statistic gem_statistics[] = { GEM_BIT(NDS_RXERR)), GEM_STAT_TITLE_BITS(RXUDPCCNT, "rx_udp_checksum_errors", GEM_BIT(NDS_RXERR)), + GEM_STAT_TITLE(RXLPI, "rx_lpi_transitions"), + GEM_STAT_TITLE(RXLPITIME, "rx_lpi_time"), + GEM_STAT_TITLE(TXLPI, "tx_lpi_transitions"), + GEM_STAT_TITLE(TXLPITIME, "tx_lpi_time"), }; #define GEM_STATS_LEN ARRAY_SIZE(gem_statistics) @@ -1341,6 +1358,12 @@ struct macb { struct macb_ptp_info *ptp_info; /* macb-ptp interface */ + /* EEE / LPI state */ + bool eee_active; + bool tx_lpi_enabled; + struct delayed_work tx_lpi_work; + unsigned int tx_lpi_timer_ms; /* idle timeout before LPI */ + struct phy *sgmii_phy; /* for ZynqMP SGMII mode */ #ifdef MACB_EXT_DESC diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index d06b6f2e52c863..cfc9d80b15b51b 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -656,6 +656,86 @@ static const struct phylink_pcs_ops macb_phylink_pcs_ops = { .pcs_config = macb_pcs_config, }; +/* Default TX LPI idle timeout in milliseconds. + * The MAC will enter LPI after this period of TX inactivity. + */ +#define MACB_TX_LPI_TIMER_DEFAULT_MS 250 + +/* PHY wake time from LPI in microseconds. + * IEEE 802.3az: Tw_sys is ~17us for 1000BASE-T, ~30us for 100BASE-TX. + * Use a conservative value to ensure the PHY has fully exited LPI. + */ +#define MACB_TX_LPI_WAKE_TIME_US 50 + +static void macb_tx_lpi_set(struct macb *bp, bool enable) +{ + unsigned long flags; + u32 ncr; + + spin_lock_irqsave(&bp->lock, flags); + + ncr = macb_readl(bp, NCR); + if (enable) + ncr |= GEM_BIT(TXLPIEN); + else + ncr &= ~GEM_BIT(TXLPIEN); + macb_writel(bp, NCR, ncr); + + bp->tx_lpi_enabled = enable; + + spin_unlock_irqrestore(&bp->lock, flags); + + netdev_dbg(bp->dev, "EEE TX LPI %s\n", + enable ? "enabled" : "disabled"); +} + +/* Schedule LPI re-entry after TX idle timeout */ +static inline void macb_tx_lpi_schedule(struct macb *bp) +{ + if (!bp->eee_active) + return; + + mod_delayed_work(system_wq, &bp->tx_lpi_work, + msecs_to_jiffies(bp->tx_lpi_timer_ms)); +} + +static void macb_tx_lpi_work_fn(struct work_struct *work) +{ + struct macb *bp = container_of(work, struct macb, tx_lpi_work.work); + unsigned int q; + + if (!bp->eee_active) + return; + + /* Only enter LPI if all TX queues are truly idle. The timer may + * have been scheduled when one queue drained but traffic resumed + * before the timer fired. + */ + for (q = 0; q < bp->num_queues; q++) { + if (bp->queues[q].tx_head != bp->queues[q].tx_tail) { + /* TX still active, reschedule and check again later */ + macb_tx_lpi_schedule(bp); + return; + } + } + + macb_tx_lpi_set(bp, true); +} + +/* Called from TX path to wake from LPI before transmitting */ +static inline void macb_tx_lpi_wake(struct macb *bp) +{ + if (!bp->tx_lpi_enabled) + return; + + macb_tx_lpi_set(bp, false); + /* Cancel any pending re-entry */ + cancel_delayed_work(&bp->tx_lpi_work); + + /* Wait for PHY to exit LPI before transmitting */ + udelay(MACB_TX_LPI_WAKE_TIME_US); +} + static void macb_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { @@ -728,10 +808,16 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode, queue_writel(queue, IDR, bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP)); - /* Disable Rx and Tx */ - ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE)); + /* Cancel any pending LPI entry */ + cancel_delayed_work(&bp->tx_lpi_work); + + /* Disable TX LPI, Rx, and Tx */ + ctrl = macb_readl(bp, NCR) & ~(GEM_BIT(TXLPIEN) | MACB_BIT(RE) | MACB_BIT(TE)); macb_writel(bp, NCR, ctrl); + bp->eee_active = false; + bp->tx_lpi_enabled = false; + netif_tx_stop_all_queues(ndev); } @@ -799,6 +885,19 @@ static void macb_mac_link_up(struct phylink_config *config, macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE)); netif_tx_wake_all_queues(ndev); + + /* EEE: check if link partner negotiated EEE. + * Per IEEE 802.3az / Microchip GMAC docs: LPI must not be + * requested until the link has been up for at least 1 second. + */ + if (phy && (bp->caps & MACB_CAPS_EEE)) { + bp->eee_active = phy_init_eee(phy, false) >= 0 && + phy->enable_tx_lpi; + netdev_dbg(ndev, "EEE: active=%d\n", bp->eee_active); + if (bp->eee_active) + schedule_delayed_work(&bp->tx_lpi_work, + msecs_to_jiffies(1000)); + } } static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config, @@ -1312,6 +1411,11 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) CIRC_CNT(queue->tx_head, queue->tx_tail, bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp)) netif_wake_subqueue(bp->dev, queue_index); + + /* Schedule LPI re-entry when TX ring is drained */ + if (queue->tx_head == queue->tx_tail) + macb_tx_lpi_schedule(bp); + spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); return packets; @@ -2341,6 +2445,10 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) bool is_lso; netdev_tx_t ret = NETDEV_TX_OK; + /* Wake from LPI before transmitting */ + if (unlikely(bp->tx_lpi_enabled)) + macb_tx_lpi_wake(bp); + if (macb_clear_csum(skb)) { dev_kfree_skb_any(skb); return ret; @@ -3064,6 +3172,9 @@ static int macb_open(struct net_device *dev) if (err) goto phy_off; + if ((bp->caps & MACB_CAPS_EEE) && dev->phydev) + phy_support_eee(dev->phydev); + netif_tx_start_all_queues(dev); if (bp->ptp_info) @@ -3095,6 +3206,8 @@ static int macb_close(struct net_device *dev) netif_tx_stop_all_queues(dev); + cancel_delayed_work_sync(&bp->tx_lpi_work); + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { napi_disable(&queue->napi_rx); napi_disable(&queue->napi_tx); @@ -3497,6 +3610,42 @@ static int macb_set_ringparam(struct net_device *netdev, return 0; } +static int macb_get_eee(struct net_device *ndev, struct ethtool_keee *edata) +{ + struct macb *bp = netdev_priv(ndev); + int ret; + + if (!(bp->caps & MACB_CAPS_EEE)) + return -EOPNOTSUPP; + + ret = phylink_ethtool_get_eee(bp->phylink, edata); + if (ret) + return ret; + + edata->tx_lpi_timer = bp->tx_lpi_timer_ms * 1000; + + return 0; +} + +static int macb_set_eee(struct net_device *ndev, struct ethtool_keee *edata) +{ + struct macb *bp = netdev_priv(ndev); + + if (!(bp->caps & MACB_CAPS_EEE)) + return -EOPNOTSUPP; + + if (edata->tx_lpi_timer) + bp->tx_lpi_timer_ms = edata->tx_lpi_timer / 1000; + + /* + * Don't directly control TXLPIEN here. phylink_ethtool_set_eee() + * updates the PHY, which will bounce the link if tx_lpi_enabled + * changes. That triggers mac_link_down/mac_link_up where we + * enable/disable TXLPIEN based on the negotiated state. + */ + return phylink_ethtool_set_eee(bp->phylink, edata); +} + #ifdef CONFIG_MACB_USE_HWSTAMP static unsigned int gem_get_tsu_rate(struct macb *bp) { @@ -3912,6 +4061,9 @@ static const struct ethtool_ops gem_ethtool_ops = { .set_ringparam = macb_set_ringparam, .get_rxnfc = gem_get_rxnfc, .set_rxnfc = gem_set_rxnfc, + .get_eee = macb_get_eee, + .set_eee = macb_set_eee, + .nway_reset = phy_ethtool_nway_reset, }; static int macb_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) @@ -5079,7 +5231,8 @@ static const struct macb_config versal_config = { static const struct macb_config raspberrypi_rp1_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG | MACB_CAPS_JUMBO | - MACB_CAPS_GEM_HAS_PTP, + MACB_CAPS_GEM_HAS_PTP | + MACB_CAPS_EEE, .dma_burst_length = 16, .clk_init = macb_clk_init, .init = macb_init, @@ -5337,6 +5490,8 @@ static int macb_probe(struct platform_device *pdev) } INIT_WORK(&bp->hresp_err_bh_work, macb_hresp_error_task); + INIT_DELAYED_WORK(&bp->tx_lpi_work, macb_tx_lpi_work_fn); + bp->tx_lpi_timer_ms = MACB_TX_LPI_TIMER_DEFAULT_MS; netdev_info(dev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n", macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID), @@ -5381,6 +5536,7 @@ static void macb_remove(struct platform_device *pdev) mdiobus_free(bp->mii_bus); device_set_wakeup_enable(&bp->pdev->dev, 0); + cancel_delayed_work_sync(&bp->tx_lpi_work); cancel_work_sync(&bp->hresp_err_bh_work); pm_runtime_disable(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 707a6ff5242bd8..2979cfd1f36320 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1728,7 +1728,7 @@ EXPORT_SYMBOL(phy_ethtool_get_eee); * phy_ethtool_set_eee_noneg - Adjusts MAC LPI configuration without PHY * renegotiation * @phydev: pointer to the target PHY device structure - * @data: pointer to the ethtool_keee structure containing the new EEE settings + * @old_cfg: pointer to the eee_config structure containing the old EEE settings * * This function updates the Energy Efficient Ethernet (EEE) configuration * for cases where only the MAC's Low Power Idle (LPI) configuration changes, @@ -1739,11 +1739,10 @@ EXPORT_SYMBOL(phy_ethtool_get_eee); * configuration. */ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev, - struct ethtool_keee *data) + const struct eee_config *old_cfg) { - if (phydev->eee_cfg.tx_lpi_enabled != data->tx_lpi_enabled || - phydev->eee_cfg.tx_lpi_timer != data->tx_lpi_timer) { - eee_to_eeecfg(&phydev->eee_cfg, data); + if (phydev->eee_cfg.tx_lpi_enabled != old_cfg->tx_lpi_enabled || + phydev->eee_cfg.tx_lpi_timer != old_cfg->tx_lpi_timer) { phydev->enable_tx_lpi = eeecfg_mac_can_tx_lpi(&phydev->eee_cfg); if (phydev->link) { phydev->link = false; @@ -1763,18 +1762,23 @@ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev, */ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data) { + struct eee_config old_cfg; int ret; if (!phydev->drv) return -EIO; mutex_lock(&phydev->lock); + + old_cfg = phydev->eee_cfg; + eee_to_eeecfg(&phydev->eee_cfg, data); + ret = genphy_c45_ethtool_set_eee(phydev, data); - if (ret >= 0) { - if (ret == 0) - phy_ethtool_set_eee_noneg(phydev, data); - eee_to_eeecfg(&phydev->eee_cfg, data); - } + if (ret == 0) + phy_ethtool_set_eee_noneg(phydev, &old_cfg); + else if (ret < 0) + phydev->eee_cfg = old_cfg; + mutex_unlock(&phydev->lock); return ret < 0 ? ret : 0;