Skip to content

Commit e66ae80

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 5ms) - 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 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 83214b5 commit e66ae80

2 files changed

Lines changed: 109 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
@@ -1360,6 +1360,12 @@ struct macb {
13601360

13611361
struct macb_ptp_info *ptp_info; /* macb-ptp interface */
13621362

1363+
/* EEE / LPI state */
1364+
bool eee_active;
1365+
bool tx_lpi_enabled;
1366+
struct delayed_work tx_lpi_work;
1367+
unsigned int tx_lpi_timer_ms; /* idle timeout before LPI */
1368+
13631369
struct phy *sgmii_phy; /* for ZynqMP SGMII mode */
13641370

13651371
#ifdef MACB_EXT_DESC

drivers/net/ethernet/cadence/macb_main.c

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,71 @@ 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 5
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+
static void macb_tx_lpi_work_fn(struct work_struct *work)
693+
{
694+
struct macb *bp = container_of(work, struct macb, tx_lpi_work.work);
695+
696+
if (bp->eee_active)
697+
macb_tx_lpi_set(bp, true);
698+
}
699+
700+
/* Called from TX path to wake from LPI before transmitting */
701+
static inline void macb_tx_lpi_wake(struct macb *bp)
702+
{
703+
if (!bp->tx_lpi_enabled)
704+
return;
705+
706+
macb_tx_lpi_set(bp, false);
707+
/* Cancel any pending re-entry */
708+
cancel_delayed_work(&bp->tx_lpi_work);
709+
710+
/* Wait for PHY to exit LPI before transmitting */
711+
udelay(MACB_TX_LPI_WAKE_TIME_US);
712+
}
713+
714+
/* Schedule LPI re-entry after TX idle timeout */
715+
static inline void macb_tx_lpi_schedule(struct macb *bp)
716+
{
717+
if (!bp->eee_active)
718+
return;
719+
720+
schedule_delayed_work(&bp->tx_lpi_work,
721+
msecs_to_jiffies(bp->tx_lpi_timer_ms));
722+
}
723+
659724
static void macb_mac_config(struct phylink_config *config, unsigned int mode,
660725
const struct phylink_link_state *state)
661726
{
@@ -728,10 +793,16 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode,
728793
queue_writel(queue, IDR,
729794
bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP));
730795

731-
/* Disable Rx and Tx */
732-
ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE));
796+
/* Cancel any pending LPI entry */
797+
cancel_delayed_work(&bp->tx_lpi_work);
798+
799+
/* Disable TX LPI, Rx, and Tx */
800+
ctrl = macb_readl(bp, NCR) & ~(GEM_BIT(TXLPIEN) | MACB_BIT(RE) | MACB_BIT(TE));
733801
macb_writel(bp, NCR, ctrl);
734802

803+
bp->eee_active = false;
804+
bp->tx_lpi_enabled = false;
805+
735806
netif_tx_stop_all_queues(ndev);
736807
}
737808

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

801872
netif_tx_wake_all_queues(ndev);
873+
874+
/* EEE: check if link partner negotiated EEE.
875+
* Per IEEE 802.3az / Microchip GMAC docs: LPI must not be
876+
* requested until the link has been up for at least 1 second.
877+
*/
878+
if (phy && (bp->caps & MACB_CAPS_EEE)) {
879+
bp->eee_active = phy_init_eee(phy, false) >= 0 &&
880+
phy->enable_tx_lpi;
881+
netdev_dbg(ndev, "EEE: active=%d\n", bp->eee_active);
882+
if (bp->eee_active)
883+
schedule_delayed_work(&bp->tx_lpi_work,
884+
msecs_to_jiffies(1000));
885+
}
802886
}
803887

804888
static struct phylink_pcs *macb_mac_select_pcs(struct phylink_config *config,
@@ -1312,6 +1396,11 @@ static int macb_tx_complete(struct macb_queue *queue, int budget)
13121396
CIRC_CNT(queue->tx_head, queue->tx_tail,
13131397
bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp))
13141398
netif_wake_subqueue(bp->dev, queue_index);
1399+
1400+
/* Schedule LPI re-entry when TX ring is drained */
1401+
if (queue->tx_head == queue->tx_tail)
1402+
macb_tx_lpi_schedule(bp);
1403+
13151404
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
13161405

13171406
return packets;
@@ -2341,6 +2430,10 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev)
23412430
bool is_lso;
23422431
netdev_tx_t ret = NETDEV_TX_OK;
23432432

2433+
/* Wake from LPI before transmitting */
2434+
if (unlikely(bp->tx_lpi_enabled))
2435+
macb_tx_lpi_wake(bp);
2436+
23442437
if (macb_clear_csum(skb)) {
23452438
dev_kfree_skb_any(skb);
23462439
return ret;
@@ -3064,6 +3157,9 @@ static int macb_open(struct net_device *dev)
30643157
if (err)
30653158
goto phy_off;
30663159

3160+
if ((bp->caps & MACB_CAPS_EEE) && dev->phydev)
3161+
phy_support_eee(dev->phydev);
3162+
30673163
netif_tx_start_all_queues(dev);
30683164

30693165
if (bp->ptp_info)
@@ -3095,6 +3191,8 @@ static int macb_close(struct net_device *dev)
30953191

30963192
netif_tx_stop_all_queues(dev);
30973193

3194+
cancel_delayed_work_sync(&bp->tx_lpi_work);
3195+
30983196
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
30993197
napi_disable(&queue->napi_rx);
31003198
napi_disable(&queue->napi_tx);
@@ -5338,6 +5436,8 @@ static int macb_probe(struct platform_device *pdev)
53385436
}
53395437

53405438
INIT_WORK(&bp->hresp_err_bh_work, macb_hresp_error_task);
5439+
INIT_DELAYED_WORK(&bp->tx_lpi_work, macb_tx_lpi_work_fn);
5440+
bp->tx_lpi_timer_ms = MACB_TX_LPI_TIMER_DEFAULT_MS;
53415441

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

53845484
device_set_wakeup_enable(&bp->pdev->dev, 0);
5485+
cancel_delayed_work_sync(&bp->tx_lpi_work);
53855486
cancel_work_sync(&bp->hresp_err_bh_work);
53865487
pm_runtime_disable(&pdev->dev);
53875488
pm_runtime_dont_use_autosuspend(&pdev->dev);

0 commit comments

Comments
 (0)