Skip to content

Commit e2b60f3

Browse files
committed
net: cadence: macb: implement EEE TX LPI support
Implement software-managed TX Low Power Idle (LPI) for the Cadence GEM MAC as part of IEEE 802.3az Energy Efficient Ethernet support. The GEM MAC has no built-in idle timer - the TXLPIEN bit (NCR bit 19) immediately asserts LPI and blocks all TX while set. The MAC does not auto-wake for transmit. Per Microchip GMAC documentation (section 40.6.19): "It is best to use firmware to control LPI." This patch implements a software idle timer using delayed_work: - On TX completion with an empty ring, schedule LPI entry after a configurable idle timeout (default 250ms). The work function verifies all TX queues are truly idle before entering LPI to prevent entering LPI while traffic is still active. - On TX start, wake from LPI by clearing TXLPIEN, cancelling any pending re-entry, and waiting 50us for the PHY to exit LPI (conservative vs IEEE 802.3az Tw_sys of ~17us/~30us) - On link up, check EEE negotiation via phy_init_eee() and defer first LPI entry by 1 second per IEEE 802.3az requirements - On link down, immediately cancel pending work and clear TXLPIEN The timer value is configurable at runtime via ethtool --set-eee tx-timer. The implementation is gated on MACB_CAPS_EEE so platforms must explicitly opt in via their macb_config. Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
1 parent e42c97c commit e2b60f3

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

drivers/net/ethernet/cadence/macb.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,12 @@ struct macb {
13581358

13591359
struct macb_ptp_info *ptp_info; /* macb-ptp interface */
13601360

1361+
/* EEE / LPI state */
1362+
bool eee_active;
1363+
bool tx_lpi_enabled;
1364+
struct delayed_work tx_lpi_work;
1365+
unsigned int tx_lpi_timer_ms; /* idle timeout before LPI */
1366+
13611367
struct phy *sgmii_phy; /* for ZynqMP SGMII mode */
13621368

13631369
#ifdef MACB_EXT_DESC

drivers/net/ethernet/cadence/macb_main.c

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,86 @@ static const struct phylink_pcs_ops macb_phylink_pcs_ops = {
656656
.pcs_config = macb_pcs_config,
657657
};
658658

659+
/* Default TX LPI idle timeout in milliseconds.
660+
* The MAC will enter LPI after this period of TX inactivity.
661+
*/
662+
#define MACB_TX_LPI_TIMER_DEFAULT_MS 250
663+
664+
/* PHY wake time from LPI in microseconds.
665+
* IEEE 802.3az: Tw_sys is ~17us for 1000BASE-T, ~30us for 100BASE-TX.
666+
* Use a conservative value to ensure the PHY has fully exited LPI.
667+
*/
668+
#define MACB_TX_LPI_WAKE_TIME_US 50
669+
670+
static void macb_tx_lpi_set(struct macb *bp, bool enable)
671+
{
672+
unsigned long flags;
673+
u32 ncr;
674+
675+
spin_lock_irqsave(&bp->lock, flags);
676+
677+
ncr = macb_readl(bp, NCR);
678+
if (enable)
679+
ncr |= GEM_BIT(TXLPIEN);
680+
else
681+
ncr &= ~GEM_BIT(TXLPIEN);
682+
macb_writel(bp, NCR, ncr);
683+
684+
bp->tx_lpi_enabled = enable;
685+
686+
spin_unlock_irqrestore(&bp->lock, flags);
687+
688+
netdev_dbg(bp->dev, "EEE TX LPI %s\n",
689+
enable ? "enabled" : "disabled");
690+
}
691+
692+
/* Schedule LPI re-entry after TX idle timeout */
693+
static inline void macb_tx_lpi_schedule(struct macb *bp)
694+
{
695+
if (!bp->eee_active)
696+
return;
697+
698+
mod_delayed_work(system_wq, &bp->tx_lpi_work,
699+
msecs_to_jiffies(bp->tx_lpi_timer_ms));
700+
}
701+
702+
static void macb_tx_lpi_work_fn(struct work_struct *work)
703+
{
704+
struct macb *bp = container_of(work, struct macb, tx_lpi_work.work);
705+
unsigned int q;
706+
707+
if (!bp->eee_active)
708+
return;
709+
710+
/* Only enter LPI if all TX queues are truly idle. The timer may
711+
* have been scheduled when one queue drained but traffic resumed
712+
* before the timer fired.
713+
*/
714+
for (q = 0; q < bp->num_queues; q++) {
715+
if (bp->queues[q].tx_head != bp->queues[q].tx_tail) {
716+
/* TX still active, reschedule and check again later */
717+
macb_tx_lpi_schedule(bp);
718+
return;
719+
}
720+
}
721+
722+
macb_tx_lpi_set(bp, true);
723+
}
724+
725+
/* Called from TX path to wake from LPI before transmitting */
726+
static inline void macb_tx_lpi_wake(struct macb *bp)
727+
{
728+
if (!bp->tx_lpi_enabled)
729+
return;
730+
731+
macb_tx_lpi_set(bp, false);
732+
/* Cancel any pending re-entry */
733+
cancel_delayed_work(&bp->tx_lpi_work);
734+
735+
/* Wait for PHY to exit LPI before transmitting */
736+
udelay(MACB_TX_LPI_WAKE_TIME_US);
737+
}
738+
659739
static void macb_mac_config(struct phylink_config *config, unsigned int mode,
660740
const struct phylink_link_state *state)
661741
{
@@ -728,10 +808,16 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode,
728808
queue_writel(queue, IDR,
729809
bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP));
730810

731-
/* Disable Rx and Tx */
732-
ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE));
811+
/* Cancel any pending LPI entry */
812+
cancel_delayed_work(&bp->tx_lpi_work);
813+
814+
/* Disable TX LPI, Rx, and Tx */
815+
ctrl = macb_readl(bp, NCR) & ~(GEM_BIT(TXLPIEN) | MACB_BIT(RE) | MACB_BIT(TE));
733816
macb_writel(bp, NCR, ctrl);
734817

