--- /dev/null
+From 93cf3297818ee61607f0a8d1d34e4fb7fcde3cdf Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Tue, 26 Dec 2023 20:18:09 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE scheduler config
+
+PPE scheduler config determines the priority of scheduling the
+packet. The scheduler config is used for supporting the QoS
+offload in PPE hardware.
+
+Change-Id: I4811bd133074757371775a6a69a1cc3cfaa8d0d0
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: rebase patch on top of PPE driver submission from 20250209.
+ Add the ppe_queue_priority_set() function and its
+ dependencies. They will be used in the edma support in
+ susequent changes.
+ ppe_queue_priority_set() used to be part of ppe_api.c, and
+ is hereby moved to ppe_config.c .
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 141 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 5 +
+ 2 files changed, 146 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -864,6 +864,51 @@ static int ppe_scheduler_l0_queue_map_se
+ val);
+ }
+
++/* Get the first level scheduler configuration. */
++static int ppe_scheduler_l0_queue_map_get(struct ppe_device *ppe_dev,
++ int node_id, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg)
++{
++ u32 val, reg;
++ int ret;
++
++ reg = PPE_L0_FLOW_MAP_TBL_ADDR + node_id * PPE_L0_FLOW_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->flow_id = FIELD_GET(PPE_L0_FLOW_MAP_TBL_FLOW_ID, val);
++ scheduler_cfg->pri = FIELD_GET(PPE_L0_FLOW_MAP_TBL_C_PRI, val);
++ scheduler_cfg->drr_node_wt = FIELD_GET(PPE_L0_FLOW_MAP_TBL_C_NODE_WT, val);
++
++ reg = PPE_L0_C_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg->flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg->pri) *
++ PPE_L0_C_FLOW_CFG_TBL_INC;
++
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->drr_node_id = FIELD_GET(PPE_L0_C_FLOW_CFG_TBL_NODE_ID, val);
++ scheduler_cfg->unit_is_packet = FIELD_GET(PPE_L0_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT, val);
++
++ reg = PPE_L0_FLOW_PORT_MAP_TBL_ADDR + node_id * PPE_L0_FLOW_PORT_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ *port = FIELD_GET(PPE_L0_FLOW_PORT_MAP_TBL_PORT_NUM, val);
++
++ reg = PPE_L0_COMP_CFG_TBL_ADDR + node_id * PPE_L0_COMP_CFG_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->frame_mode = FIELD_GET(PPE_L0_COMP_CFG_TBL_NODE_METER_LEN, val);
++
++ return 0;
++}
++
+ /* Set the PPE flow level scheduler configuration. */
+ static int ppe_scheduler_l1_queue_map_set(struct ppe_device *ppe_dev,
+ int node_id, int port,
+@@ -916,6 +961,50 @@ static int ppe_scheduler_l1_queue_map_se
+ return regmap_update_bits(ppe_dev->regmap, reg, PPE_L1_COMP_CFG_TBL_NODE_METER_LEN, val);
+ }
+
++/* Get the second level scheduler configuration. */
++static int ppe_scheduler_l1_queue_map_get(struct ppe_device *ppe_dev,
++ int node_id, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg)
++{
++ u32 val, reg;
++ int ret;
++
++ reg = PPE_L1_FLOW_MAP_TBL_ADDR + node_id * PPE_L1_FLOW_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->flow_id = FIELD_GET(PPE_L1_FLOW_MAP_TBL_FLOW_ID, val);
++ scheduler_cfg->pri = FIELD_GET(PPE_L1_FLOW_MAP_TBL_C_PRI, val);
++ scheduler_cfg->drr_node_wt = FIELD_GET(PPE_L1_FLOW_MAP_TBL_C_NODE_WT, val);
++
++ reg = PPE_L1_C_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg->flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg->pri) *
++ PPE_L1_C_FLOW_CFG_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->drr_node_id = FIELD_GET(PPE_L1_C_FLOW_CFG_TBL_NODE_ID, val);
++ scheduler_cfg->unit_is_packet = FIELD_GET(PPE_L1_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT, val);
++
++ reg = PPE_L1_FLOW_PORT_MAP_TBL_ADDR + node_id * PPE_L1_FLOW_PORT_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ *port = FIELD_GET(PPE_L1_FLOW_PORT_MAP_TBL_PORT_NUM, val);
++
++ reg = PPE_L1_COMP_CFG_TBL_ADDR + node_id * PPE_L1_COMP_CFG_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->frame_mode = FIELD_GET(PPE_L1_COMP_CFG_TBL_NODE_METER_LEN, val);
++
++ return 0;
++}
++
+ /**
+ * ppe_queue_scheduler_set - Configure scheduler for PPE hardware queue
+ * @ppe_dev: PPE device
+@@ -942,6 +1031,58 @@ int ppe_queue_scheduler_set(struct ppe_d
+ }
+
+ /**
++ * ppe_queue_scheduler_get - get QoS scheduler of PPE hardware queue
++ * @ppe_dev: PPE device
++ * @node_id: PPE node ID
++ * @flow_level: Flow level scheduler or queue level scheduler
++ * @port: PPE port ID to get scheduler config
++ * @scheduler_cfg: QoS scheduler configuration
++ *
++ * The hardware QoS function is supported by PPE, the current scheduler
++ * configuration can be acquired based on the queue ID of PPE port.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_queue_scheduler_get(struct ppe_device *ppe_dev,
++ int node_id, bool flow_level, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg)
++{
++ if (flow_level)
++ return ppe_scheduler_l1_queue_map_get(ppe_dev, node_id,
++ port, scheduler_cfg);
++
++ return ppe_scheduler_l0_queue_map_get(ppe_dev, node_id,
++ port, scheduler_cfg);
++}
++
++
++/**
++ * ppe_queue_priority_set - set scheduler priority of PPE hardware queue
++ * @ppe_dev: PPE device
++ * @node_id: PPE hardware node ID, which is either queue ID or flow ID
++ * @priority: Qos scheduler priority
++ *
++ * Configure scheduler priority of PPE hardware queque, the maximum node
++ * ID supported is PPE_QUEUE_ID_NUM added by PPE_FLOW_ID_NUM, queue ID
++ * belongs to level 0, flow ID belongs to level 1 in the packet pipeline.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_queue_priority_set(struct ppe_device *ppe_dev,
++ int node_id, int priority)
++{
++ struct ppe_scheduler_cfg sch_cfg;
++ int ret, port, level = 0;
++
++ ret = ppe_queue_scheduler_get(ppe_dev, node_id, level, &port, &sch_cfg);
++ if (ret)
++ return ret;
++
++ sch_cfg.pri = priority;
++ return ppe_queue_scheduler_set(ppe_dev, node_id, level, port, sch_cfg);
++}
++
++/**
+ * ppe_queue_ucast_base_set - Set PPE unicast queue base ID and profile ID
+ * @ppe_dev: PPE device
+ * @queue_dst: PPE queue destination configuration
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -291,6 +291,11 @@ int ppe_hw_config(struct ppe_device *ppe
+ int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
+ int node_id, bool flow_level, int port,
+ struct ppe_scheduler_cfg scheduler_cfg);
++int ppe_queue_scheduler_get(struct ppe_device *ppe_dev,
++ int node_id, bool flow_level, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg);
++int ppe_queue_priority_set(struct ppe_device *ppe_dev,
++ int queue_id, int priority);
+ int ppe_queue_ucast_base_set(struct ppe_device *ppe_dev,
+ struct ppe_queue_ucast_dest queue_dst,
+ int queue_base,
--- /dev/null
+From dbb3711ab25ea410ad5286b2f39dccd954cda225 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Thu, 29 Feb 2024 16:59:53 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add phylink support for PPE MAC
+ ports
+
+Add MAC initialization and phylink functions for PPE MAC ports.
+
+Change-Id: I39dcba671732392bcfa2e734473fd083989bfbec
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 3 +
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 9 +
+ drivers/net/ethernet/qualcomm/ppe/ppe.h | 2 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 728 +++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 76 ++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 124 ++++
+ 7 files changed, 943 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -66,6 +66,9 @@ config QCOM_PPE
+ depends on HAS_IOMEM && OF
+ depends on COMMON_CLK
+ select REGMAP_MMIO
++ select PHYLINK
++ select PCS_QCOM_IPQ_UNIPHY
++ select SFP
+ help
+ This driver supports the Qualcomm Technologies, Inc. packet
+ process engine (PPE) available with IPQ SoC. The PPE includes
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -4,4 +4,4 @@
+ #
+
+ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+-qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o
++qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -17,6 +17,7 @@
+ #include "ppe.h"
+ #include "ppe_config.h"
+ #include "ppe_debugfs.h"
++#include "ppe_port.h"
+
+ #define PPE_PORT_MAX 8
+ #define PPE_CLK_RATE 353000000
+@@ -200,6 +201,11 @@ static int qcom_ppe_probe(struct platfor
+ if (ret)
+ return dev_err_probe(dev, ret, "PPE HW config failed\n");
+
++ ret = ppe_port_mac_init(ppe_dev);
++ if (ret)
++ return dev_err_probe(dev, ret,
++ "PPE Port MAC initialization failed\n");
++
+ ppe_debugfs_setup(ppe_dev);
+ platform_set_drvdata(pdev, ppe_dev);
+
+@@ -212,6 +218,9 @@ static void qcom_ppe_remove(struct platf
+
+ ppe_dev = platform_get_drvdata(pdev);
+ ppe_debugfs_teardown(ppe_dev);
++ ppe_port_mac_deinit(ppe_dev);
++
++ platform_set_drvdata(pdev, NULL);
+ }
+
+ static const struct of_device_id qcom_ppe_of_match[] = {
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.h
+@@ -20,6 +20,7 @@ struct dentry;
+ * @clk_rate: PPE clock rate.
+ * @num_ports: Number of PPE ports.
+ * @debugfs_root: Debugfs root entry.
++ * @ports: PPE MAC ports.
+ * @num_icc_paths: Number of interconnect paths.
+ * @icc_paths: Interconnect path array.
+ *
+@@ -33,6 +34,7 @@ struct ppe_device {
+ unsigned long clk_rate;
+ unsigned int num_ports;
+ struct dentry *debugfs_root;
++ struct ppe_ports *ports;
+ unsigned int num_icc_paths;
+ struct icc_bulk_data icc_paths[] __counted_by(num_icc_paths);
+ };
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -0,0 +1,728 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE Port MAC initialization and PPE port MAC functions. */
++
++#include <linux/clk.h>
++#include <linux/of_net.h>
++#include <linux/pcs/pcs-qcom-ipq-uniphy.h>
++#include <linux/phylink.h>
++#include <linux/reset.h>
++#include <linux/regmap.h>
++#include <linux/rtnetlink.h>
++
++#include "ppe.h"
++#include "ppe_port.h"
++#include "ppe_regs.h"
++
++/* PPE MAC max frame size which including 4bytes FCS */
++#define PPE_PORT_MAC_MAX_FRAME_SIZE 0x3000
++
++/* PPE BM port start for PPE MAC ports */
++#define PPE_BM_PORT_MAC_START 7
++
++/* PPE port clock and reset name */
++static const char * const ppe_port_clk_rst_name[] = {
++ [PPE_PORT_CLK_RST_MAC] = "port_mac",
++ [PPE_PORT_CLK_RST_RX] = "port_rx",
++ [PPE_PORT_CLK_RST_TX] = "port_tx",
++};
++
++/* PPE port and MAC reset */
++static int ppe_port_mac_reset(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret;
++
++ ret = reset_control_assert(ppe_port->rstcs[PPE_PORT_CLK_RST_MAC]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_assert(ppe_port->rstcs[PPE_PORT_CLK_RST_RX]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_assert(ppe_port->rstcs[PPE_PORT_CLK_RST_TX]);
++ if (ret)
++ goto error;
++
++ /* 150ms delay is required by hardware to reset PPE port and MAC */
++ msleep(150);
++
++ ret = reset_control_deassert(ppe_port->rstcs[PPE_PORT_CLK_RST_MAC]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_deassert(ppe_port->rstcs[PPE_PORT_CLK_RST_RX]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_deassert(ppe_port->rstcs[PPE_PORT_CLK_RST_TX]);
++ if (ret)
++ goto error;
++
++ return ret;
++
++error:
++ dev_err(ppe_dev->dev, "%s: port %d reset fail %d\n",
++ __func__, ppe_port->port_id, ret);
++ return ret;
++}
++
++/* PPE port MAC configuration for phylink */
++static void ppe_port_mac_config(struct phylink_config *config,
++ unsigned int mode,
++ const struct phylink_link_state *state)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int port = ppe_port->port_id;
++ enum ppe_mac_type mac_type;
++ u32 val, mask;
++ int ret;
++
++ switch (state->interface) {
++ case PHY_INTERFACE_MODE_2500BASEX:
++ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
++ case PHY_INTERFACE_MODE_10G_QXGMII:
++ mac_type = PPE_MAC_TYPE_XGMAC;
++ break;
++ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ mac_type = PPE_MAC_TYPE_GMAC;
++ break;
++ default:
++ dev_err(ppe_dev->dev, "%s: Unsupport interface %s\n",
++ __func__, phy_modes(state->interface));
++ return;
++ }
++
++ /* Reset Port MAC for GMAC */
++ if (mac_type == PPE_MAC_TYPE_GMAC) {
++ ret = ppe_port_mac_reset(ppe_port);
++ if (ret)
++ goto err_mac_config;
++ }
++
++ /* Port mux to select GMAC or XGMAC */
++ mask = PPE_PORT_SEL_XGMAC(port);
++ val = mac_type == PPE_MAC_TYPE_GMAC ? 0 : mask;
++ ret = regmap_update_bits(ppe_dev->regmap,
++ PPE_PORT_MUX_CTRL_ADDR,
++ mask, val);
++ if (ret)
++ goto err_mac_config;
++
++ ppe_port->mac_type = mac_type;
++
++ return;
++
++err_mac_config:
++ dev_err(ppe_dev->dev, "%s: port %d MAC config fail %d\n",
++ __func__, port, ret);
++}
++
++/* PPE port GMAC link up configuration */
++static int ppe_port_gmac_link_up(struct ppe_port *ppe_port, int speed,
++ int duplex, bool tx_pause, bool rx_pause)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ /* Set GMAC speed */
++ switch (speed) {
++ case SPEED_1000:
++ val = GMAC_SPEED_1000;
++ break;
++ case SPEED_100:
++ val = GMAC_SPEED_100;
++ break;
++ case SPEED_10:
++ val = GMAC_SPEED_10;
++ break;
++ default:
++ dev_err(ppe_dev->dev, "%s: Invalid GMAC speed %s\n",
++ __func__, phy_speed_to_str(speed));
++ return -EINVAL;
++ }
++
++ reg = PPE_PORT_GMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_SPEED_ADDR,
++ GMAC_SPEED_M, val);
++ if (ret)
++ return ret;
++
++ /* Set duplex, flow control and enable GMAC */
++ val = GMAC_TRXEN;
++ if (duplex == DUPLEX_FULL)
++ val |= GMAC_DUPLEX_FULL;
++ if (tx_pause)
++ val |= GMAC_TXFCEN;
++ if (rx_pause)
++ val |= GMAC_RXFCEN;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_ENABLE_ADDR,
++ GMAC_ENABLE_ALL, val);
++
++ return ret;
++}
++
++/* PPE port XGMAC link up configuration */
++static int ppe_port_xgmac_link_up(struct ppe_port *ppe_port,
++ phy_interface_t interface,
++ int speed, int duplex,
++ bool tx_pause, bool rx_pause)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ /* Set XGMAC TX speed and enable TX */
++ switch (speed) {
++ case SPEED_10000:
++ if (interface == PHY_INTERFACE_MODE_USXGMII)
++ val = XGMAC_SPEED_10000_USXGMII;
++ else
++ val = XGMAC_SPEED_10000;
++ break;
++ case SPEED_5000:
++ val = XGMAC_SPEED_5000;
++ break;
++ case SPEED_2500:
++ if (interface == PHY_INTERFACE_MODE_USXGMII ||
++ interface == PHY_INTERFACE_MODE_10G_QXGMII)
++ val = XGMAC_SPEED_2500_USXGMII;
++ else
++ val = XGMAC_SPEED_2500;
++ break;
++ case SPEED_1000:
++ val = XGMAC_SPEED_1000;
++ break;
++ case SPEED_100:
++ val = XGMAC_SPEED_100;
++ break;
++ case SPEED_10:
++ val = XGMAC_SPEED_10;
++ break;
++ default:
++ dev_err(ppe_dev->dev, "%s: Invalid XGMAC speed %s\n",
++ __func__, phy_speed_to_str(speed));
++ return -EINVAL;
++ }
++
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ val |= XGMAC_TXEN;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_TX_CONFIG_ADDR,
++ XGMAC_SPEED_M | XGMAC_TXEN, val);
++ if (ret)
++ return ret;
++
++ /* Set XGMAC TX flow control */
++ val = FIELD_PREP(XGMAC_PAUSE_TIME_M, FIELD_MAX(XGMAC_PAUSE_TIME_M));
++ val |= tx_pause ? XGMAC_TXFCEN : 0;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_TX_FLOW_CTRL_ADDR,
++ XGMAC_PAUSE_TIME_M | XGMAC_TXFCEN, val);
++ if (ret)
++ return ret;
++
++ /* Set XGMAC RX flow control */
++ val = rx_pause ? XGMAC_RXFCEN : 0;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_RX_FLOW_CTRL_ADDR,
++ XGMAC_RXFCEN, val);
++ if (ret)
++ return ret;
++
++ /* Enable XGMAC RX*/
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_RX_CONFIG_ADDR,
++ XGMAC_RXEN, XGMAC_RXEN);
++
++ return ret;
++}
++
++/* PPE port MAC link up configuration for phylink */
++static void ppe_port_mac_link_up(struct phylink_config *config,
++ struct phy_device *phy,
++ unsigned int mode,
++ phy_interface_t interface,
++ int speed, int duplex,
++ bool tx_pause, bool rx_pause)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ enum ppe_mac_type mac_type = ppe_port->mac_type;
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ if (mac_type == PPE_MAC_TYPE_GMAC)
++ ret = ppe_port_gmac_link_up(ppe_port,
++ speed, duplex, tx_pause, rx_pause);
++ else
++ ret = ppe_port_xgmac_link_up(ppe_port, interface,
++ speed, duplex, tx_pause, rx_pause);
++ if (ret)
++ goto err_port_mac_link_up;
++
++ /* Set PPE port BM flow control */
++ reg = PPE_BM_PORT_FC_MODE_ADDR +
++ PPE_BM_PORT_FC_MODE_INC * (port + PPE_BM_PORT_MAC_START);
++ val = tx_pause ? PPE_BM_PORT_FC_MODE_EN : 0;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_BM_PORT_FC_MODE_EN, val);
++ if (ret)
++ goto err_port_mac_link_up;
++
++ /* Enable PPE port TX */
++ reg = PPE_PORT_BRIDGE_CTRL_ADDR + PPE_PORT_BRIDGE_CTRL_INC * port;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_PORT_BRIDGE_TXMAC_EN,
++ PPE_PORT_BRIDGE_TXMAC_EN);
++ if (ret)
++ goto err_port_mac_link_up;
++
++ return;
++
++err_port_mac_link_up:
++ dev_err(ppe_dev->dev, "%s: port %d link up fail %d\n",
++ __func__, port, ret);
++}
++
++/* PPE port MAC link down configuration for phylink */
++static void ppe_port_mac_link_down(struct phylink_config *config,
++ unsigned int mode,
++ phy_interface_t interface)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ enum ppe_mac_type mac_type = ppe_port->mac_type;
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg;
++
++ /* Disable PPE port TX */
++ reg = PPE_PORT_BRIDGE_CTRL_ADDR + PPE_PORT_BRIDGE_CTRL_INC * port;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_PORT_BRIDGE_TXMAC_EN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++
++ /* Disable PPE MAC */
++ if (mac_type == PPE_MAC_TYPE_GMAC) {
++ reg = PPE_PORT_GMAC_ADDR(port) + GMAC_ENABLE_ADDR;
++ ret = regmap_update_bits(ppe_dev->regmap, reg, GMAC_TRXEN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++ } else {
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap,
++ reg + XGMAC_RX_CONFIG_ADDR,
++ XGMAC_RXEN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++
++ ret = regmap_update_bits(ppe_dev->regmap,
++ reg + XGMAC_TX_CONFIG_ADDR,
++ XGMAC_TXEN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++ }
++
++ return;
++
++err_port_mac_link_down:
++ dev_err(ppe_dev->dev, "%s: port %d link down fail %d\n",
++ __func__, port, ret);
++}
++
++/* PPE port MAC PCS selection for phylink */
++static
++struct phylink_pcs *ppe_port_mac_select_pcs(struct phylink_config *config,
++ phy_interface_t interface)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 val;
++
++ /* PPE port5 can connects with PCS0 or PCS1. In PSGMII
++ * mode, it selects PCS0; otherwise, it selects PCS1.
++ */
++ if (port == 5) {
++ val = interface == PHY_INTERFACE_MODE_PSGMII ?
++ 0 : PPE_PORT5_SEL_PCS1;
++ ret = regmap_update_bits(ppe_dev->regmap,
++ PPE_PORT_MUX_CTRL_ADDR,
++ PPE_PORT5_SEL_PCS1, val);
++ if (ret) {
++ dev_err(ppe_dev->dev, "%s: port5 select PCS fail %d\n",
++ __func__, ret);
++ return NULL;
++ }
++ }
++
++ return ppe_port->pcs;
++}
++
++static const struct phylink_mac_ops ppe_phylink_ops = {
++ .mac_config = ppe_port_mac_config,
++ .mac_link_up = ppe_port_mac_link_up,
++ .mac_link_down = ppe_port_mac_link_down,
++ .mac_select_pcs = ppe_port_mac_select_pcs,
++};
++
++/**
++ * ppe_port_phylink_setup() - Set phylink instance for the given PPE port
++ * @ppe_port: PPE port
++ * @netdev: Netdevice
++ *
++ * Description: Wrapper function to help setup phylink for the PPE port
++ * specified by @ppe_port and associated with the net device @netdev.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_phylink_setup(struct ppe_port *ppe_port, struct net_device *netdev)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ struct device_node *pcs_node;
++ int ret;
++
++ /* Create PCS */
++ pcs_node = of_parse_phandle(ppe_port->np, "pcs-handle", 0);
++ if (!pcs_node)
++ return -ENODEV;
++
++ ppe_port->pcs = ipq_unipcs_create(pcs_node);
++ of_node_put(pcs_node);
++ if (IS_ERR(ppe_port->pcs)) {
++ dev_err(ppe_dev->dev, "%s: port %d failed to create PCS\n",
++ __func__, ppe_port->port_id);
++ return PTR_ERR(ppe_port->pcs);
++ }
++
++ /* Port phylink capability */
++ ppe_port->phylink_config.dev = &netdev->dev;
++ ppe_port->phylink_config.type = PHYLINK_NETDEV;
++ ppe_port->phylink_config.mac_capabilities = MAC_ASYM_PAUSE |
++ MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000 |
++ MAC_2500FD | MAC_5000FD | MAC_10000FD;
++ __set_bit(PHY_INTERFACE_MODE_QSGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_PSGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_SGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_USXGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_10GBASER,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_10G_QXGMII,
++ ppe_port->phylink_config.supported_interfaces);
++
++ /* Create phylink */
++ ppe_port->phylink = phylink_create(&ppe_port->phylink_config,
++ of_fwnode_handle(ppe_port->np),
++ ppe_port->interface,
++ &ppe_phylink_ops);
++ if (IS_ERR(ppe_port->phylink)) {
++ dev_err(ppe_dev->dev, "%s: port %d failed to create phylink\n",
++ __func__, ppe_port->port_id);
++ ret = PTR_ERR(ppe_port->phylink);
++ goto err_free_pcs;
++ }
++
++ /* Connect phylink */
++ ret = phylink_of_phy_connect(ppe_port->phylink, ppe_port->np, 0);
++ if (ret) {
++ dev_err(ppe_dev->dev, "%s: port %d failed to connect phylink\n",
++ __func__, ppe_port->port_id);
++ goto err_free_phylink;
++ }
++
++ return 0;
++
++err_free_phylink:
++ phylink_destroy(ppe_port->phylink);
++ ppe_port->phylink = NULL;
++err_free_pcs:
++ ipq_unipcs_destroy(ppe_port->pcs);
++ ppe_port->pcs = NULL;
++ return ret;
++}
++
++/**
++ * ppe_port_phylink_destroy() - Destroy phylink instance for the given PPE port
++ * @ppe_port: PPE port
++ *
++ * Description: Wrapper function to help destroy phylink for the PPE port
++ * specified by @ppe_port.
++ */
++void ppe_port_phylink_destroy(struct ppe_port *ppe_port)
++{
++ /* Destroy phylink */
++ if (ppe_port->phylink) {
++ rtnl_lock();
++ phylink_disconnect_phy(ppe_port->phylink);
++ rtnl_unlock();
++ phylink_destroy(ppe_port->phylink);
++ ppe_port->phylink = NULL;
++ }
++
++ /* Destroy PCS */
++ if (ppe_port->pcs) {
++ ipq_unipcs_destroy(ppe_port->pcs);
++ ppe_port->pcs = NULL;
++ }
++}
++
++/* PPE port clock initialization */
++static int ppe_port_clock_init(struct ppe_port *ppe_port)
++{
++ struct device_node *port_node = ppe_port->np;
++ struct reset_control *rstc;
++ struct clk *clk;
++ int i, j, ret;
++
++ for (i = 0; i < PPE_PORT_CLK_RST_MAX; i++) {
++ /* Get PPE port resets which will be used to reset PPE
++ * port and MAC.
++ */
++ rstc = of_reset_control_get_exclusive(port_node,
++ ppe_port_clk_rst_name[i]);
++ if (IS_ERR(rstc)) {
++ ret = PTR_ERR(rstc);
++ goto err_rst;
++ }
++
++ clk = of_clk_get_by_name(port_node, ppe_port_clk_rst_name[i]);
++ if (IS_ERR(clk)) {
++ ret = PTR_ERR(clk);
++ goto err_clk_get;
++ }
++
++ ret = clk_prepare_enable(clk);
++ if (ret)
++ goto err_clk_en;
++
++ ppe_port->clks[i] = clk;
++ ppe_port->rstcs[i] = rstc;
++ }
++
++ return 0;
++
++err_clk_en:
++ clk_put(clk);
++err_clk_get:
++ reset_control_put(rstc);
++err_rst:
++ for (j = 0; j < i; j++) {
++ clk_disable_unprepare(ppe_port->clks[j]);
++ clk_put(ppe_port->clks[j]);
++ reset_control_put(ppe_port->rstcs[j]);
++ }
++
++ return ret;
++}
++
++/* PPE port clock deinitialization */
++static void ppe_port_clock_deinit(struct ppe_port *ppe_port)
++{
++ int i;
++
++ for (i = 0; i < PPE_PORT_CLK_RST_MAX; i++) {
++ clk_disable_unprepare(ppe_port->clks[i]);
++ clk_put(ppe_port->clks[i]);
++ reset_control_put(ppe_port->rstcs[i]);
++ }
++}
++
++/* PPE port MAC hardware init configuration */
++static int ppe_port_mac_hw_init(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ /* GMAC RX and TX are initialized as disabled */
++ reg = PPE_PORT_GMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap,
++ reg + GMAC_ENABLE_ADDR, GMAC_TRXEN, 0);
++ if (ret)
++ return ret;
++
++ /* GMAC max frame size configuration */
++ val = FIELD_PREP(GMAC_JUMBO_SIZE_M, PPE_PORT_MAC_MAX_FRAME_SIZE);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_JUMBO_SIZE_ADDR,
++ GMAC_JUMBO_SIZE_M, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(GMAC_MAXFRAME_SIZE_M, PPE_PORT_MAC_MAX_FRAME_SIZE);
++ val |= FIELD_PREP(GMAC_TX_THD_M, 0x1);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_CTRL_ADDR,
++ GMAC_CTRL_MASK, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(GMAC_HIGH_IPG_M, 0xc);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_DBG_CTRL_ADDR,
++ GMAC_HIGH_IPG_M, val);
++ if (ret)
++ return ret;
++
++ /* Enable and reset GMAC MIB counters and set as read clear
++ * mode, the GMAC MIB counters will be cleared after reading.
++ */
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_MIB_CTRL_ADDR,
++ GMAC_MIB_CTRL_MASK, GMAC_MIB_CTRL_MASK);
++ if (ret)
++ return ret;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_MIB_CTRL_ADDR,
++ GMAC_MIB_RST, 0);
++ if (ret)
++ return ret;
++
++ /* XGMAC RX and TX disabled and max frame size configuration */
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_TX_CONFIG_ADDR,
++ XGMAC_TXEN | XGMAC_JD, XGMAC_JD);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(XGMAC_GPSL_M, PPE_PORT_MAC_MAX_FRAME_SIZE);
++ val |= XGMAC_GPSLEN;
++ val |= XGMAC_CST;
++ val |= XGMAC_ACS;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_RX_CONFIG_ADDR,
++ XGMAC_RX_CONFIG_MASK, val);
++ if (ret)
++ return ret;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_WD_TIMEOUT_ADDR,
++ XGMAC_WD_TIMEOUT_MASK, XGMAC_WD_TIMEOUT_VAL);
++ if (ret)
++ return ret;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_PKT_FILTER_ADDR,
++ XGMAC_PKT_FILTER_MASK, XGMAC_PKT_FILTER_VAL);
++ if (ret)
++ return ret;
++
++ /* Enable and reset XGMAC MIB counters */
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_MMC_CTRL_ADDR,
++ XGMAC_MCF | XGMAC_CNTRST, XGMAC_CNTRST);
++
++ return ret;
++}
++
++/**
++ * ppe_port_mac_init() - Initialization of PPE ports for the PPE device
++ * @ppe_dev: PPE device
++ *
++ * Description: Initialize the PPE MAC ports on the PPE device specified
++ * by @ppe_dev.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_mac_init(struct ppe_device *ppe_dev)
++{
++ struct device_node *ports_node, *port_node;
++ int port, num, ret, j, i = 0;
++ struct ppe_ports *ppe_ports;
++ phy_interface_t phy_mode;
++
++ ports_node = of_get_child_by_name(ppe_dev->dev->of_node,
++ "ethernet-ports");
++ if (!ports_node) {
++ dev_err(ppe_dev->dev, "Failed to get ports node\n");
++ return -ENODEV;
++ }
++
++ num = of_get_available_child_count(ports_node);
++
++ ppe_ports = devm_kzalloc(ppe_dev->dev,
++ struct_size(ppe_ports, port, num),
++ GFP_KERNEL);
++ if (!ppe_ports) {
++ ret = -ENOMEM;
++ goto err_ports_node;
++ }
++
++ ppe_dev->ports = ppe_ports;
++ ppe_ports->num = num;
++
++ for_each_available_child_of_node(ports_node, port_node) {
++ ret = of_property_read_u32(port_node, "reg", &port);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to get port id\n");
++ goto err_port_node;
++ }
++
++ ret = of_get_phy_mode(port_node, &phy_mode);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to get phy mode\n");
++ goto err_port_node;
++ }
++
++ ppe_ports->port[i].ppe_dev = ppe_dev;
++ ppe_ports->port[i].port_id = port;
++ ppe_ports->port[i].np = port_node;
++ ppe_ports->port[i].interface = phy_mode;
++
++ ret = ppe_port_clock_init(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to initialize port clocks\n");
++ goto err_port_clk;
++ }
++
++ ret = ppe_port_mac_hw_init(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to initialize MAC hardware\n");
++ goto err_port_node;
++ }
++
++ i++;
++ }
++
++ of_node_put(ports_node);
++ return 0;
++
++err_port_clk:
++ for (j = 0; j < i; j++)
++ ppe_port_clock_deinit(&ppe_ports->port[j]);
++err_port_node:
++ of_node_put(port_node);
++err_ports_node:
++ of_node_put(ports_node);
++ return ret;
++}
++
++/**
++ * ppe_port_mac_deinit() - Deinitialization of PPE ports for the PPE device
++ * @ppe_dev: PPE device
++ *
++ * Description: Deinitialize the PPE MAC ports on the PPE device specified
++ * by @ppe_dev.
++ */
++void ppe_port_mac_deinit(struct ppe_device *ppe_dev)
++{
++ struct ppe_port *ppe_port;
++ int i;
++
++ for (i = 0; i < ppe_dev->ports->num; i++) {
++ ppe_port = &ppe_dev->ports->port[i];
++ ppe_port_clock_deinit(ppe_port);
++ }
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -0,0 +1,76 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ *
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __PPE_PORT_H__
++#define __PPE_PORT_H__
++
++#include <linux/phylink.h>
++
++/**
++ * enum ppe_port_clk_rst_type - PPE port clock and reset ID type
++ * @PPE_PORT_CLK_RST_MAC: The clock and reset ID for port MAC
++ * @PPE_PORT_CLK_RST_RX: The clock and reset ID for port receive path
++ * @PPE_PORT_CLK_RST_TX: The clock and reset for port transmit path
++ * @PPE_PORT_CLK_RST_MAX: The maximum of port clock and reset
++ */
++enum ppe_port_clk_rst_type {
++ PPE_PORT_CLK_RST_MAC,
++ PPE_PORT_CLK_RST_RX,
++ PPE_PORT_CLK_RST_TX,
++ PPE_PORT_CLK_RST_MAX,
++};
++
++/**
++ * enum ppe_mac_type - PPE MAC type
++ * @PPE_MAC_TYPE_GMAC: GMAC type
++ * @PPE_MAC_TYPE_XGMAC: XGMAC type
++ */
++enum ppe_mac_type {
++ PPE_MAC_TYPE_GMAC,
++ PPE_MAC_TYPE_XGMAC,
++};
++
++/**
++ * struct ppe_port - Private data for each PPE port
++ * @phylink: Linux phylink instance
++ * @phylink_config: Linux phylink configurations
++ * @pcs: Linux phylink PCS instance
++ * @np: Port device tree node
++ * @ppe_dev: Back pointer to PPE device private data
++ * @interface: Port interface mode
++ * @mac_type: Port MAC type, GMAC or XGMAC
++ * @port_id: Port ID
++ * @clks: Port clocks
++ * @rstcs: Port resets
++ */
++struct ppe_port {
++ struct phylink *phylink;
++ struct phylink_config phylink_config;
++ struct phylink_pcs *pcs;
++ struct device_node *np;
++ struct ppe_device *ppe_dev;
++ phy_interface_t interface;
++ enum ppe_mac_type mac_type;
++ int port_id;
++ struct clk *clks[PPE_PORT_CLK_RST_MAX];
++ struct reset_control *rstcs[PPE_PORT_CLK_RST_MAX];
++};
++
++/**
++ * struct ppe_ports - Array of PPE ports
++ * @num: Number of PPE ports
++ * @port: Each PPE port private data
++ */
++struct ppe_ports {
++ unsigned int num;
++ struct ppe_port port[] __counted_by(num);
++};
++
++int ppe_port_mac_init(struct ppe_device *ppe_dev);
++void ppe_port_mac_deinit(struct ppe_device *ppe_dev);
++int ppe_port_phylink_setup(struct ppe_port *ppe_port,
++ struct net_device *netdev);
++void ppe_port_phylink_destroy(struct ppe_port *ppe_port);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -9,6 +9,17 @@
+
+ #include <linux/bitfield.h>
+
++/* PPE port mux select control register */
++#define PPE_PORT_MUX_CTRL_ADDR 0x10
++#define PPE_PORT6_SEL_XGMAC BIT(13)
++#define PPE_PORT5_SEL_XGMAC BIT(12)
++#define PPE_PORT4_SEL_XGMAC BIT(11)
++#define PPE_PORT3_SEL_XGMAC BIT(10)
++#define PPE_PORT2_SEL_XGMAC BIT(9)
++#define PPE_PORT1_SEL_XGMAC BIT(8)
++#define PPE_PORT5_SEL_PCS1 BIT(4)
++#define PPE_PORT_SEL_XGMAC(x) (BIT(8) << ((x) - 1))
++
+ /* PPE scheduler configurations for buffer manager block. */
+ #define PPE_BM_SCH_CTRL_ADDR 0xb000
+ #define PPE_BM_SCH_CTRL_INC 4
+@@ -556,4 +567,117 @@
+ #define PPE_ENQ_OPR_TBL_ENTRIES 300
+ #define PPE_ENQ_OPR_TBL_INC 0x10
+ #define PPE_ENQ_OPR_TBL_ENQ_DISABLE BIT(0)
++
++/* PPE GMAC and XGMAC register base address */
++#define PPE_PORT_GMAC_ADDR(x) (0x001000 + ((x) - 1) * 0x200)
++#define PPE_PORT_XGMAC_ADDR(x) (0x500000 + ((x) - 1) * 0x4000)
++
++/* GMAC enable register */
++#define GMAC_ENABLE_ADDR 0x0
++#define GMAC_TXFCEN BIT(6)
++#define GMAC_RXFCEN BIT(5)
++#define GMAC_DUPLEX_FULL BIT(4)
++#define GMAC_TXEN BIT(1)
++#define GMAC_RXEN BIT(0)
++
++#define GMAC_TRXEN \
++ (GMAC_TXEN | GMAC_RXEN)
++#define GMAC_ENABLE_ALL \
++ (GMAC_TXFCEN | GMAC_RXFCEN | GMAC_DUPLEX_FULL | GMAC_TXEN | GMAC_RXEN)
++
++/* GMAC speed register */
++#define GMAC_SPEED_ADDR 0x4
++#define GMAC_SPEED_M GENMASK(1, 0)
++#define GMAC_SPEED_10 0
++#define GMAC_SPEED_100 1
++#define GMAC_SPEED_1000 2
++
++/* GMAC control register */
++#define GMAC_CTRL_ADDR 0x18
++#define GMAC_TX_THD_M GENMASK(27, 24)
++#define GMAC_MAXFRAME_SIZE_M GENMASK(21, 8)
++#define GMAC_CRS_SEL BIT(6)
++
++#define GMAC_CTRL_MASK \
++ (GMAC_TX_THD_M | GMAC_MAXFRAME_SIZE_M | GMAC_CRS_SEL)
++
++/* GMAC debug control register */
++#define GMAC_DBG_CTRL_ADDR 0x1c
++#define GMAC_HIGH_IPG_M GENMASK(15, 8)
++
++/* GMAC jumbo size register */
++#define GMAC_JUMBO_SIZE_ADDR 0x30
++#define GMAC_JUMBO_SIZE_M GENMASK(13, 0)
++
++/* GMAC MIB control register */
++#define GMAC_MIB_CTRL_ADDR 0x34
++#define GMAC_MIB_RD_CLR BIT(2)
++#define GMAC_MIB_RST BIT(1)
++#define GMAC_MIB_EN BIT(0)
++
++#define GMAC_MIB_CTRL_MASK \
++ (GMAC_MIB_RD_CLR | GMAC_MIB_RST | GMAC_MIB_EN)
++
++/* XGMAC TX configuration register */
++#define XGMAC_TX_CONFIG_ADDR 0x0
++#define XGMAC_SPEED_M GENMASK(31, 29)
++#define XGMAC_SPEED_10000_USXGMII FIELD_PREP(XGMAC_SPEED_M, 4)
++#define XGMAC_SPEED_10000 FIELD_PREP(XGMAC_SPEED_M, 0)
++#define XGMAC_SPEED_5000 FIELD_PREP(XGMAC_SPEED_M, 5)
++#define XGMAC_SPEED_2500_USXGMII FIELD_PREP(XGMAC_SPEED_M, 6)
++#define XGMAC_SPEED_2500 FIELD_PREP(XGMAC_SPEED_M, 2)
++#define XGMAC_SPEED_1000 FIELD_PREP(XGMAC_SPEED_M, 3)
++#define XGMAC_SPEED_100 XGMAC_SPEED_1000
++#define XGMAC_SPEED_10 XGMAC_SPEED_1000
++#define XGMAC_JD BIT(16)
++#define XGMAC_TXEN BIT(0)
++
++/* XGMAC RX configuration register */
++#define XGMAC_RX_CONFIG_ADDR 0x4
++#define XGMAC_GPSL_M GENMASK(29, 16)
++#define XGMAC_WD BIT(7)
++#define XGMAC_GPSLEN BIT(6)
++#define XGMAC_CST BIT(2)
++#define XGMAC_ACS BIT(1)
++#define XGMAC_RXEN BIT(0)
++
++#define XGMAC_RX_CONFIG_MASK \
++ (XGMAC_GPSL_M | XGMAC_WD | XGMAC_GPSLEN | XGMAC_CST | \
++ XGMAC_ACS | XGMAC_RXEN)
++
++/* XGMAC packet filter register */
++#define XGMAC_PKT_FILTER_ADDR 0x8
++#define XGMAC_RA BIT(31)
++#define XGMAC_PCF_M GENMASK(7, 6)
++#define XGMAC_PR BIT(0)
++
++#define XGMAC_PKT_FILTER_MASK \
++ (XGMAC_RA | XGMAC_PCF_M | XGMAC_PR)
++#define XGMAC_PKT_FILTER_VAL \
++ (XGMAC_RA | XGMAC_PR | FIELD_PREP(XGMAC_PCF_M, 0x2))
++
++/* XGMAC watchdog timeout register */
++#define XGMAC_WD_TIMEOUT_ADDR 0xc
++#define XGMAC_PWE BIT(8)
++#define XGMAC_WTO_M GENMASK(3, 0)
++
++#define XGMAC_WD_TIMEOUT_MASK \
++ (XGMAC_PWE | XGMAC_WTO_M)
++#define XGMAC_WD_TIMEOUT_VAL \
++ (XGMAC_PWE | FIELD_PREP(XGMAC_WTO_M, 0xb))
++
++/* XGMAC TX flow control register */
++#define XGMAC_TX_FLOW_CTRL_ADDR 0x70
++#define XGMAC_PAUSE_TIME_M GENMASK(31, 16)
++#define XGMAC_TXFCEN BIT(1)
++
++/* XGMAC RX flow control register */
++#define XGMAC_RX_FLOW_CTRL_ADDR 0x90
++#define XGMAC_RXFCEN BIT(0)
++
++/* XGMAC management counters control register */
++#define XGMAC_MMC_CTRL_ADDR 0x800
++#define XGMAC_MCF BIT(3)
++#define XGMAC_CNTRST BIT(0)
++
+ #endif
--- /dev/null
+From dbcc0d01241a1353d8e11e764cf7fcd390ae3f1f Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Thu, 29 Feb 2024 20:16:14 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE port MAC MIB statistics
+ functions
+
+Add PPE port MAC MIB statistics functions which are used by netdev
+ops and ethtool. For GMAC, a polling task is scheduled to read the
+MIB counters periodically to avoid 32bit register counter overflow.
+
+Change-Id: Ic20e240061278f77d703f652e1f7d959db8fac37
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 465 +++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 13 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 91 ++++
+ 3 files changed, 569 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -23,6 +23,122 @@
+ /* PPE BM port start for PPE MAC ports */
+ #define PPE_BM_PORT_MAC_START 7
+
++/* Poll interval time to poll GMAC MIBs for overflow protection,
++ * the time should ensure that the 32bit GMAC packet counter
++ * register would not overflow within this time at line rate
++ * speed for 64B packet size.
++ */
++#define PPE_GMIB_POLL_INTERVAL_MS 120000
++
++#define PPE_MAC_MIB_DESC(_s, _o, _n) \
++ { \
++ .size = (_s), \
++ .offset = (_o), \
++ .name = (_n), \
++ }
++
++/* PPE MAC MIB description */
++struct ppe_mac_mib_info {
++ u32 size;
++ u32 offset;
++ const char *name;
++};
++
++/* PPE GMAC MIB statistics type */
++enum ppe_gmib_stats_type {
++ gmib_rx_broadcast,
++ gmib_rx_pause,
++ gmib_rx_multicast,
++ gmib_rx_fcserr,
++ gmib_rx_alignerr,
++ gmib_rx_runt,
++ gmib_rx_frag,
++ gmib_rx_jumbofcserr,
++ gmib_rx_jumboalignerr,
++ gmib_rx_pkt64,
++ gmib_rx_pkt65to127,
++ gmib_rx_pkt128to255,
++ gmib_rx_pkt256to511,
++ gmib_rx_pkt512to1023,
++ gmib_rx_pkt1024to1518,
++ gmib_rx_pkt1519tomax,
++ gmib_rx_toolong,
++ gmib_rx_bytes_g,
++ gmib_rx_bytes_b,
++ gmib_rx_unicast,
++ gmib_tx_broadcast,
++ gmib_tx_pause,
++ gmib_tx_multicast,
++ gmib_tx_underrun,
++ gmib_tx_pkt64,
++ gmib_tx_pkt65to127,
++ gmib_tx_pkt128to255,
++ gmib_tx_pkt256to511,
++ gmib_tx_pkt512to1023,
++ gmib_tx_pkt1024to1518,
++ gmib_tx_pkt1519tomax,
++ gmib_tx_bytes,
++ gmib_tx_collisions,
++ gmib_tx_abortcol,
++ gmib_tx_multicol,
++ gmib_tx_singlecol,
++ gmib_tx_excdeffer,
++ gmib_tx_deffer,
++ gmib_tx_latecol,
++ gmib_tx_unicast,
++};
++
++/* PPE XGMAC MIB statistics type */
++enum ppe_xgmib_stats_type {
++ xgmib_tx_bytes,
++ xgmib_tx_frames,
++ xgmib_tx_broadcast_g,
++ xgmib_tx_multicast_g,
++ xgmib_tx_pkt64,
++ xgmib_tx_pkt65to127,
++ xgmib_tx_pkt128to255,
++ xgmib_tx_pkt256to511,
++ xgmib_tx_pkt512to1023,
++ xgmib_tx_pkt1024tomax,
++ xgmib_tx_unicast,
++ xgmib_tx_multicast,
++ xgmib_tx_broadcast,
++ xgmib_tx_underflow_err,
++ xgmib_tx_bytes_g,
++ xgmib_tx_frames_g,
++ xgmib_tx_pause,
++ xgmib_tx_vlan_g,
++ xgmib_tx_lpi_usec,
++ xgmib_tx_lpi_tran,
++ xgmib_rx_frames,
++ xgmib_rx_bytes,
++ xgmib_rx_bytes_g,
++ xgmib_rx_broadcast_g,
++ xgmib_rx_multicast_g,
++ xgmib_rx_crc_err,
++ xgmib_rx_runt_err,
++ xgmib_rx_jabber_err,
++ xgmib_rx_undersize_g,
++ xgmib_rx_oversize_g,
++ xgmib_rx_pkt64,
++ xgmib_rx_pkt65to127,
++ xgmib_rx_pkt128to255,
++ xgmib_rx_pkt256to511,
++ xgmib_rx_pkt512to1023,
++ xgmib_rx_pkt1024tomax,
++ xgmib_rx_unicast_g,
++ xgmib_rx_len_err,
++ xgmib_rx_outofrange_err,
++ xgmib_rx_pause,
++ xgmib_rx_fifo_overflow,
++ xgmib_rx_vlan,
++ xgmib_rx_wdog_err,
++ xgmib_rx_lpi_usec,
++ xgmib_rx_lpi_tran,
++ xgmib_rx_drop_frames,
++ xgmib_rx_drop_bytes,
++};
++
+ /* PPE port clock and reset name */
+ static const char * const ppe_port_clk_rst_name[] = {
+ [PPE_PORT_CLK_RST_MAC] = "port_mac",
+@@ -30,6 +146,322 @@ static const char * const ppe_port_clk_r
+ [PPE_PORT_CLK_RST_TX] = "port_tx",
+ };
+
++/* PPE GMAC MIB statistics description information */
++static const struct ppe_mac_mib_info gmib_info[] = {
++ PPE_MAC_MIB_DESC(4, GMAC_RXBROAD_ADDR, "rx_broadcast"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPAUSE_ADDR, "rx_pause"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXMULTI_ADDR, "rx_multicast"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXFCSERR_ADDR, "rx_fcserr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXALIGNERR_ADDR, "rx_alignerr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXRUNT_ADDR, "rx_runt"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXFRAG_ADDR, "rx_frag"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXJUMBOFCSERR_ADDR, "rx_jumbofcserr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXJUMBOALIGNERR_ADDR, "rx_jumboalignerr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT64_ADDR, "rx_pkt64"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT65TO127_ADDR, "rx_pkt65to127"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT128TO255_ADDR, "rx_pkt128to255"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT256TO511_ADDR, "rx_pkt256to511"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT512TO1023_ADDR, "rx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT1024TO1518_ADDR, "rx_pkt1024to1518"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT1519TOX_ADDR, "rx_pkt1519tomax"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXTOOLONG_ADDR, "rx_toolong"),
++ PPE_MAC_MIB_DESC(8, GMAC_RXBYTE_G_ADDR, "rx_bytes_g"),
++ PPE_MAC_MIB_DESC(8, GMAC_RXBYTE_B_ADDR, "rx_bytes_b"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXUNI_ADDR, "rx_unicast"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXBROAD_ADDR, "tx_broadcast"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPAUSE_ADDR, "tx_pause"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXMULTI_ADDR, "tx_multicast"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXUNDERRUN_ADDR, "tx_underrun"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT64_ADDR, "tx_pkt64"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT65TO127_ADDR, "tx_pkt65to127"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT128TO255_ADDR, "tx_pkt128to255"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT256TO511_ADDR, "tx_pkt256to511"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT512TO1023_ADDR, "tx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT1024TO1518_ADDR, "tx_pkt1024to1518"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT1519TOX_ADDR, "tx_pkt1519tomax"),
++ PPE_MAC_MIB_DESC(8, GMAC_TXBYTE_ADDR, "tx_bytes"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXCOLLISIONS_ADDR, "tx_collisions"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXABORTCOL_ADDR, "tx_abortcol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXMULTICOL_ADDR, "tx_multicol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXSINGLECOL_ADDR, "tx_singlecol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXEXCESSIVEDEFER_ADDR, "tx_excdeffer"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXDEFER_ADDR, "tx_deffer"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXLATECOL_ADDR, "tx_latecol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXUNI_ADDR, "tx_unicast"),
++};
++
++/* PPE XGMAC MIB statistics description information */
++static const struct ppe_mac_mib_info xgmib_info[] = {
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBYTE_GB_ADDR, "tx_bytes"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT_GB_ADDR, "tx_frames"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBROAD_G_ADDR, "tx_broadcast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXMULTI_G_ADDR, "tx_multicast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT64_GB_ADDR, "tx_pkt64"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT65TO127_GB_ADDR, "tx_pkt65to127"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT128TO255_GB_ADDR, "tx_pkt128to255"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT256TO511_GB_ADDR, "tx_pkt256to511"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT512TO1023_GB_ADDR, "tx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT1024TOMAX_GB_ADDR, "tx_pkt1024tomax"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXUNI_GB_ADDR, "tx_unicast"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXMULTI_GB_ADDR, "tx_multicast"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBROAD_GB_ADDR, "tx_broadcast"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXUNDERFLOW_ERR_ADDR, "tx_underflow_err"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBYTE_G_ADDR, "tx_bytes_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT_G_ADDR, "tx_frames_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPAUSE_ADDR, "tx_pause"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXVLAN_G_ADDR, "tx_vlan_g"),
++ PPE_MAC_MIB_DESC(4, XGMAC_TXLPI_USEC_ADDR, "tx_lpi_usec"),
++ PPE_MAC_MIB_DESC(4, XGMAC_TXLPI_TRAN_ADDR, "tx_lpi_tran"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT_GB_ADDR, "rx_frames"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXBYTE_GB_ADDR, "rx_bytes"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXBYTE_G_ADDR, "rx_bytes_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXBROAD_G_ADDR, "rx_broadcast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXMULTI_G_ADDR, "rx_multicast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXCRC_ERR_ADDR, "rx_crc_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXRUNT_ERR_ADDR, "rx_runt_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXJABBER_ERR_ADDR, "rx_jabber_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXUNDERSIZE_G_ADDR, "rx_undersize_g"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXOVERSIZE_G_ADDR, "rx_oversize_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT64_GB_ADDR, "rx_pkt64"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT65TO127_GB_ADDR, "rx_pkt65to127"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT128TO255_GB_ADDR, "rx_pkt128to255"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT256TO511_GB_ADDR, "rx_pkt256to511"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT512TO1023_GB_ADDR, "rx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT1024TOMAX_GB_ADDR, "rx_pkt1024tomax"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXUNI_G_ADDR, "rx_unicast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXLEN_ERR_ADDR, "rx_len_err"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXOUTOFRANGE_ADDR, "rx_outofrange_err"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPAUSE_ADDR, "rx_pause"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXFIFOOVERFLOW_ADDR, "rx_fifo_overflow"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXVLAN_GB_ADDR, "rx_vlan"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXWATCHDOG_ERR_ADDR, "rx_wdog_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXLPI_USEC_ADDR, "rx_lpi_usec"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXLPI_TRAN_ADDR, "rx_lpi_tran"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXDISCARD_GB_ADDR, "rx_drop_frames"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXDISCARDBYTE_GB_ADDR, "rx_drop_bytes"),
++};
++
++/* Get GMAC MIBs from registers and accumulate to PPE port GMIB stats array */
++static void ppe_port_gmib_update(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ const struct ppe_mac_mib_info *mib;
++ int port = ppe_port->port_id;
++ u32 reg, val;
++ int i, ret;
++
++ for (i = 0; i < ARRAY_SIZE(gmib_info); i++) {
++ mib = &gmib_info[i];
++ reg = PPE_PORT_GMAC_ADDR(port) + mib->offset;
++
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n", __func__, ret);
++ continue;
++ }
++
++ ppe_port->gmib_stats[i] += val;
++ if (mib->size == 8) {
++ ret = regmap_read(ppe_dev->regmap, reg + 4, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n",
++ __func__, ret);
++ continue;
++ }
++
++ ppe_port->gmib_stats[i] += (u64)val << 32;
++ }
++ }
++}
++
++/* Polling task to read GMIB statistics to avoid GMIB 32bit register overflow */
++static void ppe_port_gmib_stats_poll(struct work_struct *work)
++{
++ struct ppe_port *ppe_port = container_of(work, struct ppe_port,
++ gmib_read.work);
++ spin_lock(&ppe_port->gmib_stats_lock);
++ ppe_port_gmib_update(ppe_port);
++ spin_unlock(&ppe_port->gmib_stats_lock);
++
++ schedule_delayed_work(&ppe_port->gmib_read,
++ msecs_to_jiffies(PPE_GMIB_POLL_INTERVAL_MS));
++}
++
++/* Get the XGMAC MIB counter based on the specific MIB stats type */
++static u64 ppe_port_xgmib_get(struct ppe_port *ppe_port,
++ enum ppe_xgmib_stats_type xgmib_type)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ const struct ppe_mac_mib_info *mib;
++ int port = ppe_port->port_id;
++ u32 reg, val;
++ u64 data = 0;
++ int ret;
++
++ mib = &xgmib_info[xgmib_type];
++ reg = PPE_PORT_XGMAC_ADDR(port) + mib->offset;
++
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n", __func__, ret);
++ goto data_return;
++ }
++
++ data = val;
++ if (mib->size == 8) {
++ ret = regmap_read(ppe_dev->regmap, reg + 4, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n", __func__, ret);
++ goto data_return;
++ }
++
++ data |= (u64)val << 32;
++ }
++
++data_return:
++ return data;
++}
++
++/**
++ * ppe_port_get_sset_count() - Get PPE port statistics string count
++ * @ppe_port: PPE port
++ * @sset: string set ID
++ *
++ * Description: Get the MAC statistics string count for the PPE port
++ * specified by @ppe_port.
++ *
++ * Return: The count of the statistics string.
++ */
++int ppe_port_get_sset_count(struct ppe_port *ppe_port, int sset)
++{
++ if (sset != ETH_SS_STATS)
++ return 0;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC)
++ return ARRAY_SIZE(gmib_info);
++ else
++ return ARRAY_SIZE(xgmib_info);
++}
++
++/**
++ * ppe_port_get_strings() - Get PPE port statistics strings
++ * @ppe_port: PPE port
++ * @stringset: string set ID
++ * @data: pointer to statistics strings
++ *
++ * Description: Get the MAC statistics stings for the PPE port
++ * specified by @ppe_port. The strings are stored in the buffer
++ * indicated by @data which used in the ethtool ops.
++ */
++void ppe_port_get_strings(struct ppe_port *ppe_port, u32 stringset, u8 *data)
++{
++ int i;
++
++ if (stringset != ETH_SS_STATS)
++ return;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ for (i = 0; i < ARRAY_SIZE(gmib_info); i++)
++ strscpy(data + i * ETH_GSTRING_LEN, gmib_info[i].name,
++ ETH_GSTRING_LEN);
++ } else {
++ for (i = 0; i < ARRAY_SIZE(xgmib_info); i++)
++ strscpy(data + i * ETH_GSTRING_LEN, xgmib_info[i].name,
++ ETH_GSTRING_LEN);
++ }
++}
++
++/**
++ * ppe_port_get_ethtool_stats() - Get PPE port ethtool statistics
++ * @ppe_port: PPE port
++ * @data: pointer to statistics data
++ *
++ * Description: Get the MAC statistics for the PPE port specified
++ * by @ppe_port. The statistics are stored in the buffer indicated
++ * by @data which used in the ethtool ops.
++ */
++void ppe_port_get_ethtool_stats(struct ppe_port *ppe_port, u64 *data)
++{
++ int i;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ spin_lock(&ppe_port->gmib_stats_lock);
++
++ ppe_port_gmib_update(ppe_port);
++ for (i = 0; i < ARRAY_SIZE(gmib_info); i++)
++ data[i] = ppe_port->gmib_stats[i];
++
++ spin_unlock(&ppe_port->gmib_stats_lock);
++ } else {
++ for (i = 0; i < ARRAY_SIZE(xgmib_info); i++)
++ data[i] = ppe_port_xgmib_get(ppe_port, i);
++ }
++}
++
++/**
++ * ppe_port_get_stats64() - Get PPE port statistics
++ * @ppe_port: PPE port
++ * @s: statistics pointer
++ *
++ * Description: Get the MAC statistics for the PPE port specified
++ * by @ppe_port.
++ */
++void ppe_port_get_stats64(struct ppe_port *ppe_port,
++ struct rtnl_link_stats64 *s)
++{
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ u64 *src = ppe_port->gmib_stats;
++
++ spin_lock(&ppe_port->gmib_stats_lock);
++
++ ppe_port_gmib_update(ppe_port);
++
++ s->rx_packets = src[gmib_rx_unicast] +
++ src[gmib_rx_broadcast] + src[gmib_rx_multicast];
++
++ s->tx_packets = src[gmib_tx_unicast] +
++ src[gmib_tx_broadcast] + src[gmib_tx_multicast];
++
++ s->rx_bytes = src[gmib_rx_bytes_g];
++ s->tx_bytes = src[gmib_tx_bytes];
++ s->multicast = src[gmib_rx_multicast];
++
++ s->rx_crc_errors = src[gmib_rx_fcserr] + src[gmib_rx_frag];
++ s->rx_frame_errors = src[gmib_rx_alignerr];
++ s->rx_errors = s->rx_crc_errors + s->rx_frame_errors;
++ s->rx_dropped = src[gmib_rx_toolong] + s->rx_errors;
++
++ s->tx_fifo_errors = src[gmib_tx_underrun];
++ s->tx_aborted_errors = src[gmib_tx_abortcol];
++ s->tx_errors = s->tx_fifo_errors + s->tx_aborted_errors;
++ s->collisions = src[gmib_tx_collisions];
++
++ spin_unlock(&ppe_port->gmib_stats_lock);
++ } else {
++ s->multicast = ppe_port_xgmib_get(ppe_port, xgmib_rx_multicast_g);
++
++ s->rx_packets = s->multicast;
++ s->rx_packets += ppe_port_xgmib_get(ppe_port, xgmib_rx_unicast_g);
++ s->rx_packets += ppe_port_xgmib_get(ppe_port, xgmib_rx_broadcast_g);
++
++ s->tx_packets = ppe_port_xgmib_get(ppe_port, xgmib_tx_frames);
++ s->rx_bytes = ppe_port_xgmib_get(ppe_port, xgmib_rx_bytes);
++ s->tx_bytes = ppe_port_xgmib_get(ppe_port, xgmib_tx_bytes);
++
++ s->rx_crc_errors = ppe_port_xgmib_get(ppe_port, xgmib_rx_crc_err);
++ s->rx_fifo_errors = ppe_port_xgmib_get(ppe_port, xgmib_rx_fifo_overflow);
++
++ s->rx_length_errors = ppe_port_xgmib_get(ppe_port, xgmib_rx_len_err);
++ s->rx_errors = s->rx_crc_errors +
++ s->rx_fifo_errors + s->rx_length_errors;
++ s->rx_dropped = s->rx_errors;
++
++ s->tx_fifo_errors = ppe_port_xgmib_get(ppe_port, xgmib_tx_underflow_err);
++ s->tx_errors = s->tx_packets -
++ ppe_port_xgmib_get(ppe_port, xgmib_tx_frames_g);
++ }
++}
++
+ /* PPE port and MAC reset */
+ static int ppe_port_mac_reset(struct ppe_port *ppe_port)
+ {
+@@ -261,6 +693,9 @@ static void ppe_port_mac_link_up(struct
+ int ret, port = ppe_port->port_id;
+ u32 reg, val;
+
++ /* Start GMIB statistics polling */
++ schedule_delayed_work(&ppe_port->gmib_read, 0);
++
+ if (mac_type == PPE_MAC_TYPE_GMAC)
+ ret = ppe_port_gmac_link_up(ppe_port,
+ speed, duplex, tx_pause, rx_pause);
+@@ -306,6 +741,9 @@ static void ppe_port_mac_link_down(struc
+ int ret, port = ppe_port->port_id;
+ u32 reg;
+
++ /* Stop GMIB statistics polling */
++ cancel_delayed_work_sync(&ppe_port->gmib_read);
++
+ /* Disable PPE port TX */
+ reg = PPE_PORT_BRIDGE_CTRL_ADDR + PPE_PORT_BRIDGE_CTRL_INC * port;
+ ret = regmap_update_bits(ppe_dev->regmap, reg,
+@@ -627,6 +1065,27 @@ static int ppe_port_mac_hw_init(struct p
+ return ret;
+ }
+
++/* PPE port MAC MIB work task initialization */
++static int ppe_port_mac_mib_work_init(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ u64 *gstats;
++
++ gstats = devm_kzalloc(ppe_dev->dev,
++ sizeof(*gstats) * ARRAY_SIZE(gmib_info),
++ GFP_KERNEL);
++ if (!gstats)
++ return -ENOMEM;
++
++ ppe_port->gmib_stats = gstats;
++
++ spin_lock_init(&ppe_port->gmib_stats_lock);
++ INIT_DELAYED_WORK(&ppe_port->gmib_read,
++ ppe_port_gmib_stats_poll);
++
++ return 0;
++}
++
+ /**
+ * ppe_port_mac_init() - Initialization of PPE ports for the PPE device
+ * @ppe_dev: PPE device
+@@ -693,6 +1152,12 @@ int ppe_port_mac_init(struct ppe_device
+ goto err_port_node;
+ }
+
++ ret = ppe_port_mac_mib_work_init(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to initialize MAC MIB work\n");
++ goto err_port_node;
++ }
++
+ i++;
+ }
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -8,6 +8,8 @@
+
+ #include <linux/phylink.h>
+
++struct rtnl_link_stats64;
++
+ /**
+ * enum ppe_port_clk_rst_type - PPE port clock and reset ID type
+ * @PPE_PORT_CLK_RST_MAC: The clock and reset ID for port MAC
+@@ -44,6 +46,9 @@ enum ppe_mac_type {
+ * @port_id: Port ID
+ * @clks: Port clocks
+ * @rstcs: Port resets
++ * @gmib_read: Delay work task for GMAC MIB statistics polling function
++ * @gmib_stats: GMAC MIB statistics array
++ * @gmib_stats_lock: Lock to protect GMAC MIB statistics
+ */
+ struct ppe_port {
+ struct phylink *phylink;
+@@ -56,6 +61,9 @@ struct ppe_port {
+ int port_id;
+ struct clk *clks[PPE_PORT_CLK_RST_MAX];
+ struct reset_control *rstcs[PPE_PORT_CLK_RST_MAX];
++ struct delayed_work gmib_read;
++ u64 *gmib_stats;
++ spinlock_t gmib_stats_lock; /* Protects GMIB stats */
+ };
+
+ /**
+@@ -73,4 +81,9 @@ void ppe_port_mac_deinit(struct ppe_devi
+ int ppe_port_phylink_setup(struct ppe_port *ppe_port,
+ struct net_device *netdev);
+ void ppe_port_phylink_destroy(struct ppe_port *ppe_port);
++int ppe_port_get_sset_count(struct ppe_port *ppe_port, int sset);
++void ppe_port_get_strings(struct ppe_port *ppe_port, u32 stringset, u8 *data);
++void ppe_port_get_ethtool_stats(struct ppe_port *ppe_port, u64 *data);
++void ppe_port_get_stats64(struct ppe_port *ppe_port,
++ struct rtnl_link_stats64 *s);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -618,6 +618,48 @@
+ #define GMAC_MIB_CTRL_MASK \
+ (GMAC_MIB_RD_CLR | GMAC_MIB_RST | GMAC_MIB_EN)
+
++/* GMAC MIB counter registers */
++#define GMAC_RXBROAD_ADDR 0x40
++#define GMAC_RXPAUSE_ADDR 0x44
++#define GMAC_RXMULTI_ADDR 0x48
++#define GMAC_RXFCSERR_ADDR 0x4C
++#define GMAC_RXALIGNERR_ADDR 0x50
++#define GMAC_RXRUNT_ADDR 0x54
++#define GMAC_RXFRAG_ADDR 0x58
++#define GMAC_RXJUMBOFCSERR_ADDR 0x5C
++#define GMAC_RXJUMBOALIGNERR_ADDR 0x60
++#define GMAC_RXPKT64_ADDR 0x64
++#define GMAC_RXPKT65TO127_ADDR 0x68
++#define GMAC_RXPKT128TO255_ADDR 0x6C
++#define GMAC_RXPKT256TO511_ADDR 0x70
++#define GMAC_RXPKT512TO1023_ADDR 0x74
++#define GMAC_RXPKT1024TO1518_ADDR 0x78
++#define GMAC_RXPKT1519TOX_ADDR 0x7C
++#define GMAC_RXTOOLONG_ADDR 0x80
++#define GMAC_RXBYTE_G_ADDR 0x84
++#define GMAC_RXBYTE_B_ADDR 0x8C
++#define GMAC_RXUNI_ADDR 0x94
++#define GMAC_TXBROAD_ADDR 0xA0
++#define GMAC_TXPAUSE_ADDR 0xA4
++#define GMAC_TXMULTI_ADDR 0xA8
++#define GMAC_TXUNDERRUN_ADDR 0xAC
++#define GMAC_TXPKT64_ADDR 0xB0
++#define GMAC_TXPKT65TO127_ADDR 0xB4
++#define GMAC_TXPKT128TO255_ADDR 0xB8
++#define GMAC_TXPKT256TO511_ADDR 0xBC
++#define GMAC_TXPKT512TO1023_ADDR 0xC0
++#define GMAC_TXPKT1024TO1518_ADDR 0xC4
++#define GMAC_TXPKT1519TOX_ADDR 0xC8
++#define GMAC_TXBYTE_ADDR 0xCC
++#define GMAC_TXCOLLISIONS_ADDR 0xD4
++#define GMAC_TXABORTCOL_ADDR 0xD8
++#define GMAC_TXMULTICOL_ADDR 0xDC
++#define GMAC_TXSINGLECOL_ADDR 0xE0
++#define GMAC_TXEXCESSIVEDEFER_ADDR 0xE4
++#define GMAC_TXDEFER_ADDR 0xE8
++#define GMAC_TXLATECOL_ADDR 0xEC
++#define GMAC_TXUNI_ADDR 0xF0
++
+ /* XGMAC TX configuration register */
+ #define XGMAC_TX_CONFIG_ADDR 0x0
+ #define XGMAC_SPEED_M GENMASK(31, 29)
+@@ -680,4 +722,53 @@
+ #define XGMAC_MCF BIT(3)
+ #define XGMAC_CNTRST BIT(0)
+
++/* XGMAC MIB counter registers */
++#define XGMAC_TXBYTE_GB_ADDR 0x814
++#define XGMAC_TXPKT_GB_ADDR 0x81C
++#define XGMAC_TXBROAD_G_ADDR 0x824
++#define XGMAC_TXMULTI_G_ADDR 0x82C
++#define XGMAC_TXPKT64_GB_ADDR 0x834
++#define XGMAC_TXPKT65TO127_GB_ADDR 0x83C
++#define XGMAC_TXPKT128TO255_GB_ADDR 0x844
++#define XGMAC_TXPKT256TO511_GB_ADDR 0x84C
++#define XGMAC_TXPKT512TO1023_GB_ADDR 0x854
++#define XGMAC_TXPKT1024TOMAX_GB_ADDR 0x85C
++#define XGMAC_TXUNI_GB_ADDR 0x864
++#define XGMAC_TXMULTI_GB_ADDR 0x86C
++#define XGMAC_TXBROAD_GB_ADDR 0x874
++#define XGMAC_TXUNDERFLOW_ERR_ADDR 0x87C
++#define XGMAC_TXBYTE_G_ADDR 0x884
++#define XGMAC_TXPKT_G_ADDR 0x88C
++#define XGMAC_TXPAUSE_ADDR 0x894
++#define XGMAC_TXVLAN_G_ADDR 0x89C
++#define XGMAC_TXLPI_USEC_ADDR 0x8A4
++#define XGMAC_TXLPI_TRAN_ADDR 0x8A8
++#define XGMAC_RXPKT_GB_ADDR 0x900
++#define XGMAC_RXBYTE_GB_ADDR 0x908
++#define XGMAC_RXBYTE_G_ADDR 0x910
++#define XGMAC_RXBROAD_G_ADDR 0x918
++#define XGMAC_RXMULTI_G_ADDR 0x920
++#define XGMAC_RXCRC_ERR_ADDR 0x928
++#define XGMAC_RXRUNT_ERR_ADDR 0x930
++#define XGMAC_RXJABBER_ERR_ADDR 0x934
++#define XGMAC_RXUNDERSIZE_G_ADDR 0x938
++#define XGMAC_RXOVERSIZE_G_ADDR 0x93C
++#define XGMAC_RXPKT64_GB_ADDR 0x940
++#define XGMAC_RXPKT65TO127_GB_ADDR 0x948
++#define XGMAC_RXPKT128TO255_GB_ADDR 0x950
++#define XGMAC_RXPKT256TO511_GB_ADDR 0x958
++#define XGMAC_RXPKT512TO1023_GB_ADDR 0x960
++#define XGMAC_RXPKT1024TOMAX_GB_ADDR 0x968
++#define XGMAC_RXUNI_G_ADDR 0x970
++#define XGMAC_RXLEN_ERR_ADDR 0x978
++#define XGMAC_RXOUTOFRANGE_ADDR 0x980
++#define XGMAC_RXPAUSE_ADDR 0x988
++#define XGMAC_RXFIFOOVERFLOW_ADDR 0x990
++#define XGMAC_RXVLAN_GB_ADDR 0x998
++#define XGMAC_RXWATCHDOG_ERR_ADDR 0x9A0
++#define XGMAC_RXLPI_USEC_ADDR 0x9A4
++#define XGMAC_RXLPI_TRAN_ADDR 0x9A8
++#define XGMAC_RXDISCARD_GB_ADDR 0x9AC
++#define XGMAC_RXDISCARDBYTE_GB_ADDR 0x9B4
++
+ #endif
--- /dev/null
+From 55fbbc8ef90df27a16bca1613a793a578b79a384 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 1 Mar 2024 13:36:26 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE port MAC address and EEE
+ functions
+
+Add PPE port MAC address set and EEE set API functions which
+will be used by netdev ops and ethtool.
+
+Change-Id: Id2b3b06ae940b3b6f5227d927316329cdf3caeaa
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Alex G: use struct ethtool_keee instead of ethtool_eee
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 75 ++++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 3 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 29 ++++++++
+ 3 files changed, 107 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -462,6 +462,81 @@ void ppe_port_get_stats64(struct ppe_por
+ }
+ }
+
++/**
++ * ppe_port_set_mac_address() - Set PPE port MAC address
++ * @ppe_port: PPE port
++ * @addr: MAC address
++ *
++ * Description: Set MAC address for the given PPE port.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_set_mac_address(struct ppe_port *ppe_port, const u8 *addr)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int port = ppe_port->port_id;
++ u32 reg, val;
++ int ret;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ reg = PPE_PORT_GMAC_ADDR(port);
++ val = (addr[5] << 8) | addr[4];
++ ret = regmap_write(ppe_dev->regmap, reg + GMAC_GOL_ADDR0_ADDR, val);
++ if (ret)
++ return ret;
++
++ val = (addr[0] << 24) | (addr[1] << 16) |
++ (addr[2] << 8) | addr[3];
++ ret = regmap_write(ppe_dev->regmap, reg + GMAC_GOL_ADDR1_ADDR, val);
++ if (ret)
++ return ret;
++ } else {
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ val = (addr[5] << 8) | addr[4] | XGMAC_ADDR_EN;
++ ret = regmap_write(ppe_dev->regmap, reg + XGMAC_ADDR0_H_ADDR, val);
++ if (ret)
++ return ret;
++
++ val = (addr[3] << 24) | (addr[2] << 16) |
++ (addr[1] << 8) | addr[0];
++ ret = regmap_write(ppe_dev->regmap, reg + XGMAC_ADDR0_L_ADDR, val);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++/**
++ * ppe_port_set_mac_eee() - Set EEE configuration for PPE port MAC
++ * @ppe_port: PPE port
++ * @eee: EEE settings
++ *
++ * Description: Set port MAC EEE settings for the given PPE port.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_set_mac_eee(struct ppe_port *ppe_port, struct ethtool_keee *eee)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int port = ppe_port->port_id;
++ u32 val;
++ int ret;
++
++ ret = regmap_read(ppe_dev->regmap, PPE_LPI_EN_ADDR, &val);
++ if (ret)
++ return ret;
++
++ if (eee->tx_lpi_enabled)
++ val |= PPE_LPI_PORT_EN(port);
++ else
++ val &= ~PPE_LPI_PORT_EN(port);
++
++ ret = regmap_write(ppe_dev->regmap, PPE_LPI_EN_ADDR, val);
++
++ return ret;
++}
++
+ /* PPE port and MAC reset */
+ static int ppe_port_mac_reset(struct ppe_port *ppe_port)
+ {
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -8,6 +8,7 @@
+
+ #include <linux/phylink.h>
+
++struct ethtool_keee;
+ struct rtnl_link_stats64;
+
+ /**
+@@ -86,4 +87,6 @@ void ppe_port_get_strings(struct ppe_por
+ void ppe_port_get_ethtool_stats(struct ppe_port *ppe_port, u64 *data);
+ void ppe_port_get_stats64(struct ppe_port *ppe_port,
+ struct rtnl_link_stats64 *s);
++int ppe_port_set_mac_address(struct ppe_port *ppe_port, const u8 *addr);
++int ppe_port_set_mac_eee(struct ppe_port *ppe_port, struct ethtool_keee *eee);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -20,6 +20,16 @@
+ #define PPE_PORT5_SEL_PCS1 BIT(4)
+ #define PPE_PORT_SEL_XGMAC(x) (BIT(8) << ((x) - 1))
+
++/* PPE port LPI enable register */
++#define PPE_LPI_EN_ADDR 0x400
++#define PPE_LPI_PORT1_EN BIT(0)
++#define PPE_LPI_PORT2_EN BIT(1)
++#define PPE_LPI_PORT3_EN BIT(2)
++#define PPE_LPI_PORT4_EN BIT(3)
++#define PPE_LPI_PORT5_EN BIT(4)
++#define PPE_LPI_PORT6_EN BIT(5)
++#define PPE_LPI_PORT_EN(x) (BIT(0) << ((x) - 1))
++
+ /* PPE scheduler configurations for buffer manager block. */
+ #define PPE_BM_SCH_CTRL_ADDR 0xb000
+ #define PPE_BM_SCH_CTRL_INC 4
+@@ -592,6 +602,17 @@
+ #define GMAC_SPEED_100 1
+ #define GMAC_SPEED_1000 2
+
++/* GMAC MAC address register */
++#define GMAC_GOL_ADDR0_ADDR 0x8
++#define GMAC_ADDR_BYTE5 GENMASK(15, 8)
++#define GMAC_ADDR_BYTE4 GENMASK(7, 0)
++
++#define GMAC_GOL_ADDR1_ADDR 0xC
++#define GMAC_ADDR_BYTE0 GENMASK(31, 24)
++#define GMAC_ADDR_BYTE1 GENMASK(23, 16)
++#define GMAC_ADDR_BYTE2 GENMASK(15, 8)
++#define GMAC_ADDR_BYTE3 GENMASK(7, 0)
++
+ /* GMAC control register */
+ #define GMAC_CTRL_ADDR 0x18
+ #define GMAC_TX_THD_M GENMASK(27, 24)
+@@ -717,6 +738,14 @@
+ #define XGMAC_RX_FLOW_CTRL_ADDR 0x90
+ #define XGMAC_RXFCEN BIT(0)
+
++/* XGMAC MAC address register */
++#define XGMAC_ADDR0_H_ADDR 0x300
++#define XGMAC_ADDR_EN BIT(31)
++#define XGMAC_ADDRH GENMASK(15, 0)
++
++#define XGMAC_ADDR0_L_ADDR 0x304
++#define XGMAC_ADDRL GENMASK(31, 0)
++
+ /* XGMAC management counters control register */
+ #define XGMAC_MMC_CTRL_ADDR 0x800
+ #define XGMAC_MCF BIT(3)
--- /dev/null
+From 3981aeae5dd43dea94a0ec10f0b2977ebd102560 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Tue, 5 Mar 2024 16:42:56 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add API to configure PPE port max
+ frame size
+
+This function is called when the MTU of an ethernet port is
+configured. It limits the size of packet passed through the
+ethernet port.
+
+Change-Id: I2a4dcd04407156d73770d2becbb7cbc0d56b3754
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 44 ++++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 1 +
+ 2 files changed, 45 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -537,6 +537,50 @@ int ppe_port_set_mac_eee(struct ppe_port
+ return ret;
+ }
+
++/**
++ * ppe_port_set_maxframe() - Set port maximum frame size
++ * @ppe_port: PPE port structure
++ * @maxframe_size: Maximum frame size supported by PPE port
++ *
++ * Description: Set MTU of network interface specified by @ppe_port.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_set_maxframe(struct ppe_port *ppe_port, int maxframe_size)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ u32 reg, val, mru_mtu_val[3];
++ int port = ppe_port->port_id;
++ int ret;
++
++ /* The max frame size should be MTU added by ETH_HLEN in PPE. */
++ maxframe_size += ETH_HLEN;
++
++ /* MAC takes cover the FCS for the calculation of frame size. */
++ if (maxframe_size > PPE_PORT_MAC_MAX_FRAME_SIZE - ETH_FCS_LEN)
++ return -EINVAL;
++
++ reg = PPE_MC_MTU_CTRL_TBL_ADDR + PPE_MC_MTU_CTRL_TBL_INC * port;
++ val = FIELD_PREP(PPE_MC_MTU_CTRL_TBL_MTU, maxframe_size);
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_MC_MTU_CTRL_TBL_MTU,
++ val);
++ if (ret)
++ return ret;
++
++ reg = PPE_MRU_MTU_CTRL_TBL_ADDR + PPE_MRU_MTU_CTRL_TBL_INC * port;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++ if (ret)
++ return ret;
++
++ PPE_MRU_MTU_CTRL_SET_MRU(mru_mtu_val, maxframe_size);
++ PPE_MRU_MTU_CTRL_SET_MTU(mru_mtu_val, maxframe_size);
++
++ return regmap_bulk_write(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++}
++
+ /* PPE port and MAC reset */
+ static int ppe_port_mac_reset(struct ppe_port *ppe_port)
+ {
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -89,4 +89,5 @@ void ppe_port_get_stats64(struct ppe_por
+ struct rtnl_link_stats64 *s);
+ int ppe_port_set_mac_address(struct ppe_port *ppe_port, const u8 *addr);
+ int ppe_port_set_mac_eee(struct ppe_port *ppe_port, struct ethtool_keee *eee);
++int ppe_port_set_maxframe(struct ppe_port *ppe_port, int maxframe_size);
+ #endif
--- /dev/null
+From 00d4f3cb4f5d1e6924151a4551f06b6a82bf0146 Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Wed, 28 Feb 2024 11:25:15 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add EDMA support for QCOM IPQ9574
+ chipset.
+
+Add the infrastructure functions such as Makefile,
+EDMA hardware configuration, clock and IRQ initializations.
+
+Change-Id: I64f65e554e70e9095b0cf3636fec421569ae6895
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Co-developed-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Alex G: use "ppe_config.h" header instead of "ppe_api.h"
+ add missing definitions and functions from ppe_api:
+ - enum ppe_queue_class_type {}
+ - ppe_edma_queue_offset_config()
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 3 +
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 480 +++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 113 +++++
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 10 +-
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 253 ++++++++++
+ 5 files changed, 858 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -5,3 +5,6 @@
+
+ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
++
++#EDMA
++qcom-ppe-objs += edma.o
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -0,0 +1,480 @@
++// SPDX-License-Identifier: GPL-2.0-only
++ /* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++ /* Qualcomm Ethernet DMA driver setup, HW configuration, clocks and
++ * interrupt initializations.
++ */
++
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/netdevice.h>
++#include <linux/of_irq.h>
++#include <linux/platform_device.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++#include <linux/reset.h>
++
++#include "edma.h"
++#include "ppe_regs.h"
++
++#define EDMA_IRQ_NAME_SIZE 32
++
++/* Global EDMA context. */
++struct edma_context *edma_ctx;
++
++/* Priority to multi-queue mapping. */
++static u8 edma_pri_map[PPE_QUEUE_INTER_PRI_NUM] = {
++ 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7};
++
++enum edma_clk_id {
++ EDMA_CLK,
++ EDMA_CFG_CLK,
++ EDMA_CLK_MAX
++};
++
++static const char * const clock_name[EDMA_CLK_MAX] = {
++ [EDMA_CLK] = "edma",
++ [EDMA_CFG_CLK] = "edma-cfg",
++};
++
++/* Rx Fill ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_rxfill_ring_info = {
++ .max_rings = 8,
++ .ring_start = 4,
++ .num_rings = 4,
++};
++
++/* Rx ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_rx_ring_info = {
++ .max_rings = 24,
++ .ring_start = 20,
++ .num_rings = 4,
++};
++
++/* Tx ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_tx_ring_info = {
++ .max_rings = 32,
++ .ring_start = 8,
++ .num_rings = 24,
++};
++
++/* Tx complete ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_txcmpl_ring_info = {
++ .max_rings = 32,
++ .ring_start = 8,
++ .num_rings = 24,
++};
++
++/* HW info for IPQ9574. */
++static struct edma_hw_info ipq9574_hw_info = {
++ .rxfill = &ipq9574_rxfill_ring_info,
++ .rx = &ipq9574_rx_ring_info,
++ .tx = &ipq9574_tx_ring_info,
++ .txcmpl = &ipq9574_txcmpl_ring_info,
++ .max_ports = 6,
++ .napi_budget_rx = 128,
++ .napi_budget_tx = 512,
++};
++
++static int edma_clock_set_and_enable(struct device *dev,
++ const char *id, unsigned long rate)
++{
++ struct device_node *edma_np;
++ struct clk *clk = NULL;
++ int ret;
++
++ edma_np = of_get_child_by_name(dev->of_node, "edma");
++
++ clk = devm_get_clk_from_child(dev, edma_np, id);
++ if (IS_ERR(clk)) {
++ dev_err(dev, "clk %s get failed\n", id);
++ of_node_put(edma_np);
++ return PTR_ERR(clk);
++ }
++
++ ret = clk_set_rate(clk, rate);
++ if (ret) {
++ dev_err(dev, "set %lu rate for %s failed\n", rate, id);
++ of_node_put(edma_np);
++ return ret;
++ }
++
++ ret = clk_prepare_enable(clk);
++ if (ret) {
++ dev_err(dev, "clk %s enable failed\n", id);
++ of_node_put(edma_np);
++ return ret;
++ }
++
++ of_node_put(edma_np);
++
++ dev_dbg(dev, "set %lu rate for %s\n", rate, id);
++
++ return 0;
++}
++
++static int edma_clock_init(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ unsigned long ppe_rate;
++ int ret;
++
++ ppe_rate = ppe_dev->clk_rate;
++
++ ret = edma_clock_set_and_enable(dev, clock_name[EDMA_CLK],
++ ppe_rate);
++ if (ret)
++ return ret;
++
++ ret = edma_clock_set_and_enable(dev, clock_name[EDMA_CFG_CLK],
++ ppe_rate);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++/**
++ * edma_configure_ucast_prio_map_tbl - Configure unicast priority map table.
++ *
++ * Map int_priority values to priority class and initialize
++ * unicast priority map table for default profile_id.
++ */
++static int edma_configure_ucast_prio_map_tbl(void)
++{
++ u8 pri_class, int_pri;
++ int ret = 0;
++
++ /* Set the priority class value for every possible priority. */
++ for (int_pri = 0; int_pri < PPE_QUEUE_INTER_PRI_NUM; int_pri++) {
++ pri_class = edma_pri_map[int_pri];
++
++ /* Priority offset should be less than maximum supported
++ * queue priority.
++ */
++ if (pri_class > EDMA_PRI_MAX_PER_CORE - 1) {
++ pr_err("Configured incorrect priority offset: %d\n",
++ pri_class);
++ return -EINVAL;
++ }
++
++ ret = ppe_edma_queue_offset_config(edma_ctx->ppe_dev,
++ PPE_QUEUE_CLASS_PRIORITY, int_pri, pri_class);
++
++ if (ret) {
++ pr_err("Failed with error: %d to set queue priority class for int_pri: %d for profile_id: %d\n",
++ ret, int_pri, 0);
++ return ret;
++ }
++
++ pr_debug("profile_id: %d, int_priority: %d, pri_class: %d\n",
++ 0, int_pri, pri_class);
++ }
++
++ return ret;
++}
++
++static int edma_irq_init(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_ring_info *rx = hw_info->rx;
++ char edma_irq_name[EDMA_IRQ_NAME_SIZE];
++ struct device *dev = ppe_dev->dev;
++ struct platform_device *pdev;
++ struct device_node *edma_np;
++ u32 i;
++
++ pdev = to_platform_device(dev);
++ edma_np = of_get_child_by_name(dev->of_node, "edma");
++ edma_ctx->intr_info.intr_txcmpl = kzalloc((sizeof(*edma_ctx->intr_info.intr_txcmpl) *
++ txcmpl->num_rings), GFP_KERNEL);
++ if (!edma_ctx->intr_info.intr_txcmpl) {
++ of_node_put(edma_np);
++ return -ENOMEM;
++ }
++
++ /* Get TXCMPL rings IRQ numbers. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ snprintf(edma_irq_name, sizeof(edma_irq_name), "edma_txcmpl_%d",
++ txcmpl->ring_start + i);
++ edma_ctx->intr_info.intr_txcmpl[i] = of_irq_get_byname(edma_np, edma_irq_name);
++ if (edma_ctx->intr_info.intr_txcmpl[i] < 0) {
++ dev_err(dev, "%s: txcmpl_info.intr[%u] irq get failed\n",
++ edma_np->name, i);
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return edma_ctx->intr_info.intr_txcmpl[i];
++ }
++
++ dev_dbg(dev, "%s: intr_info.intr_txcmpl[%u] = %u\n",
++ edma_np->name, i, edma_ctx->intr_info.intr_txcmpl[i]);
++ }
++
++ edma_ctx->intr_info.intr_rx = kzalloc((sizeof(*edma_ctx->intr_info.intr_rx) *
++ rx->num_rings), GFP_KERNEL);
++ if (!edma_ctx->intr_info.intr_rx) {
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return -ENOMEM;
++ }
++
++ /* Get RXDESC rings IRQ numbers. */
++ for (i = 0; i < rx->num_rings; i++) {
++ snprintf(edma_irq_name, sizeof(edma_irq_name), "edma_rxdesc_%d",
++ rx->ring_start + i);
++ edma_ctx->intr_info.intr_rx[i] = of_irq_get_byname(edma_np, edma_irq_name);
++ if (edma_ctx->intr_info.intr_rx[i] < 0) {
++ dev_err(dev, "%s: rx_queue_map_info.intr[%u] irq get failed\n",
++ edma_np->name, i);
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return edma_ctx->intr_info.intr_rx[i];
++ }
++
++ dev_dbg(dev, "%s: intr_info.intr_rx[%u] = %u\n",
++ edma_np->name, i, edma_ctx->intr_info.intr_rx[i]);
++ }
++
++ /* Get misc IRQ number. */
++ edma_ctx->intr_info.intr_misc = of_irq_get_byname(edma_np, "edma_misc");
++ if (edma_ctx->intr_info.intr_misc < 0) {
++ dev_err(dev, "%s: misc_intr irq get failed\n", edma_np->name);
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return edma_ctx->intr_info.intr_misc;
++ }
++
++ of_node_put(edma_np);
++
++ dev_dbg(dev, "%s: misc IRQ:%u\n", edma_np->name,
++ edma_ctx->intr_info.intr_misc);
++
++ return 0;
++}
++
++static int edma_hw_reset(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ struct reset_control *edma_hw_rst;
++ struct device_node *edma_np;
++ const char *reset_string;
++ u32 count, i;
++ int ret;
++
++ /* Count and parse reset names from DTSI. */
++ edma_np = of_get_child_by_name(dev->of_node, "edma");
++ count = of_property_count_strings(edma_np, "reset-names");
++ if (count < 0) {
++ dev_err(dev, "EDMA reset entry not found\n");
++ of_node_put(edma_np);
++ return -EINVAL;
++ }
++
++ for (i = 0; i < count; i++) {
++ ret = of_property_read_string_index(edma_np, "reset-names",
++ i, &reset_string);
++ if (ret) {
++ dev_err(dev, "Error reading reset-names");
++ of_node_put(edma_np);
++ return -EINVAL;
++ }
++
++ edma_hw_rst = of_reset_control_get_exclusive(edma_np, reset_string);
++ if (IS_ERR(edma_hw_rst)) {
++ of_node_put(edma_np);
++ return PTR_ERR(edma_hw_rst);
++ }
++
++ /* 100ms delay is required by hardware to reset EDMA. */
++ reset_control_assert(edma_hw_rst);
++ fsleep(100);
++
++ reset_control_deassert(edma_hw_rst);
++ fsleep(100);
++
++ reset_control_put(edma_hw_rst);
++ dev_dbg(dev, "EDMA HW reset, i:%d reset_string:%s\n", i, reset_string);
++ }
++
++ of_node_put(edma_np);
++
++ return 0;
++}
++
++static int edma_hw_configure(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++ int ret;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MAS_CTRL_ADDR;
++ ret = regmap_read(regmap, reg, &data);
++ if (ret)
++ return ret;
++
++ pr_debug("EDMA ver %d hw init\n", data);
++
++ /* Setup private data structure. */
++ edma_ctx->intr_info.intr_mask_rx = EDMA_RXDESC_INT_MASK_PKT_INT;
++ edma_ctx->intr_info.intr_mask_txcmpl = EDMA_TX_INT_MASK_PKT_INT;
++
++ /* Reset EDMA. */
++ ret = edma_hw_reset();
++ if (ret) {
++ pr_err("Error in resetting the hardware. ret: %d\n", ret);
++ return ret;
++ }
++
++ /* Allocate memory for netdevices. */
++ edma_ctx->netdev_arr = kzalloc((sizeof(**edma_ctx->netdev_arr) *
++ hw_info->max_ports),
++ GFP_KERNEL);
++ if (!edma_ctx->netdev_arr)
++ return -ENOMEM;
++
++ /* Configure DMA request priority, DMA read burst length,
++ * and AXI write size.
++ */
++ data = FIELD_PREP(EDMA_DMAR_BURST_LEN_MASK, EDMA_BURST_LEN_ENABLE);
++ data |= FIELD_PREP(EDMA_DMAR_REQ_PRI_MASK, 0);
++ data |= FIELD_PREP(EDMA_DMAR_TXDATA_OUTSTANDING_NUM_MASK, 31);
++ data |= FIELD_PREP(EDMA_DMAR_TXDESC_OUTSTANDING_NUM_MASK, 7);
++ data |= FIELD_PREP(EDMA_DMAR_RXFILL_OUTSTANDING_NUM_MASK, 7);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_DMAR_CTRL_ADDR;
++ ret = regmap_write(regmap, reg, data);
++ if (ret)
++ return ret;
++
++ /* Configure Tx Timeout Threshold. */
++ data = EDMA_TX_TIMEOUT_THRESH_VAL;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_TIMEOUT_THRESH_ADDR;
++ ret = regmap_write(regmap, reg, data);
++ if (ret)
++ return ret;
++
++ /* Set Miscellaneous error mask. */
++ data = EDMA_MISC_AXI_RD_ERR_MASK |
++ EDMA_MISC_AXI_WR_ERR_MASK |
++ EDMA_MISC_RX_DESC_FIFO_FULL_MASK |
++ EDMA_MISC_RX_ERR_BUF_SIZE_MASK |
++ EDMA_MISC_TX_SRAM_FULL_MASK |
++ EDMA_MISC_TX_CMPL_BUF_FULL_MASK |
++ EDMA_MISC_DATA_LEN_ERR_MASK;
++ data |= EDMA_MISC_TX_TIMEOUT_MASK;
++ edma_ctx->intr_info.intr_mask_misc = data;
++
++ /* Global EDMA enable and padding enable. */
++ data = EDMA_PORT_PAD_EN | EDMA_PORT_EDMA_EN;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_PORT_CTRL_ADDR;
++ ret = regmap_write(regmap, reg, data);
++ if (ret)
++ return ret;
++
++ /* Initialize unicast priority map table. */
++ ret = (int)edma_configure_ucast_prio_map_tbl();
++ if (ret) {
++ pr_err("Failed to initialize unicast priority map table: %d\n",
++ ret);
++ kfree(edma_ctx->netdev_arr);
++ return ret;
++ }
++
++ return 0;
++}
++
++/**
++ * edma_destroy - EDMA Destroy.
++ * @ppe_dev: PPE device
++ *
++ * Free the memory allocated during setup.
++ */
++void edma_destroy(struct ppe_device *ppe_dev)
++{
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ kfree(edma_ctx->netdev_arr);
++}
++
++/**
++ * edma_setup - EDMA Setup.
++ * @ppe_dev: PPE device
++ *
++ * Configure Ethernet global ctx, clocks, hardware and interrupts.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int edma_setup(struct ppe_device *ppe_dev)
++{
++ struct device *dev = ppe_dev->dev;
++ int ret;
++
++ edma_ctx = devm_kzalloc(dev, sizeof(*edma_ctx), GFP_KERNEL);
++ if (!edma_ctx)
++ return -ENOMEM;
++
++ edma_ctx->hw_info = &ipq9574_hw_info;
++ edma_ctx->ppe_dev = ppe_dev;
++
++ /* Configure the EDMA common clocks. */
++ ret = edma_clock_init();
++ if (ret) {
++ dev_err(dev, "Error in configuring the EDMA clocks\n");
++ return ret;
++ }
++
++ dev_dbg(dev, "QCOM EDMA common clocks are configured\n");
++
++ ret = edma_hw_configure();
++ if (ret) {
++ dev_err(dev, "Error in edma configuration\n");
++ return ret;
++ }
++
++ ret = edma_irq_init();
++ if (ret) {
++ dev_err(dev, "Error in irq initialization\n");
++ return ret;
++ }
++
++ dev_info(dev, "EDMA configuration successful\n");
++
++ return 0;
++}
++
++/**
++ * ppe_edma_queue_offset_config - Configure queue offset for EDMA interface
++ * @ppe_dev: PPE device
++ * @class: The class to configure queue offset
++ * @index: Class index, internal priority or hash value
++ * @queue_offset: Queue offset value
++ *
++ * PPE EDMA queue offset is configured based on the PPE internal priority or
++ * RSS hash value, the profile ID is fixed to 0 for EDMA interface.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
++ enum ppe_queue_class_type class,
++ int index, int queue_offset)
++{
++ if (class == PPE_QUEUE_CLASS_PRIORITY)
++ return ppe_queue_ucast_offset_pri_set(ppe_dev, 0,
++ index, queue_offset);
++
++ return ppe_queue_ucast_offset_hash_set(ppe_dev, 0,
++ index, queue_offset);
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -0,0 +1,113 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_MAIN__
++#define __EDMA_MAIN__
++
++#include "ppe_config.h"
++
++/* One clock cycle = 1/(EDMA clock frequency in Mhz) micro seconds.
++ *
++ * One timer unit is 128 clock cycles.
++ *
++ * So, therefore the microsecond to timer unit calculation is:
++ * Timer unit = time in microseconds / (one clock cycle in microsecond * cycles in 1 timer unit)
++ * = ('x' microsecond * EDMA clock frequency in MHz ('y') / 128).
++ *
++ */
++#define EDMA_CYCLE_PER_TIMER_UNIT 128
++#define EDMA_MICROSEC_TO_TIMER_UNIT(x, y) ((x) * (y) / EDMA_CYCLE_PER_TIMER_UNIT)
++#define MHZ 1000000UL
++
++/* EDMA profile ID. */
++#define EDMA_CPU_PORT_PROFILE_ID 0
++
++/* Number of PPE queue priorities supported per ARM core. */
++#define EDMA_PRI_MAX_PER_CORE 8
++
++/**
++ * enum ppe_queue_class_type - PPE queue class type
++ * @PPE_QUEUE_CLASS_PRIORITY: Queue offset configured from internal priority
++ * @PPE_QUEUE_CLASS_HASH: Queue offset configured from RSS hash.
++ */
++enum ppe_queue_class_type {
++ PPE_QUEUE_CLASS_PRIORITY,
++ PPE_QUEUE_CLASS_HASH,
++};
++
++/**
++ * struct edma_ring_info - EDMA ring data structure.
++ * @max_rings: Maximum number of rings
++ * @ring_start: Ring start ID
++ * @num_rings: Number of rings
++ */
++struct edma_ring_info {
++ u32 max_rings;
++ u32 ring_start;
++ u32 num_rings;
++};
++
++/**
++ * struct edma_hw_info - EDMA hardware data structure.
++ * @rxfill: Rx Fill ring information
++ * @rx: Rx Desc ring information
++ * @tx: Tx Desc ring information
++ * @txcmpl: Tx complete ring information
++ * @max_ports: Maximum number of ports
++ * @napi_budget_rx: Rx NAPI budget
++ * @napi_budget_tx: Tx NAPI budget
++ */
++struct edma_hw_info {
++ struct edma_ring_info *rxfill;
++ struct edma_ring_info *rx;
++ struct edma_ring_info *tx;
++ struct edma_ring_info *txcmpl;
++ u32 max_ports;
++ u32 napi_budget_rx;
++ u32 napi_budget_tx;
++};
++
++/**
++ * struct edma_intr_info - EDMA interrupt data structure.
++ * @intr_mask_rx: RX interrupt mask
++ * @intr_rx: Rx interrupts
++ * @intr_mask_txcmpl: Tx completion interrupt mask
++ * @intr_txcmpl: Tx completion interrupts
++ * @intr_mask_misc: Miscellaneous interrupt mask
++ * @intr_misc: Miscellaneous interrupts
++ */
++struct edma_intr_info {
++ u32 intr_mask_rx;
++ u32 *intr_rx;
++ u32 intr_mask_txcmpl;
++ u32 *intr_txcmpl;
++ u32 intr_mask_misc;
++ u32 intr_misc;
++};
++
++/**
++ * struct edma_context - EDMA context.
++ * @netdev_arr: Net device for each EDMA port
++ * @ppe_dev: PPE device
++ * @hw_info: EDMA Hardware info
++ * @intr_info: EDMA Interrupt info
++ */
++struct edma_context {
++ struct net_device **netdev_arr;
++ struct ppe_device *ppe_dev;
++ struct edma_hw_info *hw_info;
++ struct edma_intr_info intr_info;
++};
++
++/* Global EDMA context. */
++extern struct edma_context *edma_ctx;
++
++void edma_destroy(struct ppe_device *ppe_dev);
++int edma_setup(struct ppe_device *ppe_dev);
++int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
++ enum ppe_queue_class_type class,
++ int index, int queue_offset);
++
++
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -14,6 +14,7 @@
+ #include <linux/regmap.h>
+ #include <linux/reset.h>
+
++#include "edma.h"
+ #include "ppe.h"
+ #include "ppe_config.h"
+ #include "ppe_debugfs.h"
+@@ -201,10 +202,16 @@ static int qcom_ppe_probe(struct platfor
+ if (ret)
+ return dev_err_probe(dev, ret, "PPE HW config failed\n");
+
+- ret = ppe_port_mac_init(ppe_dev);
++ ret = edma_setup(ppe_dev);
+ if (ret)
++ return dev_err_probe(dev, ret, "EDMA setup failed\n");
++
++ ret = ppe_port_mac_init(ppe_dev);
++ if (ret) {
++ edma_destroy(ppe_dev);
+ return dev_err_probe(dev, ret,
+ "PPE Port MAC initialization failed\n");
++ }
+
+ ppe_debugfs_setup(ppe_dev);
+ platform_set_drvdata(pdev, ppe_dev);
+@@ -219,6 +226,7 @@ static void qcom_ppe_remove(struct platf
+ ppe_dev = platform_get_drvdata(pdev);
+ ppe_debugfs_teardown(ppe_dev);
+ ppe_port_mac_deinit(ppe_dev);
++ edma_destroy(ppe_dev);
+
+ platform_set_drvdata(pdev, NULL);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -800,4 +800,257 @@
+ #define XGMAC_RXDISCARD_GB_ADDR 0x9AC
+ #define XGMAC_RXDISCARDBYTE_GB_ADDR 0x9B4
+
++#define EDMA_BASE_OFFSET 0xb00000
++
++/* EDMA register offsets */
++#define EDMA_REG_MAS_CTRL_ADDR 0x0
++#define EDMA_REG_PORT_CTRL_ADDR 0x4
++#define EDMA_REG_VLAN_CTRL_ADDR 0x8
++#define EDMA_REG_RXDESC2FILL_MAP_0_ADDR 0x14
++#define EDMA_REG_RXDESC2FILL_MAP_1_ADDR 0x18
++#define EDMA_REG_RXDESC2FILL_MAP_2_ADDR 0x1c
++#define EDMA_REG_TXQ_CTRL_ADDR 0x20
++#define EDMA_REG_TXQ_CTRL_2_ADDR 0x24
++#define EDMA_REG_TXQ_FC_0_ADDR 0x28
++#define EDMA_REG_TXQ_FC_1_ADDR 0x30
++#define EDMA_REG_TXQ_FC_2_ADDR 0x34
++#define EDMA_REG_TXQ_FC_3_ADDR 0x38
++#define EDMA_REG_RXQ_CTRL_ADDR 0x3c
++#define EDMA_REG_MISC_ERR_QID_ADDR 0x40
++#define EDMA_REG_RXQ_FC_THRE_ADDR 0x44
++#define EDMA_REG_DMAR_CTRL_ADDR 0x48
++#define EDMA_REG_AXIR_CTRL_ADDR 0x4c
++#define EDMA_REG_AXIW_CTRL_ADDR 0x50
++#define EDMA_REG_MIN_MSS_ADDR 0x54
++#define EDMA_REG_LOOPBACK_CTRL_ADDR 0x58
++#define EDMA_REG_MISC_INT_STAT_ADDR 0x5c
++#define EDMA_REG_MISC_INT_MASK_ADDR 0x60
++#define EDMA_REG_DBG_CTRL_ADDR 0x64
++#define EDMA_REG_DBG_DATA_ADDR 0x68
++#define EDMA_REG_TX_TIMEOUT_THRESH_ADDR 0x6c
++#define EDMA_REG_REQ0_FIFO_THRESH_ADDR 0x80
++#define EDMA_REG_WB_OS_THRESH_ADDR 0x84
++#define EDMA_REG_MISC_ERR_QID_REG2_ADDR 0x88
++#define EDMA_REG_TXDESC2CMPL_MAP_0_ADDR 0x8c
++#define EDMA_REG_TXDESC2CMPL_MAP_1_ADDR 0x90
++#define EDMA_REG_TXDESC2CMPL_MAP_2_ADDR 0x94
++#define EDMA_REG_TXDESC2CMPL_MAP_3_ADDR 0x98
++#define EDMA_REG_TXDESC2CMPL_MAP_4_ADDR 0x9c
++#define EDMA_REG_TXDESC2CMPL_MAP_5_ADDR 0xa0
++
++/* Tx descriptor ring configuration register addresses */
++#define EDMA_REG_TXDESC_BA(n) (0x1000 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_PROD_IDX(n) (0x1004 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_CONS_IDX(n) (0x1008 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_RING_SIZE(n) (0x100c + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_CTRL(n) (0x1010 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_BA2(n) (0x1014 + (0x1000 * (n)))
++
++/* RxFill ring configuration register addresses */
++#define EDMA_REG_RXFILL_BA(n) (0x29000 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_PROD_IDX(n) (0x29004 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_CONS_IDX(n) (0x29008 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_RING_SIZE(n) (0x2900c + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_BUFFER1_SIZE(n) (0x29010 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_FC_THRE(n) (0x29014 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_UGT_THRE(n) (0x29018 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_RING_EN(n) (0x2901c + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_DISABLE(n) (0x29020 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_DISABLE_DONE(n) (0x29024 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_INT_STAT(n) (0x31000 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_INT_MASK(n) (0x31004 + (0x1000 * (n)))
++
++/* Rx descriptor ring configuration register addresses */
++#define EDMA_REG_RXDESC_BA(n) (0x39000 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_PROD_IDX(n) (0x39004 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_CONS_IDX(n) (0x39008 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_RING_SIZE(n) (0x3900c + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_FC_THRE(n) (0x39010 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_UGT_THRE(n) (0x39014 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_CTRL(n) (0x39018 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_BPC(n) (0x3901c + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_DISABLE(n) (0x39020 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_DISABLE_DONE(n) (0x39024 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_PREHEADER_BA(n) (0x39028 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_INT_STAT(n) (0x59000 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_INT_MASK(n) (0x59004 + (0x1000 * (n)))
++
++#define EDMA_REG_RX_MOD_TIMER(n) (0x59008 + (0x1000 * (n)))
++#define EDMA_REG_RX_INT_CTRL(n) (0x5900c + (0x1000 * (n)))
++
++/* Tx completion ring configuration register addresses */
++#define EDMA_REG_TXCMPL_BA(n) (0x79000 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_PROD_IDX(n) (0x79004 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_CONS_IDX(n) (0x79008 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_RING_SIZE(n) (0x7900c + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_UGT_THRE(n) (0x79010 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_CTRL(n) (0x79014 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_BPC(n) (0x79018 + (0x1000 * (n)))
++
++#define EDMA_REG_TX_INT_STAT(n) (0x99000 + (0x1000 * (n)))
++#define EDMA_REG_TX_INT_MASK(n) (0x99004 + (0x1000 * (n)))
++#define EDMA_REG_TX_MOD_TIMER(n) (0x99008 + (0x1000 * (n)))
++#define EDMA_REG_TX_INT_CTRL(n) (0x9900c + (0x1000 * (n)))
++
++/* EDMA_QID2RID_TABLE_MEM register field masks */
++#define EDMA_RX_RING_ID_QUEUE0_MASK GENMASK(7, 0)
++#define EDMA_RX_RING_ID_QUEUE1_MASK GENMASK(15, 8)
++#define EDMA_RX_RING_ID_QUEUE2_MASK GENMASK(23, 16)
++#define EDMA_RX_RING_ID_QUEUE3_MASK GENMASK(31, 24)
++
++/* EDMA_REG_PORT_CTRL register bit definitions */
++#define EDMA_PORT_PAD_EN 0x1
++#define EDMA_PORT_EDMA_EN 0x2
++
++/* EDMA_REG_DMAR_CTRL register field masks */
++#define EDMA_DMAR_REQ_PRI_MASK GENMASK(2, 0)
++#define EDMA_DMAR_BURST_LEN_MASK BIT(3)
++#define EDMA_DMAR_TXDATA_OUTSTANDING_NUM_MASK GENMASK(8, 4)
++#define EDMA_DMAR_TXDESC_OUTSTANDING_NUM_MASK GENMASK(11, 9)
++#define EDMA_DMAR_RXFILL_OUTSTANDING_NUM_MASK GENMASK(14, 12)
++
++#define EDMA_BURST_LEN_ENABLE 0
++
++/* Tx timeout threshold */
++#define EDMA_TX_TIMEOUT_THRESH_VAL 0xFFFF
++
++/* Rx descriptor ring base address mask */
++#define EDMA_RXDESC_BA_MASK 0xffffffff
++
++/* Rx Descriptor ring pre-header base address mask */
++#define EDMA_RXDESC_PREHEADER_BA_MASK 0xffffffff
++
++/* Tx descriptor prod ring index mask */
++#define EDMA_TXDESC_PROD_IDX_MASK 0xffff
++
++/* Tx descriptor consumer ring index mask */
++#define EDMA_TXDESC_CONS_IDX_MASK 0xffff
++
++/* Tx descriptor ring size mask */
++#define EDMA_TXDESC_RING_SIZE_MASK 0xffff
++
++/* Tx descriptor ring enable */
++#define EDMA_TXDESC_TX_ENABLE 0x1
++
++#define EDMA_TXDESC_CTRL_TXEN_MASK BIT(0)
++#define EDMA_TXDESC_CTRL_FC_GRP_ID_MASK GENMASK(3, 1)
++
++/* Tx completion ring prod index mask */
++#define EDMA_TXCMPL_PROD_IDX_MASK 0xffff
++
++/* Tx completion ring urgent threshold mask */
++#define EDMA_TXCMPL_LOW_THRE_MASK 0xffff
++#define EDMA_TXCMPL_LOW_THRE_SHIFT 0
++
++/* EDMA_REG_TX_MOD_TIMER mask */
++#define EDMA_TX_MOD_TIMER_INIT_MASK 0xffff
++#define EDMA_TX_MOD_TIMER_INIT_SHIFT 0
++
++/* Rx fill ring prod index mask */
++#define EDMA_RXFILL_PROD_IDX_MASK 0xffff
++
++/* Rx fill ring consumer index mask */
++#define EDMA_RXFILL_CONS_IDX_MASK 0xffff
++
++/* Rx fill ring size mask */
++#define EDMA_RXFILL_RING_SIZE_MASK 0xffff
++
++/* Rx fill ring flow control threshold masks */
++#define EDMA_RXFILL_FC_XON_THRE_MASK 0x7ff
++#define EDMA_RXFILL_FC_XON_THRE_SHIFT 12
++#define EDMA_RXFILL_FC_XOFF_THRE_MASK 0x7ff
++#define EDMA_RXFILL_FC_XOFF_THRE_SHIFT 0
++
++/* Rx fill ring enable bit */
++#define EDMA_RXFILL_RING_EN 0x1
++
++/* Rx desc ring prod index mask */
++#define EDMA_RXDESC_PROD_IDX_MASK 0xffff
++
++/* Rx descriptor ring cons index mask */
++#define EDMA_RXDESC_CONS_IDX_MASK 0xffff
++
++/* Rx descriptor ring size masks */
++#define EDMA_RXDESC_RING_SIZE_MASK 0xffff
++#define EDMA_RXDESC_PL_OFFSET_MASK 0x1ff
++#define EDMA_RXDESC_PL_OFFSET_SHIFT 16
++#define EDMA_RXDESC_PL_DEFAULT_VALUE 0
++
++/* Rx descriptor ring flow control threshold masks */
++#define EDMA_RXDESC_FC_XON_THRE_MASK 0x7ff
++#define EDMA_RXDESC_FC_XON_THRE_SHIFT 12
++#define EDMA_RXDESC_FC_XOFF_THRE_MASK 0x7ff
++#define EDMA_RXDESC_FC_XOFF_THRE_SHIFT 0
++
++/* Rx descriptor ring urgent threshold mask */
++#define EDMA_RXDESC_LOW_THRE_MASK 0xffff
++#define EDMA_RXDESC_LOW_THRE_SHIFT 0
++
++/* Rx descriptor ring enable bit */
++#define EDMA_RXDESC_RX_EN 0x1
++
++/* Tx interrupt status bit */
++#define EDMA_TX_INT_MASK_PKT_INT 0x1
++
++/* Rx interrupt mask */
++#define EDMA_RXDESC_INT_MASK_PKT_INT 0x1
++
++#define EDMA_MASK_INT_DISABLE 0x0
++#define EDMA_MASK_INT_CLEAR 0x0
++
++/* EDMA_REG_RX_MOD_TIMER register field masks */
++#define EDMA_RX_MOD_TIMER_INIT_MASK 0xffff
++#define EDMA_RX_MOD_TIMER_INIT_SHIFT 0
++
++/* EDMA Ring mask */
++#define EDMA_RING_DMA_MASK 0xffffffff
++
++/* RXDESC threshold interrupt. */
++#define EDMA_RXDESC_UGT_INT_STAT 0x2
++
++/* RXDESC timer interrupt */
++#define EDMA_RXDESC_PKT_INT_STAT 0x1
++
++/* RXDESC Interrupt status mask */
++#define EDMA_RXDESC_RING_INT_STATUS_MASK \
++ (EDMA_RXDESC_UGT_INT_STAT | EDMA_RXDESC_PKT_INT_STAT)
++
++/* TXCMPL threshold interrupt. */
++#define EDMA_TXCMPL_UGT_INT_STAT 0x2
++
++/* TXCMPL timer interrupt */
++#define EDMA_TXCMPL_PKT_INT_STAT 0x1
++
++/* TXCMPL Interrupt status mask */
++#define EDMA_TXCMPL_RING_INT_STATUS_MASK \
++ (EDMA_TXCMPL_UGT_INT_STAT | EDMA_TXCMPL_PKT_INT_STAT)
++
++#define EDMA_TXCMPL_RETMODE_OPAQUE 0x0
++
++#define EDMA_RXDESC_LOW_THRE 0
++#define EDMA_RX_MOD_TIMER_INIT 1000
++#define EDMA_RX_NE_INT_EN 0x2
++
++#define EDMA_TX_MOD_TIMER 150
++
++#define EDMA_TX_INITIAL_PROD_IDX 0x0
++#define EDMA_TX_NE_INT_EN 0x2
++
++/* EDMA misc error mask */
++#define EDMA_MISC_AXI_RD_ERR_MASK BIT(0)
++#define EDMA_MISC_AXI_WR_ERR_MASK BIT(1)
++#define EDMA_MISC_RX_DESC_FIFO_FULL_MASK BIT(2)
++#define EDMA_MISC_RX_ERR_BUF_SIZE_MASK BIT(3)
++#define EDMA_MISC_TX_SRAM_FULL_MASK BIT(4)
++#define EDMA_MISC_TX_CMPL_BUF_FULL_MASK BIT(5)
++
++#define EDMA_MISC_DATA_LEN_ERR_MASK BIT(6)
++#define EDMA_MISC_TX_TIMEOUT_MASK BIT(7)
++
++/* EDMA txdesc2cmpl map */
++#define EDMA_TXDESC2CMPL_MAP_TXDESC_MASK 0x1F
++
++/* EDMA rxdesc2fill map */
++#define EDMA_RXDESC2FILL_MAP_RXDESC_MASK 0x7
++
+ #endif
--- /dev/null
+From 5dc80c468c668d855d76b323f09bbadb95cc3147 Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:14:46 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add netdevice support for QCOM
+ IPQ9574 chipset.
+
+Add EDMA ports and netdevice operations for QCOM IPQ9574 chipset.
+
+Change-Id: I08b2eff52b4ef0d6d428c1c416f5580ef010973f
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 3 +
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 270 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_port.h | 31 ++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 19 ++
+ 5 files changed, 324 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_port.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_port.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o
++qcom-ppe-objs += edma.o edma_port.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -26,6 +26,9 @@
+ /* Number of PPE queue priorities supported per ARM core. */
+ #define EDMA_PRI_MAX_PER_CORE 8
+
++/* Interface ID start. */
++#define EDMA_START_IFNUM 1
++
+ /**
+ * enum ppe_queue_class_type - PPE queue class type
+ * @PPE_QUEUE_CLASS_PRIORITY: Queue offset configured from internal priority
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -0,0 +1,270 @@
++// SPDX-License-Identifier: GPL-2.0-only
++ /* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* EDMA port initialization, configuration and netdevice ops handling */
++
++#include <linux/etherdevice.h>
++#include <linux/net.h>
++#include <linux/netdevice.h>
++#include <linux/of_net.h>
++#include <linux/phylink.h>
++#include <linux/printk.h>
++
++#include "edma.h"
++#include "edma_port.h"
++#include "ppe_regs.h"
++
++/* Number of netdev queues. */
++#define EDMA_NETDEV_QUEUE_NUM 4
++
++static u16 __maybe_unused edma_port_select_queue(__maybe_unused struct net_device *netdev,
++ __maybe_unused struct sk_buff *skb,
++ __maybe_unused struct net_device *sb_dev)
++{
++ int cpu = get_cpu();
++
++ put_cpu();
++
++ return cpu;
++}
++
++static int edma_port_open(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ /* Inform the Linux Networking stack about the hardware capability of
++ * checksum offloading and other features. Each port is
++ * responsible to maintain the feature set it supports.
++ */
++ netdev->features |= EDMA_NETDEV_FEATURES;
++ netdev->hw_features |= EDMA_NETDEV_FEATURES;
++ netdev->vlan_features |= EDMA_NETDEV_FEATURES;
++ netdev->wanted_features |= EDMA_NETDEV_FEATURES;
++
++ ppe_port = port_priv->ppe_port;
++
++ if (ppe_port->phylink)
++ phylink_start(ppe_port->phylink);
++
++ netif_start_queue(netdev);
++
++ return 0;
++}
++
++static int edma_port_close(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ netif_stop_queue(netdev);
++
++ ppe_port = port_priv->ppe_port;
++
++ /* Phylink close. */
++ if (ppe_port->phylink)
++ phylink_stop(ppe_port->phylink);
++
++ return 0;
++}
++
++static int edma_port_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *ppe_port;
++ int ret = -EINVAL;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ ppe_port = port_priv->ppe_port;
++ if (ppe_port->phylink)
++ return phylink_mii_ioctl(ppe_port->phylink, ifr, cmd);
++
++ return ret;
++}
++
++static int edma_port_change_mtu(struct net_device *netdev, int mtu)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ if (!port_priv)
++ return -EINVAL;
++
++ netdev->mtu = mtu;
++
++ return ppe_port_set_maxframe(port_priv->ppe_port, mtu);
++}
++
++static netdev_features_t edma_port_feature_check(__maybe_unused struct sk_buff *skb,
++ __maybe_unused struct net_device *netdev,
++ netdev_features_t features)
++{
++ return features;
++}
++
++static void edma_port_get_stats64(struct net_device *netdev,
++ struct rtnl_link_stats64 *stats)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ if (!port_priv)
++ return;
++
++ ppe_port_get_stats64(port_priv->ppe_port, stats);
++}
++
++static int edma_port_set_mac_address(struct net_device *netdev, void *macaddr)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct sockaddr *addr = (struct sockaddr *)macaddr;
++ int ret;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ netdev_dbg(netdev, "AddrFamily: %d, %0x:%0x:%0x:%0x:%0x:%0x\n",
++ addr->sa_family, addr->sa_data[0], addr->sa_data[1],
++ addr->sa_data[2], addr->sa_data[3], addr->sa_data[4],
++ addr->sa_data[5]);
++
++ ret = eth_prepare_mac_addr_change(netdev, addr);
++ if (ret)
++ return ret;
++
++ if (ppe_port_set_mac_address(port_priv->ppe_port, (u8 *)addr)) {
++ netdev_err(netdev, "set mac address failed for dev: %s\n", netdev->name);
++ return -EINVAL;
++ }
++
++ eth_commit_mac_addr_change(netdev, addr);
++
++ return 0;
++}
++
++static const struct net_device_ops edma_port_netdev_ops = {
++ .ndo_open = edma_port_open,
++ .ndo_stop = edma_port_close,
++ .ndo_get_stats64 = edma_port_get_stats64,
++ .ndo_set_mac_address = edma_port_set_mac_address,
++ .ndo_validate_addr = eth_validate_addr,
++ .ndo_change_mtu = edma_port_change_mtu,
++ .ndo_eth_ioctl = edma_port_ioctl,
++ .ndo_features_check = edma_port_feature_check,
++ .ndo_select_queue = edma_port_select_queue,
++};
++
++/**
++ * edma_port_destroy - EDMA port destroy.
++ * @port: PPE port
++ *
++ * Unregister and free the netdevice.
++ */
++void edma_port_destroy(struct ppe_port *port)
++{
++ int port_id = port->port_id;
++ struct net_device *netdev = edma_ctx->netdev_arr[port_id - 1];
++
++ unregister_netdev(netdev);
++ free_netdev(netdev);
++ ppe_port_phylink_destroy(port);
++ edma_ctx->netdev_arr[port_id - 1] = NULL;
++}
++
++/**
++ * edma_port_setup - EDMA port Setup.
++ * @port: PPE port
++ *
++ * Initialize and register the netdevice.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int edma_port_setup(struct ppe_port *port)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device_node *np = port->np;
++ struct edma_port_priv *port_priv;
++ int port_id = port->port_id;
++ struct net_device *netdev;
++ u8 mac_addr[ETH_ALEN];
++ int ret = 0;
++ u8 *maddr;
++
++ netdev = alloc_etherdev_mqs(sizeof(struct edma_port_priv),
++ EDMA_NETDEV_QUEUE_NUM, EDMA_NETDEV_QUEUE_NUM);
++ if (!netdev) {
++ pr_err("alloc_etherdev() failed\n");
++ return -ENOMEM;
++ }
++
++ SET_NETDEV_DEV(netdev, ppe_dev->dev);
++ netdev->dev.of_node = np;
++
++ /* max_mtu is set to 1500 in ether_setup(). */
++ netdev->max_mtu = ETH_MAX_MTU;
++
++ port_priv = netdev_priv(netdev);
++ memset((void *)port_priv, 0, sizeof(struct edma_port_priv));
++
++ port_priv->ppe_port = port;
++ port_priv->netdev = netdev;
++ netdev->watchdog_timeo = 5 * HZ;
++ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
++ netdev->netdev_ops = &edma_port_netdev_ops;
++ netdev->gso_max_segs = GSO_MAX_SEGS;
++
++ maddr = mac_addr;
++ if (of_get_mac_address(np, maddr))
++ maddr = NULL;
++
++ if (maddr && is_valid_ether_addr(maddr)) {
++ eth_hw_addr_set(netdev, maddr);
++ } else {
++ eth_hw_addr_random(netdev);
++ netdev_info(netdev, "GMAC%d Using random MAC address - %pM\n",
++ port_id, netdev->dev_addr);
++ }
++
++ netdev_dbg(netdev, "Configuring the port %s(qcom-id:%d)\n",
++ netdev->name, port_id);
++
++ /* We expect 'port_id' to correspond to ports numbers on SoC.
++ * These begin from '1' and hence we subtract
++ * one when using it as an array index.
++ */
++ edma_ctx->netdev_arr[port_id - 1] = netdev;
++
++ /* Setup phylink. */
++ ret = ppe_port_phylink_setup(port, netdev);
++ if (ret) {
++ netdev_dbg(netdev, "EDMA port phylink setup for netdevice %s\n",
++ netdev->name);
++ goto port_phylink_setup_fail;
++ }
++
++ /* Register the network interface. */
++ ret = register_netdev(netdev);
++ if (ret) {
++ netdev_dbg(netdev, "Error registering netdevice %s\n",
++ netdev->name);
++ goto register_netdev_fail;
++ }
++
++ netdev_dbg(netdev, "Setup EDMA port GMAC%d done\n", port_id);
++ return ret;
++
++register_netdev_fail:
++ ppe_port_phylink_destroy(port);
++port_phylink_setup_fail:
++ free_netdev(netdev);
++ edma_ctx->netdev_arr[port_id - 1] = NULL;
++
++ return ret;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.h
+@@ -0,0 +1,31 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_PORTS__
++#define __EDMA_PORTS__
++
++#include "ppe_port.h"
++
++#define EDMA_NETDEV_FEATURES (NETIF_F_FRAGLIST \
++ | NETIF_F_SG \
++ | NETIF_F_RXCSUM \
++ | NETIF_F_HW_CSUM \
++ | NETIF_F_TSO \
++ | NETIF_F_TSO6)
++
++/**
++ * struct edma_port_priv - EDMA port priv structure.
++ * @ppe_port: Pointer to PPE port
++ * @netdev: Corresponding netdevice
++ * @flags: Feature flags
++ */
++struct edma_port_priv {
++ struct ppe_port *ppe_port;
++ struct net_device *netdev;
++ unsigned long flags;
++};
++
++void edma_port_destroy(struct ppe_port *port);
++int edma_port_setup(struct ppe_port *port);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -13,6 +13,7 @@
+ #include <linux/regmap.h>
+ #include <linux/rtnetlink.h>
+
++#include "edma_port.h"
+ #include "ppe.h"
+ #include "ppe_port.h"
+ #include "ppe_regs.h"
+@@ -1277,12 +1278,26 @@ int ppe_port_mac_init(struct ppe_device
+ goto err_port_node;
+ }
+
++ ret = edma_port_setup(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "QCOM EDMA port setup failed\n");
++ i--;
++ goto err_port_setup;
++ }
++
+ i++;
+ }
+
+ of_node_put(ports_node);
+ return 0;
+
++err_port_setup:
++ /* Destroy edma ports created till now */
++ while (i >= 0) {
++ edma_port_destroy(&ppe_ports->port[i]);
++ i--;
++ }
++
+ err_port_clk:
+ for (j = 0; j < i; j++)
+ ppe_port_clock_deinit(&ppe_ports->port[j]);
+@@ -1307,6 +1322,10 @@ void ppe_port_mac_deinit(struct ppe_devi
+
+ for (i = 0; i < ppe_dev->ports->num; i++) {
+ ppe_port = &ppe_dev->ports->port[i];
++
++ /* Destroy all phylinks and edma ports */
++ edma_port_destroy(ppe_port);
++
+ ppe_port_clock_deinit(ppe_port);
+ }
+ }
--- /dev/null
+From b5c8c5d3888328321e8be1db50b75dff8f514e51 Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:21:19 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add Rx Ethernet DMA support
+
+Add Rx queues, rings, descriptors configurations and
+DMA support for the EDMA.
+
+Change-Id: I612bcd661e74d5bf3ecb33de10fd5298d18ff7e9
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Alex G: add missing functions that were previously in ppe_api.c:
+ - ppe_edma_queue_resource_get()
+ - ppe_edma_ring_to_queues_config()
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 214 +++-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 22 +-
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.c | 964 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.h | 48 +
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 39 +-
+ drivers/net/ethernet/qualcomm/ppe/edma_port.h | 31 +
+ drivers/net/ethernet/qualcomm/ppe/edma_rx.c | 622 +++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_rx.h | 287 ++++++
+ 9 files changed, 2224 insertions(+), 5 deletions(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_rx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_rx.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_port.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_port.o edma_rx.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -18,12 +18,23 @@
+ #include <linux/reset.h>
+
+ #include "edma.h"
++#include "edma_cfg_rx.h"
+ #include "ppe_regs.h"
+
+ #define EDMA_IRQ_NAME_SIZE 32
+
+ /* Global EDMA context. */
+ struct edma_context *edma_ctx;
++static char **edma_rxdesc_irq_name;
++
++/* Module params. */
++static int page_mode;
++module_param(page_mode, int, 0);
++MODULE_PARM_DESC(page_mode, "Enable page mode (default:0)");
++
++static int rx_buff_size;
++module_param(rx_buff_size, int, 0640);
++MODULE_PARM_DESC(rx_buff_size, "Rx Buffer size for Jumbo MRU value (default:0)");
+
+ /* Priority to multi-queue mapping. */
+ static u8 edma_pri_map[PPE_QUEUE_INTER_PRI_NUM] = {
+@@ -178,6 +189,59 @@ static int edma_configure_ucast_prio_map
+ return ret;
+ }
+
++static int edma_irq_register(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ int ret;
++ u32 i;
++
++ /* Request IRQ for RXDESC rings. */
++ edma_rxdesc_irq_name = kzalloc((sizeof(char *) * rx->num_rings),
++ GFP_KERNEL);
++ if (!edma_rxdesc_irq_name)
++ return -ENOMEM;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ edma_rxdesc_irq_name[i] = kzalloc((sizeof(char *) * EDMA_IRQ_NAME_SIZE),
++ GFP_KERNEL);
++ if (!edma_rxdesc_irq_name[i]) {
++ ret = -ENOMEM;
++ goto rxdesc_irq_name_alloc_fail;
++ }
++
++ snprintf(edma_rxdesc_irq_name[i], 20, "edma_rxdesc_%d",
++ rx->ring_start + i);
++
++ irq_set_status_flags(edma_ctx->intr_info.intr_rx[i], IRQ_DISABLE_UNLAZY);
++
++ ret = request_irq(edma_ctx->intr_info.intr_rx[i],
++ edma_rx_handle_irq, IRQF_SHARED,
++ edma_rxdesc_irq_name[i],
++ (void *)&edma_ctx->rx_rings[i]);
++ if (ret) {
++ pr_err("RXDESC ring IRQ:%d request failed\n",
++ edma_ctx->intr_info.intr_rx[i]);
++ goto rx_desc_ring_intr_req_fail;
++ }
++
++ pr_debug("RXDESC ring: %d IRQ:%d request success: %s\n",
++ rx->ring_start + i,
++ edma_ctx->intr_info.intr_rx[i],
++ edma_rxdesc_irq_name[i]);
++ }
++
++ return 0;
++
++rx_desc_ring_intr_req_fail:
++ for (i = 0; i < rx->num_rings; i++)
++ kfree(edma_rxdesc_irq_name[i]);
++rxdesc_irq_name_alloc_fail:
++ kfree(edma_rxdesc_irq_name);
++
++ return ret;
++}
++
+ static int edma_irq_init(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+@@ -260,6 +324,16 @@ static int edma_irq_init(void)
+ return 0;
+ }
+
++static int edma_alloc_rings(void)
++{
++ if (edma_cfg_rx_rings_alloc()) {
++ pr_err("Error in allocating Rx rings\n");
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
+ static int edma_hw_reset(void)
+ {
+ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
+@@ -343,6 +417,40 @@ static int edma_hw_configure(void)
+ if (!edma_ctx->netdev_arr)
+ return -ENOMEM;
+
++ edma_ctx->dummy_dev = alloc_netdev_dummy(0);
++ if (!edma_ctx->dummy_dev) {
++ ret = -ENOMEM;
++ pr_err("Failed to allocate dummy device. ret: %d\n", ret);
++ goto dummy_dev_alloc_failed;
++ }
++
++ /* Set EDMA jumbo MRU if enabled or set page mode. */
++ if (edma_ctx->rx_buf_size) {
++ edma_ctx->rx_page_mode = false;
++ pr_debug("Rx Jumbo mru is enabled: %d\n", edma_ctx->rx_buf_size);
++ } else {
++ edma_ctx->rx_page_mode = page_mode;
++ }
++
++ ret = edma_alloc_rings();
++ if (ret) {
++ pr_err("Error in initializaing the rings. ret: %d\n", ret);
++ goto edma_alloc_rings_failed;
++ }
++
++ /* Disable interrupts. */
++ edma_cfg_rx_disable_interrupts();
++
++ edma_cfg_rx_rings_disable();
++
++ edma_cfg_rx_ring_mappings();
++
++ ret = edma_cfg_rx_rings();
++ if (ret) {
++ pr_err("Error in configuring Rx rings. ret: %d\n", ret);
++ goto edma_cfg_rx_rings_failed;
++ }
++
+ /* Configure DMA request priority, DMA read burst length,
+ * and AXI write size.
+ */
+@@ -376,6 +484,10 @@ static int edma_hw_configure(void)
+ data |= EDMA_MISC_TX_TIMEOUT_MASK;
+ edma_ctx->intr_info.intr_mask_misc = data;
+
++ edma_cfg_rx_rings_enable();
++ edma_cfg_rx_napi_add();
++ edma_cfg_rx_napi_enable();
++
+ /* Global EDMA enable and padding enable. */
+ data = EDMA_PORT_PAD_EN | EDMA_PORT_EDMA_EN;
+
+@@ -389,11 +501,32 @@ static int edma_hw_configure(void)
+ if (ret) {
+ pr_err("Failed to initialize unicast priority map table: %d\n",
+ ret);
+- kfree(edma_ctx->netdev_arr);
+- return ret;
++ goto configure_ucast_prio_map_tbl_failed;
++ }
++
++ /* Initialize RPS hash map table. */
++ ret = edma_cfg_rx_rps_hash_map();
++ if (ret) {
++ pr_err("Failed to configure rps hash table: %d\n",
++ ret);
++ goto edma_cfg_rx_rps_hash_map_failed;
+ }
+
+ return 0;
++
++edma_cfg_rx_rps_hash_map_failed:
++configure_ucast_prio_map_tbl_failed:
++ edma_cfg_rx_napi_disable();
++ edma_cfg_rx_napi_delete();
++ edma_cfg_rx_rings_disable();
++edma_cfg_rx_rings_failed:
++ edma_cfg_rx_rings_cleanup();
++edma_alloc_rings_failed:
++ free_netdev(edma_ctx->dummy_dev);
++dummy_dev_alloc_failed:
++ kfree(edma_ctx->netdev_arr);
++
++ return ret;
+ }
+
+ /**
+@@ -404,8 +537,31 @@ static int edma_hw_configure(void)
+ */
+ void edma_destroy(struct ppe_device *ppe_dev)
+ {
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ /* Disable interrupts. */
++ edma_cfg_rx_disable_interrupts();
++
++ /* Free IRQ for RXDESC rings. */
++ for (i = 0; i < rx->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_rx[i]);
++ free_irq(edma_ctx->intr_info.intr_rx[i],
++ (void *)&edma_ctx->rx_rings[i]);
++ kfree(edma_rxdesc_irq_name[i]);
++ }
++ kfree(edma_rxdesc_irq_name);
++
+ kfree(edma_ctx->intr_info.intr_rx);
+ kfree(edma_ctx->intr_info.intr_txcmpl);
++
++ edma_cfg_rx_napi_disable();
++ edma_cfg_rx_napi_delete();
++ edma_cfg_rx_rings_disable();
++ edma_cfg_rx_rings_cleanup();
++
++ free_netdev(edma_ctx->dummy_dev);
+ kfree(edma_ctx->netdev_arr);
+ }
+
+@@ -428,6 +584,7 @@ int edma_setup(struct ppe_device *ppe_de
+
+ edma_ctx->hw_info = &ipq9574_hw_info;
+ edma_ctx->ppe_dev = ppe_dev;
++ edma_ctx->rx_buf_size = rx_buff_size;
+
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+@@ -450,6 +607,16 @@ int edma_setup(struct ppe_device *ppe_de
+ return ret;
+ }
+
++ ret = edma_irq_register();
++ if (ret) {
++ dev_err(dev, "Error in irq registration\n");
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return ret;
++ }
++
++ edma_cfg_rx_enable_interrupts();
++
+ dev_info(dev, "EDMA configuration successful\n");
+
+ return 0;
+@@ -478,3 +645,46 @@ int ppe_edma_queue_offset_config(struct
+ return ppe_queue_ucast_offset_hash_set(ppe_dev, 0,
+ index, queue_offset);
+ }
++
++/**
++ * ppe_edma_queue_resource_get - Get EDMA queue resource
++ * @ppe_dev: PPE device
++ * @type: Resource type
++ * @res_start: Resource start ID returned
++ * @res_end: Resource end ID returned
++ *
++ * PPE EDMA queue resource includes unicast queue and multicast queue.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_edma_queue_resource_get(struct ppe_device *ppe_dev, int type,
++ int *res_start, int *res_end)
++{
++ if (type != PPE_RES_UCAST && type != PPE_RES_MCAST)
++ return -EINVAL;
++
++ return ppe_port_resource_get(ppe_dev, 0, type, res_start, res_end);
++};
++
++/**
++ * ppe_edma_ring_to_queues_config - Map EDMA ring to PPE queues
++ * @ppe_dev: PPE device
++ * @ring_id: EDMA ring ID
++ * @num: Number of queues mapped to EDMA ring
++ * @queues: PPE queue IDs
++ *
++ * PPE queues are configured to map with the special EDMA ring ID.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_edma_ring_to_queues_config(struct ppe_device *ppe_dev, int ring_id,
++ int num, int queues[] __counted_by(num))
++{
++ u32 queue_bmap[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT] = {};
++ int index;
++
++ for (index = 0; index < num; index++)
++ queue_bmap[queues[index] / 32] |= BIT_MASK(queues[index] % 32);
++
++ return ppe_ring_queue_map_set(ppe_dev, ring_id, queue_bmap);
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -6,6 +6,7 @@
+ #define __EDMA_MAIN__
+
+ #include "ppe_config.h"
++#include "edma_rx.h"
+
+ /* One clock cycle = 1/(EDMA clock frequency in Mhz) micro seconds.
+ *
+@@ -29,6 +30,11 @@
+ /* Interface ID start. */
+ #define EDMA_START_IFNUM 1
+
++#define EDMA_DESC_AVAIL_COUNT(head, tail, _max) ({ \
++ typeof(_max) (max) = (_max); \
++ ((((head) - (tail)) + \
++ (max)) & ((max) - 1)); })
++
+ /**
+ * enum ppe_queue_class_type - PPE queue class type
+ * @PPE_QUEUE_CLASS_PRIORITY: Queue offset configured from internal priority
+@@ -92,18 +98,28 @@ struct edma_intr_info {
+ /**
+ * struct edma_context - EDMA context.
+ * @netdev_arr: Net device for each EDMA port
++ * @dummy_dev: Dummy netdevice for RX DMA
+ * @ppe_dev: PPE device
+ * @hw_info: EDMA Hardware info
+ * @intr_info: EDMA Interrupt info
++ * @rxfill_rings: Rx fill Rings, SW is producer
++ * @rx_rings: Rx Desc Rings, SW is consumer
++ * @rx_page_mode: Page mode enabled or disabled
++ * @rx_buf_size: Rx buffer size for Jumbo MRU
+ */
+ struct edma_context {
+ struct net_device **netdev_arr;
++ struct net_device *dummy_dev;
+ struct ppe_device *ppe_dev;
+ struct edma_hw_info *hw_info;
+ struct edma_intr_info intr_info;
++ struct edma_rxfill_ring *rxfill_rings;
++ struct edma_rxdesc_ring *rx_rings;
++ u32 rx_page_mode;
++ u32 rx_buf_size;
+ };
+
+-/* Global EDMA context. */
++/* Global EDMA context */
+ extern struct edma_context *edma_ctx;
+
+ void edma_destroy(struct ppe_device *ppe_dev);
+@@ -111,6 +127,10 @@ int edma_setup(struct ppe_device *ppe_de
+ int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
+ enum ppe_queue_class_type class,
+ int index, int queue_offset);
++int ppe_edma_queue_resource_get(struct ppe_device *ppe_dev, int type,
++ int *res_start, int *res_end);
++int ppe_edma_ring_to_queues_config(struct ppe_device *ppe_dev, int ring_id,
++ int num, int queues[] __counted_by(num));
+
+
+ #endif
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+@@ -0,0 +1,964 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Configure rings, Buffers and NAPI for receive path along with
++ * providing APIs to enable, disable, clean and map the Rx rings.
++ */
++
++#include <linux/cpumask.h>
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++#include <linux/skbuff.h>
++
++#include "edma.h"
++#include "edma_cfg_rx.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++/* EDMA Queue ID to Ring ID Table. */
++#define EDMA_QID2RID_TABLE_MEM(q) (0xb9000 + (0x4 * (q)))
++
++/* Rx ring queue offset. */
++#define EDMA_QUEUE_OFFSET(q_id) ((q_id) / EDMA_MAX_PRI_PER_CORE)
++
++/* Rx EDMA maximum queue supported. */
++#define EDMA_CPU_PORT_QUEUE_MAX(queue_start) \
++ ((queue_start) + (EDMA_MAX_PRI_PER_CORE * num_possible_cpus()) - 1)
++
++/* EDMA Queue ID to Ring ID configuration. */
++#define EDMA_QID2RID_NUM_PER_REG 4
++
++int rx_queues[] = {0, 8, 16, 24};
++
++static u32 edma_rx_ring_queue_map[][EDMA_MAX_CORE] = {{ 0, 8, 16, 24 },
++ { 1, 9, 17, 25 },
++ { 2, 10, 18, 26 },
++ { 3, 11, 19, 27 },
++ { 4, 12, 20, 28 },
++ { 5, 13, 21, 29 },
++ { 6, 14, 22, 30 },
++ { 7, 15, 23, 31 }};
++
++static int edma_cfg_rx_desc_rings_reset_queue_mapping(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, ret;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ ret = ppe_edma_ring_to_queues_config(edma_ctx->ppe_dev, rxdesc_ring->ring_id,
++ ARRAY_SIZE(rx_queues), rx_queues);
++ if (ret) {
++ pr_err("Error in unmapping rxdesc ring %d to PPE queue mapping to disable its backpressure configuration\n",
++ i);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_reset_queue_priority(u32 rxdesc_ring_idx)
++{
++ u32 i, queue_id, ret;
++
++ for (i = 0; i < EDMA_MAX_PRI_PER_CORE; i++) {
++ queue_id = edma_rx_ring_queue_map[i][rxdesc_ring_idx];
++
++ ret = ppe_queue_priority_set(edma_ctx->ppe_dev, queue_id, i);
++ if (ret) {
++ pr_err("Error in resetting %u queue's priority\n",
++ queue_id);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_reset_queue_config(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, ret;
++
++ if (unlikely(rx->num_rings > num_possible_cpus())) {
++ pr_err("Invalid count of rxdesc rings: %d\n",
++ rx->num_rings);
++ return -EINVAL;
++ }
++
++ /* Unmap Rxdesc ring to PPE queue mapping */
++ ret = edma_cfg_rx_desc_rings_reset_queue_mapping();
++ if (ret) {
++ pr_err("Error in resetting Rx desc ring backpressure config\n");
++ return ret;
++ }
++
++ /* Reset the priority for PPE queues mapped to Rx rings */
++ for (i = 0; i < rx->num_rings; i++) {
++ ret = edma_cfg_rx_desc_ring_reset_queue_priority(i);
++ if (ret) {
++ pr_err("Error in resetting ring:%d queue's priority\n",
++ i + rx->ring_start);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_to_queue_mapping(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++ int ret;
++
++ /* Rxdesc ring to PPE queue mapping */
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ ret = ppe_edma_ring_to_queues_config(edma_ctx->ppe_dev,
++ rxdesc_ring->ring_id,
++ ARRAY_SIZE(rx_queues), rx_queues);
++ if (ret) {
++ pr_err("Error in configuring Rx ring to PPE queue mapping, ret: %d, id: %d\n",
++ ret, rxdesc_ring->ring_id);
++ if (!edma_cfg_rx_desc_rings_reset_queue_mapping())
++ pr_err("Error in resetting Rx desc ringbackpressure configurations\n");
++
++ return ret;
++ }
++
++ pr_debug("Rx desc ring %d to PPE queue mapping for backpressure:\n",
++ rxdesc_ring->ring_id);
++ }
++
++ return 0;
++}
++
++static void edma_cfg_rx_desc_ring_configure(struct edma_rxdesc_ring *rxdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_BA(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, (u32)(rxdesc_ring->pdma & EDMA_RXDESC_BA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_PREHEADER_BA(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, (u32)(rxdesc_ring->sdma & EDMA_RXDESC_PREHEADER_BA_MASK));
++
++ data = rxdesc_ring->count & EDMA_RXDESC_RING_SIZE_MASK;
++ data |= (EDMA_RXDESC_PL_DEFAULT_VALUE & EDMA_RXDESC_PL_OFFSET_MASK)
++ << EDMA_RXDESC_PL_OFFSET_SHIFT;
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_RING_SIZE(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++
++ /* Configure the Mitigation timer */
++ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_RX_MITIGATION_TIMER_DEF,
++ ppe_dev->clk_rate / MHZ);
++ data = ((data & EDMA_RX_MOD_TIMER_INIT_MASK)
++ << EDMA_RX_MOD_TIMER_INIT_SHIFT);
++ pr_debug("EDMA Rx mitigation timer value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RX_MOD_TIMER(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++
++ /* Configure the Mitigation packet count */
++ data = (EDMA_RX_MITIGATION_PKT_CNT_DEF & EDMA_RXDESC_LOW_THRE_MASK)
++ << EDMA_RXDESC_LOW_THRE_SHIFT;
++ pr_debug("EDMA Rx mitigation packet count value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_UGT_THRE(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++
++ /* Enable ring. Set ret mode to 'opaque'. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RX_INT_CTRL(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, EDMA_RX_NE_INT_EN);
++}
++
++static void edma_cfg_rx_qid_to_rx_desc_ring_mapping(void)
++{
++ u32 desc_index, ring_index, reg_index, data, q_id;
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 mcast_start, mcast_end, reg;
++ int ret;
++
++ desc_index = (rx->ring_start & EDMA_RX_RING_ID_MASK);
++
++ /* Here map all the queues to ring. */
++ for (q_id = EDMA_RX_QUEUE_START;
++ q_id <= EDMA_CPU_PORT_QUEUE_MAX(EDMA_RX_QUEUE_START);
++ q_id += EDMA_QID2RID_NUM_PER_REG) {
++ reg_index = q_id / EDMA_QID2RID_NUM_PER_REG;
++ ring_index = desc_index + EDMA_QUEUE_OFFSET(q_id);
++
++ data = FIELD_PREP(EDMA_RX_RING_ID_QUEUE0_MASK, ring_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE1_MASK, ring_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE2_MASK, ring_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE3_MASK, ring_index);
++
++ reg = EDMA_BASE_OFFSET + EDMA_QID2RID_TABLE_MEM(reg_index);
++ regmap_write(regmap, reg, data);
++ pr_debug("Configure QID2RID: %d reg:0x%x to 0x%x, desc_index: %d, reg_index: %d\n",
++ q_id, EDMA_QID2RID_TABLE_MEM(reg_index), data, desc_index, reg_index);
++ }
++
++ ret = ppe_edma_queue_resource_get(edma_ctx->ppe_dev, PPE_RES_MCAST,
++ &mcast_start, &mcast_end);
++ if (ret < 0) {
++ pr_err("Error in extracting multicast queue values\n");
++ return;
++ }
++
++ /* Map multicast queues to the first Rx ring. */
++ desc_index = (rx->ring_start & EDMA_RX_RING_ID_MASK);
++ for (q_id = mcast_start; q_id <= mcast_end;
++ q_id += EDMA_QID2RID_NUM_PER_REG) {
++ reg_index = q_id / EDMA_QID2RID_NUM_PER_REG;
++
++ data = FIELD_PREP(EDMA_RX_RING_ID_QUEUE0_MASK, desc_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE1_MASK, desc_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE2_MASK, desc_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE3_MASK, desc_index);
++
++ reg = EDMA_BASE_OFFSET + EDMA_QID2RID_TABLE_MEM(reg_index);
++ regmap_write(regmap, reg, data);
++
++ pr_debug("Configure QID2RID: %d reg:0x%x to 0x%x\n",
++ q_id, EDMA_QID2RID_TABLE_MEM(reg_index), data);
++ }
++}
++
++static void edma_cfg_rx_rings_to_rx_fill_mapping(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, data, reg;
++
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_0_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_1_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_2_ADDR, 0);
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring = &edma_ctx->rx_rings[i];
++ u32 data, reg, ring_id;
++
++ ring_id = rxdesc_ring->ring_id;
++ if (ring_id >= 0 && ring_id <= 9)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_0_ADDR;
++ else if (ring_id >= 10 && ring_id <= 19)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_1_ADDR;
++ else
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_2_ADDR;
++
++ pr_debug("Configure RXDESC:%u to use RXFILL:%u\n",
++ ring_id,
++ rxdesc_ring->rxfill->ring_id);
++
++ /* Set the Rx fill ring number in the mapping register. */
++ regmap_read(regmap, reg, &data);
++ data |= (rxdesc_ring->rxfill->ring_id &
++ EDMA_RXDESC2FILL_MAP_RXDESC_MASK) <<
++ ((ring_id % 10) * 3);
++ regmap_write(regmap, reg, data);
++ }
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_0_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_RXDESC2FILL_MAP_0_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_1_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_RXDESC2FILL_MAP_1_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_2_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_RXDESC2FILL_MAP_2_ADDR: 0x%x\n", data);
++}
++
++/**
++ * edma_cfg_rx_rings_enable - Enable Rx and Rxfill rings
++ *
++ * Enable Rx and Rxfill rings.
++ */
++void edma_cfg_rx_rings_enable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ /* Enable Rx rings */
++ for (i = rx->ring_start; i < rx->ring_start + rx->num_rings; i++) {
++ u32 data;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CTRL(i);
++ regmap_read(regmap, reg, &data);
++ data |= EDMA_RXDESC_RX_EN;
++ regmap_write(regmap, reg, data);
++ }
++
++ for (i = rxfill->ring_start; i < rxfill->ring_start + rxfill->num_rings; i++) {
++ u32 data;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_RING_EN(i);
++ regmap_read(regmap, reg, &data);
++ data |= EDMA_RXFILL_RING_EN;
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_rx_rings_disable - Disable Rx and Rxfill rings
++ *
++ * Disable Rx and Rxfill rings.
++ */
++void edma_cfg_rx_rings_disable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ /* Disable Rx rings */
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring = NULL;
++ u32 data;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CTRL(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &data);
++ data &= ~EDMA_RXDESC_RX_EN;
++ regmap_write(regmap, reg, data);
++ }
++
++ /* Disable RxFill Rings */
++ for (i = 0; i < rxfill->num_rings; i++) {
++ struct edma_rxfill_ring *rxfill_ring = NULL;
++ u32 data;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_RING_EN(rxfill_ring->ring_id);
++ regmap_read(regmap, reg, &data);
++ data &= ~EDMA_RXFILL_RING_EN;
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_rx_mappings - Setup RX ring mapping
++ *
++ * Setup queue ID to Rx desc ring mapping.
++ */
++void edma_cfg_rx_ring_mappings(void)
++{
++ edma_cfg_rx_qid_to_rx_desc_ring_mapping();
++ edma_cfg_rx_rings_to_rx_fill_mapping();
++}
++
++static void edma_cfg_rx_fill_ring_cleanup(struct edma_rxfill_ring *rxfill_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct device *dev = ppe_dev->dev;
++ u16 cons_idx, curr_idx;
++ u32 data, reg;
++
++ /* Get RxFill ring producer index */
++ curr_idx = rxfill_ring->prod_idx & EDMA_RXFILL_PROD_IDX_MASK;
++
++ /* Get RxFill ring consumer index */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_CONS_IDX(rxfill_ring->ring_id);
++ regmap_read(regmap, reg, &data);
++ cons_idx = data & EDMA_RXFILL_CONS_IDX_MASK;
++
++ while (curr_idx != cons_idx) {
++ struct edma_rxfill_desc *rxfill_desc;
++ struct sk_buff *skb;
++
++ /* Get RxFill descriptor */
++ rxfill_desc = EDMA_RXFILL_DESC(rxfill_ring, cons_idx);
++
++ cons_idx = (cons_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++
++ /* Get skb from opaque */
++ skb = (struct sk_buff *)EDMA_RXFILL_OPAQUE_GET(rxfill_desc);
++ if (unlikely(!skb)) {
++ pr_err("Empty skb reference at index:%d\n",
++ cons_idx);
++ continue;
++ }
++
++ dev_kfree_skb_any(skb);
++ }
++
++ /* Free RxFill ring descriptors */
++ dma_free_coherent(dev, (sizeof(struct edma_rxfill_desc)
++ * rxfill_ring->count),
++ rxfill_ring->desc, rxfill_ring->dma);
++ rxfill_ring->desc = NULL;
++ rxfill_ring->dma = (dma_addr_t)0;
++}
++
++static int edma_cfg_rx_fill_ring_dma_alloc(struct edma_rxfill_ring *rxfill_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Allocate RxFill ring descriptors */
++ rxfill_ring->desc = dma_alloc_coherent(dev, (sizeof(struct edma_rxfill_desc)
++ * rxfill_ring->count),
++ &rxfill_ring->dma,
++ GFP_KERNEL | __GFP_ZERO);
++ if (unlikely(!rxfill_ring->desc))
++ return -ENOMEM;
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_dma_alloc(struct edma_rxdesc_ring *rxdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ rxdesc_ring->pdesc = dma_alloc_coherent(dev, (sizeof(struct edma_rxdesc_pri)
++ * rxdesc_ring->count),
++ &rxdesc_ring->pdma, GFP_KERNEL | __GFP_ZERO);
++ if (unlikely(!rxdesc_ring->pdesc))
++ return -ENOMEM;
++
++ rxdesc_ring->sdesc = dma_alloc_coherent(dev, (sizeof(struct edma_rxdesc_sec)
++ * rxdesc_ring->count),
++ &rxdesc_ring->sdma, GFP_KERNEL | __GFP_ZERO);
++ if (unlikely(!rxdesc_ring->sdesc)) {
++ dma_free_coherent(dev, (sizeof(struct edma_rxdesc_pri)
++ * rxdesc_ring->count),
++ rxdesc_ring->pdesc,
++ rxdesc_ring->pdma);
++ rxdesc_ring->pdesc = NULL;
++ rxdesc_ring->pdma = (dma_addr_t)0;
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void edma_cfg_rx_desc_ring_cleanup(struct edma_rxdesc_ring *rxdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct device *dev = ppe_dev->dev;
++ u32 prod_idx, cons_idx, reg;
++
++ /* Get Rxdesc consumer & producer indices */
++ cons_idx = rxdesc_ring->cons_idx & EDMA_RXDESC_CONS_IDX_MASK;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_PROD_IDX(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &prod_idx);
++ prod_idx = prod_idx & EDMA_RXDESC_PROD_IDX_MASK;
++
++ /* Free any buffers assigned to any descriptors */
++ while (cons_idx != prod_idx) {
++ struct edma_rxdesc_pri *rxdesc_pri =
++ EDMA_RXDESC_PRI_DESC(rxdesc_ring, cons_idx);
++ struct sk_buff *skb;
++
++ /* Update consumer index */
++ cons_idx = (cons_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++
++ /* Get opaque from Rxdesc */
++ skb = (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(rxdesc_pri);
++ if (unlikely(!skb)) {
++ pr_warn("Empty skb reference at index:%d\n",
++ cons_idx);
++ continue;
++ }
++
++ dev_kfree_skb_any(skb);
++ }
++
++ /* Update the consumer index */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CONS_IDX(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, cons_idx);
++
++ /* Free Rxdesc ring descriptor */
++ dma_free_coherent(dev, (sizeof(struct edma_rxdesc_pri)
++ * rxdesc_ring->count), rxdesc_ring->pdesc,
++ rxdesc_ring->pdma);
++ rxdesc_ring->pdesc = NULL;
++ rxdesc_ring->pdma = (dma_addr_t)0;
++
++ /* Free any buffers assigned to any secondary ring descriptors */
++ dma_free_coherent(dev, (sizeof(struct edma_rxdesc_sec)
++ * rxdesc_ring->count), rxdesc_ring->sdesc,
++ rxdesc_ring->sdma);
++ rxdesc_ring->sdesc = NULL;
++ rxdesc_ring->sdma = (dma_addr_t)0;
++}
++
++static int edma_cfg_rx_rings_setup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 ring_idx, alloc_size, buf_len;
++
++ /* Set buffer allocation size */
++ if (edma_ctx->rx_buf_size) {
++ alloc_size = edma_ctx->rx_buf_size +
++ EDMA_RX_SKB_HEADROOM + NET_IP_ALIGN;
++ buf_len = alloc_size - EDMA_RX_SKB_HEADROOM - NET_IP_ALIGN;
++ } else if (edma_ctx->rx_page_mode) {
++ alloc_size = EDMA_RX_PAGE_MODE_SKB_SIZE +
++ EDMA_RX_SKB_HEADROOM + NET_IP_ALIGN;
++ buf_len = PAGE_SIZE;
++ } else {
++ alloc_size = EDMA_RX_BUFFER_SIZE;
++ buf_len = alloc_size - EDMA_RX_SKB_HEADROOM - NET_IP_ALIGN;
++ }
++
++ pr_debug("EDMA ctx:%p rx_ring alloc_size=%d, buf_len=%d\n",
++ edma_ctx, alloc_size, buf_len);
++
++ /* Allocate Rx fill ring descriptors */
++ for (ring_idx = 0; ring_idx < rxfill->num_rings; ring_idx++) {
++ u32 ret;
++ struct edma_rxfill_ring *rxfill_ring = NULL;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[ring_idx];
++ rxfill_ring->count = EDMA_RX_RING_SIZE;
++ rxfill_ring->ring_id = rxfill->ring_start + ring_idx;
++ rxfill_ring->alloc_size = alloc_size;
++ rxfill_ring->buf_len = buf_len;
++ rxfill_ring->page_mode = edma_ctx->rx_page_mode;
++
++ ret = edma_cfg_rx_fill_ring_dma_alloc(rxfill_ring);
++ if (ret) {
++ pr_err("Error in setting up %d rxfill ring. ret: %d",
++ rxfill_ring->ring_id, ret);
++ while (--ring_idx >= 0)
++ edma_cfg_rx_fill_ring_cleanup(&edma_ctx->rxfill_rings[ring_idx]);
++
++ return -ENOMEM;
++ }
++ }
++
++ /* Allocate RxDesc ring descriptors */
++ for (ring_idx = 0; ring_idx < rx->num_rings; ring_idx++) {
++ u32 index, queue_id = EDMA_RX_QUEUE_START;
++ struct edma_rxdesc_ring *rxdesc_ring = NULL;
++ u32 ret;
++
++ rxdesc_ring = &edma_ctx->rx_rings[ring_idx];
++ rxdesc_ring->count = EDMA_RX_RING_SIZE;
++ rxdesc_ring->ring_id = rx->ring_start + ring_idx;
++
++ if (queue_id > EDMA_CPU_PORT_QUEUE_MAX(EDMA_RX_QUEUE_START)) {
++ pr_err("Invalid queue_id: %d\n", queue_id);
++ while (--ring_idx >= 0)
++ edma_cfg_rx_desc_ring_cleanup(&edma_ctx->rx_rings[ring_idx]);
++
++ goto rxdesc_mem_alloc_fail;
++ }
++
++ /* Create a mapping between RX Desc ring and Rx fill ring.
++ * Number of fill rings are lesser than the descriptor rings
++ * Share the fill rings across descriptor rings.
++ */
++ index = rxfill->ring_start +
++ (ring_idx % rxfill->num_rings);
++ rxdesc_ring->rxfill = &edma_ctx->rxfill_rings[index
++ - rxfill->ring_start];
++
++ ret = edma_cfg_rx_desc_ring_dma_alloc(rxdesc_ring);
++ if (ret) {
++ pr_err("Error in setting up %d rxdesc ring. ret: %d",
++ rxdesc_ring->ring_id, ret);
++ while (--ring_idx >= 0)
++ edma_cfg_rx_desc_ring_cleanup(&edma_ctx->rx_rings[ring_idx]);
++
++ goto rxdesc_mem_alloc_fail;
++ }
++ }
++
++ pr_debug("Rx descriptor count for Rx desc and Rx fill rings : %d\n",
++ EDMA_RX_RING_SIZE);
++
++ return 0;
++
++rxdesc_mem_alloc_fail:
++ for (ring_idx = 0; ring_idx < rxfill->num_rings; ring_idx++)
++ edma_cfg_rx_fill_ring_cleanup(&edma_ctx->rxfill_rings[ring_idx]);
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_rx_buff_size_setup - Configure EDMA Rx jumbo buffer
++ *
++ * Configure EDMA Rx jumbo buffer
++ */
++void edma_cfg_rx_buff_size_setup(void)
++{
++ if (edma_ctx->rx_buf_size) {
++ edma_ctx->rx_page_mode = false;
++ pr_debug("Rx Jumbo mru is enabled: %d\n", edma_ctx->rx_buf_size);
++ }
++}
++
++/**
++ * edma_cfg_rx_rings_alloc - Allocate EDMA Rx rings
++ *
++ * Allocate EDMA Rx rings.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int edma_cfg_rx_rings_alloc(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ int ret;
++
++ edma_ctx->rxfill_rings = kzalloc((sizeof(*edma_ctx->rxfill_rings) *
++ rxfill->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->rxfill_rings)
++ return -ENOMEM;
++
++ edma_ctx->rx_rings = kzalloc((sizeof(*edma_ctx->rx_rings) *
++ rx->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->rx_rings)
++ goto rxdesc_ring_alloc_fail;
++
++ pr_debug("RxDesc:%u rx (%u-%u) RxFill:%u (%u-%u)\n",
++ rx->num_rings, rx->ring_start,
++ (rx->ring_start + rx->num_rings - 1),
++ rxfill->num_rings, rxfill->ring_start,
++ (rxfill->ring_start + rxfill->num_rings - 1));
++
++ if (edma_cfg_rx_rings_setup()) {
++ pr_err("Error in setting up Rx rings\n");
++ goto rx_rings_setup_fail;
++ }
++
++ /* Reset Rx descriptor ring mapped queue's configurations */
++ ret = edma_cfg_rx_desc_ring_reset_queue_config();
++ if (ret) {
++ pr_err("Error in resetting the Rx descriptor rings configurations\n");
++ edma_cfg_rx_rings_cleanup();
++ return ret;
++ }
++
++ return 0;
++
++rx_rings_setup_fail:
++ kfree(edma_ctx->rx_rings);
++ edma_ctx->rx_rings = NULL;
++rxdesc_ring_alloc_fail:
++ kfree(edma_ctx->rxfill_rings);
++ edma_ctx->rxfill_rings = NULL;
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_rx_rings_cleanup - Cleanup EDMA Rx rings
++ *
++ * Cleanup EDMA Rx rings
++ */
++void edma_cfg_rx_rings_cleanup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ /* Free RxFill ring descriptors */
++ for (i = 0; i < rxfill->num_rings; i++)
++ edma_cfg_rx_fill_ring_cleanup(&edma_ctx->rxfill_rings[i]);
++
++ /* Free Rx completion ring descriptors */
++ for (i = 0; i < rx->num_rings; i++)
++ edma_cfg_rx_desc_ring_cleanup(&edma_ctx->rx_rings[i]);
++
++ kfree(edma_ctx->rxfill_rings);
++ kfree(edma_ctx->rx_rings);
++ edma_ctx->rxfill_rings = NULL;
++ edma_ctx->rx_rings = NULL;
++}
++
++static void edma_cfg_rx_fill_ring_configure(struct edma_rxfill_ring *rxfill_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 ring_sz, reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_BA(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, (u32)(rxfill_ring->dma & EDMA_RING_DMA_MASK));
++
++ ring_sz = rxfill_ring->count & EDMA_RXFILL_RING_SIZE_MASK;
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_RING_SIZE(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, ring_sz);
++
++ edma_rx_alloc_buffer(rxfill_ring, rxfill_ring->count - 1);
++}
++
++static void edma_cfg_rx_desc_ring_flow_control(u32 threshold_xoff, u32 threshold_xon)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 data, i, reg;
++
++ data = (threshold_xoff & EDMA_RXDESC_FC_XOFF_THRE_MASK) << EDMA_RXDESC_FC_XOFF_THRE_SHIFT;
++ data |= ((threshold_xon & EDMA_RXDESC_FC_XON_THRE_MASK) << EDMA_RXDESC_FC_XON_THRE_SHIFT);
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_FC_THRE(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++ }
++}
++
++static void edma_cfg_rx_fill_ring_flow_control(int threshold_xoff, int threshold_xon)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, i, reg;
++
++ data = (threshold_xoff & EDMA_RXFILL_FC_XOFF_THRE_MASK) << EDMA_RXFILL_FC_XOFF_THRE_SHIFT;
++ data |= ((threshold_xon & EDMA_RXFILL_FC_XON_THRE_MASK) << EDMA_RXFILL_FC_XON_THRE_SHIFT);
++
++ for (i = 0; i < rxfill->num_rings; i++) {
++ struct edma_rxfill_ring *rxfill_ring;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_FC_THRE(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_rx_rings - Configure EDMA Rx rings.
++ *
++ * Configure EDMA Rx rings.
++ */
++int edma_cfg_rx_rings(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rxfill->num_rings; i++)
++ edma_cfg_rx_fill_ring_configure(&edma_ctx->rxfill_rings[i]);
++
++ for (i = 0; i < rx->num_rings; i++)
++ edma_cfg_rx_desc_ring_configure(&edma_ctx->rx_rings[i]);
++
++ /* Configure Rx flow control configurations */
++ edma_cfg_rx_desc_ring_flow_control(EDMA_RX_FC_XOFF_DEF, EDMA_RX_FC_XON_DEF);
++ edma_cfg_rx_fill_ring_flow_control(EDMA_RX_FC_XOFF_DEF, EDMA_RX_FC_XON_DEF);
++
++ return edma_cfg_rx_desc_ring_to_queue_mapping();
++}
++
++/**
++ * edma_cfg_rx_disable_interrupts - EDMA disable RX interrupts
++ *
++ * Disable RX interrupt masks
++ */
++void edma_cfg_rx_disable_interrupts(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring =
++ &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_CLEAR);
++ }
++}
++
++/**
++ * edma_cfg_rx_enable_interrupts - EDMA enable RX interrupts
++ *
++ * Enable RX interrupt masks
++ */
++void edma_cfg_rx_enable_interrupts(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring =
++ &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_rx);
++ }
++}
++
++/**
++ * edma_cfg_rx_napi_disable - Disable NAPI for Rx
++ *
++ * Disable NAPI for Rx
++ */
++void edma_cfg_rx_napi_disable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ if (!rxdesc_ring->napi_added)
++ continue;
++
++ napi_disable(&rxdesc_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_rx_napi_enable - Enable NAPI for Rx
++ *
++ * Enable NAPI for Rx
++ */
++void edma_cfg_rx_napi_enable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ if (!rxdesc_ring->napi_added)
++ continue;
++
++ napi_enable(&rxdesc_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_rx_napi_delete - Delete Rx NAPI
++ *
++ * Delete RX NAPI
++ */
++void edma_cfg_rx_napi_delete(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ if (!rxdesc_ring->napi_added)
++ continue;
++
++ netif_napi_del(&rxdesc_ring->napi);
++ rxdesc_ring->napi_added = false;
++ }
++}
++
++/* Add Rx NAPI */
++/**
++ * edma_cfg_rx_napi_add - Add Rx NAPI
++ * @netdev: Netdevice
++ *
++ * Add RX NAPI
++ */
++void edma_cfg_rx_napi_add(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ netif_napi_add_weight(edma_ctx->dummy_dev, &rxdesc_ring->napi,
++ edma_rx_napi_poll, hw_info->napi_budget_rx);
++ rxdesc_ring->napi_added = true;
++ }
++
++ netdev_dbg(edma_ctx->dummy_dev, "Rx NAPI budget: %d\n", hw_info->napi_budget_rx);
++}
++
++/**
++ * edma_cfg_rx_rps_hash_map - Configure rx rps hash map.
++ *
++ * Initialize and configure RPS hash map for queues
++ */
++int edma_cfg_rx_rps_hash_map(void)
++{
++ cpumask_t edma_rps_cpumask = {{EDMA_RX_DEFAULT_BITMAP}};
++ int map_len = 0, idx = 0, ret = 0;
++ u32 q_off = EDMA_RX_QUEUE_START;
++ u32 q_map[EDMA_MAX_CORE] = {0};
++ u32 hash, cpu;
++
++ /* Map all possible hash values to queues used by the EDMA Rx
++ * rings based on a bitmask, which represents the cores to be mapped.
++ * These queues are expected to be mapped to different Rx rings
++ * which are assigned to different cores using IRQ affinity configuration.
++ */
++ for_each_cpu(cpu, &edma_rps_cpumask) {
++ q_map[map_len] = q_off + (cpu * EDMA_MAX_PRI_PER_CORE);
++ map_len++;
++ }
++
++ for (hash = 0; hash < PPE_QUEUE_HASH_NUM; hash++) {
++ ret = ppe_edma_queue_offset_config(edma_ctx->ppe_dev,
++ PPE_QUEUE_CLASS_HASH, hash, q_map[idx]);
++ if (ret)
++ return ret;
++
++ pr_debug("profile_id: %u, hash: %u, q_off: %u\n",
++ EDMA_CPU_PORT_PROFILE_ID, hash, q_map[idx]);
++ idx = (idx + 1) % map_len;
++ }
++
++ return 0;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+@@ -0,0 +1,48 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_CFG_RX__
++#define __EDMA_CFG_RX__
++
++/* SKB payload size used in page mode */
++#define EDMA_RX_PAGE_MODE_SKB_SIZE 256
++
++/* Rx flow control X-OFF default value */
++#define EDMA_RX_FC_XOFF_DEF 32
++
++/* Rx flow control X-ON default value */
++#define EDMA_RX_FC_XON_DEF 64
++
++/* Rx AC flow control original threshold */
++#define EDMA_RX_AC_FC_THRE_ORIG 0x190
++
++/* Rx AC flow control default threshold */
++#define EDMA_RX_AC_FC_THRES_DEF 0x104
++/* Rx mitigation timer's default value in microseconds */
++#define EDMA_RX_MITIGATION_TIMER_DEF 25
++
++/* Rx mitigation packet count's default value */
++#define EDMA_RX_MITIGATION_PKT_CNT_DEF 16
++
++/* Default bitmap of cores for RPS to ARM cores */
++#define EDMA_RX_DEFAULT_BITMAP ((1 << EDMA_MAX_CORE) - 1)
++
++int edma_cfg_rx_rings(void);
++int edma_cfg_rx_rings_alloc(void);
++void edma_cfg_rx_ring_mappings(void);
++void edma_cfg_rx_rings_cleanup(void);
++void edma_cfg_rx_disable_interrupts(void);
++void edma_cfg_rx_enable_interrupts(void);
++void edma_cfg_rx_napi_disable(void);
++void edma_cfg_rx_napi_enable(void);
++void edma_cfg_rx_napi_delete(void);
++void edma_cfg_rx_napi_add(void);
++void edma_cfg_rx_mapping(void);
++void edma_cfg_rx_rings_enable(void);
++void edma_cfg_rx_rings_disable(void);
++void edma_cfg_rx_buff_size_setup(void);
++int edma_cfg_rx_rps_hash_map(void);
++int edma_cfg_rx_rps(struct ctl_table *table, int write,
++ void *buffer, size_t *lenp, loff_t *ppos);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -12,12 +12,39 @@
+ #include <linux/printk.h>
+
+ #include "edma.h"
++#include "edma_cfg_rx.h"
+ #include "edma_port.h"
+ #include "ppe_regs.h"
+
+ /* Number of netdev queues. */
+ #define EDMA_NETDEV_QUEUE_NUM 4
+
++static int edma_port_stats_alloc(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ if (!port_priv)
++ return -EINVAL;
++
++ /* Allocate per-cpu stats memory. */
++ port_priv->pcpu_stats.rx_stats =
++ netdev_alloc_pcpu_stats(struct edma_port_rx_stats);
++ if (!port_priv->pcpu_stats.rx_stats) {
++ netdev_err(netdev, "Per-cpu EDMA Rx stats alloc failed for %s\n",
++ netdev->name);
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void edma_port_stats_free(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ free_percpu(port_priv->pcpu_stats.rx_stats);
++}
++
+ static u16 __maybe_unused edma_port_select_queue(__maybe_unused struct net_device *netdev,
+ __maybe_unused struct sk_buff *skb,
+ __maybe_unused struct net_device *sb_dev)
+@@ -172,6 +199,7 @@ void edma_port_destroy(struct ppe_port *
+ int port_id = port->port_id;
+ struct net_device *netdev = edma_ctx->netdev_arr[port_id - 1];
+
++ edma_port_stats_free(netdev);
+ unregister_netdev(netdev);
+ free_netdev(netdev);
+ ppe_port_phylink_destroy(port);
+@@ -232,6 +260,13 @@ int edma_port_setup(struct ppe_port *por
+ port_id, netdev->dev_addr);
+ }
+
++ /* Allocate memory for EDMA port statistics. */
++ ret = edma_port_stats_alloc(netdev);
++ if (ret) {
++ netdev_dbg(netdev, "EDMA port stats alloc failed\n");
++ goto stats_alloc_fail;
++ }
++
+ netdev_dbg(netdev, "Configuring the port %s(qcom-id:%d)\n",
+ netdev->name, port_id);
+
+@@ -263,8 +298,10 @@ int edma_port_setup(struct ppe_port *por
+ register_netdev_fail:
+ ppe_port_phylink_destroy(port);
+ port_phylink_setup_fail:
+- free_netdev(netdev);
+ edma_ctx->netdev_arr[port_id - 1] = NULL;
++ edma_port_stats_free(netdev);
++stats_alloc_fail:
++ free_netdev(netdev);
+
+ return ret;
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.h
+@@ -15,14 +15,45 @@
+ | NETIF_F_TSO6)
+
+ /**
++ * struct edma_port_rx_stats - EDMA RX per CPU stats for the port.
++ * @rx_pkts: Number of Rx packets
++ * @rx_bytes: Number of Rx bytes
++ * @rx_drops: Number of Rx drops
++ * @rx_nr_frag_pkts: Number of Rx nr_frags packets
++ * @rx_fraglist_pkts: Number of Rx fraglist packets
++ * @rx_nr_frag_headroom_err: nr_frags headroom error packets
++ * @syncp: Synchronization pointer
++ */
++struct edma_port_rx_stats {
++ u64 rx_pkts;
++ u64 rx_bytes;
++ u64 rx_drops;
++ u64 rx_nr_frag_pkts;
++ u64 rx_fraglist_pkts;
++ u64 rx_nr_frag_headroom_err;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_port_pcpu_stats - EDMA per cpu stats data structure for the port.
++ * @rx_stats: Per CPU Rx statistics
++ */
++struct edma_port_pcpu_stats {
++ struct edma_port_rx_stats __percpu *rx_stats;
++};
++
++/**
+ * struct edma_port_priv - EDMA port priv structure.
+ * @ppe_port: Pointer to PPE port
+ * @netdev: Corresponding netdevice
++ * @pcpu_stats: Per CPU netdev statistics
++ * @txr_map: Tx ring per-core mapping
+ * @flags: Feature flags
+ */
+ struct edma_port_priv {
+ struct ppe_port *ppe_port;
+ struct net_device *netdev;
++ struct edma_port_pcpu_stats pcpu_stats;
+ unsigned long flags;
+ };
+
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_rx.c
+@@ -0,0 +1,622 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Provides APIs to alloc Rx Buffers, reap the buffers, receive and
++ * process linear and Scatter Gather packets.
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/etherdevice.h>
++#include <linux/irqreturn.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/platform_device.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++
++#include "edma.h"
++#include "edma_cfg_rx.h"
++#include "edma_port.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++static int edma_rx_alloc_buffer_list(struct edma_rxfill_ring *rxfill_ring, int alloc_count)
++{
++ struct edma_rxfill_stats *rxfill_stats = &rxfill_ring->rxfill_stats;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 rx_alloc_size = rxfill_ring->alloc_size;
++ struct regmap *regmap = ppe_dev->regmap;
++ bool page_mode = rxfill_ring->page_mode;
++ struct edma_rxfill_desc *rxfill_desc;
++ u32 buf_len = rxfill_ring->buf_len;
++ struct device *dev = ppe_dev->dev;
++ u16 prod_idx, start_idx;
++ u16 num_alloc = 0;
++ u32 reg;
++
++ prod_idx = rxfill_ring->prod_idx;
++ start_idx = prod_idx;
++
++ while (likely(alloc_count--)) {
++ dma_addr_t buff_addr;
++ struct sk_buff *skb;
++ struct page *pg;
++
++ rxfill_desc = EDMA_RXFILL_DESC(rxfill_ring, prod_idx);
++
++ skb = dev_alloc_skb(rx_alloc_size);
++ if (unlikely(!skb)) {
++ u64_stats_update_begin(&rxfill_stats->syncp);
++ ++rxfill_stats->alloc_failed;
++ u64_stats_update_end(&rxfill_stats->syncp);
++ break;
++ }
++
++ skb_reserve(skb, EDMA_RX_SKB_HEADROOM + NET_IP_ALIGN);
++
++ if (likely(!page_mode)) {
++ buff_addr = dma_map_single(dev, skb->data, rx_alloc_size, DMA_FROM_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "edma_context:%p Unable to dma for non page mode",
++ edma_ctx);
++ dev_kfree_skb_any(skb);
++ break;
++ }
++ } else {
++ pg = alloc_page(GFP_ATOMIC);
++ if (unlikely(!pg)) {
++ u64_stats_update_begin(&rxfill_stats->syncp);
++ ++rxfill_stats->page_alloc_failed;
++ u64_stats_update_end(&rxfill_stats->syncp);
++ dev_kfree_skb_any(skb);
++ dev_dbg(dev, "edma_context:%p Unable to allocate page",
++ edma_ctx);
++ break;
++ }
++
++ buff_addr = dma_map_page(dev, pg, 0, PAGE_SIZE, DMA_FROM_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "edma_context:%p Mapping error for page mode",
++ edma_ctx);
++ __free_page(pg);
++ dev_kfree_skb_any(skb);
++ break;
++ }
++
++ skb_fill_page_desc(skb, 0, pg, 0, PAGE_SIZE);
++ }
++
++ EDMA_RXFILL_BUFFER_ADDR_SET(rxfill_desc, buff_addr);
++
++ EDMA_RXFILL_OPAQUE_LO_SET(rxfill_desc, skb);
++#ifdef __LP64__
++ EDMA_RXFILL_OPAQUE_HI_SET(rxfill_desc, skb);
++#endif
++ EDMA_RXFILL_PACKET_LEN_SET(rxfill_desc,
++ (u32)(buf_len) & EDMA_RXFILL_BUF_SIZE_MASK);
++ prod_idx = (prod_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++ num_alloc++;
++ }
++
++ if (likely(num_alloc)) {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_PROD_IDX(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, prod_idx);
++ rxfill_ring->prod_idx = prod_idx;
++ }
++
++ return num_alloc;
++}
++
++/**
++ * edma_rx_alloc_buffer - EDMA Rx alloc buffer.
++ * @rxfill_ring: EDMA Rxfill ring
++ * @alloc_count: Number of rings to alloc
++ *
++ * Alloc Rx buffers for RxFill ring.
++ *
++ * Return the number of rings allocated.
++ */
++int edma_rx_alloc_buffer(struct edma_rxfill_ring *rxfill_ring, int alloc_count)
++{
++ return edma_rx_alloc_buffer_list(rxfill_ring, alloc_count);
++}
++
++/* Mark ip_summed appropriately in the skb as per the L3/L4 checksum
++ * status in descriptor.
++ */
++static void edma_rx_checksum_verify(struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ u8 pid = EDMA_RXDESC_PID_GET(rxdesc_pri);
++
++ skb_checksum_none_assert(skb);
++
++ if (likely(EDMA_RX_PID_IS_IPV4(pid))) {
++ if (likely(EDMA_RXDESC_L3CSUM_STATUS_GET(rxdesc_pri)) &&
++ likely(EDMA_RXDESC_L4CSUM_STATUS_GET(rxdesc_pri)))
++ skb->ip_summed = CHECKSUM_UNNECESSARY;
++ } else if (likely(EDMA_RX_PID_IS_IPV6(pid))) {
++ if (likely(EDMA_RXDESC_L4CSUM_STATUS_GET(rxdesc_pri)))
++ skb->ip_summed = CHECKSUM_UNNECESSARY;
++ }
++}
++
++static void edma_rx_process_last_segment(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ bool page_mode = rxdesc_ring->rxfill->page_mode;
++ struct edma_port_pcpu_stats *pcpu_stats;
++ struct edma_port_rx_stats *rx_stats;
++ struct edma_port_priv *port_dev;
++ struct sk_buff *skb_head;
++ struct net_device *dev;
++ u32 pkt_length;
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++
++ skb_head = rxdesc_ring->head;
++ dev = skb_head->dev;
++
++ /* Check Rx checksum offload status. */
++ if (likely(dev->features & NETIF_F_RXCSUM))
++ edma_rx_checksum_verify(rxdesc_pri, skb_head);
++
++ /* Get stats for the netdevice. */
++ port_dev = netdev_priv(dev);
++ pcpu_stats = &port_dev->pcpu_stats;
++ rx_stats = this_cpu_ptr(pcpu_stats->rx_stats);
++
++ if (unlikely(page_mode)) {
++ if (unlikely(!pskb_may_pull(skb_head, ETH_HLEN))) {
++ /* Discard the SKB that we have been building,
++ * in addition to the SKB linked to current descriptor.
++ */
++ dev_kfree_skb_any(skb_head);
++ rxdesc_ring->head = NULL;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = NULL;
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_nr_frag_headroom_err++;
++ u64_stats_update_end(&rx_stats->syncp);
++
++ return;
++ }
++ }
++
++ if (unlikely(!pskb_pull(skb_head, EDMA_RXDESC_DATA_OFFSET_GET(rxdesc_ring->pdesc_head)))) {
++ dev_kfree_skb_any(skb_head);
++ rxdesc_ring->head = NULL;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = NULL;
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_nr_frag_headroom_err++;
++ u64_stats_update_end(&rx_stats->syncp);
++
++ return;
++ }
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_pkts++;
++ rx_stats->rx_bytes += skb_head->len;
++ rx_stats->rx_nr_frag_pkts += (u64)page_mode;
++ rx_stats->rx_fraglist_pkts += (u64)(!page_mode);
++ u64_stats_update_end(&rx_stats->syncp);
++
++ pr_debug("edma_context:%p skb:%p Jumbo pkt_length:%u\n",
++ edma_ctx, skb_head, skb_head->len);
++
++ skb_head->protocol = eth_type_trans(skb_head, dev);
++
++ /* Send packet up the stack. */
++ if (dev->features & NETIF_F_GRO)
++ napi_gro_receive(&rxdesc_ring->napi, skb_head);
++ else
++ netif_receive_skb(skb_head);
++
++ rxdesc_ring->head = NULL;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = NULL;
++}
++
++static void edma_rx_handle_frag_list(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ u32 pkt_length;
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++ pr_debug("edma_context:%p skb:%p fragment pkt_length:%u\n",
++ edma_ctx, skb, pkt_length);
++
++ if (!(rxdesc_ring->head)) {
++ skb_put(skb, pkt_length);
++ rxdesc_ring->head = skb;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = rxdesc_pri;
++
++ return;
++ }
++
++ /* Append it to the fraglist of head if this is second frame
++ * If not second frame append to tail.
++ */
++ skb_put(skb, pkt_length);
++ if (!skb_has_frag_list(rxdesc_ring->head))
++ skb_shinfo(rxdesc_ring->head)->frag_list = skb;
++ else
++ rxdesc_ring->last->next = skb;
++
++ rxdesc_ring->last = skb;
++ rxdesc_ring->last->next = NULL;
++ rxdesc_ring->head->len += pkt_length;
++ rxdesc_ring->head->data_len += pkt_length;
++ rxdesc_ring->head->truesize += skb->truesize;
++
++ /* If there are more segments for this packet,
++ * then we have nothing to do. Otherwise process
++ * last segment and send packet to stack.
++ */
++ if (EDMA_RXDESC_MORE_BIT_GET(rxdesc_pri))
++ return;
++
++ edma_rx_process_last_segment(rxdesc_ring, rxdesc_pri, skb);
++}
++
++static void edma_rx_handle_nr_frags(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ skb_frag_t *frag = NULL;
++ u32 pkt_length;
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++ pr_debug("edma_context:%p skb:%p fragment pkt_length:%u\n",
++ edma_ctx, skb, pkt_length);
++
++ if (!(rxdesc_ring->head)) {
++ skb->len = pkt_length;
++ skb->data_len = pkt_length;
++ skb->truesize = SKB_TRUESIZE(PAGE_SIZE);
++ rxdesc_ring->head = skb;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = rxdesc_pri;
++
++ return;
++ }
++
++ frag = &skb_shinfo(skb)->frags[0];
++
++ /* Append current frag at correct index as nr_frag of parent. */
++ skb_add_rx_frag(rxdesc_ring->head, skb_shinfo(rxdesc_ring->head)->nr_frags,
++ skb_frag_page(frag), 0, pkt_length, PAGE_SIZE);
++ skb_shinfo(skb)->nr_frags = 0;
++
++ /* Free the SKB after we have appended its frag page to the head skb. */
++ dev_kfree_skb_any(skb);
++
++ /* If there are more segments for this packet,
++ * then we have nothing to do. Otherwise process
++ * last segment and send packet to stack.
++ */
++ if (EDMA_RXDESC_MORE_BIT_GET(rxdesc_pri))
++ return;
++
++ edma_rx_process_last_segment(rxdesc_ring, rxdesc_pri, skb);
++}
++
++static bool edma_rx_handle_linear_packets(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ bool page_mode = rxdesc_ring->rxfill->page_mode;
++ struct edma_port_pcpu_stats *pcpu_stats;
++ struct edma_port_rx_stats *rx_stats;
++ struct edma_port_priv *port_dev;
++ skb_frag_t *frag = NULL;
++ u32 pkt_length;
++
++ /* Get stats for the netdevice. */
++ port_dev = netdev_priv(skb->dev);
++ pcpu_stats = &port_dev->pcpu_stats;
++ rx_stats = this_cpu_ptr(pcpu_stats->rx_stats);
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++
++ if (likely(!page_mode)) {
++ skb_put(skb, pkt_length);
++ goto send_to_stack;
++ }
++
++ /* Handle linear packet in page mode. */
++ frag = &skb_shinfo(skb)->frags[0];
++ skb_add_rx_frag(skb, 0, skb_frag_page(frag), 0, pkt_length, PAGE_SIZE);
++
++ /* Pull ethernet header into SKB data area for header processing. */
++ if (unlikely(!pskb_may_pull(skb, ETH_HLEN))) {
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_nr_frag_headroom_err++;
++ u64_stats_update_end(&rx_stats->syncp);
++ dev_kfree_skb_any(skb);
++
++ return false;
++ }
++
++send_to_stack:
++
++ __skb_pull(skb, EDMA_RXDESC_DATA_OFFSET_GET(rxdesc_pri));
++
++ /* Check Rx checksum offload status. */
++ if (likely(skb->dev->features & NETIF_F_RXCSUM))
++ edma_rx_checksum_verify(rxdesc_pri, skb);
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_pkts++;
++ rx_stats->rx_bytes += pkt_length;
++ rx_stats->rx_nr_frag_pkts += (u64)page_mode;
++ u64_stats_update_end(&rx_stats->syncp);
++
++ skb->protocol = eth_type_trans(skb, skb->dev);
++ if (skb->dev->features & NETIF_F_GRO)
++ napi_gro_receive(&rxdesc_ring->napi, skb);
++ else
++ netif_receive_skb(skb);
++
++ netdev_dbg(skb->dev, "edma_context:%p, skb:%p pkt_length:%u\n",
++ edma_ctx, skb, skb->len);
++
++ return true;
++}
++
++static struct net_device *edma_rx_get_src_dev(struct edma_rxdesc_stats *rxdesc_stats,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ u32 src_info = EDMA_RXDESC_SRC_INFO_GET(rxdesc_pri);
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct net_device *ndev = NULL;
++ u8 src_port_num;
++
++ /* Check src_info. */
++ if (likely((src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK)
++ == EDMA_RXDESC_SRCINFO_TYPE_PORTID)) {
++ src_port_num = src_info & EDMA_RXDESC_PORTNUM_BITS;
++ } else {
++ if (net_ratelimit()) {
++ pr_warn("Invalid src info_type:0x%x. Drop skb:%p\n",
++ (src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK), skb);
++ }
++
++ u64_stats_update_begin(&rxdesc_stats->syncp);
++ ++rxdesc_stats->src_port_inval_type;
++ u64_stats_update_end(&rxdesc_stats->syncp);
++
++ return NULL;
++ }
++
++ /* Packet with PP source. */
++ if (likely(src_port_num <= hw_info->max_ports)) {
++ if (unlikely(src_port_num < EDMA_START_IFNUM)) {
++ if (net_ratelimit())
++ pr_warn("Port number error :%d. Drop skb:%p\n",
++ src_port_num, skb);
++
++ u64_stats_update_begin(&rxdesc_stats->syncp);
++ ++rxdesc_stats->src_port_inval;
++ u64_stats_update_end(&rxdesc_stats->syncp);
++
++ return NULL;
++ }
++
++ /* Get netdev for this port using the source port
++ * number as index into the netdev array. We need to
++ * subtract one since the indices start form '0' and
++ * port numbers start from '1'.
++ */
++ ndev = edma_ctx->netdev_arr[src_port_num - 1];
++ }
++
++ if (likely(ndev))
++ return ndev;
++
++ if (net_ratelimit())
++ pr_warn("Netdev Null src_info_type:0x%x src port num:%d Drop skb:%p\n",
++ (src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK),
++ src_port_num, skb);
++
++ u64_stats_update_begin(&rxdesc_stats->syncp);
++ ++rxdesc_stats->src_port_inval_netdev;
++ u64_stats_update_end(&rxdesc_stats->syncp);
++
++ return NULL;
++}
++
++static int edma_rx_reap(struct edma_rxdesc_ring *rxdesc_ring, int budget)
++{
++ struct edma_rxdesc_stats *rxdesc_stats = &rxdesc_ring->rxdesc_stats;
++ u32 alloc_size = rxdesc_ring->rxfill->alloc_size;
++ bool page_mode = rxdesc_ring->rxfill->page_mode;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_rxdesc_pri *next_rxdesc_pri;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct device *dev = ppe_dev->dev;
++ u32 prod_idx, cons_idx, end_idx;
++ u32 work_to_do, work_done = 0;
++ struct sk_buff *next_skb;
++ u32 work_leftover, reg;
++
++ /* Get Rx ring producer and consumer indices. */
++ cons_idx = rxdesc_ring->cons_idx;
++
++ if (likely(rxdesc_ring->work_leftover > EDMA_RX_MAX_PROCESS)) {
++ work_to_do = rxdesc_ring->work_leftover;
++ } else {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_PROD_IDX(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &prod_idx);
++ prod_idx = prod_idx & EDMA_RXDESC_PROD_IDX_MASK;
++ work_to_do = EDMA_DESC_AVAIL_COUNT(prod_idx,
++ cons_idx, EDMA_RX_RING_SIZE);
++ rxdesc_ring->work_leftover = work_to_do;
++ }
++
++ if (work_to_do > budget)
++ work_to_do = budget;
++
++ rxdesc_ring->work_leftover -= work_to_do;
++ end_idx = (cons_idx + work_to_do) & EDMA_RX_RING_SIZE_MASK;
++ next_rxdesc_pri = EDMA_RXDESC_PRI_DESC(rxdesc_ring, cons_idx);
++
++ /* Get opaque from RXDESC. */
++ next_skb = (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(next_rxdesc_pri);
++
++ work_leftover = work_to_do & (EDMA_RX_MAX_PROCESS - 1);
++ while (likely(work_to_do--)) {
++ struct edma_rxdesc_pri *rxdesc_pri;
++ struct net_device *ndev;
++ struct sk_buff *skb;
++ dma_addr_t dma_addr;
++
++ skb = next_skb;
++ rxdesc_pri = next_rxdesc_pri;
++ dma_addr = EDMA_RXDESC_BUFFER_ADDR_GET(rxdesc_pri);
++
++ if (!page_mode)
++ dma_unmap_single(dev, dma_addr, alloc_size,
++ DMA_TO_DEVICE);
++ else
++ dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_TO_DEVICE);
++
++ /* Update consumer index. */
++ cons_idx = (cons_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++
++ /* Get the next Rx descriptor. */
++ next_rxdesc_pri = EDMA_RXDESC_PRI_DESC(rxdesc_ring, cons_idx);
++
++ /* Handle linear packets or initial segments first. */
++ if (likely(!(rxdesc_ring->head))) {
++ ndev = edma_rx_get_src_dev(rxdesc_stats, rxdesc_pri, skb);
++ if (unlikely(!ndev)) {
++ dev_kfree_skb_any(skb);
++ goto next_rx_desc;
++ }
++
++ /* Update skb fields for head skb. */
++ skb->dev = ndev;
++ skb->skb_iif = ndev->ifindex;
++
++ /* Handle linear packets. */
++ if (likely(!EDMA_RXDESC_MORE_BIT_GET(rxdesc_pri))) {
++ next_skb =
++ (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(next_rxdesc_pri);
++
++ if (unlikely(!
++ edma_rx_handle_linear_packets(rxdesc_ring,
++ rxdesc_pri, skb)))
++ dev_kfree_skb_any(skb);
++
++ goto next_rx_desc;
++ }
++ }
++
++ next_skb = (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(next_rxdesc_pri);
++
++ /* Handle scatter frame processing for first/middle/last segments. */
++ page_mode ? edma_rx_handle_nr_frags(rxdesc_ring, rxdesc_pri, skb) :
++ edma_rx_handle_frag_list(rxdesc_ring, rxdesc_pri, skb);
++
++next_rx_desc:
++ /* Update work done. */
++ work_done++;
++
++ /* Check if we can refill EDMA_RX_MAX_PROCESS worth buffers,
++ * if yes, refill and update index before continuing.
++ */
++ if (unlikely(!(work_done & (EDMA_RX_MAX_PROCESS - 1)))) {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CONS_IDX(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, cons_idx);
++ rxdesc_ring->cons_idx = cons_idx;
++ edma_rx_alloc_buffer_list(rxdesc_ring->rxfill, EDMA_RX_MAX_PROCESS);
++ }
++ }
++
++ /* Check if we need to refill and update
++ * index for any buffers before exit.
++ */
++ if (unlikely(work_leftover)) {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CONS_IDX(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, cons_idx);
++ rxdesc_ring->cons_idx = cons_idx;
++ edma_rx_alloc_buffer_list(rxdesc_ring->rxfill, work_leftover);
++ }
++
++ return work_done;
++}
++
++/**
++ * edma_rx_napi_poll - EDMA Rx napi poll.
++ * @napi: NAPI structure
++ * @budget: Rx NAPI budget
++ *
++ * EDMA RX NAPI handler to handle the NAPI poll.
++ *
++ * Return the number of packets processed.
++ */
++int edma_rx_napi_poll(struct napi_struct *napi, int budget)
++{
++ struct edma_rxdesc_ring *rxdesc_ring = (struct edma_rxdesc_ring *)napi;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ int work_done = 0;
++ u32 status, reg;
++
++ do {
++ work_done += edma_rx_reap(rxdesc_ring, budget - work_done);
++ if (likely(work_done >= budget))
++ return work_done;
++
++ /* Check if there are more packets to process. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_STAT(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &status);
++ status = status & EDMA_RXDESC_RING_INT_STATUS_MASK;
++ } while (likely(status));
++
++ napi_complete(napi);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_rx);
++
++ return work_done;
++}
++
++/**
++ * edma_rx_handle_irq - EDMA Rx handle irq.
++ * @irq: Interrupt to handle
++ * @ctx: Context
++ *
++ * Process RX IRQ and schedule NAPI.
++ *
++ * Return IRQ_HANDLED(1) on success.
++ */
++irqreturn_t edma_rx_handle_irq(int irq, void *ctx)
++{
++ struct edma_rxdesc_ring *rxdesc_ring = (struct edma_rxdesc_ring *)ctx;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ if (likely(napi_schedule_prep(&rxdesc_ring->napi))) {
++ /* Disable RxDesc interrupt. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_DISABLE);
++ __napi_schedule(&rxdesc_ring->napi);
++ }
++
++ return IRQ_HANDLED;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_rx.h
+@@ -0,0 +1,287 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_RX__
++#define __EDMA_RX__
++
++#include <linux/netdevice.h>
++
++#define EDMA_RXFILL_RING_PER_CORE_MAX 1
++#define EDMA_RXDESC_RING_PER_CORE_MAX 1
++
++/* Max Rx processing without replenishing RxFill ring. */
++#define EDMA_RX_MAX_PROCESS 32
++
++#define EDMA_RX_SKB_HEADROOM 128
++#define EDMA_RX_QUEUE_START 0
++#define EDMA_RX_BUFFER_SIZE 1984
++#define EDMA_MAX_CORE 4
++
++#define EDMA_GET_DESC(R, i, type) (&(((type *)((R)->desc))[(i)]))
++#define EDMA_GET_PDESC(R, i, type) (&(((type *)((R)->pdesc))[(i)]))
++#define EDMA_GET_SDESC(R, i, type) (&(((type *)((R)->sdesc))[(i)]))
++#define EDMA_RXFILL_DESC(R, i) EDMA_GET_DESC(R, i, \
++ struct edma_rxfill_desc)
++#define EDMA_RXDESC_PRI_DESC(R, i) EDMA_GET_PDESC(R, i, \
++ struct edma_rxdesc_pri)
++#define EDMA_RXDESC_SEC_DESC(R, i) EDMA_GET_SDESC(R, i, \
++ struct edma_rxdesc_sec)
++
++#define EDMA_RX_RING_SIZE 2048
++
++#define EDMA_RX_RING_SIZE_MASK (EDMA_RX_RING_SIZE - 1)
++#define EDMA_RX_RING_ID_MASK 0x1F
++
++#define EDMA_MAX_PRI_PER_CORE 8
++#define EDMA_RX_PID_IPV4_MAX 0x3
++#define EDMA_RX_PID_IPV6 0x4
++#define EDMA_RX_PID_IS_IPV4(pid) (!((pid) & (~EDMA_RX_PID_IPV4_MAX)))
++#define EDMA_RX_PID_IS_IPV6(pid) (!(!((pid) & EDMA_RX_PID_IPV6)))
++
++#define EDMA_RXDESC_BUFFER_ADDR_GET(desc) \
++ ((u32)(le32_to_cpu((__force __le32)((desc)->word0))))
++#define EDMA_RXDESC_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ ((uintptr_t)((u64)((desc)->word2) | \
++ ((u64)((desc)->word3) << 0x20))); })
++
++#define EDMA_RXDESC_SRCINFO_TYPE_PORTID 0x2000
++#define EDMA_RXDESC_SRCINFO_TYPE_MASK 0xF000
++#define EDMA_RXDESC_L3CSUM_STATUS_MASK BIT(13)
++#define EDMA_RXDESC_L4CSUM_STATUS_MASK BIT(12)
++#define EDMA_RXDESC_PORTNUM_BITS 0x0FFF
++
++#define EDMA_RXDESC_PACKET_LEN_MASK 0x3FFFF
++#define EDMA_RXDESC_PACKET_LEN_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ ((le32_to_cpu((__force __le32)((desc)->word5))) & \
++ EDMA_RXDESC_PACKET_LEN_MASK); })
++
++#define EDMA_RXDESC_MORE_BIT_MASK 0x40000000
++#define EDMA_RXDESC_MORE_BIT_GET(desc) ((le32_to_cpu((__force __le32)((desc)->word1))) & \
++ EDMA_RXDESC_MORE_BIT_MASK)
++#define EDMA_RXDESC_SRC_DST_INFO_GET(desc) \
++ ((u32)((le32_to_cpu((__force __le32)((desc)->word4)))))
++
++#define EDMA_RXDESC_L3_OFFSET_MASK GENMASK(23, 16)
++#define EDMA_RXDESC_L3_OFFSET_GET(desc) FIELD_GET(EDMA_RXDESC_L3_OFFSET_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word7)))
++
++#define EDMA_RXDESC_PID_MASK GENMASK(15, 12)
++#define EDMA_RXDESC_PID_GET(desc) FIELD_GET(EDMA_RXDESC_PID_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word7)))
++
++#define EDMA_RXDESC_DST_INFO_MASK GENMASK(31, 16)
++#define EDMA_RXDESC_DST_INFO_GET(desc) FIELD_GET(EDMA_RXDESC_DST_INFO_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word4)))
++
++#define EDMA_RXDESC_SRC_INFO_MASK GENMASK(15, 0)
++#define EDMA_RXDESC_SRC_INFO_GET(desc) FIELD_GET(EDMA_RXDESC_SRC_INFO_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word4)))
++
++#define EDMA_RXDESC_PORT_ID_MASK GENMASK(11, 0)
++#define EDMA_RXDESC_PORT_ID_GET(x) FIELD_GET(EDMA_RXDESC_PORT_ID_MASK, x)
++
++#define EDMA_RXDESC_SRC_PORT_ID_GET(desc) (EDMA_RXDESC_PORT_ID_GET \
++ (EDMA_RXDESC_SRC_INFO_GET(desc)))
++#define EDMA_RXDESC_DST_PORT_ID_GET(desc) (EDMA_RXDESC_PORT_ID_GET \
++ (EDMA_RXDESC_DST_INFO_GET(desc)))
++
++#define EDMA_RXDESC_DST_PORT (0x2 << EDMA_RXDESC_PID_SHIFT)
++
++#define EDMA_RXDESC_L3CSUM_STATUS_GET(desc) FIELD_GET(EDMA_RXDESC_L3CSUM_STATUS_MASK, \
++ le32_to_cpu((__force __le32)(desc)->word6))
++#define EDMA_RXDESC_L4CSUM_STATUS_GET(desc) FIELD_GET(EDMA_RXDESC_L4CSUM_STATUS_MASK, \
++ le32_to_cpu((__force __le32)(desc)->word6))
++
++#define EDMA_RXDESC_DATA_OFFSET_MASK GENMASK(11, 0)
++#define EDMA_RXDESC_DATA_OFFSET_GET(desc) FIELD_GET(EDMA_RXDESC_DATA_OFFSET_MASK, \
++ le32_to_cpu((__force __le32)(desc)->word6))
++
++#define EDMA_RXFILL_BUF_SIZE_MASK 0xFFFF
++#define EDMA_RXFILL_BUF_SIZE_SHIFT 16
++
++/* Opaque values are not accessed by the EDMA HW,
++ * so endianness conversion is not needed.
++ */
++
++#define EDMA_RXFILL_OPAQUE_LO_SET(desc, ptr) (((desc)->word2) = \
++ (u32)(uintptr_t)(ptr))
++#ifdef __LP64__
++#define EDMA_RXFILL_OPAQUE_HI_SET(desc, ptr) (((desc)->word3) = \
++ (u32)((u64)(ptr) >> 0x20))
++#endif
++
++#define EDMA_RXFILL_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ ((uintptr_t)((u64)((desc)->word2) | \
++ ((u64)((desc)->word3) << 0x20))); })
++
++#define EDMA_RXFILL_PACKET_LEN_SET(desc, len) { \
++ (((desc)->word1) = (u32)((((u32)len) << EDMA_RXFILL_BUF_SIZE_SHIFT) & \
++ 0xFFFF0000)); \
++}
++
++#define EDMA_RXFILL_BUFFER_ADDR_SET(desc, addr) (((desc)->word0) = (u32)(addr))
++
++/* Opaque values are set in word2 and word3, they are not accessed by the EDMA HW,
++ * so endianness conversion is not needed.
++ */
++#define EDMA_RXFILL_ENDIAN_SET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ cpu_to_le32s(&((desc)->word0)); \
++ cpu_to_le32s(&((desc)->word1)); \
++})
++
++/* RX DESC size shift to obtain index from descriptor pointer. */
++#define EDMA_RXDESC_SIZE_SHIFT 5
++
++/**
++ * struct edma_rxdesc_stats - RX descriptor ring stats.
++ * @src_port_inval: Invalid source port number
++ * @src_port_inval_type: Source type is not PORT ID
++ * @src_port_inval_netdev: Invalid net device for the source port
++ * @syncp: Synchronization pointer
++ */
++struct edma_rxdesc_stats {
++ u64 src_port_inval;
++ u64 src_port_inval_type;
++ u64 src_port_inval_netdev;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_rxfill_stats - Rx fill descriptor ring stats.
++ * @alloc_failed: Buffer allocation failure count
++ * @page_alloc_failed: Page allocation failure count for page mode
++ * @syncp: Synchronization pointer
++ */
++struct edma_rxfill_stats {
++ u64 alloc_failed;
++ u64 page_alloc_failed;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_rxdesc_pri - Rx descriptor.
++ * @word0: Buffer address
++ * @word1: More bit, priority bit, service code
++ * @word2: Opaque low bits
++ * @word3: Opaque high bits
++ * @word4: Destination and source information
++ * @word5: WiFi QoS, data length
++ * @word6: Hash value, check sum status
++ * @word7: DSCP, packet offsets
++ */
++struct edma_rxdesc_pri {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++ /**
++ * struct edma_rxdesc_sec - Rx secondary descriptor.
++ * @word0: Timestamp
++ * @word1: Secondary checksum status
++ * @word2: QoS tag
++ * @word3: Flow index details
++ * @word4: Secondary packet offsets
++ * @word5: Multicast bit, checksum
++ * @word6: SVLAN, CVLAN
++ * @word7: Secondary SVLAN, CVLAN
++ */
++struct edma_rxdesc_sec {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++/**
++ * struct edma_rxfill_desc - RxFill descriptor.
++ * @word0: Buffer address
++ * @word1: Buffer size
++ * @word2: Opaque low bits
++ * @word3: Opaque high bits
++ */
++struct edma_rxfill_desc {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++};
++
++/**
++ * struct edma_rxfill_ring - RxFill ring
++ * @ring_id: RxFill ring number
++ * @count: Number of descriptors in the ring
++ * @prod_idx: Ring producer index
++ * @alloc_size: Buffer size to allocate
++ * @desc: Descriptor ring virtual address
++ * @dma: Descriptor ring physical address
++ * @buf_len: Buffer length for rxfill descriptor
++ * @page_mode: Page mode for Rx processing
++ * @rx_fill_stats: Rx fill ring statistics
++ */
++struct edma_rxfill_ring {
++ u32 ring_id;
++ u32 count;
++ u32 prod_idx;
++ u32 alloc_size;
++ struct edma_rxfill_desc *desc;
++ dma_addr_t dma;
++ u32 buf_len;
++ bool page_mode;
++ struct edma_rxfill_stats rxfill_stats;
++};
++
++/**
++ * struct edma_rxdesc_ring - RxDesc ring
++ * @napi: Pointer to napi
++ * @ring_id: Rxdesc ring number
++ * @count: Number of descriptors in the ring
++ * @work_leftover: Leftover descriptors to be processed
++ * @cons_idx: Ring consumer index
++ * @pdesc: Primary descriptor ring virtual address
++ * @pdesc_head: Primary descriptor head in case of scatter-gather frame
++ * @sdesc: Secondary descriptor ring virtual address
++ * @rxdesc_stats: Rx descriptor ring statistics
++ * @rxfill: RxFill ring used
++ * @napi_added: Flag to indicate NAPI add status
++ * @pdma: Primary descriptor ring physical address
++ * @sdma: Secondary descriptor ring physical address
++ * @head: Head of the skb list in case of scatter-gather frame
++ * @last: Last skb of the skb list in case of scatter-gather frame
++ */
++struct edma_rxdesc_ring {
++ struct napi_struct napi;
++ u32 ring_id;
++ u32 count;
++ u32 work_leftover;
++ u32 cons_idx;
++ struct edma_rxdesc_pri *pdesc;
++ struct edma_rxdesc_pri *pdesc_head;
++ struct edma_rxdesc_sec *sdesc;
++ struct edma_rxdesc_stats rxdesc_stats;
++ struct edma_rxfill_ring *rxfill;
++ bool napi_added;
++ dma_addr_t pdma;
++ dma_addr_t sdma;
++ struct sk_buff *head;
++ struct sk_buff *last;
++};
++
++irqreturn_t edma_rx_handle_irq(int irq, void *ctx);
++int edma_rx_alloc_buffer(struct edma_rxfill_ring *rxfill_ring, int alloc_count);
++int edma_rx_napi_poll(struct napi_struct *napi, int budget);
++#endif
--- /dev/null
+From 339d3a5365f150a78ed405684e379fee3acdbe90 Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:26:29 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add Tx Ethernet DMA support
+
+Add Tx queues, rings, descriptors configurations and
+DMA support for the EDMA.
+
+Change-Id: Idfb0e1fe5ac494d614097d6c97dd15d63bbce8e6
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 97 ++-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 7 +
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.c | 648 ++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.h | 28 +
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 136 +++
+ drivers/net/ethernet/qualcomm/ppe/edma_port.h | 35 +
+ drivers/net/ethernet/qualcomm/ppe/edma_tx.c | 808 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_tx.h | 302 +++++++
+ 9 files changed, 2055 insertions(+), 8 deletions(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_tx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_tx.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_cfg_rx.o edma_port.o edma_rx.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_port.o edma_rx.o edma_tx.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -18,6 +18,7 @@
+ #include <linux/reset.h>
+
+ #include "edma.h"
++#include "edma_cfg_tx.h"
+ #include "edma_cfg_rx.h"
+ #include "ppe_regs.h"
+
+@@ -25,6 +26,7 @@
+
+ /* Global EDMA context. */
+ struct edma_context *edma_ctx;
++static char **edma_txcmpl_irq_name;
+ static char **edma_rxdesc_irq_name;
+
+ /* Module params. */
+@@ -192,22 +194,59 @@ static int edma_configure_ucast_prio_map
+ static int edma_irq_register(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
+ struct edma_ring_info *rx = hw_info->rx;
+ int ret;
+ u32 i;
+
++ /* Request IRQ for TXCMPL rings. */
++ edma_txcmpl_irq_name = kzalloc((sizeof(char *) * txcmpl->num_rings), GFP_KERNEL);
++ if (!edma_txcmpl_irq_name)
++ return -ENOMEM;
++
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ edma_txcmpl_irq_name[i] = kzalloc((sizeof(char *) * EDMA_IRQ_NAME_SIZE),
++ GFP_KERNEL);
++ if (!edma_txcmpl_irq_name[i]) {
++ ret = -ENOMEM;
++ goto txcmpl_ring_irq_name_alloc_fail;
++ }
++
++ snprintf(edma_txcmpl_irq_name[i], EDMA_IRQ_NAME_SIZE, "edma_txcmpl_%d",
++ txcmpl->ring_start + i);
++
++ irq_set_status_flags(edma_ctx->intr_info.intr_txcmpl[i], IRQ_DISABLE_UNLAZY);
++
++ ret = request_irq(edma_ctx->intr_info.intr_txcmpl[i],
++ edma_tx_handle_irq, IRQF_SHARED,
++ edma_txcmpl_irq_name[i],
++ (void *)&edma_ctx->txcmpl_rings[i]);
++ if (ret) {
++ pr_err("TXCMPL ring IRQ:%d request %d failed\n",
++ edma_ctx->intr_info.intr_txcmpl[i], i);
++ goto txcmpl_ring_intr_req_fail;
++ }
++
++ pr_debug("TXCMPL ring: %d IRQ:%d request success: %s\n",
++ txcmpl->ring_start + i,
++ edma_ctx->intr_info.intr_txcmpl[i],
++ edma_txcmpl_irq_name[i]);
++ }
++
+ /* Request IRQ for RXDESC rings. */
+ edma_rxdesc_irq_name = kzalloc((sizeof(char *) * rx->num_rings),
+ GFP_KERNEL);
+- if (!edma_rxdesc_irq_name)
+- return -ENOMEM;
++ if (!edma_rxdesc_irq_name) {
++ ret = -ENOMEM;
++ goto rxdesc_irq_name_alloc_fail;
++ }
+
+ for (i = 0; i < rx->num_rings; i++) {
+ edma_rxdesc_irq_name[i] = kzalloc((sizeof(char *) * EDMA_IRQ_NAME_SIZE),
+ GFP_KERNEL);
+ if (!edma_rxdesc_irq_name[i]) {
+ ret = -ENOMEM;
+- goto rxdesc_irq_name_alloc_fail;
++ goto rxdesc_ring_irq_name_alloc_fail;
+ }
+
+ snprintf(edma_rxdesc_irq_name[i], 20, "edma_rxdesc_%d",
+@@ -236,8 +275,19 @@ static int edma_irq_register(void)
+ rx_desc_ring_intr_req_fail:
+ for (i = 0; i < rx->num_rings; i++)
+ kfree(edma_rxdesc_irq_name[i]);
+-rxdesc_irq_name_alloc_fail:
++rxdesc_ring_irq_name_alloc_fail:
+ kfree(edma_rxdesc_irq_name);
++rxdesc_irq_name_alloc_fail:
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_txcmpl[i]);
++ free_irq(edma_ctx->intr_info.intr_txcmpl[i],
++ (void *)&edma_ctx->txcmpl_rings[i]);
++ }
++txcmpl_ring_intr_req_fail:
++ for (i = 0; i < txcmpl->num_rings; i++)
++ kfree(edma_txcmpl_irq_name[i]);
++txcmpl_ring_irq_name_alloc_fail:
++ kfree(edma_txcmpl_irq_name);
+
+ return ret;
+ }
+@@ -326,12 +376,22 @@ static int edma_irq_init(void)
+
+ static int edma_alloc_rings(void)
+ {
++ if (edma_cfg_tx_rings_alloc()) {
++ pr_err("Error in allocating Tx rings\n");
++ return -ENOMEM;
++ }
++
+ if (edma_cfg_rx_rings_alloc()) {
+ pr_err("Error in allocating Rx rings\n");
+- return -ENOMEM;
++ goto rx_rings_alloc_fail;
+ }
+
+ return 0;
++
++rx_rings_alloc_fail:
++ edma_cfg_tx_rings_cleanup();
++
++ return -ENOMEM;
+ }
+
+ static int edma_hw_reset(void)
+@@ -389,7 +449,7 @@ static int edma_hw_configure(void)
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
+ struct regmap *regmap = ppe_dev->regmap;
+- u32 data, reg;
++ u32 data, reg, i;
+ int ret;
+
+ reg = EDMA_BASE_OFFSET + EDMA_REG_MAS_CTRL_ADDR;
+@@ -439,11 +499,17 @@ static int edma_hw_configure(void)
+ }
+
+ /* Disable interrupts. */
++ for (i = 1; i <= hw_info->max_ports; i++)
++ edma_cfg_tx_disable_interrupts(i);
++
+ edma_cfg_rx_disable_interrupts();
+
+ edma_cfg_rx_rings_disable();
+
+ edma_cfg_rx_ring_mappings();
++ edma_cfg_tx_ring_mappings();
++
++ edma_cfg_tx_rings();
+
+ ret = edma_cfg_rx_rings();
+ if (ret) {
+@@ -520,6 +586,7 @@ configure_ucast_prio_map_tbl_failed:
+ edma_cfg_rx_napi_delete();
+ edma_cfg_rx_rings_disable();
+ edma_cfg_rx_rings_failed:
++ edma_cfg_tx_rings_cleanup();
+ edma_cfg_rx_rings_cleanup();
+ edma_alloc_rings_failed:
+ free_netdev(edma_ctx->dummy_dev);
+@@ -538,13 +605,27 @@ dummy_dev_alloc_failed:
+ void edma_destroy(struct ppe_device *ppe_dev)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
+ struct edma_ring_info *rx = hw_info->rx;
+ u32 i;
+
+ /* Disable interrupts. */
++ for (i = 1; i <= hw_info->max_ports; i++)
++ edma_cfg_tx_disable_interrupts(i);
++
+ edma_cfg_rx_disable_interrupts();
+
+- /* Free IRQ for RXDESC rings. */
++ /* Free IRQ for TXCMPL rings. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_txcmpl[i]);
++
++ free_irq(edma_ctx->intr_info.intr_txcmpl[i],
++ (void *)&edma_ctx->txcmpl_rings[i]);
++ kfree(edma_txcmpl_irq_name[i]);
++ }
++ kfree(edma_txcmpl_irq_name);
++
++ /* Free IRQ for RXDESC rings */
+ for (i = 0; i < rx->num_rings; i++) {
+ synchronize_irq(edma_ctx->intr_info.intr_rx[i]);
+ free_irq(edma_ctx->intr_info.intr_rx[i],
+@@ -560,6 +641,7 @@ void edma_destroy(struct ppe_device *ppe
+ edma_cfg_rx_napi_delete();
+ edma_cfg_rx_rings_disable();
+ edma_cfg_rx_rings_cleanup();
++ edma_cfg_tx_rings_cleanup();
+
+ free_netdev(edma_ctx->dummy_dev);
+ kfree(edma_ctx->netdev_arr);
+@@ -585,6 +667,7 @@ int edma_setup(struct ppe_device *ppe_de
+ edma_ctx->hw_info = &ipq9574_hw_info;
+ edma_ctx->ppe_dev = ppe_dev;
+ edma_ctx->rx_buf_size = rx_buff_size;
++ edma_ctx->tx_requeue_stop = false;
+
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -7,6 +7,7 @@
+
+ #include "ppe_config.h"
+ #include "edma_rx.h"
++#include "edma_tx.h"
+
+ /* One clock cycle = 1/(EDMA clock frequency in Mhz) micro seconds.
+ *
+@@ -104,8 +105,11 @@ struct edma_intr_info {
+ * @intr_info: EDMA Interrupt info
+ * @rxfill_rings: Rx fill Rings, SW is producer
+ * @rx_rings: Rx Desc Rings, SW is consumer
++ * @tx_rings: Tx Descriptor Ring, SW is producer
++ * @txcmpl_rings: Tx complete Ring, SW is consumer
+ * @rx_page_mode: Page mode enabled or disabled
+ * @rx_buf_size: Rx buffer size for Jumbo MRU
++ * @tx_requeue_stop: Tx requeue stop enabled or disabled
+ */
+ struct edma_context {
+ struct net_device **netdev_arr;
+@@ -115,8 +119,11 @@ struct edma_context {
+ struct edma_intr_info intr_info;
+ struct edma_rxfill_ring *rxfill_rings;
+ struct edma_rxdesc_ring *rx_rings;
++ struct edma_txdesc_ring *tx_rings;
++ struct edma_txcmpl_ring *txcmpl_rings;
+ u32 rx_page_mode;
+ u32 rx_buf_size;
++ bool tx_requeue_stop;
+ };
+
+ /* Global EDMA context */
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
+@@ -0,0 +1,648 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Configure rings, Buffers and NAPI for transmit path along with
++ * providing APIs to enable, disable, clean and map the Tx rings.
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++#include <linux/skbuff.h>
++
++#include "edma.h"
++#include "edma_cfg_tx.h"
++#include "edma_port.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++static void edma_cfg_txcmpl_ring_cleanup(struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Free any buffers assigned to any descriptors. */
++ edma_tx_complete(EDMA_TX_RING_SIZE - 1, txcmpl_ring);
++
++ /* Free TxCmpl ring descriptors. */
++ dma_free_coherent(dev, sizeof(struct edma_txcmpl_desc)
++ * txcmpl_ring->count, txcmpl_ring->desc,
++ txcmpl_ring->dma);
++ txcmpl_ring->desc = NULL;
++ txcmpl_ring->dma = (dma_addr_t)0;
++}
++
++static int edma_cfg_txcmpl_ring_setup(struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Allocate RxFill ring descriptors. */
++ txcmpl_ring->desc = dma_alloc_coherent(dev, sizeof(struct edma_txcmpl_desc)
++ * txcmpl_ring->count,
++ &txcmpl_ring->dma,
++ GFP_KERNEL | __GFP_ZERO);
++
++ if (unlikely(!txcmpl_ring->desc))
++ return -ENOMEM;
++
++ return 0;
++}
++
++static void edma_cfg_tx_desc_ring_cleanup(struct edma_txdesc_ring *txdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_pri *txdesc = NULL;
++ struct device *dev = ppe_dev->dev;
++ u32 prod_idx, cons_idx, data, reg;
++ struct sk_buff *skb = NULL;
++
++ /* Free any buffers assigned to any descriptors. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_PROD_IDX(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ prod_idx = data & EDMA_TXDESC_PROD_IDX_MASK;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CONS_IDX(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ cons_idx = data & EDMA_TXDESC_CONS_IDX_MASK;
++
++ /* Walk active list, obtain skb from descriptor and free it. */
++ while (cons_idx != prod_idx) {
++ txdesc = EDMA_TXDESC_PRI_DESC(txdesc_ring, cons_idx);
++ skb = (struct sk_buff *)EDMA_TXDESC_OPAQUE_GET(txdesc);
++ dev_kfree_skb_any(skb);
++
++ cons_idx = ((cons_idx + 1) & EDMA_TX_RING_SIZE_MASK);
++ }
++
++ /* Free Tx ring descriptors. */
++ dma_free_coherent(dev, (sizeof(struct edma_txdesc_pri)
++ * txdesc_ring->count),
++ txdesc_ring->pdesc,
++ txdesc_ring->pdma);
++ txdesc_ring->pdesc = NULL;
++ txdesc_ring->pdma = (dma_addr_t)0;
++
++ /* Free any buffers assigned to any secondary descriptors. */
++ dma_free_coherent(dev, (sizeof(struct edma_txdesc_sec)
++ * txdesc_ring->count),
++ txdesc_ring->sdesc,
++ txdesc_ring->sdma);
++ txdesc_ring->sdesc = NULL;
++ txdesc_ring->sdma = (dma_addr_t)0;
++}
++
++static int edma_cfg_tx_desc_ring_setup(struct edma_txdesc_ring *txdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Allocate RxFill ring descriptors. */
++ txdesc_ring->pdesc = dma_alloc_coherent(dev, sizeof(struct edma_txdesc_pri)
++ * txdesc_ring->count,
++ &txdesc_ring->pdma,
++ GFP_KERNEL | __GFP_ZERO);
++
++ if (unlikely(!txdesc_ring->pdesc))
++ return -ENOMEM;
++
++ txdesc_ring->sdesc = dma_alloc_coherent(dev, sizeof(struct edma_txdesc_sec)
++ * txdesc_ring->count,
++ &txdesc_ring->sdma,
++ GFP_KERNEL | __GFP_ZERO);
++
++ if (unlikely(!txdesc_ring->sdesc)) {
++ dma_free_coherent(dev, (sizeof(struct edma_txdesc_pri)
++ * txdesc_ring->count),
++ txdesc_ring->pdesc,
++ txdesc_ring->pdma);
++ txdesc_ring->pdesc = NULL;
++ txdesc_ring->pdma = (dma_addr_t)0;
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void edma_cfg_tx_desc_ring_configure(struct edma_txdesc_ring *txdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_BA(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)(txdesc_ring->pdma & EDMA_RING_DMA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_BA2(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)(txdesc_ring->sdma & EDMA_RING_DMA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_RING_SIZE(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)(txdesc_ring->count & EDMA_TXDESC_RING_SIZE_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_PROD_IDX(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)EDMA_TX_INITIAL_PROD_IDX);
++
++ data = FIELD_PREP(EDMA_TXDESC_CTRL_FC_GRP_ID_MASK, txdesc_ring->fc_grp_id);
++
++ /* Configure group ID for flow control for this Tx ring. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CTRL(txdesc_ring->id);
++ regmap_write(regmap, reg, data);
++}
++
++static void edma_cfg_txcmpl_ring_configure(struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++
++ /* Configure TxCmpl ring base address. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_BA(txcmpl_ring->id);
++ regmap_write(regmap, reg, (u32)(txcmpl_ring->dma & EDMA_RING_DMA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_RING_SIZE(txcmpl_ring->id);
++ regmap_write(regmap, reg, (u32)(txcmpl_ring->count & EDMA_TXDESC_RING_SIZE_MASK));
++
++ /* Set TxCmpl ret mode to opaque. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_CTRL(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_TXCMPL_RETMODE_OPAQUE);
++
++ /* Configure the Mitigation timer. */
++ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_TX_MITIGATION_TIMER_DEF,
++ ppe_dev->clk_rate / MHZ);
++ data = ((data & EDMA_TX_MOD_TIMER_INIT_MASK)
++ << EDMA_TX_MOD_TIMER_INIT_SHIFT);
++ pr_debug("EDMA Tx mitigation timer value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_MOD_TIMER(txcmpl_ring->id);
++ regmap_write(regmap, reg, data);
++
++ /* Configure the Mitigation packet count. */
++ data = (EDMA_TX_MITIGATION_PKT_CNT_DEF & EDMA_TXCMPL_LOW_THRE_MASK)
++ << EDMA_TXCMPL_LOW_THRE_SHIFT;
++ pr_debug("EDMA Tx mitigation packet count value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_UGT_THRE(txcmpl_ring->id);
++ regmap_write(regmap, reg, data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_CTRL(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_TX_NE_INT_EN);
++}
++
++/**
++ * edma_cfg_tx_fill_per_port_tx_map - Fill Tx ring mapping.
++ * @netdev: Netdevice.
++ * @port_id: Port ID.
++ *
++ * Fill per-port Tx ring mapping in net device private area.
++ */
++void edma_cfg_tx_fill_per_port_tx_map(struct net_device *netdev, u32 port_id)
++{
++ u32 i;
++
++ /* Ring to core mapping is done in order starting from 0 for port 1. */
++ for_each_possible_cpu(i) {
++ struct edma_port_priv *port_dev = (struct edma_port_priv *)netdev_priv(netdev);
++ struct edma_txdesc_ring *txdesc_ring;
++ u32 txdesc_ring_id;
++
++ txdesc_ring_id = ((port_id - 1) * num_possible_cpus()) + i;
++ txdesc_ring = &edma_ctx->tx_rings[txdesc_ring_id];
++ port_dev->txr_map[i] = txdesc_ring;
++ }
++}
++
++/**
++ * edma_cfg_tx_rings_enable - Enable Tx rings.
++ *
++ * Enable Tx rings.
++ */
++void edma_cfg_tx_rings_enable(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_ring *txdesc_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txdesc_ring = &edma_ctx->tx_rings[ring_idx];
++ u32 data;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CTRL(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ data |= FIELD_PREP(EDMA_TXDESC_CTRL_TXEN_MASK, EDMA_TXDESC_TX_ENABLE);
++
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_tx_rings_disable - Disable Tx rings.
++ *
++ * Disable Tx rings.
++ */
++void edma_cfg_tx_rings_disable(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_ring *txdesc_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txdesc_ring = &edma_ctx->tx_rings[ring_idx];
++ u32 data;
++
++ txdesc_ring = &edma_ctx->tx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CTRL(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ data &= ~EDMA_TXDESC_TX_ENABLE;
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_tx_ring_mappings - Map Tx to Tx complete rings.
++ *
++ * Map Tx to Tx complete rings.
++ */
++void edma_cfg_tx_ring_mappings(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 desc_index, i, data, reg;
++
++ /* Clear the TXDESC2CMPL_MAP_xx reg before setting up
++ * the mapping. This register holds TXDESC to TXFILL ring
++ * mapping.
++ */
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_0_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_1_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_2_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_3_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_4_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_5_ADDR, 0);
++ desc_index = txcmpl->ring_start;
++
++ /* 6 registers to hold the completion mapping for total 32
++ * TX desc rings (0-5, 6-11, 12-17, 18-23, 24-29 and rest).
++ * In each entry 5 bits hold the mapping for a particular TX desc ring.
++ */
++ for (i = tx->ring_start; i < tx->ring_start + tx->num_rings; i++) {
++ u32 reg, data;
++
++ if (i >= 0 && i <= 5)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_0_ADDR;
++ else if (i >= 6 && i <= 11)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_1_ADDR;
++ else if (i >= 12 && i <= 17)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_2_ADDR;
++ else if (i >= 18 && i <= 23)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_3_ADDR;
++ else if (i >= 24 && i <= 29)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_4_ADDR;
++ else
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_5_ADDR;
++
++ pr_debug("Configure Tx desc:%u to use TxCmpl:%u\n", i, desc_index);
++
++ /* Set the Tx complete descriptor ring number in the mapping register.
++ * E.g. If (txcmpl ring)desc_index = 31, (txdesc ring)i = 28.
++ * reg = EDMA_REG_TXDESC2CMPL_MAP_4_ADDR
++ * data |= (desc_index & 0x1F) << ((i % 6) * 5);
++ * data |= (0x1F << 20); -
++ * This sets 11111 at 20th bit of register EDMA_REG_TXDESC2CMPL_MAP_4_ADDR.
++ */
++ regmap_read(regmap, reg, &data);
++ data |= (desc_index & EDMA_TXDESC2CMPL_MAP_TXDESC_MASK) << ((i % 6) * 5);
++ regmap_write(regmap, reg, data);
++
++ desc_index++;
++ if (desc_index == txcmpl->ring_start + txcmpl->num_rings)
++ desc_index = txcmpl->ring_start;
++ }
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_0_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_0_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_1_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_1_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_2_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_2_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_3_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_3_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_4_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_4_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_5_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_5_ADDR: 0x%x\n", data);
++}
++
++static int edma_cfg_tx_rings_setup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 i, j = 0;
++
++ /* Set Txdesc flow control group id, same as port number. */
++ for (i = 0; i < hw_info->max_ports; i++) {
++ for_each_possible_cpu(j) {
++ struct edma_txdesc_ring *txdesc_ring = NULL;
++ u32 txdesc_idx = (i * num_possible_cpus()) + j;
++
++ txdesc_ring = &edma_ctx->tx_rings[txdesc_idx];
++ txdesc_ring->fc_grp_id = i + 1;
++ }
++ }
++
++ /* Allocate TxDesc ring descriptors. */
++ for (i = 0; i < tx->num_rings; i++) {
++ struct edma_txdesc_ring *txdesc_ring = NULL;
++ int ret;
++
++ txdesc_ring = &edma_ctx->tx_rings[i];
++ txdesc_ring->count = EDMA_TX_RING_SIZE;
++ txdesc_ring->id = tx->ring_start + i;
++
++ ret = edma_cfg_tx_desc_ring_setup(txdesc_ring);
++ if (ret) {
++ pr_err("Error in setting up %d txdesc ring. ret: %d",
++ txdesc_ring->id, ret);
++ while (i-- >= 0)
++ edma_cfg_tx_desc_ring_cleanup(&edma_ctx->tx_rings[i]);
++
++ return -ENOMEM;
++ }
++ }
++
++ /* Allocate TxCmpl ring descriptors. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ struct edma_txcmpl_ring *txcmpl_ring = NULL;
++ int ret;
++
++ txcmpl_ring = &edma_ctx->txcmpl_rings[i];
++ txcmpl_ring->count = EDMA_TX_RING_SIZE;
++ txcmpl_ring->id = txcmpl->ring_start + i;
++
++ ret = edma_cfg_txcmpl_ring_setup(txcmpl_ring);
++ if (ret != 0) {
++ pr_err("Error in setting up %d TxCmpl ring. ret: %d",
++ txcmpl_ring->id, ret);
++ while (i-- >= 0)
++ edma_cfg_txcmpl_ring_cleanup(&edma_ctx->txcmpl_rings[i]);
++
++ goto txcmpl_mem_alloc_fail;
++ }
++ }
++
++ pr_debug("Tx descriptor count for Tx desc and Tx complete rings: %d\n",
++ EDMA_TX_RING_SIZE);
++
++ return 0;
++
++txcmpl_mem_alloc_fail:
++ for (i = 0; i < tx->num_rings; i++)
++ edma_cfg_tx_desc_ring_cleanup(&edma_ctx->tx_rings[i]);
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_tx_rings_alloc - Allocate EDMA Tx rings.
++ *
++ * Allocate EDMA Tx rings.
++ */
++int edma_cfg_tx_rings_alloc(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++
++ edma_ctx->tx_rings = kzalloc((sizeof(*edma_ctx->tx_rings) * tx->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->tx_rings)
++ return -ENOMEM;
++
++ edma_ctx->txcmpl_rings = kzalloc((sizeof(*edma_ctx->txcmpl_rings) * txcmpl->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->txcmpl_rings)
++ goto txcmpl_ring_alloc_fail;
++
++ pr_debug("Num rings - TxDesc:%u (%u-%u) TxCmpl:%u (%u-%u)\n",
++ tx->num_rings, tx->ring_start,
++ (tx->ring_start + tx->num_rings - 1),
++ txcmpl->num_rings, txcmpl->ring_start,
++ (txcmpl->ring_start + txcmpl->num_rings - 1));
++
++ if (edma_cfg_tx_rings_setup()) {
++ pr_err("Error in setting up tx rings\n");
++ goto tx_rings_setup_fail;
++ }
++
++ return 0;
++
++tx_rings_setup_fail:
++ kfree(edma_ctx->txcmpl_rings);
++ edma_ctx->txcmpl_rings = NULL;
++
++txcmpl_ring_alloc_fail:
++ kfree(edma_ctx->tx_rings);
++ edma_ctx->tx_rings = NULL;
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_tx_rings_cleanup - Cleanup EDMA Tx rings.
++ *
++ * Cleanup EDMA Tx rings.
++ */
++void edma_cfg_tx_rings_cleanup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 i;
++
++ /* Free any buffers assigned to any descriptors. */
++ for (i = 0; i < tx->num_rings; i++)
++ edma_cfg_tx_desc_ring_cleanup(&edma_ctx->tx_rings[i]);
++
++ /* Free Tx completion descriptors. */
++ for (i = 0; i < txcmpl->num_rings; i++)
++ edma_cfg_txcmpl_ring_cleanup(&edma_ctx->txcmpl_rings[i]);
++
++ kfree(edma_ctx->tx_rings);
++ kfree(edma_ctx->txcmpl_rings);
++ edma_ctx->tx_rings = NULL;
++ edma_ctx->txcmpl_rings = NULL;
++}
++
++/**
++ * edma_cfg_tx_rings - Configure EDMA Tx rings.
++ *
++ * Configure EDMA Tx rings.
++ */
++void edma_cfg_tx_rings(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 i;
++
++ /* Configure Tx desc ring. */
++ for (i = 0; i < tx->num_rings; i++)
++ edma_cfg_tx_desc_ring_configure(&edma_ctx->tx_rings[i]);
++
++ /* Configure TxCmpl ring. */
++ for (i = 0; i < txcmpl->num_rings; i++)
++ edma_cfg_txcmpl_ring_configure(&edma_ctx->txcmpl_rings[i]);
++}
++
++/**
++ * edma_cfg_tx_disable_interrupts - EDMA disable TX interrupts.
++ *
++ * Disable TX interrupt masks.
++ */
++void edma_cfg_tx_disable_interrupts(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_CLEAR);
++ }
++}
++
++/**
++ * edma_cfg_tx_enable_interrupts - EDMA enable TX interrupts.
++ *
++ * Enable TX interrupt masks.
++ */
++void edma_cfg_tx_enable_interrupts(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_txcmpl);
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_enable - EDMA Tx NAPI.
++ * @port_id: Port ID.
++ *
++ * Enable Tx NAPI.
++ */
++void edma_cfg_tx_napi_enable(u32 port_id)
++{
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Enabling Tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ if (!txcmpl_ring->napi_added)
++ continue;
++
++ napi_enable(&txcmpl_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_disable - Disable Tx NAPI.
++ * @port_id: Port ID.
++ *
++ * Disable Tx NAPI.
++ */
++void edma_cfg_tx_napi_disable(u32 port_id)
++{
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Disabling Tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ if (!txcmpl_ring->napi_added)
++ continue;
++
++ napi_disable(&txcmpl_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_delete - Delete Tx NAPI.
++ * @port_id: Port ID.
++ *
++ * Delete Tx NAPI.
++ */
++void edma_cfg_tx_napi_delete(u32 port_id)
++{
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Disabling Tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ if (!txcmpl_ring->napi_added)
++ continue;
++
++ netif_napi_del(&txcmpl_ring->napi);
++ txcmpl_ring->napi_added = false;
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_add - TX NAPI add.
++ * @netdev: Netdevice.
++ * @port_id: Port ID.
++ *
++ * TX NAPI add.
++ */
++void edma_cfg_tx_napi_add(struct net_device *netdev, u32 port_id)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Adding tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ netif_napi_add_weight(netdev, &txcmpl_ring->napi,
++ edma_tx_napi_poll, hw_info->napi_budget_tx);
++ txcmpl_ring->napi_added = true;
++ netdev_dbg(netdev, "Napi added for txcmpl ring: %u\n", txcmpl_ring->id);
++ }
++
++ netdev_dbg(netdev, "Tx NAPI budget: %d\n", hw_info->napi_budget_tx);
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_CFG_TX__
++#define __EDMA_CFG_TX__
++
++/* Tx mitigation timer's default value. */
++#define EDMA_TX_MITIGATION_TIMER_DEF 250
++
++/* Tx mitigation packet count default value. */
++#define EDMA_TX_MITIGATION_PKT_CNT_DEF 16
++
++void edma_cfg_tx_rings(void);
++int edma_cfg_tx_rings_alloc(void);
++void edma_cfg_tx_rings_cleanup(void);
++void edma_cfg_tx_disable_interrupts(u32 port_id);
++void edma_cfg_tx_enable_interrupts(u32 port_id);
++void edma_cfg_tx_napi_enable(u32 port_id);
++void edma_cfg_tx_napi_disable(u32 port_id);
++void edma_cfg_tx_napi_delete(u32 port_id);
++void edma_cfg_tx_napi_add(struct net_device *netdevice, u32 macid);
++void edma_cfg_tx_ring_mappings(void);
++void edma_cfg_txcmpl_mapping_fill(void);
++void edma_cfg_tx_rings_enable(u32 port_id);
++void edma_cfg_tx_rings_disable(u32 port_id);
++void edma_cfg_tx_fill_per_port_tx_map(struct net_device *netdev, u32 macid);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -13,6 +13,7 @@
+
+ #include "edma.h"
+ #include "edma_cfg_rx.h"
++#include "edma_cfg_tx.h"
+ #include "edma_port.h"
+ #include "ppe_regs.h"
+
+@@ -35,6 +36,15 @@ static int edma_port_stats_alloc(struct
+ return -ENOMEM;
+ }
+
++ port_priv->pcpu_stats.tx_stats =
++ netdev_alloc_pcpu_stats(struct edma_port_tx_stats);
++ if (!port_priv->pcpu_stats.tx_stats) {
++ netdev_err(netdev, "Per-cpu EDMA Tx stats alloc failed for %s\n",
++ netdev->name);
++ free_percpu(port_priv->pcpu_stats.rx_stats);
++ return -ENOMEM;
++ }
++
+ return 0;
+ }
+
+@@ -43,6 +53,28 @@ static void edma_port_stats_free(struct
+ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
+
+ free_percpu(port_priv->pcpu_stats.rx_stats);
++ free_percpu(port_priv->pcpu_stats.tx_stats);
++}
++
++static void edma_port_configure(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++ int port_id = port->port_id;
++
++ edma_cfg_tx_fill_per_port_tx_map(netdev, port_id);
++ edma_cfg_tx_rings_enable(port_id);
++ edma_cfg_tx_napi_add(netdev, port_id);
++}
++
++static void edma_port_deconfigure(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++ int port_id = port->port_id;
++
++ edma_cfg_tx_napi_delete(port_id);
++ edma_cfg_tx_rings_disable(port_id);
+ }
+
+ static u16 __maybe_unused edma_port_select_queue(__maybe_unused struct net_device *netdev,
+@@ -60,6 +92,7 @@ static int edma_port_open(struct net_dev
+ {
+ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
+ struct ppe_port *ppe_port;
++ int port_id;
+
+ if (!port_priv)
+ return -EINVAL;
+@@ -74,10 +107,14 @@ static int edma_port_open(struct net_dev
+ netdev->wanted_features |= EDMA_NETDEV_FEATURES;
+
+ ppe_port = port_priv->ppe_port;
++ port_id = ppe_port->port_id;
+
+ if (ppe_port->phylink)
+ phylink_start(ppe_port->phylink);
+
++ edma_cfg_tx_napi_enable(port_id);
++ edma_cfg_tx_enable_interrupts(port_id);
++
+ netif_start_queue(netdev);
+
+ return 0;
+@@ -87,13 +124,21 @@ static int edma_port_close(struct net_de
+ {
+ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
+ struct ppe_port *ppe_port;
++ int port_id;
+
+ if (!port_priv)
+ return -EINVAL;
+
+ netif_stop_queue(netdev);
+
++ /* 20ms delay would provide a plenty of margin to take care of in-flight packets. */
++ msleep(20);
++
+ ppe_port = port_priv->ppe_port;
++ port_id = ppe_port->port_id;
++
++ edma_cfg_tx_disable_interrupts(port_id);
++ edma_cfg_tx_napi_disable(port_id);
+
+ /* Phylink close. */
+ if (ppe_port->phylink)
+@@ -137,6 +182,92 @@ static netdev_features_t edma_port_featu
+ return features;
+ }
+
++static netdev_tx_t edma_port_xmit(struct sk_buff *skb,
++ struct net_device *dev)
++{
++ struct edma_port_priv *port_priv = NULL;
++ struct edma_port_pcpu_stats *pcpu_stats;
++ struct edma_txdesc_ring *txdesc_ring;
++ struct edma_port_tx_stats *stats;
++ enum edma_tx_gso_status result;
++ struct sk_buff *segs = NULL;
++ u8 cpu_id;
++ u32 skbq;
++ int ret;
++
++ if (!skb || !dev)
++ return NETDEV_TX_OK;
++
++ port_priv = netdev_priv(dev);
++
++ /* Select a TX ring. */
++ skbq = (skb_get_queue_mapping(skb) & (num_possible_cpus() - 1));
++
++ txdesc_ring = (struct edma_txdesc_ring *)port_priv->txr_map[skbq];
++
++ pcpu_stats = &port_priv->pcpu_stats;
++ stats = this_cpu_ptr(pcpu_stats->tx_stats);
++
++ /* HW does not support TSO for packets with more than or equal to
++ * 32 segments. Perform SW GSO for such packets.
++ */
++ result = edma_tx_gso_segment(skb, dev, &segs);
++ if (likely(result == EDMA_TX_GSO_NOT_NEEDED)) {
++ /* Transmit the packet. */
++ ret = edma_tx_ring_xmit(dev, skb, txdesc_ring, stats);
++
++ if (unlikely(ret == EDMA_TX_FAIL_NO_DESC)) {
++ if (likely(!edma_ctx->tx_requeue_stop)) {
++ cpu_id = smp_processor_id();
++ netdev_dbg(dev, "Stopping tx queue due to lack oftx descriptors\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_queue_stopped[cpu_id];
++ u64_stats_update_end(&stats->syncp);
++ netif_tx_stop_queue(netdev_get_tx_queue(dev, skbq));
++ return NETDEV_TX_BUSY;
++ }
++ }
++
++ if (unlikely(ret != EDMA_TX_OK)) {
++ dev_kfree_skb_any(skb);
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_drops;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ return NETDEV_TX_OK;
++ } else if (unlikely(result == EDMA_TX_GSO_FAIL)) {
++ netdev_dbg(dev, "%p: SW GSO failed for segment size: %d\n",
++ skb, skb_shinfo(skb)->gso_segs);
++ dev_kfree_skb_any(skb);
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_gso_drop_pkts;
++ u64_stats_update_end(&stats->syncp);
++ return NETDEV_TX_OK;
++ }
++
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_gso_pkts;
++ u64_stats_update_end(&stats->syncp);
++
++ dev_kfree_skb_any(skb);
++ while (segs) {
++ skb = segs;
++ segs = segs->next;
++
++ /* Transmit the packet. */
++ ret = edma_tx_ring_xmit(dev, skb, txdesc_ring, stats);
++ if (unlikely(ret != EDMA_TX_OK)) {
++ dev_kfree_skb_any(skb);
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_drops;
++ u64_stats_update_end(&stats->syncp);
++ }
++ }
++
++ return NETDEV_TX_OK;
++}
++
+ static void edma_port_get_stats64(struct net_device *netdev,
+ struct rtnl_link_stats64 *stats)
+ {
+@@ -179,6 +310,7 @@ static int edma_port_set_mac_address(str
+ static const struct net_device_ops edma_port_netdev_ops = {
+ .ndo_open = edma_port_open,
+ .ndo_stop = edma_port_close,
++ .ndo_start_xmit = edma_port_xmit,
+ .ndo_get_stats64 = edma_port_get_stats64,
+ .ndo_set_mac_address = edma_port_set_mac_address,
+ .ndo_validate_addr = eth_validate_addr,
+@@ -199,6 +331,7 @@ void edma_port_destroy(struct ppe_port *
+ int port_id = port->port_id;
+ struct net_device *netdev = edma_ctx->netdev_arr[port_id - 1];
+
++ edma_port_deconfigure(netdev);
+ edma_port_stats_free(netdev);
+ unregister_netdev(netdev);
+ free_netdev(netdev);
+@@ -276,6 +409,8 @@ int edma_port_setup(struct ppe_port *por
+ */
+ edma_ctx->netdev_arr[port_id - 1] = netdev;
+
++ edma_port_configure(netdev);
++
+ /* Setup phylink. */
+ ret = ppe_port_phylink_setup(port, netdev);
+ if (ret) {
+@@ -298,6 +433,7 @@ int edma_port_setup(struct ppe_port *por
+ register_netdev_fail:
+ ppe_port_phylink_destroy(port);
+ port_phylink_setup_fail:
++ edma_port_deconfigure(netdev);
+ edma_ctx->netdev_arr[port_id - 1] = NULL;
+ edma_port_stats_free(netdev);
+ stats_alloc_fail:
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.h
+@@ -7,6 +7,8 @@
+
+ #include "ppe_port.h"
+
++#define EDMA_PORT_MAX_CORE 4
++
+ #define EDMA_NETDEV_FEATURES (NETIF_F_FRAGLIST \
+ | NETIF_F_SG \
+ | NETIF_F_RXCSUM \
+@@ -35,11 +37,43 @@ struct edma_port_rx_stats {
+ };
+
+ /**
++ * struct edma_port_tx_stats - EDMA TX port per CPU stats for the port.
++ * @tx_pkts: Number of Tx packets
++ * @tx_bytes: Number of Tx bytes
++ * @tx_drops: Number of Tx drops
++ * @tx_nr_frag_pkts: Number of Tx nr_frag packets
++ * @tx_fraglist_pkts: Number of Tx fraglist packets
++ * @tx_fraglist_with_nr_frags_pkts: Number of Tx packets with fraglist and nr_frags
++ * @tx_tso_pkts: Number of Tx TSO packets
++ * @tx_tso_drop_pkts: Number of Tx TSO drop packets
++ * @tx_gso_pkts: Number of Tx GSO packets
++ * @tx_gso_drop_pkts: Number of Tx GSO drop packets
++ * @tx_queue_stopped: Number of Tx queue stopped packets
++ * @syncp: Synchronization pointer
++ */
++struct edma_port_tx_stats {
++ u64 tx_pkts;
++ u64 tx_bytes;
++ u64 tx_drops;
++ u64 tx_nr_frag_pkts;
++ u64 tx_fraglist_pkts;
++ u64 tx_fraglist_with_nr_frags_pkts;
++ u64 tx_tso_pkts;
++ u64 tx_tso_drop_pkts;
++ u64 tx_gso_pkts;
++ u64 tx_gso_drop_pkts;
++ u64 tx_queue_stopped[EDMA_PORT_MAX_CORE];
++ struct u64_stats_sync syncp;
++};
++
++/**
+ * struct edma_port_pcpu_stats - EDMA per cpu stats data structure for the port.
+ * @rx_stats: Per CPU Rx statistics
++ * @tx_stats: Per CPU Tx statistics
+ */
+ struct edma_port_pcpu_stats {
+ struct edma_port_rx_stats __percpu *rx_stats;
++ struct edma_port_tx_stats __percpu *tx_stats;
+ };
+
+ /**
+@@ -54,6 +88,7 @@ struct edma_port_priv {
+ struct ppe_port *ppe_port;
+ struct net_device *netdev;
+ struct edma_port_pcpu_stats pcpu_stats;
++ struct edma_txdesc_ring *txr_map[EDMA_PORT_MAX_CORE];
+ unsigned long flags;
+ };
+
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_tx.c
+@@ -0,0 +1,808 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Provide APIs to alloc Tx Buffers, fill the Tx descriptors and transmit
++ * Scatter Gather and linear packets, Tx complete to free the skb after transmit.
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/platform_device.h>
++#include <linux/printk.h>
++#include <net/gso.h>
++#include <linux/regmap.h>
++
++#include "edma.h"
++#include "edma_cfg_tx.h"
++#include "edma_port.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++static u32 edma_tx_num_descs_for_sg(struct sk_buff *skb)
++{
++ u32 nr_frags_first = 0, num_tx_desc_needed = 0;
++
++ /* Check if we have enough Tx descriptors for SG. */
++ if (unlikely(skb_shinfo(skb)->nr_frags)) {
++ nr_frags_first = skb_shinfo(skb)->nr_frags;
++ WARN_ON_ONCE(nr_frags_first > MAX_SKB_FRAGS);
++ num_tx_desc_needed += nr_frags_first;
++ }
++
++ /* Walk through fraglist skbs making a note of nr_frags
++ * One Tx desc for fraglist skb. Fraglist skb may have
++ * further nr_frags.
++ */
++ if (unlikely(skb_has_frag_list(skb))) {
++ struct sk_buff *iter_skb;
++
++ skb_walk_frags(skb, iter_skb) {
++ u32 nr_frags = skb_shinfo(iter_skb)->nr_frags;
++
++ WARN_ON_ONCE(nr_frags > MAX_SKB_FRAGS);
++ num_tx_desc_needed += (1 + nr_frags);
++ }
++ }
++
++ return (num_tx_desc_needed + 1);
++}
++
++/**
++ * edma_tx_gso_segment - Tx GSO.
++ * @skb: Socket Buffer.
++ * @netdev: Netdevice.
++ * @segs: SKB segments from GSO.
++ *
++ * Format skbs into GSOs.
++ *
++ * Return 1 on success, error code on failure.
++ */
++enum edma_tx_gso_status edma_tx_gso_segment(struct sk_buff *skb,
++ struct net_device *netdev, struct sk_buff **segs)
++{
++ u32 num_tx_desc_needed;
++
++ /* Check is skb is non-linear to proceed. */
++ if (likely(!skb_is_nonlinear(skb)))
++ return EDMA_TX_GSO_NOT_NEEDED;
++
++ /* Check if TSO is enabled. If so, return as skb doesn't
++ * need to be segmented by linux.
++ */
++ if (netdev->features & (NETIF_F_TSO | NETIF_F_TSO6)) {
++ num_tx_desc_needed = edma_tx_num_descs_for_sg(skb);
++ if (likely(num_tx_desc_needed <= EDMA_TX_TSO_SEG_MAX))
++ return EDMA_TX_GSO_NOT_NEEDED;
++ }
++
++ /* GSO segmentation of the skb into multiple segments. */
++ *segs = skb_gso_segment(skb, netdev->features
++ & ~(NETIF_F_TSO | NETIF_F_TSO6));
++
++ /* Check for error in GSO segmentation. */
++ if (IS_ERR_OR_NULL(*segs)) {
++ netdev_info(netdev, "Tx gso fail\n");
++ return EDMA_TX_GSO_FAIL;
++ }
++
++ return EDMA_TX_GSO_SUCCEED;
++}
++
++/**
++ * edma_tx_complete - Reap Tx completion descriptors.
++ * @work_to_do: Work to do.
++ * @txcmpl_ring: Tx Completion ring.
++ *
++ * Reap Tx completion descriptors of the transmitted
++ * packets and free the corresponding SKBs.
++ *
++ * Return the number descriptors for which Tx complete is done.
++ */
++u32 edma_tx_complete(u32 work_to_do, struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct edma_txcmpl_stats *txcmpl_stats = &txcmpl_ring->txcmpl_stats;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 cons_idx, end_idx, data, cpu_id;
++ struct device *dev = ppe_dev->dev;
++ u32 avail, count, txcmpl_errors;
++ struct edma_txcmpl_desc *txcmpl;
++ u32 prod_idx = 0, more_bit = 0;
++ struct netdev_queue *nq;
++ struct sk_buff *skb;
++ u32 reg;
++
++ cons_idx = txcmpl_ring->cons_idx;
++
++ if (likely(txcmpl_ring->avail_pkt >= work_to_do)) {
++ avail = work_to_do;
++ } else {
++ /* Get TXCMPL ring producer index. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_PROD_IDX(txcmpl_ring->id);
++ regmap_read(regmap, reg, &data);
++ prod_idx = data & EDMA_TXCMPL_PROD_IDX_MASK;
++
++ avail = EDMA_DESC_AVAIL_COUNT(prod_idx, cons_idx, EDMA_TX_RING_SIZE);
++ txcmpl_ring->avail_pkt = avail;
++
++ if (unlikely(!avail)) {
++ dev_dbg(dev, "No available descriptors are pending for %d txcmpl ring\n",
++ txcmpl_ring->id);
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->no_pending_desc;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ return 0;
++ }
++
++ avail = min(avail, work_to_do);
++ }
++
++ count = avail;
++
++ end_idx = (cons_idx + avail) & EDMA_TX_RING_SIZE_MASK;
++ txcmpl = EDMA_TXCMPL_DESC(txcmpl_ring, cons_idx);
++
++ /* Instead of freeing the skb, it might be better to save and use
++ * for Rxfill.
++ */
++ while (likely(avail--)) {
++ /* The last descriptor holds the SKB pointer for scattered frames.
++ * So skip the descriptors with more bit set.
++ */
++ more_bit = EDMA_TXCMPL_MORE_BIT_GET(txcmpl);
++ if (unlikely(more_bit)) {
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->desc_with_more_bit;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ cons_idx = ((cons_idx + 1) & EDMA_TX_RING_SIZE_MASK);
++ txcmpl = EDMA_TXCMPL_DESC(txcmpl_ring, cons_idx);
++ continue;
++ }
++
++ /* Find and free the skb for Tx completion. */
++ skb = (struct sk_buff *)EDMA_TXCMPL_OPAQUE_GET(txcmpl);
++ if (unlikely(!skb)) {
++ if (net_ratelimit())
++ dev_warn(dev, "Invalid cons_idx:%u prod_idx:%u word2:%x word3:%x\n",
++ cons_idx, prod_idx, txcmpl->word2, txcmpl->word3);
++
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->invalid_buffer;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ } else {
++ dev_dbg(dev, "TXCMPL: skb:%p, skb->len %d, skb->data_len %d, cons_idx:%d prod_idx:%d word2:0x%x word3:0x%x\n",
++ skb, skb->len, skb->data_len, cons_idx, prod_idx,
++ txcmpl->word2, txcmpl->word3);
++
++ txcmpl_errors = EDMA_TXCOMP_RING_ERROR_GET(txcmpl->word3);
++ if (unlikely(txcmpl_errors)) {
++ if (net_ratelimit())
++ dev_err(dev, "Error 0x%0x observed in tx complete %d ring\n",
++ txcmpl_errors, txcmpl_ring->id);
++
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->errors;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ }
++
++ /* Retrieve pool id for unmapping.
++ * 0 for linear skb and (pool id - 1) represents nr_frag index.
++ */
++ if (!EDMA_TXCOMP_POOL_ID_GET(txcmpl)) {
++ dma_unmap_single(dev, virt_to_phys(skb->data),
++ skb->len, DMA_TO_DEVICE);
++ } else {
++ u8 frag_index = (EDMA_TXCOMP_POOL_ID_GET(txcmpl) - 1);
++ skb_frag_t *frag = &skb_shinfo(skb)->frags[frag_index];
++
++ dma_unmap_page(dev, virt_to_phys(frag),
++ PAGE_SIZE, DMA_TO_DEVICE);
++ }
++
++ dev_kfree_skb(skb);
++ }
++
++ cons_idx = ((cons_idx + 1) & EDMA_TX_RING_SIZE_MASK);
++ txcmpl = EDMA_TXCMPL_DESC(txcmpl_ring, cons_idx);
++ }
++
++ txcmpl_ring->cons_idx = cons_idx;
++ txcmpl_ring->avail_pkt -= count;
++
++ dev_dbg(dev, "TXCMPL:%u count:%u prod_idx:%u cons_idx:%u\n",
++ txcmpl_ring->id, count, prod_idx, cons_idx);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_CONS_IDX(txcmpl_ring->id);
++ regmap_write(regmap, reg, cons_idx);
++
++ /* If tx_requeue_stop disabled (tx_requeue_stop = 0)
++ * Fetch the tx queue of interface and check if it is stopped.
++ * if queue is stopped and interface is up, wake up this queue.
++ */
++ if (unlikely(!edma_ctx->tx_requeue_stop)) {
++ cpu_id = smp_processor_id();
++ nq = netdev_get_tx_queue(txcmpl_ring->napi.dev, cpu_id);
++ if (unlikely(netif_tx_queue_stopped(nq)) &&
++ netif_carrier_ok(txcmpl_ring->napi.dev)) {
++ dev_dbg(dev, "Waking queue number %d, for interface %s\n",
++ cpu_id, txcmpl_ring->napi.dev->name);
++ __netif_tx_lock(nq, cpu_id);
++ netif_tx_wake_queue(nq);
++ __netif_tx_unlock(nq);
++ }
++ }
++
++ return count;
++}
++
++/**
++ * edma_tx_napi_poll - EDMA TX NAPI handler.
++ * @napi: NAPI structure.
++ * @budget: Tx NAPI Budget.
++ *
++ * EDMA TX NAPI handler.
++ */
++int edma_tx_napi_poll(struct napi_struct *napi, int budget)
++{
++ struct edma_txcmpl_ring *txcmpl_ring = (struct edma_txcmpl_ring *)napi;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 txcmpl_intr_status;
++ int work_done = 0;
++ u32 data, reg;
++
++ do {
++ work_done += edma_tx_complete(budget - work_done, txcmpl_ring);
++ if (work_done >= budget)
++ return work_done;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_STAT(txcmpl_ring->id);
++ regmap_read(regmap, reg, &data);
++ txcmpl_intr_status = data & EDMA_TXCMPL_RING_INT_STATUS_MASK;
++ } while (txcmpl_intr_status);
++
++ /* No more packets to process. Finish NAPI processing. */
++ napi_complete(napi);
++
++ /* Set TXCMPL ring interrupt mask. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_txcmpl);
++
++ return work_done;
++}
++
++/**
++ * edma_tx_handle_irq - Tx IRQ Handler.
++ * @irq: Interrupt request.
++ * @ctx: Context.
++ *
++ * Process TX IRQ and schedule NAPI.
++ *
++ * Return IRQ handler code.
++ */
++irqreturn_t edma_tx_handle_irq(int irq, void *ctx)
++{
++ struct edma_txcmpl_ring *txcmpl_ring = (struct edma_txcmpl_ring *)ctx;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ pr_debug("irq: irq=%d txcmpl_ring_id=%u\n", irq, txcmpl_ring->id);
++ if (likely(napi_schedule_prep(&txcmpl_ring->napi))) {
++ /* Disable TxCmpl intr. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_DISABLE);
++ __napi_schedule(&txcmpl_ring->napi);
++ }
++
++ return IRQ_HANDLED;
++}
++
++static void edma_tx_dma_unmap_frags(struct sk_buff *skb, u32 nr_frags)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ u32 buf_len = 0;
++ u8 i = 0;
++
++ for (i = 0; i < skb_shinfo(skb)->nr_frags - nr_frags; i++) {
++ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
++
++ /* DMA mapping was not done for zero size segments. */
++ buf_len = skb_frag_size(frag);
++ if (unlikely(buf_len == 0))
++ continue;
++
++ dma_unmap_page(dev, virt_to_phys(frag), PAGE_SIZE,
++ DMA_TO_DEVICE);
++ }
++}
++
++static u32 edma_tx_skb_nr_frags(struct edma_txdesc_ring *txdesc_ring,
++ struct edma_txdesc_pri **txdesc, struct sk_buff *skb,
++ u32 *hw_next_to_use, u32 *invalid_frag)
++{
++ u32 nr_frags = 0, buf_len = 0, num_descs = 0, start_idx = 0, end_idx = 0;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 start_hw_next_to_use = *hw_next_to_use;
++ struct edma_txdesc_pri *txd = *txdesc;
++ struct device *dev = ppe_dev->dev;
++ u8 i = 0;
++
++ /* Hold onto the index mapped to *txdesc.
++ * This will be the index previous to that of current *hw_next_to_use.
++ */
++ start_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK)
++ & EDMA_TX_RING_SIZE_MASK);
++
++ /* Handle if the skb has nr_frags. */
++ nr_frags = skb_shinfo(skb)->nr_frags;
++ num_descs = nr_frags;
++ i = 0;
++
++ while (nr_frags--) {
++ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
++ dma_addr_t buff_addr;
++
++ buf_len = skb_frag_size(frag);
++
++ /* Zero size segment can lead EDMA HW to hang so, we don't want to
++ * process them. Zero size segment can happen during TSO operation
++ * if there is nothing but header in the primary segment.
++ */
++ if (unlikely(buf_len == 0)) {
++ num_descs--;
++ i++;
++ continue;
++ }
++
++ /* Setting the MORE bit on the previous Tx descriptor.
++ * Note: We will flush this descriptor as well later.
++ */
++ EDMA_TXDESC_MORE_BIT_SET(txd, 1);
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ txd = EDMA_TXDESC_PRI_DESC(txdesc_ring, *hw_next_to_use);
++ memset(txd, 0, sizeof(struct edma_txdesc_pri));
++ buff_addr = skb_frag_dma_map(dev, frag, 0, buf_len,
++ DMA_TO_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "Unable to dma first descriptor for nr_frags tx\n");
++ *hw_next_to_use = start_hw_next_to_use;
++ *invalid_frag = nr_frags;
++ return 0;
++ }
++
++ EDMA_TXDESC_BUFFER_ADDR_SET(txd, buff_addr);
++ EDMA_TXDESC_DATA_LEN_SET(txd, buf_len);
++ EDMA_TXDESC_POOL_ID_SET(txd, (i + 1));
++
++ *hw_next_to_use = ((*hw_next_to_use + 1) & EDMA_TX_RING_SIZE_MASK);
++ i++;
++ }
++
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ /* This will be the index previous to that of current *hw_next_to_use. */
++ end_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK) & EDMA_TX_RING_SIZE_MASK);
++
++ *txdesc = txd;
++
++ return num_descs;
++}
++
++static void edma_tx_fill_pp_desc(struct edma_port_priv *port_priv,
++ struct edma_txdesc_pri *txd, struct sk_buff *skb,
++ struct edma_port_tx_stats *stats)
++{
++ struct ppe_port *port = port_priv->ppe_port;
++ int port_id = port->port_id;
++
++ /* Offload L3/L4 checksum computation. */
++ if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {
++ EDMA_TXDESC_ADV_OFFLOAD_SET(txd);
++ EDMA_TXDESC_IP_CSUM_SET(txd);
++ EDMA_TXDESC_L4_CSUM_SET(txd);
++ }
++
++ /* Check if the packet needs TSO
++ * This will be mostly true for SG packets.
++ */
++ if (unlikely(skb_is_gso(skb))) {
++ if ((skb_shinfo(skb)->gso_type == SKB_GSO_TCPV4) ||
++ (skb_shinfo(skb)->gso_type == SKB_GSO_TCPV6)) {
++ u32 mss = skb_shinfo(skb)->gso_size;
++
++ /* If MSS<256, HW will do TSO using MSS=256,
++ * if MSS>10K, HW will do TSO using MSS=10K,
++ * else HW will report error 0x200000 in Tx Cmpl.
++ */
++ if (mss < EDMA_TX_TSO_MSS_MIN)
++ mss = EDMA_TX_TSO_MSS_MIN;
++ else if (mss > EDMA_TX_TSO_MSS_MAX)
++ mss = EDMA_TX_TSO_MSS_MAX;
++
++ EDMA_TXDESC_TSO_ENABLE_SET(txd, 1);
++ EDMA_TXDESC_MSS_SET(txd, mss);
++
++ /* Update tso stats. */
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_tso_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ }
++ }
++
++ /* Set destination information in the descriptor. */
++ EDMA_TXDESC_SERVICE_CODE_SET(txd, PPE_EDMA_SC_BYPASS_ID);
++ EDMA_DST_INFO_SET(txd, port_id);
++}
++
++static struct edma_txdesc_pri *edma_tx_skb_first_desc(struct edma_port_priv *port_priv,
++ struct edma_txdesc_ring *txdesc_ring,
++ struct sk_buff *skb, u32 *hw_next_to_use,
++ struct edma_port_tx_stats *stats)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_txdesc_pri *txd = NULL;
++ struct device *dev = ppe_dev->dev;
++ dma_addr_t buff_addr;
++ u32 buf_len = 0;
++
++ /* Get the packet length. */
++ buf_len = skb_headlen(skb);
++ txd = EDMA_TXDESC_PRI_DESC(txdesc_ring, *hw_next_to_use);
++ memset(txd, 0, sizeof(struct edma_txdesc_pri));
++
++ /* Set the data pointer as the buffer address in the descriptor. */
++ buff_addr = dma_map_single(dev, skb->data, buf_len, DMA_TO_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "Unable to dma first descriptor for tx\n");
++ return NULL;
++ }
++
++ EDMA_TXDESC_BUFFER_ADDR_SET(txd, buff_addr);
++ EDMA_TXDESC_POOL_ID_SET(txd, 0);
++ edma_tx_fill_pp_desc(port_priv, txd, skb, stats);
++
++ /* Set packet length in the descriptor. */
++ EDMA_TXDESC_DATA_LEN_SET(txd, buf_len);
++ *hw_next_to_use = (*hw_next_to_use + 1) & EDMA_TX_RING_SIZE_MASK;
++
++ return txd;
++}
++
++static void edma_tx_handle_dma_err(struct sk_buff *skb, u32 num_sg_frag_list)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ struct sk_buff *iter_skb = NULL;
++ u32 cnt_sg_frag_list = 0;
++
++ /* Walk through all fraglist skbs. */
++ skb_walk_frags(skb, iter_skb) {
++ if (skb_headlen(iter_skb)) {
++ dma_unmap_single(dev, virt_to_phys(iter_skb->data),
++ skb_headlen(iter_skb), DMA_TO_DEVICE);
++ cnt_sg_frag_list += 1;
++ }
++
++ if (cnt_sg_frag_list == num_sg_frag_list)
++ return;
++
++ /* skb fraglist skb had nr_frags, unmap that memory. */
++ u32 nr_frags = skb_shinfo(iter_skb)->nr_frags;
++
++ if (nr_frags == 0)
++ continue;
++
++ for (int i = 0; i < nr_frags; i++) {
++ skb_frag_t *frag = &skb_shinfo(iter_skb)->frags[i];
++
++ /* DMA mapping was not done for zero size segments. */
++ if (unlikely(skb_frag_size(frag) == 0))
++ continue;
++
++ dma_unmap_page(dev, virt_to_phys(frag),
++ PAGE_SIZE, DMA_TO_DEVICE);
++ cnt_sg_frag_list += 1;
++ if (cnt_sg_frag_list == num_sg_frag_list)
++ return;
++ }
++ }
++}
++
++static u32 edma_tx_skb_sg_fill_desc(struct edma_txdesc_ring *txdesc_ring,
++ struct edma_txdesc_pri **txdesc,
++ struct sk_buff *skb, u32 *hw_next_to_use,
++ struct edma_port_tx_stats *stats)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 start_hw_next_to_use = 0, invalid_frag = 0;
++ struct edma_txdesc_pri *txd = *txdesc;
++ struct device *dev = ppe_dev->dev;
++ struct sk_buff *iter_skb = NULL;
++ u32 buf_len = 0, num_descs = 0;
++ u32 num_sg_frag_list = 0;
++
++ /* Head skb processed already. */
++ num_descs++;
++
++ if (unlikely(skb_has_frag_list(skb))) {
++ struct edma_txdesc_pri *start_desc = NULL;
++ u32 start_idx = 0, end_idx = 0;
++
++ /* Hold onto the index mapped to txd.
++ * This will be the index previous to that of current *hw_next_to_use.
++ */
++ start_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK)
++ & EDMA_TX_RING_SIZE_MASK);
++ start_desc = txd;
++ start_hw_next_to_use = *hw_next_to_use;
++
++ /* Walk through all fraglist skbs. */
++ skb_walk_frags(skb, iter_skb) {
++ dma_addr_t buff_addr;
++ u32 num_nr_frag = 0;
++
++ /* This case could happen during the packet decapsulation.
++ * All header content might be removed.
++ */
++ buf_len = skb_headlen(iter_skb);
++ if (unlikely(buf_len == 0))
++ goto skip_primary;
++
++ /* We make sure to flush this descriptor later. */
++ EDMA_TXDESC_MORE_BIT_SET(txd, 1);
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ txd = EDMA_TXDESC_PRI_DESC(txdesc_ring, *hw_next_to_use);
++ memset(txd, 0, sizeof(struct edma_txdesc_pri));
++ buff_addr = dma_map_single(dev, iter_skb->data,
++ buf_len, DMA_TO_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "Unable to dma for fraglist\n");
++ goto dma_err;
++ }
++
++ EDMA_TXDESC_BUFFER_ADDR_SET(txd, buff_addr);
++ EDMA_TXDESC_DATA_LEN_SET(txd, buf_len);
++ EDMA_TXDESC_POOL_ID_SET(txd, 0);
++
++ *hw_next_to_use = (*hw_next_to_use + 1) & EDMA_TX_RING_SIZE_MASK;
++ num_descs += 1;
++ num_sg_frag_list += 1;
++
++ /* skb fraglist skb can have nr_frags. */
++skip_primary:
++ if (unlikely(skb_shinfo(iter_skb)->nr_frags)) {
++ num_nr_frag = edma_tx_skb_nr_frags(txdesc_ring, &txd,
++ iter_skb, hw_next_to_use,
++ &invalid_frag);
++ if (unlikely(!num_nr_frag)) {
++ dev_dbg(dev, "No descriptor available for ring %d\n",
++ txdesc_ring->id);
++ edma_tx_dma_unmap_frags(iter_skb, invalid_frag);
++ goto dma_err;
++ }
++
++ num_descs += num_nr_frag;
++ num_sg_frag_list += num_nr_frag;
++
++ /* Update fraglist with nr_frag stats. */
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_fraglist_with_nr_frags_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ }
++ }
++
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ /* This will be the index previous to
++ * that of current *hw_next_to_use.
++ */
++ end_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK) &
++ EDMA_TX_RING_SIZE_MASK);
++
++ /* Update frag_list stats. */
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_fraglist_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ } else {
++ /* Process skb with nr_frags. */
++ num_descs += edma_tx_skb_nr_frags(txdesc_ring, &txd, skb,
++ hw_next_to_use, &invalid_frag);
++ if (unlikely(!num_descs)) {
++ dev_dbg(dev, "No descriptor available for ring %d\n", txdesc_ring->id);
++ edma_tx_dma_unmap_frags(skb, invalid_frag);
++ *txdesc = NULL;
++ return num_descs;
++ }
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_nr_frag_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ dev_dbg(dev, "skb:%p num_descs_filled: %u, nr_frags %u, frag_list fragments %u\n",
++ skb, num_descs, skb_shinfo(skb)->nr_frags, num_sg_frag_list);
++
++ *txdesc = txd;
++
++ return num_descs;
++
++dma_err:
++ if (!num_sg_frag_list)
++ goto reset_state;
++
++ edma_tx_handle_dma_err(skb, num_sg_frag_list);
++
++reset_state:
++ *hw_next_to_use = start_hw_next_to_use;
++ *txdesc = NULL;
++
++ return 0;
++}
++
++static u32 edma_tx_avail_desc(struct edma_txdesc_ring *txdesc_ring,
++ u32 hw_next_to_use)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 data = 0, avail = 0, hw_next_to_clean = 0;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CONS_IDX(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ hw_next_to_clean = data & EDMA_TXDESC_CONS_IDX_MASK;
++
++ avail = EDMA_DESC_AVAIL_COUNT(hw_next_to_clean - 1,
++ hw_next_to_use, EDMA_TX_RING_SIZE);
++
++ return avail;
++}
++
++/**
++ * edma_tx_ring_xmit - Transmit a packet.
++ * @netdev: Netdevice.
++ * @skb: Socket Buffer.
++ * @txdesc_ring: Tx Descriptor ring.
++ * @stats: EDMA Tx Statistics.
++ *
++ * Check for available descriptors, fill the descriptors
++ * and transmit both linear and non linear packets.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++enum edma_tx_status edma_tx_ring_xmit(struct net_device *netdev,
++ struct sk_buff *skb, struct edma_txdesc_ring *txdesc_ring,
++ struct edma_port_tx_stats *stats)
++{
++ struct edma_txdesc_stats *txdesc_stats = &txdesc_ring->txdesc_stats;
++ struct edma_port_priv *port_priv = netdev_priv(netdev);
++ u32 num_tx_desc_needed = 0, num_desc_filled = 0;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct ppe_port *port = port_priv->ppe_port;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_pri *txdesc = NULL;
++ struct device *dev = ppe_dev->dev;
++ int port_id = port->port_id;
++ u32 hw_next_to_use = 0;
++ u32 reg;
++
++ hw_next_to_use = txdesc_ring->prod_idx;
++
++ if (unlikely(!(txdesc_ring->avail_desc))) {
++ txdesc_ring->avail_desc = edma_tx_avail_desc(txdesc_ring,
++ hw_next_to_use);
++ if (unlikely(!txdesc_ring->avail_desc)) {
++ netdev_dbg(netdev, "No available descriptors are present at %d ring\n",
++ txdesc_ring->id);
++
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++ }
++
++ /* Process head skb for linear skb.
++ * Process head skb + nr_frags + fraglist for non linear skb.
++ */
++ if (likely(!skb_is_nonlinear(skb))) {
++ txdesc = edma_tx_skb_first_desc(port_priv, txdesc_ring, skb,
++ &hw_next_to_use, stats);
++ if (unlikely(!txdesc)) {
++ netdev_dbg(netdev, "No descriptor available for ring %d\n",
++ txdesc_ring->id);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++
++ EDMA_TXDESC_ENDIAN_SET(txdesc);
++ num_desc_filled++;
++ } else {
++ num_tx_desc_needed = edma_tx_num_descs_for_sg(skb);
++
++ /* HW does not support TSO for packets with more than 32 segments.
++ * HW hangs up if it sees more than 32 segments. Kernel Perform GSO
++ * for such packets with netdev gso_max_segs set to 32.
++ */
++ if (unlikely(num_tx_desc_needed > EDMA_TX_TSO_SEG_MAX)) {
++ netdev_dbg(netdev, "Number of segments %u more than %u for %d ring\n",
++ num_tx_desc_needed, EDMA_TX_TSO_SEG_MAX, txdesc_ring->id);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->tso_max_seg_exceed;
++ u64_stats_update_end(&txdesc_stats->syncp);
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_tso_drop_pkts++;
++ u64_stats_update_end(&stats->syncp);
++
++ return EDMA_TX_FAIL;
++ }
++
++ if (unlikely(num_tx_desc_needed > txdesc_ring->avail_desc)) {
++ txdesc_ring->avail_desc = edma_tx_avail_desc(txdesc_ring,
++ hw_next_to_use);
++ if (num_tx_desc_needed > txdesc_ring->avail_desc) {
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ netdev_dbg(netdev, "Not enough available descriptors are present at %d ring for SG packet. Needed %d, currently available %d\n",
++ txdesc_ring->id, num_tx_desc_needed,
++ txdesc_ring->avail_desc);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++ }
++
++ txdesc = edma_tx_skb_first_desc(port_priv, txdesc_ring, skb,
++ &hw_next_to_use, stats);
++ if (unlikely(!txdesc)) {
++ netdev_dbg(netdev, "No non-linear descriptor available for ring %d\n",
++ txdesc_ring->id);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++
++ num_desc_filled = edma_tx_skb_sg_fill_desc(txdesc_ring,
++ &txdesc, skb, &hw_next_to_use, stats);
++ if (unlikely(!txdesc)) {
++ netdev_dbg(netdev, "No descriptor available for ring %d\n",
++ txdesc_ring->id);
++ dma_unmap_single(dev, virt_to_phys(skb->data),
++ skb->len, DMA_TO_DEVICE);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++ }
++
++ /* Set the skb pointer to the descriptor's opaque field/s
++ * on the last descriptor of the packet/SG packet.
++ */
++ EDMA_TXDESC_OPAQUE_SET(txdesc, skb);
++
++ /* Update producer index. */
++ txdesc_ring->prod_idx = hw_next_to_use & EDMA_TXDESC_PROD_IDX_MASK;
++ txdesc_ring->avail_desc -= num_desc_filled;
++
++ netdev_dbg(netdev, "%s: skb:%p tx_ring:%u proto:0x%x skb->len:%d\n port:%u prod_idx:%u ip_summed:0x%x\n",
++ netdev->name, skb, txdesc_ring->id, ntohs(skb->protocol),
++ skb->len, port_id, hw_next_to_use, skb->ip_summed);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_PROD_IDX(txdesc_ring->id);
++ regmap_write(regmap, reg, txdesc_ring->prod_idx);
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_pkts++;
++ stats->tx_bytes += skb->len;
++ u64_stats_update_end(&stats->syncp);
++
++ return EDMA_TX_OK;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_tx.h
+@@ -0,0 +1,302 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_TX__
++#define __EDMA_TX__
++
++#include "edma_port.h"
++
++#define EDMA_GET_DESC(R, i, type) (&(((type *)((R)->desc))[(i)]))
++#define EDMA_GET_PDESC(R, i, type) (&(((type *)((R)->pdesc))[(i)]))
++#define EDMA_GET_SDESC(R, i, type) (&(((type *)((R)->sdesc))[(i)]))
++#define EDMA_TXCMPL_DESC(R, i) EDMA_GET_DESC(R, i, \
++ struct edma_txcmpl_desc)
++#define EDMA_TXDESC_PRI_DESC(R, i) EDMA_GET_PDESC(R, i, \
++ struct edma_txdesc_pri)
++#define EDMA_TXDESC_SEC_DESC(R, i) EDMA_GET_SDESC(R, i, \
++ struct edma_txdesc_sec)
++
++#define EDMA_DESC_AVAIL_COUNT(head, tail, _max) ({ \
++ typeof(_max) (max) = (_max); \
++ ((((head) - (tail)) + \
++ (max)) & ((max) - 1)); })
++
++#define EDMA_TX_RING_SIZE 2048
++#define EDMA_TX_RING_SIZE_MASK (EDMA_TX_RING_SIZE - 1)
++
++/* Max segment processing capacity of HW for TSO. */
++#define EDMA_TX_TSO_SEG_MAX 32
++
++/* HW defined low and high MSS size. */
++#define EDMA_TX_TSO_MSS_MIN 256
++#define EDMA_TX_TSO_MSS_MAX 10240
++
++#define EDMA_DST_PORT_TYPE 2
++#define EDMA_DST_PORT_TYPE_SHIFT 28
++#define EDMA_DST_PORT_TYPE_MASK (0xf << EDMA_DST_PORT_TYPE_SHIFT)
++#define EDMA_DST_PORT_ID_SHIFT 16
++#define EDMA_DST_PORT_ID_MASK (0xfff << EDMA_DST_PORT_ID_SHIFT)
++
++#define EDMA_DST_PORT_TYPE_SET(x) (((x) << EDMA_DST_PORT_TYPE_SHIFT) & \
++ EDMA_DST_PORT_TYPE_MASK)
++#define EDMA_DST_PORT_ID_SET(x) (((x) << EDMA_DST_PORT_ID_SHIFT) & \
++ EDMA_DST_PORT_ID_MASK)
++#define EDMA_DST_INFO_SET(desc, x) ((desc)->word4 |= \
++ (EDMA_DST_PORT_TYPE_SET(EDMA_DST_PORT_TYPE) | EDMA_DST_PORT_ID_SET(x)))
++
++#define EDMA_TXDESC_TSO_ENABLE_MASK BIT(24)
++#define EDMA_TXDESC_TSO_ENABLE_SET(desc, x) ((desc)->word5 |= \
++ FIELD_PREP(EDMA_TXDESC_TSO_ENABLE_MASK, x))
++#define EDMA_TXDESC_MSS_MASK GENMASK(31, 16)
++#define EDMA_TXDESC_MSS_SET(desc, x) ((desc)->word6 |= \
++ FIELD_PREP(EDMA_TXDESC_MSS_MASK, x))
++#define EDMA_TXDESC_MORE_BIT_MASK BIT(30)
++#define EDMA_TXDESC_MORE_BIT_SET(desc, x) ((desc)->word1 |= \
++ FIELD_PREP(EDMA_TXDESC_MORE_BIT_MASK, x))
++
++#define EDMA_TXDESC_ADV_OFFSET_BIT BIT(31)
++#define EDMA_TXDESC_ADV_OFFLOAD_SET(desc) ((desc)->word5 |= \
++ FIELD_PREP(EDMA_TXDESC_ADV_OFFSET_BIT, 1))
++#define EDMA_TXDESC_IP_CSUM_BIT BIT(25)
++#define EDMA_TXDESC_IP_CSUM_SET(desc) ((desc)->word5 |= \
++ FIELD_PREP(EDMA_TXDESC_IP_CSUM_BIT, 1))
++
++#define EDMA_TXDESC_L4_CSUM_SET_MASK GENMASK(27, 26)
++#define EDMA_TXDESC_L4_CSUM_SET(desc) ((desc)->word5 |= \
++ (FIELD_PREP(EDMA_TXDESC_L4_CSUM_SET_MASK, 1)))
++
++#define EDMA_TXDESC_POOL_ID_SET_MASK GENMASK(24, 18)
++#define EDMA_TXDESC_POOL_ID_SET(desc, x) ((desc)->word5 |= \
++ (FIELD_PREP(EDMA_TXDESC_POOL_ID_SET_MASK, x)))
++
++#define EDMA_TXDESC_DATA_LEN_SET(desc, x) ((desc)->word5 |= ((x) & 0x1ffff))
++#define EDMA_TXDESC_SERVICE_CODE_MASK GENMASK(24, 16)
++#define EDMA_TXDESC_SERVICE_CODE_SET(desc, x) ((desc)->word1 |= \
++ (FIELD_PREP(EDMA_TXDESC_SERVICE_CODE_MASK, x)))
++#define EDMA_TXDESC_BUFFER_ADDR_SET(desc, addr) (((desc)->word0) = (addr))
++
++#ifdef __LP64__
++#define EDMA_TXDESC_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ (((u64)(desc)->word3 << 32) | (desc)->word2); })
++
++#define EDMA_TXCMPL_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ (((u64)(desc)->word1 << 32) | \
++ (desc)->word0); })
++
++#define EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr) ((desc)->word2 = \
++ (u32)(uintptr_t)(ptr))
++
++#define EDMA_TXDESC_OPAQUE_HI_SET(desc, ptr) ((desc)->word3 = \
++ (u32)((u64)(ptr) >> 32))
++
++#define EDMA_TXDESC_OPAQUE_SET(_desc, _ptr) do { \
++ typeof(_desc) (desc) = (_desc); \
++ typeof(_ptr) (ptr) = (_ptr); \
++ EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr); \
++ EDMA_TXDESC_OPAQUE_HI_SET(desc, ptr); \
++} while (0)
++#else
++#define EDMA_TXCMPL_OPAQUE_GET(desc) ((desc)->word0)
++#define EDMA_TXDESC_OPAQUE_GET(desc) ((desc)->word2)
++#define EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr) ((desc)->word2 = (u32)(uintptr_t)ptr)
++
++#define EDMA_TXDESC_OPAQUE_SET(desc, ptr) \
++ EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr)
++#endif
++#define EDMA_TXCMPL_MORE_BIT_MASK BIT(30)
++
++#define EDMA_TXCMPL_MORE_BIT_GET(desc) ((le32_to_cpu((__force __le32)((desc)->word2))) & \
++ EDMA_TXCMPL_MORE_BIT_MASK)
++
++#define EDMA_TXCOMP_RING_ERROR_MASK GENMASK(22, 0)
++
++#define EDMA_TXCOMP_RING_ERROR_GET(x) ((le32_to_cpu((__force __le32)x)) & \
++ EDMA_TXCOMP_RING_ERROR_MASK)
++
++#define EDMA_TXCOMP_POOL_ID_MASK GENMASK(5, 0)
++
++#define EDMA_TXCOMP_POOL_ID_GET(desc) ((le32_to_cpu((__force __le32)((desc)->word2))) & \
++ EDMA_TXCOMP_POOL_ID_MASK)
++
++/* Opaque values are set in word2 and word3,
++ * they are not accessed by the EDMA HW,
++ * so endianness conversion is not needed.
++ */
++#define EDMA_TXDESC_ENDIAN_SET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ cpu_to_le32s(&((desc)->word0)); \
++ cpu_to_le32s(&((desc)->word1)); \
++ cpu_to_le32s(&((desc)->word4)); \
++ cpu_to_le32s(&((desc)->word5)); \
++ cpu_to_le32s(&((desc)->word6)); \
++ cpu_to_le32s(&((desc)->word7)); \
++})
++
++/* EDMA Tx GSO status */
++enum edma_tx_status {
++ EDMA_TX_OK = 0, /* Tx success. */
++ EDMA_TX_FAIL_NO_DESC = 1, /* Not enough descriptors. */
++ EDMA_TX_FAIL = 2, /* Tx failure. */
++};
++
++/* EDMA TX GSO status */
++enum edma_tx_gso_status {
++ EDMA_TX_GSO_NOT_NEEDED = 0,
++ /* Packet has segment count less than TX_TSO_SEG_MAX. */
++ EDMA_TX_GSO_SUCCEED = 1,
++ /* GSO Succeed. */
++ EDMA_TX_GSO_FAIL = 2,
++ /* GSO failed, drop the packet. */
++};
++
++/**
++ * struct edma_txcmpl_stats - EDMA TX complete ring statistics.
++ * @invalid_buffer: Invalid buffer address received.
++ * @errors: Other Tx complete descriptor errors indicated by the hardware.
++ * @desc_with_more_bit: Packet's segment transmit count.
++ * @no_pending_desc: No descriptor is pending for processing.
++ * @syncp: Synchronization pointer.
++ */
++struct edma_txcmpl_stats {
++ u64 invalid_buffer;
++ u64 errors;
++ u64 desc_with_more_bit;
++ u64 no_pending_desc;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_txdesc_stats - EDMA Tx descriptor ring statistics.
++ * @no_desc_avail: No descriptor available to transmit.
++ * @tso_max_seg_exceed: Packets extending EDMA_TX_TSO_SEG_MAX segments.
++ * @syncp: Synchronization pointer.
++ */
++struct edma_txdesc_stats {
++ u64 no_desc_avail;
++ u64 tso_max_seg_exceed;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_txdesc_pri - EDMA primary TX descriptor.
++ * @word0: Low 32-bit of buffer address.
++ * @word1: Buffer recycling, PTP tag flag, PRI valid flag.
++ * @word2: Low 32-bit of opaque value.
++ * @word3: High 32-bit of opaque value.
++ * @word4: Source/Destination port info.
++ * @word5: VLAN offload, csum mode, ip_csum_en, tso_en, data len.
++ * @word6: MSS/hash_value/PTP tag, data offset.
++ * @word7: L4/L3 offset, PROT type, L2 type, CVLAN/SVLAN tag, service code.
++ */
++struct edma_txdesc_pri {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++/**
++ * struct edma_txdesc_sec - EDMA secondary TX descriptor.
++ * @word0: Reserved.
++ * @word1: Custom csum offset, payload offset, TTL/NAT action.
++ * @word2: NAPT translated port, DSCP value, TTL value.
++ * @word3: Flow index value and valid flag.
++ * @word4: Reserved.
++ * @word5: Reserved.
++ * @word6: CVLAN/SVLAN command.
++ * @word7: CVLAN/SVLAN tag value.
++ */
++struct edma_txdesc_sec {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++/**
++ * struct edma_txcmpl_desc - EDMA TX complete descriptor.
++ * @word0: Low 32-bit opaque value.
++ * @word1: High 32-bit opaque value.
++ * @word2: More fragment, transmit ring id, pool id.
++ * @word3: Error indications.
++ */
++struct edma_txcmpl_desc {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++};
++
++/**
++ * struct edma_txdesc_ring - EDMA TX descriptor ring
++ * @prod_idx: Producer index
++ * @id: Tx ring number
++ * @avail_desc: Number of available descriptor to process
++ * @pdesc: Primary descriptor ring virtual address
++ * @pdma: Primary descriptor ring physical address
++ * @sdesc: Secondary descriptor ring virtual address
++ * @tx_desc_stats: Tx descriptor ring statistics
++ * @sdma: Secondary descriptor ring physical address
++ * @count: Number of descriptors
++ * @fc_grp_id: Flow control group ID
++ */
++struct edma_txdesc_ring {
++ u32 prod_idx;
++ u32 id;
++ u32 avail_desc;
++ struct edma_txdesc_pri *pdesc;
++ dma_addr_t pdma;
++ struct edma_txdesc_sec *sdesc;
++ struct edma_txdesc_stats txdesc_stats;
++ dma_addr_t sdma;
++ u32 count;
++ u8 fc_grp_id;
++};
++
++/**
++ * struct edma_txcmpl_ring - EDMA TX complete ring
++ * @napi: NAPI
++ * @cons_idx: Consumer index
++ * @avail_pkt: Number of available packets to process
++ * @desc: Descriptor ring virtual address
++ * @id: Txcmpl ring number
++ * @tx_cmpl_stats: Tx complete ring statistics
++ * @dma: Descriptor ring physical address
++ * @count: Number of descriptors in the ring
++ * @napi_added: Flag to indicate NAPI add status
++ */
++struct edma_txcmpl_ring {
++ struct napi_struct napi;
++ u32 cons_idx;
++ u32 avail_pkt;
++ struct edma_txcmpl_desc *desc;
++ u32 id;
++ struct edma_txcmpl_stats txcmpl_stats;
++ dma_addr_t dma;
++ u32 count;
++ bool napi_added;
++};
++
++enum edma_tx_status edma_tx_ring_xmit(struct net_device *netdev,
++ struct sk_buff *skb,
++ struct edma_txdesc_ring *txdesc_ring,
++ struct edma_port_tx_stats *stats);
++u32 edma_tx_complete(u32 work_to_do,
++ struct edma_txcmpl_ring *txcmpl_ring);
++irqreturn_t edma_tx_handle_irq(int irq, void *ctx);
++int edma_tx_napi_poll(struct napi_struct *napi, int budget);
++enum edma_tx_gso_status edma_tx_gso_segment(struct sk_buff *skb,
++ struct net_device *netdev, struct sk_buff **segs);
++
++#endif
--- /dev/null
+From 8a924457c0b71acee96c8f78ef386e2a354a2aca Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:31:04 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add miscellaneous error interrupts
+ and counters
+
+Miscellaneous error interrupts, EDMA Tx/Rx and error counters are supported
+using debugfs framework.
+
+Change-Id: I7da8b978a7e93947b03a45269a81b401f35da31c
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 162 ++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 30 ++
+ .../net/ethernet/qualcomm/ppe/edma_debugfs.c | 370 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/ppe_debugfs.c | 17 +
+ 5 files changed, 580 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_debugfs.c
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_port.o edma_rx.o edma_tx.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_debugfs.o edma_port.o edma_rx.o edma_tx.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -152,6 +152,42 @@ static int edma_clock_init(void)
+ }
+
+ /**
++ * edma_err_stats_alloc - Allocate stats memory
++ *
++ * Allocate memory for per-CPU error stats.
++ */
++int edma_err_stats_alloc(void)
++{
++ u32 i;
++
++ edma_ctx->err_stats = alloc_percpu(*edma_ctx->err_stats);
++ if (!edma_ctx->err_stats)
++ return -ENOMEM;
++
++ for_each_possible_cpu(i) {
++ struct edma_err_stats *stats;
++
++ stats = per_cpu_ptr(edma_ctx->err_stats, i);
++ u64_stats_init(&stats->syncp);
++ }
++
++ return 0;
++}
++
++/**
++ * edma_err_stats_free - Free stats memory
++ *
++ * Free memory of per-CPU error stats.
++ */
++void edma_err_stats_free(void)
++{
++ if (edma_ctx->err_stats) {
++ free_percpu(edma_ctx->err_stats);
++ edma_ctx->err_stats = NULL;
++ }
++}
++
++/**
+ * edma_configure_ucast_prio_map_tbl - Configure unicast priority map table.
+ *
+ * Map int_priority values to priority class and initialize
+@@ -191,11 +227,113 @@ static int edma_configure_ucast_prio_map
+ return ret;
+ }
+
++static void edma_disable_misc_interrupt(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MISC_INT_MASK_ADDR;
++ regmap_write(regmap, reg, EDMA_MASK_INT_CLEAR);
++}
++
++static void edma_enable_misc_interrupt(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MISC_INT_MASK_ADDR;
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_misc);
++}
++
++static irqreturn_t edma_misc_handle_irq(int irq,
++ __maybe_unused void *ctx)
++{
++ struct edma_err_stats *stats = this_cpu_ptr(edma_ctx->err_stats);
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 misc_intr_status, data, reg;
++
++ /* Read Misc intr status */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MISC_INT_STAT_ADDR;
++ regmap_read(regmap, reg, &data);
++ misc_intr_status = data & edma_ctx->intr_info.intr_mask_misc;
++
++ pr_debug("Received misc irq %d, status: %d\n", irq, misc_intr_status);
++
++ if (FIELD_GET(EDMA_MISC_AXI_RD_ERR_MASK, misc_intr_status)) {
++ pr_err("MISC AXI read error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_axi_read_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_AXI_WR_ERR_MASK, misc_intr_status)) {
++ pr_err("MISC AXI write error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_axi_write_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_RX_DESC_FIFO_FULL_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Rx descriptor fifo full error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_rxdesc_fifo_full;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_RX_ERR_BUF_SIZE_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Rx buffer size error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_rx_buf_size_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_TX_SRAM_FULL_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Tx SRAM full error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_tx_sram_full;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_TX_CMPL_BUF_FULL_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Tx complete buffer full error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_txcmpl_buf_full;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_DATA_LEN_ERR_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC data length error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_tx_data_len_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_TX_TIMEOUT_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Tx timeout error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_tx_timeout;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ return IRQ_HANDLED;
++}
++
+ static int edma_irq_register(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
+ struct edma_ring_info *rx = hw_info->rx;
++ struct device *dev = ppe_dev->dev;
+ int ret;
+ u32 i;
+
+@@ -270,8 +408,25 @@ static int edma_irq_register(void)
+ edma_rxdesc_irq_name[i]);
+ }
+
++ /* Request Misc IRQ */
++ ret = request_irq(edma_ctx->intr_info.intr_misc, edma_misc_handle_irq,
++ IRQF_SHARED, "edma_misc",
++ (void *)dev);
++ if (ret) {
++ pr_err("MISC IRQ:%d request failed\n",
++ edma_ctx->intr_info.intr_misc);
++ goto misc_intr_req_fail;
++ }
++
+ return 0;
+
++misc_intr_req_fail:
++ /* Free IRQ for RXDESC rings */
++ for (i = 0; i < rx->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_rx[i]);
++ free_irq(edma_ctx->intr_info.intr_rx[i],
++ (void *)&edma_ctx->rx_rings[i]);
++ }
+ rx_desc_ring_intr_req_fail:
+ for (i = 0; i < rx->num_rings; i++)
+ kfree(edma_rxdesc_irq_name[i]);
+@@ -503,6 +658,7 @@ static int edma_hw_configure(void)
+ edma_cfg_tx_disable_interrupts(i);
+
+ edma_cfg_rx_disable_interrupts();
++ edma_disable_misc_interrupt();
+
+ edma_cfg_rx_rings_disable();
+
+@@ -614,6 +770,7 @@ void edma_destroy(struct ppe_device *ppe
+ edma_cfg_tx_disable_interrupts(i);
+
+ edma_cfg_rx_disable_interrupts();
++ edma_disable_misc_interrupt();
+
+ /* Free IRQ for TXCMPL rings. */
+ for (i = 0; i < txcmpl->num_rings; i++) {
+@@ -634,6 +791,10 @@ void edma_destroy(struct ppe_device *ppe
+ }
+ kfree(edma_rxdesc_irq_name);
+
++ /* Free Misc IRQ */
++ synchronize_irq(edma_ctx->intr_info.intr_misc);
++ free_irq(edma_ctx->intr_info.intr_misc, (void *)(ppe_dev->dev));
++
+ kfree(edma_ctx->intr_info.intr_rx);
+ kfree(edma_ctx->intr_info.intr_txcmpl);
+
+@@ -699,6 +860,7 @@ int edma_setup(struct ppe_device *ppe_de
+ }
+
+ edma_cfg_rx_enable_interrupts();
++ edma_enable_misc_interrupt();
+
+ dev_info(dev, "EDMA configuration successful\n");
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -47,6 +47,30 @@ enum ppe_queue_class_type {
+ };
+
+ /**
++ * struct edma_err_stats - EDMA error stats
++ * @edma_axi_read_err: AXI read error
++ * @edma_axi_write_err: AXI write error
++ * @edma_rxdesc_fifo_full: Rx desc FIFO full error
++ * @edma_rx_buf_size_err: Rx buffer size too small error
++ * @edma_tx_sram_full: Tx packet SRAM buffer full error
++ * @edma_tx_data_len_err: Tx data length error
++ * @edma_tx_timeout: Tx timeout error
++ * @edma_txcmpl_buf_full: Tx completion buffer full error
++ * @syncp: Synchronization pointer
++ */
++struct edma_err_stats {
++ u64 edma_axi_read_err;
++ u64 edma_axi_write_err;
++ u64 edma_rxdesc_fifo_full;
++ u64 edma_rx_buf_size_err;
++ u64 edma_tx_sram_full;
++ u64 edma_tx_data_len_err;
++ u64 edma_tx_timeout;
++ u64 edma_txcmpl_buf_full;
++ struct u64_stats_sync syncp;
++};
++
++/**
+ * struct edma_ring_info - EDMA ring data structure.
+ * @max_rings: Maximum number of rings
+ * @ring_start: Ring start ID
+@@ -107,6 +131,7 @@ struct edma_intr_info {
+ * @rx_rings: Rx Desc Rings, SW is consumer
+ * @tx_rings: Tx Descriptor Ring, SW is producer
+ * @txcmpl_rings: Tx complete Ring, SW is consumer
++ * @err_stats: Per CPU error statistics
+ * @rx_page_mode: Page mode enabled or disabled
+ * @rx_buf_size: Rx buffer size for Jumbo MRU
+ * @tx_requeue_stop: Tx requeue stop enabled or disabled
+@@ -121,6 +146,7 @@ struct edma_context {
+ struct edma_rxdesc_ring *rx_rings;
+ struct edma_txdesc_ring *tx_rings;
+ struct edma_txcmpl_ring *txcmpl_rings;
++ struct edma_err_stats __percpu *err_stats;
+ u32 rx_page_mode;
+ u32 rx_buf_size;
+ bool tx_requeue_stop;
+@@ -129,8 +155,12 @@ struct edma_context {
+ /* Global EDMA context */
+ extern struct edma_context *edma_ctx;
+
++int edma_err_stats_alloc(void);
++void edma_err_stats_free(void);
+ void edma_destroy(struct ppe_device *ppe_dev);
+ int edma_setup(struct ppe_device *ppe_dev);
++void edma_debugfs_teardown(void);
++int edma_debugfs_setup(struct ppe_device *ppe_dev);
+ int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
+ enum ppe_queue_class_type class,
+ int index, int queue_offset);
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_debugfs.c
+@@ -0,0 +1,370 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* EDMA debugfs routines for display of Tx/Rx counters. */
++
++#include <linux/cpumask.h>
++#include <linux/debugfs.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/printk.h>
++
++#include "edma.h"
++
++#define EDMA_STATS_BANNER_MAX_LEN 80
++#define EDMA_RX_RING_STATS_NODE_NAME "EDMA_RX"
++#define EDMA_TX_RING_STATS_NODE_NAME "EDMA_TX"
++#define EDMA_ERR_STATS_NODE_NAME "EDMA_ERR"
++
++static struct dentry *edma_dentry;
++static struct dentry *stats_dentry;
++
++static void edma_debugfs_print_banner(struct seq_file *m, char *node)
++{
++ u32 banner_char_len, i;
++
++ for (i = 0; i < EDMA_STATS_BANNER_MAX_LEN; i++)
++ seq_puts(m, "_");
++ banner_char_len = (EDMA_STATS_BANNER_MAX_LEN - (strlen(node) + 2)) / 2;
++ seq_puts(m, "\n\n");
++
++ for (i = 0; i < banner_char_len; i++)
++ seq_puts(m, "<");
++ seq_printf(m, " %s ", node);
++
++ for (i = 0; i < banner_char_len; i++)
++ seq_puts(m, ">");
++ seq_puts(m, "\n");
++
++ for (i = 0; i < EDMA_STATS_BANNER_MAX_LEN; i++)
++ seq_puts(m, "_");
++ seq_puts(m, "\n\n");
++}
++
++static int edma_debugfs_rx_rings_stats_show(struct seq_file *m,
++ void __maybe_unused *p)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_rxfill_stats *rxfill_stats;
++ struct edma_rxdesc_stats *rxdesc_stats;
++ struct edma_ring_info *rx = hw_info->rx;
++ unsigned int start;
++ u32 i;
++
++ rxfill_stats = kcalloc(rxfill->num_rings, sizeof(*rxfill_stats), GFP_KERNEL);
++ if (!rxfill_stats)
++ return -ENOMEM;
++
++ rxdesc_stats = kcalloc(rx->num_rings, sizeof(*rxdesc_stats), GFP_KERNEL);
++ if (!rxdesc_stats) {
++ kfree(rxfill_stats);
++ return -ENOMEM;
++ }
++
++ /* Get stats for Rx fill rings. */
++ for (i = 0; i < rxfill->num_rings; i++) {
++ struct edma_rxfill_ring *rxfill_ring;
++ struct edma_rxfill_stats *stats;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[i];
++ stats = &rxfill_ring->rxfill_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ rxfill_stats[i].alloc_failed = stats->alloc_failed;
++ rxfill_stats[i].page_alloc_failed = stats->page_alloc_failed;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ /* Get stats for Rx Desc rings. */
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++ struct edma_rxdesc_stats *stats;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++ stats = &rxdesc_ring->rxdesc_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ rxdesc_stats[i].src_port_inval = stats->src_port_inval;
++ rxdesc_stats[i].src_port_inval_type = stats->src_port_inval_type;
++ rxdesc_stats[i].src_port_inval_netdev = stats->src_port_inval_netdev;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ edma_debugfs_print_banner(m, EDMA_RX_RING_STATS_NODE_NAME);
++
++ seq_puts(m, "\n#EDMA RX descriptor rings stats:\n\n");
++ for (i = 0; i < rx->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA RX descriptor %d ring stats:\n", i + rx->ring_start);
++ seq_printf(m, "\t\t rxdesc[%d]:src_port_inval = %llu\n",
++ i + rx->ring_start, rxdesc_stats[i].src_port_inval);
++ seq_printf(m, "\t\t rxdesc[%d]:src_port_inval_type = %llu\n",
++ i + rx->ring_start, rxdesc_stats[i].src_port_inval_type);
++ seq_printf(m, "\t\t rxdesc[%d]:src_port_inval_netdev = %llu\n",
++ i + rx->ring_start,
++ rxdesc_stats[i].src_port_inval_netdev);
++ seq_puts(m, "\n");
++ }
++
++ seq_puts(m, "\n#EDMA RX fill rings stats:\n\n");
++ for (i = 0; i < rxfill->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA RX fill %d ring stats:\n", i + rxfill->ring_start);
++ seq_printf(m, "\t\t rxfill[%d]:alloc_failed = %llu\n",
++ i + rxfill->ring_start, rxfill_stats[i].alloc_failed);
++ seq_printf(m, "\t\t rxfill[%d]:page_alloc_failed = %llu\n",
++ i + rxfill->ring_start, rxfill_stats[i].page_alloc_failed);
++ seq_puts(m, "\n");
++ }
++
++ kfree(rxfill_stats);
++ kfree(rxdesc_stats);
++ return 0;
++}
++
++static int edma_debugfs_tx_rings_stats_show(struct seq_file *m,
++ void __maybe_unused *p)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ struct edma_txcmpl_stats *txcmpl_stats;
++ struct edma_txdesc_stats *txdesc_stats;
++ unsigned int start;
++ u32 i;
++
++ txcmpl_stats = kcalloc(txcmpl->num_rings, sizeof(*txcmpl_stats), GFP_KERNEL);
++ if (!txcmpl_stats)
++ return -ENOMEM;
++
++ txdesc_stats = kcalloc(tx->num_rings, sizeof(*txdesc_stats), GFP_KERNEL);
++ if (!txdesc_stats) {
++ kfree(txcmpl_stats);
++ return -ENOMEM;
++ }
++
++ /* Get stats for Tx desc rings. */
++ for (i = 0; i < tx->num_rings; i++) {
++ struct edma_txdesc_ring *txdesc_ring;
++ struct edma_txdesc_stats *stats;
++
++ txdesc_ring = &edma_ctx->tx_rings[i];
++ stats = &txdesc_ring->txdesc_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ txdesc_stats[i].no_desc_avail = stats->no_desc_avail;
++ txdesc_stats[i].tso_max_seg_exceed = stats->tso_max_seg_exceed;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ /* Get stats for Tx Complete rings. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ struct edma_txcmpl_ring *txcmpl_ring;
++ struct edma_txcmpl_stats *stats;
++
++ txcmpl_ring = &edma_ctx->txcmpl_rings[i];
++ stats = &txcmpl_ring->txcmpl_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ txcmpl_stats[i].invalid_buffer = stats->invalid_buffer;
++ txcmpl_stats[i].errors = stats->errors;
++ txcmpl_stats[i].desc_with_more_bit = stats->desc_with_more_bit;
++ txcmpl_stats[i].no_pending_desc = stats->no_pending_desc;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ edma_debugfs_print_banner(m, EDMA_TX_RING_STATS_NODE_NAME);
++
++ seq_puts(m, "\n#EDMA TX complete rings stats:\n\n");
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA TX complete %d ring stats:\n", i + txcmpl->ring_start);
++ seq_printf(m, "\t\t txcmpl[%d]:invalid_buffer = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].invalid_buffer);
++ seq_printf(m, "\t\t txcmpl[%d]:errors = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].errors);
++ seq_printf(m, "\t\t txcmpl[%d]:desc_with_more_bit = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].desc_with_more_bit);
++ seq_printf(m, "\t\t txcmpl[%d]:no_pending_desc = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].no_pending_desc);
++ seq_puts(m, "\n");
++ }
++
++ seq_puts(m, "\n#EDMA TX descriptor rings stats:\n\n");
++ for (i = 0; i < tx->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA TX descriptor %d ring stats:\n", i + tx->ring_start);
++ seq_printf(m, "\t\t txdesc[%d]:no_desc_avail = %llu\n",
++ i + tx->ring_start, txdesc_stats[i].no_desc_avail);
++ seq_printf(m, "\t\t txdesc[%d]:tso_max_seg_exceed = %llu\n",
++ i + tx->ring_start, txdesc_stats[i].tso_max_seg_exceed);
++ seq_puts(m, "\n");
++ }
++
++ kfree(txcmpl_stats);
++ kfree(txdesc_stats);
++ return 0;
++}
++
++static int edma_debugfs_err_stats_show(struct seq_file *m,
++ void __maybe_unused *p)
++{
++ struct edma_err_stats *err_stats, *pcpu_err_stats;
++ unsigned int start;
++ u32 cpu;
++
++ err_stats = kzalloc(sizeof(*err_stats), GFP_KERNEL);
++ if (!err_stats)
++ return -ENOMEM;
++
++ /* Get percpu EDMA miscellaneous stats. */
++ for_each_possible_cpu(cpu) {
++ pcpu_err_stats = per_cpu_ptr(edma_ctx->err_stats, cpu);
++ do {
++ start = u64_stats_fetch_begin(&pcpu_err_stats->syncp);
++ err_stats->edma_axi_read_err +=
++ pcpu_err_stats->edma_axi_read_err;
++ err_stats->edma_axi_write_err +=
++ pcpu_err_stats->edma_axi_write_err;
++ err_stats->edma_rxdesc_fifo_full +=
++ pcpu_err_stats->edma_rxdesc_fifo_full;
++ err_stats->edma_rx_buf_size_err +=
++ pcpu_err_stats->edma_rx_buf_size_err;
++ err_stats->edma_tx_sram_full +=
++ pcpu_err_stats->edma_tx_sram_full;
++ err_stats->edma_tx_data_len_err +=
++ pcpu_err_stats->edma_tx_data_len_err;
++ err_stats->edma_tx_timeout +=
++ pcpu_err_stats->edma_tx_timeout;
++ err_stats->edma_txcmpl_buf_full +=
++ pcpu_err_stats->edma_txcmpl_buf_full;
++ } while (u64_stats_fetch_retry(&pcpu_err_stats->syncp, start));
++ }
++
++ edma_debugfs_print_banner(m, EDMA_ERR_STATS_NODE_NAME);
++
++ seq_puts(m, "\n#EDMA error stats:\n\n");
++ seq_printf(m, "\t\t axi read error = %llu\n",
++ err_stats->edma_axi_read_err);
++ seq_printf(m, "\t\t axi write error = %llu\n",
++ err_stats->edma_axi_write_err);
++ seq_printf(m, "\t\t Rx descriptor fifo full = %llu\n",
++ err_stats->edma_rxdesc_fifo_full);
++ seq_printf(m, "\t\t Rx buffer size error = %llu\n",
++ err_stats->edma_rx_buf_size_err);
++ seq_printf(m, "\t\t Tx SRAM full = %llu\n",
++ err_stats->edma_tx_sram_full);
++ seq_printf(m, "\t\t Tx data length error = %llu\n",
++ err_stats->edma_tx_data_len_err);
++ seq_printf(m, "\t\t Tx timeout = %llu\n",
++ err_stats->edma_tx_timeout);
++ seq_printf(m, "\t\t Tx completion buffer full = %llu\n",
++ err_stats->edma_txcmpl_buf_full);
++
++ kfree(err_stats);
++ return 0;
++}
++
++static int edma_debugs_rx_rings_stats_open(struct inode *inode,
++ struct file *file)
++{
++ return single_open(file, edma_debugfs_rx_rings_stats_show,
++ inode->i_private);
++}
++
++static const struct file_operations edma_debugfs_rx_rings_file_ops = {
++ .open = edma_debugs_rx_rings_stats_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release
++};
++
++static int edma_debugs_tx_rings_stats_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, edma_debugfs_tx_rings_stats_show, inode->i_private);
++}
++
++static const struct file_operations edma_debugfs_tx_rings_file_ops = {
++ .open = edma_debugs_tx_rings_stats_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release
++};
++
++static int edma_debugs_err_stats_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, edma_debugfs_err_stats_show, inode->i_private);
++}
++
++static const struct file_operations edma_debugfs_misc_file_ops = {
++ .open = edma_debugs_err_stats_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release
++};
++
++/**
++ * edma_debugfs_teardown - EDMA debugfs teardown.
++ *
++ * EDMA debugfs teardown and free stats memory.
++ */
++void edma_debugfs_teardown(void)
++{
++ /* Free EDMA miscellaneous stats memory */
++ edma_err_stats_free();
++
++ debugfs_remove_recursive(edma_dentry);
++ edma_dentry = NULL;
++ stats_dentry = NULL;
++}
++
++/**
++ * edma_debugfs_setup - EDMA debugfs setup.
++ * @ppe_dev: PPE Device
++ *
++ * EDMA debugfs setup.
++ */
++int edma_debugfs_setup(struct ppe_device *ppe_dev)
++{
++ edma_dentry = debugfs_create_dir("edma", ppe_dev->debugfs_root);
++ if (!edma_dentry) {
++ pr_err("Unable to create debugfs edma directory in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ stats_dentry = debugfs_create_dir("stats", edma_dentry);
++ if (!stats_dentry) {
++ pr_err("Unable to create debugfs stats directory in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ if (!debugfs_create_file("rx_ring_stats", 0444, stats_dentry,
++ NULL, &edma_debugfs_rx_rings_file_ops)) {
++ pr_err("Unable to create Rx rings statistics file entry in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ if (!debugfs_create_file("tx_ring_stats", 0444, stats_dentry,
++ NULL, &edma_debugfs_tx_rings_file_ops)) {
++ pr_err("Unable to create Tx rings statistics file entry in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ /* Allocate memory for EDMA miscellaneous stats */
++ if (edma_err_stats_alloc() < 0) {
++ pr_err("Unable to allocate miscellaneous percpu stats\n");
++ goto debugfs_dir_failed;
++ }
++
++ if (!debugfs_create_file("err_stats", 0444, stats_dentry,
++ NULL, &edma_debugfs_misc_file_ops)) {
++ pr_err("Unable to create EDMA miscellaneous statistics file entry in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ return 0;
++
++debugfs_dir_failed:
++ debugfs_remove_recursive(edma_dentry);
++ edma_dentry = NULL;
++ stats_dentry = NULL;
++ return -ENOMEM;
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.c
+@@ -7,9 +7,11 @@
+
+ #include <linux/bitfield.h>
+ #include <linux/debugfs.h>
++#include <linux/netdevice.h>
+ #include <linux/regmap.h>
+ #include <linux/seq_file.h>
+
++#include "edma.h"
+ #include "ppe.h"
+ #include "ppe_config.h"
+ #include "ppe_debugfs.h"
+@@ -678,15 +680,30 @@ static const struct file_operations ppe_
+
+ void ppe_debugfs_setup(struct ppe_device *ppe_dev)
+ {
++ int ret;
++
+ ppe_dev->debugfs_root = debugfs_create_dir("ppe", NULL);
+ debugfs_create_file("packet_counters", 0444,
+ ppe_dev->debugfs_root,
+ ppe_dev,
+ &ppe_debugfs_packet_counter_fops);
++
++ if (!ppe_dev->debugfs_root) {
++ dev_err(ppe_dev->dev, "Error in PPE debugfs setup\n");
++ return;
++ }
++
++ ret = edma_debugfs_setup(ppe_dev);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Error in EDMA debugfs setup API. ret: %d\n", ret);
++ debugfs_remove_recursive(ppe_dev->debugfs_root);
++ ppe_dev->debugfs_root = NULL;
++ }
+ }
+
+ void ppe_debugfs_teardown(struct ppe_device *ppe_dev)
+ {
++ edma_debugfs_teardown();
+ debugfs_remove_recursive(ppe_dev->debugfs_root);
+ ppe_dev->debugfs_root = NULL;
+ }
--- /dev/null
+From bd61a680fb657eb65272225f18c93fe338c700da Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Thu, 30 May 2024 20:46:36 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add ethtool support for EDMA
+
+ethtool ops can be used for EDMA netdevice configuration and statistics.
+
+Change-Id: I57fc19415dacbe51fed000520336463938220609
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Alex G: use struct ethtool_keee instead of ethtool_eee
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 1 +
+ .../net/ethernet/qualcomm/ppe/edma_ethtool.c | 294 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 1 +
+ 4 files changed, 297 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_ethtool.c
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_debugfs.o edma_port.o edma_rx.o edma_tx.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_debugfs.o edma_port.o edma_rx.o edma_tx.o edma_ethtool.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -161,6 +161,7 @@ void edma_destroy(struct ppe_device *ppe
+ int edma_setup(struct ppe_device *ppe_dev);
+ void edma_debugfs_teardown(void);
+ int edma_debugfs_setup(struct ppe_device *ppe_dev);
++void edma_set_ethtool_ops(struct net_device *netdev);
+ int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
+ enum ppe_queue_class_type class,
+ int index, int queue_offset);
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_ethtool.c
+@@ -0,0 +1,294 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* ethtool support for EDMA */
++
++#include <linux/cpumask.h>
++#include <linux/ethtool.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/phylink.h>
++
++#include "edma.h"
++#include "edma_port.h"
++
++struct edma_ethtool_stats {
++ u8 stat_string[ETH_GSTRING_LEN];
++ u32 stat_offset;
++};
++
++/**
++ * struct edma_gmac_stats - Per-GMAC statistics.
++ * @rx_packets: Number of RX packets
++ * @rx_bytes: Number of RX bytes
++ * @rx_dropped: Number of RX dropped packets
++ * @rx_fraglist_packets: Number of RX fraglist packets
++ * @rx_nr_frag_packets: Number of RX nr fragment packets
++ * @rx_nr_frag_headroom_err: Number of RX nr fragment packets with headroom error
++ * @tx_packets: Number of TX packets
++ * @tx_bytes: Number of TX bytes
++ * @tx_dropped: Number of TX dropped packets
++ * @tx_nr_frag_packets: Number of TX nr fragment packets
++ * @tx_fraglist_packets: Number of TX fraglist packets
++ * @tx_fraglist_with_nr_frags_packets: Number of TX fraglist packets with nr fragments
++ * @tx_tso_packets: Number of TX TCP segmentation offload packets
++ * @tx_tso_drop_packets: Number of TX TCP segmentation dropped packets
++ * @tx_gso_packets: Number of TX SW GSO packets
++ * @tx_gso_drop_packets: Number of TX SW GSO dropped packets
++ * @tx_queue_stopped: Number of times Queue got stopped
++ */
++struct edma_gmac_stats {
++ u64 rx_packets;
++ u64 rx_bytes;
++ u64 rx_dropped;
++ u64 rx_fraglist_packets;
++ u64 rx_nr_frag_packets;
++ u64 rx_nr_frag_headroom_err;
++ u64 tx_packets;
++ u64 tx_bytes;
++ u64 tx_dropped;
++ u64 tx_nr_frag_packets;
++ u64 tx_fraglist_packets;
++ u64 tx_fraglist_with_nr_frags_packets;
++ u64 tx_tso_packets;
++ u64 tx_tso_drop_packets;
++ u64 tx_gso_packets;
++ u64 tx_gso_drop_packets;
++ u64 tx_queue_stopped[EDMA_MAX_CORE];
++};
++
++#define EDMA_STAT(m) offsetof(struct edma_gmac_stats, m)
++
++static const struct edma_ethtool_stats edma_gstrings_stats[] = {
++ {"rx_bytes", EDMA_STAT(rx_bytes)},
++ {"rx_packets", EDMA_STAT(rx_packets)},
++ {"rx_dropped", EDMA_STAT(rx_dropped)},
++ {"rx_fraglist_packets", EDMA_STAT(rx_fraglist_packets)},
++ {"rx_nr_frag_packets", EDMA_STAT(rx_nr_frag_packets)},
++ {"rx_nr_frag_headroom_err", EDMA_STAT(rx_nr_frag_headroom_err)},
++ {"tx_bytes", EDMA_STAT(tx_bytes)},
++ {"tx_packets", EDMA_STAT(tx_packets)},
++ {"tx_dropped", EDMA_STAT(tx_dropped)},
++ {"tx_nr_frag_packets", EDMA_STAT(tx_nr_frag_packets)},
++ {"tx_fraglist_packets", EDMA_STAT(tx_fraglist_packets)},
++ {"tx_fraglist_nr_frags_packets", EDMA_STAT(tx_fraglist_with_nr_frags_packets)},
++ {"tx_tso_packets", EDMA_STAT(tx_tso_packets)},
++ {"tx_tso_drop_packets", EDMA_STAT(tx_tso_drop_packets)},
++ {"tx_gso_packets", EDMA_STAT(tx_gso_packets)},
++ {"tx_gso_drop_packets", EDMA_STAT(tx_gso_drop_packets)},
++ {"tx_queue_stopped_cpu0", EDMA_STAT(tx_queue_stopped[0])},
++ {"tx_queue_stopped_cpu1", EDMA_STAT(tx_queue_stopped[1])},
++ {"tx_queue_stopped_cpu2", EDMA_STAT(tx_queue_stopped[2])},
++ {"tx_queue_stopped_cpu3", EDMA_STAT(tx_queue_stopped[3])},
++};
++
++#define EDMA_STATS_LEN ARRAY_SIZE(edma_gstrings_stats)
++
++static void edma_port_get_stats(struct net_device *netdev,
++ struct edma_gmac_stats *stats)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct edma_port_rx_stats *pcpu_rx_stats;
++ struct edma_port_tx_stats *pcpu_tx_stats;
++ int i;
++
++ memset(stats, 0, sizeof(struct edma_port_pcpu_stats));
++
++ for_each_possible_cpu(i) {
++ struct edma_port_rx_stats rxp;
++ struct edma_port_tx_stats txp;
++ unsigned int start;
++
++ pcpu_rx_stats = per_cpu_ptr(port_priv->pcpu_stats.rx_stats, i);
++
++ do {
++ start = u64_stats_fetch_begin(&pcpu_rx_stats->syncp);
++ memcpy(&rxp, pcpu_rx_stats, sizeof(*pcpu_rx_stats));
++ } while (u64_stats_fetch_retry(&pcpu_rx_stats->syncp, start));
++
++ stats->rx_packets += rxp.rx_pkts;
++ stats->rx_bytes += rxp.rx_bytes;
++ stats->rx_dropped += rxp.rx_drops;
++ stats->rx_nr_frag_packets += rxp.rx_nr_frag_pkts;
++ stats->rx_fraglist_packets += rxp.rx_fraglist_pkts;
++ stats->rx_nr_frag_headroom_err += rxp.rx_nr_frag_headroom_err;
++
++ pcpu_tx_stats = per_cpu_ptr(port_priv->pcpu_stats.tx_stats, i);
++
++ do {
++ start = u64_stats_fetch_begin(&pcpu_tx_stats->syncp);
++ memcpy(&txp, pcpu_tx_stats, sizeof(*pcpu_tx_stats));
++ } while (u64_stats_fetch_retry(&pcpu_tx_stats->syncp, start));
++
++ stats->tx_packets += txp.tx_pkts;
++ stats->tx_bytes += txp.tx_bytes;
++ stats->tx_dropped += txp.tx_drops;
++ stats->tx_nr_frag_packets += txp.tx_nr_frag_pkts;
++ stats->tx_fraglist_packets += txp.tx_fraglist_pkts;
++ stats->tx_fraglist_with_nr_frags_packets += txp.tx_fraglist_with_nr_frags_pkts;
++ stats->tx_tso_packets += txp.tx_tso_pkts;
++ stats->tx_tso_drop_packets += txp.tx_tso_drop_pkts;
++ stats->tx_gso_packets += txp.tx_gso_pkts;
++ stats->tx_gso_drop_packets += txp.tx_gso_drop_pkts;
++ stats->tx_queue_stopped[i] += txp.tx_queue_stopped[i];
++ }
++}
++
++static void edma_get_ethtool_stats(struct net_device *netdev,
++ __maybe_unused struct ethtool_stats *stats,
++ u64 *data)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct edma_gmac_stats edma_stats;
++ u64 *mib_data;
++ int i;
++ u8 *p;
++
++ if (!port_priv)
++ return;
++
++ /* Get the DMA Driver statistics from the data plane if available. */
++ memset(&edma_stats, 0, sizeof(struct edma_gmac_stats));
++ edma_port_get_stats(netdev, &edma_stats);
++
++ /* Populate data plane statistics. */
++ for (i = 0; i < EDMA_STATS_LEN; i++) {
++ p = ((u8 *)(&edma_stats) + edma_gstrings_stats[i].stat_offset);
++ data[i] = *(u64 *)p;
++ }
++
++ /* Get the GMAC MIB statistics along with the DMA driver statistics. */
++ mib_data = &data[EDMA_STATS_LEN];
++ ppe_port_get_ethtool_stats(port_priv->ppe_port, mib_data);
++}
++
++static int edma_get_strset_count(struct net_device *netdev, int sset)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ int sset_count = 0;
++
++ if (!port_priv || sset != ETH_SS_STATS)
++ return 0;
++
++ sset_count = ppe_port_get_sset_count(port_priv->ppe_port, sset);
++
++ return (EDMA_STATS_LEN + sset_count);
++}
++
++static void edma_get_strings(struct net_device *netdev, u32 stringset,
++ u8 *data)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ int i;
++
++ if (!port_priv || stringset != ETH_SS_STATS)
++ return;
++
++ for (i = 0; i < EDMA_STATS_LEN; i++) {
++ memcpy(data, edma_gstrings_stats[i].stat_string,
++ strlen(edma_gstrings_stats[i].stat_string));
++ data += ETH_GSTRING_LEN;
++ }
++
++ ppe_port_get_strings(port_priv->ppe_port, stringset, data);
++}
++
++static int edma_get_link_ksettings(struct net_device *netdev,
++ struct ethtool_link_ksettings *cmd)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_ksettings_get(port->phylink, cmd);
++}
++
++static int edma_set_link_ksettings(struct net_device *netdev,
++ const struct ethtool_link_ksettings *cmd)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_ksettings_set(port->phylink, cmd);
++}
++
++static void edma_get_pauseparam(struct net_device *netdev,
++ struct ethtool_pauseparam *pause)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return;
++
++ phylink_ethtool_get_pauseparam(port->phylink, pause);
++}
++
++static int edma_set_pauseparam(struct net_device *netdev,
++ struct ethtool_pauseparam *pause)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_set_pauseparam(port->phylink, pause);
++}
++
++static int edma_get_eee(struct net_device *netdev, struct ethtool_keee *eee)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_get_eee(port->phylink, eee);
++}
++
++static int edma_set_eee(struct net_device *netdev, struct ethtool_keee *eee)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++ int ret;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ ret = ppe_port_set_mac_eee(port_priv->ppe_port, eee);
++ if (ret)
++ return ret;
++
++ return phylink_ethtool_set_eee(port->phylink, eee);
++}
++
++static const struct ethtool_ops edma_ethtool_ops = {
++ .get_strings = &edma_get_strings,
++ .get_sset_count = &edma_get_strset_count,
++ .get_ethtool_stats = &edma_get_ethtool_stats,
++ .get_link = ðtool_op_get_link,
++ .get_link_ksettings = edma_get_link_ksettings,
++ .set_link_ksettings = edma_set_link_ksettings,
++ .get_pauseparam = &edma_get_pauseparam,
++ .set_pauseparam = &edma_set_pauseparam,
++ .get_eee = &edma_get_eee,
++ .set_eee = &edma_set_eee,
++};
++
++/**
++ * edma_set_ethtool_ops - Set ethtool operations
++ * @netdev: Netdevice
++ *
++ * Set ethtool operations.
++ */
++void edma_set_ethtool_ops(struct net_device *netdev)
++{
++ netdev->ethtool_ops = &edma_ethtool_ops;
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -380,6 +380,7 @@ int edma_port_setup(struct ppe_port *por
+ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
+ netdev->netdev_ops = &edma_port_netdev_ops;
+ netdev->gso_max_segs = GSO_MAX_SEGS;
++ edma_set_ethtool_ops(netdev);
+
+ maddr = mac_addr;
+ if (of_get_mac_address(np, maddr))
--- /dev/null
+From 2ecec7e47e269e05cdd393c34aae51d4866070c6 Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Tue, 11 Jun 2024 00:00:46 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add module parameters for driver
+ tunings
+
+Add module params and corresponding functionality for Tx/Rx
+mitigation timer/packet count, napi budget and tx requeue stop.
+
+Change-Id: I1717559c931bba4f355ee06ab89f289818400ca2
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 35 +++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.c | 29 +++++++++++++--
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.h | 21 +++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.c | 29 +++++++++++++--
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.h | 16 +++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_rx.h | 4 +++
+ drivers/net/ethernet/qualcomm/ppe/edma_tx.h | 4 +++
+ 7 files changed, 134 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -38,6 +38,38 @@ static int rx_buff_size;
+ module_param(rx_buff_size, int, 0640);
+ MODULE_PARM_DESC(rx_buff_size, "Rx Buffer size for Jumbo MRU value (default:0)");
+
++int edma_rx_napi_budget = EDMA_RX_NAPI_WORK_DEF;
++module_param(edma_rx_napi_budget, int, 0444);
++MODULE_PARM_DESC(edma_rx_napi_budget, "Rx NAPI budget (default:128, min:16, max:512)");
++
++int edma_tx_napi_budget = EDMA_TX_NAPI_WORK_DEF;
++module_param(edma_tx_napi_budget, int, 0444);
++MODULE_PARM_DESC(edma_tx_napi_budget, "Tx NAPI budget (default:512 for ipq95xx, min:16, max:512)");
++
++int edma_rx_mitigation_pkt_cnt = EDMA_RX_MITIGATION_PKT_CNT_DEF;
++module_param(edma_rx_mitigation_pkt_cnt, int, 0444);
++MODULE_PARM_DESC(edma_rx_mitigation_pkt_cnt,
++ "Rx mitigation packet count value (default:16, min:0, max: 256)");
++
++s32 edma_rx_mitigation_timer = EDMA_RX_MITIGATION_TIMER_DEF;
++module_param(edma_rx_mitigation_timer, int, 0444);
++MODULE_PARM_DESC(edma_dp_rx_mitigation_timer,
++ "Rx mitigation timer value in microseconds (default:25, min:0, max: 1000)");
++
++int edma_tx_mitigation_timer = EDMA_TX_MITIGATION_TIMER_DEF;
++module_param(edma_tx_mitigation_timer, int, 0444);
++MODULE_PARM_DESC(edma_tx_mitigation_timer,
++ "Tx mitigation timer value in microseconds (default:250, min:0, max: 1000)");
++
++int edma_tx_mitigation_pkt_cnt = EDMA_TX_MITIGATION_PKT_CNT_DEF;
++module_param(edma_tx_mitigation_pkt_cnt, int, 0444);
++MODULE_PARM_DESC(edma_tx_mitigation_pkt_cnt,
++ "Tx mitigation packet count value (default:16, min:0, max: 256)");
++
++static int tx_requeue_stop;
++module_param(tx_requeue_stop, int, 0640);
++MODULE_PARM_DESC(tx_requeue_stop, "Disable Tx requeue function (default:0)");
++
+ /* Priority to multi-queue mapping. */
+ static u8 edma_pri_map[PPE_QUEUE_INTER_PRI_NUM] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7};
+@@ -828,7 +860,10 @@ int edma_setup(struct ppe_device *ppe_de
+ edma_ctx->hw_info = &ipq9574_hw_info;
+ edma_ctx->ppe_dev = ppe_dev;
+ edma_ctx->rx_buf_size = rx_buff_size;
++
+ edma_ctx->tx_requeue_stop = false;
++ if (tx_requeue_stop != 0)
++ edma_ctx->tx_requeue_stop = true;
+
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+@@ -166,6 +166,24 @@ static void edma_cfg_rx_desc_ring_config
+ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_RING_SIZE(rxdesc_ring->ring_id);
+ regmap_write(regmap, reg, data);
+
++ /* Validate mitigation timer value */
++ if (edma_rx_mitigation_timer < EDMA_RX_MITIGATION_TIMER_MIN ||
++ edma_rx_mitigation_timer > EDMA_RX_MITIGATION_TIMER_MAX) {
++ pr_err("Invalid Rx mitigation timer configured:%d for ring:%d. Using the default timer value:%d\n",
++ edma_rx_mitigation_timer, rxdesc_ring->ring_id,
++ EDMA_RX_MITIGATION_TIMER_DEF);
++ edma_rx_mitigation_timer = EDMA_RX_MITIGATION_TIMER_DEF;
++ }
++
++ /* Validate mitigation packet count value */
++ if (edma_rx_mitigation_pkt_cnt < EDMA_RX_MITIGATION_PKT_CNT_MIN ||
++ edma_rx_mitigation_pkt_cnt > EDMA_RX_MITIGATION_PKT_CNT_MAX) {
++ pr_err("Invalid Rx mitigation packet count configured:%d for ring:%d. Using the default packet counter value:%d\n",
++ edma_rx_mitigation_timer, rxdesc_ring->ring_id,
++ EDMA_RX_MITIGATION_PKT_CNT_DEF);
++ edma_rx_mitigation_pkt_cnt = EDMA_RX_MITIGATION_PKT_CNT_DEF;
++ }
++
+ /* Configure the Mitigation timer */
+ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_RX_MITIGATION_TIMER_DEF,
+ ppe_dev->clk_rate / MHZ);
+@@ -176,7 +194,7 @@ static void edma_cfg_rx_desc_ring_config
+ regmap_write(regmap, reg, data);
+
+ /* Configure the Mitigation packet count */
+- data = (EDMA_RX_MITIGATION_PKT_CNT_DEF & EDMA_RXDESC_LOW_THRE_MASK)
++ data = (edma_rx_mitigation_pkt_cnt & EDMA_RXDESC_LOW_THRE_MASK)
+ << EDMA_RXDESC_LOW_THRE_SHIFT;
+ pr_debug("EDMA Rx mitigation packet count value: %d\n", data);
+ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_UGT_THRE(rxdesc_ring->ring_id);
+@@ -915,6 +933,13 @@ void edma_cfg_rx_napi_add(void)
+ struct edma_ring_info *rx = hw_info->rx;
+ u32 i;
+
++ if (edma_rx_napi_budget < EDMA_RX_NAPI_WORK_MIN ||
++ edma_rx_napi_budget > EDMA_RX_NAPI_WORK_MAX) {
++ pr_err("Incorrect Rx NAPI budget: %d, setting to default: %d",
++ edma_rx_napi_budget, hw_info->napi_budget_rx);
++ edma_rx_napi_budget = hw_info->napi_budget_rx;
++ }
++
+ for (i = 0; i < rx->num_rings; i++) {
+ struct edma_rxdesc_ring *rxdesc_ring = &edma_ctx->rx_rings[i];
+
+@@ -923,7 +948,7 @@ void edma_cfg_rx_napi_add(void)
+ rxdesc_ring->napi_added = true;
+ }
+
+- netdev_dbg(edma_ctx->dummy_dev, "Rx NAPI budget: %d\n", hw_info->napi_budget_rx);
++ netdev_dbg(edma_ctx->dummy_dev, "Rx NAPI budget: %d\n", edma_rx_napi_budget);
+ }
+
+ /**
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+@@ -5,6 +5,15 @@
+ #ifndef __EDMA_CFG_RX__
+ #define __EDMA_CFG_RX__
+
++/* Rx default NAPI budget */
++#define EDMA_RX_NAPI_WORK_DEF 128
++
++/* RX minimum NAPI budget */
++#define EDMA_RX_NAPI_WORK_MIN 16
++
++/* Rx maximum NAPI budget */
++#define EDMA_RX_NAPI_WORK_MAX 512
++
+ /* SKB payload size used in page mode */
+ #define EDMA_RX_PAGE_MODE_SKB_SIZE 256
+
+@@ -22,9 +31,21 @@
+ /* Rx mitigation timer's default value in microseconds */
+ #define EDMA_RX_MITIGATION_TIMER_DEF 25
+
++/* Rx mitigation timer's minimum value in microseconds */
++#define EDMA_RX_MITIGATION_TIMER_MIN 0
++
++/* Rx mitigation timer's maximum value in microseconds */
++#define EDMA_RX_MITIGATION_TIMER_MAX 1000
++
+ /* Rx mitigation packet count's default value */
+ #define EDMA_RX_MITIGATION_PKT_CNT_DEF 16
+
++/* Rx mitigation packet count's minimum value */
++#define EDMA_RX_MITIGATION_PKT_CNT_MIN 0
++
++/* Rx mitigation packet count's maximum value */
++#define EDMA_RX_MITIGATION_PKT_CNT_MAX 256
++
+ /* Default bitmap of cores for RPS to ARM cores */
+ #define EDMA_RX_DEFAULT_BITMAP ((1 << EDMA_MAX_CORE) - 1)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
+@@ -170,6 +170,24 @@ static void edma_cfg_txcmpl_ring_configu
+ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_CTRL(txcmpl_ring->id);
+ regmap_write(regmap, reg, EDMA_TXCMPL_RETMODE_OPAQUE);
+
++ /* Validate mitigation timer value */
++ if (edma_tx_mitigation_timer < EDMA_TX_MITIGATION_TIMER_MIN ||
++ edma_tx_mitigation_timer > EDMA_TX_MITIGATION_TIMER_MAX) {
++ pr_err("Invalid Tx mitigation timer configured:%d for ring:%d. Using the default timer value:%d\n",
++ edma_tx_mitigation_timer, txcmpl_ring->id,
++ EDMA_TX_MITIGATION_TIMER_DEF);
++ edma_tx_mitigation_timer = EDMA_TX_MITIGATION_TIMER_DEF;
++ }
++
++ /* Validate mitigation packet count value */
++ if (edma_tx_mitigation_pkt_cnt < EDMA_TX_MITIGATION_PKT_CNT_MIN ||
++ edma_tx_mitigation_pkt_cnt > EDMA_TX_MITIGATION_PKT_CNT_MAX) {
++ pr_err("Invalid Tx mitigation packet count configured:%d for ring:%d. Using the default packet counter value:%d\n",
++ edma_tx_mitigation_timer, txcmpl_ring->id,
++ EDMA_TX_MITIGATION_PKT_CNT_DEF);
++ edma_tx_mitigation_pkt_cnt = EDMA_TX_MITIGATION_PKT_CNT_DEF;
++ }
++
+ /* Configure the Mitigation timer. */
+ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_TX_MITIGATION_TIMER_DEF,
+ ppe_dev->clk_rate / MHZ);
+@@ -180,7 +198,7 @@ static void edma_cfg_txcmpl_ring_configu
+ regmap_write(regmap, reg, data);
+
+ /* Configure the Mitigation packet count. */
+- data = (EDMA_TX_MITIGATION_PKT_CNT_DEF & EDMA_TXCMPL_LOW_THRE_MASK)
++ data = (edma_tx_mitigation_pkt_cnt & EDMA_TXCMPL_LOW_THRE_MASK)
+ << EDMA_TXCMPL_LOW_THRE_SHIFT;
+ pr_debug("EDMA Tx mitigation packet count value: %d\n", data);
+ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_UGT_THRE(txcmpl_ring->id);
+@@ -634,6 +652,13 @@ void edma_cfg_tx_napi_add(struct net_dev
+ struct edma_txcmpl_ring *txcmpl_ring;
+ u32 i, ring_idx;
+
++ if (edma_tx_napi_budget < EDMA_TX_NAPI_WORK_MIN ||
++ edma_tx_napi_budget > EDMA_TX_NAPI_WORK_MAX) {
++ pr_err("Incorrect Tx NAPI budget: %d, setting to default: %d",
++ edma_tx_napi_budget, hw_info->napi_budget_tx);
++ edma_tx_napi_budget = hw_info->napi_budget_tx;
++ }
++
+ /* Adding tx napi for a interface with each queue. */
+ for_each_possible_cpu(i) {
+ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
+@@ -644,5 +669,5 @@ void edma_cfg_tx_napi_add(struct net_dev
+ netdev_dbg(netdev, "Napi added for txcmpl ring: %u\n", txcmpl_ring->id);
+ }
+
+- netdev_dbg(netdev, "Tx NAPI budget: %d\n", hw_info->napi_budget_tx);
++ netdev_dbg(netdev, "Tx NAPI budget: %d\n", edma_tx_napi_budget);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
+@@ -5,12 +5,28 @@
+ #ifndef __EDMA_CFG_TX__
+ #define __EDMA_CFG_TX__
+
++#define EDMA_TX_NAPI_WORK_DEF 512
++#define EDMA_TX_NAPI_WORK_MIN 16
++#define EDMA_TX_NAPI_WORK_MAX 512
++
+ /* Tx mitigation timer's default value. */
+ #define EDMA_TX_MITIGATION_TIMER_DEF 250
+
++/* Tx mitigation timer's minimum value in microseconds */
++#define EDMA_TX_MITIGATION_TIMER_MIN 0
++
++/* Tx mitigation timer's maximum value in microseconds */
++#define EDMA_TX_MITIGATION_TIMER_MAX 1000
++
+ /* Tx mitigation packet count default value. */
+ #define EDMA_TX_MITIGATION_PKT_CNT_DEF 16
+
++/* Tx mitigation packet count's minimum value */
++#define EDMA_TX_MITIGATION_PKT_CNT_MIN 0
++
++/* Tx mitigation packet count's maximum value */
++#define EDMA_TX_MITIGATION_PKT_CNT_MAX 256
++
+ void edma_cfg_tx_rings(void);
+ int edma_cfg_tx_rings_alloc(void);
+ void edma_cfg_tx_rings_cleanup(void);
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_rx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_rx.h
+@@ -281,6 +281,10 @@ struct edma_rxdesc_ring {
+ struct sk_buff *last;
+ };
+
++extern int edma_rx_napi_budget;
++extern int edma_rx_mitigation_timer;
++extern int edma_rx_mitigation_pkt_cnt;
++
+ irqreturn_t edma_rx_handle_irq(int irq, void *ctx);
+ int edma_rx_alloc_buffer(struct edma_rxfill_ring *rxfill_ring, int alloc_count);
+ int edma_rx_napi_poll(struct napi_struct *napi, int budget);
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_tx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_tx.h
+@@ -288,6 +288,10 @@ struct edma_txcmpl_ring {
+ bool napi_added;
+ };
+
++extern int edma_tx_napi_budget;
++extern int edma_tx_mitigation_timer;
++extern int edma_tx_mitigation_pkt_cnt;
++
+ enum edma_tx_status edma_tx_ring_xmit(struct net_device *netdev,
+ struct sk_buff *skb,
+ struct edma_txdesc_ring *txdesc_ring,
--- /dev/null
+From dcac735a715c13a817d65ae371564cf2793330b2 Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Tue, 11 Jun 2024 01:43:22 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add sysctl for RPS bitmap
+
+Add sysctl to configure RPS bitmap for EDMA receive.
+This bitmap is used to configure the set of ARM cores
+used to receive packets from EDMA.
+
+Change-Id: Ie0e7d5971db93ea1494608a9e79c4abb13ce69b6
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Alex G: Use **const** ctl_table argument for .proc_handler
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 23 ++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 2 ++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.c | 27 +++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.h | 6 ++++-
+ 4 files changed, 57 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -797,6 +797,11 @@ void edma_destroy(struct ppe_device *ppe
+ struct edma_ring_info *rx = hw_info->rx;
+ u32 i;
+
++ if (edma_ctx->rx_rps_ctl_table_hdr) {
++ unregister_sysctl_table(edma_ctx->rx_rps_ctl_table_hdr);
++ edma_ctx->rx_rps_ctl_table_hdr = NULL;
++ }
++
+ /* Disable interrupts. */
+ for (i = 1; i <= hw_info->max_ports; i++)
+ edma_cfg_tx_disable_interrupts(i);
+@@ -840,6 +845,17 @@ void edma_destroy(struct ppe_device *ppe
+ kfree(edma_ctx->netdev_arr);
+ }
+
++/* EDMA Rx RPS core sysctl table */
++static struct ctl_table edma_rx_rps_core_table[] = {
++ {
++ .procname = "rps_bitmap_cores",
++ .data = &edma_cfg_rx_rps_bitmap_cores,
++ .maxlen = sizeof(int),
++ .mode = 0644,
++ .proc_handler = edma_cfg_rx_rps_bitmap
++ },
++};
++
+ /**
+ * edma_setup - EDMA Setup.
+ * @ppe_dev: PPE device
+@@ -865,6 +881,13 @@ int edma_setup(struct ppe_device *ppe_de
+ if (tx_requeue_stop != 0)
+ edma_ctx->tx_requeue_stop = true;
+
++ edma_ctx->rx_rps_ctl_table_hdr = register_sysctl("net/edma",
++ edma_rx_rps_core_table);
++ if (!edma_ctx->rx_rps_ctl_table_hdr) {
++ pr_err("Rx rps sysctl table configuration failed\n");
++ return -EINVAL;
++ }
++
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+ if (ret) {
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -132,6 +132,7 @@ struct edma_intr_info {
+ * @tx_rings: Tx Descriptor Ring, SW is producer
+ * @txcmpl_rings: Tx complete Ring, SW is consumer
+ * @err_stats: Per CPU error statistics
++ * @rx_rps_ctl_table_hdr: Rx RPS sysctl table
+ * @rx_page_mode: Page mode enabled or disabled
+ * @rx_buf_size: Rx buffer size for Jumbo MRU
+ * @tx_requeue_stop: Tx requeue stop enabled or disabled
+@@ -147,6 +148,7 @@ struct edma_context {
+ struct edma_txdesc_ring *tx_rings;
+ struct edma_txcmpl_ring *txcmpl_rings;
+ struct edma_err_stats __percpu *err_stats;
++ struct ctl_table_header *rx_rps_ctl_table_hdr;
+ u32 rx_page_mode;
+ u32 rx_buf_size;
+ bool tx_requeue_stop;
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+@@ -43,6 +43,8 @@ static u32 edma_rx_ring_queue_map[][EDMA
+ { 6, 14, 22, 30 },
+ { 7, 15, 23, 31 }};
+
++u32 edma_cfg_rx_rps_bitmap_cores = EDMA_RX_DEFAULT_BITMAP;
++
+ static int edma_cfg_rx_desc_rings_reset_queue_mapping(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+@@ -987,3 +989,28 @@ int edma_cfg_rx_rps_hash_map(void)
+
+ return 0;
+ }
++
++/* Configure RPS hash mapping based on bitmap */
++int edma_cfg_rx_rps_bitmap(const struct ctl_table *table, int write,
++ void *buffer, size_t *lenp, loff_t *ppos)
++{
++ int ret;
++
++ ret = proc_dointvec(table, write, buffer, lenp, ppos);
++
++ if (!write)
++ return ret;
++
++ if (!edma_cfg_rx_rps_bitmap_cores ||
++ edma_cfg_rx_rps_bitmap_cores > EDMA_RX_DEFAULT_BITMAP) {
++ pr_warn("Incorrect CPU bitmap: %x. Setting it to default value: %d",
++ edma_cfg_rx_rps_bitmap_cores, EDMA_RX_DEFAULT_BITMAP);
++ edma_cfg_rx_rps_bitmap_cores = EDMA_RX_DEFAULT_BITMAP;
++ }
++
++ ret = edma_cfg_rx_rps_hash_map();
++
++ pr_info("EDMA RPS bitmap value: %d\n", edma_cfg_rx_rps_bitmap_cores);
++
++ return ret;
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+@@ -49,6 +49,8 @@
+ /* Default bitmap of cores for RPS to ARM cores */
+ #define EDMA_RX_DEFAULT_BITMAP ((1 << EDMA_MAX_CORE) - 1)
+
++extern u32 edma_cfg_rx_rps_bitmap_cores;
++
+ int edma_cfg_rx_rings(void);
+ int edma_cfg_rx_rings_alloc(void);
+ void edma_cfg_rx_ring_mappings(void);
+@@ -64,6 +66,8 @@ void edma_cfg_rx_rings_enable(void);
+ void edma_cfg_rx_rings_disable(void);
+ void edma_cfg_rx_buff_size_setup(void);
+ int edma_cfg_rx_rps_hash_map(void);
+-int edma_cfg_rx_rps(struct ctl_table *table, int write,
++int edma_cfg_rx_rps(const struct ctl_table *table, int write,
+ void *buffer, size_t *lenp, loff_t *ppos);
++int edma_cfg_rx_rps_bitmap(const struct ctl_table *table, int write,
++ void *buffer, size_t *lenp, loff_t *ppos);
+ #endif
--- /dev/null
+From a809433c9b6a418dd886f12a5dcb3376f73bf2a7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 4 Dec 2024 01:37:05 +0100
+Subject: [PATCH] net: ethernet: qualcomm: Add support for label property for
+ EDMA port
+
+Add support for label property for EDMA port. This is useful to define
+custom name in DTS for specific ethernet port instead of assigning a
+dynamic name at runtime.
+
+This also improve the log output by using modern APIs.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -355,13 +355,25 @@ int edma_port_setup(struct ppe_port *por
+ int port_id = port->port_id;
+ struct net_device *netdev;
+ u8 mac_addr[ETH_ALEN];
++ const char *name;
++ int assign_type;
+ int ret = 0;
+ u8 *maddr;
+
+- netdev = alloc_etherdev_mqs(sizeof(struct edma_port_priv),
+- EDMA_NETDEV_QUEUE_NUM, EDMA_NETDEV_QUEUE_NUM);
++ name = of_get_property(np, "label", NULL);
++ if (name) {
++ assign_type = NET_NAME_PREDICTABLE;
++ } else {
++ name = "eth%d";
++ assign_type = NET_NAME_ENUM;
++ }
++
++ netdev = alloc_netdev_mqs(sizeof(struct edma_port_priv),
++ name, assign_type,
++ ether_setup,
++ EDMA_NETDEV_QUEUE_NUM, EDMA_NETDEV_QUEUE_NUM);
+ if (!netdev) {
+- pr_err("alloc_etherdev() failed\n");
++ dev_err(ppe_dev->dev, "alloc_netdev_mqs() failed\n");
+ return -ENOMEM;
+ }
+
--- /dev/null
+From 9c4ad75f17788a64c1e37d0b9e19ca157e01c80a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 9 Dec 2024 18:19:06 +0100
+Subject: [PATCH] net: ethernet: qualcomm: ppe: Fix unmet dependency with
+ QCOM_PPE
+
+Fix unmet dependency with QCOM_PPE on selecting SFP.
+
+WARNING: unmet direct dependencies detected for SFP
+ Depends on [m]: NETDEVICES [=y] && PHYLIB [=y] && I2C [=y] && PHYLINK [=y] && (HWMON [=m] || HWMON [=m]=n [=n])
+ Selected by [y]:
+ - QCOM_PPE [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_QUALCOMM [=y] && HAS_IOMEM [=y] && OF [=y] && COMMON_CLK [=y]
+
+This permit correct compilation of the modules with SFP enabled.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -68,7 +68,6 @@ config QCOM_PPE
+ select REGMAP_MMIO
+ select PHYLINK
+ select PCS_QCOM_IPQ_UNIPHY
+- select SFP
+ help
+ This driver supports the Qualcomm Technologies, Inc. packet
+ process engine (PPE) available with IPQ SoC. The PPE includes
--- /dev/null
+From ac41b401d274a4004027fa4000d801cd28c51f4c Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+Date: Tue, 13 May 2025 13:41:37 -0500
+Subject: [PATCH] net: ethernet: qualcomm: ppe: select correct PCS dependency
+
+The config symbol for the PCS driver has changed to PCS_QCOM_IPQ9574,
+since the original submission. Update Kconfig accordingly.
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -67,7 +67,7 @@ config QCOM_PPE
+ depends on COMMON_CLK
+ select REGMAP_MMIO
+ select PHYLINK
+- select PCS_QCOM_IPQ_UNIPHY
++ select PCS_QCOM_IPQ9574
+ help
+ This driver supports the Qualcomm Technologies, Inc. packet
+ process engine (PPE) available with IPQ SoC. The PPE includes
--- /dev/null
+From bbf706ecfd4295d73c8217d5220573dd51d7a081 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Fri, 1 Mar 2024 14:46:45 +0800
+Subject: [PATCH] arm64: dts: qcom: Add IPQ9574 PPE base device node
+
+PPE is the packet process engine on the Qualcomm IPQ platform,
+which is connected with the external switch or PHY device via
+the UNIPHY (PCS).
+
+Change-Id: I254bd48c218aa4eab54f697a2ad149f5a93b682c
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Add "qcom_ppe" label to PPE node
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 39 +++++++++++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -13,6 +13,7 @@
+ #include <dt-bindings/interconnect/qcom,ipq9574.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/reset/qcom,ipq9574-gcc.h>
++#include <dt-bindings/reset/qcom,ipq9574-nsscc.h>
+ #include <dt-bindings/thermal/thermal.h>
+
+ / {
+@@ -1269,6 +1270,44 @@
+ #interconnect-cells = <1>;
+ };
+
++ qcom_ppe: ethernet@3a000000 {
++ compatible = "qcom,ipq9574-ppe";
++ reg = <0x3a000000 0xbef800>;
++ ranges;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ clocks = <&nsscc NSS_CC_PPE_SWITCH_CLK>,
++ <&nsscc NSS_CC_PPE_SWITCH_CFG_CLK>,
++ <&nsscc NSS_CC_PPE_SWITCH_IPE_CLK>,
++ <&nsscc NSS_CC_PPE_SWITCH_BTQ_CLK>;
++ clock-names = "ppe",
++ "ppe_cfg",
++ "ppe_ipe",
++ "ppe_btq";
++ resets = <&nsscc PPE_FULL_RESET>;
++ interconnects = <&nsscc MASTER_NSSNOC_PPE
++ &nsscc SLAVE_NSSNOC_PPE>,
++ <&nsscc MASTER_NSSNOC_PPE_CFG
++ &nsscc SLAVE_NSSNOC_PPE_CFG>,
++ <&gcc MASTER_NSSNOC_QOSGEN_REF
++ &gcc SLAVE_NSSNOC_QOSGEN_REF>,
++ <&gcc MASTER_NSSNOC_TIMEOUT_REF
++ &gcc SLAVE_NSSNOC_TIMEOUT_REF>,
++ <&gcc MASTER_MEM_NOC_NSSNOC
++ &gcc SLAVE_MEM_NOC_NSSNOC>,
++ <&gcc MASTER_NSSNOC_MEMNOC
++ &gcc SLAVE_NSSNOC_MEMNOC>,
++ <&gcc MASTER_NSSNOC_MEM_NOC_1
++ &gcc SLAVE_NSSNOC_MEM_NOC_1>;
++ interconnect-names = "ppe",
++ "ppe_cfg",
++ "qos_gen",
++ "timeout_ref",
++ "nssnoc_memnoc",
++ "memnoc_nssnoc",
++ "memnoc_nssnoc_1";
++ };
++
+ pcs_uniphy0: ethernet-pcs@7a00000 {
+ compatible = "qcom,ipq9574-pcs";
+ reg = <0x7a00000 0x10000>;
--- /dev/null
+From bd50babc7db2a35d98236a0386173dccd6c6374b Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Wed, 6 Mar 2024 22:29:41 +0530
+Subject: [PATCH] arm64: dts: qcom: Add EDMA node for IPQ9574
+
+Add EDMA (Ethernet DMA) device tree node for IPQ9574 to
+enable ethernet support.
+
+Change-Id: I87d7c50f2485c8670948dce305000337f6499f8b
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 68 +++++++++++++++++++++++++++
+ 1 file changed, 68 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -1306,6 +1306,74 @@
+ "nssnoc_memnoc",
+ "memnoc_nssnoc",
+ "memnoc_nssnoc_1";
++
++ edma {
++ compatible = "qcom,ipq9574-edma";
++ clocks = <&nsscc NSS_CC_PPE_EDMA_CLK>,
++ <&nsscc NSS_CC_PPE_EDMA_CFG_CLK>;
++ clock-names = "edma",
++ "edma-cfg";
++ resets = <&nsscc EDMA_HW_RESET>;
++ reset-names = "edma_rst";
++ interrupts = <GIC_SPI 371 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 372 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 373 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 374 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 375 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 376 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 377 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 378 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 379 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 380 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 381 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 382 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 383 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 384 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 505 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 504 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 503 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 502 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 501 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 500 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 352 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 353 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 354 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 499 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "edma_txcmpl_8",
++ "edma_txcmpl_9",
++ "edma_txcmpl_10",
++ "edma_txcmpl_11",
++ "edma_txcmpl_12",
++ "edma_txcmpl_13",
++ "edma_txcmpl_14",
++ "edma_txcmpl_15",
++ "edma_txcmpl_16",
++ "edma_txcmpl_17",
++ "edma_txcmpl_18",
++ "edma_txcmpl_19",
++ "edma_txcmpl_20",
++ "edma_txcmpl_21",
++ "edma_txcmpl_22",
++ "edma_txcmpl_23",
++ "edma_txcmpl_24",
++ "edma_txcmpl_25",
++ "edma_txcmpl_26",
++ "edma_txcmpl_27",
++ "edma_txcmpl_28",
++ "edma_txcmpl_29",
++ "edma_txcmpl_30",
++ "edma_txcmpl_31",
++ "edma_rxdesc_20",
++ "edma_rxdesc_21",
++ "edma_rxdesc_22",
++ "edma_rxdesc_23",
++ "edma_misc";
++ };
+ };
+
+ pcs_uniphy0: ethernet-pcs@7a00000 {