Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/mediatek/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 139 kB image not shown  

Quelle  mtk_eth_soc.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 *
 *   Copyright (C) 2009-2016 John Crispin <blogic@openwrt.org>
 *   Copyright (C) 2009-2016 Felix Fietkau <nbd@openwrt.org>
 *   Copyright (C) 2013-2016 Michael Lee <igvtee@gmail.com>
 */


#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_address.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/if_vlan.h>
#include <linux/reset.h>
#include <linux/tcp.h>
#include <linux/interrupt.h>
#include <linux/pinctrl/devinfo.h>
#include <linux/phylink.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
#include <linux/jhash.h>
#include <linux/bitfield.h>
#include <net/dsa.h>
#include <net/dst_metadata.h>
#include <net/page_pool/helpers.h>
#include <linux/genalloc.h>

#include "mtk_eth_soc.h"
#include "mtk_wed.h"

static int mtk_msg_level = -1;
module_param_named(msg_level, mtk_msg_level, int, 0);
MODULE_PARM_DESC(msg_level, "Message level (-1=defaults,0=none,...,16=all)");

#define MTK_ETHTOOL_STAT(x) { #x, \
         offsetof(struct mtk_hw_stats, x) / sizeof(u64) }

#define MTK_ETHTOOL_XDP_STAT(x) { #x, \
      offsetof(struct mtk_hw_stats, xdp_stats.x) / \
      sizeof(u64) }

static const struct mtk_reg_map mtk_reg_map = {
 .tx_irq_mask  = 0x1a1c,
 .tx_irq_status  = 0x1a18,
 .pdma = {
  .rx_ptr  = 0x0900,
  .rx_cnt_cfg = 0x0904,
  .pcrx_ptr = 0x0908,
  .glo_cfg = 0x0a04,
  .rst_idx = 0x0a08,
  .delay_irq = 0x0a0c,
  .irq_status = 0x0a20,
  .irq_mask = 0x0a28,
  .adma_rx_dbg0 = 0x0a38,
  .int_grp = 0x0a50,
 },
 .qdma = {
  .qtx_cfg = 0x1800,
  .qtx_sch = 0x1804,
  .rx_ptr  = 0x1900,
  .rx_cnt_cfg = 0x1904,
  .qcrx_ptr = 0x1908,
  .glo_cfg = 0x1a04,
  .rst_idx = 0x1a08,
  .delay_irq = 0x1a0c,
  .fc_th  = 0x1a10,
  .tx_sch_rate = 0x1a14,
  .int_grp = 0x1a20,
  .hred  = 0x1a44,
  .ctx_ptr = 0x1b00,
  .dtx_ptr = 0x1b04,
  .crx_ptr = 0x1b10,
  .drx_ptr = 0x1b14,
  .fq_head = 0x1b20,
  .fq_tail = 0x1b24,
  .fq_count = 0x1b28,
  .fq_blen = 0x1b2c,
 },
 .gdm1_cnt  = 0x2400,
 .gdma_to_ppe = {
  [0]  = 0x4444,
 },
 .ppe_base  = 0x0c00,
 .wdma_base = {
  [0]  = 0x2800,
  [1]  = 0x2c00,
 },
 .pse_iq_sta  = 0x0110,
 .pse_oq_sta  = 0x0118,
};

static const struct mtk_reg_map mt7628_reg_map = {
 .tx_irq_mask  = 0x0a28,
 .tx_irq_status  = 0x0a20,
 .pdma = {
  .rx_ptr  = 0x0900,
  .rx_cnt_cfg = 0x0904,
  .pcrx_ptr = 0x0908,
  .glo_cfg = 0x0a04,
  .rst_idx = 0x0a08,
  .delay_irq = 0x0a0c,
  .irq_status = 0x0a20,
  .irq_mask = 0x0a28,
  .int_grp = 0x0a50,
 },
};

static const struct mtk_reg_map mt7986_reg_map = {
 .tx_irq_mask  = 0x461c,
 .tx_irq_status  = 0x4618,
 .pdma = {
  .rx_ptr  = 0x4100,
  .rx_cnt_cfg = 0x4104,
  .pcrx_ptr = 0x4108,
  .glo_cfg = 0x4204,
  .rst_idx = 0x4208,
  .delay_irq = 0x420c,
  .irq_status = 0x4220,
  .irq_mask = 0x4228,
  .adma_rx_dbg0 = 0x4238,
  .int_grp = 0x4250,
 },
 .qdma = {
  .qtx_cfg = 0x4400,
  .qtx_sch = 0x4404,
  .rx_ptr  = 0x4500,
  .rx_cnt_cfg = 0x4504,
  .qcrx_ptr = 0x4508,
  .glo_cfg = 0x4604,
  .rst_idx = 0x4608,
  .delay_irq = 0x460c,
  .fc_th  = 0x4610,
  .int_grp = 0x4620,
  .hred  = 0x4644,
  .ctx_ptr = 0x4700,
  .dtx_ptr = 0x4704,
  .crx_ptr = 0x4710,
  .drx_ptr = 0x4714,
  .fq_head = 0x4720,
  .fq_tail = 0x4724,
  .fq_count = 0x4728,
  .fq_blen = 0x472c,
  .tx_sch_rate = 0x4798,
 },
 .gdm1_cnt  = 0x1c00,
 .gdma_to_ppe = {
  [0]  = 0x3333,
  [1]  = 0x4444,
 },
 .ppe_base  = 0x2000,
 .wdma_base = {
  [0]  = 0x4800,
  [1]  = 0x4c00,
 },
 .pse_iq_sta  = 0x0180,
 .pse_oq_sta  = 0x01a0,
};

static const struct mtk_reg_map mt7988_reg_map = {
 .tx_irq_mask  = 0x461c,
 .tx_irq_status  = 0x4618,
 .pdma = {
  .rx_ptr  = 0x6900,
  .rx_cnt_cfg = 0x6904,
  .pcrx_ptr = 0x6908,
  .glo_cfg = 0x6a04,
  .rst_idx = 0x6a08,
  .delay_irq = 0x6a0c,
  .irq_status = 0x6a20,
  .irq_mask = 0x6a28,
  .adma_rx_dbg0 = 0x6a38,
  .int_grp = 0x6a50,
 },
 .qdma = {
  .qtx_cfg = 0x4400,
  .qtx_sch = 0x4404,
  .rx_ptr  = 0x4500,
  .rx_cnt_cfg = 0x4504,
  .qcrx_ptr = 0x4508,
  .glo_cfg = 0x4604,
  .rst_idx = 0x4608,
  .delay_irq = 0x460c,
  .fc_th  = 0x4610,
  .int_grp = 0x4620,
  .hred  = 0x4644,
  .ctx_ptr = 0x4700,
  .dtx_ptr = 0x4704,
  .crx_ptr = 0x4710,
  .drx_ptr = 0x4714,
  .fq_head = 0x4720,
  .fq_tail = 0x4724,
  .fq_count = 0x4728,
  .fq_blen = 0x472c,
  .tx_sch_rate = 0x4798,
 },
 .gdm1_cnt  = 0x1c00,
 .gdma_to_ppe = {
  [0]  = 0x3333,
  [1]  = 0x4444,
  [2]  = 0xcccc,
 },
 .ppe_base  = 0x2000,
 .wdma_base = {
  [0]  = 0x4800,
  [1]  = 0x4c00,
  [2]  = 0x5000,
 },
 .pse_iq_sta  = 0x0180,
 .pse_oq_sta  = 0x01a0,
};

/* strings used by ethtool */
static const struct mtk_ethtool_stats {
 char str[ETH_GSTRING_LEN];
 u32 offset;
} mtk_ethtool_stats[] = {
 MTK_ETHTOOL_STAT(tx_bytes),
 MTK_ETHTOOL_STAT(tx_packets),
 MTK_ETHTOOL_STAT(tx_skip),
 MTK_ETHTOOL_STAT(tx_collisions),
 MTK_ETHTOOL_STAT(rx_bytes),
 MTK_ETHTOOL_STAT(rx_packets),
 MTK_ETHTOOL_STAT(rx_overflow),
 MTK_ETHTOOL_STAT(rx_fcs_errors),
 MTK_ETHTOOL_STAT(rx_short_errors),
 MTK_ETHTOOL_STAT(rx_long_errors),
 MTK_ETHTOOL_STAT(rx_checksum_errors),
 MTK_ETHTOOL_STAT(rx_flow_control_packets),
 MTK_ETHTOOL_XDP_STAT(rx_xdp_redirect),
 MTK_ETHTOOL_XDP_STAT(rx_xdp_pass),
 MTK_ETHTOOL_XDP_STAT(rx_xdp_drop),
 MTK_ETHTOOL_XDP_STAT(rx_xdp_tx),
 MTK_ETHTOOL_XDP_STAT(rx_xdp_tx_errors),
 MTK_ETHTOOL_XDP_STAT(tx_xdp_xmit),
 MTK_ETHTOOL_XDP_STAT(tx_xdp_xmit_errors),
};

static const char * const mtk_clks_source_name[] = {
 "ethif",
 "sgmiitop",
 "esw",
 "gp0",
 "gp1",
 "gp2",
 "gp3",
 "xgp1",
 "xgp2",
 "xgp3",
 "crypto",
 "fe",
 "trgpll",
 "sgmii_tx250m",
 "sgmii_rx250m",
 "sgmii_cdr_ref",
 "sgmii_cdr_fb",
 "sgmii2_tx250m",
 "sgmii2_rx250m",
 "sgmii2_cdr_ref",
 "sgmii2_cdr_fb",
 "sgmii_ck",
 "eth2pll",
 "wocpu0",
 "wocpu1",
 "netsys0",
 "netsys1",
 "ethwarp_wocpu2",
 "ethwarp_wocpu1",
 "ethwarp_wocpu0",
 "top_sgm0_sel",
 "top_sgm1_sel",
 "top_eth_gmii_sel",
 "top_eth_refck_50m_sel",
 "top_eth_sys_200m_sel",
 "top_eth_sys_sel",
 "top_eth_xgmii_sel",
 "top_eth_mii_sel",
 "top_netsys_sel",
 "top_netsys_500m_sel",
 "top_netsys_pao_2x_sel",
 "top_netsys_sync_250m_sel",
 "top_netsys_ppefb_250m_sel",
 "top_netsys_warp_sel",
};

void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
{
 __raw_writel(val, eth->base + reg);
}

u32 mtk_r32(struct mtk_eth *eth, unsigned reg)
{
 return __raw_readl(eth->base + reg);
}

u32 mtk_m32(struct mtk_eth *eth, u32 mask, u32 set, unsigned int reg)
{
 u32 val;

 val = mtk_r32(eth, reg);
 val &= ~mask;
 val |= set;
 mtk_w32(eth, val, reg);
 return reg;
}

static int mtk_mdio_busy_wait(struct mtk_eth *eth)
{
 unsigned long t_start = jiffies;

 while (1) {
  if (!(mtk_r32(eth, MTK_PHY_IAC) & PHY_IAC_ACCESS))
   return 0;
  if (time_after(jiffies, t_start + PHY_IAC_TIMEOUT))
   break;
  cond_resched();
 }

 dev_err(eth->dev, "mdio: MDIO timeout\n");
 return -ETIMEDOUT;
}

