diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-03-06 05:56:53 +0300 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-03-06 05:56:54 +0300 |
| commit | 8844de1037dd5fd39f4843cd58bdac763f6edaca (patch) | |
| tree | 4306a770c888f6d119ca5ee84c631f3b9f8a788a | |
| parent | d4d8c6e6fd2a1c5144339884ca5f66e654ad54a5 (diff) | |
| parent | 48575b6e16d12365d6a69de13d061d9df2b775ec (diff) | |
| download | linux-8844de1037dd5fd39f4843cd58bdac763f6edaca.tar.xz | |
Merge branch 'net-cadence-macb-add-ieee-802-3az-eee-support'
Nicolai Buchwitz says:
====================
net: cadence: macb: add IEEE 802.3az EEE support
Add Energy Efficient Ethernet (IEEE 802.3az) support to the Cadence GEM
(macb) driver using phylink's managed EEE framework. The GEM MAC has
hardware LPI registers but no built-in idle timer, so the driver
implements software-managed TX LPI using a delayed_work timer while
delegating EEE negotiation and ethtool state to phylink.
The series is structured as follows:
1. LPI statistics: Expose the four hardware EEE counters (RX/TX LPI
transitions and time) through ethtool -S, accumulated in software
since they are clear-on-read. Adds register offset definitions
GEM_RXLPI/RXLPITIME/TXLPI/TXLPITIME (0x270-0x27c).
2. TX LPI engine: Introduces GEM_TXLPIEN (NCR bit 19) and
MACB_CAPS_EEE alongside the implementation that uses them.
phylink mac_enable_tx_lpi / mac_disable_tx_lpi callbacks with a
delayed_work-based idle timer. LPI entry is deferred 1 second
after link-up per IEEE 802.3az. Wake before transmit with a
conservative 50us PHY wake delay (IEEE 802.3az Tw_sys_tx).
3. ethtool EEE ops: get_eee/set_eee delegating to phylink for PHY
negotiation and timer management.
4. RP1 enablement: Set MACB_CAPS_EEE for the Raspberry Pi 5's RP1
southbridge (Cadence GEM_GXL rev 0x00070109 + BCM54213PE PHY).
5. EyeQ5 enablement: Set MACB_CAPS_EEE for the Mobileye EyeQ5 GEM
instance, verified with a hardware loopback by Théo Lebrun.
Tested on Raspberry Pi 5 (1000BASE-T, BCM54213PE PHY, 250ms LPI timer):
iperf3 throughput (no regression):
TCP TX: 937.8 Mbit/s (EEE on) vs 937.0 Mbit/s (EEE off)
TCP RX: 936.5 Mbit/s both
Latency (ping RTT, small expected increase from LPI wake):
1s interval: 0.273 ms (EEE on) vs 0.181 ms (EEE off)
10ms interval: 0.206 ms (EEE on) vs 0.168 ms (EEE off)
flood ping: 0.200 ms (EEE on) vs 0.156 ms (EEE off)
LPI counters (ethtool -S, 1s-interval ping, EEE on):
tx_lpi_transitions: 112
tx_lpi_time: 15574651
Zero packet loss across all tests. Also verified with
ethtool --show-eee / --set-eee and cable unplug/replug cycling.
====================
Link: https://patch.msgid.link/20260304105432.631186-1-nb@tipi-net.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | drivers/net/ethernet/cadence/macb.h | 20 | ||||
| -rw-r--r-- | drivers/net/ethernet/cadence/macb_main.c | 145 |
2 files changed, 163 insertions, 2 deletions
diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 87414a2ddf6e..c69828b27dae 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -170,6 +170,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 /* RX LPI Transitions */ +#define GEM_RXLPITIME 0x0274 /* RX LPI Time */ +#define GEM_TXLPI 0x0278 /* TX LPI Transitions */ +#define GEM_TXLPITIME 0x027c /* TX LPI Time */ #define GEM_DCFG1 0x0280 /* Design Config 1 */ #define GEM_DCFG2 0x0284 /* Design Config 2 */ #define GEM_DCFG3 0x0288 /* Design Config 3 */ @@ -305,6 +309,8 @@ #define MACB_IRXFCS_SIZE 1 /* GEM specific NCR bitfields. */ +#define GEM_TXLPIEN_OFFSET 19 +#define GEM_TXLPIEN_SIZE 1 #define GEM_ENABLE_HS_MAC_OFFSET 31 #define GEM_ENABLE_HS_MAC_SIZE 1 @@ -779,6 +785,7 @@ #define MACB_CAPS_DMA_PTP BIT(22) #define MACB_CAPS_RSC BIT(23) #define MACB_CAPS_NO_LSO BIT(24) +#define MACB_CAPS_EEE BIT(25) /* LSO settings */ #define MACB_LSO_UFO_ENABLE 0x01 @@ -1043,6 +1050,10 @@ struct gem_stats { u64 rx_ip_header_checksum_errors; u64 rx_tcp_checksum_errors; u64 rx_udp_checksum_errors; + u64 rx_lpi_transitions; + u64 rx_lpi_time; + u64 tx_lpi_transitions; + u64 tx_lpi_time; }; /* Describes the name and offset of an individual statistic register, as @@ -1142,6 +1153,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) @@ -1357,6 +1372,11 @@ struct macb { struct work_struct hresp_err_bh_work; + /* EEE / LPI state */ + bool eee_active; + struct delayed_work tx_lpi_work; + u32 tx_lpi_timer; + int rx_bd_rd_prefetch; int tx_bd_rd_prefetch; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 17f0ad3d7a09..3dcae4d5f74c 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -10,6 +10,7 @@ #include <linux/clk-provider.h> #include <linux/clk.h> #include <linux/crc32.h> +#include <linux/delay.h> #include <linux/dma-mapping.h> #include <linux/etherdevice.h> #include <linux/firmware/xlnx-zynqmp.h> @@ -621,6 +622,107 @@ static const struct phylink_pcs_ops macb_phylink_pcs_ops = { .pcs_config = macb_pcs_config, }; +static bool macb_tx_lpi_set(struct macb *bp, bool enable) +{ + u32 old, ncr; + + lockdep_assert_held(&bp->lock); + + ncr = macb_readl(bp, NCR); + old = ncr; + if (enable) + ncr |= GEM_BIT(TXLPIEN); + else + ncr &= ~GEM_BIT(TXLPIEN); + if (old != ncr) + macb_writel(bp, NCR, ncr); + + return old != ncr; +} + +static bool macb_tx_all_queues_idle(struct macb *bp) +{ + struct macb_queue *queue; + unsigned int q; + + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { + if (READ_ONCE(queue->tx_head) != READ_ONCE(queue->tx_tail)) + return false; + } + return true; +} + +static void macb_tx_lpi_work_fn(struct work_struct *work) +{ + struct macb *bp = container_of(work, struct macb, tx_lpi_work.work); + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + if (bp->eee_active && macb_tx_all_queues_idle(bp)) + macb_tx_lpi_set(bp, true); + spin_unlock_irqrestore(&bp->lock, flags); +} + +static void macb_tx_lpi_schedule(struct macb *bp) +{ + if (bp->eee_active) + mod_delayed_work(system_wq, &bp->tx_lpi_work, + usecs_to_jiffies(bp->tx_lpi_timer)); +} + +/* Wake from LPI before transmitting. The MAC must deassert TXLPIEN + * and wait for the PHY to exit LPI before any frame can be sent. + * IEEE 802.3az Tw_sys is ~17us for 1000BASE-T, ~30us for 100BASE-TX; + * we use a conservative 50us. + */ +static void macb_tx_lpi_wake(struct macb *bp) +{ + lockdep_assert_held(&bp->lock); + + if (!bp->eee_active) + return; + + if (!macb_tx_lpi_set(bp, false)) + return; + + cancel_delayed_work(&bp->tx_lpi_work); + udelay(50); +} + +static void macb_mac_disable_tx_lpi(struct phylink_config *config) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(ndev); + unsigned long flags; + + cancel_delayed_work_sync(&bp->tx_lpi_work); + + spin_lock_irqsave(&bp->lock, flags); + bp->eee_active = false; + macb_tx_lpi_set(bp, false); + spin_unlock_irqrestore(&bp->lock, flags); +} + +static int macb_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, + bool tx_clk_stop) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct macb *bp = netdev_priv(ndev); + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + bp->tx_lpi_timer = timer; + bp->eee_active = true; + spin_unlock_irqrestore(&bp->lock, flags); + + /* Defer initial LPI entry by 1 second after link-up per + * IEEE 802.3az section 22.7a. + */ + mod_delayed_work(system_wq, &bp->tx_lpi_work, msecs_to_jiffies(1000)); + + return 0; +} + static void macb_mac_config(struct phylink_config *config, unsigned int mode, const struct phylink_link_state *state) { @@ -769,6 +871,8 @@ static const struct phylink_mac_ops macb_phylink_ops = { .mac_config = macb_mac_config, .mac_link_down = macb_mac_link_down, .mac_link_up = macb_mac_link_up, + .mac_disable_tx_lpi = macb_mac_disable_tx_lpi, + .mac_enable_tx_lpi = macb_mac_enable_tx_lpi, }; static bool macb_phy_handle_exists(struct device_node *dn) @@ -864,6 +968,18 @@ static int macb_mii_probe(struct net_device *dev) } } + /* Configure EEE LPI if supported */ + if (bp->caps & MACB_CAPS_EEE) { + __set_bit(PHY_INTERFACE_MODE_MII, + bp->phylink_config.lpi_interfaces); + __set_bit(PHY_INTERFACE_MODE_GMII, + bp->phylink_config.lpi_interfaces); + phy_interface_set_rgmii(bp->phylink_config.lpi_interfaces); + bp->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD; + bp->phylink_config.lpi_timer_default = 250000; + bp->phylink_config.eee_enabled_default = true; + } + bp->phylink = phylink_create(&bp->phylink_config, bp->pdev->dev.fwnode, bp->phy_interface, &macb_phylink_ops); if (IS_ERR(bp->phylink)) { @@ -1260,6 +1376,9 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) netif_wake_subqueue(bp->dev, queue_index); spin_unlock_irqrestore(&queue->tx_ptr_lock, flags); + if (packets) + macb_tx_lpi_schedule(bp); + return packets; } @@ -2366,6 +2485,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) skb->len); spin_lock(&bp->lock); + macb_tx_lpi_wake(bp); macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART)); spin_unlock(&bp->lock); @@ -3026,6 +3146,8 @@ static int macb_close(struct net_device *dev) netdev_tx_reset_queue(netdev_get_tx_queue(dev, q)); } + cancel_delayed_work_sync(&bp->tx_lpi_work); + phylink_stop(bp->phylink); phylink_disconnect_phy(bp->phylink); @@ -3936,6 +4058,20 @@ static const struct ethtool_ops macb_ethtool_ops = { .set_ringparam = macb_set_ringparam, }; +static int macb_get_eee(struct net_device *dev, struct ethtool_keee *eee) +{ + struct macb *bp = netdev_priv(dev); + + return phylink_ethtool_get_eee(bp->phylink, eee); +} + +static int macb_set_eee(struct net_device *dev, struct ethtool_keee *eee) +{ + struct macb *bp = netdev_priv(dev); + + return phylink_ethtool_set_eee(bp->phylink, eee); +} + static const struct ethtool_ops gem_ethtool_ops = { .get_regs_len = macb_get_regs_len, .get_regs = macb_get_regs, @@ -3958,6 +4094,8 @@ static const struct ethtool_ops gem_ethtool_ops = { .set_rxnfc = gem_set_rxnfc, .get_rx_ring_count = gem_get_rx_ring_count, .nway_reset = phy_ethtool_nway_reset, + .get_eee = macb_get_eee, + .set_eee = macb_set_eee, }; static int macb_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) @@ -5388,7 +5526,7 @@ static const struct macb_config versal_config = { static const struct macb_config eyeq5_config = { .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO | MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_QUEUE_DISABLE | - MACB_CAPS_NO_LSO, + MACB_CAPS_NO_LSO | MACB_CAPS_EEE, .dma_burst_length = 16, .clk_init = macb_clk_init, .init = eyeq5_init, @@ -5399,7 +5537,8 @@ static const struct macb_config eyeq5_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, @@ -5629,6 +5768,7 @@ 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); 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), @@ -5672,6 +5812,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); |
