qualcommax: qca_ppe: add EEE support

EEE is disabled on every PPE port because the MAC side of LPI was never
programmed. The GMAC has an LPI enable per port, a wake and idle timer
per port and a microsecond prescaler at PPE offset 0x400, as the HPPE
code in qca-ssdk has it. Program them from mac_enable_tx_lpi, offer LPI
at 100 and 1000 Mbit/s on the QSGMII, PSGMII and SGMII ports, enable
EEE by default and add support_eee.

On IPQ8074 EEE negotiates on links whose partner offers it, a 10 minute
soak with bursty traffic loses 0 of 2939 pings, throughput is unchanged
(302 Mbit/s both ways, n=3) and idle ping latency rises from 0.16 to
0.22 ms (n=3). qca-ssdk uses the same LPI registers on IPQ60xx; no
IPQ60xx board was available to run it.

Fixes: a3a6679538 ("qualcommax: qca_ppe: disable EEE to prevent idle-port egress wedge")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25665
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Julius Bairaktaris authored and Robert Marko committed 2026-10-07 19:35:41 +02:00
1 parent ad77648ba7
commit dd052b4594
2 files changed
+40 -6

No files matched your search

@@ -20,6 +20,16 @@
/* --- Global --- */
/* GMAC LPI: an enable bit per port from port 1, a wake and an idle timer per
* port in microseconds, and the PPE clock cycles that make a microsecond.
* qca-ssdk uses the same layout for IPQ807x and IPQ60xx.
*/
#define PPE_LPI_ENABLE 0x400
#define PPE_LPI_PORT_TIMER(port) (0x400 + (port) * 0x4)
#define PPE_LPI_WAKEUP_TIMER GENMASK(15, 0)
#define PPE_LPI_SLEEP_TIMER GENMASK(31, 16)
#define PPE_LPI_1US_CNT 0x430
#define PPE_PORT_MUX_CTRL 0x10
/* CPPE (IPQ60xx) PORT_MUX_CTRL bit layout */
@@ -968,6 +968,15 @@ static void qca_ppe_phylink_get_caps(struct dsa_switch *ds, int port,
if (port != 0)
phy_interface_copy(config->pcs_interfaces,
config->supported_interfaces);
if (port != 0) {
config->lpi_capabilities = MAC_100FD | MAC_1000FD;
__set_bit(PHY_INTERFACE_MODE_QSGMII, config->lpi_interfaces);
__set_bit(PHY_INTERFACE_MODE_PSGMII, config->lpi_interfaces);
__set_bit(PHY_INTERFACE_MODE_SGMII, config->lpi_interfaces);
config->lpi_timer_default = 256;
config->eee_enabled_default = true;
}
}
static void ppe_pcs_set_mux_hppe(struct qca_ppe_priv *priv, int port,
@@ -1408,20 +1417,34 @@ static void qca_ppe_mac_link_up(struct phylink_config *config,
ppe_port_bridge_txmac_set(priv, port, true);
}
/* qca_ppe implements no LPI. The stubs exist only to make
* phylink_mac_implements_lpi() true with lpi_capabilities left at 0 -
* phylink's "EEE always disabled" case, where phylink_bringup_phy() calls
* phy_disable_eee(). Without that the PHYs negotiate 802.3az and egress into
* a MAC waking from LPI wedges the port. Never called.
*/
/* Tw_sys for 100BASE-TX is 30 us, the longest of the speeds offered. */
#define PPE_LPI_WAKE_US 32
/* nss_ppe_clk runs at 300 MHz, as qca-ssdk's ADPT_HPPE_FREQUENCY has it. */
#define PPE_LPI_CLK_MHZ 300
static int qca_ppe_mac_enable_tx_lpi(struct phylink_config *config, u32 timer,
bool tx_clk_stop)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct qca_ppe_priv *priv = ds_to_priv(dp->ds);
regmap_write(priv->regmap, PPE_LPI_1US_CNT, PPE_LPI_CLK_MHZ);
regmap_write(priv->regmap, PPE_LPI_PORT_TIMER(dp->index),
FIELD_PREP(PPE_LPI_WAKEUP_TIMER, PPE_LPI_WAKE_US) |
FIELD_PREP(PPE_LPI_SLEEP_TIMER,
min_t(u32, timer,
FIELD_MAX(PPE_LPI_SLEEP_TIMER))));
regmap_set_bits(priv->regmap, PPE_LPI_ENABLE, BIT(dp->index - 1));
return 0;
}
static void qca_ppe_mac_disable_tx_lpi(struct phylink_config *config)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct qca_ppe_priv *priv = ds_to_priv(dp->ds);
regmap_clear_bits(priv->regmap, PPE_LPI_ENABLE, BIT(dp->index - 1));
}
static const struct phylink_mac_ops qca_ppe_phylink_mac_ops = {
@@ -1733,6 +1756,7 @@ static const struct dsa_switch_ops qca_ppe_ops = {
.port_mdb_add = qca_ppe_port_mdb_add,
.port_mdb_del = qca_ppe_port_mdb_del,
.phylink_get_caps = qca_ppe_phylink_get_caps,
.support_eee = dsa_supports_eee,
.port_vlan_filtering = qca_ppe_port_vlan_filtering,
.port_vlan_add = qca_ppe_port_vlan_add,
.port_vlan_del = qca_ppe_port_vlan_del,