static int _mtk_mdio_write_c22(struct mtk_eth *eth, u32 phy_addr, u32 phy_reg,
          u32 write_data)
{
 int ret;

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 mtk_w32(eth, PHY_IAC_ACCESS |
  PHY_IAC_START_C22 |
  PHY_IAC_CMD_WRITE |
  PHY_IAC_REG(phy_reg) |
  PHY_IAC_ADDR(phy_addr) |
  PHY_IAC_DATA(write_data),
  MTK_PHY_IAC);

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 return 0;
}

static int _mtk_mdio_write_c45(struct mtk_eth *eth, u32 phy_addr,
          u32 devad, u32 phy_reg, u32 write_data)
{
 int ret;

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 mtk_w32(eth, PHY_IAC_ACCESS |
  PHY_IAC_START_C45 |
  PHY_IAC_CMD_C45_ADDR |
  PHY_IAC_REG(devad) |
  PHY_IAC_ADDR(phy_addr) |
  PHY_IAC_DATA(phy_reg),
  MTK_PHY_IAC);

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 mtk_w32(eth, PHY_IAC_ACCESS |
  PHY_IAC_START_C45 |
  PHY_IAC_CMD_WRITE |
  PHY_IAC_REG(devad) |
  PHY_IAC_ADDR(phy_addr) |
  PHY_IAC_DATA(write_data),
  MTK_PHY_IAC);

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 return 0;
}

static int _mtk_mdio_read_c22(struct mtk_eth *eth, u32 phy_addr, u32 phy_reg)
{
 int ret;

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 mtk_w32(eth, PHY_IAC_ACCESS |
  PHY_IAC_START_C22 |
  PHY_IAC_CMD_C22_READ |
  PHY_IAC_REG(phy_reg) |
  PHY_IAC_ADDR(phy_addr),
  MTK_PHY_IAC);

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 return mtk_r32(eth, MTK_PHY_IAC) & PHY_IAC_DATA_MASK;
}

static int _mtk_mdio_read_c45(struct mtk_eth *eth, u32 phy_addr,
         u32 devad, u32 phy_reg)
{
 int ret;

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 mtk_w32(eth, PHY_IAC_ACCESS |
  PHY_IAC_START_C45 |
  PHY_IAC_CMD_C45_ADDR |
  PHY_IAC_REG(devad) |
  PHY_IAC_ADDR(phy_addr) |
  PHY_IAC_DATA(phy_reg),
  MTK_PHY_IAC);

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 mtk_w32(eth, PHY_IAC_ACCESS |
  PHY_IAC_START_C45 |
  PHY_IAC_CMD_C45_READ |
  PHY_IAC_REG(devad) |
  PHY_IAC_ADDR(phy_addr),
  MTK_PHY_IAC);

 ret = mtk_mdio_busy_wait(eth);
 if (ret < 0)
  return ret;

 return mtk_r32(eth, MTK_PHY_IAC) & PHY_IAC_DATA_MASK;
}

static int mtk_mdio_write_c22(struct mii_bus *bus, int phy_addr,
         int phy_reg, u16 val)
{
 struct mtk_eth *eth = bus->priv;

 return _mtk_mdio_write_c22(eth, phy_addr, phy_reg, val);
}

static int mtk_mdio_write_c45(struct mii_bus *bus, int phy_addr,
         int devad, int phy_reg, u16 val)
{
 struct mtk_eth *eth = bus->priv;

 return _mtk_mdio_write_c45(eth, phy_addr, devad, phy_reg, val);
}

static int mtk_mdio_read_c22(struct mii_bus *bus, int phy_addr, int phy_reg)
{
 struct mtk_eth *eth = bus->priv;

 return _mtk_mdio_read_c22(eth, phy_addr, phy_reg);
}

static int mtk_mdio_read_c45(struct mii_bus *bus, int phy_addr, int devad,
        int phy_reg)
{
 struct mtk_eth *eth = bus->priv;

 return _mtk_mdio_read_c45(eth, phy_addr, devad, phy_reg);
}

static int mt7621_gmac0_rgmii_adjust(struct mtk_eth *eth,
         phy_interface_t interface)
{
 u32 val;

 val = (interface == PHY_INTERFACE_MODE_TRGMII) ?
  ETHSYS_TRGMII_MT7621_DDR_PLL : 0;

 regmap_update_bits(eth->ethsys, ETHSYS_CLKCFG0,
      ETHSYS_TRGMII_MT7621_MASK, val);

 return 0;
}

static void mtk_gmac0_rgmii_adjust(struct mtk_eth *eth,
       phy_interface_t interface)
{
 int ret;

 if (interface == PHY_INTERFACE_MODE_TRGMII) {
  mtk_w32(eth, TRGMII_MODE, INTF_MODE);
  ret = clk_set_rate(eth->clks[MTK_CLK_TRGPLL], 500000000);
  if (ret)
   dev_err(eth->dev, "Failed to set trgmii pll: %d\n", ret);
  return;
 }

 dev_err(eth->dev, "Missing PLL configuration, ethernet may not work\n");
}

static void mtk_setup_bridge_switch(struct mtk_eth *eth)
{
 /* Force Port1 XGMAC Link Up */
 mtk_m32(eth, 0, MTK_XGMAC_FORCE_MODE(MTK_GMAC1_ID),
  MTK_XGMAC_STS(MTK_GMAC1_ID));

 /* Adjust GSW bridge IPG to 11 */
 mtk_m32(eth, GSWTX_IPG_MASK | GSWRX_IPG_MASK,
  (GSW_IPG_11 << GSWTX_IPG_SHIFT) |
  (GSW_IPG_11 << GSWRX_IPG_SHIFT),
  MTK_GSW_CFG);
}

static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
           phy_interface_t interface)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);
 struct mtk_eth *eth = mac->hw;
 unsigned int sid;

 if (interface == PHY_INTERFACE_MODE_SGMII ||
     phy_interface_mode_is_8023z(interface)) {
  sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
         0 : mac->id;

  return eth->sgmii_pcs[sid];
 }

 return NULL;
}

static int mtk_mac_prepare(struct phylink_config *config, unsigned int mode,
      phy_interface_t iface)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);
 struct mtk_eth *eth = mac->hw;

 if (mtk_interface_mode_is_xgmii(eth, iface) &&
     mac->id != MTK_GMAC1_ID) {
  mtk_m32(mac->hw, XMAC_MCR_TRX_DISABLE,
   XMAC_MCR_TRX_DISABLE, MTK_XMAC_MCR(mac->id));

  mtk_m32(mac->hw, MTK_XGMAC_FORCE_MODE(mac->id) |
     MTK_XGMAC_FORCE_LINK(mac->id),
   MTK_XGMAC_FORCE_MODE(mac->id), MTK_XGMAC_STS(mac->id));
 }

 return 0;
}

static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
      const struct phylink_link_state *state)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);
 struct mtk_eth *eth = mac->hw;
 int val, ge_mode, err = 0;
 u32 i;

 /* MT76x8 has no hardware settings between for the MAC */
 if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) &&
     mac->interface != state->interface) {
  /* Setup soc pin functions */
  switch (state->interface) {
  case PHY_INTERFACE_MODE_TRGMII:
  case PHY_INTERFACE_MODE_RGMII_TXID:
  case PHY_INTERFACE_MODE_RGMII_RXID:
  case PHY_INTERFACE_MODE_RGMII_ID:
  case PHY_INTERFACE_MODE_RGMII:
  case PHY_INTERFACE_MODE_MII:
   if (MTK_HAS_CAPS(eth->soc->caps, MTK_RGMII)) {
    err = mtk_gmac_rgmii_path_setup(eth, mac->id);
    if (err)
     goto init_err;
   }
   break;
  case PHY_INTERFACE_MODE_1000BASEX:
  case PHY_INTERFACE_MODE_2500BASEX:
  case PHY_INTERFACE_MODE_SGMII:
   err = mtk_gmac_sgmii_path_setup(eth, mac->id);
   if (err)
    goto init_err;
   break;
  case PHY_INTERFACE_MODE_GMII:
   if (MTK_HAS_CAPS(eth->soc->caps, MTK_GEPHY)) {
    err = mtk_gmac_gephy_path_setup(eth, mac->id);
    if (err)
     goto init_err;
   }
   break;
  case PHY_INTERFACE_MODE_INTERNAL:
   if (mac->id == MTK_GMAC2_ID &&
       MTK_HAS_CAPS(eth->soc->caps, MTK_2P5GPHY)) {
    err = mtk_gmac_2p5gphy_path_setup(eth, mac->id);
    if (err)
     goto init_err;
   }
   break;
  default:
   goto err_phy;
  }

  /* Setup clock for 1st gmac */
  if (!mac->id && state->interface != PHY_INTERFACE_MODE_SGMII &&
      !phy_interface_mode_is_8023z(state->interface) &&
      MTK_HAS_CAPS(mac->hw->soc->caps, MTK_GMAC1_TRGMII)) {
   if (MTK_HAS_CAPS(mac->hw->soc->caps,
      MTK_TRGMII_MT7621_CLK)) {
    if (mt7621_gmac0_rgmii_adjust(mac->hw,
             state->interface))
     goto err_phy;
   } else {
    mtk_gmac0_rgmii_adjust(mac->hw,
             state->interface);

    /* mt7623_pad_clk_setup */
    for (i = 0 ; i < NUM_TRGMII_CTRL; i++)
     mtk_w32(mac->hw,
      TD_DM_DRVP(8) | TD_DM_DRVN(8),
      TRGMII_TD_ODT(i));

    /* Assert/release MT7623 RXC reset */
    mtk_m32(mac->hw, 0, RXC_RST | RXC_DQSISEL,
     TRGMII_RCK_CTRL);
    mtk_m32(mac->hw, RXC_RST, 0, TRGMII_RCK_CTRL);
   }
  }

  switch (state->interface) {
  case PHY_INTERFACE_MODE_MII:
  case PHY_INTERFACE_MODE_GMII:
   ge_mode = 1;
   break;
  default:
   ge_mode = 0;
   break;
  }

  /* put the gmac into the right mode */
  regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
  val &= ~SYSCFG0_GE_MODE(SYSCFG0_GE_MASK, mac->id);
  val |= SYSCFG0_GE_MODE(ge_mode, mac->id);
  regmap_write(eth->ethsys, ETHSYS_SYSCFG0, val);

  mac->interface = state->interface;
 }

 /* SGMII */
 if (state->interface == PHY_INTERFACE_MODE_SGMII ||
     phy_interface_mode_is_8023z(state->interface)) {
  /* The path GMAC to SGMII will be enabled once the SGMIISYS is
 * being setup done.
 */

  regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);

  regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
       SYSCFG0_SGMII_MASK,
       ~(u32)SYSCFG0_SGMII_MASK);

  /* Save the syscfg0 value for mac_finish */
  mac->syscfg0 = val;
 } else if (phylink_autoneg_inband(mode)) {
  dev_err(eth->dev,
   "In-band mode not supported in non SGMII mode!\n");
  return;
 }

 /* Setup gmac */
 if (mtk_interface_mode_is_xgmii(eth, state->interface)) {
  mtk_w32(mac->hw, MTK_GDMA_XGDM_SEL, MTK_GDMA_EG_CTRL(mac->id));
  mtk_w32(mac->hw, MAC_MCR_FORCE_LINK_DOWN, MTK_MAC_MCR(mac->id));

  if (mac->id == MTK_GMAC1_ID)
   mtk_setup_bridge_switch(eth);
 }

 return;