818+
bp->eee_active = false;
819+
bp->tx_lpi_enabled = false;
820+
735821
netif_tx_stop_all_queues(ndev);
736822
}
737823

@@ -799,6 +885,19 @@ static void macb_mac_link_up(struct phylink_config *config,
799885
macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE));
800886

801887
netif_tx_wake_all_queues(ndev);
888+
889+
/* EEE: check if link partner negotiated EEE.
890+
* Per IEEE 802.3az / Microchip GMAC docs: LPI must not be
891+
* requested until the link has been up for at least 1 second.
892+
*/
893+
if (phy && (bp->caps & MACB_CAPS_EEE)) {
894+
bp->eee_active = phy_init_eee(phy, false) >= 0 &&
895+
phy->enable_tx_lpi;
896+
netdev_dbg(ndev, "EEE: active=%d\n", bp->eee_active);
897+
if (bp->eee_active)
898+
schedule_delayed_work(&bp->tx_lpi_work,
899+
msecs_to_jiffies(1000));
900+
}
802901
}
803902

804903
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)
13121411
CIRC_CNT(queue->tx_head, queue->tx_tail,
13131412
bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp))
13141413
netif_wake_subqueue(bp->dev, queue_index);
1414+
1415+
/* Schedule LPI re-entry when TX ring is drained */
1416+
if (queue->tx_head == queue->tx_tail)
1417+
macb_tx_lpi_schedule(bp);
1418+
13151419
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
13161420

13171421
return packets;
@@ -2341,6 +2445,10 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev)
23412445
bool is_lso;
23422446
netdev_tx_t ret = NETDEV_TX_OK;
23432447

2448+
/* Wake from LPI before transmitting */
2449+
if (unlikely(bp->tx_lpi_enabled))
2450+
macb_tx_lpi_wake(bp);
2451+
23442452
if (macb_clear_csum(skb)) {
23452453
dev_kfree_skb_any(skb);
23462454
return ret;
@@ -3064,6 +3172,9 @@ static int macb_open(struct net_device *dev)
30643172
if (err)
30653173
goto phy_off;
30663174

3175+
if ((bp->caps & MACB_CAPS_EEE) && dev->phydev)
3176+
phy_support_eee(dev->phydev);
3177+
30673178
netif_tx_start_all_queues(dev);
30683179

30693180
if (bp->ptp_info)
@@ -3095,6 +3206,8 @@ static int macb_close(struct net_device *dev)
30953206

30963207
netif_tx_stop_all_queues(dev);
30973208

3209+
cancel_delayed_work_sync(&bp->tx_lpi_work);
3210+
30983211
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
30993212
napi_disable(&queue->napi_rx);
31003213
napi_disable(&queue->napi_tx);
@@ -5338,6 +5451,8 @@ static int macb_probe(struct platform_device *pdev)
53385451
}
53395452

53405453
INIT_WORK(&bp->hresp_err_bh_work, macb_hresp_error_task);
5454+
INIT_DELAYED_WORK(&bp->tx_lpi_work, macb_tx_lpi_work_fn);
5455+
bp->tx_lpi_timer_ms = MACB_TX_LPI_TIMER_DEFAULT_MS;
53415456

53425457
netdev_info(dev, "Cadence %s rev 0x%08x at 0x%08lx irq %d (%pM)\n",
53435458
macb_is_gem(bp) ? "GEM" : "MACB", macb_readl(bp, MID),
@@ -5382,6 +5497,7 @@ static void macb_remove(struct platform_device *pdev)
53825497
mdiobus_free(bp->mii_bus);
53835498

53845499
device_set_wakeup_enable(&bp->pdev->dev, 0);
5500+
cancel_delayed_work_sync(&bp->tx_lpi_work);
53855501
cancel_work_sync(&bp->hresp_err_bh_work);
53865502
pm_runtime_disable(&pdev->dev);
53875503
pm_runtime_dont_use_autosuspend(&pdev->dev);

0 commit comments

Comments
 (0)