[PATCH net-next 8/9] net: stmmac: qcom-ethqos: add per-platform NOC clock voting
From: Mohd Ayaan Anwar <mohd.anwar@oss.qualcomm.com>
Date: 2026-09-03 18:55:09
Also in:
linux-arm-kernel, linux-arm-msm, linux-devicetree, lkml
Subsystem:
networking drivers, qualcomm ethqos ethernet driver, stmmac ethernet driver, the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Mohd Ayaan Anwar, Maxime Chevallier, Linus Torvalds
Some SoCs gate the EMAC's path to the System NOC behind dedicated clocks that must be enabled before the DMA can reach memory. Add ethqos_noc_clk_cfg and the corresponding fields in the driver-data and runtime structs so each compatible can declare its own set with per-clock rates. The clocks are acquired during probe and enabled/disabled alongside the existing link clock in ethqos_clks_config(). No functional change for existing compatibles. This will help us when we add support for Shikra. Signed-off-by: Mohd Ayaan Anwar <mohd.anwar@oss.qualcomm.com> --- .../ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c
index 4f9bef662c5796ce3cca64b19a595bcdbebbf336..a31541506a4c930c76374d7d8b7eceb6f51f37a7 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c@@ -7,6 +7,7 @@ #include <linux/platform_device.h> #include <linux/phy.h> #include <linux/phy/phy.h> +#include <linux/pm_opp.h> #include "stmmac.h" #include "stmmac_platform.h"
@@ -84,11 +85,18 @@ #define SGMII_10M_RX_CLK_DVDR 0x31 +#define ETHQOS_MAX_NOC_CLKS 3 + struct ethqos_emac_por { unsigned int offset; unsigned int value; }; +struct ethqos_noc_clk_cfg { + const char *id; + unsigned long rate; +}; + struct ethqos_emac_driver_data { const struct ethqos_emac_por *rgmii_por; unsigned int num_rgmii_por;
@@ -98,6 +106,8 @@ struct ethqos_emac_driver_data { const char *link_clk_name; struct dwmac4_addrs dwmac4_addrs; bool needs_sgmii_loopback; + const struct ethqos_noc_clk_cfg *noc_clk_cfg; + unsigned int num_noc_clks; }; struct qcom_ethqos {
@@ -112,6 +122,10 @@ struct qcom_ethqos { bool rgmii_config_loopback_en; bool has_emac_ge_3; bool needs_sgmii_loopback; + + struct clk_bulk_data noc_clks[ETHQOS_MAX_NOC_CLKS]; + unsigned long noc_clk_rates[ETHQOS_MAX_NOC_CLKS]; + int num_noc_clks; }; static u32 rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset)
@@ -691,15 +705,51 @@ static int ethqos_mac_finish_serdes(struct net_device *ndev, void *priv, static int ethqos_clks_config(void *priv, bool enabled) { struct qcom_ethqos *ethqos = priv; + unsigned int i; int ret = 0; if (enabled) { + if (ethqos->num_noc_clks) { + ret = dev_pm_opp_set_rate(ðqos->pdev->dev, + ethqos->noc_clk_rates[0]); + if (ret) { + dev_err(ðqos->pdev->dev, + "NOC OPP rate set failed: %d\n", ret); + return ret; + } + + for (i = 1; i < ethqos->num_noc_clks; i++) { + ret = clk_set_rate(ethqos->noc_clks[i].clk, + ethqos->noc_clk_rates[i]); + if (ret) { + dev_err(ðqos->pdev->dev, + "NOC clock rate set failed: %d\n", ret); + dev_pm_opp_set_rate(ðqos->pdev->dev, 0); + return ret; + } + } + } + ret = clk_prepare_enable(ethqos->link_clk); if (ret) { dev_err(ðqos->pdev->dev, "link_clk enable failed\n"); + if (ethqos->num_noc_clks) + dev_pm_opp_set_rate(ðqos->pdev->dev, 0); return ret; } + if (ethqos->num_noc_clks) { + ret = clk_bulk_prepare_enable(ethqos->num_noc_clks, + ethqos->noc_clks); + if (ret) { + dev_err(ðqos->pdev->dev, + "NOC clocks enable failed: %d\n", ret); + dev_pm_opp_set_rate(ðqos->pdev->dev, 0); + clk_disable_unprepare(ethqos->link_clk); + return ret; + } + } + /* Enable functional clock to prevent DMA reset to timeout due * to lacking PHY clock after the hardware block has been power * cycled. The actual configuration will be adjusted once
@@ -708,7 +758,12 @@ static int ethqos_clks_config(void *priv, bool enabled) qcom_ethqos_set_sgmii_loopback(ethqos, true); ethqos_set_func_clk_en(ethqos); } else { + if (ethqos->num_noc_clks) + clk_bulk_disable_unprepare(ethqos->num_noc_clks, + ethqos->noc_clks); clk_disable_unprepare(ethqos->link_clk); + if (ethqos->num_noc_clks) + dev_pm_opp_set_rate(ðqos->pdev->dev, 0); } return ret;
@@ -736,6 +791,46 @@ static void ethqos_ptp_clk_freq_config(struct stmmac_priv *priv) netdev_dbg(priv->dev, "PTP rate %lu\n", plat_dat->clk_ptp_rate); } +/* Some SoCs gate NOC access behind dedicated clocks. Acquire them here + * so ethqos_clks_config() can enable/disable them at runtime. The OPP + * table is used to propagate the required VDD_CX performance state via + * dev_pm_opp_set_rate(). + */ +static int qcom_ethqos_init_noc_clks(struct qcom_ethqos *ethqos, + const struct ethqos_emac_driver_data *data) +{ + struct device *dev = ðqos->pdev->dev; + unsigned int i; + int ret; + + if (!data->num_noc_clks) + return 0; + + for (i = 0; i < data->num_noc_clks; i++) { + ethqos->noc_clks[i].id = data->noc_clk_cfg[i].id; + ethqos->noc_clk_rates[i] = data->noc_clk_cfg[i].rate; + } + ethqos->num_noc_clks = data->num_noc_clks; + + ret = devm_clk_bulk_get(dev, ethqos->num_noc_clks, ethqos->noc_clks); + if (ret) + return dev_err_probe(dev, ret, "Failed to get NOC clocks\n"); + + ret = devm_pm_opp_set_clkname(dev, data->noc_clk_cfg[0].id); + if (ret) + return dev_err_probe(dev, ret, "Failed to set OPP clock name\n"); + + ret = devm_pm_opp_of_add_table(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to add OPP table\n"); + + ret = dev_pm_opp_set_rate(dev, data->noc_clk_cfg[0].rate); + if (ret) + return dev_err_probe(dev, ret, "Failed to set initial NOC OPP rate\n"); + + return 0; +} + static int qcom_ethqos_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node;
@@ -795,6 +890,12 @@ static int qcom_ethqos_probe(struct platform_device *pdev) ethqos->has_emac_ge_3 = data->has_emac_ge_3; ethqos->needs_sgmii_loopback = data->needs_sgmii_loopback; + if (data->num_noc_clks) { + ret = qcom_ethqos_init_noc_clks(ethqos, data); + if (ret) + return ret; + } + ethqos->link_clk = devm_clk_get(dev, data->link_clk_name ?: "rgmii"); if (IS_ERR(ethqos->link_clk)) return dev_err_probe(dev, PTR_ERR(ethqos->link_clk),
--
2.34.1