err_phy:
 dev_err(eth->dev, "%s: GMAC%d mode %s not supported!\n", __func__,
  mac->id, phy_modes(state->interface));
 return;

init_err:
 dev_err(eth->dev, "%s: GMAC%d mode %s err: %d!\n", __func__,
  mac->id, phy_modes(state->interface), err);
}

static int mtk_mac_finish(struct phylink_config *config, unsigned int mode,
     phy_interface_t interface)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);
 struct mtk_eth *eth = mac->hw;
 u32 mcr_cur, mcr_new;

 /* Enable SGMII */
 if (interface == PHY_INTERFACE_MODE_SGMII ||
     phy_interface_mode_is_8023z(interface))
  regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
       SYSCFG0_SGMII_MASK, mac->syscfg0);

 /* Setup gmac */
 mcr_cur = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
 mcr_new = mcr_cur;
 mcr_new |= MAC_MCR_IPG_CFG | MAC_MCR_FORCE_MODE |
     MAC_MCR_BACKOFF_EN | MAC_MCR_BACKPR_EN | MAC_MCR_RX_FIFO_CLR_DIS;

 /* Only update control register when needed! */
 if (mcr_new != mcr_cur)
  mtk_w32(mac->hw, mcr_new, MTK_MAC_MCR(mac->id));

 return 0;
}

static void mtk_mac_link_down(struct phylink_config *config, unsigned int mode,
         phy_interface_t interface)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);

 if (!mtk_interface_mode_is_xgmii(mac->hw, interface)) {
  /* GMAC modes */
  mtk_m32(mac->hw,
   MAC_MCR_TX_EN | MAC_MCR_RX_EN | MAC_MCR_FORCE_LINK, 0,
   MTK_MAC_MCR(mac->id));
 } else if (mac->id != MTK_GMAC1_ID) {
  /* XGMAC except for built-in switch */
  mtk_m32(mac->hw, XMAC_MCR_TRX_DISABLE, XMAC_MCR_TRX_DISABLE,
   MTK_XMAC_MCR(mac->id));
  mtk_m32(mac->hw, MTK_XGMAC_FORCE_LINK(mac->id), 0,
   MTK_XGMAC_STS(mac->id));
 }
}

static void mtk_set_queue_speed(struct mtk_eth *eth, unsigned int idx,
    int speed)
{
 const struct mtk_soc_data *soc = eth->soc;
 u32 ofs, val;

 if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
  return;

 val = MTK_QTX_SCH_MIN_RATE_EN |
       /* minimum: 10 Mbps */
       FIELD_PREP(MTK_QTX_SCH_MIN_RATE_MAN, 1) |
       FIELD_PREP(MTK_QTX_SCH_MIN_RATE_EXP, 4) |
       MTK_QTX_SCH_LEAKY_BUCKET_SIZE;
 if (mtk_is_netsys_v1(eth))
  val |= MTK_QTX_SCH_LEAKY_BUCKET_EN;

 if (IS_ENABLED(CONFIG_SOC_MT7621)) {
  switch (speed) {
  case SPEED_10:
   val |= MTK_QTX_SCH_MAX_RATE_EN |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 103) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 2) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
   break;
  case SPEED_100:
   val |= MTK_QTX_SCH_MAX_RATE_EN |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 103) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 3) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
   break;
  case SPEED_1000:
   val |= MTK_QTX_SCH_MAX_RATE_EN |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 105) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 4) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 10);
   break;
  default:
   break;
  }
 } else {
  switch (speed) {
  case SPEED_10:
   val |= MTK_QTX_SCH_MAX_RATE_EN |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 1) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 4) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
   break;
  case SPEED_100:
   val |= MTK_QTX_SCH_MAX_RATE_EN |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 1) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 5) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 1);
   break;
  case SPEED_1000:
   val |= MTK_QTX_SCH_MAX_RATE_EN |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_MAN, 1) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_EXP, 6) |
          FIELD_PREP(MTK_QTX_SCH_MAX_RATE_WEIGHT, 10);
   break;
  default:
   break;
  }
 }

 ofs = MTK_QTX_OFFSET * idx;
 mtk_w32(eth, val, soc->reg_map->qdma.qtx_sch + ofs);
}

static void mtk_gdm_mac_link_up(struct mtk_mac *mac,
    struct phy_device *phy,
    unsigned int mode, phy_interface_t interface,
    int speed, int duplex, bool tx_pause,
    bool rx_pause)
{
 u32 mcr;

 mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
 mcr &= ~(MAC_MCR_SPEED_100 | MAC_MCR_SPEED_1000 |
   MAC_MCR_FORCE_DPX | MAC_MCR_FORCE_TX_FC |
   MAC_MCR_FORCE_RX_FC);

 /* Configure speed */
 mac->speed = speed;
 switch (speed) {
 case SPEED_2500:
 case SPEED_1000:
  mcr |= MAC_MCR_SPEED_1000;
  break;
 case SPEED_100:
  mcr |= MAC_MCR_SPEED_100;
  break;
 }

 /* Configure duplex */
 if (duplex == DUPLEX_FULL)
  mcr |= MAC_MCR_FORCE_DPX;

 /* Configure pause modes - phylink will avoid these for half duplex */
 if (tx_pause)
  mcr |= MAC_MCR_FORCE_TX_FC;
 if (rx_pause)
  mcr |= MAC_MCR_FORCE_RX_FC;

 mcr |= MAC_MCR_TX_EN | MAC_MCR_RX_EN | MAC_MCR_FORCE_LINK;
 mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
}

static void mtk_xgdm_mac_link_up(struct mtk_mac *mac,
     struct phy_device *phy,
     unsigned int mode, phy_interface_t interface,
     int speed, int duplex, bool tx_pause,
     bool rx_pause)
{
 u32 mcr;

 if (mac->id == MTK_GMAC1_ID)
  return;

 /* Eliminate the interference(before link-up) caused by PHY noise */
 mtk_m32(mac->hw, XMAC_LOGIC_RST, 0, MTK_XMAC_LOGIC_RST(mac->id));
 mdelay(20);
 mtk_m32(mac->hw, XMAC_GLB_CNTCLR, XMAC_GLB_CNTCLR,
  MTK_XMAC_CNT_CTRL(mac->id));

 mtk_m32(mac->hw, MTK_XGMAC_FORCE_LINK(mac->id),
  MTK_XGMAC_FORCE_LINK(mac->id), MTK_XGMAC_STS(mac->id));

 mcr = mtk_r32(mac->hw, MTK_XMAC_MCR(mac->id));
 mcr &= ~(XMAC_MCR_FORCE_TX_FC | XMAC_MCR_FORCE_RX_FC |
   XMAC_MCR_TRX_DISABLE);
 /* Configure pause modes -
 * phylink will avoid these for half duplex
 */

 if (tx_pause)
  mcr |= XMAC_MCR_FORCE_TX_FC;
 if (rx_pause)
  mcr |= XMAC_MCR_FORCE_RX_FC;

 mtk_w32(mac->hw, mcr, MTK_XMAC_MCR(mac->id));
}

static void mtk_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 mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);

 if (mtk_interface_mode_is_xgmii(mac->hw, interface))
  mtk_xgdm_mac_link_up(mac, phy, mode, interface, speed, duplex,
         tx_pause, rx_pause);
 else
  mtk_gdm_mac_link_up(mac, phy, mode, interface, speed, duplex,
        tx_pause, rx_pause);
}

static void mtk_mac_disable_tx_lpi(struct phylink_config *config)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);
 struct mtk_eth *eth = mac->hw;

 mtk_m32(eth, MAC_MCR_EEE100M | MAC_MCR_EEE1G, 0, MTK_MAC_MCR(mac->id));
}

static int mtk_mac_enable_tx_lpi(struct phylink_config *config, u32 timer,
     bool tx_clk_stop)
{
 struct mtk_mac *mac = container_of(config, struct mtk_mac,
        phylink_config);
 struct mtk_eth *eth = mac->hw;
 u32 val;

 if (mtk_interface_mode_is_xgmii(eth, mac->interface))
  return -EOPNOTSUPP;

 /* Tx idle timer in ms */
 timer = DIV_ROUND_UP(timer, 1000);

 /* If the timer is zero, then set LPI_MODE, which allows the
 * system to enter LPI mode immediately rather than waiting for
 * the LPI threshold.
 */

 if (!timer)
  val = MAC_EEE_LPI_MODE;
 else if (FIELD_FIT(MAC_EEE_LPI_TXIDLE_THD, timer))
  val = FIELD_PREP(MAC_EEE_LPI_TXIDLE_THD, timer);
 else
  val = MAC_EEE_LPI_TXIDLE_THD;

 if (tx_clk_stop)
  val |= MAC_EEE_CKG_TXIDLE;

 /* PHY Wake-up time, this field does not have a reset value, so use the
 * reset value from MT7531 (36us for 100M and 17us for 1000M).
 */

 val |= FIELD_PREP(MAC_EEE_WAKEUP_TIME_1000, 17) |
        FIELD_PREP(MAC_EEE_WAKEUP_TIME_100, 36);

 mtk_w32(eth, val, MTK_MAC_EEECR(mac->id));
 mtk_m32(eth, 0, MAC_MCR_EEE100M | MAC_MCR_EEE1G, MTK_MAC_MCR(mac->id));

 return 0;
}

static const struct phylink_mac_ops mtk_phylink_ops = {
 .mac_prepare = mtk_mac_prepare,
 .mac_select_pcs = mtk_mac_select_pcs,
 .mac_config = mtk_mac_config,
 .mac_finish = mtk_mac_finish,
 .mac_link_down = mtk_mac_link_down,
 .mac_link_up = mtk_mac_link_up,
 .mac_disable_tx_lpi = mtk_mac_disable_tx_lpi,
 .mac_enable_tx_lpi = mtk_mac_enable_tx_lpi,
};

static void mtk_mdio_config(struct mtk_eth *eth)
{
 u32 val;

 /* Configure MDC Divider */
 val = FIELD_PREP(PPSC_MDC_CFG, eth->mdc_divider);

 /* Configure MDC Turbo Mode */
 if (mtk_is_netsys_v3_or_greater(eth))
  mtk_m32(eth, 0, MISC_MDC_TURBO, MTK_MAC_MISC_V3);
 else
  val |= PPSC_MDC_TURBO;

 mtk_m32(eth, PPSC_MDC_CFG, val, MTK_PPSC);
}

