From 25cec241f5c1ce420e6e49c4838404f5a0d64dae Mon Sep 17 00:00:00 2001 From: Dirk Chang Date: Wed, 20 Nov 2024 17:28:26 +0800 Subject: [PATCH] rockchip: update rk3568 ethernet dts and driver --- package/base-files/files/etc/build_version | 2 +- .../boot/dts/rockchip/rk3568-tlink-r4x.dts | 42 +- .../boot/dts/rockchip/rk3568-tlink-r7.dts | 42 +- target/linux/rockchip/image/armv8.mk | 2 + ...net-ethernet-stmicro-stmmac-rockchip.patch | 1715 +++++++++++++++++ 5 files changed, 1778 insertions(+), 25 deletions(-) create mode 100644 target/linux/rockchip/patches-6.6/z_9002-drivers-net-ethernet-stmicro-stmmac-rockchip.patch diff --git a/package/base-files/files/etc/build_version b/package/base-files/files/etc/build_version index 718024efbb03a7..28d598f94861b5 100644 --- a/package/base-files/files/etc/build_version +++ b/package/base-files/files/etc/build_version @@ -1 +1 @@ -24.11.14-m +24.11.20 diff --git a/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r4x.dts b/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r4x.dts index 18d59442826600..1bbbce4ebdae0a 100644 --- a/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r4x.dts +++ b/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r4x.dts @@ -194,6 +194,20 @@ post-power-on-delay-ms = <100>; }; + gmac0_clkin: external-gmac0-clock { + compatible = "fixed-clock"; + clock-frequency = <125000000>; + clock-output-names = "gmac0_clkin"; + #clock-cells = <0>; + }; + + gmac1_clkin: external-gmac1-clock { + compatible = "fixed-clock"; + clock-frequency = <125000000>; + clock-output-names = "gmac1_clkin"; + #clock-cells = <0>; + }; + vccio_wifi: vccio-wifi { compatible = "regulator-fixed"; regulator-name = "vccio_wifi"; @@ -451,17 +465,19 @@ &gmac0 { assigned-clocks = <&cru SCLK_GMAC0_RX_TX>, <&cru SCLK_GMAC0>; - assigned-clock-parents = <&cru SCLK_GMAC0_RGMII_SPEED>; + assigned-clock-parents = <&cru SCLK_GMAC0_RGMII_SPEED>, <&gmac0_clkin>; assigned-clock-rates = <0>, <125000000>; - clock_in_out = "output"; + clock_in_out = "input"; phy-handle = <&rgmii_phy0>; - phy-mode = "rgmii-id"; + phy-mode = "rgmii"; pinctrl-names = "default"; pinctrl-0 = <&gmac0_miim &gmac0_tx_bus2 &gmac0_rx_bus2 &gmac0_rgmii_clk - &gmac0_rgmii_bus>; + &gmac0_rgmii_bus + &gmac0_clkinout + &gmac0_rst>; tx_delay = <0x3c>; rx_delay = <0x2f>; snps,reset-gpio = <&gpio2 RK_PD3 GPIO_ACTIVE_LOW>; @@ -473,17 +489,19 @@ &gmac1 { assigned-clocks = <&cru SCLK_GMAC1_RX_TX>, <&cru SCLK_GMAC1>; - assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>; + assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>, <&gmac1_clkin>; assigned-clock-rates = <0>, <125000000>; - clock_in_out = "output"; + clock_in_out = "input"; phy-handle = <&rgmii_phy1>; - phy-mode = "rgmii-id"; + phy-mode = "rgmii"; pinctrl-names = "default"; pinctrl-0 = <&gmac1m1_miim &gmac1m1_tx_bus2 &gmac1m1_rx_bus2 &gmac1m1_rgmii_clk - &gmac1m1_rgmii_bus>; + &gmac1m1_rgmii_bus + &gmac1m1_clkinout + &gmac1_rst>; tx_delay = <0x4f>; rx_delay = <0x26>; snps,reset-gpio = <&gpio2 RK_PD1 GPIO_ACTIVE_LOW>; @@ -921,11 +939,11 @@ }; gmac { - gmac0_int: gmac0-int { - rockchip,pins = <2 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; + gmac0_rst: gmac0-rst { + rockchip,pins = <2 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>; }; - gmac1_int: gmac1-int { - rockchip,pins = <2 RK_PD0 RK_FUNC_GPIO &pcfg_pull_none>; + gmac1_rst: gmac1-rst { + rockchip,pins = <2 RK_PD1 RK_FUNC_GPIO &pcfg_pull_up>; }; }; diff --git a/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r7.dts b/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r7.dts index f994bbac3ad33e..26a2cfc6b09da5 100644 --- a/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r7.dts +++ b/target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3568-tlink-r7.dts @@ -232,6 +232,20 @@ post-power-on-delay-ms = <100>; }; + gmac0_clkin: external-gmac0-clock { + compatible = "fixed-clock"; + clock-frequency = <125000000>; + clock-output-names = "gmac0_clkin"; + #clock-cells = <0>; + }; + + gmac1_clkin: external-gmac1-clock { + compatible = "fixed-clock"; + clock-frequency = <125000000>; + clock-output-names = "gmac1_clkin"; + #clock-cells = <0>; + }; + leds { compatible = "gpio-leds"; status = "okay"; @@ -539,17 +553,19 @@ &gmac0 { assigned-clocks = <&cru SCLK_GMAC0_RX_TX>, <&cru SCLK_GMAC0>; - assigned-clock-parents = <&cru SCLK_GMAC0_RGMII_SPEED>; + assigned-clock-parents = <&cru SCLK_GMAC0_RGMII_SPEED>, <&gmac0_clkin>; assigned-clock-rates = <0>, <125000000>; - clock_in_out = "output"; + clock_in_out = "input"; phy-handle = <&rgmii_phy0>; - phy-mode = "rgmii-id"; + phy-mode = "rgmii"; pinctrl-names = "default"; pinctrl-0 = <&gmac0_miim &gmac0_tx_bus2 &gmac0_rx_bus2 &gmac0_rgmii_clk - &gmac0_rgmii_bus>; + &gmac0_rgmii_bus + &gmac0_clkinout + &gmac0_rst>; tx_delay = <0x3c>; rx_delay = <0x2f>; snps,reset-gpio = <&gpio2 RK_PD3 GPIO_ACTIVE_LOW>; @@ -561,17 +577,19 @@ &gmac1 { assigned-clocks = <&cru SCLK_GMAC1_RX_TX>, <&cru SCLK_GMAC1>; - assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>; + assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>, <&gmac1_clkin>; assigned-clock-rates = <0>, <125000000>; - clock_in_out = "output"; + clock_in_out = "input"; phy-handle = <&rgmii_phy1>; - phy-mode = "rgmii-id"; + phy-mode = "rgmii"; pinctrl-names = "default"; pinctrl-0 = <&gmac1m1_miim &gmac1m1_tx_bus2 &gmac1m1_rx_bus2 &gmac1m1_rgmii_clk - &gmac1m1_rgmii_bus>; + &gmac1m1_rgmii_bus + &gmac1m1_clkinout + &gmac1_rst>; tx_delay = <0x4f>; rx_delay = <0x26>; snps,reset-gpio = <&gpio2 RK_PD1 GPIO_ACTIVE_LOW>; @@ -1031,11 +1049,11 @@ }; gmac { - gmac0_int: gmac0-int { - rockchip,pins = <2 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; + gmac0_rst: gmac0-rst { + rockchip,pins = <2 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>; }; - gmac1_int: gmac1-int { - rockchip,pins = <2 RK_PD0 RK_FUNC_GPIO &pcfg_pull_none>; + gmac1_rst: gmac1-rst { + rockchip,pins = <2 RK_PD1 RK_FUNC_GPIO &pcfg_pull_up>; }; }; diff --git a/target/linux/rockchip/image/armv8.mk b/target/linux/rockchip/image/armv8.mk index 00ea901658ff45..5652697580f08a 100644 --- a/target/linux/rockchip/image/armv8.mk +++ b/target/linux/rockchip/image/armv8.mk @@ -373,6 +373,7 @@ define Device/kooiot_tlink-r4x kmod-usb-net-cdc-mbim kmod-usb-net-rndis \ kmod-usb-xhci-pci upd72020x-firmware \ kmod-i2c-fusb30x \ + kmod-phy-realtek kmod-r8168 \ kmod-mmc kmod-brcmfmac \ kmod-can kmod-can-rockchip-canfd \ kmod-ata-ahci kmod-ata-ahci-dwc \ @@ -401,6 +402,7 @@ define Device/kooiot_tlink-r7 kmod-usb-net-cdc-mbim kmod-usb-net-rndis \ kmod-usb-xhci-pci upd72020x-firmware \ kmod-i2c-fusb30x \ + kmod-phy-realtek kmod-r8168 \ kmod-mmc kmod-brcmfmac \ kmod-can kmod-can-rockchip-canfd \ kmod-ata-ahci kmod-ata-ahci-dwc \ diff --git a/target/linux/rockchip/patches-6.6/z_9002-drivers-net-ethernet-stmicro-stmmac-rockchip.patch b/target/linux/rockchip/patches-6.6/z_9002-drivers-net-ethernet-stmicro-stmmac-rockchip.patch new file mode 100644 index 00000000000000..1934ca1eb30830 --- /dev/null +++ b/target/linux/rockchip/patches-6.6/z_9002-drivers-net-ethernet-stmicro-stmmac-rockchip.patch @@ -0,0 +1,1715 @@ +--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk-tool.c ++++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk-tool.c +@@ -0,0 +1,1607 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (c) 2020 Fuzhou Rockchip Electronics Co., Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "stmmac.h" ++#include "dwmac1000.h" ++#include "dwmac_dma.h" ++#include "dwmac-rk-tool.h" ++ ++enum { ++ LOOPBACK_TYPE_GMAC = 1, ++ LOOPBACK_TYPE_PHY ++}; ++ ++enum { ++ LOOPBACK_SPEED10 = 10, ++ LOOPBACK_SPEED100 = 100, ++ LOOPBACK_SPEED1000 = 1000 ++}; ++ ++struct dwmac_rk_packet_attrs { ++ unsigned char src[6]; ++ unsigned char dst[6]; ++ u32 ip_src; ++ u32 ip_dst; ++ int tcp; ++ int sport; ++ int dport; ++ int size; ++}; ++ ++struct dwmac_rk_hdr { ++ __be32 version; ++ __be64 magic; ++ u32 id; ++ int tx; ++ int rx; ++} __packed; ++ ++struct dwmac_rk_lb_priv { ++ /* desc && buffer */ ++ struct dma_desc *dma_tx; ++ dma_addr_t dma_tx_phy; ++ struct sk_buff *tx_skbuff; ++ dma_addr_t tx_skbuff_dma; ++ unsigned int tx_skbuff_dma_len; ++ ++ struct dma_desc *dma_rx ____cacheline_aligned_in_smp; ++ dma_addr_t dma_rx_phy; ++ struct sk_buff *rx_skbuff; ++ dma_addr_t rx_skbuff_dma; ++ u32 rx_tail_addr; ++ u32 tx_tail_addr; ++ ++ /* rx buffer size */ ++ unsigned int dma_buf_sz; ++ unsigned int buf_sz; ++ ++ int type; ++ int speed; ++ struct dwmac_rk_packet_attrs *packet; ++ ++ unsigned int actual_size; ++ int scan; ++ int sysfs; ++ u32 id; ++ int tx; ++ int rx; ++ int final_tx; ++ int final_rx; ++ int max_delay; ++}; ++ ++#define DMA_CONTROL_OSP BIT(4) ++#define DMA_CHAN_BASE_ADDR 0x00001100 ++#define DMA_CHAN_BASE_OFFSET 0x80 ++#define DMA_CHANX_BASE_ADDR(x) (DMA_CHAN_BASE_ADDR + \ ++ ((x) * DMA_CHAN_BASE_OFFSET)) ++#define DMA_CHAN_TX_CONTROL(x) (DMA_CHANX_BASE_ADDR(x) + 0x4) ++#define DMA_CHAN_STATUS(x) (DMA_CHANX_BASE_ADDR(x) + 0x60) ++#define DMA_CHAN_STATUS_ERI BIT(11) ++#define DMA_CHAN_STATUS_ETI BIT(10) ++ ++#define STMMAC_ALIGN(x) __ALIGN_KERNEL(x, SMP_CACHE_BYTES) ++#define MAX_DELAYLINE 0x7f ++#define RK3588_MAX_DELAYLINE 0xc7 ++#define SCAN_STEP 0x5 ++#define SCAN_VALID_RANGE 0xA ++ ++#define DWMAC_RK_TEST_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \ ++ sizeof(struct dwmac_rk_hdr)) ++#define DWMAC_RK_TEST_PKT_MAGIC 0xdeadcafecafedeadULL ++#define DWMAC_RK_TEST_PKT_MAX_SIZE 1500 ++ ++static __maybe_unused struct dwmac_rk_packet_attrs dwmac_rk_udp_attr = { ++ .dst = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ++ .tcp = 0, ++ .size = 1024, ++}; ++ ++static __maybe_unused struct dwmac_rk_packet_attrs dwmac_rk_tcp_attr = { ++ .dst = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ++ .tcp = 1, ++ .size = 1024, ++}; ++ ++static int dwmac_rk_enable_mac_loopback(struct stmmac_priv *priv, int speed, ++ int addr, bool phy) ++{ ++ u32 ctrl; ++ int phy_val; ++ ++ ctrl = readl(priv->ioaddr + GMAC_CONTROL); ++ ctrl &= ~priv->hw->link.speed_mask; ++ ctrl |= GMAC_CONTROL_LM; ++ ++ if (phy) ++ phy_val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ ++ switch (speed) { ++ case LOOPBACK_SPEED1000: ++ ctrl |= priv->hw->link.speed1000; ++ if (phy) { ++ phy_val &= ~BMCR_SPEED100; ++ phy_val |= BMCR_SPEED1000; ++ } ++ break; ++ case LOOPBACK_SPEED100: ++ ctrl |= priv->hw->link.speed100; ++ if (phy) { ++ phy_val &= ~BMCR_SPEED1000; ++ phy_val |= BMCR_SPEED100; ++ } ++ break; ++ case LOOPBACK_SPEED10: ++ ctrl |= priv->hw->link.speed10; ++ if (phy) { ++ phy_val &= ~BMCR_SPEED1000; ++ phy_val &= ~BMCR_SPEED100; ++ } ++ break; ++ default: ++ return -EPERM; ++ } ++ ++ ctrl |= priv->hw->link.duplex; ++ writel(ctrl, priv->ioaddr + GMAC_CONTROL); ++ ++ if (phy) { ++ phy_val &= ~BMCR_PDOWN; ++ phy_val &= ~BMCR_ANENABLE; ++ phy_val &= ~BMCR_PDOWN; ++ phy_val |= BMCR_FULLDPLX; ++ mdiobus_write(priv->mii, addr, MII_BMCR, phy_val); ++ phy_val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ } ++ ++ if (likely(priv->plat->fix_mac_speed)) ++ priv->plat->fix_mac_speed(priv->plat->bsp_priv, speed, priv->mode); ++ ++ return 0; ++} ++ ++static int dwmac_rk_disable_mac_loopback(struct stmmac_priv *priv, int addr) ++{ ++ u32 ctrl; ++ int phy_val; ++ ++ ctrl = readl(priv->ioaddr + GMAC_CONTROL); ++ ctrl &= ~GMAC_CONTROL_LM; ++ writel(ctrl, priv->ioaddr + GMAC_CONTROL); ++ ++ phy_val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ phy_val |= BMCR_ANENABLE; ++ ++ mdiobus_write(priv->mii, addr, MII_BMCR, phy_val); ++ phy_val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ ++ return 0; ++} ++ ++static int dwmac_rk_set_mac_loopback(struct stmmac_priv *priv, ++ int speed, bool enable, ++ int addr, bool phy) ++{ ++ if (enable) ++ return dwmac_rk_enable_mac_loopback(priv, speed, addr, phy); ++ else ++ return dwmac_rk_disable_mac_loopback(priv, addr); ++} ++ ++static int dwmac_rk_enable_phy_loopback(struct stmmac_priv *priv, int speed, ++ int addr, bool phy) ++{ ++ u32 ctrl; ++ int val; ++ ++ ctrl = readl(priv->ioaddr + MAC_CTRL_REG); ++ ctrl &= ~priv->hw->link.speed_mask; ++ ++ if (phy) ++ val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ ++ switch (speed) { ++ case LOOPBACK_SPEED1000: ++ ctrl |= priv->hw->link.speed1000; ++ if (phy) { ++ val &= ~BMCR_SPEED100; ++ val |= BMCR_SPEED1000; ++ } ++ break; ++ case LOOPBACK_SPEED100: ++ ctrl |= priv->hw->link.speed100; ++ if (phy) { ++ val &= ~BMCR_SPEED1000; ++ val |= BMCR_SPEED100; ++ } ++ break; ++ case LOOPBACK_SPEED10: ++ ctrl |= priv->hw->link.speed10; ++ if (phy) { ++ val &= ~BMCR_SPEED1000; ++ val &= ~BMCR_SPEED100; ++ } ++ break; ++ default: ++ return -EPERM; ++ } ++ ++ ctrl |= priv->hw->link.duplex; ++ writel(ctrl, priv->ioaddr + MAC_CTRL_REG); ++ ++ if (phy) { ++ val |= BMCR_FULLDPLX; ++ val &= ~BMCR_PDOWN; ++ val &= ~BMCR_ANENABLE; ++ val |= BMCR_LOOPBACK; ++ mdiobus_write(priv->mii, addr, MII_BMCR, val); ++ val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ } ++ ++ if (likely(priv->plat->fix_mac_speed)) ++ priv->plat->fix_mac_speed(priv->plat->bsp_priv, speed, priv->mode); ++ ++ return 0; ++} ++ ++static int dwmac_rk_disable_phy_loopback(struct stmmac_priv *priv, int addr) ++{ ++ int val; ++ ++ val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ val |= BMCR_ANENABLE; ++ val &= ~BMCR_LOOPBACK; ++ ++ mdiobus_write(priv->mii, addr, MII_BMCR, val); ++ val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ ++ return 0; ++} ++ ++static int dwmac_rk_set_phy_loopback(struct stmmac_priv *priv, ++ int speed, bool enable, ++ int addr, bool phy) ++{ ++ if (enable) ++ return dwmac_rk_enable_phy_loopback(priv, speed, ++ addr, phy); ++ else ++ return dwmac_rk_disable_phy_loopback(priv, addr); ++} ++ ++static int dwmac_rk_set_loopback(struct stmmac_priv *priv, ++ int type, int speed, bool enable, ++ int addr, bool phy) ++{ ++ int ret; ++ ++ switch (type) { ++ case LOOPBACK_TYPE_PHY: ++ ret = dwmac_rk_set_phy_loopback(priv, speed, enable, addr, phy); ++ break; ++ case LOOPBACK_TYPE_GMAC: ++ ret = dwmac_rk_set_mac_loopback(priv, speed, enable, addr, phy); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ ++ usleep_range(100000, 200000); ++ return ret; ++} ++ ++static inline void dwmac_rk_ether_addr_copy(u8 *dst, const u8 *src) ++{ ++ u16 *a = (u16 *)dst; ++ const u16 *b = (const u16 *)src; ++ ++ a[0] = b[0]; ++ a[1] = b[1]; ++ a[2] = b[2]; ++} ++ ++static void dwmac_rk_udp4_hwcsum(struct sk_buff *skb, __be32 src, __be32 dst) ++{ ++ struct udphdr *uh = udp_hdr(skb); ++ int offset = skb_transport_offset(skb); ++ int len = skb->len - offset; ++ ++ skb->csum_start = skb_transport_header(skb) - skb->head; ++ skb->csum_offset = offsetof(struct udphdr, check); ++ uh->check = ~csum_tcpudp_magic(src, dst, len, ++ IPPROTO_UDP, 0); ++} ++ ++static struct sk_buff *dwmac_rk_get_skb(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct sk_buff *skb = NULL; ++ struct udphdr *uhdr = NULL; ++ struct tcphdr *thdr = NULL; ++ struct dwmac_rk_hdr *shdr; ++ struct ethhdr *ehdr; ++ struct iphdr *ihdr; ++ struct dwmac_rk_packet_attrs *attr; ++ int iplen, size, nfrags; ++ ++ attr = lb_priv->packet; ++ size = attr->size + DWMAC_RK_TEST_PKT_SIZE; ++ if (attr->tcp) ++ size += sizeof(struct tcphdr); ++ else ++ size += sizeof(struct udphdr); ++ ++ if (size >= DWMAC_RK_TEST_PKT_MAX_SIZE) ++ return NULL; ++ ++ lb_priv->actual_size = size; ++ ++ skb = netdev_alloc_skb_ip_align(priv->dev, size); ++ if (!skb) ++ return NULL; ++ ++ skb_linearize(skb); ++ nfrags = skb_shinfo(skb)->nr_frags; ++ if (nfrags > 0) { ++ pr_err("%s: TX nfrags is not zero\n", __func__); ++ dev_kfree_skb(skb); ++ return NULL; ++ } ++ ++ ehdr = (struct ethhdr *)skb_push(skb, ETH_HLEN); ++ skb_reset_mac_header(skb); ++ ++ skb_set_network_header(skb, skb->len); ++ ihdr = (struct iphdr *)skb_put(skb, sizeof(*ihdr)); ++ ++ skb_set_transport_header(skb, skb->len); ++ if (attr->tcp) ++ thdr = (struct tcphdr *)skb_put(skb, sizeof(*thdr)); ++ else ++ uhdr = (struct udphdr *)skb_put(skb, sizeof(*uhdr)); ++ ++ eth_zero_addr(ehdr->h_source); ++ eth_zero_addr(ehdr->h_dest); ++ ++ dwmac_rk_ether_addr_copy(ehdr->h_source, priv->dev->dev_addr); ++ dwmac_rk_ether_addr_copy(ehdr->h_dest, attr->dst); ++ ++ ehdr->h_proto = htons(ETH_P_IP); ++ ++ if (attr->tcp) { ++ if (!thdr) { ++ dev_kfree_skb(skb); ++ return NULL; ++ } ++ ++ thdr->source = htons(attr->sport); ++ thdr->dest = htons(attr->dport); ++ thdr->doff = sizeof(struct tcphdr) / 4; ++ thdr->check = 0; ++ } else { ++ if (!uhdr) { ++ dev_kfree_skb(skb); ++ return NULL; ++ } ++ ++ uhdr->source = htons(attr->sport); ++ uhdr->dest = htons(attr->dport); ++ uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); ++ uhdr->check = 0; ++ } ++ ++ ihdr->ihl = 5; ++ ihdr->ttl = 32; ++ ihdr->version = 4; ++ if (attr->tcp) ++ ihdr->protocol = IPPROTO_TCP; ++ else ++ ihdr->protocol = IPPROTO_UDP; ++ ++ iplen = sizeof(*ihdr) + sizeof(*shdr) + attr->size; ++ if (attr->tcp) ++ iplen += sizeof(*thdr); ++ else ++ iplen += sizeof(*uhdr); ++ ++ ihdr->tot_len = htons(iplen); ++ ihdr->frag_off = 0; ++ ihdr->saddr = htonl(attr->ip_src); ++ ihdr->daddr = htonl(attr->ip_dst); ++ ihdr->tos = 0; ++ ihdr->id = 0; ++ ip_send_check(ihdr); ++ ++ shdr = (struct dwmac_rk_hdr *)skb_put(skb, sizeof(*shdr)); ++ shdr->version = 0; ++ shdr->magic = cpu_to_be64(DWMAC_RK_TEST_PKT_MAGIC); ++ shdr->id = lb_priv->id; ++ shdr->tx = lb_priv->tx; ++ shdr->rx = lb_priv->rx; ++ ++ if (attr->size) { ++ skb_put(skb, attr->size); ++ get_random_bytes((u8 *)shdr + sizeof(*shdr), attr->size); ++ } ++ ++ skb->csum = 0; ++ skb->ip_summed = CHECKSUM_PARTIAL; ++ if (attr->tcp) { ++ if (!thdr) { ++ dev_kfree_skb(skb); ++ return NULL; ++ } ++ ++ thdr->check = ~tcp_v4_check(skb->len, ihdr->saddr, ++ ihdr->daddr, 0); ++ skb->csum_start = skb_transport_header(skb) - skb->head; ++ skb->csum_offset = offsetof(struct tcphdr, check); ++ } else { ++ dwmac_rk_udp4_hwcsum(skb, ihdr->saddr, ihdr->daddr); ++ } ++ ++ skb->protocol = htons(ETH_P_IP); ++ skb->pkt_type = PACKET_HOST; ++ ++ return skb; ++} ++ ++static int dwmac_rk_loopback_validate(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv, ++ struct sk_buff *skb) ++{ ++ struct dwmac_rk_hdr *shdr; ++ struct ethhdr *ehdr; ++ struct udphdr *uhdr; ++ struct tcphdr *thdr; ++ struct iphdr *ihdr; ++ int ret = -EAGAIN; ++ ++ if (skb->len >= DWMAC_RK_TEST_PKT_MAX_SIZE) ++ goto out; ++ ++ if (lb_priv->actual_size != skb->len) ++ goto out; ++ ++ ehdr = (struct ethhdr *)(skb->data); ++ if (!ether_addr_equal(ehdr->h_dest, lb_priv->packet->dst)) ++ goto out; ++ ++ if (!ether_addr_equal(ehdr->h_source, priv->dev->dev_addr)) ++ goto out; ++ ++ ihdr = (struct iphdr *)(skb->data + ETH_HLEN); ++ ++ if (lb_priv->packet->tcp) { ++ if (ihdr->protocol != IPPROTO_TCP) ++ goto out; ++ ++ thdr = (struct tcphdr *)((u8 *)ihdr + 4 * ihdr->ihl); ++ if (thdr->dest != htons(lb_priv->packet->dport)) ++ goto out; ++ ++ shdr = (struct dwmac_rk_hdr *)((u8 *)thdr + sizeof(*thdr)); ++ } else { ++ if (ihdr->protocol != IPPROTO_UDP) ++ goto out; ++ ++ uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl); ++ if (uhdr->dest != htons(lb_priv->packet->dport)) ++ goto out; ++ ++ shdr = (struct dwmac_rk_hdr *)((u8 *)uhdr + sizeof(*uhdr)); ++ } ++ ++ if (shdr->magic != cpu_to_be64(DWMAC_RK_TEST_PKT_MAGIC)) ++ goto out; ++ ++ if (lb_priv->id != shdr->id) ++ goto out; ++ ++ if (lb_priv->tx != shdr->tx || lb_priv->rx != shdr->rx) ++ goto out; ++ ++ ret = 0; ++out: ++ return ret; ++} ++ ++static inline int dwmac_rk_rx_fill(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct dma_desc *p; ++ struct sk_buff *skb; ++ ++ p = lb_priv->dma_rx; ++ if (likely(!lb_priv->rx_skbuff)) { ++ skb = netdev_alloc_skb_ip_align(priv->dev, lb_priv->buf_sz); ++ if (unlikely(!skb)) ++ return -ENOMEM; ++ ++ if (skb_linearize(skb)) { ++ pr_err("%s: Rx skb linearize failed\n", __func__); ++ lb_priv->rx_skbuff = NULL; ++ dev_kfree_skb(skb); ++ return -EPERM; ++ } ++ ++ lb_priv->rx_skbuff = skb; ++ lb_priv->rx_skbuff_dma = ++ dma_map_single(priv->device, skb->data, lb_priv->dma_buf_sz, ++ DMA_FROM_DEVICE); ++ if (dma_mapping_error(priv->device, ++ lb_priv->rx_skbuff_dma)) { ++ pr_err("%s: Rx dma map failed\n", __func__); ++ lb_priv->rx_skbuff = NULL; ++ dev_kfree_skb(skb); ++ return -EFAULT; ++ } ++ ++ stmmac_set_desc_addr(priv, p, lb_priv->rx_skbuff_dma); ++ /* Fill DES3 in case of RING mode */ ++ if (lb_priv->dma_buf_sz == BUF_SIZE_16KiB) ++ p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB); ++ } ++ ++ wmb(); ++ stmmac_set_rx_owner(priv, p, priv->use_riwt); ++ wmb(); ++ ++ stmmac_set_rx_tail_ptr(priv, priv->ioaddr, lb_priv->rx_tail_addr, 0); ++ ++ return 0; ++} ++ ++static void dwmac_rk_rx_clean(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ if (likely(lb_priv->rx_skbuff_dma)) { ++ dma_unmap_single(priv->device, ++ lb_priv->rx_skbuff_dma, ++ lb_priv->dma_buf_sz, DMA_FROM_DEVICE); ++ lb_priv->rx_skbuff_dma = 0; ++ } ++ ++ if (likely(lb_priv->rx_skbuff)) { ++ dev_consume_skb_any(lb_priv->rx_skbuff); ++ lb_priv->rx_skbuff = NULL; ++ } ++} ++ ++static int dwmac_rk_rx_validate(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct dma_desc *p; ++ struct sk_buff *skb; ++ int coe = priv->hw->rx_csum; ++ unsigned int frame_len; ++ ++ p = lb_priv->dma_rx; ++ skb = lb_priv->rx_skbuff; ++ if (unlikely(!skb)) { ++ pr_err("%s: Inconsistent Rx descriptor chain\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ frame_len = priv->hw->desc->get_rx_frame_len(p, coe); ++ /* check if frame_len fits the preallocated memory */ ++ if (frame_len > lb_priv->dma_buf_sz) { ++ pr_err("%s: frame_len long: %d\n", __func__, frame_len); ++ return -ENOMEM; ++ } ++ ++ frame_len -= ETH_FCS_LEN; ++ prefetch(skb->data - NET_IP_ALIGN); ++ skb_put(skb, frame_len); ++ dma_unmap_single(priv->device, ++ lb_priv->rx_skbuff_dma, ++ lb_priv->dma_buf_sz, ++ DMA_FROM_DEVICE); ++ ++ return dwmac_rk_loopback_validate(priv, lb_priv, skb); ++} ++ ++static int dwmac_rk_get_desc_status(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct dma_desc *txp, *rxp; ++ int tx_status, rx_status; ++ ++ txp = lb_priv->dma_tx; ++ tx_status = priv->hw->desc->tx_status( ++ &priv->xstats, txp, ++ priv->ioaddr); ++ /* Check if the descriptor is owned by the DMA */ ++ if (unlikely(tx_status & tx_dma_own)) ++ return -EBUSY; ++ ++ rxp = lb_priv->dma_rx; ++ /* read the status of the incoming frame */ ++ rx_status = priv->hw->desc->rx_status( ++ &priv->xstats, rxp); ++ if (unlikely(rx_status & dma_own)) ++ return -EBUSY; ++ ++ usleep_range(100, 150); ++ ++ return 0; ++} ++ ++static void dwmac_rk_tx_clean(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct sk_buff *skb = lb_priv->tx_skbuff; ++ struct dma_desc *p; ++ ++ p = lb_priv->dma_tx; ++ ++ if (likely(lb_priv->tx_skbuff_dma)) { ++ dma_unmap_single(priv->device, ++ lb_priv->tx_skbuff_dma, ++ lb_priv->tx_skbuff_dma_len, ++ DMA_TO_DEVICE); ++ lb_priv->tx_skbuff_dma = 0; ++ } ++ ++ if (likely(skb)) { ++ dev_consume_skb_any(skb); ++ lb_priv->tx_skbuff = NULL; ++ } ++ ++ priv->hw->desc->release_tx_desc(p, priv->mode); ++} ++ ++static int dwmac_rk_xmit(struct sk_buff *skb, struct net_device *dev, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ unsigned int nopaged_len = skb_headlen(skb); ++ int csum_insertion = 0; ++ struct dma_desc *desc; ++ unsigned int des; ++ ++ priv->hw->mac->reset_eee_mode(priv->hw); ++ ++ csum_insertion = (skb->ip_summed == CHECKSUM_PARTIAL); ++ ++ desc = lb_priv->dma_tx; ++ lb_priv->tx_skbuff = skb; ++ ++ des = dma_map_single(priv->device, skb->data, ++ nopaged_len, DMA_TO_DEVICE); ++ if (dma_mapping_error(priv->device, des)) ++ goto dma_map_err; ++ lb_priv->tx_skbuff_dma = des; ++ ++ stmmac_set_desc_addr(priv, desc, des); ++ lb_priv->tx_skbuff_dma_len = nopaged_len; ++ ++ /* Prepare the first descriptor setting the OWN bit too */ ++ stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len, ++ csum_insertion, priv->mode, 1, 1, ++ skb->len); ++ stmmac_enable_dma_transmission(priv, priv->ioaddr); ++ ++ lb_priv->tx_tail_addr = lb_priv->dma_tx_phy + sizeof(*desc); ++ stmmac_set_tx_tail_ptr(priv, priv->ioaddr, lb_priv->tx_tail_addr, 0); ++ ++ return 0; ++ ++dma_map_err: ++ pr_err("%s: Tx dma map failed\n", __func__); ++ dev_kfree_skb(skb); ++ return -EFAULT; ++} ++ ++static int __dwmac_rk_loopback_run(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ u32 rx_channels_count = min_t(u32, priv->plat->rx_queues_to_use, 1); ++ u32 tx_channels_count = min_t(u32, priv->plat->tx_queues_to_use, 1); ++ struct sk_buff *tx_skb; ++ u32 chan = 0; ++ int ret = -EIO, delay; ++ u32 status; ++ bool finish = false; ++ ++ if (lb_priv->speed == LOOPBACK_SPEED1000) ++ delay = 10; ++ else if (lb_priv->speed == LOOPBACK_SPEED100) ++ delay = 20; ++ else if (lb_priv->speed == LOOPBACK_SPEED10) ++ delay = 50; ++ else ++ return -EPERM; ++ ++ if (dwmac_rk_rx_fill(priv, lb_priv)) ++ return -ENOMEM; ++ ++ /* Enable the MAC Rx/Tx */ ++ stmmac_mac_set(priv, priv->ioaddr, true); ++ ++ for (chan = 0; chan < rx_channels_count; chan++) ++ stmmac_start_rx(priv, priv->ioaddr, chan); ++ for (chan = 0; chan < tx_channels_count; chan++) ++ stmmac_start_tx(priv, priv->ioaddr, chan); ++ ++ tx_skb = dwmac_rk_get_skb(priv, lb_priv); ++ if (!tx_skb) { ++ ret = -ENOMEM; ++ goto stop; ++ } ++ ++ if (dwmac_rk_xmit(tx_skb, priv->dev, lb_priv)) { ++ ret = -EFAULT; ++ goto stop; ++ } ++ ++ do { ++ usleep_range(100, 150); ++ delay--; ++ if (priv->plat->has_gmac4) { ++ status = readl(priv->ioaddr + DMA_CHAN_STATUS(0)); ++ finish = (status & DMA_CHAN_STATUS_ERI) && (status & DMA_CHAN_STATUS_ETI); ++ } else { ++ status = readl(priv->ioaddr + DMA_STATUS); ++ finish = (status & DMA_STATUS_ERI) && (status & DMA_STATUS_ETI); ++ } ++ ++ if (finish) { ++ if (!dwmac_rk_get_desc_status(priv, lb_priv)) { ++ ret = dwmac_rk_rx_validate(priv, lb_priv); ++ break; ++ } ++ } ++ } while (delay <= 0); ++ writel((status & 0x1ffff), priv->ioaddr + DMA_STATUS); ++ ++stop: ++ for (chan = 0; chan < rx_channels_count; chan++) ++ stmmac_stop_rx(priv, priv->ioaddr, chan); ++ for (chan = 0; chan < tx_channels_count; chan++) ++ stmmac_stop_tx(priv, priv->ioaddr, chan); ++ ++ stmmac_mac_set(priv, priv->ioaddr, false); ++ /* wait for state machine is disabled */ ++ usleep_range(100, 150); ++ ++ dwmac_rk_tx_clean(priv, lb_priv); ++ dwmac_rk_rx_clean(priv, lb_priv); ++ ++ return ret; ++} ++ ++static int dwmac_rk_loopback_with_identify(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv, ++ int tx, int rx) ++{ ++ lb_priv->id++; ++ lb_priv->tx = tx; ++ lb_priv->rx = rx; ++ ++ lb_priv->packet = &dwmac_rk_tcp_attr; ++ dwmac_rk_set_rgmii_delayline(priv, tx, rx); ++ ++ return __dwmac_rk_loopback_run(priv, lb_priv); ++} ++ ++static inline bool dwmac_rk_delayline_is_txvalid(struct dwmac_rk_lb_priv *lb_priv, ++ int tx) ++{ ++ if (tx > 0 && tx < lb_priv->max_delay) ++ return true; ++ else ++ return false; ++} ++ ++static inline bool dwmac_rk_delayline_is_valid(struct dwmac_rk_lb_priv *lb_priv, ++ int tx, int rx) ++{ ++ if ((tx > 0 && tx < lb_priv->max_delay) && ++ (rx > 0 && rx < lb_priv->max_delay)) ++ return true; ++ else ++ return false; ++} ++ ++static int dwmac_rk_delayline_scan_cross(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ int tx_left, tx_right, rx_up, rx_down; ++ int i, j, tx_index, rx_index; ++ int tx_mid = 0, rx_mid = 0; ++ ++ /* initiation */ ++ tx_index = SCAN_STEP; ++ rx_index = SCAN_STEP; ++ ++re_scan: ++ /* start from rx based on the experience */ ++ for (i = rx_index; i <= (lb_priv->max_delay - SCAN_STEP); i += SCAN_STEP) { ++ tx_left = 0; ++ tx_right = 0; ++ tx_mid = 0; ++ ++ for (j = tx_index; j <= (lb_priv->max_delay - SCAN_STEP); ++ j += SCAN_STEP) { ++ if (!dwmac_rk_loopback_with_identify(priv, ++ lb_priv, j, i)) { ++ if (!tx_left) ++ tx_left = j; ++ tx_right = j; ++ } ++ } ++ ++ /* look for tx_mid */ ++ if ((tx_right - tx_left) > SCAN_VALID_RANGE) { ++ tx_mid = (tx_right + tx_left) / 2; ++ break; ++ } ++ } ++ ++ /* Worst case: reach the end */ ++ if (i >= (lb_priv->max_delay - SCAN_STEP)) ++ goto end; ++ ++ rx_up = 0; ++ rx_down = 0; ++ ++ /* look for rx_mid base on the tx_mid */ ++ for (i = SCAN_STEP; i <= (lb_priv->max_delay - SCAN_STEP); ++ i += SCAN_STEP) { ++ if (!dwmac_rk_loopback_with_identify(priv, lb_priv, ++ tx_mid, i)) { ++ if (!rx_up) ++ rx_up = i; ++ rx_down = i; ++ } ++ } ++ ++ if ((rx_down - rx_up) > SCAN_VALID_RANGE) { ++ /* Now get the rx_mid */ ++ rx_mid = (rx_up + rx_down) / 2; ++ } else { ++ rx_index += SCAN_STEP; ++ rx_mid = 0; ++ goto re_scan; ++ } ++ ++ if (dwmac_rk_delayline_is_valid(lb_priv, tx_mid, rx_mid)) { ++ lb_priv->final_tx = tx_mid; ++ lb_priv->final_rx = rx_mid; ++ ++ pr_info("Find available tx_delay = 0x%02x, rx_delay = 0x%02x\n", ++ lb_priv->final_tx, lb_priv->final_rx); ++ ++ return 0; ++ } ++end: ++ pr_err("Can't find available delayline\n"); ++ return -ENXIO; ++} ++ ++static int dwmac_rk_delayline_scan(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ int phy_iface = dwmac_rk_get_phy_interface(priv); ++ int tx, rx, tx_sum, rx_sum, count; ++ int tx_mid, rx_mid; ++ int ret = -ENXIO; ++ ++ tx_sum = 0; ++ rx_sum = 0; ++ count = 0; ++ ++ for (rx = 0x0; rx <= lb_priv->max_delay; rx++) { ++ if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID) ++ rx = -1; ++ printk(KERN_CONT "RX(%03d):", rx); ++ for (tx = 0x0; tx <= lb_priv->max_delay; tx++) { ++ if (!dwmac_rk_loopback_with_identify(priv, ++ lb_priv, tx, rx)) { ++ tx_sum += tx; ++ rx_sum += rx; ++ count++; ++ printk(KERN_CONT "O"); ++ } else { ++ printk(KERN_CONT " "); ++ } ++ } ++ printk(KERN_CONT "\n"); ++ ++ if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID) ++ break; ++ } ++ ++ if (tx_sum && rx_sum && count) { ++ tx_mid = tx_sum / count; ++ rx_mid = rx_sum / count; ++ ++ if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID) { ++ if (dwmac_rk_delayline_is_txvalid(lb_priv, tx_mid)) { ++ lb_priv->final_tx = tx_mid; ++ lb_priv->final_rx = -1; ++ ret = 0; ++ } ++ } else { ++ if (dwmac_rk_delayline_is_valid(lb_priv, tx_mid, rx_mid)) { ++ lb_priv->final_tx = tx_mid; ++ lb_priv->final_rx = rx_mid; ++ ret = 0; ++ } ++ } ++ } ++ ++ if (ret) { ++ pr_err("\nCan't find suitable delayline\n"); ++ } else { ++ if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID) ++ pr_info("Find available tx_delay = 0x%02x, rx_delay = disable\n", ++ lb_priv->final_tx); ++ else ++ pr_info("\nFind suitable tx_delay = 0x%02x, rx_delay = 0x%02x\n", ++ lb_priv->final_tx, lb_priv->final_rx); ++ } ++ ++ return ret; ++} ++ ++static int dwmac_rk_loopback_delayline_scan(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ if (lb_priv->sysfs) ++ return dwmac_rk_delayline_scan(priv, lb_priv); ++ else ++ return dwmac_rk_delayline_scan_cross(priv, lb_priv); ++} ++ ++static void dwmac_rk_dma_free_rx_skbufs(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ if (lb_priv->rx_skbuff) { ++ dma_unmap_single(priv->device, lb_priv->rx_skbuff_dma, ++ lb_priv->dma_buf_sz, DMA_FROM_DEVICE); ++ dev_kfree_skb_any(lb_priv->rx_skbuff); ++ } ++ lb_priv->rx_skbuff = NULL; ++} ++ ++static void dwmac_rk_dma_free_tx_skbufs(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ if (lb_priv->tx_skbuff_dma) { ++ dma_unmap_single(priv->device, ++ lb_priv->tx_skbuff_dma, ++ lb_priv->tx_skbuff_dma_len, ++ DMA_TO_DEVICE); ++ } ++ ++ if (lb_priv->tx_skbuff) { ++ dev_kfree_skb_any(lb_priv->tx_skbuff); ++ lb_priv->tx_skbuff = NULL; ++ lb_priv->tx_skbuff_dma = 0; ++ } ++} ++ ++static int dwmac_rk_init_dma_desc_rings(struct net_device *dev, gfp_t flags, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ struct dma_desc *p; ++ ++ p = lb_priv->dma_tx; ++ p->des2 = 0; ++ lb_priv->tx_skbuff_dma = 0; ++ lb_priv->tx_skbuff_dma_len = 0; ++ lb_priv->tx_skbuff = NULL; ++ ++ lb_priv->rx_skbuff = NULL; ++ stmmac_init_rx_desc(priv, lb_priv->dma_rx, ++ priv->use_riwt, priv->mode, ++ true, lb_priv->dma_buf_sz); ++ ++ stmmac_init_tx_desc(priv, lb_priv->dma_tx, ++ priv->mode, ++ true); ++ ++ return 0; ++} ++ ++static int dwmac_rk_alloc_dma_desc_resources(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ int ret = -ENOMEM; ++ ++ /* desc dma map */ ++ lb_priv->dma_rx = dma_alloc_coherent(priv->device, ++ sizeof(struct dma_desc), ++ &lb_priv->dma_rx_phy, ++ GFP_KERNEL); ++ if (!lb_priv->dma_rx) ++ return ret; ++ ++ lb_priv->dma_tx = dma_alloc_coherent(priv->device, ++ sizeof(struct dma_desc), ++ &lb_priv->dma_tx_phy, ++ GFP_KERNEL); ++ if (!lb_priv->dma_tx) { ++ dma_free_coherent(priv->device, ++ sizeof(struct dma_desc), ++ lb_priv->dma_rx, lb_priv->dma_rx_phy); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void dwmac_rk_free_dma_desc_resources(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ /* Release the DMA TX/RX socket buffers */ ++ dwmac_rk_dma_free_rx_skbufs(priv, lb_priv); ++ dwmac_rk_dma_free_tx_skbufs(priv, lb_priv); ++ ++ dma_free_coherent(priv->device, sizeof(struct dma_desc), ++ lb_priv->dma_tx, lb_priv->dma_tx_phy); ++ dma_free_coherent(priv->device, sizeof(struct dma_desc), ++ lb_priv->dma_rx, lb_priv->dma_rx_phy); ++} ++ ++static int dwmac_rk_init_dma_engine(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ u32 rx_channels_count = min_t(u32, priv->plat->rx_queues_to_use, 1); ++ u32 tx_channels_count = min_t(u32, priv->plat->tx_queues_to_use, 1); ++ u32 dma_csr_ch = max(rx_channels_count, tx_channels_count); ++ u32 chan = 0; ++ int ret = 0; ++ ++ ret = stmmac_reset(priv, priv->ioaddr); ++ if (ret) { ++ dev_err(priv->device, "Failed to reset the dma\n"); ++ return ret; ++ } ++ ++ /* DMA Configuration */ ++ stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg, 0); ++ ++ if (priv->plat->axi) ++ stmmac_axi(priv, priv->ioaddr, priv->plat->axi); ++ ++ for (chan = 0; chan < dma_csr_ch; chan++) ++ stmmac_init_chan(priv, priv->ioaddr, priv->plat->dma_cfg, 0); ++ ++ /* DMA RX Channel Configuration */ ++ for (chan = 0; chan < rx_channels_count; chan++) { ++ stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, ++ lb_priv->dma_rx_phy, 0); ++ ++ lb_priv->rx_tail_addr = lb_priv->dma_rx_phy + ++ (1 * sizeof(struct dma_desc)); ++ stmmac_set_rx_tail_ptr(priv, priv->ioaddr, ++ lb_priv->rx_tail_addr, 0); ++ } ++ ++ /* DMA TX Channel Configuration */ ++ for (chan = 0; chan < tx_channels_count; chan++) { ++ stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, ++ lb_priv->dma_tx_phy, chan); ++ ++ lb_priv->tx_tail_addr = lb_priv->dma_tx_phy; ++ stmmac_set_tx_tail_ptr(priv, priv->ioaddr, ++ lb_priv->tx_tail_addr, chan); ++ } ++ ++ return ret; ++} ++ ++static void dwmac_rk_dma_operation_mode(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ u32 rx_channels_count = min_t(u32, priv->plat->rx_queues_to_use, 1); ++ u32 tx_channels_count = min_t(u32, priv->plat->tx_queues_to_use, 1); ++ int rxfifosz = priv->plat->rx_fifo_size; ++ int txfifosz = priv->plat->tx_fifo_size; ++ u32 txmode = SF_DMA_MODE; ++ u32 rxmode = SF_DMA_MODE; ++ u32 chan = 0; ++ u8 qmode = 0; ++ ++ if (rxfifosz == 0) ++ rxfifosz = priv->dma_cap.rx_fifo_size; ++ if (txfifosz == 0) ++ txfifosz = priv->dma_cap.tx_fifo_size; ++ ++ /* Adjust for real per queue fifo size */ ++ rxfifosz /= rx_channels_count; ++ txfifosz /= tx_channels_count; ++ ++ /* configure all channels */ ++ for (chan = 0; chan < rx_channels_count; chan++) { ++ qmode = priv->plat->rx_queues_cfg[chan].mode_to_use; ++ ++ stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, ++ rxfifosz, qmode); ++ stmmac_set_dma_bfsize(priv, priv->ioaddr, lb_priv->dma_buf_sz, ++ chan); ++ } ++ ++ for (chan = 0; chan < tx_channels_count; chan++) { ++ qmode = priv->plat->tx_queues_cfg[chan].mode_to_use; ++ ++ stmmac_dma_tx_mode(priv, priv->ioaddr, txmode, chan, ++ txfifosz, qmode); ++ } ++} ++ ++static void dwmac_rk_rx_queue_dma_chan_map(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = min_t(u32, priv->plat->rx_queues_to_use, 1); ++ u32 queue; ++ u32 chan; ++ ++ for (queue = 0; queue < rx_queues_count; queue++) { ++ chan = priv->plat->rx_queues_cfg[queue].chan; ++ stmmac_map_mtl_to_dma(priv, priv->hw, queue, chan); ++ } ++} ++ ++static void dwmac_rk_mac_enable_rx_queues(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = min_t(u32, priv->plat->rx_queues_to_use, 1); ++ int queue; ++ u8 mode; ++ ++ for (queue = 0; queue < rx_queues_count; queue++) { ++ mode = priv->plat->rx_queues_cfg[queue].mode_to_use; ++ stmmac_rx_queue_enable(priv, priv->hw, mode, queue); ++ } ++} ++ ++static void dwmac_rk_mtl_configuration(struct stmmac_priv *priv) ++{ ++ /* Map RX MTL to DMA channels */ ++ dwmac_rk_rx_queue_dma_chan_map(priv); ++ ++ /* Enable MAC RX Queues */ ++ dwmac_rk_mac_enable_rx_queues(priv); ++} ++ ++static void dwmac_rk_mmc_setup(struct stmmac_priv *priv) ++{ ++ unsigned int mode = MMC_CNTRL_RESET_ON_READ | MMC_CNTRL_COUNTER_RESET | ++ MMC_CNTRL_PRESET | MMC_CNTRL_FULL_HALF_PRESET; ++ ++ stmmac_mmc_intr_all_mask(priv, priv->mmcaddr); ++ ++ if (priv->dma_cap.rmon) { ++ stmmac_mmc_ctrl(priv, priv->mmcaddr, mode); ++ memset(&priv->mmc, 0, sizeof(struct stmmac_counters)); ++ } else { ++ netdev_info(priv->dev, "No MAC Management Counters available\n"); ++ } ++} ++ ++static int dwmac_rk_init(struct net_device *dev, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ int ret; ++ u32 mode; ++ ++ lb_priv->dma_buf_sz = 1536; /* mtu 1500 size */ ++ ++ if (priv->plat->has_gmac4) ++ lb_priv->buf_sz = priv->dma_cap.rx_fifo_size; /* rx fifo size */ ++ else ++ lb_priv->buf_sz = 4096; /* rx fifo size */ ++ ++ ret = dwmac_rk_alloc_dma_desc_resources(priv, lb_priv); ++ if (ret < 0) { ++ pr_err("%s: DMA descriptors allocation failed\n", __func__); ++ return ret; ++ } ++ ++ ret = dwmac_rk_init_dma_desc_rings(dev, GFP_KERNEL, lb_priv); ++ if (ret < 0) { ++ pr_err("%s: DMA descriptors initialization failed\n", __func__); ++ goto init_error; ++ } ++ ++ /* DMA initialization and SW reset */ ++ ret = dwmac_rk_init_dma_engine(priv, lb_priv); ++ if (ret < 0) { ++ pr_err("%s: DMA engine initialization failed\n", __func__); ++ goto init_error; ++ } ++ ++ /* Copy the MAC addr into the HW */ ++ priv->hw->mac->set_umac_addr(priv->hw, dev->dev_addr, 0); ++ ++ /* Initialize the MAC Core */ ++ stmmac_core_init(priv, priv->hw, dev); ++ ++ dwmac_rk_mtl_configuration(priv); ++ ++ dwmac_rk_mmc_setup(priv); ++ ++ ret = priv->hw->mac->rx_ipc(priv->hw); ++ if (!ret) { ++ pr_warn(" RX IPC Checksum Offload disabled\n"); ++ priv->plat->rx_coe = STMMAC_RX_COE_NONE; ++ priv->hw->rx_csum = 0; ++ } ++ ++ /* Set the HW DMA mode and the COE */ ++ dwmac_rk_dma_operation_mode(priv, lb_priv); ++ ++ if (priv->plat->has_gmac4) { ++ mode = readl(priv->ioaddr + DMA_CHAN_TX_CONTROL(0)); ++ /* Disable OSP to get best performance */ ++ mode &= ~DMA_CONTROL_OSP; ++ writel(mode, priv->ioaddr + DMA_CHAN_TX_CONTROL(0)); ++ } else { ++ /* Disable OSF */ ++ mode = readl(priv->ioaddr + DMA_CONTROL); ++ writel((mode & ~DMA_CONTROL_OSF), priv->ioaddr + DMA_CONTROL); ++ } ++ ++ stmmac_enable_dma_irq(priv, priv->ioaddr, 0, 1, 1); ++ ++ if (priv->hw->pcs) ++ stmmac_pcs_ctrl_ane(priv, priv->hw, 1, priv->hw->ps, 0); ++ ++ return 0; ++init_error: ++ dwmac_rk_free_dma_desc_resources(priv, lb_priv); ++ ++ return ret; ++} ++ ++static void dwmac_rk_release(struct net_device *dev, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ ++ stmmac_disable_dma_irq(priv, priv->ioaddr, 0, 0, 0); ++ ++ /* Release and free the Rx/Tx resources */ ++ dwmac_rk_free_dma_desc_resources(priv, lb_priv); ++} ++ ++static int dwmac_rk_get_max_delayline(struct stmmac_priv *priv) ++{ ++ if (of_device_is_compatible(priv->device->of_node, ++ "rockchip,rk3588-gmac")) ++ return RK3588_MAX_DELAYLINE; ++ else ++ return MAX_DELAYLINE; ++} ++ ++static int dwmac_rk_phy_poll_reset(struct stmmac_priv *priv, int addr) ++{ ++ /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ ++ unsigned int val, retries = 12; ++ int ret; ++ ++ val = mdiobus_read(priv->mii, addr, MII_BMCR); ++ mdiobus_write(priv->mii, addr, MII_BMCR, val | BMCR_RESET); ++ ++ do { ++ msleep(50); ++ ret = mdiobus_read(priv->mii, addr, MII_BMCR); ++ if (ret < 0) ++ return ret; ++ } while (ret & BMCR_RESET && --retries); ++ if (ret & BMCR_RESET) ++ return -ETIMEDOUT; ++ ++ msleep(1); ++ return 0; ++} ++ ++static int dwmac_rk_loopback_run(struct stmmac_priv *priv, ++ struct dwmac_rk_lb_priv *lb_priv) ++{ ++ struct net_device *ndev = priv->dev; ++ int phy_iface = dwmac_rk_get_phy_interface(priv); ++ int ndev_up, phy_addr; ++ int ret = -EINVAL; ++ ++ if (!ndev || !priv->mii) ++ return -EINVAL; ++ ++ phy_addr = priv->dev->phydev->mdio.addr; ++ lb_priv->max_delay = dwmac_rk_get_max_delayline(priv); ++ ++ rtnl_lock(); ++ /* check the netdevice up or not */ ++ ndev_up = ndev->flags & IFF_UP; ++ ++ if (ndev_up) { ++ if (!netif_running(ndev) || !ndev->phydev) { ++ rtnl_unlock(); ++ return -EINVAL; ++ } ++ ++ /* check if the negotiation status */ ++ if (ndev->phydev->state != PHY_NOLINK && ++ ndev->phydev->state != PHY_RUNNING) { ++ rtnl_unlock(); ++ pr_warn("Try again later, after negotiation done\n"); ++ return -EAGAIN; ++ } ++ ++ ndev->netdev_ops->ndo_stop(ndev); ++ ++ if (priv->plat->stmmac_rst) ++ reset_control_assert(priv->plat->stmmac_rst); ++ dwmac_rk_phy_poll_reset(priv, phy_addr); ++ if (priv->plat->stmmac_rst) ++ reset_control_deassert(priv->plat->stmmac_rst); ++ } ++ /* wait for phy and controller ready */ ++ usleep_range(100000, 200000); ++ ++ dwmac_rk_set_loopback(priv, lb_priv->type, lb_priv->speed, ++ true, phy_addr, true); ++ ++ ret = dwmac_rk_init(ndev, lb_priv); ++ if (ret) ++ goto exit_init; ++ ++ dwmac_rk_set_loopback(priv, lb_priv->type, lb_priv->speed, ++ true, phy_addr, false); ++ ++ if (lb_priv->scan) { ++ /* scan only support for rgmii mode */ ++ if (phy_iface != PHY_INTERFACE_MODE_RGMII && ++ phy_iface != PHY_INTERFACE_MODE_RGMII_ID && ++ phy_iface != PHY_INTERFACE_MODE_RGMII_RXID && ++ phy_iface != PHY_INTERFACE_MODE_RGMII_TXID) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ret = dwmac_rk_loopback_delayline_scan(priv, lb_priv); ++ } else { ++ lb_priv->id++; ++ lb_priv->tx = 0; ++ lb_priv->rx = 0; ++ ++ lb_priv->packet = &dwmac_rk_tcp_attr; ++ ret = __dwmac_rk_loopback_run(priv, lb_priv); ++ } ++ ++out: ++ dwmac_rk_release(ndev, lb_priv); ++ dwmac_rk_set_loopback(priv, lb_priv->type, lb_priv->speed, ++ false, phy_addr, false); ++exit_init: ++ if (ndev_up) ++ ndev->netdev_ops->ndo_open(ndev); ++ ++ rtnl_unlock(); ++ ++ return ret; ++} ++ ++static ssize_t rgmii_delayline_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ int tx, rx; ++ ++ dwmac_rk_get_rgmii_delayline(priv, &tx, &rx); ++ ++ return sprintf(buf, "tx delayline: 0x%x, rx delayline: 0x%x\n", ++ tx, rx); ++} ++ ++static ssize_t rgmii_delayline_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ int tx = 0, rx = 0; ++ char tmp[32]; ++ size_t buf_size = min(count, (sizeof(tmp) - 1)); ++ char *data; ++ ++ memset(tmp, 0, sizeof(tmp)); ++ strncpy(tmp, buf, buf_size); ++ ++ data = tmp; ++ data = strstr(data, " "); ++ if (!data) ++ goto out; ++ *data = 0; ++ data++; ++ ++ if (kstrtoint(tmp, 0, &tx) || tx > dwmac_rk_get_max_delayline(priv)) ++ goto out; ++ ++ if (kstrtoint(data, 0, &rx) || rx > dwmac_rk_get_max_delayline(priv)) ++ goto out; ++ ++ dwmac_rk_set_rgmii_delayline(priv, tx, rx); ++ pr_info("Set rgmii delayline tx: 0x%x, rx: 0x%x\n", tx, rx); ++ ++ return count; ++out: ++ pr_err("wrong delayline value input, range is <0x0, 0x7f>\n"); ++ pr_err("usage: \n"); ++ ++ return count; ++} ++static DEVICE_ATTR_RW(rgmii_delayline); ++ ++static ssize_t mac_lb_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct dwmac_rk_lb_priv *lb_priv; ++ int ret, speed; ++ ++ lb_priv = kzalloc(sizeof(*lb_priv), GFP_KERNEL); ++ if (!lb_priv) ++ return -ENOMEM; ++ ++ ret = kstrtoint(buf, 0, &speed); ++ if (ret) { ++ kfree(lb_priv); ++ return count; ++ } ++ pr_info("MAC loopback speed set to %d\n", speed); ++ ++ lb_priv->sysfs = 1; ++ lb_priv->type = LOOPBACK_TYPE_GMAC; ++ lb_priv->speed = speed; ++ lb_priv->scan = 0; ++ ++ ret = dwmac_rk_loopback_run(priv, lb_priv); ++ kfree(lb_priv); ++ ++ if (!ret) ++ pr_info("MAC loopback: PASS\n"); ++ else ++ pr_info("MAC loopback: FAIL\n"); ++ ++ return count; ++} ++static DEVICE_ATTR_WO(mac_lb); ++ ++static ssize_t phy_lb_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct dwmac_rk_lb_priv *lb_priv; ++ int ret, speed; ++ ++ lb_priv = kzalloc(sizeof(*lb_priv), GFP_KERNEL); ++ if (!lb_priv) ++ return -ENOMEM; ++ ++ ret = kstrtoint(buf, 0, &speed); ++ if (ret) { ++ kfree(lb_priv); ++ return count; ++ } ++ pr_info("PHY loopback speed set to %d\n", speed); ++ ++ lb_priv->sysfs = 1; ++ lb_priv->type = LOOPBACK_TYPE_PHY; ++ lb_priv->speed = speed; ++ lb_priv->scan = 0; ++ ++ ret = dwmac_rk_loopback_run(priv, lb_priv); ++ if (!ret) ++ pr_info("PHY loopback: PASS\n"); ++ else ++ pr_info("PHY loopback: FAIL\n"); ++ ++ kfree(lb_priv); ++ return count; ++} ++static DEVICE_ATTR_WO(phy_lb); ++ ++static ssize_t phy_lb_scan_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct dwmac_rk_lb_priv *lb_priv; ++ int ret, speed; ++ ++ lb_priv = kzalloc(sizeof(*lb_priv), GFP_KERNEL); ++ if (!lb_priv) ++ return -ENOMEM; ++ ++ ret = kstrtoint(buf, 0, &speed); ++ if (ret) { ++ kfree(lb_priv); ++ return count; ++ } ++ pr_info("Delayline scan speed set to %d\n", speed); ++ ++ lb_priv->sysfs = 1; ++ lb_priv->type = LOOPBACK_TYPE_PHY; ++ lb_priv->speed = speed; ++ lb_priv->scan = 1; ++ ++ dwmac_rk_loopback_run(priv, lb_priv); ++ ++ kfree(lb_priv); ++ return count; ++} ++static DEVICE_ATTR_WO(phy_lb_scan); ++ ++int dwmac_rk_create_loopback_sysfs(struct device *device) ++{ ++ int ret; ++ ++ ret = device_create_file(device, &dev_attr_rgmii_delayline); ++ if (ret) ++ return ret; ++ ++ ret = device_create_file(device, &dev_attr_mac_lb); ++ if (ret) ++ goto remove_rgmii_delayline; ++ ++ ret = device_create_file(device, &dev_attr_phy_lb); ++ if (ret) ++ goto remove_mac_lb; ++ ++ ret = device_create_file(device, &dev_attr_phy_lb_scan); ++ if (ret) ++ goto remove_phy_lb; ++ ++ return 0; ++ ++remove_rgmii_delayline: ++ device_remove_file(device, &dev_attr_rgmii_delayline); ++ ++remove_mac_lb: ++ device_remove_file(device, &dev_attr_mac_lb); ++ ++remove_phy_lb: ++ device_remove_file(device, &dev_attr_phy_lb); ++ ++ return ret; ++} ++ ++int dwmac_rk_remove_loopback_sysfs(struct device *device) ++{ ++ device_remove_file(device, &dev_attr_rgmii_delayline); ++ device_remove_file(device, &dev_attr_mac_lb); ++ device_remove_file(device, &dev_attr_phy_lb); ++ device_remove_file(device, &dev_attr_phy_lb_scan); ++ ++ return 0; ++} +--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk-tool.h ++++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk-tool.h +@@ -0,0 +1,20 @@ ++/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ ++/* ++ * Copyright (c) 2020 Fuzhou Rockchip Electronics Co., Ltd ++ */ ++ ++#ifndef __DWMAC_RK_TOOL_H__ ++#define __DWMAC_RK_TOOL_H__ ++ ++#include ++#include "stmmac.h" ++ ++void dwmac_rk_set_rgmii_delayline(struct stmmac_priv *priv, int tx_delay, int rx_delay); ++void dwmac_rk_get_rgmii_delayline(struct stmmac_priv *priv, int *tx_delay, int *rx_delay); ++int dwmac_rk_get_phy_interface(struct stmmac_priv *priv); ++ ++int dwmac_rk_create_loopback_sysfs(struct device *dev); ++int dwmac_rk_remove_loopback_sysfs(struct device *device); ++ ++#endif /* __DWMAC_RK_TOOL_H__ */ ++ +--- a/drivers/net/ethernet/stmicro/stmmac/Makefile ++++ b/drivers/net/ethernet/stmicro/stmmac/Makefile +@@ -21,7 +21,7 @@ + obj-$(CONFIG_DWMAC_MESON) += dwmac-meson.o dwmac-meson8b.o + obj-$(CONFIG_DWMAC_OXNAS) += dwmac-oxnas.o + obj-$(CONFIG_DWMAC_QCOM_ETHQOS) += dwmac-qcom-ethqos.o +-obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o ++obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o dwmac-rk-tool.o + obj-$(CONFIG_DWMAC_SOCFPGA) += dwmac-altr-socfpga.o + obj-$(CONFIG_DWMAC_STI) += dwmac-sti.o + obj-$(CONFIG_DWMAC_STM32) += dwmac-stm32.o +--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c ++++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +@@ -24,6 +24,7 @@ + #include + + #include "stmmac_platform.h" ++#include "dwmac-rk-tool.h" + + struct rk_priv_data; + struct rk_gmac_ops { +@@ -2002,6 +2003,40 @@ static void rk_fix_speed(void *priv, uns + } + } + ++void dwmac_rk_set_rgmii_delayline(struct stmmac_priv *priv, ++ int tx_delay, int rx_delay) ++{ ++ struct rk_priv_data *bsp_priv = priv->plat->bsp_priv; ++ ++ if (bsp_priv->ops->set_to_rgmii) { ++ bsp_priv->ops->set_to_rgmii(bsp_priv, tx_delay, rx_delay); ++ bsp_priv->tx_delay = tx_delay; ++ bsp_priv->rx_delay = rx_delay; ++ } ++} ++EXPORT_SYMBOL(dwmac_rk_set_rgmii_delayline); ++ ++void dwmac_rk_get_rgmii_delayline(struct stmmac_priv *priv, ++ int *tx_delay, int *rx_delay) ++{ ++ struct rk_priv_data *bsp_priv = priv->plat->bsp_priv; ++ ++ if (!bsp_priv->ops->set_to_rgmii) ++ return; ++ ++ *tx_delay = bsp_priv->tx_delay; ++ *rx_delay = bsp_priv->rx_delay; ++} ++EXPORT_SYMBOL(dwmac_rk_get_rgmii_delayline); ++ ++int dwmac_rk_get_phy_interface(struct stmmac_priv *priv) ++{ ++ struct rk_priv_data *bsp_priv = priv->plat->bsp_priv; ++ ++ return bsp_priv->phy_iface; ++} ++EXPORT_SYMBOL(dwmac_rk_get_phy_interface); ++ + static int rk_gmac_probe(struct platform_device *pdev) + { + struct plat_stmmacenet_data *plat_dat; +@@ -2047,6 +2082,10 @@ static int rk_gmac_probe(struct platform + ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); + if (ret) + goto err_gmac_powerdown; ++ ++ ret = dwmac_rk_create_loopback_sysfs(&pdev->dev); ++ if (ret) ++ goto err_gmac_powerdown; + + return 0; + +@@ -2065,6 +2104,8 @@ static void rk_gmac_remove(struct platfo + stmmac_dvr_remove(&pdev->dev); + + rk_gmac_powerdown(bsp_priv); ++ ++ dwmac_rk_remove_loopback_sysfs(&pdev->dev); + } + + #ifdef CONFIG_PM_SLEEP