static int mtk_mdio_init(struct mtk_eth *eth)
{
 unsigned int max_clk = 2500000;
 struct device_node *mii_np;
 int ret;
 u32 val;

 mii_np = of_get_available_child_by_name(eth->dev->of_node, "mdio-bus");
 if (!mii_np) {
  dev_err(eth->dev, "no %s child node found""mdio-bus");
  return -ENODEV;
 }

 eth->mii_bus = devm_mdiobus_alloc(eth->dev);
 if (!eth->mii_bus) {
  ret = -ENOMEM;
  goto err_put_node;
 }

 eth->mii_bus->name = "mdio";
 eth->mii_bus->read = mtk_mdio_read_c22;
 eth->mii_bus->write = mtk_mdio_write_c22;
 eth->mii_bus->read_c45 = mtk_mdio_read_c45;
 eth->mii_bus->write_c45 = mtk_mdio_write_c45;
 eth->mii_bus->priv = eth;
 eth->mii_bus->parent = eth->dev;

 snprintf(eth->mii_bus->id, MII_BUS_ID_SIZE, "%pOFn", mii_np);

 if (!of_property_read_u32(mii_np, "clock-frequency", &val)) {
  if (val > MDC_MAX_FREQ || val < MDC_MAX_FREQ / MDC_MAX_DIVIDER) {
   dev_err(eth->dev, "MDIO clock frequency out of range");
   ret = -EINVAL;
   goto err_put_node;
  }
  max_clk = val;
 }
 eth->mdc_divider = min_t(unsigned int, DIV_ROUND_UP(MDC_MAX_FREQ, max_clk), 63);
 mtk_mdio_config(eth);
 dev_dbg(eth->dev, "MDC is running on %d Hz\n", MDC_MAX_FREQ / eth->mdc_divider);
 ret = of_mdiobus_register(eth->mii_bus, mii_np);

err_put_node:
 of_node_put(mii_np);
 return ret;
}

static void mtk_mdio_cleanup(struct mtk_eth *eth)
{
 if (!eth->mii_bus)
  return;

 mdiobus_unregister(eth->mii_bus);
}

static inline void mtk_tx_irq_disable(struct mtk_eth *eth, u32 mask)
{
 unsigned long flags;
 u32 val;

 spin_lock_irqsave(ð->tx_irq_lock, flags);
 val = mtk_r32(eth, eth->soc->reg_map->tx_irq_mask);
 mtk_w32(eth, val & ~mask, eth->soc->reg_map->tx_irq_mask);
 spin_unlock_irqrestore(ð->tx_irq_lock, flags);
}

static inline void mtk_tx_irq_enable(struct mtk_eth *eth, u32 mask)
{
 unsigned long flags;
 u32 val;

 spin_lock_irqsave(ð->tx_irq_lock, flags);
 val = mtk_r32(eth, eth->soc->reg_map->tx_irq_mask);
 mtk_w32(eth, val | mask, eth->soc->reg_map->tx_irq_mask);
 spin_unlock_irqrestore(ð->tx_irq_lock, flags);
}

static inline void mtk_rx_irq_disable(struct mtk_eth *eth, u32 mask)
{
 unsigned long flags;
 u32 val;

 spin_lock_irqsave(ð->rx_irq_lock, flags);
 val = mtk_r32(eth, eth->soc->reg_map->pdma.irq_mask);
 mtk_w32(eth, val & ~mask, eth->soc->reg_map->pdma.irq_mask);
 spin_unlock_irqrestore(ð->rx_irq_lock, flags);
}

static inline void mtk_rx_irq_enable(struct mtk_eth *eth, u32 mask)
{
 unsigned long flags;
 u32 val;

 spin_lock_irqsave(ð->rx_irq_lock, flags);
 val = mtk_r32(eth, eth->soc->reg_map->pdma.irq_mask);
 mtk_w32(eth, val | mask, eth->soc->reg_map->pdma.irq_mask);
 spin_unlock_irqrestore(ð->rx_irq_lock, flags);
}

static int mtk_set_mac_address(struct net_device *dev, void *p)
{
 int ret = eth_mac_addr(dev, p);
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_eth *eth = mac->hw;
 const char *macaddr = dev->dev_addr;

 if (ret)
  return ret;

 if (unlikely(test_bit(MTK_RESETTING, &mac->hw->state)))
  return -EBUSY;

 spin_lock_bh(&mac->hw->page_lock);
 if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
  mtk_w32(mac->hw, (macaddr[0] << 8) | macaddr[1],
   MT7628_SDM_MAC_ADRH);
  mtk_w32(mac->hw, (macaddr[2] << 24) | (macaddr[3] << 16) |
   (macaddr[4] << 8) | macaddr[5],
   MT7628_SDM_MAC_ADRL);
 } else {
  mtk_w32(mac->hw, (macaddr[0] << 8) | macaddr[1],
   MTK_GDMA_MAC_ADRH(mac->id));
  mtk_w32(mac->hw, (macaddr[2] << 24) | (macaddr[3] << 16) |
   (macaddr[4] << 8) | macaddr[5],
   MTK_GDMA_MAC_ADRL(mac->id));
 }
 spin_unlock_bh(&mac->hw->page_lock);

 return 0;
}

void mtk_stats_update_mac(struct mtk_mac *mac)
{
 struct mtk_hw_stats *hw_stats = mac->hw_stats;
 struct mtk_eth *eth = mac->hw;

 u64_stats_update_begin(&hw_stats->syncp);

 if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) {
  hw_stats->tx_packets += mtk_r32(mac->hw, MT7628_SDM_TPCNT);
  hw_stats->tx_bytes += mtk_r32(mac->hw, MT7628_SDM_TBCNT);
  hw_stats->rx_packets += mtk_r32(mac->hw, MT7628_SDM_RPCNT);
  hw_stats->rx_bytes += mtk_r32(mac->hw, MT7628_SDM_RBCNT);
  hw_stats->rx_checksum_errors +=
   mtk_r32(mac->hw, MT7628_SDM_CS_ERR);
 } else {
  const struct mtk_reg_map *reg_map = eth->soc->reg_map;
  unsigned int offs = hw_stats->reg_offset;
  u64 stats;

  hw_stats->rx_bytes += mtk_r32(mac->hw, reg_map->gdm1_cnt + offs);
  stats = mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x4 + offs);
  if (stats)
   hw_stats->rx_bytes += (stats << 32);
  hw_stats->rx_packets +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x8 + offs);
  hw_stats->rx_overflow +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x10 + offs);
  hw_stats->rx_fcs_errors +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x14 + offs);
  hw_stats->rx_short_errors +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x18 + offs);
  hw_stats->rx_long_errors +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x1c + offs);
  hw_stats->rx_checksum_errors +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x20 + offs);
  hw_stats->rx_flow_control_packets +=
   mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x24 + offs);

  if (mtk_is_netsys_v3_or_greater(eth)) {
   hw_stats->tx_skip +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x50 + offs);
   hw_stats->tx_collisions +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x54 + offs);
   hw_stats->tx_bytes +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x40 + offs);
   stats =  mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x44 + offs);
   if (stats)
    hw_stats->tx_bytes += (stats << 32);
   hw_stats->tx_packets +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x48 + offs);
  } else {
   hw_stats->tx_skip +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x28 + offs);
   hw_stats->tx_collisions +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x2c + offs);
   hw_stats->tx_bytes +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x30 + offs);
   stats =  mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x34 + offs);
   if (stats)
    hw_stats->tx_bytes += (stats << 32);
   hw_stats->tx_packets +=
    mtk_r32(mac->hw, reg_map->gdm1_cnt + 0x38 + offs);
  }
 }

 u64_stats_update_end(&hw_stats->syncp);
}

static void mtk_stats_update(struct mtk_eth *eth)
{
 int i;

 for (i = 0; i < MTK_MAX_DEVS; i++) {
  if (!eth->mac[i] || !eth->mac[i]->hw_stats)
   continue;
  if (spin_trylock(ð->mac[i]->hw_stats->stats_lock)) {
   mtk_stats_update_mac(eth->mac[i]);
   spin_unlock(ð->mac[i]->hw_stats->stats_lock);
  }
 }
}

static void mtk_get_stats64(struct net_device *dev,
       struct rtnl_link_stats64 *storage)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_hw_stats *hw_stats = mac->hw_stats;
 unsigned int start;

 if (netif_running(dev) && netif_device_present(dev)) {
  if (spin_trylock_bh(&hw_stats->stats_lock)) {
   mtk_stats_update_mac(mac);
   spin_unlock_bh(&hw_stats->stats_lock);
  }
 }

 do {
  start = u64_stats_fetch_begin(&hw_stats->syncp);
  storage->rx_packets = hw_stats->rx_packets;
  storage->tx_packets = hw_stats->tx_packets;
  storage->rx_bytes = hw_stats->rx_bytes;
  storage->tx_bytes = hw_stats->tx_bytes;
  storage->collisions = hw_stats->tx_collisions;
  storage->rx_length_errors = hw_stats->rx_short_errors +
   hw_stats->rx_long_errors;
  storage->rx_over_errors = hw_stats->rx_overflow;
  storage->rx_crc_errors = hw_stats->rx_fcs_errors;
  storage->rx_errors = hw_stats->rx_checksum_errors;
  storage->tx_aborted_errors = hw_stats->tx_skip;
 } while (u64_stats_fetch_retry(&hw_stats->syncp, start));

 storage->tx_errors = dev->stats.tx_errors;
 storage->rx_dropped = dev->stats.rx_dropped;
 storage->tx_dropped = dev->stats.tx_dropped;
}

static inline int mtk_max_frag_size(int mtu)
{
 /* make sure buf_size will be at least MTK_MAX_RX_LENGTH */
 if (mtu + MTK_RX_ETH_HLEN < MTK_MAX_RX_LENGTH_2K)
  mtu = MTK_MAX_RX_LENGTH_2K - MTK_RX_ETH_HLEN;

 return SKB_DATA_ALIGN(MTK_RX_HLEN + mtu) +
  SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
}

static inline int mtk_max_buf_size(int frag_size)
{
 int buf_size = frag_size - NET_SKB_PAD - NET_IP_ALIGN -
         SKB_DATA_ALIGN(sizeof(struct skb_shared_info));

 WARN_ON(buf_size < MTK_MAX_RX_LENGTH_2K);

 return buf_size;
}

static bool mtk_rx_get_desc(struct mtk_eth *eth, struct mtk_rx_dma_v2 *rxd,
       struct mtk_rx_dma_v2 *dma_rxd)
{
 rxd->rxd2 = READ_ONCE(dma_rxd->rxd2);
 if (!(rxd->rxd2 & RX_DMA_DONE))
  return false;

 rxd->rxd1 = READ_ONCE(dma_rxd->rxd1);
 rxd->rxd3 = READ_ONCE(dma_rxd->rxd3);
 rxd->rxd4 = READ_ONCE(dma_rxd->rxd4);
 if (mtk_is_netsys_v3_or_greater(eth)) {
  rxd->rxd5 = READ_ONCE(dma_rxd->rxd5);
  rxd->rxd6 = READ_ONCE(dma_rxd->rxd6);
 }

 return true;
}

static void *mtk_max_lro_buf_alloc(gfp_t gfp_mask)
{
 unsigned int size = mtk_max_frag_size(MTK_MAX_LRO_RX_LENGTH);
 unsigned long data;

 data = __get_free_pages(gfp_mask | __GFP_COMP | __GFP_NOWARN,
    get_order(size));

 return (void *)data;
}

static void *mtk_dma_ring_alloc(struct mtk_eth *eth, size_t size,
    dma_addr_t *dma_handle, bool use_sram)
{
 void *dma_ring;

 if (use_sram && eth->sram_pool) {
  dma_ring = (void *)gen_pool_alloc(eth->sram_pool, size);
  if (!dma_ring)
   return dma_ring;
  *dma_handle = gen_pool_virt_to_phys(eth->sram_pool,
          (unsigned long)dma_ring);
 } else {
  dma_ring = dma_alloc_coherent(eth->dma_dev, size, dma_handle,
           GFP_KERNEL);
 }

 return dma_ring;
}

static void mtk_dma_ring_free(struct mtk_eth *eth, size_t size, void *dma_ring,
         dma_addr_t dma_handle, bool in_sram)
{
 if (in_sram && eth->sram_pool)
  gen_pool_free(eth->sram_pool, (unsigned long)dma_ring, size);
 else
  dma_free_coherent(eth->dma_dev, size, dma_ring, dma_handle);
}

/* the qdma core needs scratch memory to be setup */
static int mtk_init_fq_dma(struct mtk_eth *eth)
{
 const struct mtk_soc_data *soc = eth->soc;
 dma_addr_t phy_ring_tail;
 int cnt = soc->tx.fq_dma_size;
 dma_addr_t dma_addr;
 int i, j, len;

 eth->scratch_ring = mtk_dma_ring_alloc(eth, cnt * soc->tx.desc_size,
            ð->phy_scratch_ring, true);

 if (unlikely(!eth->scratch_ring))
  return -ENOMEM;

 phy_ring_tail = eth->phy_scratch_ring + soc->tx.desc_size * (cnt - 1);

 for (j = 0; j < DIV_ROUND_UP(soc->tx.fq_dma_size, MTK_FQ_DMA_LENGTH); j++) {
  len = min_t(int, cnt - j * MTK_FQ_DMA_LENGTH, MTK_FQ_DMA_LENGTH);
  eth->scratch_head[j] = kcalloc(len, MTK_QDMA_PAGE_SIZE, GFP_KERNEL);

  if (unlikely(!eth->scratch_head[j]))
   return -ENOMEM;

  dma_addr = dma_map_single(eth->dma_dev,
       eth->scratch_head[j], len * MTK_QDMA_PAGE_SIZE,
       DMA_FROM_DEVICE);

  if (unlikely(dma_mapping_error(eth->dma_dev, dma_addr)))
   return -ENOMEM;

  for (i = 0; i < len; i++) {
   struct mtk_tx_dma_v2 *txd;

   txd = eth->scratch_ring + (j * MTK_FQ_DMA_LENGTH + i) * soc->tx.desc_size;
   txd->txd1 = dma_addr + i * MTK_QDMA_PAGE_SIZE;
   if (j * MTK_FQ_DMA_LENGTH + i < cnt)
    txd->txd2 = eth->phy_scratch_ring +
         (j * MTK_FQ_DMA_LENGTH + i + 1) * soc->tx.desc_size;

   txd->txd3 = TX_DMA_PLEN0(MTK_QDMA_PAGE_SIZE);
   if (MTK_HAS_CAPS(soc->caps, MTK_36BIT_DMA))
    txd->txd3 |= TX_DMA_PREP_ADDR64(dma_addr + i * MTK_QDMA_PAGE_SIZE);

   txd->txd4 = 0;
   if (mtk_is_netsys_v2_or_greater(eth)) {
    txd->txd5 = 0;
    txd->txd6 = 0;
    txd->txd7 = 0;
    txd->txd8 = 0;
   }
  }
 }

 mtk_w32(eth, eth->phy_scratch_ring, soc->reg_map->qdma.fq_head);
 mtk_w32(eth, phy_ring_tail, soc->reg_map->qdma.fq_tail);
 mtk_w32(eth, (cnt << 16) | cnt, soc->reg_map->qdma.fq_count);
 mtk_w32(eth, MTK_QDMA_PAGE_SIZE << 16, soc->reg_map->qdma.fq_blen);

 return 0;
}

static void *mtk_qdma_phys_to_virt(struct mtk_tx_ring *ring, u32 desc)
{
 return ring->dma + (desc - ring->phys);
}

static struct mtk_tx_buf *mtk_desc_to_tx_buf(struct mtk_tx_ring *ring,
          void *txd, u32 txd_size)
{
 int idx = (txd - ring->dma) / txd_size;

 return &ring->buf[idx];
}

static struct mtk_tx_dma *qdma_to_pdma(struct mtk_tx_ring *ring,
           struct mtk_tx_dma *dma)
{
 return ring->dma_pdma - (struct mtk_tx_dma *)ring->dma + dma;
}

static int txd_to_idx(struct mtk_tx_ring *ring, void *dma, u32 txd_size)
{
 return (dma - ring->dma) / txd_size;
}

static void mtk_tx_unmap(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
    struct xdp_frame_bulk *bq, bool napi)
{
 if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
  if (tx_buf->flags & MTK_TX_FLAGS_SINGLE0) {
   dma_unmap_single(eth->dma_dev,
      dma_unmap_addr(tx_buf, dma_addr0),
      dma_unmap_len(tx_buf, dma_len0),
      DMA_TO_DEVICE);
  } else if (tx_buf->flags & MTK_TX_FLAGS_PAGE0) {
   dma_unmap_page(eth->dma_dev,
           dma_unmap_addr(tx_buf, dma_addr0),
           dma_unmap_len(tx_buf, dma_len0),
           DMA_TO_DEVICE);
  }
 } else {
  if (dma_unmap_len(tx_buf, dma_len0)) {
   dma_unmap_page(eth->dma_dev,
           dma_unmap_addr(tx_buf, dma_addr0),
           dma_unmap_len(tx_buf, dma_len0),
           DMA_TO_DEVICE);
  }

  if (dma_unmap_len(tx_buf, dma_len1)) {
   dma_unmap_page(eth->dma_dev,
           dma_unmap_addr(tx_buf, dma_addr1),
           dma_unmap_len(tx_buf, dma_len1),
           DMA_TO_DEVICE);
  }
 }

 if (tx_buf->data && tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
  if (tx_buf->type == MTK_TYPE_SKB) {
   struct sk_buff *skb = tx_buf->data;

   if (napi)
    napi_consume_skb(skb, napi);
   else
    dev_kfree_skb_any(skb);
  } else {
   struct xdp_frame *xdpf = tx_buf->data;

   if (napi && tx_buf->type == MTK_TYPE_XDP_TX)
    xdp_return_frame_rx_napi(xdpf);
   else if (bq)
    xdp_return_frame_bulk(xdpf, bq);
   else
    xdp_return_frame(xdpf);
  }
 }
 tx_buf->flags = 0;
 tx_buf->data = NULL;
}

static void setup_tx_buf(struct mtk_eth *eth, struct mtk_tx_buf *tx_buf,
    struct mtk_tx_dma *txd, dma_addr_t mapped_addr,
    size_t size, int idx)
{
 if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) {
  dma_unmap_addr_set(tx_buf, dma_addr0, mapped_addr);
  dma_unmap_len_set(tx_buf, dma_len0, size);
 } else {
  if (idx & 1) {
   txd->txd3 = mapped_addr;
   txd->txd2 |= TX_DMA_PLEN1(size);
   dma_unmap_addr_set(tx_buf, dma_addr1, mapped_addr);
   dma_unmap_len_set(tx_buf, dma_len1, size);
  } else {
   tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
   txd->txd1 = mapped_addr;
   txd->txd2 = TX_DMA_PLEN0(size);
   dma_unmap_addr_set(tx_buf, dma_addr0, mapped_addr);
   dma_unmap_len_set(tx_buf, dma_len0, size);
  }
 }
}

static void mtk_tx_set_dma_desc_v1(struct net_device *dev, void *txd,
       struct mtk_tx_dma_desc_info *info)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_eth *eth = mac->hw;
 struct mtk_tx_dma *desc = txd;
 u32 data;

 WRITE_ONCE(desc->txd1, info->addr);

 data = TX_DMA_SWC | TX_DMA_PLEN0(info->size) |
        FIELD_PREP(TX_DMA_PQID, info->qid);
 if (info->last)
  data |= TX_DMA_LS0;
 WRITE_ONCE(desc->txd3, data);

 data = (mac->id + 1) << TX_DMA_FPORT_SHIFT; /* forward port */
 if (info->first) {
  if (info->gso)
   data |= TX_DMA_TSO;
  /* tx checksum offload */
  if (info->csum)
   data |= TX_DMA_CHKSUM;
  /* vlan header offload */
  if (info->vlan)
   data |= TX_DMA_INS_VLAN | info->vlan_tci;
 }
 WRITE_ONCE(desc->txd4, data);
}

static void mtk_tx_set_dma_desc_v2(struct net_device *dev, void *txd,
       struct mtk_tx_dma_desc_info *info)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_tx_dma_v2 *desc = txd;
 struct mtk_eth *eth = mac->hw;
 u32 data;

 WRITE_ONCE(desc->txd1, info->addr);

 data = TX_DMA_PLEN0(info->size);
 if (info->last)
  data |= TX_DMA_LS0;

 if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
  data |= TX_DMA_PREP_ADDR64(info->addr);

 WRITE_ONCE(desc->txd3, data);

  /* set forward port */
 switch (mac->id) {
 case MTK_GMAC1_ID:
  data = PSE_GDM1_PORT << TX_DMA_FPORT_SHIFT_V2;
  break;
 case MTK_GMAC2_ID:
  data = PSE_GDM2_PORT << TX_DMA_FPORT_SHIFT_V2;
  break;
 case MTK_GMAC3_ID:
  data = PSE_GDM3_PORT << TX_DMA_FPORT_SHIFT_V2;
  break;
 }

 data |= TX_DMA_SWC_V2 | QID_BITS_V2(info->qid);
 WRITE_ONCE(desc->txd4, data);

 data = 0;
 if (info->first) {
  if (info->gso)
   data |= TX_DMA_TSO_V2;
  /* tx checksum offload */
  if (info->csum)
   data |= TX_DMA_CHKSUM_V2;
  if (mtk_is_netsys_v3_or_greater(eth) && netdev_uses_dsa(dev))
   data |= TX_DMA_SPTAG_V3;
 }
 WRITE_ONCE(desc->txd5, data);

 data = 0;
 if (info->first && info->vlan)
  data |= TX_DMA_INS_VLAN_V2 | info->vlan_tci;
 WRITE_ONCE(desc->txd6, data);

 WRITE_ONCE(desc->txd7, 0);
 WRITE_ONCE(desc->txd8, 0);
}

static void mtk_tx_set_dma_desc(struct net_device *dev, void *txd,
    struct mtk_tx_dma_desc_info *info)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_eth *eth = mac->hw;

 if (mtk_is_netsys_v2_or_greater(eth))
  mtk_tx_set_dma_desc_v2(dev, txd, info);
 else
  mtk_tx_set_dma_desc_v1(dev, txd, info);
}

static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
        int tx_num, struct mtk_tx_ring *ring, bool gso)
{
 struct mtk_tx_dma_desc_info txd_info = {
  .size = skb_headlen(skb),
  .gso = gso,
  .csum = skb->ip_summed == CHECKSUM_PARTIAL,
  .vlan = skb_vlan_tag_present(skb),
  .qid = skb_get_queue_mapping(skb),
  .vlan_tci = skb_vlan_tag_get(skb),
  .first = true,
  .last = !skb_is_nonlinear(skb),
 };
 struct netdev_queue *txq;
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_eth *eth = mac->hw;
 const struct mtk_soc_data *soc = eth->soc;
 struct mtk_tx_dma *itxd, *txd;
 struct mtk_tx_dma *itxd_pdma, *txd_pdma;
 struct mtk_tx_buf *itx_buf, *tx_buf;
 int i, n_desc = 1;
 int queue = skb_get_queue_mapping(skb);
 int k = 0;

 txq = netdev_get_tx_queue(dev, queue);
 itxd = ring->next_free;
 itxd_pdma = qdma_to_pdma(ring, itxd);
 if (itxd == ring->last_free)
  return -ENOMEM;

 itx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_size);
 memset(itx_buf, 0, sizeof(*itx_buf));

 txd_info.addr = dma_map_single(eth->dma_dev, skb->data, txd_info.size,
           DMA_TO_DEVICE);
 if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
  return -ENOMEM;

 mtk_tx_set_dma_desc(dev, itxd, &txd_info);

 itx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
 itx_buf->mac_id = mac->id;
 setup_tx_buf(eth, itx_buf, itxd_pdma, txd_info.addr, txd_info.size,
       k++);

 /* TX SG offload */
 txd = itxd;
 txd_pdma = qdma_to_pdma(ring, txd);

 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
  unsigned int offset = 0;
  int frag_size = skb_frag_size(frag);

  while (frag_size) {
   bool new_desc = true;

   if (MTK_HAS_CAPS(soc->caps, MTK_QDMA) ||
       (i & 0x1)) {
    txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
    txd_pdma = qdma_to_pdma(ring, txd);
    if (txd == ring->last_free)
     goto err_dma;

    n_desc++;
   } else {
    new_desc = false;
   }

   memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
   txd_info.size = min_t(unsigned int, frag_size,
           soc->tx.dma_max_len);
   txd_info.qid = queue;
   txd_info.last = i == skb_shinfo(skb)->nr_frags - 1 &&
     !(frag_size - txd_info.size);
   txd_info.addr = skb_frag_dma_map(eth->dma_dev, frag,
        offset, txd_info.size,
        DMA_TO_DEVICE);
   if (unlikely(dma_mapping_error(eth->dma_dev, txd_info.addr)))
    goto err_dma;

   mtk_tx_set_dma_desc(dev, txd, &txd_info);

   tx_buf = mtk_desc_to_tx_buf(ring, txd,
          soc->tx.desc_size);
   if (new_desc)
    memset(tx_buf, 0, sizeof(*tx_buf));
   tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;
   tx_buf->flags |= MTK_TX_FLAGS_PAGE0;
   tx_buf->mac_id = mac->id;

   setup_tx_buf(eth, tx_buf, txd_pdma, txd_info.addr,
         txd_info.size, k++);

   frag_size -= txd_info.size;
   offset += txd_info.size;
  }
 }

 /* store skb to cleanup */
 itx_buf->type = MTK_TYPE_SKB;
 itx_buf->data = skb;

 if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
  if (k & 0x1)
   txd_pdma->txd2 |= TX_DMA_LS0;
  else
   txd_pdma->txd2 |= TX_DMA_LS1;
 }

 netdev_tx_sent_queue(txq, skb->len);
 skb_tx_timestamp(skb);

 ring->next_free = mtk_qdma_phys_to_virt(ring, txd->txd2);
 atomic_sub(n_desc, &ring->free_count);

 /* make sure that all changes to the dma ring are flushed before we
 * continue
 */

 wmb();

 if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
  if (netif_xmit_stopped(txq) || !netdev_xmit_more())
   mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
 } else {
  int next_idx;

  next_idx = NEXT_DESP_IDX(txd_to_idx(ring, txd, soc->tx.desc_size),
      ring->dma_size);
  mtk_w32(eth, next_idx, MT7628_TX_CTX_IDX0);
 }

 return 0;

err_dma:
 do {
  tx_buf = mtk_desc_to_tx_buf(ring, itxd, soc->tx.desc_size);

  /* unmap dma */
  mtk_tx_unmap(eth, tx_buf, NULL, false);

  itxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
  if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA))
   itxd_pdma->txd2 = TX_DMA_DESP2_DEF;

  itxd = mtk_qdma_phys_to_virt(ring, itxd->txd2);
  itxd_pdma = qdma_to_pdma(ring, itxd);
 } while (itxd != txd);

 return -ENOMEM;
}

static int mtk_cal_txd_req(struct mtk_eth *eth, struct sk_buff *skb)
{
 int i, nfrags = 1;
 skb_frag_t *frag;

 if (skb_is_gso(skb)) {
  for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
   frag = &skb_shinfo(skb)->frags[i];
   nfrags += DIV_ROUND_UP(skb_frag_size(frag),
            eth->soc->tx.dma_max_len);
  }
 } else {
  nfrags += skb_shinfo(skb)->nr_frags;
 }

 return nfrags;
}

static int mtk_queue_stopped(struct mtk_eth *eth)
{
 int i;

 for (i = 0; i < MTK_MAX_DEVS; i++) {
  if (!eth->netdev[i])
   continue;
  if (netif_queue_stopped(eth->netdev[i]))
   return 1;
 }

 return 0;
}

static void mtk_wake_queue(struct mtk_eth *eth)
{
 int i;

 for (i = 0; i < MTK_MAX_DEVS; i++) {
  if (!eth->netdev[i])
   continue;
  netif_tx_wake_all_queues(eth->netdev[i]);
 }
}

static netdev_tx_t mtk_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_eth *eth = mac->hw;
 struct mtk_tx_ring *ring = ð->tx_ring;
 struct net_device_stats *stats = &dev->stats;
 bool gso = false;
 int tx_num;

 if (skb_vlan_tag_present(skb) &&
     !eth_proto_is_802_3(eth_hdr(skb)->h_proto)) {
  skb = __vlan_hwaccel_push_inside(skb);
  if (!skb)
   goto dropped;
 }

 /* normally we can rely on the stack not calling this more than once,
 * however we have 2 queues running on the same ring so we need to lock
 * the ring access
 */

 spin_lock(ð->page_lock);

 if (unlikely(test_bit(MTK_RESETTING, ð->state)))
  goto drop;

 tx_num = mtk_cal_txd_req(eth, skb);
 if (unlikely(atomic_read(&ring->free_count) <= tx_num)) {
  netif_tx_stop_all_queues(dev);
  netif_err(eth, tx_queued, dev,
     "Tx Ring full when queue awake!\n");
  spin_unlock(ð->page_lock);
  return NETDEV_TX_BUSY;
 }

 /* TSO: fill MSS info in tcp checksum field */
 if (skb_is_gso(skb)) {
  if (skb_cow_head(skb, 0)) {
   netif_warn(eth, tx_err, dev,
       "GSO expand head fail.\n");
   goto drop;
  }

  if (skb_shinfo(skb)->gso_type &
    (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) {
   gso = true;
   tcp_hdr(skb)->check = htons(skb_shinfo(skb)->gso_size);
  }
 }

 if (mtk_tx_map(skb, dev, tx_num, ring, gso) < 0)
  goto drop;

 if (unlikely(atomic_read(&ring->free_count) <= ring->thresh))
  netif_tx_stop_all_queues(dev);

 spin_unlock(ð->page_lock);

 return NETDEV_TX_OK;

drop:
 spin_unlock(ð->page_lock);
 dev_kfree_skb_any(skb);
dropped:
 stats->tx_dropped++;
 return NETDEV_TX_OK;
}

static struct mtk_rx_ring *mtk_get_rx_ring(struct mtk_eth *eth)
{
 int i;
 struct mtk_rx_ring *ring;
 int idx;

 if (!eth->hwlro)
  return ð->rx_ring[0];

 for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
  struct mtk_rx_dma *rxd;

  ring = ð->rx_ring[i];
  idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
  rxd = ring->dma + idx * eth->soc->rx.desc_size;
  if (rxd->rxd2 & RX_DMA_DONE) {
   ring->calc_idx_update = true;
   return ring;
  }
 }

 return NULL;
}

static void mtk_update_rx_cpu_idx(struct mtk_eth *eth)
{
 struct mtk_rx_ring *ring;
 int i;

 if (!eth->hwlro) {
  ring = ð->rx_ring[0];
  mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
 } else {
  for (i = 0; i < MTK_MAX_RX_RING_NUM; i++) {
   ring = ð->rx_ring[i];
   if (ring->calc_idx_update) {
    ring->calc_idx_update = false;
    mtk_w32(eth, ring->calc_idx, ring->crx_idx_reg);
   }
  }
 }
}

static bool mtk_page_pool_enabled(struct mtk_eth *eth)
{
 return mtk_is_netsys_v2_or_greater(eth);
}

static struct page_pool *mtk_create_page_pool(struct mtk_eth *eth,
           struct xdp_rxq_info *xdp_q,
           int id, int size)
{
 struct page_pool_params pp_params = {
  .order = 0,
  .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
  .pool_size = size,
  .nid = NUMA_NO_NODE,
  .dev = eth->dma_dev,
  .offset = MTK_PP_HEADROOM,
  .max_len = MTK_PP_MAX_BUF_SIZE,
 };
 struct page_pool *pp;
 int err;

 pp_params.dma_dir = rcu_access_pointer(eth->prog) ? DMA_BIDIRECTIONAL
         : DMA_FROM_DEVICE;
 pp = page_pool_create(&pp_params);
 if (IS_ERR(pp))
  return pp;

 err = __xdp_rxq_info_reg(xdp_q, eth->dummy_dev, id,
     eth->rx_napi.napi_id, PAGE_SIZE);
 if (err < 0)
  goto err_free_pp;

 err = xdp_rxq_info_reg_mem_model(xdp_q, MEM_TYPE_PAGE_POOL, pp);
 if (err)
  goto err_unregister_rxq;

 return pp;

err_unregister_rxq:
 xdp_rxq_info_unreg(xdp_q);
err_free_pp:
 page_pool_destroy(pp);

 return ERR_PTR(err);
}

static void *mtk_page_pool_get_buff(struct page_pool *pp, dma_addr_t *dma_addr,
        gfp_t gfp_mask)
{
 struct page *page;

 page = page_pool_alloc_pages(pp, gfp_mask | __GFP_NOWARN);
 if (!page)
  return NULL;

 *dma_addr = page_pool_get_dma_addr(page) + MTK_PP_HEADROOM;
 return page_address(page);
}

static void mtk_rx_put_buff(struct mtk_rx_ring *ring, void *data, bool napi)
{
 if (ring->page_pool)
  page_pool_put_full_page(ring->page_pool,
     virt_to_head_page(data), napi);
 else
  skb_free_frag(data);
}

static int mtk_xdp_frame_map(struct mtk_eth *eth, struct net_device *dev,
        struct mtk_tx_dma_desc_info *txd_info,
        struct mtk_tx_dma *txd, struct mtk_tx_buf *tx_buf,
        void *data, u16 headroom, int index, bool dma_map)
{
 struct mtk_tx_ring *ring = ð->tx_ring;
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_tx_dma *txd_pdma;

 if (dma_map) {  /* ndo_xdp_xmit */
  txd_info->addr = dma_map_single(eth->dma_dev, data,
      txd_info->size, DMA_TO_DEVICE);
  if (unlikely(dma_mapping_error(eth->dma_dev, txd_info->addr)))
   return -ENOMEM;

  tx_buf->flags |= MTK_TX_FLAGS_SINGLE0;
 } else {
  struct page *page = virt_to_head_page(data);

  txd_info->addr = page_pool_get_dma_addr(page) +
     sizeof(struct xdp_frame) + headroom;
  dma_sync_single_for_device(eth->dma_dev, txd_info->addr,
        txd_info->size, DMA_BIDIRECTIONAL);
 }
 mtk_tx_set_dma_desc(dev, txd, txd_info);

 tx_buf->mac_id = mac->id;
 tx_buf->type = dma_map ? MTK_TYPE_XDP_NDO : MTK_TYPE_XDP_TX;
 tx_buf->data = (void *)MTK_DMA_DUMMY_DESC;

 txd_pdma = qdma_to_pdma(ring, txd);
 setup_tx_buf(eth, tx_buf, txd_pdma, txd_info->addr, txd_info->size,
       index);

 return 0;
}

static int mtk_xdp_submit_frame(struct mtk_eth *eth, struct xdp_frame *xdpf,
    struct net_device *dev, bool dma_map)
{
 struct skb_shared_info *sinfo = xdp_get_shared_info_from_frame(xdpf);
 const struct mtk_soc_data *soc = eth->soc;
 struct mtk_tx_ring *ring = ð->tx_ring;
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_tx_dma_desc_info txd_info = {
  .size = xdpf->len,
  .first = true,
  .last = !xdp_frame_has_frags(xdpf),
  .qid = mac->id,
 };
 int err, index = 0, n_desc = 1, nr_frags;
 struct mtk_tx_buf *htx_buf, *tx_buf;
 struct mtk_tx_dma *htxd, *txd;
 void *data = xdpf->data;

 if (unlikely(test_bit(MTK_RESETTING, ð->state)))
  return -EBUSY;

 nr_frags = unlikely(xdp_frame_has_frags(xdpf)) ? sinfo->nr_frags : 0;
 if (unlikely(atomic_read(&ring->free_count) <= 1 + nr_frags))
  return -EBUSY;

 spin_lock(ð->page_lock);

 txd = ring->next_free;
 if (txd == ring->last_free) {
  spin_unlock(ð->page_lock);
  return -ENOMEM;
 }
 htxd = txd;

 tx_buf = mtk_desc_to_tx_buf(ring, txd, soc->tx.desc_size);
 memset(tx_buf, 0, sizeof(*tx_buf));
 htx_buf = tx_buf;

 for (;;) {
  err = mtk_xdp_frame_map(eth, dev, &txd_info, txd, tx_buf,
     data, xdpf->headroom, index, dma_map);
  if (err < 0)
   goto unmap;

  if (txd_info.last)
   break;

  if (MTK_HAS_CAPS(soc->caps, MTK_QDMA) || (index & 0x1)) {
   txd = mtk_qdma_phys_to_virt(ring, txd->txd2);
   if (txd == ring->last_free)
    goto unmap;

   tx_buf = mtk_desc_to_tx_buf(ring, txd,
          soc->tx.desc_size);
   memset(tx_buf, 0, sizeof(*tx_buf));
   n_desc++;
  }

  memset(&txd_info, 0, sizeof(struct mtk_tx_dma_desc_info));
  txd_info.size = skb_frag_size(&sinfo->frags[index]);
  txd_info.last = index + 1 == nr_frags;
  txd_info.qid = mac->id;
  data = skb_frag_address(&sinfo->frags[index]);

  index++;
 }
 /* store xdpf for cleanup */
 htx_buf->data = xdpf;

 if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
  struct mtk_tx_dma *txd_pdma = qdma_to_pdma(ring, txd);

  if (index & 1)
   txd_pdma->txd2 |= TX_DMA_LS0;
  else
   txd_pdma->txd2 |= TX_DMA_LS1;
 }

 ring->next_free = mtk_qdma_phys_to_virt(ring, txd->txd2);
 atomic_sub(n_desc, &ring->free_count);

 /* make sure that all changes to the dma ring are flushed before we
 * continue
 */

 wmb();

 if (MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
  mtk_w32(eth, txd->txd2, soc->reg_map->qdma.ctx_ptr);
 } else {
  int idx;

  idx = txd_to_idx(ring, txd, soc->tx.desc_size);
  mtk_w32(eth, NEXT_DESP_IDX(idx, ring->dma_size),
   MT7628_TX_CTX_IDX0);
 }

 spin_unlock(ð->page_lock);

 return 0;

unmap:
 while (htxd != txd) {
  tx_buf = mtk_desc_to_tx_buf(ring, htxd, soc->tx.desc_size);
  mtk_tx_unmap(eth, tx_buf, NULL, false);

  htxd->txd3 = TX_DMA_LS0 | TX_DMA_OWNER_CPU;
  if (!MTK_HAS_CAPS(soc->caps, MTK_QDMA)) {
   struct mtk_tx_dma *txd_pdma = qdma_to_pdma(ring, htxd);

   txd_pdma->txd2 = TX_DMA_DESP2_DEF;
  }

  htxd = mtk_qdma_phys_to_virt(ring, htxd->txd2);
 }

 spin_unlock(ð->page_lock);

 return err;
}

static int mtk_xdp_xmit(struct net_device *dev, int num_frame,
   struct xdp_frame **frames, u32 flags)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_hw_stats *hw_stats = mac->hw_stats;
 struct mtk_eth *eth = mac->hw;
 int i, nxmit = 0;

 if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
  return -EINVAL;

 for (i = 0; i < num_frame; i++) {
  if (mtk_xdp_submit_frame(eth, frames[i], dev, true))
   break;
  nxmit++;
 }

 u64_stats_update_begin(&hw_stats->syncp);
 hw_stats->xdp_stats.tx_xdp_xmit += nxmit;
 hw_stats->xdp_stats.tx_xdp_xmit_errors += num_frame - nxmit;
 u64_stats_update_end(&hw_stats->syncp);

 return nxmit;
}

static u32 mtk_xdp_run(struct mtk_eth *eth, struct mtk_rx_ring *ring,
         struct xdp_buff *xdp, struct net_device *dev)
{
 struct mtk_mac *mac = netdev_priv(dev);
 struct mtk_hw_stats *hw_stats = mac->hw_stats;
 u64 *count = &hw_stats->xdp_stats.rx_xdp_drop;
 struct bpf_prog *prog;
 u32 act = XDP_PASS;

 rcu_read_lock();

 prog = rcu_dereference(eth->prog);
 if (!prog)
  goto out;

 act = bpf_prog_run_xdp(prog, xdp);
 switch (act) {
 case XDP_PASS:
  count = &hw_stats->xdp_stats.rx_xdp_pass;
  goto update_stats;
 case XDP_REDIRECT:
  if (unlikely(xdp_do_redirect(dev, xdp, prog))) {
   act = XDP_DROP;
   break;
  }

  count = &hw_stats->xdp_stats.rx_xdp_redirect;
  goto update_stats;
 case XDP_TX: {
  struct xdp_frame *xdpf = xdp_convert_buff_to_frame(xdp);

  if (!xdpf || mtk_xdp_submit_frame(eth, xdpf, dev, false)) {
   count = &hw_stats->xdp_stats.rx_xdp_tx_errors;
   act = XDP_DROP;
   break;
  }

  count = &hw_stats->xdp_stats.rx_xdp_tx;
  goto update_stats;
 }
 default:
  bpf_warn_invalid_xdp_action(dev, prog, act);
  fallthrough;
 case XDP_ABORTED:
  trace_xdp_exception(dev, prog, act);
  fallthrough;
 case XDP_DROP:
  break;
 }

 page_pool_put_full_page(ring->page_pool,
    virt_to_head_page(xdp->data), true);

update_stats:
 u64_stats_update_begin(&hw_stats->syncp);
 *count = *count + 1;
 u64_stats_update_end(&hw_stats->syncp);
out:
 rcu_read_unlock();

 return act;
}

static int mtk_poll_rx(struct napi_struct *napi, int budget,
         struct mtk_eth *eth)
{
 struct dim_sample dim_sample = {};
 struct mtk_rx_ring *ring;
 bool xdp_flush = false;
 int idx;
 struct sk_buff *skb;
 u64 addr64 = 0;
 u8 *data, *new_data;
 struct mtk_rx_dma_v2 *rxd, trxd;
 int done = 0, bytes = 0;
 dma_addr_t dma_addr = DMA_MAPPING_ERROR;
 int ppe_idx = 0;

 while (done < budget) {
  unsigned int pktlen, *rxdcsum;
  struct net_device *netdev;
  u32 hash, reason;
  int mac = 0;

  ring = mtk_get_rx_ring(eth);
  if (unlikely(!ring))
   goto rx_done;

  idx = NEXT_DESP_IDX(ring->calc_idx, ring->dma_size);
  rxd = ring->dma + idx * eth->soc->rx.desc_size;
  data = ring->data[idx];

  if (!mtk_rx_get_desc(eth, &trxd, rxd))
   break;

  /* find out which mac the packet come from. values start at 1 */
  if (mtk_is_netsys_v3_or_greater(eth)) {
   u32 val = RX_DMA_GET_SPORT_V2(trxd.rxd5);

   switch (val) {
   case PSE_GDM1_PORT:
   case PSE_GDM2_PORT:
    mac = val - 1;
    break;
   case PSE_GDM3_PORT:
    mac = MTK_GMAC3_ID;
    break;
   default:
    break;
   }
  } else if (!MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628) &&
      !(trxd.rxd4 & RX_DMA_SPECIAL_TAG)) {
   mac = RX_DMA_GET_SPORT(trxd.rxd4) - 1;
  }

  if (unlikely(mac < 0 || mac >= MTK_MAX_DEVS ||
        !eth->netdev[mac]))
   goto release_desc;

  netdev = eth->netdev[mac];
  ppe_idx = eth->mac[mac]->ppe_idx;

  if (unlikely(test_bit(MTK_RESETTING, ð->state)))
   goto release_desc;

  pktlen = RX_DMA_GET_PLEN0(trxd.rxd2);

  /* alloc new buffer */
  if (ring->page_pool) {
   struct page *page = virt_to_head_page(data);
   struct xdp_buff xdp;
   u32 ret, metasize;

   new_data = mtk_page_pool_get_buff(ring->page_pool,
         &dma_addr,
         GFP_ATOMIC);
   if (unlikely(!new_data)) {
    netdev->stats.rx_dropped++;
    goto release_desc;
   }

   dma_sync_single_for_cpu(eth->dma_dev,
    page_pool_get_dma_addr(page) + MTK_PP_HEADROOM,
    pktlen, page_pool_get_dma_dir(ring->page_pool));

   xdp_init_buff(&xdp, PAGE_SIZE, &ring->xdp_q);
   xdp_prepare_buff(&xdp, data, MTK_PP_HEADROOM, pktlen,
      true);
   xdp_buff_clear_frags_flag(&xdp);

   ret = mtk_xdp_run(eth, ring, &xdp, netdev);
   if (ret == XDP_REDIRECT)
    xdp_flush = true;

   if (ret != XDP_PASS)
    goto skip_rx;

   skb = build_skb(data, PAGE_SIZE);
   if (unlikely(!skb)) {
    page_pool_put_full_page(ring->page_pool,
       page, true);
    netdev->stats.rx_dropped++;
    goto skip_rx;
   }

   skb_reserve(skb, xdp.data - xdp.data_hard_start);
   skb_put(skb, xdp.data_end - xdp.data);
   metasize = xdp.data - xdp.data_meta;
   if (metasize)
    skb_metadata_set(skb, metasize);
   skb_mark_for_recycle(skb);
  } else {
   if (ring->frag_size <= PAGE_SIZE)
    new_data = napi_alloc_frag(ring->frag_size);
   else
    new_data = mtk_max_lro_buf_alloc(GFP_ATOMIC);

   if (unlikely(!new_data)) {
    netdev->stats.rx_dropped++;
    goto release_desc;
   }

   dma_addr = dma_map_single(eth->dma_dev,
    new_data + NET_SKB_PAD + eth->ip_align,
    ring->buf_size, DMA_FROM_DEVICE);
   if (unlikely(dma_mapping_error(eth->dma_dev,
             dma_addr))) {
    skb_free_frag(new_data);
    netdev->stats.rx_dropped++;
    goto release_desc;
   }

   if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA))
    addr64 = RX_DMA_GET_ADDR64(trxd.rxd2);

   dma_unmap_single(eth->dma_dev, ((u64)trxd.rxd1 | addr64),
      ring->buf_size, DMA_FROM_DEVICE);

   skb = build_skb(data, ring->frag_size);
   if (unlikely(!skb)) {
    netdev->stats.rx_dropped++;
    skb_free_frag(data);
    goto skip_rx;
   }

   skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
   skb_put(skb, pktlen);
  }

  skb->dev = netdev;
  bytes += skb->len;

  if (mtk_is_netsys_v3_or_greater(eth)) {
   reason = FIELD_GET(MTK_RXD5_PPE_CPU_REASON, trxd.rxd5);
   hash = trxd.rxd5 & MTK_RXD5_FOE_ENTRY;
   if (hash != MTK_RXD5_FOE_ENTRY)
    skb_set_hash(skb, jhash_1word(hash, 0),
          PKT_HASH_TYPE_L4);
   rxdcsum = &trxd.rxd3;
  } else {
   reason = FIELD_GET(MTK_RXD4_PPE_CPU_REASON, trxd.rxd4);
   hash = trxd.rxd4 & MTK_RXD4_FOE_ENTRY;
   if (hash != MTK_RXD4_FOE_ENTRY)
    skb_set_hash(skb, jhash_1word(hash, 0),
          PKT_HASH_TYPE_L4);
   rxdcsum = &trxd.rxd4;
  }

  if (*rxdcsum & eth->soc->rx.dma_l4_valid)
   skb->ip_summed = CHECKSUM_UNNECESSARY;
  else
   skb_checksum_none_assert(skb);
  skb->protocol = eth_type_trans(skb, netdev);

  /* When using VLAN untagging in combination with DSA, the
 * hardware treats the MTK special tag as a VLAN and untags it.
 */

  if (mtk_is_netsys_v1(eth) && (trxd.rxd2 & RX_DMA_VTAG) &&
      netdev_uses_dsa(netdev)) {
   unsigned int port = RX_DMA_VPID(trxd.rxd3) & GENMASK(2, 0);

   if (port < ARRAY_SIZE(eth->dsa_meta) &&
       eth->dsa_meta[port])
    skb_dst_set_noref(skb, ð->dsa_meta[port]->dst);
  }

  if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
   mtk_ppe_check_skb(eth->ppe[ppe_idx], skb, hash);

  skb_record_rx_queue(skb, 0);
  napi_gro_receive(napi, skb);

skip_rx:
  ring->data[idx] = new_data;
  rxd->rxd1 = (unsigned int)dma_addr;
release_desc:
  if (MTK_HAS_CAPS(eth->soc->caps, MTK_36BIT_DMA)) {
   if (unlikely(dma_addr == DMA_MAPPING_ERROR))
    addr64 = FIELD_GET(RX_DMA_ADDR64_MASK,
         rxd->rxd2);
   else
    addr64 = RX_DMA_PREP_ADDR64(dma_addr);
  }

  if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628))
   rxd->rxd2 = RX_DMA_LSO;
  else
   rxd->rxd2 = RX_DMA_PREP_PLEN0(ring->buf_size) | addr64;

  ring->calc_idx = idx;
  done++;
 }

rx_done:
 if (done) {
  /* make sure that all changes to the dma ring are flushed before
 * we continue
 */

  wmb();
  mtk_update_rx_cpu_idx(eth);
 }

 eth->rx_packets += done;
 eth->rx_bytes += bytes;
 dim_update_sample(eth->rx_events, eth->rx_packets, eth->rx_bytes,
     &dim_sample);
 net_dim(ð->rx_dim, &dim_sample);

 if (xdp_flush)
  xdp_do_flush();

 return done;
}

struct mtk_poll_state {
    struct netdev_queue *txq;
    unsigned int total;
    unsigned int done;
    unsigned int bytes;
};

static void
mtk_poll_tx_done(struct mtk_eth *eth, struct mtk_poll_state *state, u8 mac,
   struct sk_buff *skb)
{
 struct netdev_queue *txq;
 struct net_device *dev;
 unsigned int bytes = skb->len;

 state->total++;
 eth->tx_packets++;
 eth->tx_bytes += bytes;

 dev = eth->netdev[mac];
 if (!dev)
  return;

 txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
 if (state->txq == txq) {
  state->done++;
  state->bytes += bytes;
  return;
 }

 if (state->txq)
  netdev_tx_completed_queue(state->txq, state->done, state->bytes);

 state->txq = txq;
 state->done = 1;
 state->bytes = bytes;
}

static int mtk_poll_tx_qdma(struct mtk_eth *eth, int budget,
       struct mtk_poll_state *state)
{
 const struct mtk_reg_map *reg_map = eth->soc->reg_map;
 struct mtk_tx_ring *ring = ð->tx_ring;
 struct mtk_tx_buf *tx_buf;
 struct xdp_frame_bulk bq;
 struct mtk_tx_dma *desc;
 u32 cpu, dma;

 cpu = ring->last_free_ptr;
 dma = mtk_r32(eth, reg_map->qdma.drx_ptr);

 desc = mtk_qdma_phys_to_virt(ring, cpu);
 xdp_frame_bulk_init(&bq);

 while ((cpu != dma) && budget) {
  u32 next_cpu = desc->txd2;

  desc = mtk_qdma_phys_to_virt(ring, desc->txd2);
  if ((desc->txd3 & TX_DMA_OWNER_CPU) == 0)
   break;

  tx_buf = mtk_desc_to_tx_buf(ring, desc,
         eth->soc->tx.desc_size);
  if (!tx_buf->data)
   break;

  if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
   if (tx_buf->type == MTK_TYPE_SKB)
    mtk_poll_tx_done(eth, state, tx_buf->mac_id,
       tx_buf->data);

   budget--;
  }
  mtk_tx_unmap(eth, tx_buf, &bq, true);

  ring->last_free = desc;
  atomic_inc(&ring->free_count);

  cpu = next_cpu;
 }
 xdp_flush_frame_bulk(&bq);

 ring->last_free_ptr = cpu;
 mtk_w32(eth, cpu, reg_map->qdma.crx_ptr);

 return budget;
}

static int mtk_poll_tx_pdma(struct mtk_eth *eth, int budget,
       struct mtk_poll_state *state)
{
 struct mtk_tx_ring *ring = ð->tx_ring;
 struct mtk_tx_buf *tx_buf;
 struct xdp_frame_bulk bq;
 struct mtk_tx_dma *desc;
 u32 cpu, dma;

 cpu = ring->cpu_idx;
 dma = mtk_r32(eth, MT7628_TX_DTX_IDX0);
 xdp_frame_bulk_init(&bq);

 while ((cpu != dma) && budget) {
  tx_buf = &ring->buf[cpu];
  if (!tx_buf->data)
   break;

  if (tx_buf->data != (void *)MTK_DMA_DUMMY_DESC) {
   if (tx_buf->type == MTK_TYPE_SKB)
    mtk_poll_tx_done(eth, state, 0, tx_buf->data);
   budget--;
  }
  mtk_tx_unmap(eth, tx_buf, &bq, true);

  desc = ring->dma + cpu * eth->soc->tx.desc_size;
  ring->last_free = desc;
  atomic_inc(&ring->free_count);

  cpu = NEXT_DESP_IDX(cpu, ring->dma_size);
 }
 xdp_flush_frame_bulk(&bq);

 ring->cpu_idx = cpu;

 return budget;
}

static int mtk_poll_tx(struct mtk_eth *eth, int budget)
{
 struct mtk_tx_ring *ring = ð->tx_ring;
 struct dim_sample dim_sample = {};
 struct mtk_poll_state state = {};

 if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
  budget = mtk_poll_tx_qdma(eth, budget, &state);
 else
  budget = mtk_poll_tx_pdma(eth, budget, &state);

 if (state.txq)
  netdev_tx_completed_queue(state.txq, state.done, state.bytes);

 dim_update_sample(eth->tx_events, eth->tx_packets, eth->tx_bytes,
     &dim_sample);
 net_dim(ð->tx_dim, &dim_sample);

 if (mtk_queue_stopped(eth) &&
     (atomic_read(&ring->free_count) > ring->thresh))
  mtk_wake_queue(eth);

 return state.total;
}

static void mtk_handle_status_irq(struct mtk_eth *eth)
{
 u32 status2 = mtk_r32(eth, MTK_INT_STATUS2);

 if (unlikely(status2 & (MTK_GDM1_AF | MTK_GDM2_AF))) {
  mtk_stats_update(eth);
  mtk_w32(eth, (MTK_GDM1_AF | MTK_GDM2_AF),
   MTK_INT_STATUS2);
 }
}

static int mtk_napi_tx(struct napi_struct *napi, int budget)
{
 struct mtk_eth *eth = container_of(napi, struct mtk_eth, tx_napi);
 const struct mtk_reg_map *reg_map = eth->soc->reg_map;
 int tx_done = 0;

 if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA))
  mtk_handle_status_irq(eth);
 mtk_w32(eth, MTK_TX_DONE_INT, reg_map->tx_irq_status);
 tx_done = mtk_poll_tx(eth, budget);

 if (unlikely(netif_msg_intr(eth))) {
  dev_info(eth->dev,
    "done tx %d, intr 0x%08x/0x%x\n", tx_done,
    mtk_r32(eth, reg_map->tx_irq_status),
    mtk_r32(eth, reg_map->tx_irq_mask));
 }

 if (tx_done == budget)
  return budget;

 if (mtk_r32(eth, reg_map->tx_irq_status) & MTK_TX_DONE_INT)
  return budget;

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=89 G=92

¤ Dauer der Verarbeitung: 0.27 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.