summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/stmicro
diff options
context:
space:
mode:
authorJoel Fernandes <joelagnelf@nvidia.com>2026-07-18 13:15:36 -0400
committerPaul E. McKenney <paulmck@kernel.org>2026-07-23 10:39:57 -0700
commit34ff636f322c6d5e4f0a52338fa63a7dff09a495 (patch)
tree7629759c1ec7148b95bc8ab3080df20a5ecd2273 /drivers/net/ethernet/stmicro
downloadlinux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.tar.gz
linux-stable-34ff636f322c6d5e4f0a52338fa63a7dff09a495.zip
scftorture: Count single_rpc offline failures in statistics outputgrafted
scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Diffstat (limited to 'drivers/net/ethernet/stmicro')
-rw-r--r--drivers/net/ethernet/stmicro/Kconfig23
-rw-r--r--drivers/net/ethernet/stmicro/Makefile6
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/Kconfig420
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/Makefile54
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/chain_mode.c166
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/common.h670
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/descs.h181
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/descs_com.h129
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-anarion.c137
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c374
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c287
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c80
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-imx.c405
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-ingenic.c284
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c151
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c1398
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-intel.h81
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c510
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c619
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c240
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c90
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c685
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-meson.c95
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c520
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c387
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-nuvoton.c137
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c820
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c236
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c1649
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c93
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c236
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c692
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c92
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c229
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c181
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-sti.c314
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c685
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-sun55i.c159
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c1299
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c168
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c418
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c286
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c274
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac100.h79
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac1000.h261
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c609
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c274
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c192
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c125
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4.h478
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c1045
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c569
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h142
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c604
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h200
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c228
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac5.c572
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac5.h97
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h163
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c300
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h468
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c1597
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c376
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c613
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/dwxlgmac2.h22
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/enh_desc.c451
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/hwif.c403
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/hwif.h696
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/mmc.h145
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/mmc_core.c604
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/norm_desc.c295
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/ring_mode.c141
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac.h460
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_est.c179
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_est.h67
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c1136
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c314
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.h28
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c279
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.c48
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.h12
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_main.c8435
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c751
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c227
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c254
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h89
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c1010
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h40
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c400
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h110
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c2049
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c1289
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c377
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h64
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c141
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.h14
96 files changed, 45482 insertions, 0 deletions
diff --git a/drivers/net/ethernet/stmicro/Kconfig b/drivers/net/ethernet/stmicro/Kconfig
new file mode 100644
index 000000000..cc136b4c9
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# STMicroelectronics device configuration
+#
+
+config NET_VENDOR_STMICRO
+ bool "STMicroelectronics devices"
+ default y
+ depends on HAS_IOMEM
+ help
+ If you have a network (Ethernet) card based on Synopsys Ethernet IP
+ Cores, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about STMicroelectronics cards. If you say Y, you will
+ be asked for your specific card in the following questions.
+
+if NET_VENDOR_STMICRO
+
+source "drivers/net/ethernet/stmicro/stmmac/Kconfig"
+
+endif # NET_VENDOR_STMICRO
diff --git a/drivers/net/ethernet/stmicro/Makefile b/drivers/net/ethernet/stmicro/Makefile
new file mode 100644
index 000000000..72fd1f6ab
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the STMicroelectronics device drivers.
+#
+
+obj-$(CONFIG_STMMAC_ETH) += stmmac/
diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
new file mode 100644
index 000000000..e3dd5adda
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -0,0 +1,420 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config STMMAC_ETH
+ tristate "STMicroelectronics Multi-Gigabit Ethernet driver"
+ depends on HAS_IOMEM && HAS_DMA
+ depends on PTP_1588_CLOCK_OPTIONAL
+ depends on ETHTOOL_NETLINK
+ select MII
+ select PCS_XPCS
+ select PAGE_POOL
+ select PHYLINK
+ select CRC32
+ select RESET_CONTROLLER
+ select NET_DEVLINK
+ help
+ This is the driver for the Ethernet IPs built around a
+ Synopsys IP Core.
+
+if STMMAC_ETH
+
+config STMMAC_SELFTESTS
+ bool "Support for STMMAC Selftests"
+ depends on INET
+ default n
+ help
+ This adds support for STMMAC Selftests using ethtool. Enable this
+ feature if you are facing problems with your HW and submit the test
+ results to the netdev Mailing List.
+
+config STMMAC_PLATFORM
+ tristate "STMMAC Platform bus support"
+ select MFD_SYSCON
+ default y
+ help
+ This selects the platform specific bus support for the stmmac driver.
+ This is the driver used on several SoCs:
+ STi, Allwinner, Amlogic Meson, Altera SOCFPGA.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+if STMMAC_PLATFORM
+
+config DWMAC_DWC_QOS_ETH
+ tristate "Support for snps,dwc-qos-ethernet.txt DT binding."
+ select CRC32
+ select MII
+ depends on OF && HAS_DMA
+ help
+ Support for chips using the snps,dwc-qos-ethernet.txt DT binding.
+
+config DWMAC_GENERIC
+ tristate "Generic driver for DWMAC"
+ default STMMAC_PLATFORM
+ help
+ Generic DWMAC driver for platforms that don't require any
+ platform specific code to function or is using platform
+ data for setup.
+
+config DWMAC_ANARION
+ tristate "Adaptrum Anarion GMAC support"
+ default ARC
+ depends on OF && (ARC || COMPILE_TEST)
+ help
+ Support for Adaptrum Anarion GMAC Ethernet controller.
+
+ This selects the Anarion SoC glue layer support for the stmmac driver.
+
+config DWMAC_EIC7700
+ tristate "Support for Eswin eic7700 ethernet driver"
+ depends on OF && HAS_DMA && ARCH_ESWIN || COMPILE_TEST
+ help
+ This driver supports the Eswin EIC7700 Ethernet controller,
+ which integrates Synopsys DesignWare QoS features. It enables
+ high-speed networking with DMA acceleration and is optimized
+ for embedded systems.
+
+config DWMAC_INGENIC
+ tristate "Ingenic MAC support"
+ default MACH_INGENIC
+ depends on OF && HAS_IOMEM && (MACH_INGENIC || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for ethernet controller on Ingenic SoCs.
+
+ This selects Ingenic SoCs glue layer support for the stmmac
+ device driver. This driver is used on for the Ingenic SoCs
+ MAC ethernet controller.
+
+config DWMAC_IPQ806X
+ tristate "QCA IPQ806x DWMAC support"
+ default ARCH_QCOM
+ depends on OF && (ARCH_QCOM || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for QCA IPQ806X DWMAC Ethernet.
+
+ This selects the IPQ806x SoC glue layer support for the stmmac
+ device driver. This driver does not use any of the hardware
+ acceleration features available on this SoC. Network devices
+ will behave like standard non-accelerated ethernet interfaces.
+
+ Select the QCOM_SOCINFO config flag to enable specific dwmac
+ fixup based on the ipq806x SoC revision.
+
+config DWMAC_LPC18XX
+ tristate "NXP LPC18xx/43xx DWMAC support"
+ default ARCH_LPC18XX
+ depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for NXP LPC18xx/43xx DWMAC Ethernet.
+
+config DWMAC_MEDIATEK
+ tristate "MediaTek MT27xx GMAC support"
+ depends on OF && (ARCH_MEDIATEK || COMPILE_TEST)
+ help
+ Support for MediaTek GMAC Ethernet controller.
+
+ This selects the MT2712 SoC support for the stmmac driver.
+
+config DWMAC_MESON
+ tristate "Amlogic Meson dwmac support"
+ default ARCH_MESON
+ depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST)
+ help
+ Support for Ethernet controller on Amlogic Meson SoCs.
+
+ This selects the Amlogic Meson SoC glue layer support for
+ the stmmac device driver. This driver is used for Meson6,
+ Meson8, Meson8b and GXBB SoCs.
+
+config DWMAC_NUVOTON
+ tristate "Nuvoton MA35 dwmac support"
+ default ARCH_MA35
+ depends on OF && (ARCH_MA35 || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for Ethernet controller on Nuvoton MA35 series SoC.
+
+ This selects the Nuvoton MA35 series SoC glue layer support
+ for the stmmac device driver. The nuvoton-dwmac driver is
+ used for MA35 series SoCs.
+
+config DWMAC_QCOM_ETHQOS
+ tristate "Qualcomm ETHQOS support"
+ default ARCH_QCOM
+ depends on OF && (ARCH_QCOM || COMPILE_TEST)
+ help
+ Support for the Qualcomm ETHQOS core.
+
+ This selects the Qualcomm ETHQOS glue layer support for the
+ stmmac device driver.
+
+config DWMAC_RENESAS_GBETH
+ tristate "Renesas RZ/V2H(P) GBETH and RZ/T2H, RZ/N2H GMAC support"
+ default ARCH_RENESAS
+ depends on OF && (ARCH_RENESAS || COMPILE_TEST)
+ select PCS_RZN1_MIIC
+ help
+ Support for Gigabit Ethernet Interface (GBETH)/ Ethernet MAC (GMAC)
+ on Renesas SoCs.
+
+ This selects Renesas SoC glue layer support for the stmmac device
+ driver. This driver is used for the RZ/V2H(P) family, RZ/T2H and
+ RZ/N2H SoCs.
+
+config DWMAC_ROCKCHIP
+ tristate "Rockchip dwmac support"
+ default ARCH_ROCKCHIP
+ depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for Ethernet controller on Rockchip RK3288 SoC.
+
+ This selects the Rockchip RK3288 SoC glue layer support for
+ the stmmac device driver.
+
+config DWMAC_RZN1
+ tristate "Renesas RZ/N1 dwmac support"
+ default ARCH_RZN1
+ depends on OF && (ARCH_RZN1 || COMPILE_TEST)
+ select PCS_RZN1_MIIC
+ help
+ Support for Ethernet controller on Renesas RZ/N1 SoC family.
+
+ This selects the Renesas RZ/N1 SoC glue layer support for
+ the stmmac device driver. This support can make use of a custom MII
+ converter PCS device.
+
+config DWMAC_S32
+ tristate "NXP S32G/S32R GMAC support"
+ default ARCH_S32
+ depends on OF && (ARCH_S32 || COMPILE_TEST)
+ help
+ Support for ethernet controller on NXP S32CC SOCs.
+
+ This selects NXP SoC glue layer support for the stmmac
+ device driver. This driver is used for the S32CC series
+ SOCs GMAC ethernet controller, ie. S32G2xx, S32G3xx and
+ S32R45.
+
+config DWMAC_SOCFPGA
+ tristate "SOCFPGA dwmac support"
+ default ARCH_INTEL_SOCFPGA
+ depends on OF && (ARCH_INTEL_SOCFPGA || COMPILE_TEST)
+ select MFD_SYSCON
+ select MDIO_REGMAP
+ select REGMAP_MMIO
+ select PCS_LYNX
+ help
+ Support for ethernet controller on Altera SOCFPGA
+
+ This selects the Altera SOCFPGA SoC glue layer support
+ for the stmmac device driver. This driver is used for
+ arria5 and cyclone5 FPGA SoCs.
+
+config DWMAC_SOPHGO
+ tristate "Sophgo dwmac support"
+ depends on OF && (ARCH_SOPHGO || COMPILE_TEST)
+ default m if ARCH_SOPHGO
+ help
+ Support for ethernet controllers on Sophgo RISC-V SoCs
+
+ This selects the Sophgo SoC specific glue layer support
+ for the stmmac device driver. This driver is used for the
+ ethernet controllers on various Sophgo SoCs.
+
+config DWMAC_SPACEMIT
+ tristate "Spacemit dwmac support"
+ depends on OF && (ARCH_SPACEMIT || COMPILE_TEST)
+ select MFD_SYSCON
+ default m if ARCH_SPACEMIT
+ help
+ Support for ethernet controllers on Spacemit RISC-V SoCs
+
+ This selects the Spacemit platform specific glue layer support
+ for the stmmac device driver. This driver is used for the
+ Spacemit K3 ethernet controllers.
+
+config DWMAC_STARFIVE
+ tristate "StarFive dwmac support"
+ depends on OF && (ARCH_STARFIVE || COMPILE_TEST)
+ select MFD_SYSCON
+ default m if ARCH_STARFIVE
+ help
+ Support for ethernet controllers on StarFive RISC-V SoCs
+
+ This selects the StarFive platform specific glue layer support
+ for the stmmac device driver. This driver is used for the
+ StarFive JH7100 and JH7110 ethernet controllers.
+
+config DWMAC_STI
+ tristate "STi GMAC support"
+ default ARCH_STI
+ depends on OF && (ARCH_STI || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for ethernet controller on STi SOCs.
+
+ This selects STi SoC glue layer support for the stmmac
+ device driver. This driver is used on for the STi series
+ SOCs GMAC ethernet controller.
+
+config DWMAC_STM32
+ tristate "STM32 DWMAC support"
+ default ARCH_STM32
+ depends on OF && HAS_IOMEM && (ARCH_STM32 || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for ethernet controller on STM32 SOCs.
+
+ This selects STM32 SoC glue layer support for the stmmac
+ device driver. This driver is used on for the STM32 series
+ SOCs GMAC ethernet controller.
+
+config DWMAC_SUNXI
+ tristate "Allwinner GMAC support"
+ default ARCH_SUNXI
+ depends on OF && (ARCH_SUNXI || COMPILE_TEST)
+ help
+ Support for Allwinner A20/A31 GMAC ethernet controllers.
+
+ This selects Allwinner SoC glue layer support for the
+ stmmac device driver. This driver is used for A20/A31
+ GMAC ethernet controller.
+
+config DWMAC_SUN8I
+ tristate "Allwinner sun8i GMAC support"
+ default ARCH_SUNXI
+ depends on OF && (ARCH_SUNXI || COMPILE_TEST)
+ select MDIO_BUS_MUX
+ help
+ Support for Allwinner H3 A83T A64 EMAC ethernet controllers.
+
+ This selects Allwinner SoC glue layer support for the
+ stmmac device driver. This driver is used for H3/A83T/A64
+ EMAC ethernet controller.
+
+config DWMAC_SUN55I
+ tristate "Allwinner sun55i GMAC200 support"
+ default ARCH_SUNXI
+ depends on OF && (ARCH_SUNXI || COMPILE_TEST)
+ select MDIO_BUS_MUX
+ help
+ Support for Allwinner A523/T527 GMAC200 ethernet controllers.
+
+ This selects Allwinner SoC glue layer support for the
+ stmmac device driver. This driver is used for A523/T527
+ GMAC200 ethernet controller.
+
+config DWMAC_THEAD
+ tristate "T-HEAD dwmac support"
+ depends on OF && (ARCH_THEAD || COMPILE_TEST)
+ help
+ Support for ethernet controllers on T-HEAD RISC-V SoCs
+
+ This selects the T-HEAD platform specific glue layer support for
+ the stmmac device driver. This driver is used for T-HEAD TH1520
+ ethernet controller.
+
+config DWMAC_IMX8
+ tristate "NXP IMX8 DWMAC support"
+ default ARCH_MXC
+ depends on OF && (ARCH_MXC || COMPILE_TEST)
+ select MFD_SYSCON
+ help
+ Support for ethernet controller on NXP i.MX8 SOCs.
+
+ This selects NXP SoC glue layer support for the stmmac
+ device driver. This driver is used for i.MX8 series like
+ iMX8MP/iMX8DXL GMAC ethernet controller.
+
+config DWMAC_INTEL_PLAT
+ tristate "Intel dwmac support"
+ depends on OF && COMMON_CLK
+ help
+ Support for ethernet controllers on Intel SoCs
+
+ This selects the Intel platform specific glue layer support for
+ the stmmac device driver. This driver is used for the Intel Keem Bay
+ SoC.
+
+config DWMAC_LOONGSON1
+ tristate "Loongson1 GMAC support"
+ default MACH_LOONGSON32
+ depends on OF && (MACH_LOONGSON32 || COMPILE_TEST)
+ help
+ Support for ethernet controller on Loongson1 SoC.
+
+ This selects Loongson1 SoC glue layer support for the stmmac
+ device driver. This driver is used for Loongson1-based boards
+ like Loongson LS1B/LS1C.
+
+config DWMAC_TEGRA
+ tristate "NVIDIA Tegra MGBE support"
+ depends on ARCH_TEGRA || COMPILE_TEST
+ help
+ This selects the Multi-GigaBit Ethernet (MGBE) Controller that is
+ found on the NVIDIA Tegra SoC devices. This driver provides the glue
+ layer on top of the stmmac driver required for these NVIDIA Tegra SoC
+ devices.
+
+config DWMAC_VISCONTI
+ tristate "Toshiba Visconti DWMAC support"
+ default ARCH_VISCONTI
+ depends on OF && COMMON_CLK && (ARCH_VISCONTI || COMPILE_TEST)
+ help
+ Support for ethernet controller on Visconti SoCs.
+
+endif # STMMAC_PLATFORM
+
+config STMMAC_LIBPCI
+ tristate
+ help
+ This option enables the PCI bus helpers for the stmmac driver.
+
+config DWMAC_INTEL
+ tristate "Intel GMAC support"
+ default X86
+ depends on X86 && PCI
+ depends on COMMON_CLK
+ depends on ACPI
+ help
+ This selects the Intel platform specific bus support for the
+ stmmac driver. This driver is used for Intel Quark/EHL/TGL.
+
+config DWMAC_LOONGSON
+ tristate "Loongson PCI DWMAC support"
+ default MACH_LOONGSON64
+ depends on (MACH_LOONGSON64 || COMPILE_TEST) && PCI
+ depends on COMMON_CLK
+ select STMMAC_LIBPCI
+ help
+ This selects the LOONGSON PCI bus support for the stmmac driver,
+ Support for ethernet controller on Loongson-2K1000 SoC and LS7A1000 bridge.
+
+config DWMAC_MOTORCOMM
+ tristate "Motorcomm PCI DWMAC support"
+ depends on PCI
+ select MOTORCOMM_PHY
+ select STMMAC_LIBPCI
+ help
+ This enables glue driver for Motorcomm DWMAC-based PCI Ethernet
+ controllers. Currently only YT6801 is supported.
+
+config STMMAC_PCI
+ tristate "STMMAC PCI bus support"
+ depends on PCI
+ depends on COMMON_CLK
+ select STMMAC_LIBPCI
+ help
+ This selects the platform specific bus support for the stmmac driver.
+ This driver was tested on XLINX XC2V3000 FF1152AMT0221
+ D1215994A VIRTEX FPGA board and SNPS QoS IPK Prototyping Kit.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+endif # STMMAC_ETH
diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile
new file mode 100644
index 000000000..a1cea2f57
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/Makefile
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_STMMAC_ETH) += stmmac.o
+stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o ring_mode.o \
+ chain_mode.o dwmac_lib.o dwmac1000_core.o dwmac1000_dma.o \
+ dwmac100_core.o dwmac100_dma.o enh_desc.o norm_desc.o \
+ mmc_core.o stmmac_hwtstamp.o stmmac_ptp.o dwmac4_descs.o \
+ dwmac4_dma.o dwmac4_lib.o dwmac4_core.o dwmac5.o hwif.o \
+ stmmac_tc.o dwxgmac2_core.o dwxgmac2_dma.o dwxgmac2_descs.o \
+ stmmac_xdp.o stmmac_est.o stmmac_fpe.o stmmac_vlan.o \
+ stmmac_pcs.o $(stmmac-y)
+
+stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o
+
+# Ordering matters. Generic driver must be last.
+obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o
+obj-$(CONFIG_DWMAC_ANARION) += dwmac-anarion.o
+obj-$(CONFIG_DWMAC_EIC7700) += dwmac-eic7700.o
+obj-$(CONFIG_DWMAC_INGENIC) += dwmac-ingenic.o
+obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o
+obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o
+obj-$(CONFIG_DWMAC_MEDIATEK) += dwmac-mediatek.o
+obj-$(CONFIG_DWMAC_MESON) += dwmac-meson.o dwmac-meson8b.o
+obj-$(CONFIG_DWMAC_NUVOTON) += dwmac-nuvoton.o
+obj-$(CONFIG_DWMAC_QCOM_ETHQOS) += dwmac-qcom-ethqos.o
+obj-$(CONFIG_DWMAC_RENESAS_GBETH) += dwmac-renesas-gbeth.o
+obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o
+obj-$(CONFIG_DWMAC_RZN1) += dwmac-rzn1.o
+obj-$(CONFIG_DWMAC_S32) += dwmac-s32.o
+obj-$(CONFIG_DWMAC_SOCFPGA) += dwmac-altr-socfpga.o
+obj-$(CONFIG_DWMAC_SOPHGO) += dwmac-sophgo.o
+obj-$(CONFIG_DWMAC_SPACEMIT) += dwmac-spacemit.o
+obj-$(CONFIG_DWMAC_STARFIVE) += dwmac-starfive.o
+obj-$(CONFIG_DWMAC_STI) += dwmac-sti.o
+obj-$(CONFIG_DWMAC_STM32) += dwmac-stm32.o
+obj-$(CONFIG_DWMAC_SUNXI) += dwmac-sunxi.o
+obj-$(CONFIG_DWMAC_SUN8I) += dwmac-sun8i.o
+obj-$(CONFIG_DWMAC_SUN55I) += dwmac-sun55i.o
+obj-$(CONFIG_DWMAC_THEAD) += dwmac-thead.o
+obj-$(CONFIG_DWMAC_DWC_QOS_ETH) += dwmac-dwc-qos-eth.o
+obj-$(CONFIG_DWMAC_INTEL_PLAT) += dwmac-intel-plat.o
+obj-$(CONFIG_DWMAC_LOONGSON1) += dwmac-loongson1.o
+obj-$(CONFIG_DWMAC_GENERIC) += dwmac-generic.o
+obj-$(CONFIG_DWMAC_IMX8) += dwmac-imx.o
+obj-$(CONFIG_DWMAC_TEGRA) += dwmac-tegra.o
+obj-$(CONFIG_DWMAC_VISCONTI) += dwmac-visconti.o
+stmmac-platform-objs:= stmmac_platform.o
+dwmac-altr-socfpga-objs := dwmac-socfpga.o
+
+obj-$(CONFIG_STMMAC_LIBPCI) += stmmac_libpci.o
+obj-$(CONFIG_STMMAC_PCI) += stmmac-pci.o
+obj-$(CONFIG_DWMAC_INTEL) += dwmac-intel.o
+obj-$(CONFIG_DWMAC_LOONGSON) += dwmac-loongson.o
+obj-$(CONFIG_DWMAC_MOTORCOMM) += dwmac-motorcomm.o
+stmmac-pci-objs:= stmmac_pci.o
diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
new file mode 100644
index 000000000..fc04a2334
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ Specialised functions for managing Chained mode
+
+ Copyright(C) 2011 STMicroelectronics Ltd
+
+ It defines all the functions used to handle the normal/enhanced
+ descriptors in case of the DMA is configured to work in chained or
+ in ring mode.
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include "stmmac.h"
+
+static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
+ int csum)
+{
+ unsigned int nopaged_len = skb_headlen(skb);
+ struct stmmac_priv *priv = tx_q->priv_data;
+ unsigned int entry = tx_q->cur_tx;
+ unsigned int bmax, buf_len, des2;
+ unsigned int i = 1, len;
+ struct dma_desc *desc;
+
+ desc = tx_q->dma_tx + entry;
+
+ if (priv->plat->enh_desc)
+ bmax = BUF_SIZE_8KiB;
+ else
+ bmax = BUF_SIZE_2KiB;
+
+ buf_len = min_t(unsigned int, nopaged_len, bmax);
+ len = nopaged_len - buf_len;
+
+ des2 = dma_map_single(priv->device, skb->data,
+ buf_len, DMA_TO_DEVICE);
+ desc->des2 = cpu_to_le32(des2);
+ if (dma_mapping_error(priv->device, des2))
+ return -1;
+ tx_q->tx_skbuff_dma[entry].buf = des2;
+ tx_q->tx_skbuff_dma[entry].len = buf_len;
+ /* do not close the descriptor and do not set own bit */
+ stmmac_prepare_tx_desc(priv, desc, 1, buf_len, csum, STMMAC_CHAIN_MODE,
+ 0, false, skb->len);
+
+ while (len != 0) {
+ tx_q->tx_skbuff[entry] = NULL;
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ desc = tx_q->dma_tx + entry;
+
+ if (len > bmax) {
+ des2 = dma_map_single(priv->device,
+ (skb->data + bmax * i),
+ bmax, DMA_TO_DEVICE);
+ desc->des2 = cpu_to_le32(des2);
+ if (dma_mapping_error(priv->device, des2))
+ return -1;
+ tx_q->tx_skbuff_dma[entry].buf = des2;
+ tx_q->tx_skbuff_dma[entry].len = bmax;
+ stmmac_prepare_tx_desc(priv, desc, 0, bmax, csum,
+ STMMAC_CHAIN_MODE, 1, false, skb->len);
+ len -= bmax;
+ i++;
+ } else {
+ des2 = dma_map_single(priv->device,
+ (skb->data + bmax * i), len,
+ DMA_TO_DEVICE);
+ desc->des2 = cpu_to_le32(des2);
+ if (dma_mapping_error(priv->device, des2))
+ return -1;
+ tx_q->tx_skbuff_dma[entry].buf = des2;
+ tx_q->tx_skbuff_dma[entry].len = len;
+ /* last descriptor can be set now */
+ stmmac_prepare_tx_desc(priv, desc, 0, len, csum,
+ STMMAC_CHAIN_MODE, 1, true, skb->len);
+ len = 0;
+ }
+ }
+
+ tx_q->cur_tx = entry;
+
+ return entry;
+}
+
+static bool is_jumbo_frm(unsigned int len, bool enh_desc)
+{
+ bool ret = false;
+
+ if ((enh_desc && (len > BUF_SIZE_8KiB)) ||
+ (!enh_desc && (len > BUF_SIZE_2KiB)))
+ ret = true;
+
+ return ret;
+}
+
+static void init_dma_chain(void *des, dma_addr_t phy_addr,
+ unsigned int size, unsigned int extend_desc)
+{
+ /*
+ * In chained mode the des3 points to the next element in the ring.
+ * The latest element has to point to the head.
+ */
+ int i;
+ dma_addr_t dma_phy = phy_addr;
+
+ if (extend_desc) {
+ struct dma_extended_desc *p = (struct dma_extended_desc *)des;
+ for (i = 0; i < (size - 1); i++) {
+ dma_phy += sizeof(struct dma_extended_desc);
+ p->basic.des3 = cpu_to_le32((unsigned int)dma_phy);
+ p++;
+ }
+ p->basic.des3 = cpu_to_le32((unsigned int)phy_addr);
+
+ } else {
+ struct dma_desc *p = (struct dma_desc *)des;
+ for (i = 0; i < (size - 1); i++) {
+ dma_phy += sizeof(struct dma_desc);
+ p->des3 = cpu_to_le32((unsigned int)dma_phy);
+ p++;
+ }
+ p->des3 = cpu_to_le32((unsigned int)phy_addr);
+ }
+}
+
+static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p)
+{
+ struct stmmac_priv *priv = rx_q->priv_data;
+
+ if (priv->hwts_rx_en && !priv->extend_desc)
+ /* NOTE: Device will overwrite des3 with timestamp value if
+ * 1588-2002 time stamping is enabled, hence reinitialize it
+ * to keep explicit chaining in the descriptor.
+ */
+ p->des3 = cpu_to_le32((unsigned int)(rx_q->dma_rx_phy +
+ (((rx_q->dirty_rx) + 1) %
+ priv->dma_conf.dma_rx_size) *
+ sizeof(struct dma_desc)));
+}
+
+static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p)
+{
+ struct stmmac_priv *priv = tx_q->priv_data;
+ unsigned int entry = tx_q->dirty_tx;
+
+ if (tx_q->tx_skbuff_dma[entry].last_segment && !priv->extend_desc &&
+ priv->hwts_tx_en)
+ /* NOTE: Device will overwrite des3 with timestamp value if
+ * 1588-2002 time stamping is enabled, hence reinitialize it
+ * to keep explicit chaining in the descriptor.
+ */
+ p->des3 = cpu_to_le32((unsigned int)((tx_q->dma_tx_phy +
+ ((tx_q->dirty_tx + 1) %
+ priv->dma_conf.dma_tx_size))
+ * sizeof(struct dma_desc)));
+}
+
+const struct stmmac_mode_ops chain_mode_ops = {
+ .init = init_dma_chain,
+ .is_jumbo_frm = is_jumbo_frm,
+ .jumbo_frm = jumbo_frm,
+ .refill_desc3 = refill_desc3,
+ .clean_desc3 = clean_desc3,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h
new file mode 100644
index 000000000..927ea6230
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/common.h
@@ -0,0 +1,670 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ STMMAC Common Header File
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/stmmac.h>
+#include <linux/phy.h>
+#include <linux/pcs/pcs-xpcs.h>
+#include <linux/module.h>
+#if IS_ENABLED(CONFIG_VLAN_8021Q)
+#define STMMAC_VLAN_TAG_USED
+#include <linux/if_vlan.h>
+#endif
+
+#include "descs.h"
+#include "hwif.h"
+#include "mmc.h"
+
+#define DWMAC_SNPSVER GENMASK_U32(7, 0)
+#define DWMAC_USERVER GENMASK_U32(15, 8)
+
+/* Synopsys Core versions */
+#define DWMAC_CORE_3_40 0x34
+#define DWMAC_CORE_3_50 0x35
+#define DWMAC_CORE_3_70 0x37
+#define DWMAC_CORE_4_00 0x40
+#define DWMAC_CORE_4_10 0x41
+#define DWMAC_CORE_5_00 0x50
+#define DWMAC_CORE_5_10 0x51
+#define DWMAC_CORE_5_20 0x52
+#define DWXGMAC_CORE_2_10 0x21
+#define DWXGMAC_CORE_2_20 0x22
+#define DWXLGMAC_CORE_2_00 0x20
+
+/* Device ID */
+#define DWXGMAC_ID 0x76
+#define DWXLGMAC_ID 0x27
+
+static inline bool dwmac_is_xmac(enum dwmac_core_type core_type)
+{
+ return core_type == DWMAC_CORE_GMAC4 || core_type == DWMAC_CORE_XGMAC;
+}
+
+#define STMMAC_CHAN0 0 /* Always supported and default for all chips */
+
+/* TX and RX Descriptor Length, these need to be power of two.
+ * TX descriptor length less than 64 may cause transmit queue timed out error.
+ * RX descriptor length less than 64 may cause inconsistent Rx chain error.
+ */
+#define DMA_MIN_TX_SIZE 64
+#define DMA_MAX_TX_SIZE 1024
+#define DMA_DEFAULT_TX_SIZE 512
+#define DMA_MIN_RX_SIZE 64
+#define DMA_MAX_RX_SIZE 1024
+#define DMA_DEFAULT_RX_SIZE 512
+#define STMMAC_NEXT_ENTRY(x, size) ((x + 1) & (size - 1))
+
+#undef FRAME_FILTER_DEBUG
+/* #define FRAME_FILTER_DEBUG */
+
+struct stmmac_q_tx_stats {
+ u64_stats_t tx_bytes;
+ u64_stats_t tx_set_ic_bit;
+ u64_stats_t tx_tso_frames;
+ u64_stats_t tx_tso_nfrags;
+};
+
+struct stmmac_napi_tx_stats {
+ u64_stats_t tx_packets;
+ u64_stats_t tx_pkt_n;
+ u64_stats_t poll;
+ u64_stats_t tx_clean;
+ u64_stats_t tx_set_ic_bit;
+};
+
+struct stmmac_txq_stats {
+ /* Updates protected by tx queue lock. */
+ struct u64_stats_sync q_syncp;
+ struct stmmac_q_tx_stats q;
+
+ /* Updates protected by NAPI poll logic. */
+ struct u64_stats_sync napi_syncp;
+ struct stmmac_napi_tx_stats napi;
+} ____cacheline_aligned_in_smp;
+
+struct stmmac_napi_rx_stats {
+ u64_stats_t rx_bytes;
+ u64_stats_t rx_packets;
+ u64_stats_t rx_pkt_n;
+ u64_stats_t poll;
+};
+
+struct stmmac_rxq_stats {
+ /* Updates protected by NAPI poll logic. */
+ struct u64_stats_sync napi_syncp;
+ struct stmmac_napi_rx_stats napi;
+} ____cacheline_aligned_in_smp;
+
+/* Updates on each CPU protected by not allowing nested irqs. */
+struct stmmac_pcpu_stats {
+ struct u64_stats_sync syncp;
+ u64_stats_t rx_normal_irq_n[MTL_MAX_RX_QUEUES];
+ u64_stats_t tx_normal_irq_n[MTL_MAX_TX_QUEUES];
+};
+
+/* Extra statistic and debug information exposed by ethtool */
+struct stmmac_extra_stats {
+ /* Transmit errors */
+ unsigned long tx_underflow ____cacheline_aligned;
+ unsigned long tx_carrier;
+ unsigned long tx_losscarrier;
+ unsigned long vlan_tag;
+ unsigned long tx_deferred;
+ unsigned long tx_vlan;
+ unsigned long tx_jabber;
+ unsigned long tx_frame_flushed;
+ unsigned long tx_payload_error;
+ unsigned long tx_ip_header_error;
+ unsigned long tx_collision;
+ /* Receive errors */
+ unsigned long rx_desc;
+ unsigned long sa_filter_fail;
+ unsigned long overflow_error;
+ unsigned long ipc_csum_error;
+ unsigned long rx_collision;
+ unsigned long rx_crc_errors;
+ unsigned long dribbling_bit;
+ unsigned long rx_length;
+ unsigned long rx_mii;
+ unsigned long rx_multicast;
+ unsigned long rx_gmac_overflow;
+ unsigned long rx_watchdog;
+ unsigned long da_rx_filter_fail;
+ unsigned long sa_rx_filter_fail;
+ unsigned long rx_missed_cntr;
+ unsigned long rx_overflow_cntr;
+ unsigned long rx_vlan;
+ unsigned long rx_split_hdr_pkt_n;
+ /* Tx/Rx IRQ error info */
+ unsigned long tx_undeflow_irq;
+ unsigned long tx_process_stopped_irq;
+ unsigned long tx_jabber_irq;
+ unsigned long rx_overflow_irq;
+ unsigned long rx_buf_unav_irq;
+ unsigned long rx_process_stopped_irq;
+ unsigned long rx_watchdog_irq;
+ unsigned long tx_early_irq;
+ unsigned long fatal_bus_error_irq;
+ /* Tx/Rx IRQ Events */
+ unsigned long rx_early_irq;
+ unsigned long threshold;
+ unsigned long irq_receive_pmt_irq_n;
+ /* MMC info */
+ unsigned long mmc_tx_irq_n;
+ unsigned long mmc_rx_irq_n;
+ unsigned long mmc_rx_csum_offload_irq_n;
+ /* EEE */
+ unsigned long irq_tx_path_in_lpi_mode_n;
+ unsigned long irq_tx_path_exit_lpi_mode_n;
+ unsigned long irq_rx_path_in_lpi_mode_n;
+ unsigned long irq_rx_path_exit_lpi_mode_n;
+ unsigned long phy_eee_wakeup_error_n;
+ /* Extended RDES status */
+ unsigned long ip_hdr_err;
+ unsigned long ip_payload_err;
+ unsigned long ip_csum_bypassed;
+ unsigned long ipv4_pkt_rcvd;
+ unsigned long ipv6_pkt_rcvd;
+ unsigned long no_ptp_rx_msg_type_ext;
+ unsigned long ptp_rx_msg_type_sync;
+ unsigned long ptp_rx_msg_type_follow_up;
+ unsigned long ptp_rx_msg_type_delay_req;
+ unsigned long ptp_rx_msg_type_delay_resp;
+ unsigned long ptp_rx_msg_type_pdelay_req;
+ unsigned long ptp_rx_msg_type_pdelay_resp;
+ unsigned long ptp_rx_msg_type_pdelay_follow_up;
+ unsigned long ptp_rx_msg_type_announce;
+ unsigned long ptp_rx_msg_type_management;
+ unsigned long ptp_rx_msg_pkt_reserved_type;
+ unsigned long ptp_frame_type;
+ unsigned long ptp_ver;
+ unsigned long timestamp_dropped;
+ unsigned long av_pkt_rcvd;
+ unsigned long av_tagged_pkt_rcvd;
+ unsigned long vlan_tag_priority_val;
+ unsigned long l3_filter_match;
+ unsigned long l4_filter_match;
+ unsigned long l3_l4_filter_no_match;
+ /* PCS */
+ unsigned long irq_pcs_ane_n;
+ unsigned long irq_pcs_link_n;
+ unsigned long irq_rgmii_n;
+ /* debug register */
+ unsigned long mtl_tx_status_fifo_full;
+ unsigned long mtl_tx_fifo_not_empty;
+ unsigned long mmtl_fifo_ctrl;
+ unsigned long mtl_tx_fifo_read_ctrl_write;
+ unsigned long mtl_tx_fifo_read_ctrl_wait;
+ unsigned long mtl_tx_fifo_read_ctrl_read;
+ unsigned long mtl_tx_fifo_read_ctrl_idle;
+ unsigned long mac_tx_in_pause;
+ unsigned long mac_tx_frame_ctrl_xfer;
+ unsigned long mac_tx_frame_ctrl_idle;
+ unsigned long mac_tx_frame_ctrl_wait;
+ unsigned long mac_tx_frame_ctrl_pause;
+ unsigned long mac_gmii_tx_proto_engine;
+ unsigned long mtl_rx_fifo_fill_level_full;
+ unsigned long mtl_rx_fifo_fill_above_thresh;
+ unsigned long mtl_rx_fifo_fill_below_thresh;
+ unsigned long mtl_rx_fifo_fill_level_empty;
+ unsigned long mtl_rx_fifo_read_ctrl_flush;
+ unsigned long mtl_rx_fifo_read_ctrl_read_data;
+ unsigned long mtl_rx_fifo_read_ctrl_status;
+ unsigned long mtl_rx_fifo_read_ctrl_idle;
+ unsigned long mtl_rx_fifo_ctrl_active;
+ unsigned long mac_rx_frame_ctrl_fifo;
+ unsigned long mac_gmii_rx_proto_engine;
+ /* EST */
+ unsigned long mtl_est_cgce;
+ unsigned long mtl_est_hlbs;
+ unsigned long mtl_est_hlbf;
+ unsigned long mtl_est_btre;
+ unsigned long mtl_est_btrlm;
+ unsigned long max_sdu_txq_drop[MTL_MAX_TX_QUEUES];
+ unsigned long mtl_est_txq_hlbf[MTL_MAX_TX_QUEUES];
+ unsigned long mtl_est_txq_hlbs[MTL_MAX_TX_QUEUES];
+ /* per queue statistics */
+ struct stmmac_txq_stats txq_stats[MTL_MAX_TX_QUEUES];
+ struct stmmac_rxq_stats rxq_stats[MTL_MAX_RX_QUEUES];
+ struct stmmac_pcpu_stats __percpu *pcpu_stats;
+ unsigned long rx_dropped;
+ unsigned long rx_errors;
+ unsigned long tx_dropped;
+ unsigned long tx_errors;
+};
+
+/* Safety Feature statistics exposed by ethtool */
+struct stmmac_safety_stats {
+ unsigned long mac_errors[32];
+ unsigned long mtl_errors[32];
+ unsigned long dma_errors[32];
+ unsigned long dma_dpp_errors[32];
+};
+
+/* Number of fields in Safety Stats */
+#define STMMAC_SAFETY_FEAT_SIZE \
+ (sizeof(struct stmmac_safety_stats) / sizeof(unsigned long))
+
+/* CSR Frequency Access Defines*/
+#define CSR_F_20M 20000000
+#define CSR_F_35M 35000000
+#define CSR_F_60M 60000000
+#define CSR_F_100M 100000000
+#define CSR_F_150M 150000000
+#define CSR_F_250M 250000000
+#define CSR_F_300M 300000000
+#define CSR_F_500M 500000000
+#define CSR_F_800M 800000000
+
+#define MAC_CSR_H_FRQ_MASK 0x20
+
+#define HASH_TABLE_SIZE 64
+#define PAUSE_TIME 0xffff
+
+/* Flow Control defines */
+#define FLOW_OFF 0
+#define FLOW_RX 1
+#define FLOW_TX 2
+#define FLOW_AUTO (FLOW_TX | FLOW_RX)
+
+#define SF_DMA_MODE 1 /* DMA STORE-AND-FORWARD Operation Mode */
+
+/* DMA HW feature register fields */
+#define DMA_HW_FEAT_MIISEL 0x00000001 /* 10/100 Mbps Support */
+#define DMA_HW_FEAT_GMIISEL 0x00000002 /* 1000 Mbps Support */
+#define DMA_HW_FEAT_HDSEL 0x00000004 /* Half-Duplex Support */
+#define DMA_HW_FEAT_EXTHASHEN 0x00000008 /* Expanded DA Hash Filter */
+#define DMA_HW_FEAT_HASHSEL 0x00000010 /* HASH Filter */
+#define DMA_HW_FEAT_ADDMAC 0x00000020 /* Multiple MAC Addr Reg */
+#define DMA_HW_FEAT_PCSSEL 0x00000040 /* PCS registers */
+#define DMA_HW_FEAT_L3L4FLTREN 0x00000080 /* Layer 3 & Layer 4 Feature */
+#define DMA_HW_FEAT_SMASEL 0x00000100 /* SMA(MDIO) Interface */
+#define DMA_HW_FEAT_RWKSEL 0x00000200 /* PMT Remote Wakeup */
+#define DMA_HW_FEAT_MGKSEL 0x00000400 /* PMT Magic Packet */
+#define DMA_HW_FEAT_MMCSEL 0x00000800 /* RMON Module */
+#define DMA_HW_FEAT_TSVER1SEL 0x00001000 /* Only IEEE 1588-2002 */
+#define DMA_HW_FEAT_TSVER2SEL 0x00002000 /* IEEE 1588-2008 PTPv2 */
+#define DMA_HW_FEAT_EEESEL 0x00004000 /* Energy Efficient Ethernet */
+#define DMA_HW_FEAT_AVSEL 0x00008000 /* AV Feature */
+#define DMA_HW_FEAT_TXCOESEL 0x00010000 /* Checksum Offload in Tx */
+#define DMA_HW_FEAT_RXTYP1COE 0x00020000 /* IP COE (Type 1) in Rx */
+#define DMA_HW_FEAT_RXTYP2COE 0x00040000 /* IP COE (Type 2) in Rx */
+#define DMA_HW_FEAT_RXFIFOSIZE 0x00080000 /* Rx FIFO > 2048 Bytes */
+#define DMA_HW_FEAT_RXCHCNT 0x00300000 /* No. additional Rx Channels */
+#define DMA_HW_FEAT_TXCHCNT 0x00c00000 /* No. additional Tx Channels */
+#define DMA_HW_FEAT_ENHDESSEL 0x01000000 /* Alternate Descriptor */
+/* Timestamping with Internal System Time */
+#define DMA_HW_FEAT_INTTSEN 0x02000000
+#define DMA_HW_FEAT_FLEXIPPSEN 0x04000000 /* Flexible PPS Output */
+#define DMA_HW_FEAT_SAVLANINS 0x08000000 /* Source Addr or VLAN */
+#define DMA_HW_FEAT_ACTPHYIF 0x70000000 /* Active/selected PHY iface */
+#define DEFAULT_DMA_PBL 8
+
+/* phy_intf_sel_i and ACTPHYIF encodings */
+#define PHY_INTF_SEL_GMII_MII 0
+#define PHY_INTF_SEL_RGMII 1
+#define PHY_INTF_SEL_SGMII 2
+#define PHY_INTF_SEL_TBI 3
+#define PHY_INTF_SEL_RMII 4
+#define PHY_INTF_SEL_RTBI 5
+#define PHY_INTF_SEL_SMII 6
+#define PHY_INTF_SEL_REVMII 7
+
+/* XGMAC uses a different encoding - from the AgileX5 documentation */
+#define PHY_INTF_GMII 0
+#define PHY_INTF_RGMII 1
+
+/* MSI defines */
+#define STMMAC_MSI_VEC_MAX 32
+
+/* PCS status and mask defines */
+#define PCS_ANE_IRQ BIT(2) /* PCS Auto-Negotiation */
+#define PCS_LINK_IRQ BIT(1) /* PCS Link */
+#define PCS_RGSMIIIS_IRQ BIT(0) /* RGMII or SMII Interrupt */
+
+/* Max/Min RI Watchdog Timer count value */
+#define MAX_DMA_RIWT 0xff
+#define MIN_DMA_RIWT 0x10
+#define DEF_DMA_RIWT 0xa0
+/* Tx coalesce parameters */
+#define STMMAC_COAL_TX_TIMER 5000
+#define STMMAC_MAX_COAL_TX_TICK 100000
+#define STMMAC_TX_MAX_FRAMES 256
+#define STMMAC_TX_FRAMES 25
+#define STMMAC_RX_FRAMES 0
+
+/* Packets types */
+enum packets_types {
+ PACKET_AVCPQ = 0x1, /* AV Untagged Control packets */
+ PACKET_PTPQ = 0x2, /* PTP Packets */
+ PACKET_DCBCPQ = 0x3, /* DCB Control Packets */
+ PACKET_UPQ = 0x4, /* Untagged Packets */
+ PACKET_MCBCQ = 0x5, /* Multicast & Broadcast Packets */
+};
+
+/* Rx IPC status */
+enum rx_frame_status {
+ good_frame = 0x0,
+ discard_frame = 0x1,
+ csum_none = 0x2,
+ llc_snap = 0x4,
+ dma_own = 0x8,
+ rx_not_ls = 0x10,
+};
+
+/* Tx status */
+enum tx_frame_status {
+ tx_done = 0x0,
+ tx_not_ls = 0x1,
+ tx_err = 0x2,
+ tx_dma_own = 0x4,
+ tx_err_bump_tc = 0x8,
+};
+
+enum dma_irq_status {
+ tx_hard_error = 0x1,
+ tx_hard_error_bump_tc = 0x2,
+ handle_rx = 0x4,
+ handle_tx = 0x8,
+};
+
+enum dma_irq_dir {
+ DMA_DIR_RX = 0x1,
+ DMA_DIR_TX = 0x2,
+ DMA_DIR_RXTX = 0x3,
+};
+
+enum request_irq_err {
+ REQ_IRQ_ERR_ALL,
+ REQ_IRQ_ERR_TX,
+ REQ_IRQ_ERR_RX,
+ REQ_IRQ_ERR_SFTY,
+ REQ_IRQ_ERR_SFTY_UE,
+ REQ_IRQ_ERR_SFTY_CE,
+ REQ_IRQ_ERR_WOL,
+ REQ_IRQ_ERR_MAC,
+ REQ_IRQ_ERR_NO,
+};
+
+/* EEE and LPI defines */
+#define CORE_IRQ_TX_PATH_IN_LPI_MODE (1 << 0)
+#define CORE_IRQ_TX_PATH_EXIT_LPI_MODE (1 << 1)
+#define CORE_IRQ_RX_PATH_IN_LPI_MODE (1 << 2)
+#define CORE_IRQ_RX_PATH_EXIT_LPI_MODE (1 << 3)
+
+/* FPE defines */
+#define FPE_EVENT_UNKNOWN 0
+#define FPE_EVENT_TRSP BIT(0)
+#define FPE_EVENT_TVER BIT(1)
+#define FPE_EVENT_RRSP BIT(2)
+#define FPE_EVENT_RVER BIT(3)
+
+#define CORE_IRQ_MTL_RX_OVERFLOW BIT(8)
+
+/* DMA HW capabilities */
+struct dma_features {
+ unsigned int mbps_10_100;
+ unsigned int mbps_1000;
+ unsigned int half_duplex;
+ unsigned int hash_filter;
+ unsigned int multi_addr;
+ unsigned int pcs;
+ unsigned int sma_mdio;
+ unsigned int pmt_remote_wake_up;
+ unsigned int pmt_magic_frame;
+ unsigned int rmon;
+ /* IEEE 1588-2002 */
+ unsigned int time_stamp;
+ /* IEEE 1588-2008 */
+ unsigned int atime_stamp;
+ /* 802.3az - Energy-Efficient Ethernet (EEE) */
+ unsigned int eee;
+ unsigned int av;
+ unsigned int hash_tb_sz;
+ unsigned int tsoen;
+ /* TX and RX csum */
+ unsigned int tx_coe;
+ unsigned int rx_coe;
+ unsigned int rx_coe_type1;
+ unsigned int rx_coe_type2;
+ unsigned int rxfifo_over_2048;
+ /* TX and RX number of channels */
+ unsigned int number_rx_channel;
+ unsigned int number_tx_channel;
+ /* TX and RX number of queues */
+ u8 number_rx_queues;
+ u8 number_tx_queues;
+ /* PPS output */
+ unsigned int pps_out_num;
+ /* Number of Traffic Classes */
+ unsigned int numtc;
+ /* DCB Feature Enable */
+ unsigned int dcben;
+ /* IEEE 1588 High Word Register Enable */
+ unsigned int advthword;
+ /* PTP Offload Enable */
+ unsigned int ptoen;
+ /* One-Step Timestamping Enable */
+ unsigned int osten;
+ /* Priority-Based Flow Control Enable */
+ unsigned int pfcen;
+ /* Alternate (enhanced) DESC mode */
+ unsigned int enh_desc;
+ /* TX and RX FIFO sizes */
+ unsigned int tx_fifo_size;
+ unsigned int rx_fifo_size;
+ /* Automotive Safety Package */
+ unsigned int asp;
+ /* RX Parser */
+ unsigned int frpsel;
+ unsigned int frpbs;
+ unsigned int frpes;
+ unsigned int addr64;
+ unsigned int host_dma_width;
+ unsigned int rssen;
+ unsigned int vlhash;
+ unsigned int sphen;
+ unsigned int vlins;
+ unsigned int dvlan;
+ unsigned int l3l4fnum;
+ unsigned int arpoffsel;
+ /* One Step for PTP over UDP/IP Feature Enable */
+ unsigned int pou_ost_en;
+ /* Tx Timestamp FIFO Depth */
+ unsigned int ttsfd;
+ /* Queue/Channel-Based VLAN tag insertion on Tx */
+ unsigned int cbtisel;
+ /* Supported Parallel Instruction Processor Engines */
+ unsigned int frppipe_num;
+ /* Number of Extended VLAN Tag Filters */
+ unsigned int nrvf_num;
+ /* TSN Features */
+ unsigned int estwid;
+ unsigned int estdep;
+ unsigned int estsel;
+ unsigned int fpesel;
+ unsigned int tbssel;
+ /* Number of DMA channels enabled for TBS */
+ unsigned int tbs_ch_num;
+ /* Per-Stream Filtering Enable */
+ unsigned int sgfsel;
+ /* Numbers of Auxiliary Snapshot Inputs */
+ unsigned int aux_snapshot_n;
+ /* Timestamp System Time Source */
+ unsigned int tssrc;
+ /* Enhanced DMA Enable */
+ unsigned int edma;
+ /* Different Descriptor Cache Enable */
+ unsigned int ediffc;
+ /* VxLAN/NVGRE Enable */
+ unsigned int vxn;
+ /* Debug Memory Interface Enable */
+ unsigned int dbgmem;
+ /* Number of Policing Counters */
+ unsigned int pcsel;
+ /* Active PHY interface, PHY_INTF_SEL_xxx */
+ u8 actphyif;
+};
+
+/* RX Buffer size must be multiple of 4/8/16 bytes */
+#define BUF_SIZE_16KiB 16368
+#define BUF_SIZE_8KiB 8188
+#define BUF_SIZE_4KiB 4096
+#define BUF_SIZE_2KiB 2048
+
+/* Power Down and WOL */
+#define PMT_NOT_SUPPORTED 0
+#define PMT_SUPPORTED 1
+
+/* Common MAC defines */
+#define MAC_CTRL_REG 0x00000000 /* MAC Control */
+#define MAC_ENABLE_TX 0x00000008 /* Transmitter Enable */
+#define MAC_ENABLE_RX 0x00000004 /* Receiver Enable */
+
+/* Default LPI timers */
+#define STMMAC_DEFAULT_LIT_LS 0x3E8
+#define STMMAC_DEFAULT_TWT_LS 0x1E
+#define STMMAC_ET_MAX 0xFFFFF
+
+/* Common LPI register bits */
+#define LPI_CTRL_STATUS_LPITCSE BIT(21) /* LPI Tx Clock Stop Enable, gmac4, xgmac2 only */
+#define LPI_CTRL_STATUS_LPIATE BIT(20) /* LPI Timer Enable, gmac4 only */
+#define LPI_CTRL_STATUS_LPITXA BIT(19) /* Enable LPI TX Automate */
+#define LPI_CTRL_STATUS_PLSEN BIT(18) /* Enable PHY Link Status */
+#define LPI_CTRL_STATUS_PLS BIT(17) /* PHY Link Status */
+#define LPI_CTRL_STATUS_LPIEN BIT(16) /* LPI Enable */
+#define LPI_CTRL_STATUS_RLPIST BIT(9) /* Receive LPI state, gmac1000 only? */
+#define LPI_CTRL_STATUS_TLPIST BIT(8) /* Transmit LPI state, gmac1000 only? */
+#define LPI_CTRL_STATUS_RLPIEX BIT(3) /* Receive LPI Exit */
+#define LPI_CTRL_STATUS_RLPIEN BIT(2) /* Receive LPI Entry */
+#define LPI_CTRL_STATUS_TLPIEX BIT(1) /* Transmit LPI Exit */
+#define LPI_CTRL_STATUS_TLPIEN BIT(0) /* Transmit LPI Entry */
+
+/* Common definitions for AXI Master Bus Mode */
+#define DMA_AXI_AAL BIT(12)
+#define DMA_AXI_BLEN256 BIT(7)
+#define DMA_AXI_BLEN128 BIT(6)
+#define DMA_AXI_BLEN64 BIT(5)
+#define DMA_AXI_BLEN32 BIT(4)
+#define DMA_AXI_BLEN16 BIT(3)
+#define DMA_AXI_BLEN8 BIT(2)
+#define DMA_AXI_BLEN4 BIT(1)
+#define DMA_AXI_BLEN_MASK GENMASK(7, 1)
+
+void stmmac_axi_blen_to_mask(u32 *regval, const u32 *blen, size_t len);
+
+#define STMMAC_CHAIN_MODE 0x1
+#define STMMAC_RING_MODE 0x2
+
+#define JUMBO_LEN 9000
+
+/* Receive Side Scaling */
+#define STMMAC_RSS_HASH_KEY_SIZE 40
+#define STMMAC_RSS_MAX_TABLE_SIZE 256
+
+/* VLAN */
+#define STMMAC_VLAN_NONE 0x0
+#define STMMAC_VLAN_REMOVE 0x1
+#define STMMAC_VLAN_INSERT 0x2
+#define STMMAC_VLAN_REPLACE 0x3
+
+struct mac_device_info;
+
+struct mac_link {
+ u32 caps;
+ u32 speed_mask;
+ u32 speed10;
+ u32 speed100;
+ u32 speed1000;
+ u32 speed2500;
+ u32 duplex;
+ struct {
+ u32 speed2500;
+ u32 speed5000;
+ u32 speed10000;
+ } xgmii;
+ struct {
+ u32 speed25000;
+ u32 speed40000;
+ u32 speed50000;
+ u32 speed100000;
+ } xlgmii;
+};
+
+struct mii_regs {
+ unsigned int addr; /* MII Address */
+ unsigned int data; /* MII Data */
+ u32 addr_mask; /* MII address mask */
+ u32 reg_mask; /* MII reg mask */
+ u32 clk_csr_mask;
+};
+
+struct mac_device_info {
+ const struct stmmac_ops *mac;
+ const struct stmmac_desc_ops *desc;
+ const struct stmmac_dma_ops *dma;
+ const struct stmmac_mode_ops *mode;
+ const struct stmmac_hwtimestamp *ptp;
+ const struct stmmac_tc_ops *tc;
+ const struct stmmac_mmc_ops *mmc;
+ const struct stmmac_est_ops *est;
+ const struct stmmac_vlan_ops *vlan;
+ struct dw_xpcs *xpcs;
+ struct phylink_pcs *phylink_pcs;
+ struct mii_regs mii; /* MII register Addresses */
+ struct mac_link link;
+ void __iomem *pcsr; /* vpointer to device CSRs */
+ unsigned int multicast_filter_bins;
+ unsigned int unicast_filter_entries;
+ unsigned int mcast_bits_log2;
+ unsigned int rx_csum;
+ unsigned int num_vlan;
+ u32 vlan_filter[32];
+ bool vlan_fail_q_en;
+ u8 vlan_fail_q;
+ bool hw_vlan_en;
+ bool reverse_sgmii_enable;
+
+ /* This spinlock protects read-modify-write of the interrupt
+ * mask/enable registers.
+ */
+ spinlock_t irq_ctrl_lock;
+};
+
+struct stmmac_rx_routing {
+ u32 reg_mask;
+ u32 reg_shift;
+};
+
+int dwmac100_setup(struct stmmac_priv *priv);
+int dwmac1000_setup(struct stmmac_priv *priv);
+int dwmac4_setup(struct stmmac_priv *priv);
+int dwxgmac2_setup(struct stmmac_priv *priv);
+int dwxlgmac2_setup(struct stmmac_priv *priv);
+
+void stmmac_set_mac_addr(void __iomem *ioaddr, const u8 addr[6],
+ unsigned int high, unsigned int low);
+void stmmac_get_mac_addr(void __iomem *ioaddr, unsigned char *addr,
+ unsigned int high, unsigned int low);
+void stmmac_set_mac(void __iomem *ioaddr, bool enable);
+
+void stmmac_dwmac4_set_mac_addr(void __iomem *ioaddr, const u8 addr[6],
+ unsigned int high, unsigned int low);
+void stmmac_dwmac4_get_mac_addr(void __iomem *ioaddr, unsigned char *addr,
+ unsigned int high, unsigned int low);
+void stmmac_dwmac4_set_mac(void __iomem *ioaddr, bool enable);
+
+void dwmac_dma_flush_tx_fifo(void __iomem *ioaddr);
+
+#endif /* __COMMON_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/descs.h b/drivers/net/ethernet/stmicro/stmmac/descs.h
new file mode 100644
index 000000000..846007cc2
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/descs.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ Header File to describe the DMA descriptors and related definitions.
+ This is for DWMAC100 and 1000 cores.
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __DESCS_H__
+#define __DESCS_H__
+
+#include <linux/bitops.h>
+
+/* Normal receive descriptor defines */
+
+/* RDES0 */
+#define RDES0_PAYLOAD_CSUM_ERR BIT(0)
+#define RDES0_CRC_ERROR BIT(1)
+#define RDES0_DRIBBLING BIT(2)
+#define RDES0_MII_ERROR BIT(3)
+#define RDES0_RECEIVE_WATCHDOG BIT(4)
+#define RDES0_FRAME_TYPE BIT(5)
+#define RDES0_COLLISION BIT(6)
+#define RDES0_IPC_CSUM_ERROR BIT(7)
+#define RDES0_LAST_DESCRIPTOR BIT(8)
+#define RDES0_FIRST_DESCRIPTOR BIT(9)
+#define RDES0_VLAN_TAG BIT(10)
+#define RDES0_OVERFLOW_ERROR BIT(11)
+#define RDES0_LENGTH_ERROR BIT(12)
+#define RDES0_SA_FILTER_FAIL BIT(13)
+#define RDES0_DESCRIPTOR_ERROR BIT(14)
+#define RDES0_ERROR_SUMMARY BIT(15)
+#define RDES0_FRAME_LEN_MASK GENMASK(29, 16)
+#define RDES0_DA_FILTER_FAIL BIT(30)
+#define RDES0_OWN BIT(31)
+ /* RDES1 */
+#define RDES1_BUFFER1_SIZE_MASK GENMASK(10, 0)
+#define RDES1_BUFFER2_SIZE_MASK GENMASK(21, 11)
+#define RDES1_SECOND_ADDRESS_CHAINED BIT(24)
+#define RDES1_END_RING BIT(25)
+#define RDES1_DISABLE_IC BIT(31)
+
+/* Enhanced receive descriptor defines */
+
+/* RDES0 (similar to normal RDES) */
+#define ERDES0_RX_MAC_ADDR BIT(0)
+
+/* RDES1: completely differ from normal desc definitions */
+#define ERDES1_BUFFER1_SIZE_MASK GENMASK(12, 0)
+#define ERDES1_SECOND_ADDRESS_CHAINED BIT(14)
+#define ERDES1_END_RING BIT(15)
+#define ERDES1_BUFFER2_SIZE_MASK GENMASK(28, 16)
+#define ERDES1_DISABLE_IC BIT(31)
+
+/* Normal transmit descriptor defines */
+/* TDES0 */
+#define TDES0_DEFERRED BIT(0)
+#define TDES0_UNDERFLOW_ERROR BIT(1)
+#define TDES0_EXCESSIVE_DEFERRAL BIT(2)
+#define TDES0_COLLISION_COUNT_MASK GENMASK(6, 3)
+#define TDES0_VLAN_FRAME BIT(7)
+#define TDES0_EXCESSIVE_COLLISIONS BIT(8)
+#define TDES0_LATE_COLLISION BIT(9)
+#define TDES0_NO_CARRIER BIT(10)
+#define TDES0_LOSS_CARRIER BIT(11)
+#define TDES0_PAYLOAD_ERROR BIT(12)
+#define TDES0_FRAME_FLUSHED BIT(13)
+#define TDES0_JABBER_TIMEOUT BIT(14)
+#define TDES0_ERROR_SUMMARY BIT(15)
+#define TDES0_IP_HEADER_ERROR BIT(16)
+#define TDES0_TIME_STAMP_STATUS BIT(17)
+#define TDES0_OWN ((u32)BIT(31)) /* silence sparse */
+/* TDES1 */
+#define TDES1_BUFFER1_SIZE_MASK GENMASK(10, 0)
+#define TDES1_BUFFER2_SIZE_MASK GENMASK(21, 11)
+#define TDES1_TIME_STAMP_ENABLE BIT(22)
+#define TDES1_DISABLE_PADDING BIT(23)
+#define TDES1_SECOND_ADDRESS_CHAINED BIT(24)
+#define TDES1_END_RING BIT(25)
+#define TDES1_CRC_DISABLE BIT(26)
+#define TDES1_CHECKSUM_INSERTION_MASK GENMASK(28, 27)
+#define TDES1_FIRST_SEGMENT BIT(29)
+#define TDES1_LAST_SEGMENT BIT(30)
+#define TDES1_INTERRUPT BIT(31)
+
+/* Enhanced transmit descriptor defines */
+/* TDES0 */
+#define ETDES0_DEFERRED BIT(0)
+#define ETDES0_UNDERFLOW_ERROR BIT(1)
+#define ETDES0_EXCESSIVE_DEFERRAL BIT(2)
+#define ETDES0_COLLISION_COUNT_MASK GENMASK(6, 3)
+#define ETDES0_VLAN_FRAME BIT(7)
+#define ETDES0_EXCESSIVE_COLLISIONS BIT(8)
+#define ETDES0_LATE_COLLISION BIT(9)
+#define ETDES0_NO_CARRIER BIT(10)
+#define ETDES0_LOSS_CARRIER BIT(11)
+#define ETDES0_PAYLOAD_ERROR BIT(12)
+#define ETDES0_FRAME_FLUSHED BIT(13)
+#define ETDES0_JABBER_TIMEOUT BIT(14)
+#define ETDES0_ERROR_SUMMARY BIT(15)
+#define ETDES0_IP_HEADER_ERROR BIT(16)
+#define ETDES0_TIME_STAMP_STATUS BIT(17)
+#define ETDES0_SECOND_ADDRESS_CHAINED BIT(20)
+#define ETDES0_END_RING BIT(21)
+#define ETDES0_CHECKSUM_INSERTION_MASK GENMASK(23, 22)
+#define ETDES0_TIME_STAMP_ENABLE BIT(25)
+#define ETDES0_DISABLE_PADDING BIT(26)
+#define ETDES0_CRC_DISABLE BIT(27)
+#define ETDES0_FIRST_SEGMENT BIT(28)
+#define ETDES0_LAST_SEGMENT BIT(29)
+#define ETDES0_INTERRUPT BIT(30)
+#define ETDES0_OWN ((u32)BIT(31)) /* silence sparse */
+/* TDES1 */
+#define ETDES1_BUFFER1_SIZE_MASK GENMASK(12, 0)
+#define ETDES1_BUFFER2_SIZE_MASK GENMASK(28, 16)
+
+/* Extended Receive descriptor definitions */
+#define ERDES4_IP_PAYLOAD_TYPE_MASK GENMASK(6, 2)
+#define ERDES4_IP_HDR_ERR BIT(3)
+#define ERDES4_IP_PAYLOAD_ERR BIT(4)
+#define ERDES4_IP_CSUM_BYPASSED BIT(5)
+#define ERDES4_IPV4_PKT_RCVD BIT(6)
+#define ERDES4_IPV6_PKT_RCVD BIT(7)
+#define ERDES4_MSG_TYPE_MASK GENMASK(11, 8)
+#define ERDES4_PTP_FRAME_TYPE BIT(12)
+#define ERDES4_PTP_VER BIT(13)
+#define ERDES4_TIMESTAMP_DROPPED BIT(14)
+#define ERDES4_AV_PKT_RCVD BIT(16)
+#define ERDES4_AV_TAGGED_PKT_RCVD BIT(17)
+#define ERDES4_VLAN_TAG_PRI_VAL_MASK GENMASK(20, 18)
+#define ERDES4_L3_FILTER_MATCH BIT(24)
+#define ERDES4_L4_FILTER_MATCH BIT(25)
+#define ERDES4_L3_L4_FILT_NO_MATCH_MASK GENMASK(27, 26)
+
+/* Extended RDES4 message type definitions */
+#define RDES_EXT_NO_PTP 0x0
+#define RDES_EXT_SYNC 0x1
+#define RDES_EXT_FOLLOW_UP 0x2
+#define RDES_EXT_DELAY_REQ 0x3
+#define RDES_EXT_DELAY_RESP 0x4
+#define RDES_EXT_PDELAY_REQ 0x5
+#define RDES_EXT_PDELAY_RESP 0x6
+#define RDES_EXT_PDELAY_FOLLOW_UP 0x7
+#define RDES_PTP_ANNOUNCE 0x8
+#define RDES_PTP_MANAGEMENT 0x9
+#define RDES_PTP_SIGNALING 0xa
+#define RDES_PTP_PKT_RESERVED_TYPE 0xf
+
+/* Basic descriptor structure for normal and alternate descriptors */
+struct dma_desc {
+ __le32 des0;
+ __le32 des1;
+ __le32 des2;
+ __le32 des3;
+};
+
+/* Extended descriptor structure (e.g. >= databook 3.50a) */
+struct dma_extended_desc {
+ struct dma_desc basic; /* Basic descriptors */
+ __le32 des4; /* Extended Status */
+ __le32 des5; /* Reserved */
+ __le32 des6; /* Tx/Rx Timestamp Low */
+ __le32 des7; /* Tx/Rx Timestamp High */
+};
+
+/* Enhanced descriptor for TBS */
+struct dma_edesc {
+ __le32 des4;
+ __le32 des5;
+ __le32 des6;
+ __le32 des7;
+ struct dma_desc basic;
+};
+
+#define dma_desc_to_edesc(x) container_of(x, struct dma_edesc, basic)
+
+/* Transmit checksum insertion control */
+#define TX_CIC_FULL 3 /* Include IP header and pseudoheader */
+
+#endif /* __DESCS_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h
new file mode 100644
index 000000000..9d1a94a4f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ Header File to describe Normal/enhanced descriptor functions used for RING
+ and CHAINED modes.
+
+ Copyright(C) 2011 STMicroelectronics Ltd
+
+ It defines all the functions used to handle the normal/enhanced
+ descriptors in case of the DMA is configured to work in chained or
+ in ring mode.
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __DESC_COM_H__
+#define __DESC_COM_H__
+
+/* Specific functions used for Ring mode */
+
+/* Enhanced descriptors */
+static inline void ehn_desc_rx_set_on_ring(struct dma_desc *p, int end,
+ int bfsize)
+{
+ if (bfsize == BUF_SIZE_16KiB)
+ p->des1 |= cpu_to_le32(FIELD_PREP(ERDES1_BUFFER2_SIZE_MASK,
+ BUF_SIZE_8KiB));
+
+ if (end)
+ p->des1 |= cpu_to_le32(ERDES1_END_RING);
+}
+
+static inline void enh_desc_end_tx_desc_on_ring(struct dma_desc *p, int end)
+{
+ if (end)
+ p->des0 |= cpu_to_le32(ETDES0_END_RING);
+ else
+ p->des0 &= cpu_to_le32(~ETDES0_END_RING);
+}
+
+/* The maximum buffer 1 size is 8KiB - 1. However, we limit to 4KiB. */
+static inline void enh_set_tx_desc_len_on_ring(struct dma_desc *p, int len)
+{
+ unsigned int buffer1_max_length = BUF_SIZE_4KiB;
+
+ if (unlikely(len > buffer1_max_length)) {
+ p->des1 |= cpu_to_le32(FIELD_PREP(ETDES1_BUFFER2_SIZE_MASK,
+ len - buffer1_max_length) |
+ FIELD_PREP(ETDES1_BUFFER1_SIZE_MASK,
+ buffer1_max_length));
+ } else {
+ p->des1 |= cpu_to_le32(FIELD_PREP(ETDES1_BUFFER1_SIZE_MASK,
+ len));
+ }
+}
+
+/* Normal descriptors */
+static inline void ndesc_rx_set_on_ring(struct dma_desc *p, int end, int bfsize)
+{
+ if (bfsize >= BUF_SIZE_2KiB) {
+ int bfsize2;
+
+ bfsize2 = min(bfsize - BUF_SIZE_2KiB + 1, BUF_SIZE_2KiB - 1);
+ p->des1 |= cpu_to_le32(FIELD_PREP(RDES1_BUFFER2_SIZE_MASK,
+ bfsize2));
+ }
+
+ if (end)
+ p->des1 |= cpu_to_le32(RDES1_END_RING);
+}
+
+static inline void ndesc_end_tx_desc_on_ring(struct dma_desc *p, int end)
+{
+ if (end)
+ p->des1 |= cpu_to_le32(TDES1_END_RING);
+ else
+ p->des1 &= cpu_to_le32(~TDES1_END_RING);
+}
+
+/* The maximum buffer 1 size is 2KiB - 1, limited by the mask width */
+static inline void norm_set_tx_desc_len_on_ring(struct dma_desc *p, int len)
+{
+ unsigned int buffer1_max_length = BUF_SIZE_2KiB - 1;
+
+ if (unlikely(len > buffer1_max_length)) {
+ p->des1 |= cpu_to_le32(FIELD_PREP(TDES1_BUFFER2_SIZE_MASK,
+ len - buffer1_max_length) |
+ FIELD_PREP(TDES1_BUFFER1_SIZE_MASK,
+ buffer1_max_length));
+ } else {
+ p->des1 |= cpu_to_le32(FIELD_PREP(TDES1_BUFFER1_SIZE_MASK,
+ len));
+ }
+}
+
+/* Specific functions used for Chain mode */
+
+/* Enhanced descriptors */
+static inline void ehn_desc_rx_set_on_chain(struct dma_desc *p)
+{
+ p->des1 |= cpu_to_le32(ERDES1_SECOND_ADDRESS_CHAINED);
+}
+
+static inline void enh_desc_end_tx_desc_on_chain(struct dma_desc *p)
+{
+ p->des0 |= cpu_to_le32(ETDES0_SECOND_ADDRESS_CHAINED);
+}
+
+static inline void enh_set_tx_desc_len_on_chain(struct dma_desc *p, int len)
+{
+ p->des1 |= cpu_to_le32(len & ETDES1_BUFFER1_SIZE_MASK);
+}
+
+/* Normal descriptors */
+static inline void ndesc_rx_set_on_chain(struct dma_desc *p, int end)
+{
+ p->des1 |= cpu_to_le32(RDES1_SECOND_ADDRESS_CHAINED);
+}
+
+static inline void ndesc_tx_set_on_chain(struct dma_desc *p)
+{
+ p->des1 |= cpu_to_le32(TDES1_SECOND_ADDRESS_CHAINED);
+}
+
+static inline void norm_set_tx_desc_len_on_chain(struct dma_desc *p, int len)
+{
+ p->des1 |= cpu_to_le32(len & TDES1_BUFFER1_SIZE_MASK);
+}
+#endif /* __DESC_COM_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-anarion.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-anarion.c
new file mode 100644
index 000000000..5e0fc3176
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-anarion.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Adaptrum Anarion DWMAC glue layer
+ *
+ * Copyright (C) 2017, Adaptrum, Inc.
+ * (Written by Alexandru Gagniuc <alex.g at adaptrum.com> for Adaptrum, Inc.)
+ */
+
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/stmmac.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+#define GMAC_RESET_CONTROL_REG 0
+#define GMAC_SW_CONFIG_REG 4
+#define GMAC_CONFIG_INTF_SEL_MASK (0x7 << 0)
+#define GMAC_CONFIG_INTF_RGMII (0x1 << 0)
+
+struct anarion_gmac {
+ void __iomem *ctl_block;
+ uint32_t phy_intf_sel;
+};
+
+static uint32_t gmac_read_reg(struct anarion_gmac *gmac, uint8_t reg)
+{
+ return readl(gmac->ctl_block + reg);
+};
+
+static void gmac_write_reg(struct anarion_gmac *gmac, uint8_t reg, uint32_t val)
+{
+ writel(val, gmac->ctl_block + reg);
+}
+
+static int anarion_gmac_init(struct device *dev, void *priv)
+{
+ uint32_t sw_config;
+ struct anarion_gmac *gmac = priv;
+
+ /* Reset logic, configure interface mode, then release reset. SIMPLE! */
+ gmac_write_reg(gmac, GMAC_RESET_CONTROL_REG, 1);
+
+ sw_config = gmac_read_reg(gmac, GMAC_SW_CONFIG_REG);
+ sw_config &= ~GMAC_CONFIG_INTF_SEL_MASK;
+ sw_config |= (gmac->phy_intf_sel & GMAC_CONFIG_INTF_SEL_MASK);
+ gmac_write_reg(gmac, GMAC_SW_CONFIG_REG, sw_config);
+
+ gmac_write_reg(gmac, GMAC_RESET_CONTROL_REG, 0);
+
+ return 0;
+}
+
+static void anarion_gmac_exit(struct device *dev, void *priv)
+{
+ struct anarion_gmac *gmac = priv;
+
+ gmac_write_reg(gmac, GMAC_RESET_CONTROL_REG, 1);
+}
+
+static struct anarion_gmac *
+anarion_config_dt(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat)
+{
+ struct anarion_gmac *gmac;
+ void __iomem *ctl_block;
+
+ ctl_block = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(ctl_block)) {
+ dev_err(&pdev->dev, "Cannot get reset region (%pe)!\n",
+ ctl_block);
+ return ERR_CAST(ctl_block);
+ }
+
+ gmac = devm_kzalloc(&pdev->dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac)
+ return ERR_PTR(-ENOMEM);
+
+ gmac->ctl_block = ctl_block;
+
+ if (phy_interface_mode_is_rgmii(plat_dat->phy_interface)) {
+ gmac->phy_intf_sel = GMAC_CONFIG_INTF_RGMII;
+ } else {
+ dev_err(&pdev->dev, "Unsupported phy-mode (%s)\n",
+ phy_modes(plat_dat->phy_interface));
+ return ERR_PTR(-ENOTSUPP);
+ }
+
+ return gmac;
+}
+
+static int anarion_dwmac_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct anarion_gmac *gmac;
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ gmac = anarion_config_dt(pdev, plat_dat);
+ if (IS_ERR(gmac))
+ return PTR_ERR(gmac);
+
+ plat_dat->init = anarion_gmac_init;
+ plat_dat->exit = anarion_gmac_exit;
+ plat_dat->bsp_priv = gmac;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id anarion_dwmac_match[] = {
+ { .compatible = "adaptrum,anarion-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, anarion_dwmac_match);
+
+static struct platform_driver anarion_dwmac_driver = {
+ .probe = anarion_dwmac_probe,
+ .driver = {
+ .name = "anarion-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = anarion_dwmac_match,
+ },
+};
+module_platform_driver(anarion_dwmac_driver);
+
+MODULE_DESCRIPTION("Adaptrum Anarion DWMAC specific glue layer");
+MODULE_AUTHOR("Alexandru Gagniuc <mr.nuke.me@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c
new file mode 100644
index 000000000..b0c5d1eca
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Synopsys DWC Ethernet Quality-of-Service v4.10a linux driver
+ *
+ * Copyright (C) 2016 Joao Pinto <jpinto@synopsys.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+#include "dwmac4.h"
+
+struct tegra_eqos {
+ struct device *dev;
+ void __iomem *regs;
+
+ struct reset_control *rst;
+
+ struct gpio_desc *reset;
+};
+
+static int dwc_eth_dwmac_config_dt(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat)
+{
+ struct device *dev = &pdev->dev;
+ u32 burst_map = 0;
+
+ if (!plat_dat->axi) {
+ plat_dat->axi = devm_kzalloc(&pdev->dev,
+ sizeof(struct stmmac_axi),
+ GFP_KERNEL);
+
+ if (!plat_dat->axi)
+ return -ENOMEM;
+ }
+
+ plat_dat->axi->axi_lpi_en = device_property_read_bool(dev,
+ "snps,en-lpi");
+ if (device_property_read_u32(dev, "snps,write-requests",
+ &plat_dat->axi->axi_wr_osr_lmt)) {
+ /**
+ * Since the register has a reset value of 1, if property
+ * is missing, default to 1.
+ */
+ plat_dat->axi->axi_wr_osr_lmt = 1;
+ } else {
+ /**
+ * If property exists, to keep the behavior from dwc_eth_qos,
+ * subtract one after parsing.
+ */
+ plat_dat->axi->axi_wr_osr_lmt--;
+ }
+
+ if (device_property_read_u32(dev, "snps,read-requests",
+ &plat_dat->axi->axi_rd_osr_lmt)) {
+ /**
+ * Since the register has a reset value of 1, if property
+ * is missing, default to 1.
+ */
+ plat_dat->axi->axi_rd_osr_lmt = 1;
+ } else {
+ /**
+ * If property exists, to keep the behavior from dwc_eth_qos,
+ * subtract one after parsing.
+ */
+ plat_dat->axi->axi_rd_osr_lmt--;
+ }
+ device_property_read_u32(dev, "snps,burst-map", &burst_map);
+
+ plat_dat->axi->axi_blen_regval = FIELD_PREP(DMA_AXI_BLEN_MASK,
+ burst_map);
+
+ /* dwc-qos needs GMAC4, AAL, TSO and PMT */
+ plat_dat->core_type = DWMAC_CORE_GMAC4;
+ plat_dat->dma_cfg->aal = 1;
+ plat_dat->flags |= STMMAC_FLAG_TSO_EN;
+ plat_dat->pmt = true;
+
+ return 0;
+}
+
+static int dwc_qos_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *stmmac_res)
+{
+ plat_dat->pclk = stmmac_pltfr_find_clk(plat_dat, "phy_ref_clk");
+
+ return 0;
+}
+
+#define SDMEMCOMPPADCTRL 0x8800
+#define SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD BIT(31)
+
+#define AUTO_CAL_CONFIG 0x8804
+#define AUTO_CAL_CONFIG_START BIT(31)
+#define AUTO_CAL_CONFIG_ENABLE BIT(29)
+
+#define AUTO_CAL_STATUS 0x880c
+#define AUTO_CAL_STATUS_ACTIVE BIT(31)
+
+static void tegra_eqos_fix_speed(void *bsp_priv, phy_interface_t interface,
+ int speed, unsigned int mode)
+{
+ struct tegra_eqos *eqos = bsp_priv;
+ bool needs_calibration = false;
+ struct stmmac_priv *priv;
+ u32 value;
+ int err;
+
+ switch (speed) {
+ case SPEED_1000:
+ case SPEED_100:
+ needs_calibration = true;
+ fallthrough;
+
+ case SPEED_10:
+ break;
+
+ default:
+ dev_err(eqos->dev, "invalid speed %d\n", speed);
+ break;
+ }
+
+ if (needs_calibration) {
+ priv = netdev_priv(dev_get_drvdata(eqos->dev));
+
+ /* Calibration should be done with the MDIO bus idle */
+ stmmac_mdio_lock(priv);
+
+ /* calibrate */
+ value = readl(eqos->regs + SDMEMCOMPPADCTRL);
+ value |= SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD;
+ writel(value, eqos->regs + SDMEMCOMPPADCTRL);
+
+ udelay(1);
+
+ value = readl(eqos->regs + AUTO_CAL_CONFIG);
+ value |= AUTO_CAL_CONFIG_START | AUTO_CAL_CONFIG_ENABLE;
+ writel(value, eqos->regs + AUTO_CAL_CONFIG);
+
+ err = readl_poll_timeout_atomic(eqos->regs + AUTO_CAL_STATUS,
+ value,
+ value & AUTO_CAL_STATUS_ACTIVE,
+ 1, 10);
+ if (err < 0) {
+ dev_err(eqos->dev, "calibration did not start\n");
+ goto failed;
+ }
+
+ err = readl_poll_timeout_atomic(eqos->regs + AUTO_CAL_STATUS,
+ value,
+ (value & AUTO_CAL_STATUS_ACTIVE) == 0,
+ 20, 200);
+ if (err < 0) {
+ dev_err(eqos->dev, "calibration didn't finish\n");
+ goto failed;
+ }
+
+ failed:
+ value = readl(eqos->regs + SDMEMCOMPPADCTRL);
+ value &= ~SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD;
+ writel(value, eqos->regs + SDMEMCOMPPADCTRL);
+
+ stmmac_mdio_unlock(priv);
+ } else {
+ value = readl(eqos->regs + AUTO_CAL_CONFIG);
+ value &= ~AUTO_CAL_CONFIG_ENABLE;
+ writel(value, eqos->regs + AUTO_CAL_CONFIG);
+ }
+}
+
+static int tegra_eqos_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *res)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra_eqos *eqos;
+ int err;
+
+ eqos = devm_kzalloc(&pdev->dev, sizeof(*eqos), GFP_KERNEL);
+ if (!eqos)
+ return -ENOMEM;
+
+ eqos->dev = &pdev->dev;
+ eqos->regs = res->addr;
+
+ if (!is_of_node(dev->fwnode))
+ goto bypass_clk_reset_gpio;
+
+ plat_dat->clk_tx_i = stmmac_pltfr_find_clk(plat_dat, "tx");
+
+ eqos->reset = devm_gpiod_get(&pdev->dev, "phy-reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(eqos->reset)) {
+ err = PTR_ERR(eqos->reset);
+ return err;
+ }
+
+ usleep_range(2000, 4000);
+ gpiod_set_value(eqos->reset, 0);
+
+ /* MDIO bus was already reset just above */
+ plat_dat->mdio_bus_data->needs_reset = false;
+
+ eqos->rst = devm_reset_control_get(&pdev->dev, "eqos");
+ if (IS_ERR(eqos->rst)) {
+ err = PTR_ERR(eqos->rst);
+ goto reset_phy;
+ }
+
+ err = reset_control_assert(eqos->rst);
+ if (err < 0)
+ goto reset_phy;
+
+ usleep_range(2000, 4000);
+
+ err = reset_control_deassert(eqos->rst);
+ if (err < 0)
+ goto reset_phy;
+
+ usleep_range(2000, 4000);
+
+bypass_clk_reset_gpio:
+ plat_dat->fix_mac_speed = tegra_eqos_fix_speed;
+ plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+ plat_dat->bsp_priv = eqos;
+ plat_dat->flags |= STMMAC_FLAG_SPH_DISABLE |
+ STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP |
+ STMMAC_FLAG_USE_PHY_WOL;
+
+ return 0;
+
+reset_phy:
+ gpiod_set_value(eqos->reset, 1);
+
+ return err;
+}
+
+static void tegra_eqos_remove(struct platform_device *pdev)
+{
+ struct tegra_eqos *eqos = get_stmmac_bsp_priv(&pdev->dev);
+
+ reset_control_assert(eqos->rst);
+ gpiod_set_value(eqos->reset, 1);
+}
+
+struct dwc_eth_dwmac_data {
+ int (*probe)(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *res);
+ void (*remove)(struct platform_device *pdev);
+ const char *stmmac_clk_name;
+};
+
+static const struct dwc_eth_dwmac_data dwc_qos_data = {
+ .probe = dwc_qos_probe,
+ .stmmac_clk_name = "apb_pclk",
+};
+
+static const struct dwc_eth_dwmac_data tegra_eqos_data = {
+ .probe = tegra_eqos_probe,
+ .remove = tegra_eqos_remove,
+ .stmmac_clk_name = "slave_bus",
+};
+
+static const struct dwc_eth_dwmac_data fsd_eqos_data = {
+ .stmmac_clk_name = "slave_bus",
+};
+
+static int dwc_eth_dwmac_probe(struct platform_device *pdev)
+{
+ const struct dwc_eth_dwmac_data *data;
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ int ret;
+
+ data = device_get_match_data(&pdev->dev);
+
+ memset(&stmmac_res, 0, sizeof(struct stmmac_resources));
+
+ /**
+ * Since stmmac_platform supports name IRQ only, basic platform
+ * resource initialization is done in the glue logic.
+ */
+ stmmac_res.irq = platform_get_irq(pdev, 0);
+ if (stmmac_res.irq < 0)
+ return stmmac_res.irq;
+ stmmac_res.wol_irq = stmmac_res.irq;
+
+ stmmac_res.addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(stmmac_res.addr))
+ return PTR_ERR(stmmac_res.addr);
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &plat_dat->clks);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to retrieve and enable all required clocks\n");
+ plat_dat->num_clks = ret;
+
+ plat_dat->stmmac_clk = stmmac_pltfr_find_clk(plat_dat,
+ data->stmmac_clk_name);
+
+ if (data->probe)
+ ret = data->probe(pdev, plat_dat, &stmmac_res);
+ if (ret < 0) {
+ dev_err_probe(&pdev->dev, ret, "failed to probe subdriver\n");
+ return ret;
+ }
+
+ ret = dwc_eth_dwmac_config_dt(pdev, plat_dat);
+ if (ret)
+ goto remove;
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+ if (ret)
+ goto remove;
+
+ return ret;
+
+remove:
+ if (data->remove)
+ data->remove(pdev);
+
+ return ret;
+}
+
+static void dwc_eth_dwmac_remove(struct platform_device *pdev)
+{
+ const struct dwc_eth_dwmac_data *data = device_get_match_data(&pdev->dev);
+
+ stmmac_dvr_remove(&pdev->dev);
+
+ if (data->remove)
+ data->remove(pdev);
+}
+
+static const struct of_device_id dwc_eth_dwmac_match[] = {
+ { .compatible = "snps,dwc-qos-ethernet-4.10", .data = &dwc_qos_data },
+ { .compatible = "nvidia,tegra186-eqos", .data = &tegra_eqos_data },
+ { .compatible = "tesla,fsd-ethqos", .data = &fsd_eqos_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dwc_eth_dwmac_match);
+
+static struct platform_driver dwc_eth_dwmac_driver = {
+ .probe = dwc_eth_dwmac_probe,
+ .remove = dwc_eth_dwmac_remove,
+ .driver = {
+ .name = "dwc-eth-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = dwc_eth_dwmac_match,
+ },
+};
+module_platform_driver(dwc_eth_dwmac_driver);
+
+MODULE_AUTHOR("Joao Pinto <jpinto@synopsys.com>");
+MODULE_DESCRIPTION("Synopsys DWC Ethernet Quality-of-Service v4.10a driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c
new file mode 100644
index 000000000..4ac979d87
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Eswin DWC Ethernet linux driver
+ *
+ * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
+ *
+ * Authors:
+ * Zhi Li <lizhi2@eswincomputing.com>
+ * Shuang Liang <liangshuang@eswincomputing.com>
+ * Shangjuan Wei <weishangjuan@eswincomputing.com>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/pm_runtime.h>
+#include <linux/stmmac.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+
+#include "stmmac_platform.h"
+
+/* eth_phy_ctrl_offset eth0:0x100 */
+#define EIC7700_ETH_TX_CLK_SEL BIT(16)
+#define EIC7700_ETH_PHY_INTF_SELI BIT(0)
+
+/* eth_axi_lp_ctrl_offset eth0:0x108 */
+#define EIC7700_ETH_CSYSREQ_VAL BIT(0)
+
+/*
+ * TX/RX Clock Delay Bit Masks:
+ * - TX Delay: bits [14:8] — TX_CLK delay (unit: 0.02ns per bit)
+ * - RX Delay: bits [30:24] — RX_CLK delay (unit: 0.02ns per bit)
+ */
+#define EIC7700_ETH_TX_ADJ_DELAY GENMASK(14, 8)
+#define EIC7700_ETH_RX_ADJ_DELAY GENMASK(30, 24)
+
+#define EIC7700_MAX_DELAY_STEPS 0x7F
+#define EIC7700_DELAY_STEP_PS 20
+#define EIC7700_MAX_DELAY_PS \
+ (EIC7700_MAX_DELAY_STEPS * EIC7700_DELAY_STEP_PS)
+
+static const char * const eic7700_clk_names[] = {
+ "tx", "axi", "cfg",
+};
+
+struct eic7700_qos_priv {
+ struct plat_stmmacenet_data *plat_dat;
+ struct regmap *eic7700_hsp_regmap;
+ u32 eth_axi_lp_ctrl_offset;
+ u32 eth_phy_ctrl_offset;
+ u32 eth_clk_offset;
+ u32 eth_txd_offset;
+ u32 eth_rxd_offset;
+ u32 eth_clk_dly_param;
+ bool has_txd_offset;
+ bool has_rxd_offset;
+};
+
+static int eic7700_clks_config(void *priv, bool enabled)
+{
+ struct eic7700_qos_priv *dwc = (struct eic7700_qos_priv *)priv;
+ struct plat_stmmacenet_data *plat = dwc->plat_dat;
+ int ret = 0;
+
+ if (enabled)
+ ret = clk_bulk_prepare_enable(plat->num_clks, plat->clks);
+ else
+ clk_bulk_disable_unprepare(plat->num_clks, plat->clks);
+
+ return ret;
+}
+
+static int eic7700_dwmac_init(struct device *dev, void *priv)
+{
+ struct eic7700_qos_priv *dwc = priv;
+ int ret;
+
+ ret = eic7700_clks_config(dwc, true);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(dwc->eic7700_hsp_regmap,
+ dwc->eth_phy_ctrl_offset,
+ EIC7700_ETH_TX_CLK_SEL |
+ EIC7700_ETH_PHY_INTF_SELI);
+ if (ret) {
+ eic7700_clks_config(dwc, false);
+ return ret;
+ }
+
+ regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_axi_lp_ctrl_offset,
+ EIC7700_ETH_CSYSREQ_VAL);
+
+ if (dwc->has_txd_offset)
+ regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_txd_offset, 0);
+
+ if (dwc->has_rxd_offset)
+ regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_rxd_offset, 0);
+
+ regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset,
+ dwc->eth_clk_dly_param);
+
+ return 0;
+}
+
+static void eic7700_dwmac_exit(struct device *dev, void *priv)
+{
+ struct eic7700_qos_priv *dwc = priv;
+
+ eic7700_clks_config(dwc, false);
+}
+
+static int eic7700_dwmac_suspend(struct device *dev, void *priv)
+{
+ return pm_runtime_force_suspend(dev);
+}
+
+static int eic7700_dwmac_resume(struct device *dev, void *priv)
+{
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ dev_err(dev, "%s failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int eic7700_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct eic7700_qos_priv *dwc_priv;
+ u32 delay_ps, val;
+ int i, ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to get resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(&pdev->dev, PTR_ERR(plat_dat),
+ "dt configuration failed\n");
+
+ dwc_priv = devm_kzalloc(&pdev->dev, sizeof(*dwc_priv), GFP_KERNEL);
+ if (!dwc_priv)
+ return -ENOMEM;
+
+ /* Read rx-internal-delay-ps and update rx_clk delay */
+ if (!of_property_read_u32(pdev->dev.of_node,
+ "rx-internal-delay-ps", &delay_ps)) {
+ if (delay_ps % EIC7700_DELAY_STEP_PS)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "rx delay must be multiple of %dps\n",
+ EIC7700_DELAY_STEP_PS);
+
+ if (delay_ps > EIC7700_MAX_DELAY_PS)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "rx delay out of range\n");
+
+ val = delay_ps / EIC7700_DELAY_STEP_PS;
+
+ dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY;
+ dwc_priv->eth_clk_dly_param |=
+ FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val);
+ } else {
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "missing required property rx-internal-delay-ps\n");
+ }
+
+ /* Read tx-internal-delay-ps and update tx_clk delay */
+ if (!of_property_read_u32(pdev->dev.of_node,
+ "tx-internal-delay-ps", &delay_ps)) {
+ if (delay_ps % EIC7700_DELAY_STEP_PS)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "tx delay must be multiple of %dps\n",
+ EIC7700_DELAY_STEP_PS);
+
+ if (delay_ps > EIC7700_MAX_DELAY_PS)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "tx delay out of range\n");
+
+ val = delay_ps / EIC7700_DELAY_STEP_PS;
+
+ dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY;
+ dwc_priv->eth_clk_dly_param |=
+ FIELD_PREP(EIC7700_ETH_TX_ADJ_DELAY, val);
+ } else {
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "missing required property tx-internal-delay-ps\n");
+ }
+
+ dwc_priv->eic7700_hsp_regmap =
+ syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "eswin,hsp-sp-csr");
+ if (IS_ERR(dwc_priv->eic7700_hsp_regmap))
+ return dev_err_probe(&pdev->dev,
+ PTR_ERR(dwc_priv->eic7700_hsp_regmap),
+ "Failed to get hsp-sp-csr regmap\n");
+
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "eswin,hsp-sp-csr",
+ 1, &dwc_priv->eth_phy_ctrl_offset);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "can't get eth_phy_ctrl_offset\n");
+
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "eswin,hsp-sp-csr",
+ 2, &dwc_priv->eth_axi_lp_ctrl_offset);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "can't get eth_axi_lp_ctrl_offset\n");
+
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "eswin,hsp-sp-csr",
+ 3, &dwc_priv->eth_clk_offset);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "can't get eth_clk_offset\n");
+
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "eswin,hsp-sp-csr",
+ 4, &dwc_priv->eth_txd_offset);
+ if (!ret)
+ dwc_priv->has_txd_offset = true;
+
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "eswin,hsp-sp-csr",
+ 5, &dwc_priv->eth_rxd_offset);
+ if (!ret)
+ dwc_priv->has_rxd_offset = true;
+
+ plat_dat->num_clks = ARRAY_SIZE(eic7700_clk_names);
+ plat_dat->clks = devm_kcalloc(&pdev->dev,
+ plat_dat->num_clks,
+ sizeof(*plat_dat->clks),
+ GFP_KERNEL);
+ if (!plat_dat->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(eic7700_clk_names); i++)
+ plat_dat->clks[i].id = eic7700_clk_names[i];
+
+ ret = devm_clk_bulk_get_optional(&pdev->dev,
+ plat_dat->num_clks,
+ plat_dat->clks);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to get clocks\n");
+
+ plat_dat->clk_tx_i = stmmac_pltfr_find_clk(plat_dat, "tx");
+ plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+ plat_dat->clks_config = eic7700_clks_config;
+ plat_dat->bsp_priv = dwc_priv;
+ dwc_priv->plat_dat = plat_dat;
+ plat_dat->init = eic7700_dwmac_init;
+ plat_dat->exit = eic7700_dwmac_exit;
+ plat_dat->suspend = eic7700_dwmac_suspend;
+ plat_dat->resume = eic7700_dwmac_resume;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id eic7700_dwmac_match[] = {
+ { .compatible = "eswin,eic7700-qos-eth" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, eic7700_dwmac_match);
+
+static struct platform_driver eic7700_dwmac_driver = {
+ .probe = eic7700_dwmac_probe,
+ .driver = {
+ .name = "eic7700-eth-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = eic7700_dwmac_match,
+ },
+};
+module_platform_driver(eic7700_dwmac_driver);
+
+MODULE_AUTHOR("Zhi Li <lizhi2@eswincomputing.com>");
+MODULE_AUTHOR("Shuang Liang <liangshuang@eswincomputing.com>");
+MODULE_AUTHOR("Shangjuan Wei <weishangjuan@eswincomputing.com>");
+MODULE_DESCRIPTION("Eswin eic7700 qos ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c
new file mode 100644
index 000000000..b9218c07e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c
@@ -0,0 +1,80 @@
+/*
+ * Generic DWMAC platform driver
+ *
+ * Copyright (C) 2007-2011 STMicroelectronics Ltd
+ * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+static int dwmac_generic_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ if (pdev->dev.of_node) {
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat)) {
+ dev_err(&pdev->dev, "dt configuration failed\n");
+ return PTR_ERR(plat_dat);
+ }
+ } else {
+ plat_dat = dev_get_platdata(&pdev->dev);
+ if (!plat_dat) {
+ dev_err(&pdev->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ /* Set default value for multicast hash bins */
+ plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
+
+ /* Set default value for unicast filter entries */
+ plat_dat->unicast_filter_entries = 1;
+ }
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id dwmac_generic_match[] = {
+ { .compatible = "st,spear600-gmac"},
+ { .compatible = "snps,dwmac-3.40a"},
+ { .compatible = "snps,dwmac-3.50a"},
+ { .compatible = "snps,dwmac-3.610"},
+ { .compatible = "snps,dwmac-3.70a"},
+ { .compatible = "snps,dwmac-3.710"},
+ { .compatible = "snps,dwmac-3.72a"},
+ { .compatible = "snps,dwmac-4.00"},
+ { .compatible = "snps,dwmac-4.10a"},
+ { .compatible = "snps,dwmac"},
+ { .compatible = "snps,dwxgmac-2.10"},
+ { .compatible = "snps,dwxgmac"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, dwmac_generic_match);
+
+static struct platform_driver dwmac_generic_driver = {
+ .probe = dwmac_generic_probe,
+ .driver = {
+ .name = STMMAC_RESOURCE_NAME,
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = dwmac_generic_match,
+ },
+};
+module_platform_driver(dwmac_generic_driver);
+
+MODULE_DESCRIPTION("Generic dwmac driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-imx.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-imx.c
new file mode 100644
index 000000000..01260dbbb
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-imx.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwmac-imx.c - DWMAC Specific Glue layer for NXP imx8
+ *
+ * Copyright 2020 NXP
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define GPR_ENET_QOS_INTF_MODE_MASK GENMASK(21, 16)
+#define GPR_ENET_QOS_INTF_SEL_MASK GENMASK(20, 16)
+#define GPR_ENET_QOS_CLK_GEN_EN (0x1 << 19)
+#define GPR_ENET_QOS_CLK_TX_CLK_SEL (0x1 << 20)
+#define GPR_ENET_QOS_RGMII_EN (0x1 << 21)
+
+#define MX93_GPR_ENET_QOS_INTF_SEL_MASK GENMASK(3, 1)
+#define MX93_GPR_ENET_QOS_ENABLE BIT(0)
+
+#define MX93_ENET_CLK_SEL_OFFSET (4)
+#define MX93_ENET_QOS_CLK_TX_SEL_MASK BIT_MASK(0)
+
+#define DMA_BUS_MODE 0x00001000
+#define DMA_BUS_MODE_SFT_RESET (0x1 << 0)
+#define RMII_RESET_SPEED (0x3 << 14)
+#define CTRL_SPEED_MASK GENMASK(15, 14)
+
+struct imx_priv_data;
+
+struct imx_dwmac_ops {
+ u32 flags;
+ u8 addr_width;
+ bool mac_rgmii_txclk_auto_adj;
+
+ int (*fix_soc_reset)(struct stmmac_priv *priv);
+ int (*set_intf_mode)(struct imx_priv_data *dwmac, u8 phy_intf_sel);
+ void (*fix_mac_speed)(void *priv, phy_interface_t interface,
+ int speed, unsigned int mode);
+};
+
+struct imx_priv_data {
+ struct device *dev;
+ struct clk *clk_tx;
+ struct clk *clk_mem;
+ struct regmap *intf_regmap;
+ u32 intf_reg_off;
+ bool rmii_refclk_ext;
+ void __iomem *base_addr;
+
+ const struct imx_dwmac_ops *ops;
+ struct plat_stmmacenet_data *plat_dat;
+};
+
+static int imx8mp_set_intf_mode(struct imx_priv_data *dwmac, u8 phy_intf_sel)
+{
+ unsigned int val;
+
+ val = FIELD_PREP(GPR_ENET_QOS_INTF_SEL_MASK, phy_intf_sel) |
+ GPR_ENET_QOS_CLK_GEN_EN;
+
+ if (phy_intf_sel == PHY_INTF_SEL_RMII && !dwmac->rmii_refclk_ext)
+ val |= GPR_ENET_QOS_CLK_TX_CLK_SEL;
+ else if (phy_intf_sel == PHY_INTF_SEL_RGMII)
+ val |= GPR_ENET_QOS_RGMII_EN;
+
+ return regmap_update_bits(dwmac->intf_regmap, dwmac->intf_reg_off,
+ GPR_ENET_QOS_INTF_MODE_MASK, val);
+};
+
+static int
+imx8dxl_set_intf_mode(struct imx_priv_data *dwmac, u8 phy_intf_sel)
+{
+ /* TBD: depends on imx8dxl scu interfaces to be upstreamed */
+ return 0;
+}
+
+static int imx93_set_intf_mode(struct imx_priv_data *dwmac, u8 phy_intf_sel)
+{
+ unsigned int val;
+ int ret;
+
+ if (phy_intf_sel == PHY_INTF_SEL_RMII && dwmac->rmii_refclk_ext) {
+ ret = regmap_clear_bits(dwmac->intf_regmap,
+ dwmac->intf_reg_off +
+ MX93_ENET_CLK_SEL_OFFSET,
+ MX93_ENET_QOS_CLK_TX_SEL_MASK);
+ if (ret)
+ return ret;
+ }
+
+ val = FIELD_PREP(MX93_GPR_ENET_QOS_INTF_SEL_MASK, phy_intf_sel) |
+ MX93_GPR_ENET_QOS_ENABLE;
+
+ return regmap_update_bits(dwmac->intf_regmap, dwmac->intf_reg_off,
+ MX93_GPR_ENET_QOS_INTF_SEL_MASK |
+ MX93_GPR_ENET_QOS_ENABLE, val);
+};
+
+static int imx_dwmac_clks_config(void *priv, bool enabled)
+{
+ struct imx_priv_data *dwmac = priv;
+ int ret = 0;
+
+ if (enabled) {
+ ret = clk_prepare_enable(dwmac->clk_mem);
+ if (ret) {
+ dev_err(dwmac->dev, "mem clock enable failed\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dwmac->clk_tx);
+ if (ret) {
+ dev_err(dwmac->dev, "tx clock enable failed\n");
+ clk_disable_unprepare(dwmac->clk_mem);
+ return ret;
+ }
+ } else {
+ clk_disable_unprepare(dwmac->clk_tx);
+ clk_disable_unprepare(dwmac->clk_mem);
+ }
+
+ return ret;
+}
+
+static int imx_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel)
+{
+ struct imx_priv_data *dwmac = bsp_priv;
+
+ if (!dwmac->ops->set_intf_mode)
+ return 0;
+
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII)
+ return -EINVAL;
+
+ return dwmac->ops->set_intf_mode(dwmac, phy_intf_sel);
+}
+
+static int imx_dwmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ if (interface == PHY_INTERFACE_MODE_RMII ||
+ interface == PHY_INTERFACE_MODE_MII)
+ return 0;
+
+ return stmmac_set_clk_tx_rate(bsp_priv, clk_tx_i, interface, speed);
+}
+
+static void imx_dwmac_fix_speed(void *priv, phy_interface_t interface,
+ int speed, unsigned int mode)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct imx_priv_data *dwmac = priv;
+ long rate;
+ int err;
+
+ plat_dat = dwmac->plat_dat;
+
+ if (dwmac->ops->mac_rgmii_txclk_auto_adj ||
+ (plat_dat->phy_interface == PHY_INTERFACE_MODE_RMII) ||
+ (plat_dat->phy_interface == PHY_INTERFACE_MODE_MII))
+ return;
+
+ rate = rgmii_clock(speed);
+ if (rate < 0) {
+ dev_err(dwmac->dev, "invalid speed %d\n", speed);
+ return;
+ }
+
+ err = clk_set_rate(dwmac->clk_tx, rate);
+ if (err < 0)
+ dev_err(dwmac->dev, "failed to set tx rate %lu\n", rate);
+}
+
+static void imx93_dwmac_fix_speed(void *priv, phy_interface_t interface,
+ int speed, unsigned int mode)
+{
+ struct imx_priv_data *dwmac = priv;
+ unsigned int iface;
+ int ctrl, old_ctrl;
+
+ imx_dwmac_fix_speed(priv, interface, speed, mode);
+
+ if (!dwmac || mode != MLO_AN_FIXED)
+ return;
+
+ if (regmap_read(dwmac->intf_regmap, dwmac->intf_reg_off, &iface))
+ return;
+
+ if (FIELD_GET(MX93_GPR_ENET_QOS_INTF_SEL_MASK, iface) !=
+ PHY_INTF_SEL_RGMII)
+ return;
+
+ old_ctrl = readl(dwmac->base_addr + MAC_CTRL_REG);
+ ctrl = old_ctrl & ~CTRL_SPEED_MASK;
+ regmap_update_bits(dwmac->intf_regmap, dwmac->intf_reg_off,
+ MX93_GPR_ENET_QOS_INTF_SEL_MASK |
+ MX93_GPR_ENET_QOS_ENABLE, 0);
+ writel(ctrl, dwmac->base_addr + MAC_CTRL_REG);
+
+ /* Ensure the settings for CTRL are applied. */
+ readl(dwmac->base_addr + MAC_CTRL_REG);
+
+ usleep_range(10, 20);
+ iface &= MX93_GPR_ENET_QOS_INTF_SEL_MASK;
+ iface |= MX93_GPR_ENET_QOS_ENABLE;
+ regmap_update_bits(dwmac->intf_regmap, dwmac->intf_reg_off,
+ MX93_GPR_ENET_QOS_INTF_SEL_MASK |
+ MX93_GPR_ENET_QOS_ENABLE, iface);
+
+ writel(old_ctrl, dwmac->base_addr + MAC_CTRL_REG);
+}
+
+static int imx_dwmac_mx93_reset(struct stmmac_priv *priv)
+{
+ struct plat_stmmacenet_data *plat_dat = priv->plat;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 value;
+
+ /* DMA SW reset */
+ value = readl(ioaddr + DMA_BUS_MODE);
+ value |= DMA_BUS_MODE_SFT_RESET;
+ writel(value, ioaddr + DMA_BUS_MODE);
+
+ if (plat_dat->phy_interface == PHY_INTERFACE_MODE_RMII) {
+ usleep_range(100, 200);
+ writel(RMII_RESET_SPEED, ioaddr + MAC_CTRL_REG);
+ }
+
+ return readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+ !(value & DMA_BUS_MODE_SFT_RESET),
+ 10000, 1000000);
+}
+
+static int
+imx_dwmac_parse_dt(struct imx_priv_data *dwmac, struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ int err = 0;
+
+ dwmac->rmii_refclk_ext = of_property_read_bool(np, "snps,rmii_refclk_ext");
+
+ dwmac->clk_tx = devm_clk_get(dev, "tx");
+ if (IS_ERR(dwmac->clk_tx)) {
+ dev_err(dev, "failed to get tx clock\n");
+ return PTR_ERR(dwmac->clk_tx);
+ }
+
+ dwmac->clk_mem = NULL;
+
+ if (of_machine_is_compatible("fsl,imx8dxl") ||
+ of_machine_is_compatible("fsl,imx91") ||
+ of_machine_is_compatible("fsl,imx93")) {
+ dwmac->clk_mem = devm_clk_get(dev, "mem");
+ if (IS_ERR(dwmac->clk_mem)) {
+ dev_err(dev, "failed to get mem clock\n");
+ return PTR_ERR(dwmac->clk_mem);
+ }
+ }
+
+ if (of_machine_is_compatible("fsl,imx8mp") ||
+ of_machine_is_compatible("fsl,imx91") ||
+ of_machine_is_compatible("fsl,imx93")) {
+ /* Binding doc describes the property:
+ * is required by i.MX8MP, i.MX91, i.MX93.
+ * is optional for i.MX8DXL.
+ */
+ dwmac->intf_regmap =
+ syscon_regmap_lookup_by_phandle_args(np, "intf_mode", 1,
+ &dwmac->intf_reg_off);
+ if (IS_ERR(dwmac->intf_regmap))
+ return PTR_ERR(dwmac->intf_regmap);
+ }
+
+ return err;
+}
+
+static int imx_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct imx_priv_data *dwmac;
+ const struct imx_dwmac_ops *data;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "failed to get match data\n");
+ return -EINVAL;
+ }
+
+ dwmac->ops = data;
+ dwmac->dev = &pdev->dev;
+
+ ret = imx_dwmac_parse_dt(dwmac, &pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to parse OF data\n");
+ return ret;
+ }
+
+ plat_dat->flags |= data->flags;
+
+ /* Default TX Q0 to use TSO and rest TXQ for TBS */
+ for (int i = 1; i < plat_dat->tx_queues_to_use; i++)
+ plat_dat->tx_queues_cfg[i].tbs_en = 1;
+
+ plat_dat->host_dma_width = dwmac->ops->addr_width;
+ plat_dat->set_phy_intf_sel = imx_set_phy_intf_sel;
+ plat_dat->clks_config = imx_dwmac_clks_config;
+ plat_dat->bsp_priv = dwmac;
+ dwmac->plat_dat = plat_dat;
+ dwmac->base_addr = stmmac_res.addr;
+
+ ret = imx_dwmac_clks_config(dwmac, true);
+ if (ret)
+ return ret;
+
+ if (dwmac->ops->fix_mac_speed) {
+ plat_dat->fix_mac_speed = dwmac->ops->fix_mac_speed;
+ } else if (!dwmac->ops->mac_rgmii_txclk_auto_adj) {
+ plat_dat->clk_tx_i = dwmac->clk_tx;
+ plat_dat->set_clk_tx_rate = imx_dwmac_set_clk_tx_rate;
+ }
+
+ dwmac->plat_dat->fix_soc_reset = dwmac->ops->fix_soc_reset;
+
+ ret = stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+ if (ret)
+ imx_dwmac_clks_config(dwmac, false);
+
+ return ret;
+}
+
+static struct imx_dwmac_ops imx8mp_dwmac_data = {
+ .addr_width = 34,
+ .mac_rgmii_txclk_auto_adj = false,
+ .set_intf_mode = imx8mp_set_intf_mode,
+ .flags = STMMAC_FLAG_EEE_DISABLE |
+ STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY |
+ STMMAC_FLAG_KEEP_PREAMBLE_BEFORE_SFD,
+};
+
+static struct imx_dwmac_ops imx8dxl_dwmac_data = {
+ .addr_width = 32,
+ .mac_rgmii_txclk_auto_adj = true,
+ .set_intf_mode = imx8dxl_set_intf_mode,
+};
+
+static struct imx_dwmac_ops imx93_dwmac_data = {
+ .addr_width = 32,
+ .mac_rgmii_txclk_auto_adj = true,
+ .set_intf_mode = imx93_set_intf_mode,
+ .fix_soc_reset = imx_dwmac_mx93_reset,
+ .fix_mac_speed = imx93_dwmac_fix_speed,
+};
+
+static const struct of_device_id imx_dwmac_match[] = {
+ { .compatible = "nxp,imx8mp-dwmac-eqos", .data = &imx8mp_dwmac_data },
+ { .compatible = "nxp,imx8dxl-dwmac-eqos", .data = &imx8dxl_dwmac_data },
+ { .compatible = "nxp,imx93-dwmac-eqos", .data = &imx93_dwmac_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx_dwmac_match);
+
+static struct platform_driver imx_dwmac_driver = {
+ .probe = imx_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "imx-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = imx_dwmac_match,
+ },
+};
+module_platform_driver(imx_dwmac_driver);
+
+MODULE_AUTHOR("NXP");
+MODULE_DESCRIPTION("NXP imx8 DWMAC Specific Glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-ingenic.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-ingenic.c
new file mode 100644
index 000000000..8e4a30c11
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-ingenic.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwmac-ingenic.c - Ingenic SoCs DWMAC specific glue layer
+ *
+ * Copyright (c) 2021 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define MACPHYC_TXCLK_SEL_MASK GENMASK(31, 31)
+#define MACPHYC_TXCLK_SEL_OUTPUT 0x1
+#define MACPHYC_TXCLK_SEL_INPUT 0x0
+#define MACPHYC_MODE_SEL_MASK GENMASK(31, 31)
+#define MACPHYC_MODE_SEL_RMII 0x0
+#define MACPHYC_TX_SEL_MASK GENMASK(19, 19)
+#define MACPHYC_TX_SEL_ORIGIN 0x0
+#define MACPHYC_TX_SEL_DELAY 0x1
+#define MACPHYC_TX_DELAY_MASK GENMASK(18, 12)
+#define MACPHYC_RX_SEL_MASK GENMASK(11, 11)
+#define MACPHYC_RX_SEL_ORIGIN 0x0
+#define MACPHYC_RX_SEL_DELAY 0x1
+#define MACPHYC_RX_DELAY_MASK GENMASK(10, 4)
+#define MACPHYC_SOFT_RST_MASK GENMASK(3, 3)
+#define MACPHYC_PHY_INFT_MASK GENMASK(2, 0)
+
+#define MACPHYC_TX_DELAY_PS_MAX 2496
+#define MACPHYC_TX_DELAY_PS_MIN 20
+
+#define MACPHYC_RX_DELAY_PS_MAX 2496
+#define MACPHYC_RX_DELAY_PS_MIN 20
+
+enum ingenic_mac_version {
+ ID_JZ4775,
+ ID_X1000,
+ ID_X1600,
+ ID_X1830,
+ ID_X2000,
+};
+
+struct ingenic_mac {
+ const struct ingenic_soc_info *soc_info;
+ struct plat_stmmacenet_data *plat_dat;
+ struct device *dev;
+ struct regmap *regmap;
+
+ int rx_delay;
+ int tx_delay;
+};
+
+struct ingenic_soc_info {
+ enum ingenic_mac_version version;
+ u32 mask;
+
+ int (*set_mode)(struct ingenic_mac *mac, u8 phy_intf_sel);
+
+ u8 valid_phy_intf_sel;
+};
+
+static int jz4775_mac_set_mode(struct ingenic_mac *mac, u8 phy_intf_sel)
+{
+ unsigned int val;
+
+ val = FIELD_PREP(MACPHYC_PHY_INFT_MASK, phy_intf_sel) |
+ FIELD_PREP(MACPHYC_TXCLK_SEL_MASK, MACPHYC_TXCLK_SEL_INPUT);
+
+ /* Update MAC PHY control register */
+ return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val);
+}
+
+static int x1000_mac_set_mode(struct ingenic_mac *mac, u8 phy_intf_sel)
+{
+ /* Update MAC PHY control register */
+ return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, 0);
+}
+
+static int x1600_mac_set_mode(struct ingenic_mac *mac, u8 phy_intf_sel)
+{
+ unsigned int val;
+
+ val = FIELD_PREP(MACPHYC_PHY_INFT_MASK, phy_intf_sel);
+
+ /* Update MAC PHY control register */
+ return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val);
+}
+
+static int x1830_mac_set_mode(struct ingenic_mac *mac, u8 phy_intf_sel)
+{
+ unsigned int val;
+
+ val = FIELD_PREP(MACPHYC_MODE_SEL_MASK, MACPHYC_MODE_SEL_RMII) |
+ FIELD_PREP(MACPHYC_PHY_INFT_MASK, phy_intf_sel);
+
+ /* Update MAC PHY control register */
+ return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val);
+}
+
+static int x2000_mac_set_mode(struct ingenic_mac *mac, u8 phy_intf_sel)
+{
+ unsigned int val;
+
+ val = FIELD_PREP(MACPHYC_PHY_INFT_MASK, phy_intf_sel);
+
+ if (phy_intf_sel == PHY_INTF_SEL_RMII) {
+ val |= FIELD_PREP(MACPHYC_TX_SEL_MASK, MACPHYC_TX_SEL_ORIGIN) |
+ FIELD_PREP(MACPHYC_RX_SEL_MASK, MACPHYC_RX_SEL_ORIGIN);
+ } else if (phy_intf_sel == PHY_INTF_SEL_RGMII) {
+ if (mac->tx_delay == 0)
+ val |= FIELD_PREP(MACPHYC_TX_SEL_MASK, MACPHYC_TX_SEL_ORIGIN);
+ else
+ val |= FIELD_PREP(MACPHYC_TX_SEL_MASK, MACPHYC_TX_SEL_DELAY) |
+ FIELD_PREP(MACPHYC_TX_DELAY_MASK, (mac->tx_delay + 9750) / 19500 - 1);
+
+ if (mac->rx_delay == 0)
+ val |= FIELD_PREP(MACPHYC_RX_SEL_MASK, MACPHYC_RX_SEL_ORIGIN);
+ else
+ val |= FIELD_PREP(MACPHYC_RX_SEL_MASK, MACPHYC_RX_SEL_DELAY) |
+ FIELD_PREP(MACPHYC_RX_DELAY_MASK, (mac->rx_delay + 9750) / 19500 - 1);
+ }
+
+ /* Update MAC PHY control register */
+ return regmap_update_bits(mac->regmap, 0, mac->soc_info->mask, val);
+}
+
+static int ingenic_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel)
+{
+ struct ingenic_mac *mac = bsp_priv;
+
+ if (!mac->soc_info->set_mode)
+ return 0;
+
+ if (phy_intf_sel >= BITS_PER_BYTE ||
+ ~mac->soc_info->valid_phy_intf_sel & BIT(phy_intf_sel))
+ return -EINVAL;
+
+ dev_dbg(mac->dev, "MAC PHY control register: interface %s\n",
+ phy_modes(mac->plat_dat->phy_interface));
+
+ return mac->soc_info->set_mode(mac, phy_intf_sel);
+}
+
+static int ingenic_mac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct ingenic_mac *mac;
+ const struct ingenic_soc_info *data;
+ u32 tx_delay_ps, rx_delay_ps;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ mac = devm_kzalloc(&pdev->dev, sizeof(*mac), GFP_KERNEL);
+ if (!mac)
+ return -ENOMEM;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "No of match data provided\n");
+ return -EINVAL;
+ }
+
+ /* Get MAC PHY control register */
+ mac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "mode-reg");
+ if (IS_ERR(mac->regmap)) {
+ dev_err(&pdev->dev, "%s: Failed to get syscon regmap\n", __func__);
+ return PTR_ERR(mac->regmap);
+ }
+
+ if (!of_property_read_u32(pdev->dev.of_node, "tx-clk-delay-ps", &tx_delay_ps)) {
+ if (tx_delay_ps >= MACPHYC_TX_DELAY_PS_MIN &&
+ tx_delay_ps <= MACPHYC_TX_DELAY_PS_MAX) {
+ mac->tx_delay = tx_delay_ps * 1000;
+ } else {
+ dev_err(&pdev->dev, "Invalid TX clock delay: %dps\n", tx_delay_ps);
+ return -EINVAL;
+ }
+ }
+
+ if (!of_property_read_u32(pdev->dev.of_node, "rx-clk-delay-ps", &rx_delay_ps)) {
+ if (rx_delay_ps >= MACPHYC_RX_DELAY_PS_MIN &&
+ rx_delay_ps <= MACPHYC_RX_DELAY_PS_MAX) {
+ mac->rx_delay = rx_delay_ps * 1000;
+ } else {
+ dev_err(&pdev->dev, "Invalid RX clock delay: %dps\n", rx_delay_ps);
+ return -EINVAL;
+ }
+ }
+
+ mac->soc_info = data;
+ mac->dev = &pdev->dev;
+ mac->plat_dat = plat_dat;
+
+ plat_dat->bsp_priv = mac;
+ plat_dat->set_phy_intf_sel = ingenic_set_phy_intf_sel;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static struct ingenic_soc_info jz4775_soc_info = {
+ .version = ID_JZ4775,
+ .mask = MACPHYC_TXCLK_SEL_MASK | MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK,
+
+ .set_mode = jz4775_mac_set_mode,
+ .valid_phy_intf_sel = BIT(PHY_INTF_SEL_GMII_MII) |
+ BIT(PHY_INTF_SEL_RGMII) |
+ BIT(PHY_INTF_SEL_RMII),
+};
+
+static struct ingenic_soc_info x1000_soc_info = {
+ .version = ID_X1000,
+ .mask = MACPHYC_SOFT_RST_MASK,
+
+ .set_mode = x1000_mac_set_mode,
+ .valid_phy_intf_sel = BIT(PHY_INTF_SEL_RMII),
+};
+
+static struct ingenic_soc_info x1600_soc_info = {
+ .version = ID_X1600,
+ .mask = MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK,
+
+ .set_mode = x1600_mac_set_mode,
+ .valid_phy_intf_sel = BIT(PHY_INTF_SEL_RMII),
+};
+
+static struct ingenic_soc_info x1830_soc_info = {
+ .version = ID_X1830,
+ .mask = MACPHYC_MODE_SEL_MASK | MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK,
+
+ .set_mode = x1830_mac_set_mode,
+ .valid_phy_intf_sel = BIT(PHY_INTF_SEL_RMII),
+};
+
+static struct ingenic_soc_info x2000_soc_info = {
+ .version = ID_X2000,
+ .mask = MACPHYC_TX_SEL_MASK | MACPHYC_TX_DELAY_MASK | MACPHYC_RX_SEL_MASK |
+ MACPHYC_RX_DELAY_MASK | MACPHYC_SOFT_RST_MASK | MACPHYC_PHY_INFT_MASK,
+
+ .set_mode = x2000_mac_set_mode,
+ .valid_phy_intf_sel = BIT(PHY_INTF_SEL_RGMII) |
+ BIT(PHY_INTF_SEL_RMII),
+};
+
+static const struct of_device_id ingenic_mac_of_matches[] = {
+ { .compatible = "ingenic,jz4775-mac", .data = &jz4775_soc_info },
+ { .compatible = "ingenic,x1000-mac", .data = &x1000_soc_info },
+ { .compatible = "ingenic,x1600-mac", .data = &x1600_soc_info },
+ { .compatible = "ingenic,x1830-mac", .data = &x1830_soc_info },
+ { .compatible = "ingenic,x2000-mac", .data = &x2000_soc_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ingenic_mac_of_matches);
+
+static struct platform_driver ingenic_mac_driver = {
+ .probe = ingenic_mac_probe,
+ .driver = {
+ .name = "ingenic-mac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = ingenic_mac_of_matches,
+ },
+};
+module_platform_driver(ingenic_mac_driver);
+
+MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>");
+MODULE_DESCRIPTION("Ingenic SoCs DWMAC specific glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c
new file mode 100644
index 000000000..4ea7b0a80
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Intel DWMAC platform driver
+ *
+ * Copyright(C) 2020 Intel Corporation
+ */
+
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/stmmac.h>
+
+#include "dwmac4.h"
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+struct intel_dwmac {
+ struct device *dev;
+ struct clk *tx_clk;
+ const struct intel_dwmac_data *data;
+};
+
+struct intel_dwmac_data {
+ unsigned long ptp_ref_clk_rate;
+ unsigned long tx_clk_rate;
+ bool tx_clk_en;
+};
+
+static const struct intel_dwmac_data kmb_data = {
+ .ptp_ref_clk_rate = 200000000,
+ .tx_clk_rate = 125000000,
+ .tx_clk_en = true,
+};
+
+static const struct of_device_id intel_eth_plat_match[] = {
+ { .compatible = "intel,keembay-dwmac", .data = &kmb_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, intel_eth_plat_match);
+
+static int intel_eth_plat_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct intel_dwmac *dwmac;
+ unsigned long rate;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat)) {
+ dev_err(&pdev->dev, "dt configuration failed\n");
+ return PTR_ERR(plat_dat);
+ }
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ dwmac->dev = &pdev->dev;
+ dwmac->tx_clk = NULL;
+
+ /*
+ * This cannot return NULL at this point because the driver’s
+ * compatibility with the device has already been validated in
+ * platform_match().
+ */
+ dwmac->data = device_get_match_data(&pdev->dev);
+
+ /* Enable TX clock */
+ if (dwmac->data->tx_clk_en) {
+ dwmac->tx_clk = devm_clk_get(&pdev->dev, "tx_clk");
+ if (IS_ERR(dwmac->tx_clk))
+ return PTR_ERR(dwmac->tx_clk);
+
+ ret = clk_prepare_enable(dwmac->tx_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to enable tx_clk\n");
+ return ret;
+ }
+
+ /* Check and configure TX clock rate */
+ rate = clk_get_rate(dwmac->tx_clk);
+ if (dwmac->data->tx_clk_rate &&
+ rate != dwmac->data->tx_clk_rate) {
+ rate = dwmac->data->tx_clk_rate;
+ ret = clk_set_rate(dwmac->tx_clk, rate);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to set tx_clk\n");
+ goto err_tx_clk_disable;
+ }
+ }
+
+ /* Check and configure PTP ref clock rate */
+ rate = clk_get_rate(plat_dat->clk_ptp_ref);
+ if (dwmac->data->ptp_ref_clk_rate &&
+ rate != dwmac->data->ptp_ref_clk_rate) {
+ rate = dwmac->data->ptp_ref_clk_rate;
+ ret = clk_set_rate(plat_dat->clk_ptp_ref, rate);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to set clk_ptp_ref\n");
+ goto err_tx_clk_disable;
+ }
+ }
+ }
+
+ plat_dat->clk_tx_i = dwmac->tx_clk;
+ plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+ plat_dat->bsp_priv = dwmac;
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+ if (ret)
+ goto err_tx_clk_disable;
+
+ return 0;
+
+err_tx_clk_disable:
+ if (dwmac->data->tx_clk_en)
+ clk_disable_unprepare(dwmac->tx_clk);
+ return ret;
+}
+
+static void intel_eth_plat_remove(struct platform_device *pdev)
+{
+ struct intel_dwmac *dwmac = get_stmmac_bsp_priv(&pdev->dev);
+
+ stmmac_pltfr_remove(pdev);
+ if (dwmac->data->tx_clk_en)
+ clk_disable_unprepare(dwmac->tx_clk);
+}
+
+static struct platform_driver intel_eth_plat_driver = {
+ .probe = intel_eth_plat_probe,
+ .remove = intel_eth_plat_remove,
+ .driver = {
+ .name = "intel-eth-plat",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = intel_eth_plat_match,
+ },
+};
+module_platform_driver(intel_eth_plat_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel DWMAC platform driver");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
new file mode 100644
index 000000000..b8d467ba6
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
@@ -0,0 +1,1398 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020, Intel Corporation
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/pci.h>
+#include <linux/dmi.h>
+#include <linux/platform_data/x86/intel_pmc_ipc.h>
+#include <asm/cpuid/api.h>
+#include "dwmac-intel.h"
+#include "dwmac4.h"
+#include "stmmac.h"
+#include "stmmac_ptp.h"
+
+struct pmc_serdes_regs {
+ u8 index;
+ u32 val;
+};
+
+struct pmc_serdes_reg_info {
+ const struct pmc_serdes_regs *regs;
+ u8 num_regs;
+};
+
+struct intel_priv_data {
+ int mdio_adhoc_addr; /* mdio address for serdes & etc */
+ unsigned long crossts_adj;
+ bool is_pse;
+ const int *tsn_lane_regs;
+ int max_tsn_lane_regs;
+ struct pmc_serdes_reg_info pid_1g;
+ struct pmc_serdes_reg_info pid_2p5g;
+};
+
+/* This struct is used to associate PCI Function of MAC controller on a board,
+ * discovered via DMI, with the address of PHY connected to the MAC. The
+ * negative value of the address means that MAC controller is not connected
+ * with PHY.
+ */
+struct stmmac_pci_func_data {
+ unsigned int func;
+ int phy_addr;
+};
+
+struct stmmac_pci_dmi_data {
+ const struct stmmac_pci_func_data *func;
+ size_t nfuncs;
+};
+
+struct stmmac_pci_info {
+ int (*setup)(struct pci_dev *pdev, struct plat_stmmacenet_data *plat);
+};
+
+static const struct pmc_serdes_regs pid_modphy3_1g_regs[] = {
+ { PID_MODPHY3_B_MODPHY_PCR_LCPLL_DWORD0, B_MODPHY_PCR_LCPLL_DWORD0_1G },
+ { PID_MODPHY3_N_MODPHY_PCR_LCPLL_DWORD2, N_MODPHY_PCR_LCPLL_DWORD2_1G },
+ { PID_MODPHY3_N_MODPHY_PCR_LCPLL_DWORD7, N_MODPHY_PCR_LCPLL_DWORD7_1G },
+ { PID_MODPHY3_N_MODPHY_PCR_LPPLL_DWORD10, N_MODPHY_PCR_LPPLL_DWORD10_1G },
+ { PID_MODPHY3_N_MODPHY_PCR_CMN_ANA_DWORD30, N_MODPHY_PCR_CMN_ANA_DWORD30_1G },
+ {}
+};
+
+static const struct pmc_serdes_regs pid_modphy3_2p5g_regs[] = {
+ { PID_MODPHY3_B_MODPHY_PCR_LCPLL_DWORD0, B_MODPHY_PCR_LCPLL_DWORD0_2P5G },
+ { PID_MODPHY3_N_MODPHY_PCR_LCPLL_DWORD2, N_MODPHY_PCR_LCPLL_DWORD2_2P5G },
+ { PID_MODPHY3_N_MODPHY_PCR_LCPLL_DWORD7, N_MODPHY_PCR_LCPLL_DWORD7_2P5G },
+ { PID_MODPHY3_N_MODPHY_PCR_LPPLL_DWORD10, N_MODPHY_PCR_LPPLL_DWORD10_2P5G },
+ { PID_MODPHY3_N_MODPHY_PCR_CMN_ANA_DWORD30, N_MODPHY_PCR_CMN_ANA_DWORD30_2P5G },
+ {}
+};
+
+static const struct pmc_serdes_regs pid_modphy1_1g_regs[] = {
+ { PID_MODPHY1_B_MODPHY_PCR_LCPLL_DWORD0, B_MODPHY_PCR_LCPLL_DWORD0_1G },
+ { PID_MODPHY1_N_MODPHY_PCR_LCPLL_DWORD2, N_MODPHY_PCR_LCPLL_DWORD2_1G },
+ { PID_MODPHY1_N_MODPHY_PCR_LCPLL_DWORD7, N_MODPHY_PCR_LCPLL_DWORD7_1G },
+ { PID_MODPHY1_N_MODPHY_PCR_LPPLL_DWORD10, N_MODPHY_PCR_LPPLL_DWORD10_1G },
+ { PID_MODPHY1_N_MODPHY_PCR_CMN_ANA_DWORD30, N_MODPHY_PCR_CMN_ANA_DWORD30_1G },
+ {}
+};
+
+static const struct pmc_serdes_regs pid_modphy1_2p5g_regs[] = {
+ { PID_MODPHY1_B_MODPHY_PCR_LCPLL_DWORD0, B_MODPHY_PCR_LCPLL_DWORD0_2P5G },
+ { PID_MODPHY1_N_MODPHY_PCR_LCPLL_DWORD2, N_MODPHY_PCR_LCPLL_DWORD2_2P5G },
+ { PID_MODPHY1_N_MODPHY_PCR_LCPLL_DWORD7, N_MODPHY_PCR_LCPLL_DWORD7_2P5G },
+ { PID_MODPHY1_N_MODPHY_PCR_LPPLL_DWORD10, N_MODPHY_PCR_LPPLL_DWORD10_2P5G },
+ { PID_MODPHY1_N_MODPHY_PCR_CMN_ANA_DWORD30, N_MODPHY_PCR_CMN_ANA_DWORD30_2P5G },
+ {}
+};
+
+static const int ehl_tsn_lane_regs[] = {7, 8, 9, 10, 11};
+static const int adln_tsn_lane_regs[] = {6};
+
+static int stmmac_pci_find_phy_addr(struct pci_dev *pdev,
+ const struct dmi_system_id *dmi_list)
+{
+ const struct stmmac_pci_func_data *func_data;
+ const struct stmmac_pci_dmi_data *dmi_data;
+ const struct dmi_system_id *dmi_id;
+ int func = PCI_FUNC(pdev->devfn);
+ size_t n;
+
+ dmi_id = dmi_first_match(dmi_list);
+ if (!dmi_id)
+ return -ENODEV;
+
+ dmi_data = dmi_id->driver_data;
+ func_data = dmi_data->func;
+
+ for (n = 0; n < dmi_data->nfuncs; n++, func_data++)
+ if (func_data->func == func)
+ return func_data->phy_addr;
+
+ return -ENODEV;
+}
+
+static int serdes_status_poll(struct stmmac_priv *priv, int phyaddr,
+ int phyreg, u32 mask, u32 val)
+{
+ unsigned int retries = 10;
+ int val_rd;
+
+ do {
+ val_rd = mdiobus_read(priv->mii, phyaddr, phyreg);
+ if ((val_rd & mask) == (val & mask))
+ return 0;
+ udelay(POLL_DELAY_US);
+ } while (--retries);
+
+ return -ETIMEDOUT;
+}
+
+static int intel_serdes_powerup(struct net_device *ndev, void *priv_data)
+{
+ struct intel_priv_data *intel_priv = priv_data;
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int serdes_phy_addr = 0;
+ u32 data = 0;
+
+ if (!intel_priv->mdio_adhoc_addr)
+ return 0;
+
+ serdes_phy_addr = intel_priv->mdio_adhoc_addr;
+
+ /* Set the serdes rate and the PCLK rate */
+ data = mdiobus_read(priv->mii, serdes_phy_addr,
+ SERDES_GCR0);
+
+ data &= ~SERDES_RATE_MASK;
+ data &= ~SERDES_PCLK_MASK;
+
+ if (priv->plat->phy_interface == PHY_INTERFACE_MODE_2500BASEX)
+ data |= SERDES_RATE_PCIE_GEN2 << SERDES_RATE_PCIE_SHIFT |
+ SERDES_PCLK_37p5MHZ << SERDES_PCLK_SHIFT;
+ else
+ data |= SERDES_RATE_PCIE_GEN1 << SERDES_RATE_PCIE_SHIFT |
+ SERDES_PCLK_70MHZ << SERDES_PCLK_SHIFT;
+
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* assert clk_req */
+ data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
+ data |= SERDES_PLL_CLK;
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* check for clk_ack assertion */
+ data = serdes_status_poll(priv, serdes_phy_addr,
+ SERDES_GSR0,
+ SERDES_PLL_CLK,
+ SERDES_PLL_CLK);
+
+ if (data) {
+ dev_err(priv->device, "Serdes PLL clk request timeout\n");
+ return data;
+ }
+
+ /* assert lane reset */
+ data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
+ data |= SERDES_RST;
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* check for assert lane reset reflection */
+ data = serdes_status_poll(priv, serdes_phy_addr,
+ SERDES_GSR0,
+ SERDES_RST,
+ SERDES_RST);
+
+ if (data) {
+ dev_err(priv->device, "Serdes assert lane reset timeout\n");
+ return data;
+ }
+
+ /* move power state to P0 */
+ data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
+
+ data &= ~SERDES_PWR_ST_MASK;
+ data |= SERDES_PWR_ST_P0 << SERDES_PWR_ST_SHIFT;
+
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* Check for P0 state */
+ data = serdes_status_poll(priv, serdes_phy_addr,
+ SERDES_GSR0,
+ SERDES_PWR_ST_MASK,
+ SERDES_PWR_ST_P0 << SERDES_PWR_ST_SHIFT);
+
+ if (data) {
+ dev_err(priv->device, "Serdes power state P0 timeout.\n");
+ return data;
+ }
+
+ /* PSE only - ungate SGMII PHY Rx Clock */
+ if (intel_priv->is_pse)
+ mdiobus_modify(priv->mii, serdes_phy_addr, SERDES_GCR0,
+ 0, SERDES_PHY_RX_CLK);
+
+ return 0;
+}
+
+static void intel_serdes_powerdown(struct net_device *ndev, void *intel_data)
+{
+ struct intel_priv_data *intel_priv = intel_data;
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int serdes_phy_addr = 0;
+ u32 data = 0;
+
+ if (!intel_priv->mdio_adhoc_addr)
+ return;
+
+ serdes_phy_addr = intel_priv->mdio_adhoc_addr;
+
+ /* PSE only - gate SGMII PHY Rx Clock */
+ if (intel_priv->is_pse)
+ mdiobus_modify(priv->mii, serdes_phy_addr, SERDES_GCR0,
+ SERDES_PHY_RX_CLK, 0);
+
+ /* move power state to P3 */
+ data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
+
+ data &= ~SERDES_PWR_ST_MASK;
+ data |= SERDES_PWR_ST_P3 << SERDES_PWR_ST_SHIFT;
+
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* Check for P3 state */
+ data = serdes_status_poll(priv, serdes_phy_addr,
+ SERDES_GSR0,
+ SERDES_PWR_ST_MASK,
+ SERDES_PWR_ST_P3 << SERDES_PWR_ST_SHIFT);
+
+ if (data) {
+ dev_err(priv->device, "Serdes power state P3 timeout\n");
+ return;
+ }
+
+ /* de-assert clk_req */
+ data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
+ data &= ~SERDES_PLL_CLK;
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* check for clk_ack de-assert */
+ data = serdes_status_poll(priv, serdes_phy_addr,
+ SERDES_GSR0,
+ SERDES_PLL_CLK,
+ (u32)~SERDES_PLL_CLK);
+
+ if (data) {
+ dev_err(priv->device, "Serdes PLL clk de-assert timeout\n");
+ return;
+ }
+
+ /* de-assert lane reset */
+ data = mdiobus_read(priv->mii, serdes_phy_addr, SERDES_GCR0);
+ data &= ~SERDES_RST;
+ mdiobus_write(priv->mii, serdes_phy_addr, SERDES_GCR0, data);
+
+ /* check for de-assert lane reset reflection */
+ data = serdes_status_poll(priv, serdes_phy_addr,
+ SERDES_GSR0,
+ SERDES_RST,
+ (u32)~SERDES_RST);
+
+ if (data) {
+ dev_err(priv->device, "Serdes de-assert lane reset timeout\n");
+ return;
+ }
+}
+
+static void tgl_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
+ unsigned long *interfaces)
+{
+ struct intel_priv_data *intel_priv = bsp_priv;
+ phy_interface_t interface;
+ int data;
+
+ /* Determine the link speed mode: 2.5Gbps/1Gbps */
+ data = mdiobus_read(priv->mii, intel_priv->mdio_adhoc_addr, SERDES_GCR);
+ if (data < 0)
+ return;
+
+ if (FIELD_GET(SERDES_LINK_MODE_MASK, data) == SERDES_LINK_MODE_2G5) {
+ dev_info(priv->device, "Link Speed Mode: 2.5Gbps\n");
+ priv->plat->default_an_inband = false;
+ interface = PHY_INTERFACE_MODE_2500BASEX;
+ } else {
+ interface = PHY_INTERFACE_MODE_SGMII;
+ }
+
+ __set_bit(interface, interfaces);
+ priv->plat->phy_interface = interface;
+}
+
+/* Program PTP Clock Frequency for different variant of
+ * Intel mGBE that has slightly different GPO mapping
+ */
+static void intel_mgbe_ptp_clk_freq_config(struct stmmac_priv *priv)
+{
+ struct intel_priv_data *intel_priv;
+ u32 gpio_value;
+
+ intel_priv = (struct intel_priv_data *)priv->plat->bsp_priv;
+
+ gpio_value = readl(priv->ioaddr + GMAC_GPIO_STATUS);
+
+ if (intel_priv->is_pse) {
+ /* For PSE GbE, use 200MHz */
+ gpio_value &= ~PSE_PTP_CLK_FREQ_MASK;
+ gpio_value |= PSE_PTP_CLK_FREQ_200MHZ;
+ } else {
+ /* For PCH GbE, use 200MHz */
+ gpio_value &= ~PCH_PTP_CLK_FREQ_MASK;
+ gpio_value |= PCH_PTP_CLK_FREQ_200MHZ;
+ }
+
+ writel(gpio_value, priv->ioaddr + GMAC_GPIO_STATUS);
+}
+
+static void get_arttime(struct mii_bus *mii, int intel_adhoc_addr,
+ u64 *art_time)
+{
+ u64 ns;
+
+ ns = mdiobus_read(mii, intel_adhoc_addr, PMC_ART_VALUE3);
+ ns <<= GMAC4_ART_TIME_SHIFT;
+ ns |= mdiobus_read(mii, intel_adhoc_addr, PMC_ART_VALUE2);
+ ns <<= GMAC4_ART_TIME_SHIFT;
+ ns |= mdiobus_read(mii, intel_adhoc_addr, PMC_ART_VALUE1);
+ ns <<= GMAC4_ART_TIME_SHIFT;
+ ns |= mdiobus_read(mii, intel_adhoc_addr, PMC_ART_VALUE0);
+
+ *art_time = ns;
+}
+
+static int stmmac_cross_ts_isr(struct stmmac_priv *priv)
+{
+ return (readl(priv->ioaddr + GMAC_INT_STATUS) & GMAC_INT_TSIE);
+}
+
+static int intel_crosststamp(ktime_t *device,
+ struct system_counterval_t *system,
+ void *ctx)
+{
+ struct intel_priv_data *intel_priv;
+
+ struct stmmac_priv *priv = (struct stmmac_priv *)ctx;
+ void __iomem *ptpaddr = priv->ptpaddr;
+ void __iomem *ioaddr = priv->hw->pcsr;
+ unsigned long flags;
+ u64 art_time = 0;
+ u64 ptp_time = 0;
+ u32 num_snapshot;
+ u32 gpio_value;
+ u32 acr_value;
+ int i;
+
+ intel_priv = priv->plat->bsp_priv;
+
+ /* Both internal crosstimestamping and external triggered event
+ * timestamping cannot be run concurrently.
+ */
+ if (priv->plat->flags & STMMAC_FLAG_EXT_SNAPSHOT_EN)
+ return -EBUSY;
+
+ priv->plat->flags |= STMMAC_FLAG_INT_SNAPSHOT_EN;
+
+ mutex_lock(&priv->aux_ts_lock);
+ /* Enable Internal snapshot trigger */
+ acr_value = readl(ptpaddr + PTP_ACR);
+ acr_value &= ~PTP_ACR_MASK;
+ switch (priv->plat->int_snapshot_num) {
+ case AUX_SNAPSHOT0:
+ acr_value |= PTP_ACR_ATSEN0;
+ break;
+ case AUX_SNAPSHOT1:
+ acr_value |= PTP_ACR_ATSEN1;
+ break;
+ case AUX_SNAPSHOT2:
+ acr_value |= PTP_ACR_ATSEN2;
+ break;
+ case AUX_SNAPSHOT3:
+ acr_value |= PTP_ACR_ATSEN3;
+ break;
+ default:
+ mutex_unlock(&priv->aux_ts_lock);
+ priv->plat->flags &= ~STMMAC_FLAG_INT_SNAPSHOT_EN;
+ return -EINVAL;
+ }
+ writel(acr_value, ptpaddr + PTP_ACR);
+
+ /* Clear FIFO */
+ acr_value = readl(ptpaddr + PTP_ACR);
+ acr_value |= PTP_ACR_ATSFC;
+ writel(acr_value, ptpaddr + PTP_ACR);
+ /* Release the mutex */
+ mutex_unlock(&priv->aux_ts_lock);
+
+ /* Trigger Internal snapshot signal
+ * Create a rising edge by just toggle the GPO1 to low
+ * and back to high.
+ */
+ gpio_value = readl(ioaddr + GMAC_GPIO_STATUS);
+ gpio_value &= ~GMAC_GPO1;
+ writel(gpio_value, ioaddr + GMAC_GPIO_STATUS);
+ gpio_value |= GMAC_GPO1;
+ writel(gpio_value, ioaddr + GMAC_GPIO_STATUS);
+
+ /* Time sync done Indication - Interrupt method */
+ if (!wait_event_interruptible_timeout(priv->tstamp_busy_wait,
+ stmmac_cross_ts_isr(priv),
+ HZ / 100)) {
+ priv->plat->flags &= ~STMMAC_FLAG_INT_SNAPSHOT_EN;
+ return -ETIMEDOUT;
+ }
+
+ *system = (struct system_counterval_t) {
+ .cycles = 0,
+ .cs_id = CSID_X86_ART,
+ .use_nsecs = false,
+ };
+
+ num_snapshot = (readl(ioaddr + GMAC_TIMESTAMP_STATUS) &
+ GMAC_TIMESTAMP_ATSNS_MASK) >>
+ GMAC_TIMESTAMP_ATSNS_SHIFT;
+
+ /* Repeat until the timestamps are from the FIFO last segment */
+ for (i = 0; i < num_snapshot; i++) {
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_get_ptptime(priv, ptpaddr, &ptp_time);
+ *device = ns_to_ktime(ptp_time);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+ get_arttime(priv->mii, intel_priv->mdio_adhoc_addr, &art_time);
+ system->cycles = art_time;
+ }
+
+ system->cycles *= intel_priv->crossts_adj;
+
+ priv->plat->flags &= ~STMMAC_FLAG_INT_SNAPSHOT_EN;
+
+ return 0;
+}
+
+static void intel_mgbe_pse_crossts_adj(struct intel_priv_data *intel_priv,
+ int base)
+{
+ if (boot_cpu_has(X86_FEATURE_ART)) {
+ unsigned int art_freq;
+
+ /* On systems that support ART, ART frequency can be obtained
+ * from ECX register of CPUID leaf (0x15).
+ */
+ art_freq = cpuid_ecx(ART_CPUID_LEAF);
+ do_div(art_freq, base);
+ intel_priv->crossts_adj = art_freq;
+ }
+}
+
+static int intel_tsn_lane_is_available(struct net_device *ndev,
+ struct intel_priv_data *intel_priv)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct pmc_ipc_cmd tmp = {};
+ struct pmc_ipc_rbuf rbuf = {};
+ int ret = 0, i, j;
+ const int max_fia_regs = 5;
+
+ tmp.cmd = IPC_SOC_REGISTER_ACCESS;
+ tmp.sub_cmd = IPC_SOC_SUB_CMD_READ;
+
+ for (i = 0; i < max_fia_regs; i++) {
+ tmp.wbuf[0] = R_PCH_FIA_15_PCR_LOS1_REG_BASE + i;
+
+ ret = intel_pmc_ipc(&tmp, &rbuf);
+ if (ret < 0) {
+ netdev_info(priv->dev, "Failed to read from PMC.\n");
+ return ret;
+ }
+
+ for (j = 0; j <= intel_priv->max_tsn_lane_regs; j++)
+ if ((rbuf.buf[0] >>
+ (4 * (intel_priv->tsn_lane_regs[j] % 8)) &
+ B_PCH_FIA_PCR_L0O) == 0xB)
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int intel_set_reg_access(const struct pmc_serdes_regs *regs, int max_regs)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < max_regs; i++) {
+ struct pmc_ipc_cmd tmp = {};
+ struct pmc_ipc_rbuf rbuf = {};
+
+ tmp.cmd = IPC_SOC_REGISTER_ACCESS;
+ tmp.sub_cmd = IPC_SOC_SUB_CMD_WRITE;
+ tmp.wbuf[0] = (u32)regs[i].index;
+ tmp.wbuf[1] = regs[i].val;
+
+ ret = intel_pmc_ipc(&tmp, &rbuf);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int intel_mac_finish(struct net_device *ndev,
+ void *intel_data,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct intel_priv_data *intel_priv = intel_data;
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ const struct pmc_serdes_regs *regs;
+ int max_regs = 0;
+ int ret = 0;
+
+ ret = intel_tsn_lane_is_available(ndev, intel_priv);
+ if (ret < 0) {
+ netdev_info(priv->dev, "No TSN lane available to set the registers.\n");
+ return ret;
+ }
+
+ if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+ regs = intel_priv->pid_2p5g.regs;
+ max_regs = intel_priv->pid_2p5g.num_regs;
+ } else {
+ regs = intel_priv->pid_1g.regs;
+ max_regs = intel_priv->pid_1g.num_regs;
+ }
+
+ ret = intel_set_reg_access(regs, max_regs);
+ if (ret < 0)
+ return ret;
+
+ priv->plat->phy_interface = interface;
+
+ intel_serdes_powerdown(ndev, intel_priv);
+ intel_serdes_powerup(ndev, intel_priv);
+
+ return ret;
+}
+
+static void common_default_data(struct plat_stmmacenet_data *plat)
+{
+ /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */
+ plat->clk_csr = STMMAC_CSR_20_35M;
+ plat->core_type = DWMAC_CORE_GMAC;
+ plat->force_sf_dma_mode = true;
+
+ plat->mdio_bus_data->needs_reset = true;
+}
+
+static struct phylink_pcs *intel_mgbe_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ /* plat->mdio_bus_data->has_xpcs has been set true, so there
+ * should always be an XPCS. The original code would always
+ * return this if present.
+ */
+ return xpcs_to_phylink_pcs(priv->hw->xpcs);
+}
+
+static int intel_mgbe_common_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct fwnode_handle *fwnode;
+ char clk_name[20];
+ int ret;
+ int i;
+
+ plat->provide_bus_info = true;
+ plat->phy_addr = -1;
+ plat->clk_csr = STMMAC_CSR_250_300M;
+ plat->core_type = DWMAC_CORE_GMAC4;
+ plat->force_sf_dma_mode = 0;
+ plat->flags |= (STMMAC_FLAG_TSO_EN | STMMAC_FLAG_SPH_DISABLE);
+
+ /* Multiplying factor to the clk_eee_i clock time
+ * period to make it closer to 100 ns. This value
+ * should be programmed such that the clk_eee_time_period *
+ * (MULT_FACT_100NS + 1) should be within 80 ns to 120 ns
+ * clk_eee frequency is 19.2Mhz
+ * clk_eee_time_period is 52ns
+ * 52ns * (1 + 1) = 104ns
+ * MULT_FACT_100NS = 1
+ */
+ plat->mult_fact_100ns = 1;
+
+ plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
+
+ for (i = 0; i < plat->rx_queues_to_use; i++)
+ plat->rx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+ for (i = 0; i < plat->tx_queues_to_use; i++) {
+ plat->tx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+ /* Default TX Q0 to use TSO and rest TXQ for TBS */
+ if (i > 0)
+ plat->tx_queues_cfg[i].tbs_en = 1;
+ }
+
+ /* FIFO size is 4096 bytes for 1 tx/rx queue */
+ plat->tx_fifo_size = plat->tx_queues_to_use * 4096;
+ plat->rx_fifo_size = plat->rx_queues_to_use * 4096;
+
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR;
+ plat->tx_queues_cfg[0].weight = 0x09;
+ plat->tx_queues_cfg[1].weight = 0x0A;
+ plat->tx_queues_cfg[2].weight = 0x0B;
+ plat->tx_queues_cfg[3].weight = 0x0C;
+ plat->tx_queues_cfg[4].weight = 0x0D;
+ plat->tx_queues_cfg[5].weight = 0x0E;
+ plat->tx_queues_cfg[6].weight = 0x0F;
+ plat->tx_queues_cfg[7].weight = 0x10;
+
+ plat->dma_cfg->pbl = 32;
+ plat->dma_cfg->pblx8 = true;
+ plat->dma_cfg->aal = 0;
+ plat->dma_cfg->dche = true;
+
+ plat->axi = devm_kzalloc(&pdev->dev, sizeof(*plat->axi),
+ GFP_KERNEL);
+ if (!plat->axi)
+ return -ENOMEM;
+
+ plat->axi->axi_lpi_en = 0;
+ plat->axi->axi_xit_frm = 0;
+ plat->axi->axi_wr_osr_lmt = 1;
+ plat->axi->axi_rd_osr_lmt = 1;
+ plat->axi->axi_blen_regval = DMA_AXI_BLEN4 | DMA_AXI_BLEN8 |
+ DMA_AXI_BLEN16;
+
+ plat->ptp_max_adj = plat->clk_ptp_rate;
+
+ /* Set system clock */
+ sprintf(clk_name, "%s-%s", "stmmac", pci_name(pdev));
+
+ plat->stmmac_clk = clk_register_fixed_rate(&pdev->dev,
+ clk_name, NULL, 0,
+ plat->clk_ptp_rate);
+
+ if (IS_ERR(plat->stmmac_clk)) {
+ dev_warn(&pdev->dev, "Fail to register stmmac-clk\n");
+ plat->stmmac_clk = NULL;
+ }
+
+ ret = clk_prepare_enable(plat->stmmac_clk);
+ if (ret) {
+ clk_unregister_fixed_rate(plat->stmmac_clk);
+ return ret;
+ }
+
+ plat->ptp_clk_freq_config = intel_mgbe_ptp_clk_freq_config;
+
+ plat->flags |= STMMAC_FLAG_VLAN_FAIL_Q_EN;
+
+ /* Use the last Rx queue */
+ plat->vlan_fail_q = plat->rx_queues_to_use - 1;
+
+ /* For fixed-link setup, we allow phy-mode setting */
+ fwnode = dev_fwnode(&pdev->dev);
+ if (fwnode) {
+ int phy_mode;
+
+ /* "phy-mode" setting is optional. If it is set,
+ * we allow either sgmii or 1000base-x for now.
+ */
+ phy_mode = fwnode_get_phy_mode(fwnode);
+ if (phy_mode >= 0) {
+ if (phy_mode == PHY_INTERFACE_MODE_SGMII ||
+ phy_mode == PHY_INTERFACE_MODE_1000BASEX)
+ plat->phy_interface = phy_mode;
+ else
+ dev_warn(&pdev->dev, "Invalid phy-mode\n");
+ }
+ }
+
+ /* Intel mgbe SGMII interface uses pcs-xcps */
+ if (plat->phy_interface == PHY_INTERFACE_MODE_SGMII ||
+ plat->phy_interface == PHY_INTERFACE_MODE_1000BASEX) {
+ plat->mdio_bus_data->pcs_mask = BIT_U32(INTEL_MGBE_XPCS_ADDR);
+ plat->default_an_inband = true;
+ plat->select_pcs = intel_mgbe_select_pcs;
+ }
+
+ /* Ensure mdio bus scan skips intel serdes and pcs-xpcs */
+ plat->mdio_bus_data->phy_mask = 1 << INTEL_MGBE_ADHOC_ADDR;
+ plat->mdio_bus_data->phy_mask |= 1 << INTEL_MGBE_XPCS_ADDR;
+
+ plat->int_snapshot_num = AUX_SNAPSHOT1;
+
+ if (boot_cpu_has(X86_FEATURE_ART))
+ plat->crosststamp = intel_crosststamp;
+
+ plat->flags &= ~STMMAC_FLAG_INT_SNAPSHOT_EN;
+
+ /* Setup MSI vector offset specific to Intel mGbE controller */
+ plat->msi_mac_vec = 29;
+ plat->msi_sfty_ce_vec = 27;
+ plat->msi_sfty_ue_vec = 26;
+ plat->msi_rx_base_vec = 0;
+ plat->msi_tx_base_vec = 1;
+
+ return 0;
+}
+
+static int ehl_common_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ plat->rx_queues_to_use = 8;
+ plat->tx_queues_to_use = 8;
+ plat->flags |= STMMAC_FLAG_USE_PHY_WOL;
+ plat->flags |= STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY;
+
+ plat->safety_feat_cfg->tsoee = 1;
+ plat->safety_feat_cfg->mrxpee = 1;
+ plat->safety_feat_cfg->mestee = 1;
+ plat->safety_feat_cfg->mrxee = 1;
+ plat->safety_feat_cfg->mtxee = 1;
+ plat->safety_feat_cfg->epsi = 0;
+ plat->safety_feat_cfg->edpp = 0;
+ plat->safety_feat_cfg->prtyen = 0;
+ plat->safety_feat_cfg->tmouten = 0;
+
+ intel_priv->tsn_lane_regs = ehl_tsn_lane_regs;
+ intel_priv->max_tsn_lane_regs = ARRAY_SIZE(ehl_tsn_lane_regs);
+
+ return intel_mgbe_common_data(pdev, plat);
+}
+
+static int ehl_sgmii_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ plat->bus_id = 1;
+ plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
+ plat->serdes_powerup = intel_serdes_powerup;
+ plat->serdes_powerdown = intel_serdes_powerdown;
+ plat->mac_finish = intel_mac_finish;
+ plat->clk_ptp_rate = 204800000;
+
+ intel_priv->pid_1g.regs = pid_modphy3_1g_regs;
+ intel_priv->pid_1g.num_regs = ARRAY_SIZE(pid_modphy3_1g_regs);
+ intel_priv->pid_2p5g.regs = pid_modphy3_2p5g_regs;
+ intel_priv->pid_2p5g.num_regs = ARRAY_SIZE(pid_modphy3_2p5g_regs);
+
+ return ehl_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info ehl_sgmii1g_info = {
+ .setup = ehl_sgmii_data,
+};
+
+static int ehl_rgmii_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->bus_id = 1;
+ plat->phy_interface = PHY_INTERFACE_MODE_RGMII;
+
+ plat->clk_ptp_rate = 204800000;
+
+ return ehl_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info ehl_rgmii1g_info = {
+ .setup = ehl_rgmii_data,
+};
+
+static int ehl_pse0_common_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ intel_priv->is_pse = true;
+ plat->bus_id = 2;
+ plat->host_dma_width = 32;
+
+ plat->clk_ptp_rate = 200000000;
+
+ intel_mgbe_pse_crossts_adj(intel_priv, EHL_PSE_ART_MHZ);
+
+ return ehl_common_data(pdev, plat);
+}
+
+static int ehl_pse0_rgmii1g_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->phy_interface = PHY_INTERFACE_MODE_RGMII_ID;
+ return ehl_pse0_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info ehl_pse0_rgmii1g_info = {
+ .setup = ehl_pse0_rgmii1g_data,
+};
+
+static int ehl_pse0_sgmii1g_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
+ plat->serdes_powerup = intel_serdes_powerup;
+ plat->serdes_powerdown = intel_serdes_powerdown;
+ plat->mac_finish = intel_mac_finish;
+
+ intel_priv->pid_1g.regs = pid_modphy1_1g_regs;
+ intel_priv->pid_1g.num_regs = ARRAY_SIZE(pid_modphy1_1g_regs);
+ intel_priv->pid_2p5g.regs = pid_modphy1_2p5g_regs;
+ intel_priv->pid_2p5g.num_regs = ARRAY_SIZE(pid_modphy1_2p5g_regs);
+
+ return ehl_pse0_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info ehl_pse0_sgmii1g_info = {
+ .setup = ehl_pse0_sgmii1g_data,
+};
+
+static int ehl_pse1_common_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ intel_priv->is_pse = true;
+ plat->bus_id = 3;
+ plat->host_dma_width = 32;
+
+ plat->clk_ptp_rate = 200000000;
+
+ intel_mgbe_pse_crossts_adj(intel_priv, EHL_PSE_ART_MHZ);
+
+ return ehl_common_data(pdev, plat);
+}
+
+static int ehl_pse1_rgmii1g_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->phy_interface = PHY_INTERFACE_MODE_RGMII_ID;
+ return ehl_pse1_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info ehl_pse1_rgmii1g_info = {
+ .setup = ehl_pse1_rgmii1g_data,
+};
+
+static int ehl_pse1_sgmii1g_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
+ plat->serdes_powerup = intel_serdes_powerup;
+ plat->serdes_powerdown = intel_serdes_powerdown;
+ plat->mac_finish = intel_mac_finish;
+
+ intel_priv->pid_1g.regs = pid_modphy1_1g_regs;
+ intel_priv->pid_1g.num_regs = ARRAY_SIZE(pid_modphy1_1g_regs);
+ intel_priv->pid_2p5g.regs = pid_modphy1_2p5g_regs;
+ intel_priv->pid_2p5g.num_regs = ARRAY_SIZE(pid_modphy1_2p5g_regs);
+
+ return ehl_pse1_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info ehl_pse1_sgmii1g_info = {
+ .setup = ehl_pse1_sgmii1g_data,
+};
+
+static int tgl_common_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->rx_queues_to_use = 6;
+ plat->tx_queues_to_use = 4;
+ plat->clk_ptp_rate = 204800000;
+ plat->get_interfaces = tgl_get_interfaces;
+
+ plat->safety_feat_cfg->tsoee = 1;
+ plat->safety_feat_cfg->mrxpee = 0;
+ plat->safety_feat_cfg->mestee = 1;
+ plat->safety_feat_cfg->mrxee = 1;
+ plat->safety_feat_cfg->mtxee = 1;
+ plat->safety_feat_cfg->epsi = 0;
+ plat->safety_feat_cfg->edpp = 0;
+ plat->safety_feat_cfg->prtyen = 0;
+ plat->safety_feat_cfg->tmouten = 0;
+
+ return intel_mgbe_common_data(pdev, plat);
+}
+
+static int tgl_sgmii_phy0_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->bus_id = 1;
+ plat->serdes_powerup = intel_serdes_powerup;
+ plat->serdes_powerdown = intel_serdes_powerdown;
+ return tgl_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info tgl_sgmii1g_phy0_info = {
+ .setup = tgl_sgmii_phy0_data,
+};
+
+static int tgl_sgmii_phy1_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->bus_id = 2;
+ plat->serdes_powerup = intel_serdes_powerup;
+ plat->serdes_powerdown = intel_serdes_powerdown;
+ return tgl_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info tgl_sgmii1g_phy1_info = {
+ .setup = tgl_sgmii_phy1_data,
+};
+
+static int adls_sgmii_phy0_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->bus_id = 1;
+
+ /* SerDes power up and power down are done in BIOS for ADL */
+
+ return tgl_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info adls_sgmii1g_phy0_info = {
+ .setup = adls_sgmii_phy0_data,
+};
+
+static int adls_sgmii_phy1_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ plat->bus_id = 2;
+
+ /* SerDes power up and power down are done in BIOS for ADL */
+
+ return tgl_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info adls_sgmii1g_phy1_info = {
+ .setup = adls_sgmii_phy1_data,
+};
+
+static int adln_common_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ plat->rx_queues_to_use = 6;
+ plat->tx_queues_to_use = 4;
+ plat->clk_ptp_rate = 204800000;
+
+ plat->safety_feat_cfg->tsoee = 1;
+ plat->safety_feat_cfg->mrxpee = 0;
+ plat->safety_feat_cfg->mestee = 1;
+ plat->safety_feat_cfg->mrxee = 1;
+ plat->safety_feat_cfg->mtxee = 1;
+ plat->safety_feat_cfg->epsi = 0;
+ plat->safety_feat_cfg->edpp = 0;
+ plat->safety_feat_cfg->prtyen = 0;
+ plat->safety_feat_cfg->tmouten = 0;
+
+ intel_priv->tsn_lane_regs = adln_tsn_lane_regs;
+ intel_priv->max_tsn_lane_regs = ARRAY_SIZE(adln_tsn_lane_regs);
+
+ return intel_mgbe_common_data(pdev, plat);
+}
+
+static int adln_sgmii_phy0_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct intel_priv_data *intel_priv = plat->bsp_priv;
+
+ plat->bus_id = 1;
+ plat->phy_interface = PHY_INTERFACE_MODE_SGMII;
+ plat->serdes_powerup = intel_serdes_powerup;
+ plat->serdes_powerdown = intel_serdes_powerdown;
+ plat->mac_finish = intel_mac_finish;
+
+ intel_priv->pid_1g.regs = pid_modphy1_1g_regs;
+ intel_priv->pid_1g.num_regs = ARRAY_SIZE(pid_modphy1_1g_regs);
+ intel_priv->pid_2p5g.regs = pid_modphy1_2p5g_regs;
+ intel_priv->pid_2p5g.num_regs = ARRAY_SIZE(pid_modphy1_2p5g_regs);
+
+ return adln_common_data(pdev, plat);
+}
+
+static struct stmmac_pci_info adln_sgmii1g_phy0_info = {
+ .setup = adln_sgmii_phy0_data,
+};
+
+static const struct stmmac_pci_func_data galileo_stmmac_func_data[] = {
+ {
+ .func = 6,
+ .phy_addr = 1,
+ },
+};
+
+static const struct stmmac_pci_dmi_data galileo_stmmac_dmi_data = {
+ .func = galileo_stmmac_func_data,
+ .nfuncs = ARRAY_SIZE(galileo_stmmac_func_data),
+};
+
+static const struct stmmac_pci_func_data iot2040_stmmac_func_data[] = {
+ {
+ .func = 6,
+ .phy_addr = 1,
+ },
+ {
+ .func = 7,
+ .phy_addr = 1,
+ },
+};
+
+static const struct stmmac_pci_dmi_data iot2040_stmmac_dmi_data = {
+ .func = iot2040_stmmac_func_data,
+ .nfuncs = ARRAY_SIZE(iot2040_stmmac_func_data),
+};
+
+static const struct dmi_system_id quark_pci_dmi[] = {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Galileo"),
+ },
+ .driver_data = (void *)&galileo_stmmac_dmi_data,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GalileoGen2"),
+ },
+ .driver_data = (void *)&galileo_stmmac_dmi_data,
+ },
+ /* There are 2 types of SIMATIC IOT2000: IOT2020 and IOT2040.
+ * The asset tag "6ES7647-0AA00-0YA2" is only for IOT2020 which
+ * has only one pci network device while other asset tags are
+ * for IOT2040 which has two.
+ */
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "SIMATIC IOT2000"),
+ DMI_EXACT_MATCH(DMI_BOARD_ASSET_TAG,
+ "6ES7647-0AA00-0YA2"),
+ },
+ .driver_data = (void *)&galileo_stmmac_dmi_data,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "SIMATIC IOT2000"),
+ },
+ .driver_data = (void *)&iot2040_stmmac_dmi_data,
+ },
+ {}
+};
+
+static int quark_default_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ int ret;
+
+ /* Set common default data first */
+ common_default_data(plat);
+
+ /* Refuse to load the driver and register net device if MAC controller
+ * does not connect to any PHY interface.
+ */
+ ret = stmmac_pci_find_phy_addr(pdev, quark_pci_dmi);
+ if (ret < 0) {
+ /* Return error to the caller on DMI enabled boards. */
+ if (dmi_get_system_info(DMI_BOARD_NAME))
+ return ret;
+
+ /* Galileo boards with old firmware don't support DMI. We always
+ * use 1 here as PHY address, so at least the first found MAC
+ * controller would be probed.
+ */
+ ret = 1;
+ }
+
+ plat->bus_id = pci_dev_id(pdev);
+ plat->phy_addr = ret;
+ plat->phy_interface = PHY_INTERFACE_MODE_RMII;
+
+ plat->dma_cfg->pbl = 16;
+ plat->dma_cfg->pblx8 = true;
+ plat->dma_cfg->fixed_burst = true;
+ /* AXI (TODO) */
+
+ return 0;
+}
+
+static const struct stmmac_pci_info quark_info = {
+ .setup = quark_default_data,
+};
+
+static int stmmac_config_single_msi(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ int ret;
+
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0) {
+ dev_info(&pdev->dev, "%s: Single IRQ enablement failed\n",
+ __func__);
+ return ret;
+ }
+
+ res->irq = pci_irq_vector(pdev, 0);
+ res->wol_irq = res->irq;
+ plat->flags &= ~STMMAC_FLAG_MULTI_MSI_EN;
+ dev_info(&pdev->dev, "%s: Single IRQ enablement successful\n",
+ __func__);
+
+ return 0;
+}
+
+static int stmmac_config_multi_msi(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ int ret;
+ int i;
+
+ if (plat->msi_rx_base_vec >= STMMAC_MSI_VEC_MAX ||
+ plat->msi_tx_base_vec >= STMMAC_MSI_VEC_MAX) {
+ dev_info(&pdev->dev, "%s: Invalid RX & TX vector defined\n",
+ __func__);
+ return -1;
+ }
+
+ ret = pci_alloc_irq_vectors(pdev, 2, STMMAC_MSI_VEC_MAX,
+ PCI_IRQ_MSI | PCI_IRQ_MSIX);
+ if (ret < 0) {
+ dev_info(&pdev->dev, "%s: multi MSI enablement failed\n",
+ __func__);
+ return ret;
+ }
+
+ /* For RX MSI */
+ for (i = 0; i < plat->rx_queues_to_use; i++) {
+ res->rx_irq[i] = pci_irq_vector(pdev,
+ plat->msi_rx_base_vec + i * 2);
+ }
+
+ /* For TX MSI */
+ for (i = 0; i < plat->tx_queues_to_use; i++) {
+ res->tx_irq[i] = pci_irq_vector(pdev,
+ plat->msi_tx_base_vec + i * 2);
+ }
+
+ if (plat->msi_mac_vec < STMMAC_MSI_VEC_MAX)
+ res->irq = pci_irq_vector(pdev, plat->msi_mac_vec);
+ if (plat->msi_wol_vec < STMMAC_MSI_VEC_MAX)
+ res->wol_irq = pci_irq_vector(pdev, plat->msi_wol_vec);
+ if (plat->msi_sfty_ce_vec < STMMAC_MSI_VEC_MAX)
+ res->sfty_ce_irq = pci_irq_vector(pdev, plat->msi_sfty_ce_vec);
+ if (plat->msi_sfty_ue_vec < STMMAC_MSI_VEC_MAX)
+ res->sfty_ue_irq = pci_irq_vector(pdev, plat->msi_sfty_ue_vec);
+
+ plat->flags |= STMMAC_FLAG_MULTI_MSI_EN;
+ dev_info(&pdev->dev, "%s: multi MSI enablement successful\n", __func__);
+
+ return 0;
+}
+
+static int intel_eth_pci_suspend(struct device *dev, void *bsp_priv)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int ret;
+
+ ret = pci_save_state(pdev);
+ if (ret)
+ return ret;
+
+ pci_wake_from_d3(pdev, true);
+ pci_set_power_state(pdev, PCI_D3hot);
+ return 0;
+}
+
+static int intel_eth_pci_resume(struct device *dev, void *bsp_priv)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int ret;
+
+ pci_restore_state(pdev);
+ pci_set_power_state(pdev, PCI_D0);
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ return 0;
+}
+
+/**
+ * intel_eth_pci_probe
+ *
+ * @pdev: pci device pointer
+ * @id: pointer to table of device id/id's.
+ *
+ * Description: This probing function gets called for all PCI devices which
+ * match the ID table and are not "owned" by other driver yet. This function
+ * gets passed a "struct pci_dev *" for each device whose entry in the ID table
+ * matches the device. The probe functions returns zero when the driver choose
+ * to take "ownership" of the device or an error code(-ve no) otherwise.
+ */
+static int intel_eth_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct stmmac_pci_info *info = (struct stmmac_pci_info *)id->driver_data;
+ struct intel_priv_data *intel_priv;
+ struct plat_stmmacenet_data *plat;
+ struct stmmac_resources res;
+ int ret;
+
+ intel_priv = devm_kzalloc(&pdev->dev, sizeof(*intel_priv), GFP_KERNEL);
+ if (!intel_priv)
+ return -ENOMEM;
+
+ plat = stmmac_plat_dat_alloc(&pdev->dev);
+ if (!plat)
+ return -ENOMEM;
+
+ plat->mdio_bus_data = devm_kzalloc(&pdev->dev,
+ sizeof(*plat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat->mdio_bus_data)
+ return -ENOMEM;
+
+ plat->safety_feat_cfg = devm_kzalloc(&pdev->dev,
+ sizeof(*plat->safety_feat_cfg),
+ GFP_KERNEL);
+ if (!plat->safety_feat_cfg)
+ return -ENOMEM;
+
+ /* Enable pci device */
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: ERROR: failed to enable device\n",
+ __func__);
+ return ret;
+ }
+
+ ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev));
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ plat->bsp_priv = intel_priv;
+ plat->suspend = intel_eth_pci_suspend;
+ plat->resume = intel_eth_pci_resume;
+
+ intel_priv->mdio_adhoc_addr = INTEL_MGBE_ADHOC_ADDR;
+ intel_priv->crossts_adj = 1;
+
+ /* Initialize all MSI vectors to invalid so that it can be set
+ * according to platform data settings below.
+ * Note: MSI vector takes value from 0 upto 31 (STMMAC_MSI_VEC_MAX)
+ */
+ plat->msi_mac_vec = STMMAC_MSI_VEC_MAX;
+ plat->msi_wol_vec = STMMAC_MSI_VEC_MAX;
+ plat->msi_sfty_ce_vec = STMMAC_MSI_VEC_MAX;
+ plat->msi_sfty_ue_vec = STMMAC_MSI_VEC_MAX;
+ plat->msi_rx_base_vec = STMMAC_MSI_VEC_MAX;
+ plat->msi_tx_base_vec = STMMAC_MSI_VEC_MAX;
+
+ ret = info->setup(pdev, plat);
+ if (ret)
+ return ret;
+
+ memset(&res, 0, sizeof(res));
+ res.addr = pcim_iomap_table(pdev)[0];
+
+ ret = stmmac_config_multi_msi(pdev, plat, &res);
+ if (ret) {
+ ret = stmmac_config_single_msi(pdev, plat, &res);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: ERROR: failed to enable IRQ\n",
+ __func__);
+ goto err_alloc_irq;
+ }
+ }
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat, &res);
+ if (ret) {
+ goto err_alloc_irq;
+ }
+
+ return 0;
+
+err_alloc_irq:
+ clk_disable_unprepare(plat->stmmac_clk);
+ clk_unregister_fixed_rate(plat->stmmac_clk);
+ return ret;
+}
+
+/**
+ * intel_eth_pci_remove
+ *
+ * @pdev: pci device pointer
+ * Description: this function calls the main to free the net resources
+ * and releases the PCI resources.
+ */
+static void intel_eth_pci_remove(struct pci_dev *pdev)
+{
+ struct net_device *ndev = dev_get_drvdata(&pdev->dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ stmmac_dvr_remove(&pdev->dev);
+
+ clk_disable_unprepare(priv->plat->stmmac_clk);
+ clk_unregister_fixed_rate(priv->plat->stmmac_clk);
+}
+
+#define PCI_DEVICE_ID_INTEL_QUARK 0x0937
+#define PCI_DEVICE_ID_INTEL_EHL_RGMII1G 0x4b30
+#define PCI_DEVICE_ID_INTEL_EHL_SGMII1G 0x4b31
+#define PCI_DEVICE_ID_INTEL_EHL_SGMII2G5 0x4b32
+/* Intel(R) Programmable Services Engine (Intel(R) PSE) consist of 2 MAC
+ * which are named PSE0 and PSE1
+ */
+#define PCI_DEVICE_ID_INTEL_EHL_PSE0_RGMII1G 0x4ba0
+#define PCI_DEVICE_ID_INTEL_EHL_PSE0_SGMII1G 0x4ba1
+#define PCI_DEVICE_ID_INTEL_EHL_PSE0_SGMII2G5 0x4ba2
+#define PCI_DEVICE_ID_INTEL_EHL_PSE1_RGMII1G 0x4bb0
+#define PCI_DEVICE_ID_INTEL_EHL_PSE1_SGMII1G 0x4bb1
+#define PCI_DEVICE_ID_INTEL_EHL_PSE1_SGMII2G5 0x4bb2
+#define PCI_DEVICE_ID_INTEL_TGLH_SGMII1G_0 0x43ac
+#define PCI_DEVICE_ID_INTEL_TGLH_SGMII1G_1 0x43a2
+#define PCI_DEVICE_ID_INTEL_TGL_SGMII1G 0xa0ac
+#define PCI_DEVICE_ID_INTEL_ADLS_SGMII1G_0 0x7aac
+#define PCI_DEVICE_ID_INTEL_ADLS_SGMII1G_1 0x7aad
+#define PCI_DEVICE_ID_INTEL_ADLN_SGMII1G 0x54ac
+#define PCI_DEVICE_ID_INTEL_RPLP_SGMII1G 0x51ac
+
+static const struct pci_device_id intel_eth_pci_id_table[] = {
+ { PCI_DEVICE_DATA(INTEL, QUARK, &quark_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_RGMII1G, &ehl_rgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_SGMII1G, &ehl_sgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_SGMII2G5, &ehl_sgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_PSE0_RGMII1G, &ehl_pse0_rgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_PSE0_SGMII1G, &ehl_pse0_sgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_PSE0_SGMII2G5, &ehl_pse0_sgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_PSE1_RGMII1G, &ehl_pse1_rgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_PSE1_SGMII1G, &ehl_pse1_sgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, EHL_PSE1_SGMII2G5, &ehl_pse1_sgmii1g_info) },
+ { PCI_DEVICE_DATA(INTEL, TGL_SGMII1G, &tgl_sgmii1g_phy0_info) },
+ { PCI_DEVICE_DATA(INTEL, TGLH_SGMII1G_0, &tgl_sgmii1g_phy0_info) },
+ { PCI_DEVICE_DATA(INTEL, TGLH_SGMII1G_1, &tgl_sgmii1g_phy1_info) },
+ { PCI_DEVICE_DATA(INTEL, ADLS_SGMII1G_0, &adls_sgmii1g_phy0_info) },
+ { PCI_DEVICE_DATA(INTEL, ADLS_SGMII1G_1, &adls_sgmii1g_phy1_info) },
+ { PCI_DEVICE_DATA(INTEL, ADLN_SGMII1G, &adln_sgmii1g_phy0_info) },
+ { PCI_DEVICE_DATA(INTEL, RPLP_SGMII1G, &adln_sgmii1g_phy0_info) },
+ {}
+};
+MODULE_DEVICE_TABLE(pci, intel_eth_pci_id_table);
+
+static struct pci_driver intel_eth_pci_driver = {
+ .name = "intel-eth-pci",
+ .id_table = intel_eth_pci_id_table,
+ .probe = intel_eth_pci_probe,
+ .remove = intel_eth_pci_remove,
+ .driver = {
+ .pm = &stmmac_simple_pm_ops,
+ },
+};
+
+module_pci_driver(intel_eth_pci_driver);
+
+MODULE_DESCRIPTION("INTEL 10/100/1000 Ethernet PCI driver");
+MODULE_AUTHOR("Voon Weifeng <weifeng.voon@intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.h b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.h
new file mode 100644
index 000000000..7511c224b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2020, Intel Corporation
+ * DWMAC Intel header file
+ */
+
+#ifndef __DWMAC_INTEL_H__
+#define __DWMAC_INTEL_H__
+
+#define POLL_DELAY_US 8
+
+/* SERDES Register */
+#define SERDES_GCR 0x0 /* Global Conguration */
+#define SERDES_GSR0 0x5 /* Global Status Reg0 */
+#define SERDES_GCR0 0xb /* Global Configuration Reg0 */
+
+/* SERDES defines */
+#define SERDES_PLL_CLK BIT(0) /* PLL clk valid signal */
+#define SERDES_PHY_RX_CLK BIT(1) /* PSE SGMII PHY rx clk */
+#define SERDES_RST BIT(2) /* Serdes Reset */
+#define SERDES_PWR_ST_MASK GENMASK(6, 4) /* Serdes Power state*/
+#define SERDES_RATE_MASK GENMASK(9, 8)
+#define SERDES_PCLK_MASK GENMASK(14, 12) /* PCLK rate to PHY */
+#define SERDES_LINK_MODE_MASK GENMASK(2, 1)
+#define SERDES_PWR_ST_SHIFT 4
+#define SERDES_PWR_ST_P0 0x0
+#define SERDES_PWR_ST_P3 0x3
+#define SERDES_LINK_MODE_2G5 0x3
+#define SERSED_LINK_MODE_1G 0x2
+#define SERDES_PCLK_37p5MHZ 0x0
+#define SERDES_PCLK_70MHZ 0x1
+#define SERDES_RATE_PCIE_GEN1 0x0
+#define SERDES_RATE_PCIE_GEN2 0x1
+#define SERDES_RATE_PCIE_SHIFT 8
+#define SERDES_PCLK_SHIFT 12
+
+#define INTEL_MGBE_ADHOC_ADDR 0x15
+#define INTEL_MGBE_XPCS_ADDR 0x16
+
+/* Cross-timestamping defines */
+#define ART_CPUID_LEAF 0x15
+#define EHL_PSE_ART_MHZ 19200000
+
+/* Selection for PTP Clock Freq belongs to PSE & PCH GbE */
+#define PSE_PTP_CLK_FREQ_MASK (GMAC_GPO0 | GMAC_GPO3)
+#define PSE_PTP_CLK_FREQ_19_2MHZ (GMAC_GPO0)
+#define PSE_PTP_CLK_FREQ_200MHZ (GMAC_GPO0 | GMAC_GPO3)
+#define PSE_PTP_CLK_FREQ_256MHZ (0)
+#define PCH_PTP_CLK_FREQ_MASK (GMAC_GPO0)
+#define PCH_PTP_CLK_FREQ_19_2MHZ (GMAC_GPO0)
+#define PCH_PTP_CLK_FREQ_200MHZ (0)
+
+/* Modphy Register index */
+#define R_PCH_FIA_15_PCR_LOS1_REG_BASE 8
+#define R_PCH_FIA_15_PCR_LOS2_REG_BASE 9
+#define R_PCH_FIA_15_PCR_LOS3_REG_BASE 10
+#define R_PCH_FIA_15_PCR_LOS4_REG_BASE 11
+#define R_PCH_FIA_15_PCR_LOS5_REG_BASE 12
+#define B_PCH_FIA_PCR_L0O GENMASK(3, 0)
+#define PID_MODPHY1_B_MODPHY_PCR_LCPLL_DWORD0 13
+#define PID_MODPHY1_N_MODPHY_PCR_LCPLL_DWORD2 14
+#define PID_MODPHY1_N_MODPHY_PCR_LCPLL_DWORD7 15
+#define PID_MODPHY1_N_MODPHY_PCR_LPPLL_DWORD10 16
+#define PID_MODPHY1_N_MODPHY_PCR_CMN_ANA_DWORD30 17
+#define PID_MODPHY3_B_MODPHY_PCR_LCPLL_DWORD0 18
+#define PID_MODPHY3_N_MODPHY_PCR_LCPLL_DWORD2 19
+#define PID_MODPHY3_N_MODPHY_PCR_LCPLL_DWORD7 20
+#define PID_MODPHY3_N_MODPHY_PCR_LPPLL_DWORD10 21
+#define PID_MODPHY3_N_MODPHY_PCR_CMN_ANA_DWORD30 22
+
+#define B_MODPHY_PCR_LCPLL_DWORD0_1G 0x46AAAA41
+#define N_MODPHY_PCR_LCPLL_DWORD2_1G 0x00000139
+#define N_MODPHY_PCR_LCPLL_DWORD7_1G 0x002A0003
+#define N_MODPHY_PCR_LPPLL_DWORD10_1G 0x00170008
+#define N_MODPHY_PCR_CMN_ANA_DWORD30_1G 0x0000D4AC
+#define B_MODPHY_PCR_LCPLL_DWORD0_2P5G 0x58555551
+#define N_MODPHY_PCR_LCPLL_DWORD2_2P5G 0x0000012D
+#define N_MODPHY_PCR_LCPLL_DWORD7_2P5G 0x001F0003
+#define N_MODPHY_PCR_LPPLL_DWORD10_2P5G 0x00170008
+#define N_MODPHY_PCR_CMN_ANA_DWORD30_2P5G 0x8200ACAC
+
+#endif /* __DWMAC_INTEL_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c
new file mode 100644
index 000000000..c05f85534
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c
@@ -0,0 +1,510 @@
+/*
+ * Qualcomm Atheros IPQ806x GMAC glue layer
+ *
+ * Copyright (C) 2015 The Linux Foundation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/stmmac.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/sys_soc.h>
+#include <linux/bitfield.h>
+
+#include "stmmac_platform.h"
+
+#define NSS_COMMON_CLK_GATE 0x8
+#define NSS_COMMON_CLK_GATE_PTP_EN(x) BIT(0x10 + x)
+#define NSS_COMMON_CLK_GATE_RGMII_RX_EN(x) BIT(0x9 + (x * 2))
+#define NSS_COMMON_CLK_GATE_RGMII_TX_EN(x) BIT(0x8 + (x * 2))
+#define NSS_COMMON_CLK_GATE_GMII_RX_EN(x) BIT(0x4 + x)
+#define NSS_COMMON_CLK_GATE_GMII_TX_EN(x) BIT(0x0 + x)
+
+#define NSS_COMMON_CLK_DIV0 0xC
+#define NSS_COMMON_CLK_DIV_OFFSET(x) (x * 8)
+#define NSS_COMMON_CLK_DIV_MASK 0x7f
+
+#define NSS_COMMON_CLK_SRC_CTRL 0x14
+#define NSS_COMMON_CLK_SRC_CTRL_OFFSET(x) (x)
+/* Mode is coded on 1 bit but is different depending on the MAC ID:
+ * MAC0: QSGMII=0 RGMII=1
+ * MAC1: QSGMII=0 SGMII=0 RGMII=1
+ * MAC2 & MAC3: QSGMII=0 SGMII=1
+ */
+#define NSS_COMMON_CLK_SRC_CTRL_RGMII(x) 1
+#define NSS_COMMON_CLK_SRC_CTRL_SGMII(x) ((x >= 2) ? 1 : 0)
+
+#define NSS_COMMON_GMAC_CTL(x) (0x30 + (x * 4))
+#define NSS_COMMON_GMAC_CTL_CSYS_REQ BIT(19)
+#define NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL BIT(16)
+#define NSS_COMMON_GMAC_CTL_IFG_LIMIT_OFFSET 8
+#define NSS_COMMON_GMAC_CTL_IFG_OFFSET 0
+
+#define NSS_COMMON_CLK_DIV_RGMII_1000 1
+#define NSS_COMMON_CLK_DIV_RGMII_100 9
+#define NSS_COMMON_CLK_DIV_RGMII_10 99
+#define NSS_COMMON_CLK_DIV_SGMII_1000 0
+#define NSS_COMMON_CLK_DIV_SGMII_100 4
+#define NSS_COMMON_CLK_DIV_SGMII_10 49
+
+#define QSGMII_PCS_ALL_CH_CTL 0x80
+#define QSGMII_PCS_CH_SPEED_FORCE BIT(1)
+#define QSGMII_PCS_CH_SPEED_10 0x0
+#define QSGMII_PCS_CH_SPEED_100 BIT(2)
+#define QSGMII_PCS_CH_SPEED_1000 BIT(3)
+#define QSGMII_PCS_CH_SPEED_MASK (QSGMII_PCS_CH_SPEED_FORCE | \
+ QSGMII_PCS_CH_SPEED_10 | \
+ QSGMII_PCS_CH_SPEED_100 | \
+ QSGMII_PCS_CH_SPEED_1000)
+#define QSGMII_PCS_CH_SPEED_SHIFT(x) ((x) * 4)
+
+#define QSGMII_PCS_CAL_LCKDT_CTL 0x120
+#define QSGMII_PCS_CAL_LCKDT_CTL_RST BIT(19)
+
+/* Only GMAC1/2/3 support SGMII and their CTL register are not contiguous */
+#define QSGMII_PHY_SGMII_CTL(x) ((x == 1) ? 0x134 : \
+ (0x13c + (4 * (x - 2))))
+#define QSGMII_PHY_CDR_EN BIT(0)
+#define QSGMII_PHY_RX_FRONT_EN BIT(1)
+#define QSGMII_PHY_RX_SIGNAL_DETECT_EN BIT(2)
+#define QSGMII_PHY_TX_DRIVER_EN BIT(3)
+#define QSGMII_PHY_QSGMII_EN BIT(7)
+#define QSGMII_PHY_DEEMPHASIS_LVL_MASK GENMASK(11, 10)
+#define QSGMII_PHY_DEEMPHASIS_LVL(x) FIELD_PREP(QSGMII_PHY_DEEMPHASIS_LVL_MASK, (x))
+#define QSGMII_PHY_PHASE_LOOP_GAIN_MASK GENMASK(14, 12)
+#define QSGMII_PHY_PHASE_LOOP_GAIN(x) FIELD_PREP(QSGMII_PHY_PHASE_LOOP_GAIN_MASK, (x))
+#define QSGMII_PHY_RX_DC_BIAS_MASK GENMASK(19, 18)
+#define QSGMII_PHY_RX_DC_BIAS(x) FIELD_PREP(QSGMII_PHY_RX_DC_BIAS_MASK, (x))
+#define QSGMII_PHY_RX_INPUT_EQU_MASK GENMASK(21, 20)
+#define QSGMII_PHY_RX_INPUT_EQU(x) FIELD_PREP(QSGMII_PHY_RX_INPUT_EQU_MASK, (x))
+#define QSGMII_PHY_CDR_PI_SLEW_MASK GENMASK(23, 22)
+#define QSGMII_PHY_CDR_PI_SLEW(x) FIELD_PREP(QSGMII_PHY_CDR_PI_SLEW_MASK, (x))
+#define QSGMII_PHY_TX_SLEW_MASK GENMASK(27, 26)
+#define QSGMII_PHY_TX_SLEW(x) FIELD_PREP(QSGMII_PHY_TX_SLEW_MASK, (x))
+#define QSGMII_PHY_TX_DRV_AMP_MASK GENMASK(31, 28)
+#define QSGMII_PHY_TX_DRV_AMP(x) FIELD_PREP(QSGMII_PHY_TX_DRV_AMP_MASK, (x))
+
+struct ipq806x_gmac {
+ struct platform_device *pdev;
+ struct regmap *nss_common;
+ struct regmap *qsgmii_csr;
+ uint32_t id;
+ struct clk *core_clk;
+ phy_interface_t phy_mode;
+};
+
+static int get_clk_div_sgmii(struct ipq806x_gmac *gmac, int speed)
+{
+ struct device *dev = &gmac->pdev->dev;
+ int div;
+
+ switch (speed) {
+ case SPEED_1000:
+ div = NSS_COMMON_CLK_DIV_SGMII_1000;
+ break;
+
+ case SPEED_100:
+ div = NSS_COMMON_CLK_DIV_SGMII_100;
+ break;
+
+ case SPEED_10:
+ div = NSS_COMMON_CLK_DIV_SGMII_10;
+ break;
+
+ default:
+ dev_err(dev, "Speed %dMbps not supported in SGMII\n", speed);
+ return -EINVAL;
+ }
+
+ return div;
+}
+
+static int get_clk_div_rgmii(struct ipq806x_gmac *gmac, int speed)
+{
+ struct device *dev = &gmac->pdev->dev;
+ int div;
+
+ switch (speed) {
+ case SPEED_1000:
+ div = NSS_COMMON_CLK_DIV_RGMII_1000;
+ break;
+
+ case SPEED_100:
+ div = NSS_COMMON_CLK_DIV_RGMII_100;
+ break;
+
+ case SPEED_10:
+ div = NSS_COMMON_CLK_DIV_RGMII_10;
+ break;
+
+ default:
+ dev_err(dev, "Speed %dMbps not supported in RGMII\n", speed);
+ return -EINVAL;
+ }
+
+ return div;
+}
+
+static int ipq806x_gmac_set_speed(struct ipq806x_gmac *gmac, int speed)
+{
+ uint32_t clk_bits, val;
+ int div;
+
+ switch (gmac->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ div = get_clk_div_rgmii(gmac, speed);
+ clk_bits = NSS_COMMON_CLK_GATE_RGMII_RX_EN(gmac->id) |
+ NSS_COMMON_CLK_GATE_RGMII_TX_EN(gmac->id);
+ break;
+
+ case PHY_INTERFACE_MODE_SGMII:
+ div = get_clk_div_sgmii(gmac, speed);
+ clk_bits = NSS_COMMON_CLK_GATE_GMII_RX_EN(gmac->id) |
+ NSS_COMMON_CLK_GATE_GMII_TX_EN(gmac->id);
+ break;
+
+ default:
+ dev_err(&gmac->pdev->dev, "Unsupported PHY mode: \"%s\"\n",
+ phy_modes(gmac->phy_mode));
+ return -EINVAL;
+ }
+
+ /* Disable the clocks */
+ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val);
+ val &= ~clk_bits;
+ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val);
+
+ /* Set the divider */
+ regmap_read(gmac->nss_common, NSS_COMMON_CLK_DIV0, &val);
+ val &= ~(NSS_COMMON_CLK_DIV_MASK
+ << NSS_COMMON_CLK_DIV_OFFSET(gmac->id));
+ val |= div << NSS_COMMON_CLK_DIV_OFFSET(gmac->id);
+ regmap_write(gmac->nss_common, NSS_COMMON_CLK_DIV0, val);
+
+ /* Enable the clock back */
+ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val);
+ val |= clk_bits;
+ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val);
+
+ return 0;
+}
+
+static int ipq806x_gmac_of_parse(struct ipq806x_gmac *gmac,
+ struct plat_stmmacenet_data *plat_dat)
+{
+ struct device *dev = &gmac->pdev->dev;
+
+ gmac->phy_mode = plat_dat->phy_interface;
+
+ if (of_property_read_u32(dev->of_node, "qcom,id", &gmac->id) < 0) {
+ dev_err(dev, "missing qcom id property\n");
+ return -EINVAL;
+ }
+
+ /* The GMACs are called 1 to 4 in the documentation, but to simplify the
+ * code and keep it consistent with the Linux convention, we'll number
+ * them from 0 to 3 here.
+ */
+ if (gmac->id > 3) {
+ dev_err(dev, "invalid gmac id\n");
+ return -EINVAL;
+ }
+
+ gmac->core_clk = devm_clk_get(dev, "stmmaceth");
+ if (IS_ERR(gmac->core_clk)) {
+ dev_err(dev, "missing stmmaceth clk property\n");
+ return PTR_ERR(gmac->core_clk);
+ }
+ clk_set_rate(gmac->core_clk, 266000000);
+
+ /* Setup the register map for the nss common registers */
+ gmac->nss_common = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "qcom,nss-common");
+ if (IS_ERR(gmac->nss_common)) {
+ dev_err(dev, "missing nss-common node\n");
+ return PTR_ERR(gmac->nss_common);
+ }
+
+ /* Setup the register map for the qsgmii csr registers */
+ gmac->qsgmii_csr = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "qcom,qsgmii-csr");
+ if (IS_ERR(gmac->qsgmii_csr))
+ dev_err(dev, "missing qsgmii-csr node\n");
+
+ return PTR_ERR_OR_ZERO(gmac->qsgmii_csr);
+}
+
+static int ipq806x_gmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct ipq806x_gmac *gmac = bsp_priv;
+
+ return ipq806x_gmac_set_speed(gmac, speed);
+}
+
+static int
+ipq806x_gmac_configure_qsgmii_pcs_speed(struct ipq806x_gmac *gmac)
+{
+ struct platform_device *pdev = gmac->pdev;
+ struct device *dev = &pdev->dev;
+ struct device_node *dn;
+ int link_speed;
+ int val = 0;
+ int ret;
+
+ /* Some bootloader may apply wrong configuration and cause
+ * not functioning port. If fixed link is not set,
+ * reset the force speed bit.
+ */
+ if (!of_phy_is_fixed_link(pdev->dev.of_node))
+ goto write;
+
+ dn = of_get_child_by_name(pdev->dev.of_node, "fixed-link");
+ ret = of_property_read_u32(dn, "speed", &link_speed);
+ of_node_put(dn);
+ if (ret) {
+ dev_err(dev, "found fixed-link node with no speed");
+ return ret;
+ }
+
+ val = QSGMII_PCS_CH_SPEED_FORCE;
+
+ switch (link_speed) {
+ case SPEED_1000:
+ val |= QSGMII_PCS_CH_SPEED_1000;
+ break;
+ case SPEED_100:
+ val |= QSGMII_PCS_CH_SPEED_100;
+ break;
+ case SPEED_10:
+ val |= QSGMII_PCS_CH_SPEED_10;
+ break;
+ }
+
+write:
+ regmap_update_bits(gmac->qsgmii_csr, QSGMII_PCS_ALL_CH_CTL,
+ QSGMII_PCS_CH_SPEED_MASK <<
+ QSGMII_PCS_CH_SPEED_SHIFT(gmac->id),
+ val <<
+ QSGMII_PCS_CH_SPEED_SHIFT(gmac->id));
+
+ return 0;
+}
+
+static const struct soc_device_attribute ipq806x_gmac_soc_v1[] = {
+ {
+ .revision = "1.*",
+ },
+ {
+ /* sentinel */
+ }
+};
+
+static int
+ipq806x_gmac_configure_qsgmii_params(struct ipq806x_gmac *gmac)
+{
+ struct platform_device *pdev = gmac->pdev;
+ const struct soc_device_attribute *soc;
+ struct device *dev = &pdev->dev;
+ u32 qsgmii_param;
+
+ switch (gmac->id) {
+ case 1:
+ soc = soc_device_match(ipq806x_gmac_soc_v1);
+
+ if (soc)
+ qsgmii_param = QSGMII_PHY_TX_DRV_AMP(0xc) |
+ QSGMII_PHY_TX_SLEW(0x2) |
+ QSGMII_PHY_DEEMPHASIS_LVL(0x2);
+ else
+ qsgmii_param = QSGMII_PHY_TX_DRV_AMP(0xd) |
+ QSGMII_PHY_TX_SLEW(0x0) |
+ QSGMII_PHY_DEEMPHASIS_LVL(0x0);
+
+ qsgmii_param |= QSGMII_PHY_RX_DC_BIAS(0x2);
+ break;
+ case 2:
+ case 3:
+ qsgmii_param = QSGMII_PHY_RX_DC_BIAS(0x3) |
+ QSGMII_PHY_TX_DRV_AMP(0xc);
+ break;
+ default: /* gmac 0 can't be set in SGMII mode */
+ dev_err(dev, "gmac id %d can't be in SGMII mode", gmac->id);
+ return -EINVAL;
+ }
+
+ /* Common params across all gmac id */
+ qsgmii_param |= QSGMII_PHY_CDR_EN |
+ QSGMII_PHY_RX_FRONT_EN |
+ QSGMII_PHY_RX_SIGNAL_DETECT_EN |
+ QSGMII_PHY_TX_DRIVER_EN |
+ QSGMII_PHY_QSGMII_EN |
+ QSGMII_PHY_PHASE_LOOP_GAIN(0x4) |
+ QSGMII_PHY_RX_INPUT_EQU(0x1) |
+ QSGMII_PHY_CDR_PI_SLEW(0x2);
+
+ regmap_write(gmac->qsgmii_csr, QSGMII_PHY_SGMII_CTL(gmac->id),
+ qsgmii_param);
+
+ return 0;
+}
+
+static int ipq806x_gmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct ipq806x_gmac *gmac;
+ int val;
+ int err;
+
+ val = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (val)
+ return val;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac)
+ return -ENOMEM;
+
+ gmac->pdev = pdev;
+
+ err = ipq806x_gmac_of_parse(gmac, plat_dat);
+ if (err) {
+ dev_err(dev, "device tree parsing error\n");
+ return err;
+ }
+
+ regmap_write(gmac->qsgmii_csr, QSGMII_PCS_CAL_LCKDT_CTL,
+ QSGMII_PCS_CAL_LCKDT_CTL_RST);
+
+ /* Inter frame gap is set to 12 */
+ val = 12 << NSS_COMMON_GMAC_CTL_IFG_OFFSET |
+ 12 << NSS_COMMON_GMAC_CTL_IFG_LIMIT_OFFSET;
+ /* We also initiate an AXI low power exit request */
+ val |= NSS_COMMON_GMAC_CTL_CSYS_REQ;
+ switch (gmac->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val |= NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL;
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ val &= ~NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL;
+ break;
+ default:
+ goto err_unsupported_phy;
+ }
+ regmap_write(gmac->nss_common, NSS_COMMON_GMAC_CTL(gmac->id), val);
+
+ /* Configure the clock src according to the mode */
+ regmap_read(gmac->nss_common, NSS_COMMON_CLK_SRC_CTRL, &val);
+ val &= ~(1 << NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id));
+ switch (gmac->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val |= NSS_COMMON_CLK_SRC_CTRL_RGMII(gmac->id) <<
+ NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ val |= NSS_COMMON_CLK_SRC_CTRL_SGMII(gmac->id) <<
+ NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id);
+ break;
+ default:
+ goto err_unsupported_phy;
+ }
+ regmap_write(gmac->nss_common, NSS_COMMON_CLK_SRC_CTRL, val);
+
+ /* Enable PTP clock */
+ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val);
+ val |= NSS_COMMON_CLK_GATE_PTP_EN(gmac->id);
+ switch (gmac->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val |= NSS_COMMON_CLK_GATE_RGMII_RX_EN(gmac->id) |
+ NSS_COMMON_CLK_GATE_RGMII_TX_EN(gmac->id);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ val |= NSS_COMMON_CLK_GATE_GMII_RX_EN(gmac->id) |
+ NSS_COMMON_CLK_GATE_GMII_TX_EN(gmac->id);
+ break;
+ default:
+ goto err_unsupported_phy;
+ }
+ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val);
+
+ if (gmac->phy_mode == PHY_INTERFACE_MODE_SGMII) {
+ err = ipq806x_gmac_configure_qsgmii_params(gmac);
+ if (err)
+ return err;
+
+ err = ipq806x_gmac_configure_qsgmii_pcs_speed(gmac);
+ if (err)
+ return err;
+ }
+
+ plat_dat->core_type = DWMAC_CORE_GMAC;
+ plat_dat->bsp_priv = gmac;
+ plat_dat->set_clk_tx_rate = ipq806x_gmac_set_clk_tx_rate;
+ plat_dat->multicast_filter_bins = 0;
+ plat_dat->tx_fifo_size = 8192;
+ plat_dat->rx_fifo_size = 8192;
+
+ return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+
+err_unsupported_phy:
+ dev_err(&pdev->dev, "Unsupported PHY mode: \"%s\"\n",
+ phy_modes(gmac->phy_mode));
+ return -EINVAL;
+}
+
+static const struct of_device_id ipq806x_gmac_dwmac_match[] = {
+ { .compatible = "qcom,ipq806x-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ipq806x_gmac_dwmac_match);
+
+static struct platform_driver ipq806x_gmac_dwmac_driver = {
+ .probe = ipq806x_gmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "ipq806x-gmac-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = ipq806x_gmac_dwmac_match,
+ },
+};
+module_platform_driver(ipq806x_gmac_dwmac_driver);
+
+MODULE_AUTHOR("Mathieu Olivari <mathieu@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm Atheros IPQ806x DWMAC specific glue layer");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c
new file mode 100644
index 000000000..eb14c197d
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c
@@ -0,0 +1,619 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020, Loongson Corporation
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/pci.h>
+#include <linux/dmi.h>
+#include <linux/device.h>
+#include <linux/of_irq.h>
+#include "stmmac.h"
+#include "stmmac_libpci.h"
+#include "dwmac_dma.h"
+#include "dwmac1000.h"
+
+#define DRIVER_NAME "dwmac-loongson-pci"
+
+/* Normal Loongson Tx Summary */
+#define DMA_INTR_ENA_NIE_TX_LOONGSON 0x00040000
+/* Normal Loongson Rx Summary */
+#define DMA_INTR_ENA_NIE_RX_LOONGSON 0x00020000
+
+#define DMA_INTR_NORMAL_LOONGSON (DMA_INTR_ENA_NIE_TX_LOONGSON | \
+ DMA_INTR_ENA_NIE_RX_LOONGSON | \
+ DMA_INTR_ENA_RIE | DMA_INTR_ENA_TIE)
+
+/* Abnormal Loongson Tx Summary */
+#define DMA_INTR_ENA_AIE_TX_LOONGSON 0x00010000
+/* Abnormal Loongson Rx Summary */
+#define DMA_INTR_ENA_AIE_RX_LOONGSON 0x00008000
+
+#define DMA_INTR_ABNORMAL_LOONGSON (DMA_INTR_ENA_AIE_TX_LOONGSON | \
+ DMA_INTR_ENA_AIE_RX_LOONGSON | \
+ DMA_INTR_ENA_FBE | DMA_INTR_ENA_UNE)
+
+#define DMA_INTR_DEFAULT_MASK_LOONGSON (DMA_INTR_NORMAL_LOONGSON | \
+ DMA_INTR_ABNORMAL_LOONGSON)
+
+/* Normal Loongson Tx Interrupt Summary */
+#define DMA_STATUS_NIS_TX_LOONGSON 0x00040000
+/* Normal Loongson Rx Interrupt Summary */
+#define DMA_STATUS_NIS_RX_LOONGSON 0x00020000
+
+/* Abnormal Loongson Tx Interrupt Summary */
+#define DMA_STATUS_AIS_TX_LOONGSON 0x00010000
+/* Abnormal Loongson Rx Interrupt Summary */
+#define DMA_STATUS_AIS_RX_LOONGSON 0x00008000
+
+/* Fatal Loongson Tx Bus Error Interrupt */
+#define DMA_STATUS_FBI_TX_LOONGSON 0x00002000
+/* Fatal Loongson Rx Bus Error Interrupt */
+#define DMA_STATUS_FBI_RX_LOONGSON 0x00001000
+
+#define DMA_STATUS_MSK_COMMON_LOONGSON (DMA_STATUS_NIS_TX_LOONGSON | \
+ DMA_STATUS_NIS_RX_LOONGSON | \
+ DMA_STATUS_AIS_TX_LOONGSON | \
+ DMA_STATUS_AIS_RX_LOONGSON | \
+ DMA_STATUS_FBI_TX_LOONGSON | \
+ DMA_STATUS_FBI_RX_LOONGSON)
+
+#define DMA_STATUS_MSK_RX_LOONGSON (DMA_STATUS_ERI | DMA_STATUS_RWT | \
+ DMA_STATUS_RPS | DMA_STATUS_RU | \
+ DMA_STATUS_RI | DMA_STATUS_OVF | \
+ DMA_STATUS_MSK_COMMON_LOONGSON)
+
+#define DMA_STATUS_MSK_TX_LOONGSON (DMA_STATUS_ETI | DMA_STATUS_UNF | \
+ DMA_STATUS_TJT | DMA_STATUS_TU | \
+ DMA_STATUS_TPS | DMA_STATUS_TI | \
+ DMA_STATUS_MSK_COMMON_LOONGSON)
+
+#define PCI_DEVICE_ID_LOONGSON_GMAC1 0x7a03
+#define PCI_DEVICE_ID_LOONGSON_GMAC2 0x7a23
+#define PCI_DEVICE_ID_LOONGSON_GNET 0x7a13
+#define DWMAC_CORE_MULTICHAN_V1 0x10 /* Loongson custom ID 0x10 */
+#define DWMAC_CORE_MULTICHAN_V2 0x12 /* Loongson custom ID 0x12 */
+
+struct loongson_data {
+ u32 multichan;
+ u32 loongson_id;
+ struct device *dev;
+};
+
+struct stmmac_pci_info {
+ int (*setup)(struct pci_dev *pdev, struct plat_stmmacenet_data *plat);
+};
+
+static void loongson_default_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct loongson_data *ld = plat->bsp_priv;
+
+ /* Get bus_id, this can be overwritten later */
+ plat->bus_id = pci_dev_id(pdev);
+
+ /* clk_csr_i = 100-150MHz & MDC = clk_csr_i/62 */
+ plat->clk_csr = STMMAC_CSR_100_150M;
+ plat->core_type = DWMAC_CORE_GMAC;
+ plat->force_sf_dma_mode = true;
+
+ /* Increase the default value for multicast hash bins */
+ plat->multicast_filter_bins = 256;
+
+ plat->clk_ref_rate = 125000000;
+ plat->clk_ptp_rate = 125000000;
+
+ plat->dma_cfg->pbl = 32;
+ plat->dma_cfg->pblx8 = true;
+
+ switch (ld->loongson_id) {
+ case DWMAC_CORE_MULTICHAN_V1:
+ ld->multichan = 1;
+ plat->rx_queues_to_use = 8;
+ plat->tx_queues_to_use = 8;
+
+ /* Only channel 0 supports checksum,
+ * so turn off checksum to enable multiple channels.
+ */
+ for (int i = 1; i < 8; i++)
+ plat->tx_queues_cfg[i].coe_unsupported = 1;
+
+ break;
+ case DWMAC_CORE_MULTICHAN_V2:
+ ld->multichan = 1;
+ plat->rx_queues_to_use = 4;
+ plat->tx_queues_to_use = 4;
+ break;
+ default:
+ ld->multichan = 0;
+ break;
+ }
+}
+
+static int loongson_gmac_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ loongson_default_data(pdev, plat);
+
+ plat->phy_interface = PHY_INTERFACE_MODE_RGMII_ID;
+
+ return 0;
+}
+
+static struct stmmac_pci_info loongson_gmac_pci_info = {
+ .setup = loongson_gmac_data,
+};
+
+static void loongson_gnet_fix_speed(void *priv, phy_interface_t interface,
+ int speed, unsigned int mode)
+{
+ struct loongson_data *ld = (struct loongson_data *)priv;
+ struct net_device *ndev = dev_get_drvdata(ld->dev);
+ struct stmmac_priv *ptr = netdev_priv(ndev);
+
+ /* The integrated PHY has a weird problem with switching from the low
+ * speeds to 1000Mbps mode. The speedup procedure requires the PHY-link
+ * re-negotiation.
+ */
+ if (speed == SPEED_1000) {
+ if (readl(ptr->ioaddr + MAC_CTRL_REG) &
+ GMAC_CONTROL_PS)
+ /* Word around hardware bug, restart autoneg */
+ phy_restart_aneg(ndev->phydev);
+ }
+}
+
+static int loongson_gnet_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ loongson_default_data(pdev, plat);
+
+ plat->phy_interface = PHY_INTERFACE_MODE_GMII;
+ plat->mdio_bus_data->phy_mask = ~BIT_U32(2);
+ plat->fix_mac_speed = loongson_gnet_fix_speed;
+
+ return 0;
+}
+
+static struct stmmac_pci_info loongson_gnet_pci_info = {
+ .setup = loongson_gnet_data,
+};
+
+static void loongson_dwmac_dma_init_channel(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ u32 chan)
+{
+ int txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
+ int rxpbl = dma_cfg->rxpbl ?: dma_cfg->pbl;
+ u32 value;
+
+ value = readl(ioaddr + DMA_CHAN_BUS_MODE(chan));
+
+ if (dma_cfg->pblx8)
+ value |= DMA_BUS_MODE_MAXPBL;
+
+ value |= DMA_BUS_MODE_USP;
+ value = u32_replace_bits(value, txpbl, DMA_BUS_MODE_PBL_MASK);
+ value = u32_replace_bits(value, rxpbl, DMA_BUS_MODE_RPBL_MASK);
+
+ /* Set the Fixed burst mode */
+ if (dma_cfg->fixed_burst)
+ value |= DMA_BUS_MODE_FB;
+
+ /* Mixed Burst has no effect when fb is set */
+ if (dma_cfg->mixed_burst)
+ value |= DMA_BUS_MODE_MB;
+
+ if (dma_cfg->atds)
+ value |= DMA_BUS_MODE_ATDS;
+
+ if (dma_cfg->aal)
+ value |= DMA_BUS_MODE_AAL;
+
+ writel(value, ioaddr + DMA_CHAN_BUS_MODE(chan));
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(DMA_INTR_DEFAULT_MASK_LOONGSON, ioaddr +
+ DMA_CHAN_INTR_ENA(chan));
+}
+
+static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_extra_stats *x,
+ u32 chan, u32 dir)
+{
+ struct stmmac_pcpu_stats *stats = this_cpu_ptr(priv->xstats.pcpu_stats);
+ u32 abnor_intr_status;
+ u32 nor_intr_status;
+ u32 fb_intr_status;
+ u32 intr_status;
+ int ret = 0;
+
+ /* read the status register (CSR5) */
+ intr_status = readl(ioaddr + DMA_CHAN_STATUS(chan));
+
+ if (dir == DMA_DIR_RX)
+ intr_status &= DMA_STATUS_MSK_RX_LOONGSON;
+ else if (dir == DMA_DIR_TX)
+ intr_status &= DMA_STATUS_MSK_TX_LOONGSON;
+
+ nor_intr_status = intr_status & (DMA_STATUS_NIS_TX_LOONGSON |
+ DMA_STATUS_NIS_RX_LOONGSON);
+ abnor_intr_status = intr_status & (DMA_STATUS_AIS_TX_LOONGSON |
+ DMA_STATUS_AIS_RX_LOONGSON);
+ fb_intr_status = intr_status & (DMA_STATUS_FBI_TX_LOONGSON |
+ DMA_STATUS_FBI_RX_LOONGSON);
+
+ /* ABNORMAL interrupts */
+ if (unlikely(abnor_intr_status)) {
+ if (unlikely(intr_status & DMA_STATUS_UNF)) {
+ ret = tx_hard_error_bump_tc;
+ x->tx_undeflow_irq++;
+ }
+ if (unlikely(intr_status & DMA_STATUS_TJT))
+ x->tx_jabber_irq++;
+ if (unlikely(intr_status & DMA_STATUS_OVF))
+ x->rx_overflow_irq++;
+ if (unlikely(intr_status & DMA_STATUS_RU))
+ x->rx_buf_unav_irq++;
+ if (unlikely(intr_status & DMA_STATUS_RPS))
+ x->rx_process_stopped_irq++;
+ if (unlikely(intr_status & DMA_STATUS_RWT))
+ x->rx_watchdog_irq++;
+ if (unlikely(intr_status & DMA_STATUS_ETI))
+ x->tx_early_irq++;
+ if (unlikely(intr_status & DMA_STATUS_TPS)) {
+ x->tx_process_stopped_irq++;
+ ret = tx_hard_error;
+ }
+ if (unlikely(fb_intr_status)) {
+ x->fatal_bus_error_irq++;
+ ret = tx_hard_error;
+ }
+ }
+ /* TX/RX NORMAL interrupts */
+ if (likely(nor_intr_status)) {
+ if (likely(intr_status & DMA_STATUS_RI)) {
+ u32 value = readl(ioaddr + DMA_INTR_ENA);
+ /* to schedule NAPI on real RIE event. */
+ if (likely(value & DMA_INTR_ENA_RIE)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->rx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_rx;
+ }
+ }
+ if (likely(intr_status & DMA_STATUS_TI)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->tx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_tx;
+ }
+ if (unlikely(intr_status & DMA_STATUS_ERI))
+ x->rx_early_irq++;
+ }
+ /* Optional hardware blocks, interrupts should be disabled */
+ if (unlikely(intr_status &
+ (DMA_STATUS_GPI | DMA_STATUS_GMI | DMA_STATUS_GLI)))
+ pr_warn("%s: unexpected status %08x\n", __func__, intr_status);
+
+ /* Clear the interrupt by writing a logic 1 to the CSR5[19-0] */
+ writel((intr_status & 0x7ffff), ioaddr + DMA_CHAN_STATUS(chan));
+
+ return ret;
+}
+
+static int loongson_dwmac_setup(void *apriv, struct mac_device_info *mac)
+{
+ struct stmmac_priv *priv = apriv;
+ struct stmmac_dma_ops *dma;
+ struct loongson_data *ld;
+ struct pci_dev *pdev;
+
+ ld = priv->plat->bsp_priv;
+ pdev = to_pci_dev(priv->device);
+
+ dma = devm_kzalloc(priv->device, sizeof(*dma), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+
+ /* The Loongson GMAC and GNET devices are based on the DW GMAC
+ * v3.50a and v3.73a IP-cores. But the HW designers have changed
+ * the GMAC_VERSION.SNPSVER field to the custom 0x10/0x12 value
+ * on the network controllers with the multi-channels feature
+ * available to emphasize the differences: multiple DMA-channels,
+ * AV feature and GMAC_INT_STATUS CSR flags layout. Get back the
+ * original value so the correct HW-interface would be selected.
+ */
+ if (ld->multichan) {
+ priv->synopsys_id = DWMAC_CORE_3_70;
+ *dma = dwmac1000_dma_ops;
+ dma->init_chan = loongson_dwmac_dma_init_channel;
+ dma->dma_interrupt = loongson_dwmac_dma_interrupt;
+ mac->dma = dma;
+ }
+
+ priv->dev->priv_flags |= IFF_UNICAST_FLT;
+
+ /* Pre-initialize the respective "mac" fields as it's done in
+ * dwmac1000_setup()
+ */
+ mac->pcsr = priv->ioaddr;
+ mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
+ mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
+ mac->mcast_bits_log2 = 0;
+
+ if (mac->multicast_filter_bins)
+ mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
+
+ /* Loongson GMAC doesn't support the flow control. Loongson GNET
+ * without multi-channel doesn't support the half-duplex link mode.
+ */
+ if (pdev->device != PCI_DEVICE_ID_LOONGSON_GNET) {
+ mac->link.caps = MAC_10 | MAC_100 | MAC_1000;
+ } else {
+ if (ld->multichan)
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000;
+ else
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10FD | MAC_100FD | MAC_1000FD;
+ }
+
+ mac->link.duplex = GMAC_CONTROL_DM;
+ mac->link.speed10 = GMAC_CONTROL_PS;
+ mac->link.speed100 = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
+ mac->link.speed1000 = 0;
+ mac->link.speed_mask = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
+ mac->mii.addr = GMAC_MII_ADDR;
+ mac->mii.data = GMAC_MII_DATA;
+ mac->mii.addr_mask = GENMASK_U32(15, 11);
+ mac->mii.reg_mask = GENMASK_U32(10, 6);
+ mac->mii.clk_csr_mask = GENMASK_U32(5, 2);
+
+ return 0;
+}
+
+static int loongson_dwmac_msi_config(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ int i, ch_num, ret, vecs;
+
+ ch_num = min(plat->tx_queues_to_use, plat->rx_queues_to_use);
+
+ vecs = roundup_pow_of_two(ch_num * 2 + 1);
+ ret = pci_alloc_irq_vectors(pdev, vecs, vecs, PCI_IRQ_MSI);
+ if (ret < 0) {
+ dev_warn(&pdev->dev, "Failed to allocate MSI IRQs\n");
+ return ret;
+ }
+
+ res->irq = pci_irq_vector(pdev, 0);
+
+ for (i = 0; i < ch_num; i++) {
+ res->rx_irq[ch_num - 1 - i] = pci_irq_vector(pdev, 1 + i * 2);
+ }
+
+ for (i = 0; i < ch_num; i++) {
+ res->tx_irq[ch_num - 1 - i] = pci_irq_vector(pdev, 2 + i * 2);
+ }
+
+ plat->flags |= STMMAC_FLAG_MULTI_MSI_EN;
+
+ return 0;
+}
+
+static void loongson_dwmac_msi_clear(struct pci_dev *pdev)
+{
+ pci_free_irq_vectors(pdev);
+}
+
+static int loongson_dwmac_dt_config(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ struct device_node *np = dev_of_node(&pdev->dev);
+ int ret;
+
+ plat->mdio_node = of_get_child_by_name(np, "mdio");
+ if (plat->mdio_node) {
+ dev_info(&pdev->dev, "Found MDIO subnode\n");
+ plat->mdio_bus_data->needs_reset = true;
+ }
+
+ ret = of_alias_get_id(np, "ethernet");
+ if (ret >= 0)
+ plat->bus_id = ret;
+
+ res->irq = of_irq_get_byname(np, "macirq");
+ if (res->irq < 0) {
+ dev_err(&pdev->dev, "IRQ macirq not found\n");
+ ret = -ENODEV;
+ goto err_put_node;
+ }
+
+ res->wol_irq = of_irq_get_byname(np, "eth_wake_irq");
+ if (res->wol_irq < 0) {
+ dev_info(&pdev->dev,
+ "IRQ eth_wake_irq not found, using macirq\n");
+ res->wol_irq = res->irq;
+ }
+
+ ret = device_get_phy_mode(&pdev->dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "phy_mode not found\n");
+ ret = -ENODEV;
+ goto err_put_node;
+ }
+
+ plat->phy_interface = ret;
+
+ return 0;
+
+err_put_node:
+ of_node_put(plat->mdio_node);
+
+ return ret;
+}
+
+static void loongson_dwmac_dt_clear(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ of_node_put(plat->mdio_node);
+}
+
+static int loongson_dwmac_acpi_config(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ if (!pdev->irq)
+ return -EINVAL;
+
+ res->irq = pdev->irq;
+
+ return 0;
+}
+
+/* Loongson's DWMAC device may take nearly two seconds to complete DMA reset */
+static int loongson_dwmac_fix_reset(struct stmmac_priv *priv)
+{
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 value;
+
+ value = readl(ioaddr + DMA_BUS_MODE);
+ if (value & DMA_BUS_MODE_SFT_RESET) {
+ netdev_err(priv->dev, "the PHY clock is missing\n");
+ return -EINVAL;
+ }
+
+ value |= DMA_BUS_MODE_SFT_RESET;
+ writel(value, ioaddr + DMA_BUS_MODE);
+
+ return readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+ !(value & DMA_BUS_MODE_SFT_RESET),
+ 10000, 2000000);
+}
+
+static int loongson_dwmac_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct plat_stmmacenet_data *plat;
+ struct stmmac_resources res = {};
+ struct stmmac_pci_info *info;
+ struct loongson_data *ld;
+ int ret;
+
+ plat = stmmac_plat_dat_alloc(&pdev->dev);
+ if (!plat)
+ return -ENOMEM;
+
+ plat->mdio_bus_data = devm_kzalloc(&pdev->dev,
+ sizeof(*plat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat->mdio_bus_data)
+ return -ENOMEM;
+
+ ld = devm_kzalloc(&pdev->dev, sizeof(*ld), GFP_KERNEL);
+ if (!ld)
+ return -ENOMEM;
+
+ /* Enable pci device */
+ ret = pci_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: ERROR: failed to enable device\n", __func__);
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ /* Get the base address of device */
+ res.addr = pcim_iomap_region(pdev, 0, DRIVER_NAME);
+ ret = PTR_ERR_OR_ZERO(res.addr);
+ if (ret)
+ goto err_disable_device;
+
+ plat->bsp_priv = ld;
+ plat->mac_setup = loongson_dwmac_setup;
+ plat->fix_soc_reset = loongson_dwmac_fix_reset;
+ plat->suspend = stmmac_pci_plat_suspend;
+ plat->resume = stmmac_pci_plat_resume;
+ ld->dev = &pdev->dev;
+ ld->loongson_id = readl(res.addr + GMAC_VERSION) & 0xff;
+
+ info = (struct stmmac_pci_info *)id->driver_data;
+ ret = info->setup(pdev, plat);
+ if (ret)
+ goto err_disable_device;
+
+ plat->tx_fifo_size = SZ_16K * plat->tx_queues_to_use;
+ plat->rx_fifo_size = SZ_16K * plat->rx_queues_to_use;
+
+ if (dev_of_node(&pdev->dev))
+ ret = loongson_dwmac_dt_config(pdev, plat, &res);
+ else
+ ret = loongson_dwmac_acpi_config(pdev, plat, &res);
+ if (ret)
+ goto err_disable_device;
+
+ /* Use the common MAC IRQ if per-channel MSIs allocation failed */
+ if (ld->multichan)
+ loongson_dwmac_msi_config(pdev, plat, &res);
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat, &res);
+ if (ret)
+ goto err_plat_clear;
+
+ return 0;
+
+err_plat_clear:
+ if (dev_of_node(&pdev->dev))
+ loongson_dwmac_dt_clear(pdev, plat);
+ if (ld->multichan)
+ loongson_dwmac_msi_clear(pdev);
+err_disable_device:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void loongson_dwmac_remove(struct pci_dev *pdev)
+{
+ struct net_device *ndev = dev_get_drvdata(&pdev->dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct loongson_data *ld;
+
+ ld = priv->plat->bsp_priv;
+ stmmac_dvr_remove(&pdev->dev);
+
+ if (dev_of_node(&pdev->dev))
+ loongson_dwmac_dt_clear(pdev, priv->plat);
+
+ if (ld->multichan)
+ loongson_dwmac_msi_clear(pdev);
+
+ pci_disable_device(pdev);
+}
+
+static const struct pci_device_id loongson_dwmac_id_table[] = {
+ { PCI_DEVICE_DATA(LOONGSON, GMAC1, &loongson_gmac_pci_info) },
+ { PCI_DEVICE_DATA(LOONGSON, GMAC2, &loongson_gmac_pci_info) },
+ { PCI_DEVICE_DATA(LOONGSON, GNET, &loongson_gnet_pci_info) },
+ {}
+};
+MODULE_DEVICE_TABLE(pci, loongson_dwmac_id_table);
+
+static struct pci_driver loongson_dwmac_driver = {
+ .name = DRIVER_NAME,
+ .id_table = loongson_dwmac_id_table,
+ .probe = loongson_dwmac_probe,
+ .remove = loongson_dwmac_remove,
+ .driver = {
+ .pm = &stmmac_simple_pm_ops,
+ },
+};
+
+module_pci_driver(loongson_dwmac_driver);
+
+MODULE_DESCRIPTION("Loongson DWMAC PCI driver");
+MODULE_AUTHOR("Qing Zhang <zhangqing@loongson.cn>");
+MODULE_AUTHOR("Yanteng Si <siyanteng@loongson.cn>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c
new file mode 100644
index 000000000..de9aba756
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson1.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Loongson-1 DWMAC glue layer
+ *
+ * Copyright (C) 2011-2023 Keguang Zhang <keguang.zhang@gmail.com>
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+#define LS1B_GMAC0_BASE (0x1fe10000)
+#define LS1B_GMAC1_BASE (0x1fe20000)
+
+/* Loongson-1 SYSCON Registers */
+#define LS1X_SYSCON0 (0x0)
+#define LS1X_SYSCON1 (0x4)
+
+/* Loongson-1B SYSCON Register Bits */
+#define GMAC1_USE_UART1 BIT(4)
+#define GMAC1_USE_UART0 BIT(3)
+
+#define GMAC1_SHUT BIT(13)
+#define GMAC0_SHUT BIT(12)
+
+#define GMAC1_USE_TXCLK BIT(3)
+#define GMAC0_USE_TXCLK BIT(2)
+#define GMAC1_USE_PWM23 BIT(1)
+#define GMAC0_USE_PWM01 BIT(0)
+
+/* Loongson-1C SYSCON Register Bits */
+#define GMAC_SHUT BIT(6)
+
+#define PHY_INTF_SELI GENMASK(30, 28)
+
+struct ls1x_dwmac {
+ struct plat_stmmacenet_data *plat_dat;
+ struct regmap *regmap;
+ unsigned int id;
+};
+
+struct ls1x_data {
+ int (*setup)(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat);
+ int (*init)(struct device *dev, void *bsp_priv);
+};
+
+static int ls1b_dwmac_setup(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat)
+{
+ struct ls1x_dwmac *dwmac = plat_dat->bsp_priv;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ /* This shouldn't fail - stmmac_get_platform_resources()
+ * already mapped this resource.
+ */
+ dev_err(&pdev->dev, "Could not get IO_MEM resources\n");
+ return -EINVAL;
+ }
+
+ if (res->start == LS1B_GMAC0_BASE) {
+ dwmac->id = 0;
+ } else if (res->start == LS1B_GMAC1_BASE) {
+ dwmac->id = 1;
+ } else {
+ dev_err(&pdev->dev, "Invalid Ethernet MAC base address %pR",
+ res);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1b_dwmac_syscon_init(struct device *dev, void *priv)
+{
+ struct ls1x_dwmac *dwmac = priv;
+ struct plat_stmmacenet_data *plat = dwmac->plat_dat;
+ struct regmap *regmap = dwmac->regmap;
+
+ if (dwmac->id == 0) {
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ regmap_update_bits(regmap, LS1X_SYSCON0,
+ GMAC0_USE_TXCLK | GMAC0_USE_PWM01,
+ 0);
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ regmap_update_bits(regmap, LS1X_SYSCON0,
+ GMAC0_USE_TXCLK | GMAC0_USE_PWM01,
+ GMAC0_USE_TXCLK | GMAC0_USE_PWM01);
+ break;
+ default:
+ dev_err(dev, "Unsupported PHY mode %u\n",
+ plat->phy_interface);
+ return -EOPNOTSUPP;
+ }
+
+ regmap_update_bits(regmap, LS1X_SYSCON0, GMAC0_SHUT, 0);
+ } else if (dwmac->id == 1) {
+ regmap_update_bits(regmap, LS1X_SYSCON0,
+ GMAC1_USE_UART1 | GMAC1_USE_UART0,
+ GMAC1_USE_UART1 | GMAC1_USE_UART0);
+
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ regmap_update_bits(regmap, LS1X_SYSCON1,
+ GMAC1_USE_TXCLK | GMAC1_USE_PWM23,
+ 0);
+
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ regmap_update_bits(regmap, LS1X_SYSCON1,
+ GMAC1_USE_TXCLK | GMAC1_USE_PWM23,
+ GMAC1_USE_TXCLK | GMAC1_USE_PWM23);
+ break;
+ default:
+ dev_err(dev, "Unsupported PHY mode %u\n",
+ plat->phy_interface);
+ return -EOPNOTSUPP;
+ }
+
+ regmap_update_bits(regmap, LS1X_SYSCON1, GMAC1_SHUT, 0);
+ }
+
+ return 0;
+}
+
+static int ls1c_dwmac_syscon_init(struct device *dev, void *priv)
+{
+ struct ls1x_dwmac *dwmac = priv;
+ struct plat_stmmacenet_data *plat = dwmac->plat_dat;
+ struct regmap *regmap = dwmac->regmap;
+ int phy_intf_sel;
+
+ phy_intf_sel = stmmac_get_phy_intf_sel(plat->phy_interface);
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII) {
+ dev_err(dev, "Unsupported PHY-mode %u\n",
+ plat->phy_interface);
+ return -EOPNOTSUPP;
+ }
+
+ regmap_update_bits(regmap, LS1X_SYSCON1, PHY_INTF_SELI,
+ FIELD_PREP(PHY_INTF_SELI, phy_intf_sel));
+ regmap_update_bits(regmap, LS1X_SYSCON0, GMAC0_SHUT, 0);
+
+ return 0;
+}
+
+static int ls1x_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ const struct ls1x_data *data;
+ struct regmap *regmap;
+ struct ls1x_dwmac *dwmac;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ /* Probe syscon */
+ regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "loongson,ls1-syscon");
+ if (IS_ERR(regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(regmap),
+ "Unable to find syscon\n");
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "No of match data provided\n");
+ return -EINVAL;
+ }
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(&pdev->dev, PTR_ERR(plat_dat),
+ "dt configuration failed\n");
+
+ plat_dat->bsp_priv = dwmac;
+ plat_dat->init = data->init;
+ dwmac->plat_dat = plat_dat;
+ dwmac->regmap = regmap;
+
+ if (data->setup) {
+ ret = data->setup(pdev, plat_dat);
+ if (ret)
+ return ret;
+ }
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct ls1x_data ls1b_dwmac_data = {
+ .setup = ls1b_dwmac_setup,
+ .init = ls1b_dwmac_syscon_init,
+};
+
+static const struct ls1x_data ls1c_dwmac_data = {
+ .init = ls1c_dwmac_syscon_init,
+};
+
+static const struct of_device_id ls1x_dwmac_match[] = {
+ {
+ .compatible = "loongson,ls1b-gmac",
+ .data = &ls1b_dwmac_data,
+ },
+ {
+ .compatible = "loongson,ls1c-emac",
+ .data = &ls1c_dwmac_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ls1x_dwmac_match);
+
+static struct platform_driver ls1x_dwmac_driver = {
+ .probe = ls1x_dwmac_probe,
+ .driver = {
+ .name = "loongson1-dwmac",
+ .of_match_table = ls1x_dwmac_match,
+ },
+};
+module_platform_driver(ls1x_dwmac_driver);
+
+MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
+MODULE_DESCRIPTION("Loongson-1 DWMAC glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c
new file mode 100644
index 000000000..c68d7de1f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c
@@ -0,0 +1,90 @@
+/*
+ * DWMAC glue for NXP LPC18xx/LPC43xx Ethernet
+ *
+ * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+/* Register defines for CREG syscon */
+#define LPC18XX_CREG_CREG6 0x12c
+# define LPC18XX_CREG_CREG6_ETHMODE_MASK GENMASK(2, 0)
+
+static int lpc18xx_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel)
+{
+ struct regmap *reg = bsp_priv;
+
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII)
+ return -EINVAL;
+
+ regmap_update_bits(reg, LPC18XX_CREG_CREG6,
+ LPC18XX_CREG_CREG6_ETHMODE_MASK,
+ FIELD_PREP(LPC18XX_CREG_CREG6_ETHMODE_MASK,
+ phy_intf_sel));
+
+ return 0;
+}
+
+static int lpc18xx_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct regmap *regmap;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ plat_dat->core_type = DWMAC_CORE_GMAC;
+
+ regmap = syscon_regmap_lookup_by_compatible("nxp,lpc1850-creg");
+ if (IS_ERR(regmap)) {
+ dev_err(&pdev->dev, "syscon lookup failed\n");
+ return PTR_ERR(regmap);
+ }
+
+ plat_dat->bsp_priv = regmap;
+ plat_dat->set_phy_intf_sel = lpc18xx_set_phy_intf_sel;
+
+ return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id lpc18xx_dwmac_match[] = {
+ { .compatible = "nxp,lpc1850-dwmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lpc18xx_dwmac_match);
+
+static struct platform_driver lpc18xx_dwmac_driver = {
+ .probe = lpc18xx_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "lpc18xx-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = lpc18xx_dwmac_match,
+ },
+};
+module_platform_driver(lpc18xx_dwmac_driver);
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_DESCRIPTION("DWMAC glue for LPC18xx/43xx Ethernet");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c
new file mode 100644
index 000000000..30ae0dba7
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c
@@ -0,0 +1,685 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+/* Peri Configuration register for mt2712 */
+#define PERI_ETH_PHY_INTF_SEL 0x418
+#define RMII_CLK_SRC_RXC BIT(4)
+#define RMII_CLK_SRC_INTERNAL BIT(5)
+
+#define PERI_ETH_DLY 0x428
+#define ETH_DLY_GTXC_INV BIT(6)
+#define ETH_DLY_GTXC_ENABLE BIT(5)
+#define ETH_DLY_GTXC_STAGES GENMASK(4, 0)
+#define ETH_DLY_TXC_INV BIT(20)
+#define ETH_DLY_TXC_ENABLE BIT(19)
+#define ETH_DLY_TXC_STAGES GENMASK(18, 14)
+#define ETH_DLY_RXC_INV BIT(13)
+#define ETH_DLY_RXC_ENABLE BIT(12)
+#define ETH_DLY_RXC_STAGES GENMASK(11, 7)
+
+#define PERI_ETH_DLY_FINE 0x800
+#define ETH_RMII_DLY_TX_INV BIT(2)
+#define ETH_FINE_DLY_GTXC BIT(1)
+#define ETH_FINE_DLY_RXC BIT(0)
+
+/* Peri Configuration register for mt8195 */
+#define MT8195_PERI_ETH_CTRL0 0xFD0
+#define MT8195_RMII_CLK_SRC_INTERNAL BIT(28)
+#define MT8195_RMII_CLK_SRC_RXC BIT(27)
+#define MT8195_ETH_INTF_SEL GENMASK(26, 24)
+#define MT8195_RGMII_TXC_PHASE_CTRL BIT(22)
+#define MT8195_EXT_PHY_MODE BIT(21)
+#define MT8195_DLY_GTXC_INV BIT(12)
+#define MT8195_DLY_GTXC_ENABLE BIT(5)
+#define MT8195_DLY_GTXC_STAGES GENMASK(4, 0)
+
+#define MT8195_PERI_ETH_CTRL1 0xFD4
+#define MT8195_DLY_RXC_INV BIT(25)
+#define MT8195_DLY_RXC_ENABLE BIT(18)
+#define MT8195_DLY_RXC_STAGES GENMASK(17, 13)
+#define MT8195_DLY_TXC_INV BIT(12)
+#define MT8195_DLY_TXC_ENABLE BIT(5)
+#define MT8195_DLY_TXC_STAGES GENMASK(4, 0)
+
+#define MT8195_PERI_ETH_CTRL2 0xFD8
+#define MT8195_DLY_RMII_RXC_INV BIT(25)
+#define MT8195_DLY_RMII_RXC_ENABLE BIT(18)
+#define MT8195_DLY_RMII_RXC_STAGES GENMASK(17, 13)
+#define MT8195_DLY_RMII_TXC_INV BIT(12)
+#define MT8195_DLY_RMII_TXC_ENABLE BIT(5)
+#define MT8195_DLY_RMII_TXC_STAGES GENMASK(4, 0)
+
+struct mac_delay_struct {
+ u32 tx_delay;
+ u32 rx_delay;
+ bool tx_inv;
+ bool rx_inv;
+};
+
+struct mediatek_dwmac_plat_data {
+ const struct mediatek_dwmac_variant *variant;
+ struct mac_delay_struct mac_delay;
+ struct clk *rmii_internal_clk;
+ struct clk_bulk_data *clks;
+ struct regmap *peri_regmap;
+ struct device_node *np;
+ struct device *dev;
+ phy_interface_t phy_mode;
+ bool rmii_clk_from_mac;
+ bool rmii_rxc;
+ bool mac_wol;
+};
+
+struct mediatek_dwmac_variant {
+ int (*dwmac_set_phy_interface)(struct mediatek_dwmac_plat_data *plat,
+ u8 phy_intf_sel);
+ int (*dwmac_set_delay)(struct mediatek_dwmac_plat_data *plat);
+
+ /* clock ids to be requested */
+ const char * const *clk_list;
+ int num_clks;
+
+ u32 rx_delay_max;
+ u32 tx_delay_max;
+ u8 dma_bit_mask;
+};
+
+/* list of clocks required for mac */
+static const char * const mt2712_dwmac_clk_l[] = {
+ "axi", "apb", "mac_main", "ptp_ref"
+};
+
+static const char * const mt8195_dwmac_clk_l[] = {
+ "axi", "apb", "mac_cg", "mac_main", "ptp_ref"
+};
+
+static int mt2712_set_interface(struct mediatek_dwmac_plat_data *plat,
+ u8 phy_intf_sel)
+{
+ u32 intf_val = phy_intf_sel;
+
+ if (phy_intf_sel == PHY_INTF_SEL_RMII) {
+ if (plat->rmii_clk_from_mac)
+ intf_val |= RMII_CLK_SRC_INTERNAL;
+ if (plat->rmii_rxc)
+ intf_val |= RMII_CLK_SRC_RXC;
+ }
+
+ regmap_write(plat->peri_regmap, PERI_ETH_PHY_INTF_SEL, intf_val);
+
+ return 0;
+}
+
+static void mt2712_delay_ps2stage(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+
+ switch (plat->phy_mode) {
+ case PHY_INTERFACE_MODE_MII:
+ case PHY_INTERFACE_MODE_RMII:
+ /* 550ps per stage for MII/RMII */
+ mac_delay->tx_delay /= 550;
+ mac_delay->rx_delay /= 550;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ /* 170ps per stage for RGMII */
+ mac_delay->tx_delay /= 170;
+ mac_delay->rx_delay /= 170;
+ break;
+ default:
+ dev_err(plat->dev, "phy interface not supported\n");
+ break;
+ }
+}
+
+static void mt2712_delay_stage2ps(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+
+ switch (plat->phy_mode) {
+ case PHY_INTERFACE_MODE_MII:
+ case PHY_INTERFACE_MODE_RMII:
+ /* 550ps per stage for MII/RMII */
+ mac_delay->tx_delay *= 550;
+ mac_delay->rx_delay *= 550;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ /* 170ps per stage for RGMII */
+ mac_delay->tx_delay *= 170;
+ mac_delay->rx_delay *= 170;
+ break;
+ default:
+ dev_err(plat->dev, "phy interface not supported\n");
+ break;
+ }
+}
+
+static int mt2712_set_delay(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+ u32 delay_val = 0, fine_val = 0;
+
+ mt2712_delay_ps2stage(plat);
+
+ switch (plat->phy_mode) {
+ case PHY_INTERFACE_MODE_MII:
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_ENABLE, !!mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_STAGES, mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_INV, mac_delay->tx_inv);
+
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_INV, mac_delay->rx_inv);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (plat->rmii_clk_from_mac) {
+ /* case 1: mac provides the rmii reference clock,
+ * and the clock output to TXC pin.
+ * The egress timing can be adjusted by GTXC delay macro circuit.
+ * The ingress timing can be adjusted by TXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_INV, mac_delay->rx_inv);
+
+ delay_val |= FIELD_PREP(ETH_DLY_GTXC_ENABLE, !!mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_GTXC_STAGES, mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_GTXC_INV, mac_delay->tx_inv);
+ } else {
+ /* case 2: the rmii reference clock is from external phy,
+ * and the property "rmii_rxc" indicates which pin(TXC/RXC)
+ * the reference clk is connected to. The reference clock is a
+ * received signal, so rx_delay/rx_inv are used to indicate
+ * the reference clock timing adjustment
+ */
+ if (plat->rmii_rxc) {
+ /* the rmii reference clock from outside is connected
+ * to RXC pin, the reference clock will be adjusted
+ * by RXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_INV, mac_delay->rx_inv);
+ } else {
+ /* the rmii reference clock from outside is connected
+ * to TXC pin, the reference clock will be adjusted
+ * by TXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_TXC_INV, mac_delay->rx_inv);
+ }
+ /* tx_inv will inverse the tx clock inside mac relateive to
+ * reference clock from external phy,
+ * and this bit is located in the same register with fine-tune
+ */
+ if (mac_delay->tx_inv)
+ fine_val = ETH_RMII_DLY_TX_INV;
+ }
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ fine_val = ETH_FINE_DLY_GTXC | ETH_FINE_DLY_RXC;
+
+ delay_val |= FIELD_PREP(ETH_DLY_GTXC_ENABLE, !!mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_GTXC_STAGES, mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_GTXC_INV, mac_delay->tx_inv);
+
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(ETH_DLY_RXC_INV, mac_delay->rx_inv);
+ break;
+ default:
+ dev_err(plat->dev, "phy interface not supported\n");
+ return -EINVAL;
+ }
+ regmap_write(plat->peri_regmap, PERI_ETH_DLY, delay_val);
+ regmap_write(plat->peri_regmap, PERI_ETH_DLY_FINE, fine_val);
+
+ mt2712_delay_stage2ps(plat);
+
+ return 0;
+}
+
+static const struct mediatek_dwmac_variant mt2712_gmac_variant = {
+ .dwmac_set_phy_interface = mt2712_set_interface,
+ .dwmac_set_delay = mt2712_set_delay,
+ .clk_list = mt2712_dwmac_clk_l,
+ .num_clks = ARRAY_SIZE(mt2712_dwmac_clk_l),
+ .rx_delay_max = 17600,
+ .tx_delay_max = 17600,
+ .dma_bit_mask = 33,
+};
+
+static int mt8195_set_interface(struct mediatek_dwmac_plat_data *plat,
+ u8 phy_intf_sel)
+{
+ u32 intf_val = FIELD_PREP(MT8195_ETH_INTF_SEL, phy_intf_sel);
+
+ if (phy_intf_sel == PHY_INTF_SEL_RMII) {
+ if (plat->rmii_clk_from_mac)
+ intf_val |= MT8195_RMII_CLK_SRC_INTERNAL;
+ if (plat->rmii_rxc)
+ intf_val |= MT8195_RMII_CLK_SRC_RXC;
+ }
+
+ /* MT8195 only support external PHY */
+ intf_val |= MT8195_EXT_PHY_MODE;
+
+ regmap_write(plat->peri_regmap, MT8195_PERI_ETH_CTRL0, intf_val);
+
+ return 0;
+}
+
+static void mt8195_delay_ps2stage(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+
+ /* 290ps per stage */
+ mac_delay->tx_delay /= 290;
+ mac_delay->rx_delay /= 290;
+}
+
+static void mt8195_delay_stage2ps(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+
+ /* 290ps per stage */
+ mac_delay->tx_delay *= 290;
+ mac_delay->rx_delay *= 290;
+}
+
+static int mt8195_set_delay(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+ u32 gtxc_delay_val = 0, delay_val = 0, rmii_delay_val = 0;
+
+ mt8195_delay_ps2stage(plat);
+
+ switch (plat->phy_mode) {
+ case PHY_INTERFACE_MODE_MII:
+ delay_val |= FIELD_PREP(MT8195_DLY_TXC_ENABLE, !!mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_TXC_STAGES, mac_delay->tx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_TXC_INV, mac_delay->tx_inv);
+
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_INV, mac_delay->rx_inv);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (plat->rmii_clk_from_mac) {
+ /* case 1: mac provides the rmii reference clock,
+ * and the clock output to TXC pin.
+ * The egress timing can be adjusted by RMII_TXC delay macro circuit.
+ * The ingress timing can be adjusted by RMII_RXC delay macro circuit.
+ */
+ rmii_delay_val |= FIELD_PREP(MT8195_DLY_RMII_TXC_ENABLE,
+ !!mac_delay->tx_delay);
+ rmii_delay_val |= FIELD_PREP(MT8195_DLY_RMII_TXC_STAGES,
+ mac_delay->tx_delay);
+ rmii_delay_val |= FIELD_PREP(MT8195_DLY_RMII_TXC_INV,
+ mac_delay->tx_inv);
+
+ rmii_delay_val |= FIELD_PREP(MT8195_DLY_RMII_RXC_ENABLE,
+ !!mac_delay->rx_delay);
+ rmii_delay_val |= FIELD_PREP(MT8195_DLY_RMII_RXC_STAGES,
+ mac_delay->rx_delay);
+ rmii_delay_val |= FIELD_PREP(MT8195_DLY_RMII_RXC_INV,
+ mac_delay->rx_inv);
+ } else {
+ /* case 2: the rmii reference clock is from external phy,
+ * and the property "rmii_rxc" indicates which pin(TXC/RXC)
+ * the reference clk is connected to. The reference clock is a
+ * received signal, so rx_delay/rx_inv are used to indicate
+ * the reference clock timing adjustment
+ */
+ if (plat->rmii_rxc) {
+ /* the rmii reference clock from outside is connected
+ * to RXC pin, the reference clock will be adjusted
+ * by RXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_ENABLE,
+ !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_STAGES,
+ mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_INV,
+ mac_delay->rx_inv);
+ } else {
+ /* the rmii reference clock from outside is connected
+ * to TXC pin, the reference clock will be adjusted
+ * by TXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(MT8195_DLY_TXC_ENABLE,
+ !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_TXC_STAGES,
+ mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_TXC_INV,
+ mac_delay->rx_inv);
+ }
+ }
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ gtxc_delay_val |= FIELD_PREP(MT8195_DLY_GTXC_ENABLE, !!mac_delay->tx_delay);
+ gtxc_delay_val |= FIELD_PREP(MT8195_DLY_GTXC_STAGES, mac_delay->tx_delay);
+ gtxc_delay_val |= FIELD_PREP(MT8195_DLY_GTXC_INV, mac_delay->tx_inv);
+
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_ENABLE, !!mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_STAGES, mac_delay->rx_delay);
+ delay_val |= FIELD_PREP(MT8195_DLY_RXC_INV, mac_delay->rx_inv);
+
+ break;
+ default:
+ dev_err(plat->dev, "phy interface not supported\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(plat->peri_regmap,
+ MT8195_PERI_ETH_CTRL0,
+ MT8195_RGMII_TXC_PHASE_CTRL |
+ MT8195_DLY_GTXC_INV |
+ MT8195_DLY_GTXC_ENABLE |
+ MT8195_DLY_GTXC_STAGES,
+ gtxc_delay_val);
+ regmap_write(plat->peri_regmap, MT8195_PERI_ETH_CTRL1, delay_val);
+ regmap_write(plat->peri_regmap, MT8195_PERI_ETH_CTRL2, rmii_delay_val);
+
+ mt8195_delay_stage2ps(plat);
+
+ return 0;
+}
+
+static const struct mediatek_dwmac_variant mt8195_gmac_variant = {
+ .dwmac_set_phy_interface = mt8195_set_interface,
+ .dwmac_set_delay = mt8195_set_delay,
+ .clk_list = mt8195_dwmac_clk_l,
+ .num_clks = ARRAY_SIZE(mt8195_dwmac_clk_l),
+ .rx_delay_max = 9280,
+ .tx_delay_max = 9280,
+ .dma_bit_mask = 35,
+};
+
+static int mediatek_dwmac_config_dt(struct mediatek_dwmac_plat_data *plat)
+{
+ struct mac_delay_struct *mac_delay = &plat->mac_delay;
+ u32 tx_delay_ps, rx_delay_ps;
+
+ plat->peri_regmap = syscon_regmap_lookup_by_phandle(plat->np, "mediatek,pericfg");
+ if (IS_ERR(plat->peri_regmap)) {
+ dev_err(plat->dev, "Failed to get pericfg syscon\n");
+ return PTR_ERR(plat->peri_regmap);
+ }
+
+ if (!of_property_read_u32(plat->np, "mediatek,tx-delay-ps", &tx_delay_ps)) {
+ if (tx_delay_ps < plat->variant->tx_delay_max) {
+ mac_delay->tx_delay = tx_delay_ps;
+ } else {
+ dev_err(plat->dev, "Invalid TX clock delay: %dps\n", tx_delay_ps);
+ return -EINVAL;
+ }
+ }
+
+ if (!of_property_read_u32(plat->np, "mediatek,rx-delay-ps", &rx_delay_ps)) {
+ if (rx_delay_ps < plat->variant->rx_delay_max) {
+ mac_delay->rx_delay = rx_delay_ps;
+ } else {
+ dev_err(plat->dev, "Invalid RX clock delay: %dps\n", rx_delay_ps);
+ return -EINVAL;
+ }
+ }
+
+ mac_delay->tx_inv = of_property_read_bool(plat->np, "mediatek,txc-inverse");
+ mac_delay->rx_inv = of_property_read_bool(plat->np, "mediatek,rxc-inverse");
+ plat->rmii_rxc = of_property_read_bool(plat->np, "mediatek,rmii-rxc");
+ plat->rmii_clk_from_mac = of_property_read_bool(plat->np, "mediatek,rmii-clk-from-mac");
+ plat->mac_wol = of_property_read_bool(plat->np, "mediatek,mac-wol");
+
+ return 0;
+}
+
+static int mediatek_dwmac_clk_init(struct mediatek_dwmac_plat_data *plat)
+{
+ const struct mediatek_dwmac_variant *variant = plat->variant;
+ int i, ret;
+
+ plat->clks = devm_kcalloc(plat->dev, variant->num_clks, sizeof(*plat->clks), GFP_KERNEL);
+ if (!plat->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < variant->num_clks; i++)
+ plat->clks[i].id = variant->clk_list[i];
+
+ ret = devm_clk_bulk_get(plat->dev, variant->num_clks, plat->clks);
+ if (ret)
+ return ret;
+
+ /* The clock labeled as "rmii_internal" is needed only in RMII(when
+ * MAC provides the reference clock), and useless for RGMII/MII or
+ * RMII(when PHY provides the reference clock).
+ * So, "rmii_internal" clock is got and configured only when
+ * reference clock of RMII is from MAC.
+ */
+ if (plat->rmii_clk_from_mac) {
+ plat->rmii_internal_clk = devm_clk_get(plat->dev, "rmii_internal");
+ if (IS_ERR(plat->rmii_internal_clk))
+ ret = PTR_ERR(plat->rmii_internal_clk);
+ } else {
+ plat->rmii_internal_clk = NULL;
+ }
+
+ return ret;
+}
+
+static int mediatek_dwmac_init(struct device *dev, void *priv)
+{
+ struct mediatek_dwmac_plat_data *plat = priv;
+ const struct mediatek_dwmac_variant *variant = plat->variant;
+ int phy_intf_sel, ret;
+
+ if (variant->dwmac_set_phy_interface) {
+ phy_intf_sel = stmmac_get_phy_intf_sel(plat->phy_mode);
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII) {
+ dev_err(plat->dev, "phy interface not supported\n");
+ return phy_intf_sel < 0 ? phy_intf_sel : -EINVAL;
+ }
+
+ ret = variant->dwmac_set_phy_interface(plat, phy_intf_sel);
+ if (ret) {
+ dev_err(dev, "failed to set phy interface, err = %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (variant->dwmac_set_delay) {
+ ret = variant->dwmac_set_delay(plat);
+ if (ret) {
+ dev_err(dev, "failed to set delay value, err = %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int mediatek_dwmac_clks_config(void *priv, bool enabled)
+{
+ struct mediatek_dwmac_plat_data *plat = priv;
+ const struct mediatek_dwmac_variant *variant = plat->variant;
+ int ret = 0;
+
+ if (enabled) {
+ ret = clk_bulk_prepare_enable(variant->num_clks, plat->clks);
+ if (ret) {
+ dev_err(plat->dev, "failed to enable clks, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(plat->rmii_internal_clk);
+ if (ret) {
+ dev_err(plat->dev, "failed to enable rmii internal clk, err = %d\n", ret);
+ return ret;
+ }
+ } else {
+ clk_disable_unprepare(plat->rmii_internal_clk);
+ clk_bulk_disable_unprepare(variant->num_clks, plat->clks);
+ }
+
+ return ret;
+}
+
+static int mediatek_dwmac_common_data(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct mediatek_dwmac_plat_data *priv_plat)
+{
+ int i;
+
+ priv_plat->phy_mode = plat->phy_interface;
+ if (priv_plat->mac_wol)
+ plat->flags &= ~STMMAC_FLAG_USE_PHY_WOL;
+ else
+ plat->flags |= STMMAC_FLAG_USE_PHY_WOL;
+ plat->riwt_off = true;
+ plat->maxmtu = ETH_DATA_LEN;
+ plat->host_dma_width = priv_plat->variant->dma_bit_mask;
+ plat->bsp_priv = priv_plat;
+ plat->resume = mediatek_dwmac_init;
+ plat->clks_config = mediatek_dwmac_clks_config;
+
+ plat->safety_feat_cfg = devm_kzalloc(&pdev->dev,
+ sizeof(*plat->safety_feat_cfg),
+ GFP_KERNEL);
+ if (!plat->safety_feat_cfg)
+ return -ENOMEM;
+
+ plat->safety_feat_cfg->tsoee = 1;
+ plat->safety_feat_cfg->mrxpee = 0;
+ plat->safety_feat_cfg->mestee = 1;
+ plat->safety_feat_cfg->mrxee = 1;
+ plat->safety_feat_cfg->mtxee = 1;
+ plat->safety_feat_cfg->epsi = 0;
+ plat->safety_feat_cfg->edpp = 1;
+ plat->safety_feat_cfg->prtyen = 1;
+ plat->safety_feat_cfg->tmouten = 1;
+
+ for (i = 0; i < plat->tx_queues_to_use; i++) {
+ /* Default TX Q0 to use TSO and rest TXQ for TBS */
+ if (i > 0)
+ plat->tx_queues_cfg[i].tbs_en = 1;
+ }
+
+ return 0;
+}
+
+static int mediatek_dwmac_probe(struct platform_device *pdev)
+{
+ struct mediatek_dwmac_plat_data *priv_plat;
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ int ret;
+
+ priv_plat = devm_kzalloc(&pdev->dev, sizeof(*priv_plat), GFP_KERNEL);
+ if (!priv_plat)
+ return -ENOMEM;
+
+ priv_plat->variant = of_device_get_match_data(&pdev->dev);
+ if (!priv_plat->variant) {
+ dev_err(&pdev->dev, "Missing dwmac-mediatek variant\n");
+ return -EINVAL;
+ }
+
+ priv_plat->dev = &pdev->dev;
+ priv_plat->np = pdev->dev.of_node;
+
+ ret = mediatek_dwmac_config_dt(priv_plat);
+ if (ret)
+ return ret;
+
+ ret = mediatek_dwmac_clk_init(priv_plat);
+ if (ret)
+ return ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ mediatek_dwmac_common_data(pdev, plat_dat, priv_plat);
+ mediatek_dwmac_init(&pdev->dev, priv_plat);
+
+ ret = mediatek_dwmac_clks_config(priv_plat, true);
+ if (ret)
+ return ret;
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+ if (ret)
+ goto err_drv_probe;
+
+ return 0;
+
+err_drv_probe:
+ mediatek_dwmac_clks_config(priv_plat, false);
+
+ return ret;
+}
+
+static void mediatek_dwmac_remove(struct platform_device *pdev)
+{
+ struct mediatek_dwmac_plat_data *priv_plat = get_stmmac_bsp_priv(&pdev->dev);
+
+ stmmac_pltfr_remove(pdev);
+ mediatek_dwmac_clks_config(priv_plat, false);
+}
+
+static const struct of_device_id mediatek_dwmac_match[] = {
+ { .compatible = "mediatek,mt2712-gmac",
+ .data = &mt2712_gmac_variant },
+ { .compatible = "mediatek,mt8195-gmac",
+ .data = &mt8195_gmac_variant },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, mediatek_dwmac_match);
+
+static struct platform_driver mediatek_dwmac_driver = {
+ .probe = mediatek_dwmac_probe,
+ .remove = mediatek_dwmac_remove,
+ .driver = {
+ .name = "dwmac-mediatek",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = mediatek_dwmac_match,
+ },
+};
+module_platform_driver(mediatek_dwmac_driver);
+
+MODULE_AUTHOR("Biao Huang <biao.huang@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek DWMAC specific glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson.c
new file mode 100644
index 000000000..07c504d07
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Amlogic Meson6 and Meson8 DWMAC glue layer
+ *
+ * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define ETHMAC_SPEED_100 BIT(1)
+
+struct meson_dwmac {
+ struct device *dev;
+ void __iomem *reg;
+};
+
+static int meson6_dwmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct meson_dwmac *dwmac = bsp_priv;
+ unsigned int val;
+
+ val = readl(dwmac->reg);
+
+ switch (speed) {
+ case SPEED_10:
+ val &= ~ETHMAC_SPEED_100;
+ break;
+ case SPEED_100:
+ val |= ETHMAC_SPEED_100;
+ break;
+ }
+
+ writel(val, dwmac->reg);
+
+ return 0;
+}
+
+static int meson6_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct meson_dwmac *dwmac;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ dwmac->reg = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(dwmac->reg))
+ return PTR_ERR(dwmac->reg);
+
+ plat_dat->bsp_priv = dwmac;
+ plat_dat->set_clk_tx_rate = meson6_dwmac_set_clk_tx_rate;
+
+ return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id meson6_dwmac_match[] = {
+ { .compatible = "amlogic,meson6-dwmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, meson6_dwmac_match);
+
+static struct platform_driver meson6_dwmac_driver = {
+ .probe = meson6_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "meson6-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = meson6_dwmac_match,
+ },
+};
+module_platform_driver(meson6_dwmac_driver);
+
+MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>");
+MODULE_DESCRIPTION("Amlogic Meson6 and Meson8 DWMAC glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
new file mode 100644
index 000000000..e4d5c4129
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Amlogic Meson8b, Meson8m2 and GXBB DWMAC glue layer
+ *
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define PRG_ETH0 0x0
+
+#define PRG_ETH0_RGMII_MODE BIT(0)
+
+#define PRG_ETH0_EXT_PHY_MODE_MASK GENMASK(2, 0)
+
+/* mux to choose between fclk_div2 (bit unset) and mpll2 (bit set) */
+#define PRG_ETH0_CLK_M250_SEL_MASK GENMASK(4, 4)
+
+/* TX clock delay in ns = "8ns / 4 * tx_dly_val" (where 8ns are exactly one
+ * cycle of the 125MHz RGMII TX clock):
+ * 0ns = 0x0, 2ns = 0x1, 4ns = 0x2, 6ns = 0x3
+ */
+#define PRG_ETH0_TXDLY_MASK GENMASK(6, 5)
+
+/* divider for the result of m250_sel */
+#define PRG_ETH0_CLK_M250_DIV_SHIFT 7
+#define PRG_ETH0_CLK_M250_DIV_WIDTH 3
+
+#define PRG_ETH0_RGMII_TX_CLK_EN 10
+
+#define PRG_ETH0_INVERTED_RMII_CLK BIT(11)
+#define PRG_ETH0_TX_AND_PHY_REF_CLK BIT(12)
+
+/* Bypass (= 0, the signal from the GPIO input directly connects to the
+ * internal sampling) or enable (= 1) the internal logic for RXEN and RXD[3:0]
+ * timing tuning.
+ */
+#define PRG_ETH0_ADJ_ENABLE BIT(13)
+/* Controls whether the RXEN and RXD[3:0] signals should be aligned with the
+ * input RX rising/falling edge and sent to the Ethernet internals. This sets
+ * the automatically delay and skew automatically (internally).
+ */
+#define PRG_ETH0_ADJ_SETUP BIT(14)
+/* An internal counter based on the "timing-adjustment" clock. The counter is
+ * cleared on both, the falling and rising edge of the RX_CLK. This selects the
+ * delay (= the counter value) when to start sampling RXEN and RXD[3:0].
+ */
+#define PRG_ETH0_ADJ_DELAY GENMASK(19, 15)
+/* Adjusts the skew between each bit of RXEN and RXD[3:0]. If a signal has a
+ * large input delay, the bit for that signal (RXEN = bit 0, RXD[3] = bit 1,
+ * ...) can be configured to be 1 to compensate for a delay of about 1ns.
+ */
+#define PRG_ETH0_ADJ_SKEW GENMASK(24, 20)
+
+#define PRG_ETH1 0x4
+
+/* Defined for adding a delay to the input RX_CLK for better timing.
+ * Each step is 200ps. These bits are used with external RGMII PHYs
+ * because RGMII RX only has the small window. cfg_rxclk_dly can
+ * adjust the window between RX_CLK and RX_DATA and improve the stability
+ * of "rx data valid".
+ */
+#define PRG_ETH1_CFG_RXCLK_DLY GENMASK(19, 16)
+
+struct meson8b_dwmac;
+
+struct meson8b_dwmac_data {
+ int (*set_phy_mode)(struct meson8b_dwmac *dwmac);
+ bool has_prg_eth1_rgmii_rx_delay;
+};
+
+struct meson8b_dwmac {
+ struct device *dev;
+ void __iomem *regs;
+
+ const struct meson8b_dwmac_data *data;
+ phy_interface_t phy_mode;
+ struct clk *rgmii_tx_clk;
+ u32 tx_delay_ns;
+ u32 rx_delay_ps;
+ struct clk *timing_adj_clk;
+};
+
+struct meson8b_dwmac_clk_configs {
+ struct clk_mux m250_mux;
+ struct clk_divider m250_div;
+ struct clk_fixed_factor fixed_div2;
+ struct clk_gate rgmii_tx_en;
+};
+
+static void meson8b_dwmac_mask_bits(struct meson8b_dwmac *dwmac, u32 reg,
+ u32 mask, u32 value)
+{
+ u32 data;
+
+ data = readl(dwmac->regs + reg);
+ data &= ~mask;
+ data |= (value & mask);
+
+ writel(data, dwmac->regs + reg);
+}
+
+static struct clk *meson8b_dwmac_register_clk(struct meson8b_dwmac *dwmac,
+ const char *name_suffix,
+ const struct clk_parent_data *parents,
+ int num_parents,
+ const struct clk_ops *ops,
+ struct clk_hw *hw)
+{
+ struct clk_init_data init = { };
+ char clk_name[32];
+
+ snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dwmac->dev),
+ name_suffix);
+
+ init.name = clk_name;
+ init.ops = ops;
+ init.flags = CLK_SET_RATE_PARENT;
+ init.parent_data = parents;
+ init.num_parents = num_parents;
+
+ hw->init = &init;
+
+ return devm_clk_register(dwmac->dev, hw);
+}
+
+static int meson8b_init_rgmii_tx_clk(struct meson8b_dwmac *dwmac)
+{
+ struct clk *clk;
+ struct device *dev = dwmac->dev;
+ static const struct clk_parent_data mux_parents[] = {
+ { .fw_name = "clkin0", },
+ { .index = -1, },
+ };
+ static const struct clk_div_table div_table[] = {
+ { .div = 2, .val = 2, },
+ { .div = 3, .val = 3, },
+ { .div = 4, .val = 4, },
+ { .div = 5, .val = 5, },
+ { .div = 6, .val = 6, },
+ { .div = 7, .val = 7, },
+ { /* end of array */ }
+ };
+ struct meson8b_dwmac_clk_configs *clk_configs;
+ struct clk_parent_data parent_data = { };
+
+ clk_configs = devm_kzalloc(dev, sizeof(*clk_configs), GFP_KERNEL);
+ if (!clk_configs)
+ return -ENOMEM;
+
+ clk_configs->m250_mux.reg = dwmac->regs + PRG_ETH0;
+ clk_configs->m250_mux.shift = __ffs(PRG_ETH0_CLK_M250_SEL_MASK);
+ clk_configs->m250_mux.mask = PRG_ETH0_CLK_M250_SEL_MASK >>
+ clk_configs->m250_mux.shift;
+ clk = meson8b_dwmac_register_clk(dwmac, "m250_sel", mux_parents,
+ ARRAY_SIZE(mux_parents), &clk_mux_ops,
+ &clk_configs->m250_mux.hw);
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ parent_data.hw = &clk_configs->m250_mux.hw;
+ clk_configs->m250_div.reg = dwmac->regs + PRG_ETH0;
+ clk_configs->m250_div.shift = PRG_ETH0_CLK_M250_DIV_SHIFT;
+ clk_configs->m250_div.width = PRG_ETH0_CLK_M250_DIV_WIDTH;
+ clk_configs->m250_div.table = div_table;
+ clk_configs->m250_div.flags = CLK_DIVIDER_ALLOW_ZERO |
+ CLK_DIVIDER_ROUND_CLOSEST;
+ clk = meson8b_dwmac_register_clk(dwmac, "m250_div", &parent_data, 1,
+ &clk_divider_ops,
+ &clk_configs->m250_div.hw);
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ parent_data.hw = &clk_configs->m250_div.hw;
+ clk_configs->fixed_div2.mult = 1;
+ clk_configs->fixed_div2.div = 2;
+ clk = meson8b_dwmac_register_clk(dwmac, "fixed_div2", &parent_data, 1,
+ &clk_fixed_factor_ops,
+ &clk_configs->fixed_div2.hw);
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ parent_data.hw = &clk_configs->fixed_div2.hw;
+ clk_configs->rgmii_tx_en.reg = dwmac->regs + PRG_ETH0;
+ clk_configs->rgmii_tx_en.bit_idx = PRG_ETH0_RGMII_TX_CLK_EN;
+ clk = meson8b_dwmac_register_clk(dwmac, "rgmii_tx_en", &parent_data, 1,
+ &clk_gate_ops,
+ &clk_configs->rgmii_tx_en.hw);
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ dwmac->rgmii_tx_clk = clk;
+
+ return 0;
+}
+
+static int meson8b_set_phy_mode(struct meson8b_dwmac *dwmac)
+{
+ switch (dwmac->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ /* enable RGMII mode */
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
+ PRG_ETH0_RGMII_MODE,
+ PRG_ETH0_RGMII_MODE);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ /* disable RGMII mode -> enables RMII mode */
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
+ PRG_ETH0_RGMII_MODE, 0);
+ break;
+ default:
+ dev_err(dwmac->dev, "fail to set phy-mode %s\n",
+ phy_modes(dwmac->phy_mode));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int meson_axg_set_phy_mode(struct meson8b_dwmac *dwmac)
+{
+ int phy_intf_sel;
+
+ phy_intf_sel = stmmac_get_phy_intf_sel(dwmac->phy_mode);
+ if (phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII) {
+ dev_err(dwmac->dev, "fail to set phy-mode %s\n",
+ phy_modes(dwmac->phy_mode));
+ return phy_intf_sel < 0 ? phy_intf_sel : -EINVAL;
+ }
+
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_EXT_PHY_MODE_MASK,
+ FIELD_PREP(PRG_ETH0_EXT_PHY_MODE_MASK,
+ phy_intf_sel));
+
+ return 0;
+}
+
+static void meson8b_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int meson8b_devm_clk_prepare_enable(struct meson8b_dwmac *dwmac,
+ struct clk *clk)
+{
+ int ret;
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dwmac->dev,
+ meson8b_clk_disable_unprepare, clk);
+}
+
+static int meson8b_init_rgmii_delays(struct meson8b_dwmac *dwmac)
+{
+ u32 tx_dly_config, rx_adj_config, cfg_rxclk_dly, delay_config;
+ int ret;
+
+ rx_adj_config = 0;
+ cfg_rxclk_dly = 0;
+ tx_dly_config = FIELD_PREP(PRG_ETH0_TXDLY_MASK,
+ dwmac->tx_delay_ns >> 1);
+
+ if (dwmac->data->has_prg_eth1_rgmii_rx_delay)
+ cfg_rxclk_dly = FIELD_PREP(PRG_ETH1_CFG_RXCLK_DLY,
+ dwmac->rx_delay_ps / 200);
+ else if (dwmac->rx_delay_ps == 2000)
+ rx_adj_config = PRG_ETH0_ADJ_ENABLE | PRG_ETH0_ADJ_SETUP;
+
+ switch (dwmac->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ delay_config = tx_dly_config | rx_adj_config;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ delay_config = tx_dly_config;
+ cfg_rxclk_dly = 0;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ delay_config = rx_adj_config;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RMII:
+ delay_config = 0;
+ cfg_rxclk_dly = 0;
+ break;
+ default:
+ dev_err(dwmac->dev, "unsupported phy-mode %s\n",
+ phy_modes(dwmac->phy_mode));
+ return -EINVAL;
+ }
+
+ if (delay_config & PRG_ETH0_ADJ_ENABLE) {
+ if (!dwmac->timing_adj_clk) {
+ dev_err(dwmac->dev,
+ "The timing-adjustment clock is mandatory for the RX delay re-timing\n");
+ return -EINVAL;
+ }
+
+ /* The timing adjustment logic is driven by a separate clock */
+ ret = meson8b_devm_clk_prepare_enable(dwmac,
+ dwmac->timing_adj_clk);
+ if (ret) {
+ dev_err(dwmac->dev,
+ "Failed to enable the timing-adjustment clock\n");
+ return ret;
+ }
+ }
+
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK |
+ PRG_ETH0_ADJ_ENABLE | PRG_ETH0_ADJ_SETUP |
+ PRG_ETH0_ADJ_DELAY | PRG_ETH0_ADJ_SKEW,
+ delay_config);
+
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH1, PRG_ETH1_CFG_RXCLK_DLY,
+ cfg_rxclk_dly);
+
+ return 0;
+}
+
+static int meson8b_init_prg_eth(struct meson8b_dwmac *dwmac)
+{
+ int ret;
+
+ if (phy_interface_mode_is_rgmii(dwmac->phy_mode)) {
+ /* only relevant for RMII mode -> disable in RGMII mode */
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
+ PRG_ETH0_INVERTED_RMII_CLK, 0);
+
+ /* Configure the 125MHz RGMII TX clock, the IP block changes
+ * the output automatically (= without us having to configure
+ * a register) based on the line-speed (125MHz for Gbit speeds,
+ * 25MHz for 100Mbit/s and 2.5MHz for 10Mbit/s).
+ */
+ ret = clk_set_rate(dwmac->rgmii_tx_clk, 125 * 1000 * 1000);
+ if (ret) {
+ dev_err(dwmac->dev,
+ "failed to set RGMII TX clock\n");
+ return ret;
+ }
+
+ ret = meson8b_devm_clk_prepare_enable(dwmac,
+ dwmac->rgmii_tx_clk);
+ if (ret) {
+ dev_err(dwmac->dev,
+ "failed to enable the RGMII TX clock\n");
+ return ret;
+ }
+ } else {
+ /* invert internal clk_rmii_i to generate 25/2.5 tx_rx_clk */
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
+ PRG_ETH0_INVERTED_RMII_CLK,
+ PRG_ETH0_INVERTED_RMII_CLK);
+ }
+
+ /* enable TX_CLK and PHY_REF_CLK generator */
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TX_AND_PHY_REF_CLK,
+ PRG_ETH0_TX_AND_PHY_REF_CLK);
+
+ return 0;
+}
+
+static int meson8b_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct meson8b_dwmac *dwmac;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ dwmac->data = (const struct meson8b_dwmac_data *)
+ of_device_get_match_data(&pdev->dev);
+ if (!dwmac->data)
+ return -EINVAL;
+ dwmac->regs = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(dwmac->regs))
+ return PTR_ERR(dwmac->regs);
+
+ dwmac->dev = &pdev->dev;
+ dwmac->phy_mode = plat_dat->phy_interface;
+
+ /* use 2ns as fallback since this value was previously hardcoded */
+ if (of_property_read_u32(pdev->dev.of_node, "amlogic,tx-delay-ns",
+ &dwmac->tx_delay_ns))
+ dwmac->tx_delay_ns = 2;
+
+ /* RX delay defaults to 0ps since this is what many boards use */
+ if (of_property_read_u32(pdev->dev.of_node, "rx-internal-delay-ps",
+ &dwmac->rx_delay_ps)) {
+ if (!of_property_read_u32(pdev->dev.of_node,
+ "amlogic,rx-delay-ns",
+ &dwmac->rx_delay_ps))
+ /* convert ns to ps */
+ dwmac->rx_delay_ps *= 1000;
+ }
+
+ if (dwmac->data->has_prg_eth1_rgmii_rx_delay) {
+ if (dwmac->rx_delay_ps > 3000 || dwmac->rx_delay_ps % 200) {
+ dev_err(dwmac->dev,
+ "The RGMII RX delay range is 0..3000ps in 200ps steps");
+ return -EINVAL;
+ }
+ } else {
+ if (dwmac->rx_delay_ps != 0 && dwmac->rx_delay_ps != 2000) {
+ dev_err(dwmac->dev,
+ "The only allowed RGMII RX delays values are: 0ps, 2000ps");
+ return -EINVAL;
+ }
+ }
+
+ dwmac->timing_adj_clk = devm_clk_get_optional(dwmac->dev,
+ "timing-adjustment");
+ if (IS_ERR(dwmac->timing_adj_clk))
+ return PTR_ERR(dwmac->timing_adj_clk);
+
+ ret = meson8b_init_rgmii_delays(dwmac);
+ if (ret)
+ return ret;
+
+ ret = meson8b_init_rgmii_tx_clk(dwmac);
+ if (ret)
+ return ret;
+
+ ret = dwmac->data->set_phy_mode(dwmac);
+ if (ret)
+ return ret;
+
+ ret = meson8b_init_prg_eth(dwmac);
+ if (ret)
+ return ret;
+
+ plat_dat->bsp_priv = dwmac;
+
+ return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+}
+
+static const struct meson8b_dwmac_data meson8b_dwmac_data = {
+ .set_phy_mode = meson8b_set_phy_mode,
+ .has_prg_eth1_rgmii_rx_delay = false,
+};
+
+static const struct meson8b_dwmac_data meson_axg_dwmac_data = {
+ .set_phy_mode = meson_axg_set_phy_mode,
+ .has_prg_eth1_rgmii_rx_delay = false,
+};
+
+static const struct meson8b_dwmac_data meson_g12a_dwmac_data = {
+ .set_phy_mode = meson_axg_set_phy_mode,
+ .has_prg_eth1_rgmii_rx_delay = true,
+};
+
+static const struct of_device_id meson8b_dwmac_match[] = {
+ {
+ .compatible = "amlogic,meson8b-dwmac",
+ .data = &meson8b_dwmac_data,
+ },
+ {
+ .compatible = "amlogic,meson8m2-dwmac",
+ .data = &meson8b_dwmac_data,
+ },
+ {
+ .compatible = "amlogic,meson-gxbb-dwmac",
+ .data = &meson8b_dwmac_data,
+ },
+ {
+ .compatible = "amlogic,meson-axg-dwmac",
+ .data = &meson_axg_dwmac_data,
+ },
+ {
+ .compatible = "amlogic,meson-g12a-dwmac",
+ .data = &meson_g12a_dwmac_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, meson8b_dwmac_match);
+
+static struct platform_driver meson8b_dwmac_driver = {
+ .probe = meson8b_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "meson8b-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = meson8b_dwmac_match,
+ },
+};
+module_platform_driver(meson8b_dwmac_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Amlogic Meson8b, Meson8m2 and GXBB DWMAC glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
new file mode 100644
index 000000000..124fcd55a
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DWMAC glue driver for Motorcomm PCI Ethernet controllers
+ *
+ * Copyright (c) 2025-2026 Yao Zi <me@ziyao.cc>
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/stmmac.h>
+
+#include "dwmac4.h"
+#include "stmmac.h"
+#include "stmmac_libpci.h"
+
+#define DRIVER_NAME "dwmac-motorcomm"
+
+#define PCI_VENDOR_ID_MOTORCOMM 0x1f0a
+
+/* Register definition */
+#define EPHY_CTRL 0x1004
+/* Clearing this bit asserts resets for internal MDIO bus and PHY */
+#define EPHY_MDIO_PHY_RESET BIT(0)
+#define OOB_WOL_CTRL 0x1010
+#define OOB_WOL_CTRL_DIS BIT(0)
+#define MGMT_INT_CTRL0 0x1100
+#define INT_MODERATION 0x1108
+#define INT_MODERATION_RX GENMASK(11, 0)
+#define INT_MODERATION_TX GENMASK(27, 16)
+#define EFUSE_OP_CTRL_0 0x1500
+#define EFUSE_OP_MODE GENMASK(1, 0)
+#define EFUSE_OP_ROW_READ 0x1
+#define EFUSE_OP_START BIT(2)
+#define EFUSE_OP_ADDR GENMASK(15, 8)
+#define EFUSE_OP_CTRL_1 0x1504
+#define EFUSE_OP_DONE BIT(1)
+#define EFUSE_OP_RD_DATA GENMASK(31, 24)
+#define SYS_RESET 0x152c
+#define SYS_RESET_RESET BIT(31)
+#define GMAC_OFFSET 0x2000
+
+/* Constants */
+#define EFUSE_READ_TIMEOUT_US 20000
+#define EFUSE_PATCH_REGION_OFFSET 18
+#define EFUSE_PATCH_MAX_NUM 39
+#define EFUSE_ADDR_MACA0LR 0x1520
+#define EFUSE_ADDR_MACA0HR 0x1524
+
+struct motorcomm_efuse_patch {
+ __le16 addr;
+ __le32 data;
+} __packed;
+
+struct dwmac_motorcomm_priv {
+ void __iomem *base;
+};
+
+static int motorcomm_efuse_read_byte(struct dwmac_motorcomm_priv *priv,
+ u8 offset, u8 *byte)
+{
+ u32 reg;
+ int ret;
+
+ writel(FIELD_PREP(EFUSE_OP_MODE, EFUSE_OP_ROW_READ) |
+ FIELD_PREP(EFUSE_OP_ADDR, offset) |
+ EFUSE_OP_START, priv->base + EFUSE_OP_CTRL_0);
+
+ ret = readl_poll_timeout(priv->base + EFUSE_OP_CTRL_1,
+ reg, reg & EFUSE_OP_DONE, 2000,
+ EFUSE_READ_TIMEOUT_US);
+
+ *byte = FIELD_GET(EFUSE_OP_RD_DATA, reg);
+
+ return ret;
+}
+
+static int motorcomm_efuse_read_patch(struct dwmac_motorcomm_priv *priv,
+ u8 index,
+ struct motorcomm_efuse_patch *patch)
+{
+ u8 *p = (u8 *)patch, offset;
+ int i, ret;
+
+ for (i = 0; i < sizeof(*patch); i++) {
+ offset = EFUSE_PATCH_REGION_OFFSET + sizeof(*patch) * index + i;
+
+ ret = motorcomm_efuse_read_byte(priv, offset, &p[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int motorcomm_efuse_get_patch_value(struct dwmac_motorcomm_priv *priv,
+ u16 addr, u32 *value)
+{
+ struct motorcomm_efuse_patch patch;
+ int i, ret;
+
+ for (i = 0; i < EFUSE_PATCH_MAX_NUM; i++) {
+ ret = motorcomm_efuse_read_patch(priv, i, &patch);
+ if (ret)
+ return ret;
+
+ if (patch.addr == 0) {
+ return -ENOENT;
+ } else if (le16_to_cpu(patch.addr) == addr) {
+ *value = le32_to_cpu(patch.data);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int motorcomm_efuse_read_mac(struct device *dev,
+ struct dwmac_motorcomm_priv *priv, u8 *mac)
+{
+ u32 maca0lr, maca0hr;
+ int ret;
+
+ ret = motorcomm_efuse_get_patch_value(priv, EFUSE_ADDR_MACA0LR,
+ &maca0lr);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read maca0lr from eFuse\n");
+
+ ret = motorcomm_efuse_get_patch_value(priv, EFUSE_ADDR_MACA0HR,
+ &maca0hr);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read maca0hr from eFuse\n");
+
+ mac[0] = FIELD_GET(GENMASK(15, 8), maca0hr);
+ mac[1] = FIELD_GET(GENMASK(7, 0), maca0hr);
+ mac[2] = FIELD_GET(GENMASK(31, 24), maca0lr);
+ mac[3] = FIELD_GET(GENMASK(23, 16), maca0lr);
+ mac[4] = FIELD_GET(GENMASK(15, 8), maca0lr);
+ mac[5] = FIELD_GET(GENMASK(7, 0), maca0lr);
+
+ return 0;
+}
+
+static void motorcomm_deassert_mdio_phy_reset(struct dwmac_motorcomm_priv *priv)
+{
+ u32 reg = readl(priv->base + EPHY_CTRL);
+
+ reg |= EPHY_MDIO_PHY_RESET;
+
+ writel(reg, priv->base + EPHY_CTRL);
+}
+
+static void motorcomm_reset(struct dwmac_motorcomm_priv *priv)
+{
+ u32 reg = readl(priv->base + SYS_RESET);
+
+ reg &= ~SYS_RESET_RESET;
+ writel(reg, priv->base + SYS_RESET);
+
+ reg |= SYS_RESET_RESET;
+ writel(reg, priv->base + SYS_RESET);
+
+ motorcomm_deassert_mdio_phy_reset(priv);
+}
+
+static void motorcomm_init(struct dwmac_motorcomm_priv *priv)
+{
+ writel(0x0, priv->base + MGMT_INT_CTRL0);
+
+ writel(FIELD_PREP(INT_MODERATION_RX, 200) |
+ FIELD_PREP(INT_MODERATION_TX, 200),
+ priv->base + INT_MODERATION);
+
+ /*
+ * OOB WOL must be disabled during normal operation, or DMA interrupts
+ * cannot be delivered to the host.
+ */
+ writel(OOB_WOL_CTRL_DIS, priv->base + OOB_WOL_CTRL);
+}
+
+static int motorcomm_resume(struct device *dev, void *bsp_priv)
+{
+ struct dwmac_motorcomm_priv *priv = bsp_priv;
+ int ret;
+
+ ret = stmmac_pci_plat_resume(dev, bsp_priv);
+ if (ret)
+ return ret;
+
+ /*
+ * When recovering from D3hot, EPHY_MDIO_PHY_RESET is automatically
+ * asserted, and must be deasserted for normal operation.
+ */
+ motorcomm_deassert_mdio_phy_reset(priv);
+ motorcomm_init(priv);
+
+ return 0;
+}
+
+static struct plat_stmmacenet_data *
+motorcomm_default_plat_data(struct pci_dev *pdev)
+{
+ struct plat_stmmacenet_data *plat;
+ struct device *dev = &pdev->dev;
+
+ plat = stmmac_plat_dat_alloc(dev);
+ if (!plat)
+ return NULL;
+
+ plat->mdio_bus_data = devm_kzalloc(dev, sizeof(*plat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat->mdio_bus_data)
+ return NULL;
+
+ plat->axi = devm_kzalloc(dev, sizeof(*plat->axi), GFP_KERNEL);
+ if (!plat->axi)
+ return NULL;
+
+ plat->dma_cfg->pbl = DEFAULT_DMA_PBL;
+ plat->dma_cfg->pblx8 = true;
+ plat->dma_cfg->txpbl = 32;
+ plat->dma_cfg->rxpbl = 32;
+ plat->dma_cfg->eame = true;
+ plat->dma_cfg->mixed_burst = true;
+
+ plat->axi->axi_wr_osr_lmt = 1;
+ plat->axi->axi_rd_osr_lmt = 1;
+ plat->axi->axi_blen_regval = DMA_AXI_BLEN4 | DMA_AXI_BLEN8 |
+ DMA_AXI_BLEN16 | DMA_AXI_BLEN32;
+
+ plat->bus_id = pci_dev_id(pdev);
+ plat->phy_interface = PHY_INTERFACE_MODE_GMII;
+ /*
+ * YT6801 requires an 25MHz clock input/oscillator to function, which
+ * is likely the source of CSR clock.
+ */
+ plat->clk_csr = STMMAC_CSR_20_35M;
+ plat->tx_coe = 1;
+ plat->rx_coe = 1;
+ plat->clk_ref_rate = 125000000;
+ plat->core_type = DWMAC_CORE_GMAC4;
+ plat->suspend = stmmac_pci_plat_suspend;
+ plat->resume = motorcomm_resume;
+ plat->flags = STMMAC_FLAG_TSO_EN |
+ STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP;
+
+ return plat;
+}
+
+static void motorcomm_free_irq(void *data)
+{
+ struct pci_dev *pdev = data;
+
+ pci_free_irq_vectors(pdev);
+}
+
+static int motorcomm_setup_irq(struct pci_dev *pdev,
+ struct stmmac_resources *res,
+ struct plat_stmmacenet_data *plat)
+{
+ int ret;
+
+ ret = pci_alloc_irq_vectors(pdev, 6, 6, PCI_IRQ_MSIX);
+ if (ret > 0) {
+ res->rx_irq[0] = pci_irq_vector(pdev, 0);
+ res->tx_irq[0] = pci_irq_vector(pdev, 4);
+ res->irq = pci_irq_vector(pdev, 5);
+
+ plat->flags |= STMMAC_FLAG_MULTI_MSI_EN;
+ } else {
+ dev_info(&pdev->dev, "failed to allocate MSI-X vector: %d\n",
+ ret);
+ dev_info(&pdev->dev, "try MSI instead\n");
+
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to allocate MSI\n");
+
+ res->irq = pci_irq_vector(pdev, 0);
+ }
+
+ return devm_add_action_or_reset(&pdev->dev, motorcomm_free_irq, pdev);
+}
+
+static int motorcomm_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct plat_stmmacenet_data *plat;
+ struct dwmac_motorcomm_priv *priv;
+ struct stmmac_resources res = {};
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ plat = motorcomm_default_plat_data(pdev);
+ if (!plat)
+ return -ENOMEM;
+
+ plat->bsp_priv = priv;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to enable device\n");
+
+ priv->base = pcim_iomap_region(pdev, 0, DRIVER_NAME);
+ if (IS_ERR(priv->base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->base),
+ "failed to map IO region\n");
+
+ pci_set_master(pdev);
+
+ /*
+ * Some PCIe addons cards based on YT6801 don't deliver MSI(X) with ASPM
+ * enabled. Sadly there isn't a reliable way to read out OEM of the
+ * card, so let's disable L1 state unconditionally for safety.
+ */
+ ret = pci_disable_link_state(pdev, PCIE_LINK_STATE_L1);
+ if (ret)
+ dev_warn(&pdev->dev, "failed to disable L1 state: %d\n", ret);
+
+ motorcomm_reset(priv);
+
+ /*
+ * After system reset, the eFuse controller needs time to load
+ * its internal data. Without this delay, eFuse reads return
+ * all zeros, causing MAC address detection to fail.
+ */
+ usleep_range(2000, 5000);
+
+ ret = motorcomm_efuse_read_mac(&pdev->dev, priv, res.mac);
+ if (ret == -ENOENT) {
+ dev_warn(&pdev->dev, "eFuse contains no valid MAC address\n");
+ dev_warn(&pdev->dev, "fallback to random MAC address\n");
+
+ eth_random_addr(res.mac);
+ } else if (ret) {
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to read MAC address from eFuse\n");
+ }
+
+ ret = motorcomm_setup_irq(pdev, &res, plat);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to setup IRQ\n");
+
+ motorcomm_init(priv);
+
+ res.addr = priv->base + GMAC_OFFSET;
+
+ return stmmac_dvr_probe(&pdev->dev, plat, &res);
+}
+
+static void motorcomm_remove(struct pci_dev *pdev)
+{
+ stmmac_dvr_remove(&pdev->dev);
+}
+
+static const struct pci_device_id dwmac_motorcomm_pci_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_MOTORCOMM, 0x6801) },
+ { },
+};
+MODULE_DEVICE_TABLE(pci, dwmac_motorcomm_pci_id_table);
+
+static struct pci_driver dwmac_motorcomm_pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = dwmac_motorcomm_pci_id_table,
+ .probe = motorcomm_probe,
+ .remove = motorcomm_remove,
+ .driver = {
+ .pm = &stmmac_simple_pm_ops,
+ },
+};
+
+module_pci_driver(dwmac_motorcomm_pci_driver);
+
+MODULE_DESCRIPTION("DWMAC glue driver for Motorcomm PCI Ethernet controllers");
+MODULE_AUTHOR("Yao Zi <me@ziyao.cc>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-nuvoton.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-nuvoton.c
new file mode 100644
index 000000000..b027cdf6a
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-nuvoton.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Nuvoton DWMAC specific glue layer
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ *
+ * Author: Joey Lu <a0987203069@gmail.com>
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define NVT_REG_SYS_GMAC0MISCR 0x108
+#define NVT_REG_SYS_GMAC1MISCR 0x10C
+
+#define NVT_MISCR_RMII BIT(0)
+
+/* Two thousand picoseconds are evenly mapped to a 4-bit field,
+ * resulting in each step being 2000/15 picoseconds.
+ */
+#define NVT_PATH_DELAY_STEP 134
+#define NVT_TX_DELAY_MASK GENMASK(19, 16)
+#define NVT_RX_DELAY_MASK GENMASK(23, 20)
+
+struct nvt_priv_data {
+ struct device *dev;
+ struct regmap *regmap;
+ u32 macid;
+};
+
+static int nvt_gmac_get_delay(struct device *dev, const char *property)
+{
+ u32 arg;
+
+ if (of_property_read_u32(dev->of_node, property, &arg))
+ return 0;
+
+ if (arg > 2000)
+ return -EINVAL;
+
+ if (arg == 2000)
+ return 15;
+
+ return arg / NVT_PATH_DELAY_STEP;
+}
+
+static int nvt_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel)
+{
+ struct nvt_priv_data *priv = bsp_priv;
+ u32 reg, val;
+ int ret;
+
+ if (phy_intf_sel == PHY_INTF_SEL_RGMII) {
+ ret = nvt_gmac_get_delay(priv->dev, "rx-internal-delay-ps");
+ if (ret < 0)
+ return ret;
+ val = FIELD_PREP(NVT_RX_DELAY_MASK, ret);
+
+ ret = nvt_gmac_get_delay(priv->dev, "tx-internal-delay-ps");
+ if (ret < 0)
+ return ret;
+ val |= FIELD_PREP(NVT_TX_DELAY_MASK, ret);
+ } else if (phy_intf_sel == PHY_INTF_SEL_RMII) {
+ val = NVT_MISCR_RMII;
+ } else {
+ return -EINVAL;
+ }
+
+ reg = (priv->macid == 0) ? NVT_REG_SYS_GMAC0MISCR : NVT_REG_SYS_GMAC1MISCR;
+ regmap_update_bits(priv->regmap, reg,
+ NVT_RX_DELAY_MASK | NVT_TX_DELAY_MASK | NVT_MISCR_RMII, val);
+
+ return 0;
+}
+
+static int nvt_gmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct nvt_priv_data *priv;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get platform resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(dev, PTR_ERR(plat_dat), "Failed to get platform data\n");
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return dev_err_probe(dev, -ENOMEM, "Failed to allocate private data\n");
+
+ priv->dev = dev;
+
+ priv->regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node, "nuvoton,sys",
+ 1, &priv->macid);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap), "Failed to get sys register\n");
+
+ if (priv->macid > 1)
+ return dev_err_probe(dev, -EINVAL, "Invalid sys arguments\n");
+
+ plat_dat->bsp_priv = priv;
+ plat_dat->set_phy_intf_sel = nvt_set_phy_intf_sel;
+
+ return stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id nvt_dwmac_match[] = {
+ { .compatible = "nuvoton,ma35d1-dwmac"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, nvt_dwmac_match);
+
+static struct platform_driver nvt_dwmac_driver = {
+ .probe = nvt_gmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "nuvoton-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = nvt_dwmac_match,
+ },
+};
+module_platform_driver(nvt_dwmac_driver);
+
+MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>");
+MODULE_DESCRIPTION("Nuvoton DWMAC specific glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c
new file mode 100644
index 000000000..ac7d6d3e2
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c
@@ -0,0 +1,820 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018-19, Linaro Limited
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_device.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+#define RGMII_IO_MACRO_CONFIG 0x0
+#define SDCC_HC_REG_DLL_CONFIG 0x4
+#define SDCC_TEST_CTL 0x8
+#define SDCC_HC_REG_DDR_CONFIG 0xC
+#define SDCC_HC_REG_DLL_CONFIG2 0x10
+#define SDC4_STATUS 0x14
+#define SDCC_USR_CTL 0x18
+#define RGMII_IO_MACRO_CONFIG2 0x1C
+#define RGMII_IO_MACRO_DEBUG1 0x20
+#define EMAC_SYSTEM_LOW_POWER_DEBUG 0x28
+#define EMAC_WRAPPER_SGMII_PHY_CNTRL1 0xf4
+
+/* RGMII_IO_MACRO_CONFIG fields */
+#define RGMII_CONFIG_FUNC_CLK_EN BIT(30)
+#define RGMII_CONFIG_POS_NEG_DATA_SEL BIT(23)
+#define RGMII_CONFIG_GPIO_CFG_RX_INT GENMASK(21, 20)
+#define RGMII_CONFIG_GPIO_CFG_TX_INT GENMASK(19, 17)
+#define RGMII_CONFIG_MAX_SPD_PRG_9 GENMASK(16, 8)
+#define RGMII_CONFIG_MAX_SPD_PRG_2 GENMASK(7, 6)
+#define RGMII_CONFIG_INTF_SEL GENMASK(5, 4)
+#define RGMII_CONFIG_BYPASS_TX_ID_EN BIT(3)
+#define RGMII_CONFIG_LOOPBACK_EN BIT(2)
+#define RGMII_CONFIG_PROG_SWAP BIT(1)
+#define RGMII_CONFIG_DDR_MODE BIT(0)
+#define RGMII_CONFIG_SGMII_CLK_DVDR GENMASK(18, 10)
+
+/* SDCC_HC_REG_DLL_CONFIG fields */
+#define SDCC_DLL_CONFIG_DLL_RST BIT(30)
+#define SDCC_DLL_CONFIG_PDN BIT(29)
+#define SDCC_DLL_CONFIG_MCLK_FREQ GENMASK(26, 24)
+#define SDCC_DLL_CONFIG_CDR_SELEXT GENMASK(23, 20)
+#define SDCC_DLL_CONFIG_CDR_EXT_EN BIT(19)
+#define SDCC_DLL_CONFIG_CK_OUT_EN BIT(18)
+#define SDCC_DLL_CONFIG_CDR_EN BIT(17)
+#define SDCC_DLL_CONFIG_DLL_EN BIT(16)
+#define SDCC_DLL_MCLK_GATING_EN BIT(5)
+#define SDCC_DLL_CDR_FINE_PHASE GENMASK(3, 2)
+
+/* SDCC_HC_REG_DDR_CONFIG fields */
+#define SDCC_DDR_CONFIG_PRG_DLY_EN BIT(31)
+#define SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY GENMASK(26, 21)
+#define SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_CODE GENMASK(29, 27)
+#define SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN BIT(30)
+#define SDCC_DDR_CONFIG_TCXO_CYCLES_CNT GENMASK(11, 9)
+#define SDCC_DDR_CONFIG_PRG_RCLK_DLY GENMASK(8, 0)
+
+/* SDCC_HC_REG_DLL_CONFIG2 fields */
+#define SDCC_DLL_CONFIG2_DLL_CLOCK_DIS BIT(21)
+#define SDCC_DLL_CONFIG2_MCLK_FREQ_CALC GENMASK(17, 10)
+#define SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SEL GENMASK(3, 2)
+#define SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SW BIT(1)
+#define SDCC_DLL_CONFIG2_DDR_CAL_EN BIT(0)
+
+/* SDC4_STATUS bits */
+#define SDC4_STATUS_DLL_LOCK BIT(7)
+
+/* RGMII_IO_MACRO_CONFIG2 fields */
+#define RGMII_CONFIG2_RSVD_CONFIG15 GENMASK(31, 17)
+#define RGMII_CONFIG2_RGMII_CLK_SEL_CFG BIT(16)
+#define RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN BIT(13)
+#define RGMII_CONFIG2_CLK_DIVIDE_SEL BIT(12)
+#define RGMII_CONFIG2_RX_PROG_SWAP BIT(7)
+#define RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL BIT(6)
+#define RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN BIT(5)
+
+/* EMAC_WRAPPER_SGMII_PHY_CNTRL1 bits */
+#define SGMII_PHY_CNTRL1_SGMII_TX_TO_RX_LOOPBACK_EN BIT(3)
+
+#define SGMII_10M_RX_CLK_DVDR 0x31
+
+struct ethqos_emac_por {
+ unsigned int offset;
+ unsigned int value;
+};
+
+struct ethqos_emac_driver_data {
+ const struct ethqos_emac_por *rgmii_por;
+ unsigned int num_rgmii_por;
+ bool rgmii_config_loopback_en;
+ bool has_emac_ge_3;
+ u8 dma_addr_width;
+ const char *link_clk_name;
+ struct dwmac4_addrs dwmac4_addrs;
+ bool needs_sgmii_loopback;
+};
+
+struct qcom_ethqos {
+ struct platform_device *pdev;
+ void __iomem *rgmii_base;
+ struct clk *link_clk;
+ struct phy *serdes_phy;
+ phy_interface_t phy_mode;
+
+ const struct ethqos_emac_por *rgmii_por;
+ unsigned int num_rgmii_por;
+ bool rgmii_config_loopback_en;
+ bool has_emac_ge_3;
+ bool needs_sgmii_loopback;
+};
+
+static u32 rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset)
+{
+ return readl(ethqos->rgmii_base + offset);
+}
+
+static void rgmii_writel(struct qcom_ethqos *ethqos, u32 value,
+ unsigned int offset)
+{
+ writel(value, ethqos->rgmii_base + offset);
+}
+
+static void rgmii_updatel(struct qcom_ethqos *ethqos, u32 mask, u32 val,
+ unsigned int offset)
+{
+ u32 temp;
+
+ temp = rgmii_readl(ethqos, offset);
+ temp = (temp & ~(mask)) | val;
+ rgmii_writel(ethqos, temp, offset);
+}
+
+static void rgmii_setmask(struct qcom_ethqos *ethqos, u32 mask,
+ unsigned int offset)
+{
+ rgmii_updatel(ethqos, mask, mask, offset);
+}
+
+static void rgmii_clrmask(struct qcom_ethqos *ethqos, u32 mask,
+ unsigned int offset)
+{
+ rgmii_updatel(ethqos, mask, 0, offset);
+}
+
+static void rgmii_dump(void *priv)
+{
+ struct qcom_ethqos *ethqos = priv;
+ struct device *dev = &ethqos->pdev->dev;
+
+ dev_dbg(dev, "Rgmii register dump\n");
+ dev_dbg(dev, "RGMII_IO_MACRO_CONFIG: %x\n",
+ rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG));
+ dev_dbg(dev, "SDCC_HC_REG_DLL_CONFIG: %x\n",
+ rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG));
+ dev_dbg(dev, "SDCC_HC_REG_DDR_CONFIG: %x\n",
+ rgmii_readl(ethqos, SDCC_HC_REG_DDR_CONFIG));
+ dev_dbg(dev, "SDCC_HC_REG_DLL_CONFIG2: %x\n",
+ rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG2));
+ dev_dbg(dev, "SDC4_STATUS: %x\n",
+ rgmii_readl(ethqos, SDC4_STATUS));
+ dev_dbg(dev, "SDCC_USR_CTL: %x\n",
+ rgmii_readl(ethqos, SDCC_USR_CTL));
+ dev_dbg(dev, "RGMII_IO_MACRO_CONFIG2: %x\n",
+ rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG2));
+ dev_dbg(dev, "RGMII_IO_MACRO_DEBUG1: %x\n",
+ rgmii_readl(ethqos, RGMII_IO_MACRO_DEBUG1));
+ dev_dbg(dev, "EMAC_SYSTEM_LOW_POWER_DEBUG: %x\n",
+ rgmii_readl(ethqos, EMAC_SYSTEM_LOW_POWER_DEBUG));
+}
+
+static int ethqos_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct qcom_ethqos *ethqos = bsp_priv;
+ long rate;
+
+ if (!phy_interface_mode_is_rgmii(interface))
+ return 0;
+
+ rate = rgmii_clock(speed);
+ if (rate < 0)
+ return rate;
+
+ return clk_set_rate(ethqos->link_clk, rate * 2);
+}
+
+static void
+qcom_ethqos_set_sgmii_loopback(struct qcom_ethqos *ethqos, bool enable)
+{
+ if (!ethqos->needs_sgmii_loopback ||
+ ethqos->phy_mode != PHY_INTERFACE_MODE_2500BASEX)
+ return;
+
+ rgmii_updatel(ethqos,
+ SGMII_PHY_CNTRL1_SGMII_TX_TO_RX_LOOPBACK_EN,
+ enable ? SGMII_PHY_CNTRL1_SGMII_TX_TO_RX_LOOPBACK_EN : 0,
+ EMAC_WRAPPER_SGMII_PHY_CNTRL1);
+}
+
+static void ethqos_set_func_clk_en(struct qcom_ethqos *ethqos)
+{
+ rgmii_setmask(ethqos, RGMII_CONFIG_FUNC_CLK_EN, RGMII_IO_MACRO_CONFIG);
+}
+
+static const struct ethqos_emac_por emac_v2_3_0_por[] = {
+ { .offset = RGMII_IO_MACRO_CONFIG, .value = 0x00C01343 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG, .value = 0x2004642C },
+ { .offset = SDCC_HC_REG_DDR_CONFIG, .value = 0x00000000 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG2, .value = 0x00200000 },
+ { .offset = SDCC_USR_CTL, .value = 0x00010800 },
+ { .offset = RGMII_IO_MACRO_CONFIG2, .value = 0x00002060 },
+};
+
+static const struct ethqos_emac_driver_data emac_v2_3_0_data = {
+ .rgmii_por = emac_v2_3_0_por,
+ .num_rgmii_por = ARRAY_SIZE(emac_v2_3_0_por),
+ .rgmii_config_loopback_en = true,
+ .has_emac_ge_3 = false,
+};
+
+static const struct ethqos_emac_por emac_v2_1_0_por[] = {
+ { .offset = RGMII_IO_MACRO_CONFIG, .value = 0x40C01343 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG, .value = 0x2004642C },
+ { .offset = SDCC_HC_REG_DDR_CONFIG, .value = 0x00000000 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG2, .value = 0x00200000 },
+ { .offset = SDCC_USR_CTL, .value = 0x00010800 },
+ { .offset = RGMII_IO_MACRO_CONFIG2, .value = 0x00002060 },
+};
+
+static const struct ethqos_emac_driver_data emac_v2_1_0_data = {
+ .rgmii_por = emac_v2_1_0_por,
+ .num_rgmii_por = ARRAY_SIZE(emac_v2_1_0_por),
+ .rgmii_config_loopback_en = false,
+ .has_emac_ge_3 = false,
+};
+
+static const struct ethqos_emac_por emac_v3_0_0_por[] = {
+ { .offset = RGMII_IO_MACRO_CONFIG, .value = 0x40c01343 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG, .value = 0x2004642c },
+ { .offset = SDCC_HC_REG_DDR_CONFIG, .value = 0x80040800 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG2, .value = 0x00200000 },
+ { .offset = SDCC_USR_CTL, .value = 0x00010800 },
+ { .offset = RGMII_IO_MACRO_CONFIG2, .value = 0x00002060 },
+};
+
+static const struct ethqos_emac_driver_data emac_v3_0_0_data = {
+ .rgmii_por = emac_v3_0_0_por,
+ .num_rgmii_por = ARRAY_SIZE(emac_v3_0_0_por),
+ .rgmii_config_loopback_en = false,
+ .has_emac_ge_3 = true,
+ .dwmac4_addrs = {
+ .dma_chan = 0x00008100,
+ .dma_chan_offset = 0x1000,
+ .mtl_chan = 0x00008000,
+ .mtl_chan_offset = 0x1000,
+ .mtl_ets_ctrl = 0x00008010,
+ .mtl_ets_ctrl_offset = 0x1000,
+ .mtl_txq_weight = 0x00008018,
+ .mtl_txq_weight_offset = 0x1000,
+ .mtl_send_slp_cred = 0x0000801c,
+ .mtl_send_slp_cred_offset = 0x1000,
+ .mtl_high_cred = 0x00008020,
+ .mtl_high_cred_offset = 0x1000,
+ .mtl_low_cred = 0x00008024,
+ .mtl_low_cred_offset = 0x1000,
+ },
+};
+
+static const struct ethqos_emac_por emac_v4_0_0_por[] = {
+ { .offset = RGMII_IO_MACRO_CONFIG, .value = 0x40c01343 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG, .value = 0x2004642c },
+ { .offset = SDCC_HC_REG_DDR_CONFIG, .value = 0x80040800 },
+ { .offset = SDCC_HC_REG_DLL_CONFIG2, .value = 0x00200000 },
+ { .offset = SDCC_USR_CTL, .value = 0x00010800 },
+ { .offset = RGMII_IO_MACRO_CONFIG2, .value = 0x00002060 },
+};
+
+static const struct ethqos_emac_driver_data emac_v4_0_0_data = {
+ .rgmii_por = emac_v4_0_0_por,
+ .num_rgmii_por = ARRAY_SIZE(emac_v4_0_0_por),
+ .rgmii_config_loopback_en = false,
+ .has_emac_ge_3 = true,
+ .link_clk_name = "phyaux",
+ .needs_sgmii_loopback = true,
+ .dma_addr_width = 36,
+ .dwmac4_addrs = {
+ .dma_chan = 0x00008100,
+ .dma_chan_offset = 0x1000,
+ .mtl_chan = 0x00008000,
+ .mtl_chan_offset = 0x1000,
+ .mtl_ets_ctrl = 0x00008010,
+ .mtl_ets_ctrl_offset = 0x1000,
+ .mtl_txq_weight = 0x00008018,
+ .mtl_txq_weight_offset = 0x1000,
+ .mtl_send_slp_cred = 0x0000801c,
+ .mtl_send_slp_cred_offset = 0x1000,
+ .mtl_high_cred = 0x00008020,
+ .mtl_high_cred_offset = 0x1000,
+ .mtl_low_cred = 0x00008024,
+ .mtl_low_cred_offset = 0x1000,
+ },
+};
+
+static int ethqos_dll_configure(struct qcom_ethqos *ethqos)
+{
+ struct device *dev = &ethqos->pdev->dev;
+ u32 val;
+
+ /* Set CDR_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CDR_EN, SDCC_HC_REG_DLL_CONFIG);
+
+ /* Set CDR_EXT_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CDR_EXT_EN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ /* Clear CK_OUT_EN */
+ rgmii_clrmask(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ /* Set DLL_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_DLL_EN, SDCC_HC_REG_DLL_CONFIG);
+
+ if (!ethqos->has_emac_ge_3) {
+ rgmii_clrmask(ethqos, SDCC_DLL_MCLK_GATING_EN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ rgmii_clrmask(ethqos, SDCC_DLL_CDR_FINE_PHASE,
+ SDCC_HC_REG_DLL_CONFIG);
+ }
+
+ /* Wait for CK_OUT_EN clear */
+ if (read_poll_timeout_atomic(rgmii_readl, val,
+ !(val & SDCC_DLL_CONFIG_CK_OUT_EN),
+ 1000, 1000000, false,
+ ethqos, SDCC_HC_REG_DLL_CONFIG))
+ dev_err(dev, "Clear CK_OUT_EN timedout\n");
+
+ /* Set CK_OUT_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ /* Wait for CK_OUT_EN set */
+ if (read_poll_timeout_atomic(rgmii_readl, val,
+ val & SDCC_DLL_CONFIG_CK_OUT_EN,
+ 1000, 1000000, false,
+ ethqos, SDCC_HC_REG_DLL_CONFIG))
+ dev_err(dev, "Set CK_OUT_EN timedout\n");
+
+ /* Set DDR_CAL_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG2_DDR_CAL_EN,
+ SDCC_HC_REG_DLL_CONFIG2);
+
+ if (!ethqos->has_emac_ge_3) {
+ rgmii_clrmask(ethqos, SDCC_DLL_CONFIG2_DLL_CLOCK_DIS,
+ SDCC_HC_REG_DLL_CONFIG2);
+
+ rgmii_updatel(ethqos, SDCC_DLL_CONFIG2_MCLK_FREQ_CALC,
+ FIELD_PREP(SDCC_DLL_CONFIG2_MCLK_FREQ_CALC, 26),
+ SDCC_HC_REG_DLL_CONFIG2);
+
+ rgmii_updatel(ethqos, SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SEL,
+ FIELD_PREP(SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SEL,
+ 1), SDCC_HC_REG_DLL_CONFIG2);
+
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG2_DDR_TRAFFIC_INIT_SW,
+ SDCC_HC_REG_DLL_CONFIG2);
+ }
+
+ return 0;
+}
+
+static int ethqos_rgmii_macro_init(struct qcom_ethqos *ethqos, int speed)
+{
+ struct device *dev = &ethqos->pdev->dev;
+ unsigned int prg_rclk_dly, loopback;
+ unsigned int phase_shift;
+
+ /* Disable loopback mode */
+ rgmii_clrmask(ethqos, RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN,
+ RGMII_IO_MACRO_CONFIG2);
+
+ /* Select RGMII, write 0 to interface select */
+ rgmii_clrmask(ethqos, RGMII_CONFIG_INTF_SEL, RGMII_IO_MACRO_CONFIG);
+
+ if (speed != SPEED_1000 && speed != SPEED_100 && speed != SPEED_10) {
+ dev_err(dev, "Invalid speed %d\n", speed);
+ return -EINVAL;
+ }
+
+ rgmii_setmask(ethqos, RGMII_CONFIG_DDR_MODE, RGMII_IO_MACRO_CONFIG);
+
+ if (speed == SPEED_1000) {
+ rgmii_clrmask(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN,
+ RGMII_IO_MACRO_CONFIG);
+ rgmii_setmask(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL,
+ RGMII_IO_MACRO_CONFIG);
+ rgmii_setmask(ethqos, RGMII_CONFIG_PROG_SWAP,
+ RGMII_IO_MACRO_CONFIG);
+ } else {
+ rgmii_setmask(ethqos, RGMII_CONFIG_BYPASS_TX_ID_EN,
+ RGMII_IO_MACRO_CONFIG);
+ rgmii_clrmask(ethqos, RGMII_CONFIG_POS_NEG_DATA_SEL,
+ RGMII_IO_MACRO_CONFIG);
+ rgmii_clrmask(ethqos, RGMII_CONFIG_PROG_SWAP,
+ RGMII_IO_MACRO_CONFIG);
+ }
+
+ rgmii_clrmask(ethqos, RGMII_CONFIG2_DATA_DIVIDE_CLK_SEL,
+ RGMII_IO_MACRO_CONFIG2);
+
+ /* Determine if the PHY adds a 2 ns TX delay or the MAC handles it */
+ if (ethqos->phy_mode == PHY_INTERFACE_MODE_RGMII_ID ||
+ ethqos->phy_mode == PHY_INTERFACE_MODE_RGMII_TXID)
+ phase_shift = 0;
+ else
+ phase_shift = RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN;
+
+ rgmii_updatel(ethqos, RGMII_CONFIG2_TX_CLK_PHASE_SHIFT_EN, phase_shift,
+ RGMII_IO_MACRO_CONFIG2);
+
+ if (speed == SPEED_100)
+ rgmii_updatel(ethqos, RGMII_CONFIG_MAX_SPD_PRG_2,
+ FIELD_PREP(RGMII_CONFIG_MAX_SPD_PRG_2, 1),
+ RGMII_IO_MACRO_CONFIG);
+ else if (speed == SPEED_10)
+ rgmii_updatel(ethqos, RGMII_CONFIG_MAX_SPD_PRG_9,
+ FIELD_PREP(RGMII_CONFIG_MAX_SPD_PRG_9, 19),
+ RGMII_IO_MACRO_CONFIG);
+
+ rgmii_clrmask(ethqos, RGMII_CONFIG2_RSVD_CONFIG15,
+ RGMII_IO_MACRO_CONFIG2);
+
+ if (speed == SPEED_1000 || ethqos->has_emac_ge_3)
+ rgmii_setmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
+ RGMII_IO_MACRO_CONFIG2);
+ else
+ rgmii_clrmask(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
+ RGMII_IO_MACRO_CONFIG2);
+
+ if (speed != SPEED_1000) {
+ /* Write 0x5 to PRG_RCLK_DLY_CODE */
+ rgmii_updatel(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_CODE,
+ FIELD_PREP(SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_CODE,
+ 5), SDCC_HC_REG_DDR_CONFIG);
+
+ rgmii_setmask(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY,
+ SDCC_HC_REG_DDR_CONFIG);
+
+ rgmii_setmask(ethqos, SDCC_DDR_CONFIG_EXT_PRG_RCLK_DLY_EN,
+ SDCC_HC_REG_DDR_CONFIG);
+ } else {
+ /* PRG_RCLK_DLY = TCXO period * TCXO_CYCLES_CNT /
+ * (2 * RX delay ns),
+ * in practice this becomes PRG_RCLK_DLY = 52 * 4 /
+ * (2 * RX delay ns)
+ */
+ if (ethqos->has_emac_ge_3) {
+ /* 0.9 ns */
+ prg_rclk_dly = 115;
+ } else {
+ /* 1.8 ns */
+ prg_rclk_dly = 57;
+ }
+
+ rgmii_updatel(ethqos, SDCC_DDR_CONFIG_PRG_RCLK_DLY,
+ FIELD_PREP(SDCC_DDR_CONFIG_PRG_RCLK_DLY,
+ prg_rclk_dly), SDCC_HC_REG_DDR_CONFIG);
+
+ rgmii_setmask(ethqos, SDCC_DDR_CONFIG_PRG_DLY_EN,
+ SDCC_HC_REG_DDR_CONFIG);
+ }
+
+ if (ethqos->rgmii_config_loopback_en)
+ loopback = RGMII_CONFIG_LOOPBACK_EN;
+ else
+ loopback = 0;
+
+ rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN, loopback,
+ RGMII_IO_MACRO_CONFIG);
+
+ return 0;
+}
+
+static void ethqos_fix_mac_speed_rgmii(void *bsp_priv,
+ phy_interface_t interface, int speed,
+ unsigned int mode)
+{
+ struct qcom_ethqos *ethqos = bsp_priv;
+ struct device *dev;
+ unsigned int i;
+ u32 val;
+
+ dev = &ethqos->pdev->dev;
+
+ /* Reset to POR values and enable clk */
+ for (i = 0; i < ethqos->num_rgmii_por; i++)
+ rgmii_writel(ethqos, ethqos->rgmii_por[i].value,
+ ethqos->rgmii_por[i].offset);
+
+ ethqos_set_func_clk_en(ethqos);
+
+ /* Initialize the DLL first */
+
+ /* Set DLL_RST */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_DLL_RST,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ /* Set PDN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_PDN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ if (ethqos->has_emac_ge_3) {
+ if (speed == SPEED_1000) {
+ rgmii_writel(ethqos, 0x1800000, SDCC_TEST_CTL);
+ rgmii_writel(ethqos, 0x2C010800, SDCC_USR_CTL);
+ rgmii_writel(ethqos, 0xA001, SDCC_HC_REG_DLL_CONFIG2);
+ } else {
+ rgmii_writel(ethqos, 0x40010800, SDCC_USR_CTL);
+ rgmii_writel(ethqos, 0xA001, SDCC_HC_REG_DLL_CONFIG2);
+ }
+ }
+
+ /* Clear DLL_RST */
+ rgmii_clrmask(ethqos, SDCC_DLL_CONFIG_DLL_RST, SDCC_HC_REG_DLL_CONFIG);
+
+ /* Clear PDN */
+ rgmii_clrmask(ethqos, SDCC_DLL_CONFIG_PDN, SDCC_HC_REG_DLL_CONFIG);
+
+ if (speed != SPEED_100 && speed != SPEED_10) {
+ /* Set DLL_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_DLL_EN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ /* Set CK_OUT_EN */
+ rgmii_setmask(ethqos, SDCC_DLL_CONFIG_CK_OUT_EN,
+ SDCC_HC_REG_DLL_CONFIG);
+
+ /* Set USR_CTL bit 26 with mask of 3 bits */
+ if (!ethqos->has_emac_ge_3)
+ rgmii_updatel(ethqos, GENMASK(26, 24), BIT(26),
+ SDCC_USR_CTL);
+
+ /* wait for DLL LOCK */
+ if (read_poll_timeout_atomic(rgmii_readl, val,
+ val & SDC4_STATUS_DLL_LOCK,
+ 1000, 1000000, true,
+ ethqos, SDC4_STATUS))
+ dev_err(dev, "Timeout while waiting for DLL lock\n");
+ }
+
+ if (speed == SPEED_1000)
+ ethqos_dll_configure(ethqos);
+
+ ethqos_rgmii_macro_init(ethqos, speed);
+}
+
+static void ethqos_pcs_set_inband(struct qcom_ethqos *ethqos, bool enable)
+{
+ struct net_device *dev = platform_get_drvdata(ethqos->pdev);
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ stmmac_pcs_ctrl_ane(priv, enable, 0);
+}
+
+/* On interface toggle MAC registers gets reset.
+ * Configure MAC block for SGMII on ethernet phy link up
+ */
+static void ethqos_fix_mac_speed_sgmii(void *bsp_priv,
+ phy_interface_t interface, int speed,
+ unsigned int mode)
+{
+ struct qcom_ethqos *ethqos = bsp_priv;
+
+ switch (speed) {
+ case SPEED_2500:
+ case SPEED_1000:
+ rgmii_setmask(ethqos, RGMII_CONFIG2_RGMII_CLK_SEL_CFG,
+ RGMII_IO_MACRO_CONFIG2);
+ break;
+ case SPEED_100:
+ break;
+ case SPEED_10:
+ rgmii_updatel(ethqos, RGMII_CONFIG_SGMII_CLK_DVDR,
+ FIELD_PREP(RGMII_CONFIG_SGMII_CLK_DVDR,
+ SGMII_10M_RX_CLK_DVDR),
+ RGMII_IO_MACRO_CONFIG);
+ break;
+ }
+
+ ethqos_pcs_set_inband(ethqos, interface == PHY_INTERFACE_MODE_SGMII);
+}
+
+static int qcom_ethqos_serdes_powerup(struct net_device *ndev, void *priv)
+{
+ struct qcom_ethqos *ethqos = priv;
+ int ret;
+
+ ret = phy_init(ethqos->serdes_phy);
+ if (ret)
+ return ret;
+
+ ret = phy_power_on(ethqos->serdes_phy);
+ if (ret)
+ phy_exit(ethqos->serdes_phy);
+
+ return ret;
+}
+
+static void qcom_ethqos_serdes_powerdown(struct net_device *ndev, void *priv)
+{
+ struct qcom_ethqos *ethqos = priv;
+
+ phy_power_off(ethqos->serdes_phy);
+ phy_exit(ethqos->serdes_phy);
+}
+
+static int ethqos_mac_finish_serdes(struct net_device *ndev, void *priv,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct qcom_ethqos *ethqos = priv;
+ int ret = 0;
+
+ qcom_ethqos_set_sgmii_loopback(ethqos, false);
+
+ if (interface == PHY_INTERFACE_MODE_SGMII ||
+ interface == PHY_INTERFACE_MODE_2500BASEX)
+ ret = phy_set_mode_ext(ethqos->serdes_phy, PHY_MODE_ETHERNET,
+ interface);
+
+ return ret;
+}
+
+static int ethqos_clks_config(void *priv, bool enabled)
+{
+ struct qcom_ethqos *ethqos = priv;
+ int ret = 0;
+
+ if (enabled) {
+ ret = clk_prepare_enable(ethqos->link_clk);
+ if (ret) {
+ dev_err(&ethqos->pdev->dev, "link_clk enable failed\n");
+ return ret;
+ }
+
+ /* Enable functional clock to prevent DMA reset to timeout due
+ * to lacking PHY clock after the hardware block has been power
+ * cycled. The actual configuration will be adjusted once
+ * ethqos' fix_mac_speed() method is invoked.
+ */
+ qcom_ethqos_set_sgmii_loopback(ethqos, true);
+ ethqos_set_func_clk_en(ethqos);
+ } else {
+ clk_disable_unprepare(ethqos->link_clk);
+ }
+
+ return ret;
+}
+
+static void ethqos_clks_disable(void *data)
+{
+ ethqos_clks_config(data, false);
+}
+
+static void ethqos_ptp_clk_freq_config(struct stmmac_priv *priv)
+{
+ struct plat_stmmacenet_data *plat_dat = priv->plat;
+ int err;
+
+ if (!plat_dat->clk_ptp_ref)
+ return;
+
+ /* Max the PTP ref clock out to get the best resolution possible */
+ err = clk_set_rate(plat_dat->clk_ptp_ref, ULONG_MAX);
+ if (err)
+ netdev_err(priv->dev, "Failed to max out clk_ptp_ref: %d\n", err);
+ plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->clk_ptp_ref);
+
+ netdev_dbg(priv->dev, "PTP rate %lu\n", plat_dat->clk_ptp_rate);
+}
+
+static int qcom_ethqos_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct ethqos_emac_driver_data *data;
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct qcom_ethqos *ethqos;
+ int ret, i;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get platform resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat)) {
+ return dev_err_probe(dev, PTR_ERR(plat_dat),
+ "dt configuration failed\n");
+ }
+
+ plat_dat->clks_config = ethqos_clks_config;
+
+ ethqos = devm_kzalloc(dev, sizeof(*ethqos), GFP_KERNEL);
+ if (!ethqos)
+ return -ENOMEM;
+
+ ethqos->phy_mode = plat_dat->phy_interface;
+ switch (ethqos->phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ plat_dat->fix_mac_speed = ethqos_fix_mac_speed_rgmii;
+ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ plat_dat->fix_mac_speed = ethqos_fix_mac_speed_sgmii;
+ plat_dat->mac_finish = ethqos_mac_finish_serdes;
+ break;
+ default:
+ dev_err(dev, "Unsupported phy mode %s\n",
+ phy_modes(ethqos->phy_mode));
+ return -EINVAL;
+ }
+
+ ethqos->pdev = pdev;
+ ethqos->rgmii_base = devm_platform_ioremap_resource_byname(pdev, "rgmii");
+ if (IS_ERR(ethqos->rgmii_base))
+ return dev_err_probe(dev, PTR_ERR(ethqos->rgmii_base),
+ "Failed to map rgmii resource\n");
+
+ data = of_device_get_match_data(dev);
+ ethqos->rgmii_por = data->rgmii_por;
+ ethqos->num_rgmii_por = data->num_rgmii_por;
+ ethqos->rgmii_config_loopback_en = data->rgmii_config_loopback_en;
+ ethqos->has_emac_ge_3 = data->has_emac_ge_3;
+ ethqos->needs_sgmii_loopback = data->needs_sgmii_loopback;
+
+ ethqos->link_clk = devm_clk_get(dev, data->link_clk_name ?: "rgmii");
+ if (IS_ERR(ethqos->link_clk))
+ return dev_err_probe(dev, PTR_ERR(ethqos->link_clk),
+ "Failed to get link_clk\n");
+
+ ret = ethqos_clks_config(ethqos, true);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ethqos_clks_disable, ethqos);
+ if (ret)
+ return ret;
+
+ ethqos->serdes_phy = devm_phy_optional_get(dev, "serdes");
+ if (IS_ERR(ethqos->serdes_phy))
+ return dev_err_probe(dev, PTR_ERR(ethqos->serdes_phy),
+ "Failed to get serdes phy\n");
+
+ ethqos_set_clk_tx_rate(ethqos, NULL, plat_dat->phy_interface,
+ SPEED_1000);
+
+ qcom_ethqos_set_sgmii_loopback(ethqos, true);
+ ethqos_set_func_clk_en(ethqos);
+
+ /* The clocks are controlled by firmware, so we don't know for certain
+ * what clock rate is being used. Hardware documentation mentions that
+ * the AHB slave clock will be in the range of 50 to 100MHz, which
+ * equates to a MDC between 1.19 and 2.38MHz.
+ */
+ plat_dat->clk_csr = STMMAC_CSR_60_100M;
+ plat_dat->bsp_priv = ethqos;
+ plat_dat->set_clk_tx_rate = ethqos_set_clk_tx_rate;
+ plat_dat->dump_debug_regs = rgmii_dump;
+ plat_dat->ptp_clk_freq_config = ethqos_ptp_clk_freq_config;
+ plat_dat->core_type = DWMAC_CORE_GMAC4;
+ if (ethqos->has_emac_ge_3)
+ plat_dat->dwmac4_addrs = &data->dwmac4_addrs;
+ plat_dat->pmt = true;
+ if (of_property_read_bool(np, "snps,tso"))
+ plat_dat->flags |= STMMAC_FLAG_TSO_EN;
+ if (of_device_is_compatible(np, "qcom,qcs404-ethqos"))
+ plat_dat->flags |= STMMAC_FLAG_RX_CLK_RUNS_IN_LPI;
+ if (data->dma_addr_width)
+ plat_dat->host_dma_width = data->dma_addr_width;
+
+ if (ethqos->serdes_phy) {
+ plat_dat->serdes_powerup = qcom_ethqos_serdes_powerup;
+ plat_dat->serdes_powerdown = qcom_ethqos_serdes_powerdown;
+ }
+
+ /* Enable TSO on queue0 and enable TBS on rest of the queues */
+ for (i = 1; i < plat_dat->tx_queues_to_use; i++)
+ plat_dat->tx_queues_cfg[i].tbs_en = 1;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id qcom_ethqos_match[] = {
+ { .compatible = "qcom,qcs404-ethqos", .data = &emac_v2_3_0_data},
+ { .compatible = "qcom,sa8775p-ethqos", .data = &emac_v4_0_0_data},
+ { .compatible = "qcom,sc8280xp-ethqos", .data = &emac_v3_0_0_data},
+ { .compatible = "qcom,sm8150-ethqos", .data = &emac_v2_1_0_data},
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_ethqos_match);
+
+static struct platform_driver qcom_ethqos_driver = {
+ .probe = qcom_ethqos_probe,
+ .driver = {
+ .name = "qcom-ethqos",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = qcom_ethqos_match,
+ },
+};
+module_platform_driver(qcom_ethqos_driver);
+
+MODULE_DESCRIPTION("Qualcomm ETHQOS driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
new file mode 100644
index 000000000..19f34e18b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * dwmac-renesas-gbeth.c - DWMAC Specific Glue layer for Renesas GBETH
+ *
+ * The Rx and Tx clocks are supplied as follows for the GBETH IP.
+ *
+ * Rx / Tx
+ * -------+------------- on / off -------
+ * |
+ * | Rx-180 / Tx-180
+ * +---- not ---- on / off -------
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pcs-rzn1-miic.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#include "stmmac_platform.h"
+
+/**
+ * struct renesas_gbeth_of_data - OF data for Renesas GBETH
+ *
+ * @clks: Array of clock names
+ * @num_clks: Number of clocks
+ * @stmmac_flags: Flags for the stmmac driver
+ * @handle_reset: Flag to indicate if reset control is
+ * handled by the glue driver or core driver.
+ * @set_clk_tx_rate: Flag to indicate if Tx clock is fixed or
+ * set_clk_tx_rate is needed.
+ * @has_pcs: Flag to indicate if the MAC has a PCS
+ */
+struct renesas_gbeth_of_data {
+ const char * const *clks;
+ u8 num_clks;
+ u32 stmmac_flags;
+ bool handle_reset;
+ bool set_clk_tx_rate;
+ bool has_pcs;
+};
+
+struct renesas_gbeth {
+ const struct renesas_gbeth_of_data *of_data;
+ struct plat_stmmacenet_data *plat_dat;
+ struct reset_control *rstc;
+ struct device *dev;
+};
+
+static const char *const renesas_gbeth_clks[] = {
+ "tx", "tx-180", "rx", "rx-180",
+};
+
+static const char *const renesas_gmac_clks[] = {
+ "tx",
+};
+
+static int renesas_gmac_pcs_init(struct stmmac_priv *priv)
+{
+ struct device_node *np = priv->device->of_node;
+ struct device_node *pcs_node;
+ struct phylink_pcs *pcs;
+
+ pcs_node = of_parse_phandle(np, "pcs-handle", 0);
+ if (pcs_node) {
+ pcs = miic_create(priv->device, pcs_node);
+ of_node_put(pcs_node);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+
+ priv->hw->phylink_pcs = pcs;
+ }
+
+ return 0;
+}
+
+static void renesas_gmac_pcs_exit(struct stmmac_priv *priv)
+{
+ if (priv->hw->phylink_pcs)
+ miic_destroy(priv->hw->phylink_pcs);
+}
+
+static struct phylink_pcs *renesas_gmac_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ return priv->hw->phylink_pcs;
+}
+
+static int renesas_gbeth_init(struct device *dev, void *priv)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct renesas_gbeth *gbeth = priv;
+ int ret;
+
+ plat_dat = gbeth->plat_dat;
+
+ ret = reset_control_deassert(gbeth->rstc);
+ if (ret) {
+ dev_err(gbeth->dev, "Reset deassert failed\n");
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(plat_dat->num_clks,
+ plat_dat->clks);
+ if (ret)
+ reset_control_assert(gbeth->rstc);
+
+ return ret;
+}
+
+static void renesas_gbeth_exit(struct device *dev, void *priv)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct renesas_gbeth *gbeth = priv;
+ int ret;
+
+ plat_dat = gbeth->plat_dat;
+
+ clk_bulk_disable_unprepare(plat_dat->num_clks, plat_dat->clks);
+
+ ret = reset_control_assert(gbeth->rstc);
+ if (ret)
+ dev_err(gbeth->dev, "Reset assert failed\n");
+}
+
+static int renesas_gbeth_probe(struct platform_device *pdev)
+{
+ const struct renesas_gbeth_of_data *of_data;
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct renesas_gbeth *gbeth;
+ unsigned int i;
+ int err;
+
+ err = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to get resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(dev, PTR_ERR(plat_dat),
+ "dt configuration failed\n");
+
+ gbeth = devm_kzalloc(dev, sizeof(*gbeth), GFP_KERNEL);
+ if (!gbeth)
+ return -ENOMEM;
+
+ of_data = of_device_get_match_data(&pdev->dev);
+ gbeth->of_data = of_data;
+
+ plat_dat->num_clks = of_data->num_clks;
+ plat_dat->clks = devm_kcalloc(dev, plat_dat->num_clks,
+ sizeof(*plat_dat->clks), GFP_KERNEL);
+ if (!plat_dat->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < plat_dat->num_clks; i++)
+ plat_dat->clks[i].id = of_data->clks[i];
+
+ err = devm_clk_bulk_get(dev, plat_dat->num_clks, plat_dat->clks);
+ if (err < 0)
+ return err;
+
+ plat_dat->clk_tx_i = stmmac_pltfr_find_clk(plat_dat, "tx");
+ if (!plat_dat->clk_tx_i)
+ return dev_err_probe(dev, -EINVAL,
+ "error finding tx clock\n");
+
+ if (of_data->handle_reset) {
+ gbeth->rstc = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(gbeth->rstc))
+ return PTR_ERR(gbeth->rstc);
+ }
+
+ gbeth->dev = dev;
+ gbeth->plat_dat = plat_dat;
+ plat_dat->bsp_priv = gbeth;
+ if (of_data->set_clk_tx_rate)
+ plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+ plat_dat->init = renesas_gbeth_init;
+ plat_dat->exit = renesas_gbeth_exit;
+ plat_dat->flags |= STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP |
+ gbeth->of_data->stmmac_flags;
+ if (of_data->has_pcs) {
+ plat_dat->pcs_init = renesas_gmac_pcs_init;
+ plat_dat->pcs_exit = renesas_gmac_pcs_exit;
+ plat_dat->select_pcs = renesas_gmac_select_pcs;
+ }
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct renesas_gbeth_of_data renesas_gbeth_of_data = {
+ .clks = renesas_gbeth_clks,
+ .num_clks = ARRAY_SIZE(renesas_gbeth_clks),
+ .handle_reset = true,
+ .set_clk_tx_rate = true,
+ .stmmac_flags = STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY |
+ STMMAC_FLAG_SPH_DISABLE,
+};
+
+static const struct renesas_gbeth_of_data renesas_gmac_of_data = {
+ .clks = renesas_gmac_clks,
+ .num_clks = ARRAY_SIZE(renesas_gmac_clks),
+ .stmmac_flags = STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY,
+ .has_pcs = true,
+};
+
+static const struct of_device_id renesas_gbeth_match[] = {
+ { .compatible = "renesas,r9a08g046-gbeth", .data = &renesas_gbeth_of_data },
+ { .compatible = "renesas,r9a09g077-gbeth", .data = &renesas_gmac_of_data },
+ { .compatible = "renesas,rzv2h-gbeth", .data = &renesas_gbeth_of_data },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, renesas_gbeth_match);
+
+static struct platform_driver renesas_gbeth_driver = {
+ .probe = renesas_gbeth_probe,
+ .driver = {
+ .name = "renesas-gbeth",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = renesas_gbeth_match,
+ },
+};
+module_platform_driver(renesas_gbeth_driver);
+
+MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>");
+MODULE_DESCRIPTION("Renesas GBETH DWMAC Specific Glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
new file mode 100644
index 000000000..8d7042e68
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
@@ -0,0 +1,1649 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * DOC: dwmac-rk.c - Rockchip RK3288 DWMAC specific glue layer
+ *
+ * Copyright (C) 2014 Chen-Zhi (Roger Chen)
+ *
+ * Chen-Zhi (Roger Chen) <roger.chen@rock-chips.com>
+ */
+
+#include <linux/stmmac.h>
+#include <linux/hw_bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/phy.h>
+#include <linux/of_net.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+
+#include "stmmac_platform.h"
+
+struct rk_priv_data;
+
+struct rk_clock_fields {
+ /* io_clksel_cru_mask - io_clksel bit in clock GRF register which,
+ * when set, selects the tx clock from CRU.
+ */
+ u16 io_clksel_cru_mask;
+ /* io_clksel_io_mask - io_clksel bit in clock GRF register which,
+ * when set, selects the tx clock from IO.
+ */
+ u16 io_clksel_io_mask;
+ u16 gmii_clk_sel_mask;
+ u16 rmii_clk_sel_mask;
+ u16 rmii_gate_en_mask;
+ u16 rmii_mode_mask;
+ u16 mac_speed_mask;
+};
+
+struct rk_gmac_ops {
+ int (*init)(struct rk_priv_data *bsp_priv);
+ void (*set_to_rgmii)(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay);
+ void (*set_to_rmii)(struct rk_priv_data *bsp_priv);
+ int (*set_speed)(struct rk_priv_data *bsp_priv,
+ phy_interface_t interface, int speed);
+ void (*integrated_phy_powerup)(struct rk_priv_data *bsp_priv);
+ void (*integrated_phy_powerdown)(struct rk_priv_data *bsp_priv);
+
+ u16 gmac_grf_reg;
+ u16 gmac_phy_intf_sel_mask;
+ u16 gmac_rmii_mode_mask;
+
+ u16 clock_grf_reg;
+ struct rk_clock_fields clock;
+
+ bool gmac_grf_reg_in_php;
+ bool clock_grf_reg_in_php;
+ bool supports_rgmii;
+ bool supports_rmii;
+ bool php_grf_required;
+ bool regs_valid;
+ u32 regs[];
+};
+
+static const char * const rk_clocks[] = {
+ "aclk_mac", "pclk_mac", "mac_clk_tx", "clk_mac_speed",
+};
+
+static const char * const rk_rmii_clocks[] = {
+ "mac_clk_rx", "clk_mac_ref", "clk_mac_refout",
+};
+
+enum rk_clocks_index {
+ RK_ACLK_MAC = 0,
+ RK_PCLK_MAC,
+ RK_MAC_CLK_TX,
+ RK_CLK_MAC_SPEED,
+ RK_MAC_CLK_RX,
+ RK_CLK_MAC_REF,
+ RK_CLK_MAC_REFOUT,
+};
+
+struct rk_priv_data {
+ struct device *dev;
+ phy_interface_t phy_iface;
+ int id;
+ struct regulator *regulator;
+ const struct rk_gmac_ops *ops;
+
+ bool clk_enabled;
+ bool clock_input;
+ bool integrated_phy;
+ bool supports_rgmii;
+ bool supports_rmii;
+
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct clk *clk_phy;
+
+ struct reset_control *phy_reset;
+
+ int tx_delay;
+ int rx_delay;
+
+ struct regmap *grf;
+ struct regmap *php_grf;
+
+ u16 gmac_grf_reg;
+ u16 gmac_phy_intf_sel_mask;
+ u16 gmac_rmii_mode_mask;
+
+ u16 clock_grf_reg;
+ struct rk_clock_fields clock;
+};
+
+#define GMAC_CLK_DIV1_125M 0
+#define GMAC_CLK_DIV50_2_5M 2
+#define GMAC_CLK_DIV5_25M 3
+
+static int rk_gmac_rgmii_clk_div(int speed)
+{
+ if (speed == SPEED_10)
+ return GMAC_CLK_DIV50_2_5M;
+ if (speed == SPEED_100)
+ return GMAC_CLK_DIV5_25M;
+ if (speed == SPEED_1000)
+ return GMAC_CLK_DIV1_125M;
+ return -EINVAL;
+}
+
+static int rk_get_phy_intf_sel(phy_interface_t interface)
+{
+ int ret = stmmac_get_phy_intf_sel(interface);
+
+ /* Only RGMII and RMII are supported */
+ if (ret != PHY_INTF_SEL_RGMII && ret != PHY_INTF_SEL_RMII)
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static u32 rk_encode_wm16(u16 val, u16 mask)
+{
+ u32 reg_val = mask << 16;
+
+ if (mask)
+ reg_val |= mask & (val << (ffs(mask) - 1));
+
+ return reg_val;
+}
+
+static int rk_write_gmac_grf_reg(struct rk_priv_data *bsp_priv, u32 val)
+{
+ struct regmap *regmap;
+
+ if (bsp_priv->ops->gmac_grf_reg_in_php)
+ regmap = bsp_priv->php_grf;
+ else
+ regmap = bsp_priv->grf;
+
+ return regmap_write(regmap, bsp_priv->gmac_grf_reg, val);
+}
+
+static int rk_write_clock_grf_reg(struct rk_priv_data *bsp_priv, u32 val)
+{
+ struct regmap *regmap;
+
+ if (bsp_priv->ops->clock_grf_reg_in_php)
+ regmap = bsp_priv->php_grf;
+ else
+ regmap = bsp_priv->grf;
+
+ return regmap_write(regmap, bsp_priv->clock_grf_reg, val);
+}
+
+static int rk_set_rmii_gate_en(struct rk_priv_data *bsp_priv, bool state)
+{
+ u32 val;
+
+ if (!bsp_priv->clock.rmii_gate_en_mask)
+ return 0;
+
+ val = rk_encode_wm16(state, bsp_priv->clock.rmii_gate_en_mask);
+
+ return rk_write_clock_grf_reg(bsp_priv, val);
+}
+
+static int rk_ungate_rmii_clock(struct rk_priv_data *bsp_priv)
+{
+ return rk_set_rmii_gate_en(bsp_priv, false);
+}
+
+static int rk_gate_rmii_clock(struct rk_priv_data *bsp_priv)
+{
+ return rk_set_rmii_gate_en(bsp_priv, true);
+}
+
+static int rk_configure_io_clksel(struct rk_priv_data *bsp_priv)
+{
+ bool io, cru;
+ u32 val;
+
+ if (!bsp_priv->clock.io_clksel_io_mask &&
+ !bsp_priv->clock.io_clksel_cru_mask)
+ return 0;
+
+ io = bsp_priv->clock_input;
+ cru = !io;
+
+ /* The io_clksel configuration can be either:
+ * 0=CRU, 1=IO (rk3506, rk3528, rk3576) or
+ * 0=IO, 1=CRU (rk3588)
+ * where CRU means the transmit clock comes from the CRU and IO
+ * means the transmit clock comes from IO.
+ *
+ * Handle this by having two masks.
+ */
+ val = rk_encode_wm16(io, bsp_priv->clock.io_clksel_io_mask) |
+ rk_encode_wm16(cru, bsp_priv->clock.io_clksel_cru_mask);
+
+ return rk_write_clock_grf_reg(bsp_priv, val);
+}
+
+static int rk_set_clk_mac_speed(struct rk_priv_data *bsp_priv,
+ phy_interface_t interface, int speed)
+{
+ struct clk *clk_mac_speed = bsp_priv->clks[RK_CLK_MAC_SPEED].clk;
+ long rate;
+
+ rate = rgmii_clock(speed);
+ if (rate < 0)
+ return rate;
+
+ return clk_set_rate(clk_mac_speed, rate);
+}
+
+#define GRF_FIELD(hi, lo, val) \
+ FIELD_PREP_WM16(GENMASK_U16(hi, lo), val)
+
+#define GRF_BIT(nr) (BIT(nr) | BIT(nr+16))
+#define GRF_CLR_BIT(nr) (BIT(nr+16))
+
+#define DELAY_ENABLE(soc, tx, rx) \
+ (((tx) ? soc##_GMAC_TXCLK_DLY_ENABLE : soc##_GMAC_TXCLK_DLY_DISABLE) | \
+ ((rx) ? soc##_GMAC_RXCLK_DLY_ENABLE : soc##_GMAC_RXCLK_DLY_DISABLE))
+
+#define RK_GRF_MACPHY_CON0 0xb00
+#define RK_MACPHY_ENABLE GRF_BIT(0)
+#define RK_MACPHY_DISABLE GRF_CLR_BIT(0)
+#define RK_MACPHY_CFG_CLK_50M GRF_BIT(14)
+#define RK_GMAC2PHY_RMII_MODE GRF_FIELD(7, 6, 1)
+
+#define RK_GRF_MACPHY_CON1 0xb04
+
+#define RK_GRF_MACPHY_CON2 0xb08
+#define RK_GRF_CON2_MACPHY_ID GRF_FIELD(15, 0, 0x1234)
+
+#define RK_GRF_MACPHY_CON3 0xb0c
+#define RK_GRF_CON3_MACPHY_ID GRF_FIELD(5, 0, 0x35)
+
+static void rk_gmac_integrated_ephy_powerup(struct rk_priv_data *priv)
+{
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON0, RK_MACPHY_CFG_CLK_50M);
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON0, RK_GMAC2PHY_RMII_MODE);
+
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON2, RK_GRF_CON2_MACPHY_ID);
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON3, RK_GRF_CON3_MACPHY_ID);
+
+ if (priv->phy_reset) {
+ /* PHY needs to be disabled before trying to reset it */
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON0, RK_MACPHY_DISABLE);
+ if (priv->phy_reset)
+ reset_control_assert(priv->phy_reset);
+ usleep_range(10, 20);
+ if (priv->phy_reset)
+ reset_control_deassert(priv->phy_reset);
+ usleep_range(10, 20);
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON0, RK_MACPHY_ENABLE);
+ msleep(30);
+ }
+}
+
+static void rk_gmac_integrated_ephy_powerdown(struct rk_priv_data *priv)
+{
+ regmap_write(priv->grf, RK_GRF_MACPHY_CON0, RK_MACPHY_DISABLE);
+ if (priv->phy_reset)
+ reset_control_assert(priv->phy_reset);
+}
+
+#define RK_FEPHY_SHUTDOWN GRF_BIT(1)
+#define RK_FEPHY_POWERUP GRF_CLR_BIT(1)
+#define RK_FEPHY_INTERNAL_RMII_SEL GRF_BIT(6)
+#define RK_FEPHY_24M_CLK_SEL GRF_FIELD(9, 8, 3)
+#define RK_FEPHY_PHY_ID GRF_BIT(11)
+
+static void rk_gmac_integrated_fephy_powerup(struct rk_priv_data *priv,
+ unsigned int reg)
+{
+ reset_control_assert(priv->phy_reset);
+ usleep_range(20, 30);
+
+ regmap_write(priv->grf, reg,
+ RK_FEPHY_POWERUP |
+ RK_FEPHY_INTERNAL_RMII_SEL |
+ RK_FEPHY_24M_CLK_SEL |
+ RK_FEPHY_PHY_ID);
+ usleep_range(10000, 12000);
+
+ reset_control_deassert(priv->phy_reset);
+ usleep_range(50000, 60000);
+}
+
+static void rk_gmac_integrated_fephy_powerdown(struct rk_priv_data *priv,
+ unsigned int reg)
+{
+ regmap_write(priv->grf, reg, RK_FEPHY_SHUTDOWN);
+}
+
+#define PX30_GRF_GMAC_CON1 0x0904
+
+static const struct rk_gmac_ops px30_ops = {
+ .set_speed = rk_set_clk_mac_speed,
+
+ .gmac_grf_reg = PX30_GRF_GMAC_CON1,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
+
+ .clock_grf_reg = PX30_GRF_GMAC_CON1,
+ .clock.mac_speed_mask = BIT_U16(2),
+
+ .supports_rmii = true,
+};
+
+#define RK3128_GRF_MAC_CON0 0x0168
+#define RK3128_GRF_MAC_CON1 0x016c
+
+/* RK3128_GRF_MAC_CON0 */
+#define RK3128_GMAC_TXCLK_DLY_ENABLE GRF_BIT(14)
+#define RK3128_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(14)
+#define RK3128_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3128_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3128_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(13, 7, val)
+#define RK3128_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+/* RK3128_GRF_MAC_CON1 */
+#define RK3128_GMAC_FLOW_CTRL GRF_BIT(9)
+#define RK3128_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(9)
+
+static void rk3128_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3128_GRF_MAC_CON0,
+ DELAY_ENABLE(RK3128, tx_delay, rx_delay) |
+ RK3128_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3128_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rk3128_ops = {
+ .set_to_rgmii = rk3128_set_to_rgmii,
+
+ .gmac_grf_reg = RK3128_GRF_MAC_CON1,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(8, 6),
+ .gmac_rmii_mode_mask = BIT_U16(14),
+
+ .clock_grf_reg = RK3128_GRF_MAC_CON1,
+ .clock.gmii_clk_sel_mask = GENMASK_U16(13, 12),
+ .clock.rmii_clk_sel_mask = BIT_U16(11),
+ .clock.mac_speed_mask = BIT_U16(10),
+
+ .supports_rmii = true,
+};
+
+#define RK3228_GRF_MAC_CON0 0x0900
+#define RK3228_GRF_MAC_CON1 0x0904
+
+#define RK3228_GRF_CON_MUX 0x50
+
+/* RK3228_GRF_MAC_CON0 */
+#define RK3228_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(13, 7, val)
+#define RK3228_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+/* RK3228_GRF_MAC_CON1 */
+#define RK3228_GMAC_FLOW_CTRL GRF_BIT(3)
+#define RK3228_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(3)
+#define RK3228_GMAC_TXCLK_DLY_ENABLE GRF_BIT(0)
+#define RK3228_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(0)
+#define RK3228_GMAC_RXCLK_DLY_ENABLE GRF_BIT(1)
+#define RK3228_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(1)
+
+/* RK3228_GRF_COM_MUX */
+#define RK3228_GRF_CON_MUX_GMAC_INTEGRATED_PHY GRF_BIT(15)
+
+static void rk3228_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1,
+ DELAY_ENABLE(RK3228, tx_delay, rx_delay));
+
+ regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON0,
+ RK3228_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3228_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static void rk3228_set_to_rmii(struct rk_priv_data *bsp_priv)
+{
+ /* set MAC to RMII mode */
+ regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, GRF_BIT(11));
+}
+
+static void rk3228_integrated_phy_powerup(struct rk_priv_data *priv)
+{
+ regmap_write(priv->grf, RK3228_GRF_CON_MUX,
+ RK3228_GRF_CON_MUX_GMAC_INTEGRATED_PHY);
+
+ rk_gmac_integrated_ephy_powerup(priv);
+}
+
+static const struct rk_gmac_ops rk3228_ops = {
+ .set_to_rgmii = rk3228_set_to_rgmii,
+ .set_to_rmii = rk3228_set_to_rmii,
+ .integrated_phy_powerup = rk3228_integrated_phy_powerup,
+ .integrated_phy_powerdown = rk_gmac_integrated_ephy_powerdown,
+
+ .gmac_grf_reg = RK3228_GRF_MAC_CON1,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
+ .gmac_rmii_mode_mask = BIT_U16(10),
+
+ .clock_grf_reg = RK3228_GRF_MAC_CON1,
+ .clock.gmii_clk_sel_mask = GENMASK_U16(9, 8),
+ .clock.rmii_clk_sel_mask = BIT_U16(7),
+ .clock.mac_speed_mask = BIT_U16(2),
+};
+
+#define RK3288_GRF_SOC_CON1 0x0248
+#define RK3288_GRF_SOC_CON3 0x0250
+
+/*RK3288_GRF_SOC_CON1*/
+#define RK3288_GMAC_FLOW_CTRL GRF_BIT(9)
+#define RK3288_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(9)
+
+/*RK3288_GRF_SOC_CON3*/
+#define RK3288_GMAC_TXCLK_DLY_ENABLE GRF_BIT(14)
+#define RK3288_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(14)
+#define RK3288_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3288_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3288_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(13, 7, val)
+#define RK3288_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+static void rk3288_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3288_GRF_SOC_CON3,
+ DELAY_ENABLE(RK3288, tx_delay, rx_delay) |
+ RK3288_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3288_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rk3288_ops = {
+ .set_to_rgmii = rk3288_set_to_rgmii,
+
+ .gmac_grf_reg = RK3288_GRF_SOC_CON1,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(8, 6),
+ .gmac_rmii_mode_mask = BIT_U16(14),
+
+ .clock_grf_reg = RK3288_GRF_SOC_CON1,
+ .clock.gmii_clk_sel_mask = GENMASK_U16(13, 12),
+ .clock.rmii_clk_sel_mask = BIT_U16(11),
+ .clock.mac_speed_mask = BIT_U16(10),
+
+ .supports_rmii = true,
+};
+
+#define RK3308_GRF_MAC_CON0 0x04a0
+
+/* RK3308_GRF_MAC_CON0 */
+#define RK3308_GMAC_FLOW_CTRL GRF_BIT(3)
+#define RK3308_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(3)
+
+static const struct rk_gmac_ops rk3308_ops = {
+ .gmac_grf_reg = RK3308_GRF_MAC_CON0,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(4, 2),
+
+ .clock_grf_reg = RK3308_GRF_MAC_CON0,
+ .clock.mac_speed_mask = BIT_U16(0),
+
+ .supports_rmii = true,
+};
+
+#define RK3328_GRF_MAC_CON0 0x0900
+#define RK3328_GRF_MAC_CON1 0x0904
+#define RK3328_GRF_MAC_CON2 0x0908
+#define RK3328_GRF_MACPHY_CON1 0xb04
+
+/* RK3328_GRF_MAC_CON0 */
+#define RK3328_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(13, 7, val)
+#define RK3328_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+/* RK3328_GRF_MAC_CON1 */
+#define RK3328_GMAC_FLOW_CTRL GRF_BIT(3)
+#define RK3328_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(3)
+#define RK3328_GMAC_TXCLK_DLY_ENABLE GRF_BIT(0)
+#define RK3328_GMAC_RXCLK_DLY_ENABLE GRF_BIT(1)
+
+/* RK3328_GRF_MACPHY_CON1 */
+#define RK3328_MACPHY_RMII_MODE GRF_BIT(9)
+
+static int rk3328_init(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->id) {
+ case 0: /* gmac2io */
+ bsp_priv->gmac_grf_reg = RK3328_GRF_MAC_CON1;
+ bsp_priv->clock_grf_reg = RK3328_GRF_MAC_CON1;
+ bsp_priv->clock.gmii_clk_sel_mask = GENMASK_U16(12, 11);
+ return 0;
+
+ case 1: /* gmac2phy */
+ bsp_priv->gmac_grf_reg = RK3328_GRF_MAC_CON2;
+ bsp_priv->clock_grf_reg = RK3328_GRF_MAC_CON2;
+ bsp_priv->supports_rgmii = false;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void rk3328_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3328_GRF_MAC_CON1,
+ RK3328_GMAC_RXCLK_DLY_ENABLE |
+ RK3328_GMAC_TXCLK_DLY_ENABLE);
+
+ regmap_write(bsp_priv->grf, RK3328_GRF_MAC_CON0,
+ RK3328_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3328_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static void rk3328_integrated_phy_powerup(struct rk_priv_data *priv)
+{
+ regmap_write(priv->grf, RK3328_GRF_MACPHY_CON1,
+ RK3328_MACPHY_RMII_MODE);
+
+ rk_gmac_integrated_ephy_powerup(priv);
+}
+
+static const struct rk_gmac_ops rk3328_ops = {
+ .init = rk3328_init,
+ .set_to_rgmii = rk3328_set_to_rgmii,
+ .integrated_phy_powerup = rk3328_integrated_phy_powerup,
+ .integrated_phy_powerdown = rk_gmac_integrated_ephy_powerdown,
+
+ .gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
+ .gmac_rmii_mode_mask = BIT_U16(9),
+
+ .clock.rmii_clk_sel_mask = BIT_U16(7),
+ .clock.mac_speed_mask = BIT_U16(2),
+
+ .supports_rmii = true,
+
+ .regs_valid = true,
+ .regs = {
+ 0xff540000, /* gmac2io */
+ 0xff550000, /* gmac2phy */
+ 0, /* sentinel */
+ },
+};
+
+#define RK3366_GRF_SOC_CON6 0x0418
+#define RK3366_GRF_SOC_CON7 0x041c
+
+/* RK3366_GRF_SOC_CON6 */
+#define RK3366_GMAC_FLOW_CTRL GRF_BIT(8)
+#define RK3366_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(8)
+
+/* RK3366_GRF_SOC_CON7 */
+#define RK3366_GMAC_TXCLK_DLY_ENABLE GRF_BIT(7)
+#define RK3366_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(7)
+#define RK3366_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3366_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3366_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RK3366_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+static void rk3366_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3366_GRF_SOC_CON7,
+ DELAY_ENABLE(RK3366, tx_delay, rx_delay) |
+ RK3366_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3366_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rk3366_ops = {
+ .set_to_rgmii = rk3366_set_to_rgmii,
+
+ .gmac_grf_reg = RK3366_GRF_SOC_CON6,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(11, 9),
+ .gmac_rmii_mode_mask = BIT_U16(6),
+
+ .clock_grf_reg = RK3366_GRF_SOC_CON6,
+ .clock.gmii_clk_sel_mask = GENMASK_U16(5, 4),
+ .clock.rmii_clk_sel_mask = BIT_U16(3),
+ .clock.mac_speed_mask = BIT_U16(7),
+
+ .supports_rmii = true,
+};
+
+#define RK3368_GRF_SOC_CON15 0x043c
+#define RK3368_GRF_SOC_CON16 0x0440
+
+/* RK3368_GRF_SOC_CON15 */
+#define RK3368_GMAC_FLOW_CTRL GRF_BIT(8)
+#define RK3368_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(8)
+
+/* RK3368_GRF_SOC_CON16 */
+#define RK3368_GMAC_TXCLK_DLY_ENABLE GRF_BIT(7)
+#define RK3368_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(7)
+#define RK3368_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3368_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3368_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RK3368_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+static void rk3368_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3368_GRF_SOC_CON16,
+ DELAY_ENABLE(RK3368, tx_delay, rx_delay) |
+ RK3368_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3368_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rk3368_ops = {
+ .set_to_rgmii = rk3368_set_to_rgmii,
+
+ .gmac_grf_reg = RK3368_GRF_SOC_CON15,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(11, 9),
+ .gmac_rmii_mode_mask = BIT_U16(6),
+
+ .clock_grf_reg = RK3368_GRF_SOC_CON15,
+ .clock.gmii_clk_sel_mask = GENMASK_U16(5, 4),
+ .clock.rmii_clk_sel_mask = BIT_U16(3),
+ .clock.mac_speed_mask = BIT_U16(7),
+
+ .supports_rmii = true,
+};
+
+#define RK3399_GRF_SOC_CON5 0xc214
+#define RK3399_GRF_SOC_CON6 0xc218
+
+/* RK3399_GRF_SOC_CON5 */
+#define RK3399_GMAC_FLOW_CTRL GRF_BIT(8)
+#define RK3399_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(8)
+
+/* RK3399_GRF_SOC_CON6 */
+#define RK3399_GMAC_TXCLK_DLY_ENABLE GRF_BIT(7)
+#define RK3399_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(7)
+#define RK3399_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3399_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3399_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RK3399_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+static void rk3399_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3399_GRF_SOC_CON6,
+ DELAY_ENABLE(RK3399, tx_delay, rx_delay) |
+ RK3399_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3399_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rk3399_ops = {
+ .set_to_rgmii = rk3399_set_to_rgmii,
+
+ .gmac_grf_reg = RK3399_GRF_SOC_CON5,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(11, 9),
+ .gmac_rmii_mode_mask = BIT_U16(6),
+
+ .clock_grf_reg = RK3399_GRF_SOC_CON5,
+ .clock.gmii_clk_sel_mask = GENMASK_U16(5, 4),
+ .clock.rmii_clk_sel_mask = BIT_U16(3),
+ .clock.mac_speed_mask = BIT_U16(7),
+
+ .supports_rmii = true,
+};
+
+#define RK3506_GRF_SOC_CON8 0x0020
+#define RK3506_GRF_SOC_CON11 0x002c
+
+#define RK3506_GMAC_RMII_MODE GRF_BIT(1)
+
+static int rk3506_init(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->id) {
+ case 0:
+ bsp_priv->clock_grf_reg = RK3506_GRF_SOC_CON8;
+ return 0;
+
+ case 1:
+ bsp_priv->clock_grf_reg = RK3506_GRF_SOC_CON11;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct rk_gmac_ops rk3506_ops = {
+ .init = rk3506_init,
+
+ .clock.io_clksel_io_mask = BIT_U16(5),
+ .clock.rmii_clk_sel_mask = BIT_U16(3),
+ .clock.rmii_gate_en_mask = BIT_U16(2),
+ .clock.rmii_mode_mask = BIT_U16(1),
+
+ .supports_rmii = true,
+
+ .regs_valid = true,
+ .regs = {
+ 0xff4c8000, /* gmac0 */
+ 0xff4d0000, /* gmac1 */
+ 0x0, /* sentinel */
+ },
+};
+
+#define RK3528_VO_GRF_GMAC_CON 0x0018
+#define RK3528_VO_GRF_MACPHY_CON0 0x001c
+#define RK3528_VO_GRF_MACPHY_CON1 0x0020
+#define RK3528_VPU_GRF_GMAC_CON5 0x0018
+#define RK3528_VPU_GRF_GMAC_CON6 0x001c
+
+#define RK3528_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3528_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3528_GMAC_TXCLK_DLY_ENABLE GRF_BIT(14)
+#define RK3528_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(14)
+
+#define RK3528_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(15, 8, val)
+#define RK3528_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(7, 0, val)
+
+static int rk3528_init(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->id) {
+ case 0:
+ bsp_priv->clock_grf_reg = RK3528_VO_GRF_GMAC_CON;
+ bsp_priv->clock.rmii_clk_sel_mask = BIT_U16(3);
+ bsp_priv->clock.rmii_gate_en_mask = BIT_U16(2);
+ bsp_priv->clock.rmii_mode_mask = BIT_U16(1);
+ bsp_priv->supports_rgmii = false;
+ return 0;
+
+ case 1:
+ bsp_priv->clock_grf_reg = RK3528_VPU_GRF_GMAC_CON5;
+ bsp_priv->clock.io_clksel_io_mask = BIT_U16(12);
+ bsp_priv->clock.gmii_clk_sel_mask = GENMASK_U16(11, 10);
+ bsp_priv->clock.rmii_clk_sel_mask = BIT_U16(10);
+ bsp_priv->clock.rmii_gate_en_mask = BIT_U16(9);
+ bsp_priv->clock.rmii_mode_mask = BIT_U16(8);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void rk3528_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RK3528_VPU_GRF_GMAC_CON5,
+ DELAY_ENABLE(RK3528, tx_delay, rx_delay));
+
+ regmap_write(bsp_priv->grf, RK3528_VPU_GRF_GMAC_CON6,
+ RK3528_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3528_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static void rk3528_integrated_phy_powerup(struct rk_priv_data *bsp_priv)
+{
+ rk_gmac_integrated_fephy_powerup(bsp_priv, RK3528_VO_GRF_MACPHY_CON0);
+}
+
+static void rk3528_integrated_phy_powerdown(struct rk_priv_data *bsp_priv)
+{
+ rk_gmac_integrated_fephy_powerdown(bsp_priv, RK3528_VO_GRF_MACPHY_CON0);
+}
+
+static const struct rk_gmac_ops rk3528_ops = {
+ .init = rk3528_init,
+ .set_to_rgmii = rk3528_set_to_rgmii,
+ .integrated_phy_powerup = rk3528_integrated_phy_powerup,
+ .integrated_phy_powerdown = rk3528_integrated_phy_powerdown,
+
+ .supports_rmii = true,
+
+ .regs_valid = true,
+ .regs = {
+ 0xffbd0000, /* gmac0 */
+ 0xffbe0000, /* gmac1 */
+ 0x0, /* sentinel */
+ },
+};
+
+#define RK3568_GRF_GMAC0_CON0 0x0380
+#define RK3568_GRF_GMAC0_CON1 0x0384
+#define RK3568_GRF_GMAC1_CON0 0x0388
+#define RK3568_GRF_GMAC1_CON1 0x038c
+
+/* RK3568_GRF_GMAC0_CON1 && RK3568_GRF_GMAC1_CON1 */
+#define RK3568_GMAC_FLOW_CTRL GRF_BIT(3)
+#define RK3568_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(3)
+#define RK3568_GMAC_RXCLK_DLY_ENABLE GRF_BIT(1)
+#define RK3568_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(1)
+#define RK3568_GMAC_TXCLK_DLY_ENABLE GRF_BIT(0)
+#define RK3568_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(0)
+
+/* RK3568_GRF_GMAC0_CON0 && RK3568_GRF_GMAC1_CON0 */
+#define RK3568_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RK3568_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+static int rk3568_init(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->id) {
+ case 0:
+ bsp_priv->gmac_grf_reg = RK3568_GRF_GMAC0_CON1;
+ return 0;
+
+ case 1:
+ bsp_priv->gmac_grf_reg = RK3568_GRF_GMAC1_CON1;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void rk3568_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ u32 con0, con1;
+
+ con0 = (bsp_priv->id == 1) ? RK3568_GRF_GMAC1_CON0 :
+ RK3568_GRF_GMAC0_CON0;
+ con1 = (bsp_priv->id == 1) ? RK3568_GRF_GMAC1_CON1 :
+ RK3568_GRF_GMAC0_CON1;
+
+ regmap_write(bsp_priv->grf, con0,
+ RK3568_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3568_GMAC_CLK_TX_DL_CFG(tx_delay));
+
+ regmap_write(bsp_priv->grf, con1,
+ RK3568_GMAC_RXCLK_DLY_ENABLE |
+ RK3568_GMAC_TXCLK_DLY_ENABLE);
+}
+
+static const struct rk_gmac_ops rk3568_ops = {
+ .init = rk3568_init,
+ .set_to_rgmii = rk3568_set_to_rgmii,
+ .set_speed = rk_set_clk_mac_speed,
+
+ .gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
+
+ .supports_rmii = true,
+
+ .regs_valid = true,
+ .regs = {
+ 0xfe2a0000, /* gmac0 */
+ 0xfe010000, /* gmac1 */
+ 0x0, /* sentinel */
+ },
+};
+
+/* VCCIO0_1_3_IOC */
+#define RK3576_VCCIO0_1_3_IOC_CON2 0X6408
+#define RK3576_VCCIO0_1_3_IOC_CON3 0X640c
+#define RK3576_VCCIO0_1_3_IOC_CON4 0X6410
+#define RK3576_VCCIO0_1_3_IOC_CON5 0X6414
+
+#define RK3576_GMAC_RXCLK_DLY_ENABLE GRF_BIT(15)
+#define RK3576_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(15)
+#define RK3576_GMAC_TXCLK_DLY_ENABLE GRF_BIT(7)
+#define RK3576_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(7)
+
+#define RK3576_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RK3576_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+/* SDGMAC_GRF */
+#define RK3576_GRF_GMAC_CON0 0X0020
+#define RK3576_GRF_GMAC_CON1 0X0024
+
+static int rk3576_init(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->id) {
+ case 0:
+ bsp_priv->gmac_grf_reg = RK3576_GRF_GMAC_CON0;
+ bsp_priv->clock_grf_reg = RK3576_GRF_GMAC_CON0;
+ return 0;
+
+ case 1:
+ bsp_priv->gmac_grf_reg = RK3576_GRF_GMAC_CON1;
+ bsp_priv->clock_grf_reg = RK3576_GRF_GMAC_CON1;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void rk3576_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ unsigned int offset_con;
+
+ offset_con = bsp_priv->id == 1 ? RK3576_VCCIO0_1_3_IOC_CON4 :
+ RK3576_VCCIO0_1_3_IOC_CON2;
+
+ /* m0 && m1 delay enabled */
+ regmap_write(bsp_priv->php_grf, offset_con,
+ DELAY_ENABLE(RK3576, tx_delay, rx_delay));
+ regmap_write(bsp_priv->php_grf, offset_con + 0x4,
+ DELAY_ENABLE(RK3576, tx_delay, rx_delay));
+
+ /* m0 && m1 delay value */
+ regmap_write(bsp_priv->php_grf, offset_con,
+ RK3576_GMAC_CLK_TX_DL_CFG(tx_delay) |
+ RK3576_GMAC_CLK_RX_DL_CFG(rx_delay));
+ regmap_write(bsp_priv->php_grf, offset_con + 0x4,
+ RK3576_GMAC_CLK_TX_DL_CFG(tx_delay) |
+ RK3576_GMAC_CLK_RX_DL_CFG(rx_delay));
+}
+
+static const struct rk_gmac_ops rk3576_ops = {
+ .init = rk3576_init,
+ .set_to_rgmii = rk3576_set_to_rgmii,
+
+ .gmac_rmii_mode_mask = BIT_U16(3),
+
+ .clock.io_clksel_io_mask = BIT_U16(7),
+ .clock.gmii_clk_sel_mask = GENMASK_U16(6, 5),
+ .clock.rmii_clk_sel_mask = BIT_U16(5),
+ .clock.rmii_gate_en_mask = BIT_U16(4),
+
+ .supports_rmii = true,
+
+ .php_grf_required = true,
+ .regs_valid = true,
+ .regs = {
+ 0x2a220000, /* gmac0 */
+ 0x2a230000, /* gmac1 */
+ 0x0, /* sentinel */
+ },
+};
+
+/* sys_grf */
+#define RK3588_GRF_GMAC_CON7 0X031c
+#define RK3588_GRF_GMAC_CON8 0X0320
+#define RK3588_GRF_GMAC_CON9 0X0324
+
+#define RK3588_GMAC_RXCLK_DLY_ENABLE(id) GRF_BIT(2 * (id) + 3)
+#define RK3588_GMAC_RXCLK_DLY_DISABLE(id) GRF_CLR_BIT(2 * (id) + 3)
+#define RK3588_GMAC_TXCLK_DLY_ENABLE(id) GRF_BIT(2 * (id) + 2)
+#define RK3588_GMAC_TXCLK_DLY_DISABLE(id) GRF_CLR_BIT(2 * (id) + 2)
+
+#define RK3588_GMAC_CLK_RX_DL_CFG(val) GRF_FIELD(15, 8, val)
+#define RK3588_GMAC_CLK_TX_DL_CFG(val) GRF_FIELD(7, 0, val)
+
+/* php_grf */
+#define RK3588_GRF_GMAC_CON0 0X0008
+#define RK3588_GRF_CLK_CON1 0X0070
+
+static int rk3588_init(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->id) {
+ case 0:
+ bsp_priv->gmac_phy_intf_sel_mask = GENMASK_U16(5, 3);
+ bsp_priv->clock.io_clksel_cru_mask = BIT_U16(4);
+ bsp_priv->clock.gmii_clk_sel_mask = GENMASK_U16(3, 2);
+ bsp_priv->clock.rmii_clk_sel_mask = BIT_U16(2);
+ bsp_priv->clock.rmii_gate_en_mask = BIT_U16(1);
+ bsp_priv->clock.rmii_mode_mask = BIT_U16(0);
+ return 0;
+
+ case 1:
+ bsp_priv->gmac_phy_intf_sel_mask = GENMASK_U16(11, 9);
+ bsp_priv->clock.io_clksel_cru_mask = BIT_U16(9);
+ bsp_priv->clock.gmii_clk_sel_mask = GENMASK_U16(8, 7);
+ bsp_priv->clock.rmii_clk_sel_mask = BIT_U16(7);
+ bsp_priv->clock.rmii_gate_en_mask = BIT_U16(6);
+ bsp_priv->clock.rmii_mode_mask = BIT_U16(5);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void rk3588_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ u32 offset_con, id = bsp_priv->id;
+
+ offset_con = bsp_priv->id == 1 ? RK3588_GRF_GMAC_CON9 :
+ RK3588_GRF_GMAC_CON8;
+
+ regmap_write(bsp_priv->grf, RK3588_GRF_GMAC_CON7,
+ RK3588_GMAC_RXCLK_DLY_ENABLE(id) |
+ RK3588_GMAC_TXCLK_DLY_ENABLE(id));
+
+ regmap_write(bsp_priv->grf, offset_con,
+ RK3588_GMAC_CLK_RX_DL_CFG(rx_delay) |
+ RK3588_GMAC_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rk3588_ops = {
+ .init = rk3588_init,
+ .set_to_rgmii = rk3588_set_to_rgmii,
+
+ .gmac_grf_reg_in_php = true,
+ .gmac_grf_reg = RK3588_GRF_GMAC_CON0,
+
+ .clock_grf_reg_in_php = true,
+ .clock_grf_reg = RK3588_GRF_CLK_CON1,
+
+ .supports_rmii = true,
+
+ .php_grf_required = true,
+ .regs_valid = true,
+ .regs = {
+ 0xfe1b0000, /* gmac0 */
+ 0xfe1c0000, /* gmac1 */
+ 0x0, /* sentinel */
+ },
+};
+
+#define RV1108_GRF_GMAC_CON0 0X0900
+
+/* RV1108_GRF_GMAC_CON0 */
+#define RV1108_GMAC_FLOW_CTRL GRF_BIT(3)
+#define RV1108_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(3)
+
+static const struct rk_gmac_ops rv1108_ops = {
+ .gmac_grf_reg = RV1108_GRF_GMAC_CON0,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
+
+ .clock_grf_reg = RV1108_GRF_GMAC_CON0,
+ .clock.rmii_clk_sel_mask = BIT_U16(7),
+ .clock.mac_speed_mask = BIT_U16(2),
+
+ .supports_rmii = true,
+};
+
+#define RV1126_GRF_GMAC_CON0 0X0070
+#define RV1126_GRF_GMAC_CON1 0X0074
+#define RV1126_GRF_GMAC_CON2 0X0078
+
+/* RV1126_GRF_GMAC_CON0 */
+#define RV1126_GMAC_FLOW_CTRL GRF_BIT(7)
+#define RV1126_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(7)
+#define RV1126_GMAC_M0_RXCLK_DLY_ENABLE GRF_BIT(1)
+#define RV1126_GMAC_M0_RXCLK_DLY_DISABLE GRF_CLR_BIT(1)
+#define RV1126_GMAC_M0_TXCLK_DLY_ENABLE GRF_BIT(0)
+#define RV1126_GMAC_M0_TXCLK_DLY_DISABLE GRF_CLR_BIT(0)
+#define RV1126_GMAC_M1_RXCLK_DLY_ENABLE GRF_BIT(3)
+#define RV1126_GMAC_M1_RXCLK_DLY_DISABLE GRF_CLR_BIT(3)
+#define RV1126_GMAC_M1_TXCLK_DLY_ENABLE GRF_BIT(2)
+#define RV1126_GMAC_M1_TXCLK_DLY_DISABLE GRF_CLR_BIT(2)
+
+/* RV1126_GRF_GMAC_CON1 */
+#define RV1126_GMAC_M0_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RV1126_GMAC_M0_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+/* RV1126_GRF_GMAC_CON2 */
+#define RV1126_GMAC_M1_CLK_RX_DL_CFG(val) GRF_FIELD(14, 8, val)
+#define RV1126_GMAC_M1_CLK_TX_DL_CFG(val) GRF_FIELD(6, 0, val)
+
+static void rv1126_set_to_rgmii(struct rk_priv_data *bsp_priv,
+ int tx_delay, int rx_delay)
+{
+ regmap_write(bsp_priv->grf, RV1126_GRF_GMAC_CON0,
+ RV1126_GMAC_M0_RXCLK_DLY_ENABLE |
+ RV1126_GMAC_M0_TXCLK_DLY_ENABLE |
+ RV1126_GMAC_M1_RXCLK_DLY_ENABLE |
+ RV1126_GMAC_M1_TXCLK_DLY_ENABLE);
+
+ regmap_write(bsp_priv->grf, RV1126_GRF_GMAC_CON1,
+ RV1126_GMAC_M0_CLK_RX_DL_CFG(rx_delay) |
+ RV1126_GMAC_M0_CLK_TX_DL_CFG(tx_delay));
+
+ regmap_write(bsp_priv->grf, RV1126_GRF_GMAC_CON2,
+ RV1126_GMAC_M1_CLK_RX_DL_CFG(rx_delay) |
+ RV1126_GMAC_M1_CLK_TX_DL_CFG(tx_delay));
+}
+
+static const struct rk_gmac_ops rv1126_ops = {
+ .set_to_rgmii = rv1126_set_to_rgmii,
+ .set_speed = rk_set_clk_mac_speed,
+
+ .gmac_grf_reg = RV1126_GRF_GMAC_CON0,
+ .gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
+
+ .supports_rmii = true,
+};
+
+static int rk_gmac_clk_init(struct plat_stmmacenet_data *plat)
+{
+ struct rk_priv_data *bsp_priv = plat->bsp_priv;
+ int phy_iface = bsp_priv->phy_iface;
+ struct device *dev = bsp_priv->dev;
+ int i, j, ret;
+
+ bsp_priv->clk_enabled = false;
+
+ bsp_priv->num_clks = ARRAY_SIZE(rk_clocks);
+ if (phy_iface == PHY_INTERFACE_MODE_RMII)
+ bsp_priv->num_clks += ARRAY_SIZE(rk_rmii_clocks);
+
+ bsp_priv->clks = devm_kcalloc(dev, bsp_priv->num_clks,
+ sizeof(*bsp_priv->clks), GFP_KERNEL);
+ if (!bsp_priv->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(rk_clocks); i++)
+ bsp_priv->clks[i].id = rk_clocks[i];
+
+ if (phy_iface == PHY_INTERFACE_MODE_RMII) {
+ for (j = 0; j < ARRAY_SIZE(rk_rmii_clocks); j++)
+ bsp_priv->clks[i++].id = rk_rmii_clocks[j];
+ }
+
+ ret = devm_clk_bulk_get_optional(dev, bsp_priv->num_clks,
+ bsp_priv->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get clocks\n");
+
+ if (bsp_priv->clock_input) {
+ dev_info(dev, "clock input from PHY\n");
+ } else if (phy_iface == PHY_INTERFACE_MODE_RMII) {
+ clk_set_rate(plat->stmmac_clk, 50000000);
+ }
+
+ if (plat->phy_node && bsp_priv->integrated_phy) {
+ bsp_priv->clk_phy = of_clk_get(plat->phy_node, 0);
+ ret = PTR_ERR_OR_ZERO(bsp_priv->clk_phy);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot get PHY clock\n");
+ clk_set_rate(bsp_priv->clk_phy, 50000000);
+ }
+
+ return 0;
+}
+
+static int gmac_clk_enable(struct rk_priv_data *bsp_priv, bool enable)
+{
+ int ret;
+
+ if (enable) {
+ if (!bsp_priv->clk_enabled) {
+ ret = clk_bulk_prepare_enable(bsp_priv->num_clks,
+ bsp_priv->clks);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(bsp_priv->clk_phy);
+ if (ret)
+ return ret;
+
+ rk_configure_io_clksel(bsp_priv);
+ rk_ungate_rmii_clock(bsp_priv);
+
+ mdelay(5);
+ bsp_priv->clk_enabled = true;
+ }
+ } else {
+ if (bsp_priv->clk_enabled) {
+ rk_gate_rmii_clock(bsp_priv);
+
+ clk_bulk_disable_unprepare(bsp_priv->num_clks,
+ bsp_priv->clks);
+ clk_disable_unprepare(bsp_priv->clk_phy);
+
+ bsp_priv->clk_enabled = false;
+ }
+ }
+
+ return 0;
+}
+
+static int rk_phy_powerup(struct rk_priv_data *bsp_priv)
+{
+ struct regulator *ldo = bsp_priv->regulator;
+ int ret;
+
+ ret = regulator_enable(ldo);
+ if (ret)
+ dev_err(bsp_priv->dev, "fail to enable phy-supply\n");
+
+ return ret;
+}
+
+static void rk_phy_powerdown(struct rk_priv_data *bsp_priv)
+{
+ struct regulator *ldo = bsp_priv->regulator;
+ int ret;
+
+ ret = regulator_disable(ldo);
+ if (ret)
+ dev_err(bsp_priv->dev, "fail to disable phy-supply\n");
+}
+
+static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat,
+ const struct rk_gmac_ops *ops)
+{
+ struct rk_priv_data *bsp_priv;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret;
+ const char *strings = NULL;
+ int value;
+
+ bsp_priv = devm_kzalloc(dev, sizeof(*bsp_priv), GFP_KERNEL);
+ if (!bsp_priv)
+ return ERR_PTR(-ENOMEM);
+
+ bsp_priv->phy_iface = plat->phy_interface;
+ bsp_priv->ops = ops;
+
+ /* Some SoCs have multiple MAC controllers, which need
+ * to be distinguished.
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res && ops->regs_valid) {
+ int i = 0;
+
+ while (ops->regs[i]) {
+ if (ops->regs[i] == res->start) {
+ bsp_priv->id = i;
+ break;
+ }
+ i++;
+ }
+ }
+
+ bsp_priv->regulator = devm_regulator_get(dev, "phy");
+ if (IS_ERR(bsp_priv->regulator)) {
+ ret = PTR_ERR(bsp_priv->regulator);
+ dev_err_probe(dev, ret, "failed to get phy regulator\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_string(dev->of_node, "clock_in_out", &strings);
+ if (ret) {
+ dev_err(dev, "Can not read property: clock_in_out.\n");
+ bsp_priv->clock_input = true;
+ } else {
+ dev_info(dev, "clock input or output? (%s).\n",
+ strings);
+ if (!strcmp(strings, "input"))
+ bsp_priv->clock_input = true;
+ else
+ bsp_priv->clock_input = false;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "tx_delay", &value);
+ if (ret) {
+ bsp_priv->tx_delay = 0x30;
+ dev_err(dev, "Can not read property: tx_delay.");
+ dev_err(dev, "set tx_delay to 0x%x\n",
+ bsp_priv->tx_delay);
+ } else {
+ dev_info(dev, "TX delay(0x%x).\n", value);
+ bsp_priv->tx_delay = value;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "rx_delay", &value);
+ if (ret) {
+ bsp_priv->rx_delay = 0x10;
+ dev_err(dev, "Can not read property: rx_delay.");
+ dev_err(dev, "set rx_delay to 0x%x\n",
+ bsp_priv->rx_delay);
+ } else {
+ dev_info(dev, "RX delay(0x%x).\n", value);
+ bsp_priv->rx_delay = value;
+ }
+
+ bsp_priv->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(bsp_priv->grf)) {
+ dev_err_probe(dev, PTR_ERR(bsp_priv->grf),
+ "failed to lookup rockchip,grf\n");
+ return ERR_CAST(bsp_priv->grf);
+ }
+
+ if (ops->php_grf_required) {
+ bsp_priv->php_grf =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,php-grf");
+ if (IS_ERR(bsp_priv->php_grf)) {
+ dev_err_probe(dev, PTR_ERR(bsp_priv->php_grf),
+ "failed to lookup rockchip,php-grf\n");
+ return ERR_CAST(bsp_priv->php_grf);
+ }
+ }
+
+ if (plat->phy_node) {
+ bsp_priv->integrated_phy = of_property_read_bool(plat->phy_node,
+ "phy-is-integrated");
+ if (bsp_priv->integrated_phy) {
+ bsp_priv->phy_reset = of_reset_control_get(plat->phy_node, NULL);
+ if (IS_ERR(bsp_priv->phy_reset)) {
+ dev_err(&pdev->dev, "No PHY reset control found.\n");
+ bsp_priv->phy_reset = NULL;
+ }
+ }
+ }
+ dev_info(dev, "integrated PHY? (%s).\n",
+ bsp_priv->integrated_phy ? "yes" : "no");
+
+ bsp_priv->dev = dev;
+
+ /* Set the default phy_intf_sel and RMII mode register parameters. */
+ bsp_priv->gmac_grf_reg = ops->gmac_grf_reg;
+ bsp_priv->gmac_phy_intf_sel_mask = ops->gmac_phy_intf_sel_mask;
+ bsp_priv->gmac_rmii_mode_mask = ops->gmac_rmii_mode_mask;
+
+ /* Set the default clock control register related parameters */
+ bsp_priv->clock_grf_reg = ops->clock_grf_reg;
+ bsp_priv->clock = ops->clock;
+
+ bsp_priv->supports_rgmii = ops->supports_rgmii || !!ops->set_to_rgmii;
+ bsp_priv->supports_rmii = ops->supports_rmii || !!ops->set_to_rmii;
+
+ if (ops->init) {
+ ret = ops->init(bsp_priv);
+ if (ret) {
+ reset_control_put(bsp_priv->phy_reset);
+ dev_err_probe(dev, ret, "failed to init BSP\n");
+ return ERR_PTR(ret);
+ }
+ }
+
+ if (bsp_priv->clock.io_clksel_cru_mask &&
+ bsp_priv->clock.io_clksel_io_mask)
+ dev_warn(dev, "both CRU and IO io_clksel masks should not be populated - driver may malfunction\n");
+
+ return bsp_priv;
+}
+
+static int rk_gmac_check_ops(struct rk_priv_data *bsp_priv)
+{
+ switch (bsp_priv->phy_iface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ if (!bsp_priv->supports_rgmii)
+ return -EINVAL;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (!bsp_priv->supports_rmii)
+ return -EINVAL;
+ break;
+ default:
+ dev_err(bsp_priv->dev,
+ "unsupported interface %d", bsp_priv->phy_iface);
+ }
+ return 0;
+}
+
+static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
+{
+ struct device *dev = bsp_priv->dev;
+ u32 val;
+ int ret;
+ u8 intf;
+
+ ret = rk_gmac_check_ops(bsp_priv);
+ if (ret)
+ return ret;
+
+ ret = rk_get_phy_intf_sel(bsp_priv->phy_iface);
+ if (ret < 0)
+ return ret;
+
+ intf = ret;
+
+ ret = gmac_clk_enable(bsp_priv, true);
+ if (ret)
+ return ret;
+
+ if (bsp_priv->gmac_phy_intf_sel_mask ||
+ bsp_priv->gmac_rmii_mode_mask) {
+ /* If defined, encode the phy_intf_sel value */
+ val = rk_encode_wm16(intf, bsp_priv->gmac_phy_intf_sel_mask);
+
+ /* If defined, encode the RMII mode mask setting. */
+ val |= rk_encode_wm16(intf == PHY_INTF_SEL_RMII,
+ bsp_priv->gmac_rmii_mode_mask);
+
+ ret = rk_write_gmac_grf_reg(bsp_priv, val);
+ if (ret < 0) {
+ gmac_clk_enable(bsp_priv, false);
+ return ret;
+ }
+ }
+
+ if (bsp_priv->clock.rmii_mode_mask) {
+ val = rk_encode_wm16(intf == PHY_INTF_SEL_RMII,
+ bsp_priv->clock.rmii_mode_mask);
+
+ ret = rk_write_clock_grf_reg(bsp_priv, val);
+ if (ret < 0) {
+ gmac_clk_enable(bsp_priv, false);
+ return ret;
+ }
+ }
+
+ /*rmii or rgmii*/
+ switch (bsp_priv->phy_iface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ dev_info(dev, "init for RGMII\n");
+ if (bsp_priv->ops->set_to_rgmii)
+ bsp_priv->ops->set_to_rgmii(bsp_priv,
+ bsp_priv->tx_delay,
+ bsp_priv->rx_delay);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ dev_info(dev, "init for RGMII_ID\n");
+ if (bsp_priv->ops->set_to_rgmii)
+ bsp_priv->ops->set_to_rgmii(bsp_priv, 0, 0);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ dev_info(dev, "init for RGMII_RXID\n");
+ if (bsp_priv->ops->set_to_rgmii)
+ bsp_priv->ops->set_to_rgmii(bsp_priv,
+ bsp_priv->tx_delay, 0);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ dev_info(dev, "init for RGMII_TXID\n");
+ if (bsp_priv->ops->set_to_rgmii)
+ bsp_priv->ops->set_to_rgmii(bsp_priv,
+ 0, bsp_priv->rx_delay);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ dev_info(dev, "init for RMII\n");
+ if (bsp_priv->ops->set_to_rmii)
+ bsp_priv->ops->set_to_rmii(bsp_priv);
+ break;
+ default:
+ dev_err(dev, "NO interface defined!\n");
+ }
+
+ ret = rk_phy_powerup(bsp_priv);
+ if (ret) {
+ gmac_clk_enable(bsp_priv, false);
+ return ret;
+ }
+
+ pm_runtime_get_sync(dev);
+
+ if (bsp_priv->integrated_phy && bsp_priv->ops->integrated_phy_powerup)
+ bsp_priv->ops->integrated_phy_powerup(bsp_priv);
+
+ return 0;
+}
+
+static void rk_gmac_powerdown(struct rk_priv_data *gmac)
+{
+ if (gmac->integrated_phy && gmac->ops->integrated_phy_powerdown)
+ gmac->ops->integrated_phy_powerdown(gmac);
+
+ pm_runtime_put_sync(gmac->dev);
+
+ rk_phy_powerdown(gmac);
+ gmac_clk_enable(gmac, false);
+}
+
+static void rk_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
+ unsigned long *interfaces)
+{
+ struct rk_priv_data *rk = bsp_priv;
+
+ if (rk->supports_rgmii)
+ phy_interface_set_rgmii(interfaces);
+
+ if (rk->supports_rmii)
+ __set_bit(PHY_INTERFACE_MODE_RMII, interfaces);
+}
+
+static int rk_set_clk_tx_rate(void *bsp_priv_, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct rk_priv_data *bsp_priv = bsp_priv_;
+ int ret = -EINVAL;
+ bool is_100m;
+ u32 val;
+
+ if (bsp_priv->ops->set_speed) {
+ ret = bsp_priv->ops->set_speed(bsp_priv, interface, speed);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (phy_interface_mode_is_rgmii(interface) &&
+ bsp_priv->clock.gmii_clk_sel_mask) {
+ ret = rk_gmac_rgmii_clk_div(speed);
+ if (ret < 0)
+ return ret;
+
+ val = rk_encode_wm16(ret, bsp_priv->clock.gmii_clk_sel_mask);
+
+ ret = rk_write_clock_grf_reg(bsp_priv, val);
+ } else if (interface == PHY_INTERFACE_MODE_RMII &&
+ (bsp_priv->clock.rmii_clk_sel_mask ||
+ bsp_priv->clock.mac_speed_mask)) {
+ is_100m = speed == SPEED_100;
+ val = rk_encode_wm16(is_100m, bsp_priv->clock.mac_speed_mask) |
+ rk_encode_wm16(is_100m,
+ bsp_priv->clock.rmii_clk_sel_mask);
+
+ ret = rk_write_clock_grf_reg(bsp_priv, val);
+ }
+
+ return ret;
+}
+
+static int rk_gmac_suspend(struct device *dev, void *bsp_priv_)
+{
+ struct rk_priv_data *bsp_priv = bsp_priv_;
+
+ /* Keep the PHY up if we use Wake-on-Lan. */
+ if (!device_may_wakeup(dev))
+ rk_gmac_powerdown(bsp_priv);
+
+ return 0;
+}
+
+static int rk_gmac_resume(struct device *dev, void *bsp_priv_)
+{
+ struct rk_priv_data *bsp_priv = bsp_priv_;
+
+ /* The PHY was up for Wake-on-Lan. */
+ if (!device_may_wakeup(dev))
+ rk_gmac_powerup(bsp_priv);
+
+ return 0;
+}
+
+static int rk_gmac_init(struct device *dev, void *bsp_priv)
+{
+ return rk_gmac_powerup(bsp_priv);
+}
+
+static void rk_gmac_exit(struct device *dev, void *bsp_priv_)
+{
+ struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev));
+ struct rk_priv_data *bsp_priv = bsp_priv_;
+
+ rk_gmac_powerdown(bsp_priv);
+
+ if (priv->plat->phy_node && bsp_priv->integrated_phy)
+ clk_put(bsp_priv->clk_phy);
+
+ reset_control_put(bsp_priv->phy_reset);
+}
+
+static int rk_gmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ const struct rk_gmac_ops *data;
+ int ret;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "no of match data provided\n");
+ return -EINVAL;
+ }
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ /* If the stmmac is not already selected as gmac4,
+ * then make sure we fallback to gmac.
+ */
+ if (plat_dat->core_type != DWMAC_CORE_GMAC4) {
+ plat_dat->core_type = DWMAC_CORE_GMAC;
+ plat_dat->rx_fifo_size = 4096;
+ plat_dat->tx_fifo_size = 2048;
+ }
+
+ plat_dat->get_interfaces = rk_get_interfaces;
+ plat_dat->set_clk_tx_rate = rk_set_clk_tx_rate;
+ plat_dat->init = rk_gmac_init;
+ plat_dat->exit = rk_gmac_exit;
+ plat_dat->suspend = rk_gmac_suspend;
+ plat_dat->resume = rk_gmac_resume;
+
+ plat_dat->bsp_priv = rk_gmac_setup(pdev, plat_dat, data);
+ if (IS_ERR(plat_dat->bsp_priv))
+ return PTR_ERR(plat_dat->bsp_priv);
+
+ ret = rk_gmac_clk_init(plat_dat);
+ if (ret)
+ return ret;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id rk_gmac_dwmac_match[] = {
+ { .compatible = "rockchip,px30-gmac", .data = &px30_ops },
+ { .compatible = "rockchip,rk3128-gmac", .data = &rk3128_ops },
+ { .compatible = "rockchip,rk3228-gmac", .data = &rk3228_ops },
+ { .compatible = "rockchip,rk3288-gmac", .data = &rk3288_ops },
+ { .compatible = "rockchip,rk3308-gmac", .data = &rk3308_ops },
+ { .compatible = "rockchip,rk3328-gmac", .data = &rk3328_ops },
+ { .compatible = "rockchip,rk3366-gmac", .data = &rk3366_ops },
+ { .compatible = "rockchip,rk3368-gmac", .data = &rk3368_ops },
+ { .compatible = "rockchip,rk3399-gmac", .data = &rk3399_ops },
+ { .compatible = "rockchip,rk3506-gmac", .data = &rk3506_ops },
+ { .compatible = "rockchip,rk3528-gmac", .data = &rk3528_ops },
+ { .compatible = "rockchip,rk3568-gmac", .data = &rk3568_ops },
+ { .compatible = "rockchip,rk3576-gmac", .data = &rk3576_ops },
+ { .compatible = "rockchip,rk3588-gmac", .data = &rk3588_ops },
+ { .compatible = "rockchip,rv1108-gmac", .data = &rv1108_ops },
+ { .compatible = "rockchip,rv1126-gmac", .data = &rv1126_ops },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rk_gmac_dwmac_match);
+
+static struct platform_driver rk_gmac_dwmac_driver = {
+ .probe = rk_gmac_probe,
+ .driver = {
+ .name = "rk_gmac-dwmac",
+ .pm = &stmmac_simple_pm_ops,
+ .of_match_table = rk_gmac_dwmac_match,
+ },
+};
+module_platform_driver(rk_gmac_dwmac_driver);
+
+MODULE_AUTHOR("Chen-Zhi (Roger Chen) <roger.chen@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip RK3288 DWMAC specific glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
new file mode 100644
index 000000000..13634965b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 Schneider-Electric
+ *
+ * Clément Léger <clement.leger@bootlin.com>
+ */
+
+#include <linux/of.h>
+#include <linux/pcs-rzn1-miic.h>
+#include <linux/phylink.h>
+#include <linux/platform_device.h>
+
+#include "stmmac_platform.h"
+#include "stmmac.h"
+
+static int rzn1_dwmac_pcs_init(struct stmmac_priv *priv)
+{
+ struct device_node *np = priv->device->of_node;
+ struct device_node *pcs_node;
+ struct phylink_pcs *pcs;
+
+ pcs_node = of_parse_phandle(np, "pcs-handle", 0);
+
+ if (pcs_node) {
+ pcs = miic_create(priv->device, pcs_node);
+ of_node_put(pcs_node);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+
+ priv->hw->phylink_pcs = pcs;
+ }
+
+ return 0;
+}
+
+static void rzn1_dwmac_pcs_exit(struct stmmac_priv *priv)
+{
+ if (priv->hw->phylink_pcs)
+ miic_destroy(priv->hw->phylink_pcs);
+}
+
+static struct phylink_pcs *rzn1_dwmac_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ return priv->hw->phylink_pcs;
+}
+
+static int rzn1_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ plat_dat->bsp_priv = plat_dat;
+ plat_dat->pcs_init = rzn1_dwmac_pcs_init;
+ plat_dat->pcs_exit = rzn1_dwmac_pcs_exit;
+ plat_dat->select_pcs = rzn1_dwmac_select_pcs;
+
+ ret = stmmac_dvr_probe(dev, plat_dat, &stmmac_res);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id rzn1_dwmac_match[] = {
+ { .compatible = "renesas,rzn1-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rzn1_dwmac_match);
+
+static struct platform_driver rzn1_dwmac_driver = {
+ .probe = rzn1_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "rzn1-dwmac",
+ .of_match_table = rzn1_dwmac_match,
+ },
+};
+module_platform_driver(rzn1_dwmac_driver);
+
+MODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>");
+MODULE_DESCRIPTION("Renesas RZN1 DWMAC specific glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c
new file mode 100644
index 000000000..024d8e10e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NXP S32G/R GMAC glue layer
+ *
+ * Copyright 2019-2026 NXP
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+#include <linux/phylink.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define GMAC_INTF_RATE_125M 125000000 /* 125MHz */
+
+/* SoC PHY interface control register */
+#define S32_PHY_INTF_SEL_MII 0x00
+#define S32_PHY_INTF_SEL_SGMII 0x01
+#define S32_PHY_INTF_SEL_RGMII 0x02
+#define S32_PHY_INTF_SEL_RMII 0x08
+
+struct s32_priv_data {
+ void __iomem *ioaddr;
+ void __iomem *ctrl_sts;
+ struct regmap *sts_regmap;
+ unsigned int sts_offset;
+ struct device *dev;
+ phy_interface_t *intf_mode;
+ struct clk *tx_clk;
+ struct clk *rx_clk;
+};
+
+static int s32_gmac_write_phy_intf_select(struct s32_priv_data *gmac)
+{
+ int ret = 0;
+
+ if (gmac->ctrl_sts)
+ writel(S32_PHY_INTF_SEL_RGMII, gmac->ctrl_sts);
+ else
+ ret = regmap_write(gmac->sts_regmap, gmac->sts_offset,
+ S32_PHY_INTF_SEL_RGMII);
+
+ dev_dbg(gmac->dev, "PHY mode set to %s\n", phy_modes(*gmac->intf_mode));
+
+ return ret;
+}
+
+static int s32_gmac_init(struct device *dev, void *priv)
+{
+ struct s32_priv_data *gmac = priv;
+ int ret;
+
+ /* Set initial TX interface clock */
+ ret = clk_prepare_enable(gmac->tx_clk);
+ if (ret) {
+ dev_err(dev, "Can't enable tx clock\n");
+ return ret;
+ }
+ ret = clk_set_rate(gmac->tx_clk, GMAC_INTF_RATE_125M);
+ if (ret) {
+ dev_err(dev, "Can't set tx clock\n");
+ goto err_tx_disable;
+ }
+
+ /* Set initial RX interface clock */
+ ret = clk_prepare_enable(gmac->rx_clk);
+ if (ret) {
+ dev_err(dev, "Can't enable rx clock\n");
+ goto err_tx_disable;
+ }
+ ret = clk_set_rate(gmac->rx_clk, GMAC_INTF_RATE_125M);
+ if (ret) {
+ dev_err(dev, "Can't set rx clock\n");
+ goto err_txrx_disable;
+ }
+
+ /* Set interface mode */
+ ret = s32_gmac_write_phy_intf_select(gmac);
+ if (ret) {
+ dev_err(dev, "Can't set PHY interface mode\n");
+ goto err_txrx_disable;
+ }
+
+ return 0;
+
+err_txrx_disable:
+ clk_disable_unprepare(gmac->rx_clk);
+err_tx_disable:
+ clk_disable_unprepare(gmac->tx_clk);
+ return ret;
+}
+
+static void s32_gmac_exit(struct device *dev, void *priv)
+{
+ struct s32_priv_data *gmac = priv;
+
+ clk_disable_unprepare(gmac->tx_clk);
+ clk_disable_unprepare(gmac->rx_clk);
+}
+
+static void s32_gmac_setup_multi_irq(struct device *dev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ int i;
+
+ /* RX IRQs */
+ for (i = 0; i < plat->rx_queues_to_use; i++) {
+ if (res->rx_irq[i] <= 0) {
+ dev_dbg(dev, "Missing RX queue %d interrupt\n", i);
+ goto mac_irq_mode;
+ }
+ }
+
+ /* TX IRQs */
+ for (i = 0; i < plat->tx_queues_to_use; i++) {
+ if (res->tx_irq[i] <= 0) {
+ dev_dbg(dev, "Missing TX queue %d interrupt\n", i);
+ goto mac_irq_mode;
+ }
+ }
+
+ plat->flags |= STMMAC_FLAG_MULTI_MSI_EN;
+ dev_info(dev, "Multi-IRQ mode (per queue IRQs) selected\n");
+ return;
+
+mac_irq_mode:
+ plat->flags &= ~STMMAC_FLAG_MULTI_MSI_EN;
+ dev_info(dev, "MAC IRQ mode selected\n");
+}
+
+static int s32_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat;
+ struct device *dev = &pdev->dev;
+ struct stmmac_resources res;
+ struct s32_priv_data *gmac;
+ int ret;
+
+ gmac = devm_kzalloc(&pdev->dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac)
+ return -ENOMEM;
+
+ gmac->dev = &pdev->dev;
+
+ ret = stmmac_get_platform_resources(pdev, &res);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get platform resources\n");
+
+ plat = devm_stmmac_probe_config_dt(pdev, res.mac);
+ if (IS_ERR(plat))
+ return dev_err_probe(dev, PTR_ERR(plat),
+ "dt configuration failed\n");
+
+ /* PHY interface mode control reg */
+ gmac->sts_regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node,
+ "nxp,phy-sel", 1, &gmac->sts_offset);
+ if (gmac->sts_regmap == ERR_PTR(-EPROBE_DEFER))
+ return PTR_ERR(gmac->sts_regmap);
+ if (IS_ERR(gmac->sts_regmap)) {
+ gmac->ctrl_sts = devm_platform_get_and_ioremap_resource(pdev, 1, NULL);
+ if (IS_ERR(gmac->ctrl_sts))
+ return dev_err_probe(dev, PTR_ERR(gmac->ctrl_sts),
+ "S32CC config region is missing\n");
+ }
+
+ /* tx clock */
+ gmac->tx_clk = devm_clk_get(&pdev->dev, "tx");
+ if (IS_ERR(gmac->tx_clk))
+ return dev_err_probe(dev, PTR_ERR(gmac->tx_clk),
+ "tx clock not found\n");
+
+ /* rx clock */
+ gmac->rx_clk = devm_clk_get(&pdev->dev, "rx");
+ if (IS_ERR(gmac->rx_clk))
+ return dev_err_probe(dev, PTR_ERR(gmac->rx_clk),
+ "rx clock not found\n");
+
+ gmac->intf_mode = &plat->phy_interface;
+ gmac->ioaddr = res.addr;
+
+ /* S32CC core feature set */
+ plat->core_type = DWMAC_CORE_GMAC4;
+ plat->pmt = true;
+ plat->flags |= STMMAC_FLAG_SPH_DISABLE;
+
+ s32_gmac_setup_multi_irq(dev, plat, &res);
+
+ plat->rx_fifo_size = 20480;
+ plat->tx_fifo_size = 20480;
+
+ plat->init = s32_gmac_init;
+ plat->exit = s32_gmac_exit;
+
+ plat->clk_tx_i = gmac->tx_clk;
+ plat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+
+ plat->bsp_priv = gmac;
+
+ return stmmac_pltfr_probe(pdev, plat, &res);
+}
+
+static const struct of_device_id s32_dwmac_match[] = {
+ { .compatible = "nxp,s32g2-dwmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s32_dwmac_match);
+
+static struct platform_driver s32_dwmac_driver = {
+ .probe = s32_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "s32-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = s32_dwmac_match,
+ },
+};
+module_platform_driver(s32_dwmac_driver);
+
+MODULE_AUTHOR("Jan Petrous (OSS) <jan.petrous@oss.nxp.com>");
+MODULE_DESCRIPTION("NXP S32G/R common chassis GMAC driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
new file mode 100644
index 000000000..1d7f0a57d
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
@@ -0,0 +1,692 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright Altera Corporation (C) 2014. All rights reserved.
+ *
+ * Adopted from dwmac-sti.c
+ */
+
+#include <linux/mfd/altera-sysmgr.h>
+#include <linux/clocksource_ids.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+#include <linux/mdio/mdio-regmap.h>
+#include <linux/pcs-lynx.h>
+#include <linux/reset.h>
+#include <linux/stmmac.h>
+
+#include "dwxgmac2.h"
+#include "stmmac.h"
+#include "stmmac_platform.h"
+#include "stmmac_ptp.h"
+
+#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII 0x0
+#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII 0x1
+#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII 0x2
+#define SYSMGR_EMACGRP_CTRL_PHYSEL_WIDTH 2
+#define SYSMGR_EMACGRP_CTRL_PHYSEL_MASK 0x00000003
+#define SYSMGR_EMACGRP_CTRL_PTP_REF_CLK_MASK 0x00000010
+#define SYSMGR_GEN10_EMACGRP_CTRL_PTP_REF_CLK_MASK 0x00000100
+
+#define SYSMGR_FPGAGRP_MODULE_REG 0x00000028
+#define SYSMGR_FPGAGRP_MODULE_EMAC 0x00000004
+#define SYSMGR_FPGAINTF_EMAC_REG 0x00000070
+#define SYSMGR_FPGAINTF_EMAC_BIT 0x1
+
+#define EMAC_SPLITTER_CTRL_REG 0x0
+#define EMAC_SPLITTER_CTRL_SPEED_MASK 0x3
+#define EMAC_SPLITTER_CTRL_SPEED_10 0x2
+#define EMAC_SPLITTER_CTRL_SPEED_100 0x3
+#define EMAC_SPLITTER_CTRL_SPEED_1000 0x0
+
+#define SGMII_ADAPTER_CTRL_REG 0x00
+#define SGMII_ADAPTER_ENABLE 0x0000
+#define SGMII_ADAPTER_DISABLE 0x0001
+
+#define SMTG_MDIO_ADDR 0x15
+#define SMTG_TSC_WORD0 0xC
+#define SMTG_TSC_WORD1 0xD
+#define SMTG_TSC_WORD2 0xE
+#define SMTG_TSC_WORD3 0xF
+#define SMTG_TSC_SHIFT 16
+
+struct socfpga_dwmac;
+struct socfpga_dwmac_ops {
+ int (*set_phy_mode)(struct socfpga_dwmac *dwmac_priv,
+ struct device *dev);
+ void (*setup_plat_dat)(struct socfpga_dwmac *dwmac_priv);
+};
+
+struct socfpga_dwmac {
+ u32 reg_offset;
+ u32 reg_shift;
+ struct plat_stmmacenet_data *plat_dat;
+ struct regmap *sys_mgr_base_addr;
+ struct reset_control *stmmac_rst;
+ struct reset_control *stmmac_ocp_rst;
+ void __iomem *splitter_base;
+ void __iomem *tse_pcs_base;
+ void __iomem *sgmii_adapter_base;
+ bool f2h_ptp_ref_clk;
+ const struct socfpga_dwmac_ops *ops;
+};
+
+static phy_interface_t socfpga_get_plat_phymode(struct socfpga_dwmac *dwmac)
+{
+ return dwmac->plat_dat->phy_interface;
+}
+
+static void socfpga_sgmii_config(struct socfpga_dwmac *dwmac, bool enable)
+{
+ u16 val = enable ? SGMII_ADAPTER_ENABLE : SGMII_ADAPTER_DISABLE;
+
+ writew(val, dwmac->sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
+}
+
+static void socfpga_dwmac_fix_mac_speed(void *bsp_priv,
+ phy_interface_t interface, int speed,
+ unsigned int mode)
+{
+ struct socfpga_dwmac *dwmac = (struct socfpga_dwmac *)bsp_priv;
+ void __iomem *sgmii_adapter_base = dwmac->sgmii_adapter_base;
+ phy_interface_t phymode = socfpga_get_plat_phymode(dwmac);
+ void __iomem *splitter_base = dwmac->splitter_base;
+ u32 val;
+
+ if (sgmii_adapter_base)
+ socfpga_sgmii_config(dwmac, false);
+
+ if (splitter_base) {
+ val = readl(splitter_base + EMAC_SPLITTER_CTRL_REG);
+ val &= ~EMAC_SPLITTER_CTRL_SPEED_MASK;
+
+ switch (speed) {
+ case 1000:
+ val |= EMAC_SPLITTER_CTRL_SPEED_1000;
+ break;
+ case 100:
+ val |= EMAC_SPLITTER_CTRL_SPEED_100;
+ break;
+ case 10:
+ val |= EMAC_SPLITTER_CTRL_SPEED_10;
+ break;
+ default:
+ return;
+ }
+ writel(val, splitter_base + EMAC_SPLITTER_CTRL_REG);
+ }
+
+ if ((phymode == PHY_INTERFACE_MODE_SGMII ||
+ phymode == PHY_INTERFACE_MODE_1000BASEX) && sgmii_adapter_base)
+ socfpga_sgmii_config(dwmac, true);
+}
+
+static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct regmap *sys_mgr_base_addr;
+ u32 reg_offset, reg_shift;
+ int ret, index;
+ struct device_node *np_splitter = NULL;
+ struct device_node *np_sgmii_adapter = NULL;
+ struct resource res_splitter;
+ struct resource res_tse_pcs;
+ struct resource res_sgmii_adapter;
+
+ sys_mgr_base_addr =
+ altr_sysmgr_regmap_lookup_by_phandle(np, "altr,sysmgr-syscon");
+ if (IS_ERR(sys_mgr_base_addr)) {
+ dev_info(dev, "No sysmgr-syscon node found\n");
+ return PTR_ERR(sys_mgr_base_addr);
+ }
+
+ ret = of_property_read_u32_index(np, "altr,sysmgr-syscon", 1, &reg_offset);
+ if (ret) {
+ dev_info(dev, "Could not read reg_offset from sysmgr-syscon!\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(np, "altr,sysmgr-syscon", 2, &reg_shift);
+ if (ret) {
+ dev_info(dev, "Could not read reg_shift from sysmgr-syscon!\n");
+ return -EINVAL;
+ }
+
+ dwmac->f2h_ptp_ref_clk = of_property_read_bool(np, "altr,f2h_ptp_ref_clk");
+
+ np_splitter = of_parse_phandle(np, "altr,emac-splitter", 0);
+ if (np_splitter) {
+ ret = of_address_to_resource(np_splitter, 0, &res_splitter);
+ of_node_put(np_splitter);
+ if (ret) {
+ dev_info(dev, "Missing emac splitter address\n");
+ return -EINVAL;
+ }
+
+ dwmac->splitter_base = devm_ioremap_resource(dev, &res_splitter);
+ if (IS_ERR(dwmac->splitter_base)) {
+ dev_info(dev, "Failed to mapping emac splitter\n");
+ return PTR_ERR(dwmac->splitter_base);
+ }
+ }
+
+ np_sgmii_adapter = of_parse_phandle(np,
+ "altr,gmii-to-sgmii-converter", 0);
+ if (np_sgmii_adapter) {
+ index = of_property_match_string(np_sgmii_adapter, "reg-names",
+ "hps_emac_interface_splitter_avalon_slave");
+
+ if (index >= 0) {
+ if (of_address_to_resource(np_sgmii_adapter, index,
+ &res_splitter)) {
+ dev_err(dev,
+ "%s: ERROR: missing emac splitter address\n",
+ __func__);
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+
+ dwmac->splitter_base =
+ devm_ioremap_resource(dev, &res_splitter);
+
+ if (IS_ERR(dwmac->splitter_base)) {
+ ret = PTR_ERR(dwmac->splitter_base);
+ goto err_node_put;
+ }
+ }
+
+ index = of_property_match_string(np_sgmii_adapter, "reg-names",
+ "gmii_to_sgmii_adapter_avalon_slave");
+
+ if (index >= 0) {
+ if (of_address_to_resource(np_sgmii_adapter, index,
+ &res_sgmii_adapter)) {
+ dev_err(dev,
+ "%s: ERROR: failed mapping adapter\n",
+ __func__);
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+
+ dwmac->sgmii_adapter_base =
+ devm_ioremap_resource(dev, &res_sgmii_adapter);
+
+ if (IS_ERR(dwmac->sgmii_adapter_base)) {
+ ret = PTR_ERR(dwmac->sgmii_adapter_base);
+ goto err_node_put;
+ }
+ }
+
+ index = of_property_match_string(np_sgmii_adapter, "reg-names",
+ "eth_tse_control_port");
+
+ if (index >= 0) {
+ if (of_address_to_resource(np_sgmii_adapter, index,
+ &res_tse_pcs)) {
+ dev_err(dev,
+ "%s: ERROR: failed mapping tse control port\n",
+ __func__);
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+
+ dwmac->tse_pcs_base =
+ devm_ioremap_resource(dev, &res_tse_pcs);
+
+ if (IS_ERR(dwmac->tse_pcs_base)) {
+ ret = PTR_ERR(dwmac->tse_pcs_base);
+ goto err_node_put;
+ }
+ }
+ }
+ dwmac->reg_offset = reg_offset;
+ dwmac->reg_shift = reg_shift;
+ dwmac->sys_mgr_base_addr = sys_mgr_base_addr;
+ of_node_put(np_sgmii_adapter);
+
+ return 0;
+
+err_node_put:
+ of_node_put(np_sgmii_adapter);
+ return ret;
+}
+
+static int socfpga_set_phy_mode_common(int phymode, u32 *val)
+{
+ switch (phymode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ *val = SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII;
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ case PHY_INTERFACE_MODE_GMII:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ *val = SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ *val = SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void get_smtgtime(struct mii_bus *mii, int smtg_addr, u64 *smtg_time)
+{
+ u64 ns;
+
+ ns = mdiobus_read(mii, smtg_addr, SMTG_TSC_WORD3);
+ ns <<= SMTG_TSC_SHIFT;
+ ns |= mdiobus_read(mii, smtg_addr, SMTG_TSC_WORD2);
+ ns <<= SMTG_TSC_SHIFT;
+ ns |= mdiobus_read(mii, smtg_addr, SMTG_TSC_WORD1);
+ ns <<= SMTG_TSC_SHIFT;
+ ns |= mdiobus_read(mii, smtg_addr, SMTG_TSC_WORD0);
+
+ *smtg_time = ns;
+}
+
+static int smtg_crosststamp(ktime_t *device, struct system_counterval_t *system,
+ void *ctx)
+{
+ struct stmmac_priv *priv = (struct stmmac_priv *)ctx;
+ u32 num_snapshot, gpio_value, acr_value;
+ void __iomem *ptpaddr = priv->ptpaddr;
+ void __iomem *ioaddr = priv->hw->pcsr;
+ unsigned long flags;
+ u64 smtg_time = 0;
+ u64 ptp_time = 0;
+ int i, ret;
+ u32 v;
+
+ /* Both internal crosstimestamping and external triggered event
+ * timestamping cannot be run concurrently.
+ */
+ if (priv->plat->flags & STMMAC_FLAG_EXT_SNAPSHOT_EN)
+ return -EBUSY;
+
+ mutex_lock(&priv->aux_ts_lock);
+ /* Enable Internal snapshot trigger */
+ acr_value = readl(ptpaddr + PTP_ACR);
+ acr_value &= ~PTP_ACR_MASK;
+ switch (priv->plat->int_snapshot_num) {
+ case AUX_SNAPSHOT0:
+ acr_value |= PTP_ACR_ATSEN0;
+ break;
+ case AUX_SNAPSHOT1:
+ acr_value |= PTP_ACR_ATSEN1;
+ break;
+ case AUX_SNAPSHOT2:
+ acr_value |= PTP_ACR_ATSEN2;
+ break;
+ case AUX_SNAPSHOT3:
+ acr_value |= PTP_ACR_ATSEN3;
+ break;
+ default:
+ mutex_unlock(&priv->aux_ts_lock);
+ return -EINVAL;
+ }
+ writel(acr_value, ptpaddr + PTP_ACR);
+
+ /* Clear FIFO */
+ acr_value = readl(ptpaddr + PTP_ACR);
+ acr_value |= PTP_ACR_ATSFC;
+ writel(acr_value, ptpaddr + PTP_ACR);
+ /* Release the mutex */
+ mutex_unlock(&priv->aux_ts_lock);
+
+ /* Trigger Internal snapshot signal. Create a rising edge by just toggle
+ * the GPO0 to low and back to high.
+ */
+ gpio_value = readl(ioaddr + XGMAC_GPIO_STATUS);
+ gpio_value &= ~XGMAC_GPIO_GPO0;
+ writel(gpio_value, ioaddr + XGMAC_GPIO_STATUS);
+ gpio_value |= XGMAC_GPIO_GPO0;
+ writel(gpio_value, ioaddr + XGMAC_GPIO_STATUS);
+
+ /* Poll for time sync operation done */
+ ret = readl_poll_timeout(priv->ioaddr + XGMAC_INT_STATUS, v,
+ (v & XGMAC_INT_TSIS), 100, 10000);
+ if (ret) {
+ netdev_err(priv->dev, "%s: Wait for time sync operation timeout\n",
+ __func__);
+ return ret;
+ }
+
+ *system = (struct system_counterval_t) {
+ .cycles = 0,
+ .cs_id = CSID_ARM_ARCH_COUNTER,
+ .use_nsecs = false,
+ };
+
+ num_snapshot = FIELD_GET(XGMAC_TIMESTAMP_ATSNS_MASK,
+ readl(ioaddr + XGMAC_TIMESTAMP_STATUS));
+
+ /* Repeat until the timestamps are from the FIFO last segment */
+ for (i = 0; i < num_snapshot; i++) {
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_get_ptptime(priv, ptpaddr, &ptp_time);
+ *device = ns_to_ktime(ptp_time);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+ }
+
+ get_smtgtime(priv->mii, SMTG_MDIO_ADDR, &smtg_time);
+ system->cycles = smtg_time;
+
+ return 0;
+}
+
+static int socfpga_gen5_set_phy_mode(struct socfpga_dwmac *dwmac,
+ struct device *dev)
+{
+ struct regmap *sys_mgr_base_addr = dwmac->sys_mgr_base_addr;
+ phy_interface_t phymode = socfpga_get_plat_phymode(dwmac);
+ u32 reg_offset = dwmac->reg_offset;
+ u32 reg_shift = dwmac->reg_shift;
+ u32 ctrl, val, module;
+
+ if (socfpga_set_phy_mode_common(phymode, &val)) {
+ dev_err(dev, "bad phy mode %d\n", phymode);
+ return -EINVAL;
+ }
+
+ /* Overwrite val to GMII if splitter core is enabled. The phymode here
+ * is the actual phy mode on phy hardware, but phy interface from
+ * EMAC core is GMII.
+ */
+ if (dwmac->splitter_base)
+ val = SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII;
+
+ /* Assert reset to the enet controller before changing the phy mode */
+ reset_control_assert(dwmac->stmmac_ocp_rst);
+ reset_control_assert(dwmac->stmmac_rst);
+
+ regmap_read(sys_mgr_base_addr, reg_offset, &ctrl);
+ ctrl &= ~(SYSMGR_EMACGRP_CTRL_PHYSEL_MASK << reg_shift);
+ ctrl |= val << reg_shift;
+
+ if (dwmac->f2h_ptp_ref_clk ||
+ phymode == PHY_INTERFACE_MODE_MII ||
+ phymode == PHY_INTERFACE_MODE_GMII ||
+ phymode == PHY_INTERFACE_MODE_SGMII) {
+ regmap_read(sys_mgr_base_addr, SYSMGR_FPGAGRP_MODULE_REG,
+ &module);
+ module |= (SYSMGR_FPGAGRP_MODULE_EMAC << (reg_shift / 2));
+ regmap_write(sys_mgr_base_addr, SYSMGR_FPGAGRP_MODULE_REG,
+ module);
+ }
+
+ if (dwmac->f2h_ptp_ref_clk)
+ ctrl |= SYSMGR_EMACGRP_CTRL_PTP_REF_CLK_MASK << (reg_shift / 2);
+ else
+ ctrl &= ~(SYSMGR_EMACGRP_CTRL_PTP_REF_CLK_MASK <<
+ (reg_shift / 2));
+
+ regmap_write(sys_mgr_base_addr, reg_offset, ctrl);
+
+ /* Deassert reset for the phy configuration to be sampled by
+ * the enet controller, and operation to start in requested mode
+ */
+ reset_control_deassert(dwmac->stmmac_ocp_rst);
+ reset_control_deassert(dwmac->stmmac_rst);
+ if (phymode == PHY_INTERFACE_MODE_SGMII)
+ socfpga_sgmii_config(dwmac, true);
+
+ return 0;
+}
+
+static int socfpga_gen10_set_phy_mode(struct socfpga_dwmac *dwmac,
+ struct device *dev)
+{
+ struct regmap *sys_mgr_base_addr = dwmac->sys_mgr_base_addr;
+ phy_interface_t phymode = socfpga_get_plat_phymode(dwmac);
+ u32 reg_offset = dwmac->reg_offset;
+ u32 reg_shift = dwmac->reg_shift;
+ u32 ctrl, val, module;
+
+ if (socfpga_set_phy_mode_common(phymode, &val))
+ return -EINVAL;
+
+ /* Overwrite val to GMII if splitter core is enabled. The phymode here
+ * is the actual phy mode on phy hardware, but phy interface from
+ * EMAC core is GMII.
+ */
+ if (dwmac->splitter_base)
+ val = SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII;
+
+ /* Assert reset to the enet controller before changing the phy mode */
+ reset_control_assert(dwmac->stmmac_ocp_rst);
+ reset_control_assert(dwmac->stmmac_rst);
+
+ regmap_read(sys_mgr_base_addr, reg_offset, &ctrl);
+ ctrl &= ~(SYSMGR_EMACGRP_CTRL_PHYSEL_MASK);
+ ctrl |= val;
+
+ if (dwmac->f2h_ptp_ref_clk ||
+ phymode == PHY_INTERFACE_MODE_MII ||
+ phymode == PHY_INTERFACE_MODE_GMII ||
+ phymode == PHY_INTERFACE_MODE_SGMII) {
+ ctrl |= SYSMGR_GEN10_EMACGRP_CTRL_PTP_REF_CLK_MASK;
+ regmap_read(sys_mgr_base_addr, SYSMGR_FPGAINTF_EMAC_REG,
+ &module);
+ module |= (SYSMGR_FPGAINTF_EMAC_BIT << reg_shift);
+ regmap_write(sys_mgr_base_addr, SYSMGR_FPGAINTF_EMAC_REG,
+ module);
+ } else {
+ ctrl &= ~SYSMGR_GEN10_EMACGRP_CTRL_PTP_REF_CLK_MASK;
+ }
+
+ regmap_write(sys_mgr_base_addr, reg_offset, ctrl);
+
+ /* Deassert reset for the phy configuration to be sampled by
+ * the enet controller, and operation to start in requested mode
+ */
+ reset_control_deassert(dwmac->stmmac_ocp_rst);
+ reset_control_deassert(dwmac->stmmac_rst);
+ if (phymode == PHY_INTERFACE_MODE_SGMII)
+ socfpga_sgmii_config(dwmac, true);
+ return 0;
+}
+
+static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)
+{
+ struct socfpga_dwmac *dwmac = priv->plat->bsp_priv;
+ struct regmap_config pcs_regmap_cfg = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .reg_shift = REGMAP_UPSHIFT(1),
+ };
+ struct mdio_regmap_config mrc;
+ struct regmap *pcs_regmap;
+ struct phylink_pcs *pcs;
+ struct mii_bus *pcs_bus;
+
+ if (!dwmac->tse_pcs_base)
+ return 0;
+
+ pcs_regmap = devm_regmap_init_mmio(priv->device, dwmac->tse_pcs_base,
+ &pcs_regmap_cfg);
+ if (IS_ERR(pcs_regmap))
+ return PTR_ERR(pcs_regmap);
+
+ memset(&mrc, 0, sizeof(mrc));
+ mrc.regmap = pcs_regmap;
+ mrc.parent = priv->device;
+ mrc.valid_addr = 0x0;
+ mrc.autoscan = false;
+
+ /* Can't use ndev->name here because it will not have been initialised,
+ * and in any case, the user can rename network interfaces at runtime.
+ */
+ snprintf(mrc.name, MII_BUS_ID_SIZE, "%s-pcs-mii",
+ dev_name(priv->device));
+ pcs_bus = devm_mdio_regmap_register(priv->device, &mrc);
+ if (IS_ERR(pcs_bus))
+ return PTR_ERR(pcs_bus);
+
+ pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+
+ priv->hw->phylink_pcs = pcs;
+ return 0;
+}
+
+static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv)
+{
+ if (priv->hw->phylink_pcs)
+ lynx_pcs_destroy(priv->hw->phylink_pcs);
+}
+
+static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ return priv->hw->phylink_pcs;
+}
+
+static int socfpga_dwmac_init(struct device *dev, void *bsp_priv)
+{
+ struct socfpga_dwmac *dwmac = bsp_priv;
+
+ return dwmac->ops->set_phy_mode(dwmac, dev);
+}
+
+static void socfpga_gen5_setup_plat_dat(struct socfpga_dwmac *dwmac)
+{
+ struct plat_stmmacenet_data *plat_dat = dwmac->plat_dat;
+
+ plat_dat->core_type = DWMAC_CORE_GMAC;
+
+ /* Rx watchdog timer in dwmac is buggy in this hw */
+ plat_dat->riwt_off = true;
+}
+
+static void socfpga_agilex5_setup_plat_dat(struct socfpga_dwmac *dwmac)
+{
+ struct plat_stmmacenet_data *plat_dat = dwmac->plat_dat;
+
+ plat_dat->core_type = DWMAC_CORE_XGMAC;
+
+ /* Enable TSO */
+ plat_dat->flags |= STMMAC_FLAG_TSO_EN;
+
+ /* Enable TBS */
+ switch (plat_dat->tx_queues_to_use) {
+ case 8:
+ plat_dat->tx_queues_cfg[7].tbs_en = true;
+ fallthrough;
+ case 7:
+ plat_dat->tx_queues_cfg[6].tbs_en = true;
+ break;
+ default:
+ /* Tx Queues 0 - 5 doesn't support TBS on Agilex5 */
+ break;
+ }
+
+ /* Hw supported cross-timestamp */
+ plat_dat->int_snapshot_num = AUX_SNAPSHOT0;
+ plat_dat->crosststamp = smtg_crosststamp;
+}
+
+static int socfpga_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct socfpga_dwmac *dwmac;
+ const struct socfpga_dwmac_ops *ops;
+
+ ops = device_get_match_data(&pdev->dev);
+ if (!ops) {
+ dev_err(&pdev->dev, "no of match data provided\n");
+ return -EINVAL;
+ }
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ dwmac = devm_kzalloc(dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ dwmac->stmmac_ocp_rst = devm_reset_control_get_optional(dev, "stmmaceth-ocp");
+ if (IS_ERR(dwmac->stmmac_ocp_rst)) {
+ ret = PTR_ERR(dwmac->stmmac_ocp_rst);
+ dev_err(dev, "error getting reset control of ocp %d\n", ret);
+ return ret;
+ }
+
+ reset_control_deassert(dwmac->stmmac_ocp_rst);
+
+ ret = socfpga_dwmac_parse_data(dwmac, dev);
+ if (ret) {
+ dev_err(dev, "Unable to parse OF data\n");
+ return ret;
+ }
+
+ /* The socfpga driver needs to control the stmmac reset to set the phy
+ * mode. Create a copy of the core reset handle so it can be used by
+ * the driver later.
+ */
+ dwmac->stmmac_rst = plat_dat->stmmac_rst;
+ dwmac->ops = ops;
+ dwmac->plat_dat = plat_dat;
+
+ plat_dat->bsp_priv = dwmac;
+ plat_dat->fix_mac_speed = socfpga_dwmac_fix_mac_speed;
+ plat_dat->init = socfpga_dwmac_init;
+ plat_dat->pcs_init = socfpga_dwmac_pcs_init;
+ plat_dat->pcs_exit = socfpga_dwmac_pcs_exit;
+ plat_dat->select_pcs = socfpga_dwmac_select_pcs;
+
+ ops->setup_plat_dat(dwmac);
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct socfpga_dwmac_ops socfpga_gen5_ops = {
+ .set_phy_mode = socfpga_gen5_set_phy_mode,
+ .setup_plat_dat = socfpga_gen5_setup_plat_dat,
+};
+
+static const struct socfpga_dwmac_ops socfpga_gen10_ops = {
+ .set_phy_mode = socfpga_gen10_set_phy_mode,
+ .setup_plat_dat = socfpga_gen5_setup_plat_dat,
+};
+
+static const struct socfpga_dwmac_ops socfpga_agilex5_ops = {
+ .set_phy_mode = socfpga_gen10_set_phy_mode,
+ .setup_plat_dat = socfpga_agilex5_setup_plat_dat,
+};
+
+static const struct of_device_id socfpga_dwmac_match[] = {
+ { .compatible = "altr,socfpga-stmmac", .data = &socfpga_gen5_ops },
+ { .compatible = "altr,socfpga-stmmac-a10-s10", .data = &socfpga_gen10_ops },
+ { .compatible = "altr,socfpga-stmmac-agilex5", .data = &socfpga_agilex5_ops },
+ { }
+};
+MODULE_DEVICE_TABLE(of, socfpga_dwmac_match);
+
+static struct platform_driver socfpga_dwmac_driver = {
+ .probe = socfpga_dwmac_probe,
+ .driver = {
+ .name = "socfpga-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = socfpga_dwmac_match,
+ },
+};
+module_platform_driver(socfpga_dwmac_driver);
+
+MODULE_DESCRIPTION("Altera SOC DWMAC Specific Glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c
new file mode 100644
index 000000000..e02ad3762
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sophgo DWMAC platform driver
+ *
+ * Copyright (C) 2024 Inochi Amaoto <inochiama@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+
+#include "stmmac_platform.h"
+
+struct sophgo_dwmac_data {
+ bool has_internal_rx_delay;
+};
+
+static int sophgo_sg2044_dwmac_init(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *stmmac_res)
+{
+ plat_dat->clk_tx_i = devm_clk_get_enabled(&pdev->dev, "tx");
+ if (IS_ERR(plat_dat->clk_tx_i))
+ return dev_err_probe(&pdev->dev, PTR_ERR(plat_dat->clk_tx_i),
+ "failed to get tx clock\n");
+
+ plat_dat->flags |= STMMAC_FLAG_SPH_DISABLE;
+ plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+ plat_dat->multicast_filter_bins = 0;
+
+ return 0;
+}
+
+static int sophgo_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ const struct sophgo_dwmac_data *data;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get platform resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(dev, PTR_ERR(plat_dat),
+ "failed to parse DT parameters\n");
+
+ ret = sophgo_sg2044_dwmac_init(pdev, plat_dat, &stmmac_res);
+ if (ret)
+ return ret;
+
+ data = device_get_match_data(&pdev->dev);
+ if (data && data->has_internal_rx_delay) {
+ plat_dat->phy_interface = phy_fix_phy_mode_for_mac_delays(plat_dat->phy_interface,
+ false, true);
+ if (plat_dat->phy_interface == PHY_INTERFACE_MODE_NA)
+ return -EINVAL;
+ }
+
+ return stmmac_dvr_probe(dev, plat_dat, &stmmac_res);
+}
+
+static const struct sophgo_dwmac_data sg2042_dwmac_data = {
+ .has_internal_rx_delay = true,
+};
+
+static const struct of_device_id sophgo_dwmac_match[] = {
+ { .compatible = "sophgo,sg2042-dwmac", .data = &sg2042_dwmac_data },
+ { .compatible = "sophgo,sg2044-dwmac" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sophgo_dwmac_match);
+
+static struct platform_driver sophgo_dwmac_driver = {
+ .probe = sophgo_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "sophgo-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = sophgo_dwmac_match,
+ },
+};
+module_platform_driver(sophgo_dwmac_driver);
+
+MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>");
+MODULE_DESCRIPTION("Sophgo DWMAC platform driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c
new file mode 100644
index 000000000..62d8ac538
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Spacemit DWMAC platform driver
+ *
+ * Copyright (C) 2026 Inochi Amaoto <inochiama@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "stmmac_platform.h"
+
+/* ctrl register bits */
+#define CTRL_PHY_INTF_MODE GENMASK(4, 3)
+#define CTRL_PHY_INTF_RMII FIELD_PREP(CTRL_PHY_INTF_MODE, 0)
+#define CTRL_PHY_INTF_RGMII FIELD_PREP(CTRL_PHY_INTF_MODE, 1)
+#define CTRL_PHY_INTF_MII FIELD_PREP(CTRL_PHY_INTF_MODE, 3)
+#define CTRL_LPI_IRQ_EN BIT(9)
+#define CTRL_WAKE_IRQ_EN BIT(12)
+
+/* dline register bits */
+#define RGMII_RX_DLINE_EN BIT(0)
+#define RGMII_RX_DLINE_STEP GENMASK(5, 4)
+#define RGMII_RX_DLINE_CODE GENMASK(15, 8)
+#define RGMII_TX_DLINE_EN BIT(16)
+#define RGMII_TX_DLINE_STEP GENMASK(21, 20)
+#define RGMII_TX_DLINE_CODE GENMASK(31, 24)
+
+#define MAX_DLINE_DELAY_CODE 0xff
+#define MAX_WORKED_DELAY 2800
+/* Note: the delay step value is at 0.1ps */
+#define K3_DELAY_STEP 367
+
+struct spacmit_dwmac {
+ struct regmap *apmu;
+ unsigned int ctrl_offset;
+ unsigned int dline_offset;
+};
+
+static int spacemit_dwmac_set_delay(struct spacmit_dwmac *dwmac,
+ unsigned int tx_code, unsigned int rx_code)
+{
+ unsigned int mask, val;
+
+ mask = RGMII_TX_DLINE_STEP | RGMII_TX_DLINE_CODE | RGMII_TX_DLINE_EN |
+ RGMII_RX_DLINE_STEP | RGMII_RX_DLINE_CODE | RGMII_RX_DLINE_EN;
+
+ /*
+ * Since the delay step provided by config 0 is small enough, and
+ * it can cover the range of the valid delay, so there is no needed
+ * to use other step config.
+ */
+ val = FIELD_PREP(RGMII_TX_DLINE_STEP, 0) |
+ FIELD_PREP(RGMII_TX_DLINE_CODE, tx_code) | RGMII_TX_DLINE_EN |
+ FIELD_PREP(RGMII_RX_DLINE_STEP, 0) |
+ FIELD_PREP(RGMII_RX_DLINE_CODE, rx_code) | RGMII_RX_DLINE_EN;
+
+ return regmap_update_bits(dwmac->apmu, dwmac->dline_offset,
+ mask, val);
+}
+
+static int spacemit_dwmac_detected_delay_value(unsigned int delay)
+{
+ if (delay == 0)
+ return 0;
+
+ if (delay > MAX_WORKED_DELAY)
+ return -EINVAL;
+
+ /*
+ * Note K3 require a specific factor for calculate
+ * the delay, in this scenario it is 0.9. So the
+ * formula is code * step / 10 * 0.9
+ */
+ return DIV_ROUND_CLOSEST(delay * 10 * 10, K3_DELAY_STEP * 9);
+}
+
+static int spacemit_dwmac_fix_delay(struct spacmit_dwmac *dwmac,
+ unsigned int tx_delay,
+ unsigned int rx_delay)
+{
+ int rx_code;
+ int tx_code;
+
+ rx_code = spacemit_dwmac_detected_delay_value(rx_delay);
+ if (rx_code < 0)
+ return rx_code;
+
+ tx_code = spacemit_dwmac_detected_delay_value(tx_delay);
+ if (tx_code < 0)
+ return tx_code;
+
+ return spacemit_dwmac_set_delay(dwmac, tx_code, rx_code);
+}
+
+static int spacemit_dwmac_update_irq_config(struct spacmit_dwmac *dwmac,
+ struct stmmac_resources *stmmac_res)
+{
+ unsigned int val = stmmac_res->wol_irq >= 0 ? CTRL_WAKE_IRQ_EN : 0;
+ unsigned int mask = CTRL_WAKE_IRQ_EN;
+
+ return regmap_update_bits(dwmac->apmu, dwmac->ctrl_offset,
+ mask, val);
+}
+
+static void spacemit_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
+ unsigned long *interfaces)
+{
+ __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
+ __set_bit(PHY_INTERFACE_MODE_RMII, interfaces);
+ phy_interface_set_rgmii(interfaces);
+}
+
+static int spacemit_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel)
+{
+ unsigned int mask = CTRL_PHY_INTF_MODE;
+ struct spacmit_dwmac *dwmac = bsp_priv;
+ unsigned int val = 0;
+
+ switch (phy_intf_sel) {
+ case PHY_INTF_SEL_GMII_MII:
+ val = CTRL_PHY_INTF_MII;
+ break;
+
+ case PHY_INTF_SEL_RMII:
+ val = CTRL_PHY_INTF_RMII;
+ break;
+
+ case PHY_INTF_SEL_RGMII:
+ val = CTRL_PHY_INTF_RGMII;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(dwmac->apmu, dwmac->ctrl_offset,
+ mask, val);
+}
+
+static int spacemit_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct spacmit_dwmac *dwmac;
+ unsigned int offset[2];
+ struct regmap *apmu;
+ struct clk *clk_tx;
+ u32 rx_delay = 0;
+ u32 tx_delay = 0;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get platform resources\n");
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(dev, PTR_ERR(plat_dat),
+ "failed to parse DT parameters\n");
+
+ clk_tx = devm_clk_get_enabled(&pdev->dev, "tx");
+ if (IS_ERR(clk_tx))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk_tx),
+ "failed to get tx clock\n");
+
+ apmu = syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node,
+ "spacemit,apmu", 2,
+ offset);
+ if (IS_ERR(apmu))
+ return dev_err_probe(dev, PTR_ERR(apmu),
+ "Failed to get apmu regmap\n");
+
+ dwmac->apmu = apmu;
+ dwmac->ctrl_offset = offset[0];
+ dwmac->dline_offset = offset[1];
+
+ ret = spacemit_dwmac_update_irq_config(dwmac, &stmmac_res);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to configure irq config\n");
+
+ of_property_read_u32(pdev->dev.of_node, "tx-internal-delay-ps",
+ &tx_delay);
+ of_property_read_u32(pdev->dev.of_node, "rx-internal-delay-ps",
+ &rx_delay);
+
+ plat_dat->get_interfaces = spacemit_get_interfaces;
+ plat_dat->set_phy_intf_sel = spacemit_set_phy_intf_sel;
+ plat_dat->bsp_priv = dwmac;
+
+ ret = spacemit_dwmac_fix_delay(dwmac, tx_delay, rx_delay);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to configure delay\n");
+
+ return stmmac_dvr_probe(dev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id spacemit_dwmac_match[] = {
+ { .compatible = "spacemit,k3-dwmac" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, spacemit_dwmac_match);
+
+static struct platform_driver spacemit_dwmac_driver = {
+ .probe = spacemit_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "spacemit-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = spacemit_dwmac_match,
+ },
+};
+module_platform_driver(spacemit_dwmac_driver);
+
+MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>");
+MODULE_DESCRIPTION("Spacemit DWMAC platform driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
new file mode 100644
index 000000000..4ee5b5fe1
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * StarFive DWMAC platform driver
+ *
+ * Copyright (C) 2021 Emil Renner Berthing <kernel@esmil.dk>
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "stmmac_platform.h"
+
+#define STARFIVE_DWMAC_PHY_INFT_FIELD 0x7U
+
+#define JH7100_SYSMAIN_REGISTER49_DLYCHAIN 0xc8
+
+struct starfive_dwmac_data {
+ unsigned int gtxclk_dlychain;
+};
+
+struct starfive_dwmac {
+ struct device *dev;
+ const struct starfive_dwmac_data *data;
+ struct clk *sgmii_rx;
+};
+
+static int starfive_dwmac_set_mode(struct plat_stmmacenet_data *plat_dat)
+{
+ struct starfive_dwmac *dwmac = plat_dat->bsp_priv;
+ struct regmap *regmap;
+ unsigned int args[2];
+ int phy_intf_sel;
+ int err;
+
+ phy_intf_sel = stmmac_get_phy_intf_sel(plat_dat->phy_interface);
+ if (phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII) {
+ dev_err(dwmac->dev, "unsupported interface %s\n",
+ phy_modes(plat_dat->phy_interface));
+ return phy_intf_sel < 0 ? phy_intf_sel : -EINVAL;
+ }
+
+ regmap = syscon_regmap_lookup_by_phandle_args(dwmac->dev->of_node,
+ "starfive,syscon",
+ 2, args);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dwmac->dev, PTR_ERR(regmap), "getting the regmap failed\n");
+
+ /* args[0]:offset args[1]: shift */
+ err = regmap_update_bits(regmap, args[0],
+ STARFIVE_DWMAC_PHY_INFT_FIELD << args[1],
+ phy_intf_sel << args[1]);
+ if (err)
+ return dev_err_probe(dwmac->dev, err, "error setting phy mode\n");
+
+ if (dwmac->data) {
+ err = regmap_write(regmap, JH7100_SYSMAIN_REGISTER49_DLYCHAIN,
+ dwmac->data->gtxclk_dlychain);
+ if (err)
+ return dev_err_probe(dwmac->dev, err,
+ "error selecting gtxclk delay chain\n");
+ }
+
+ return 0;
+}
+
+static int stmmac_starfive_sgmii_set_clk_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t __maybe_unused interface,
+ int speed)
+{
+ struct starfive_dwmac *dwmac = bsp_priv;
+ long rate = rgmii_clock(speed);
+ int ret;
+
+ /* MAC clock rate the same as RGMII */
+ if (rate < 0)
+ return -EINVAL;
+
+ ret = clk_set_rate(clk_tx_i, rate);
+ if (ret)
+ return ret;
+
+ return clk_set_rate(dwmac->sgmii_rx, rate);
+}
+
+static int starfive_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct starfive_dwmac *dwmac;
+ struct clk *clk_gtx;
+ int err;
+
+ err = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (err)
+ return dev_err_probe(&pdev->dev, err,
+ "failed to get resources\n");
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return dev_err_probe(&pdev->dev, PTR_ERR(plat_dat),
+ "dt configuration failed\n");
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ dwmac->data = device_get_match_data(&pdev->dev);
+
+ plat_dat->clk_tx_i = devm_clk_get_enabled(&pdev->dev, "tx");
+ if (IS_ERR(plat_dat->clk_tx_i))
+ return dev_err_probe(&pdev->dev, PTR_ERR(plat_dat->clk_tx_i),
+ "error getting tx clock\n");
+
+ clk_gtx = devm_clk_get_enabled(&pdev->dev, "gtx");
+ if (IS_ERR(clk_gtx))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk_gtx),
+ "error getting gtx clock\n");
+
+ dwmac->dev = &pdev->dev;
+ plat_dat->bsp_priv = dwmac;
+ /* generic sgmii, 1000_BASEX not support yet */
+ if (plat_dat->phy_interface == PHY_INTERFACE_MODE_SGMII) {
+ dwmac->sgmii_rx = devm_clk_get_enabled(&pdev->dev, "sgmii_rx");
+ if (IS_ERR(dwmac->sgmii_rx))
+ return dev_err_probe(&pdev->dev,
+ PTR_ERR(dwmac->sgmii_rx),
+ "error getting sgmii rx clock\n");
+ plat_dat->set_clk_tx_rate = stmmac_starfive_sgmii_set_clk_rate;
+ } else {
+ /*
+ * Generally, the rgmii_tx clock is provided by the internal clock,
+ * which needs to match the corresponding clock frequency according
+ * to different speeds. If the rgmii_tx clock is provided by the
+ * external rgmii_rxin, there is no need to configure the clock
+ * internally, because rgmii_rxin will be adaptively adjusted.
+ */
+ if (!device_property_read_bool(&pdev->dev, "starfive,tx-use-rgmii-clk"))
+ plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+
+ err = starfive_dwmac_set_mode(plat_dat);
+ if (err)
+ return err;
+ }
+
+ plat_dat->flags |= (STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP | STMMAC_FLAG_SPH_DISABLE);
+ plat_dat->dma_cfg->dche = true;
+
+ return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+}
+
+static const struct starfive_dwmac_data jh7100_data = {
+ .gtxclk_dlychain = 4,
+};
+
+static const struct of_device_id starfive_dwmac_match[] = {
+ { .compatible = "starfive,jh7100-dwmac", .data = &jh7100_data },
+ { .compatible = "starfive,jh7110-dwmac" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, starfive_dwmac_match);
+
+static struct platform_driver starfive_dwmac_driver = {
+ .probe = starfive_dwmac_probe,
+ .remove = stmmac_pltfr_remove,
+ .driver = {
+ .name = "starfive-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = starfive_dwmac_match,
+ },
+};
+module_platform_driver(starfive_dwmac_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("StarFive DWMAC platform driver");
+MODULE_AUTHOR("Emil Renner Berthing <kernel@esmil.dk>");
+MODULE_AUTHOR("Samin Guo <samin.guo@starfivetech.com>");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sti.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sti.c
new file mode 100644
index 000000000..6ebbf95d1
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sti.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * dwmac-sti.c - STMicroelectronics DWMAC Specific Glue layer
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ * Author: Srinivas Kandagatla <srinivas.kandagatla@st.com>
+ * Contributors: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/stmmac.h>
+#include <linux/phy.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+
+#include "stmmac_platform.h"
+
+#define DWMAC_50MHZ 50000000
+
+#define IS_PHY_IF_MODE_GBIT(iface) (phy_interface_mode_is_rgmii(iface) || \
+ iface == PHY_INTERFACE_MODE_GMII)
+
+/* STiH4xx register definitions (STiH407/STiH410 families)
+ *
+ * Below table summarizes the clock requirement and clock sources for
+ * supported phy interface modes with link speeds.
+ * ________________________________________________
+ *| PHY_MODE | 1000 Mbit Link | 100 Mbit Link |
+ * ------------------------------------------------
+ *| MII | n/a | 25Mhz |
+ *| | | txclk |
+ * ------------------------------------------------
+ *| GMII | 125Mhz | 25Mhz |
+ *| | clk-125/txclk | txclk |
+ * ------------------------------------------------
+ *| RGMII | 125Mhz | 25Mhz |
+ *| | clk-125/txclk | clkgen |
+ *| | clkgen | |
+ * ------------------------------------------------
+ *| RMII | n/a | 25Mhz |
+ *| | |clkgen/phyclk-in |
+ * ------------------------------------------------
+ *
+ * Register Configuration
+ *-------------------------------
+ * src |BIT(8)| BIT(7)| BIT(6)|
+ *-------------------------------
+ * txclk | 0 | n/a | 1 |
+ *-------------------------------
+ * ck_125| 0 | n/a | 0 |
+ *-------------------------------
+ * phyclk| 1 | 0 | n/a |
+ *-------------------------------
+ * clkgen| 1 | 1 | n/a |
+ *-------------------------------
+ */
+
+#define STIH4XX_RETIME_SRC_MASK GENMASK(8, 6)
+#define STIH4XX_ETH_SEL_TX_RETIME_CLK BIT(8)
+#define STIH4XX_ETH_SEL_INTERNAL_NOTEXT_PHYCLK BIT(7)
+#define STIH4XX_ETH_SEL_TXCLK_NOT_CLK125 BIT(6)
+
+#define ENMII_MASK GENMASK(5, 5)
+#define ENMII BIT(5)
+#define EN_MASK GENMASK(1, 1)
+#define EN BIT(1)
+
+/*
+ * 3 bits [4:2]
+ * 000-GMII/MII
+ * 001-RGMII
+ * 010-SGMII
+ * 100-RMII
+ * These are the DW MAC phy_intf_sel values.
+ */
+#define MII_PHY_SEL_MASK GENMASK(4, 2)
+
+struct sti_dwmac {
+ phy_interface_t interface; /* MII interface */
+ bool ext_phyclk; /* Clock from external PHY */
+ u32 tx_retime_src; /* TXCLK Retiming*/
+ struct clk *clk; /* PHY clock */
+ u32 ctrl_reg; /* GMAC glue-logic control register */
+ int clk_sel_reg; /* GMAC ext clk selection register */
+ struct regmap *regmap;
+ bool gmac_en;
+ int speed;
+ void (*fix_retime_src)(void *priv, phy_interface_t interface,
+ int speed, unsigned int mode);
+};
+
+struct sti_dwmac_of_data {
+ void (*fix_retime_src)(void *priv, phy_interface_t interface,
+ int speed, unsigned int mode);
+};
+
+enum {
+ TX_RETIME_SRC_NA = 0,
+ TX_RETIME_SRC_TXCLK = 1,
+ TX_RETIME_SRC_CLK_125,
+ TX_RETIME_SRC_PHYCLK,
+ TX_RETIME_SRC_CLKGEN,
+};
+
+static u32 stih4xx_tx_retime_val[] = {
+ [TX_RETIME_SRC_TXCLK] = STIH4XX_ETH_SEL_TXCLK_NOT_CLK125,
+ [TX_RETIME_SRC_CLK_125] = 0x0,
+ [TX_RETIME_SRC_PHYCLK] = STIH4XX_ETH_SEL_TX_RETIME_CLK,
+ [TX_RETIME_SRC_CLKGEN] = STIH4XX_ETH_SEL_TX_RETIME_CLK
+ | STIH4XX_ETH_SEL_INTERNAL_NOTEXT_PHYCLK,
+};
+
+static void stih4xx_fix_retime_src(void *priv, phy_interface_t interface,
+ int spd, unsigned int mode)
+{
+ struct sti_dwmac *dwmac = priv;
+ u32 src = dwmac->tx_retime_src;
+ u32 reg = dwmac->ctrl_reg;
+ long freq = 0;
+
+ if (dwmac->interface == PHY_INTERFACE_MODE_MII) {
+ src = TX_RETIME_SRC_TXCLK;
+ } else if (dwmac->interface == PHY_INTERFACE_MODE_RMII) {
+ if (dwmac->ext_phyclk) {
+ src = TX_RETIME_SRC_PHYCLK;
+ } else {
+ src = TX_RETIME_SRC_CLKGEN;
+ freq = DWMAC_50MHZ;
+ }
+ } else if (phy_interface_mode_is_rgmii(dwmac->interface)) {
+ /* On GiGa clk source can be either ext or from clkgen */
+ freq = rgmii_clock(spd);
+
+ if (spd != SPEED_1000 && freq > 0)
+ /* Switch to clkgen for these speeds */
+ src = TX_RETIME_SRC_CLKGEN;
+ }
+
+ if (src == TX_RETIME_SRC_CLKGEN && freq > 0)
+ clk_set_rate(dwmac->clk, freq);
+
+ regmap_update_bits(dwmac->regmap, reg, STIH4XX_RETIME_SRC_MASK,
+ stih4xx_tx_retime_val[src]);
+}
+
+static int sti_set_phy_intf_sel(void *bsp_priv, u8 phy_intf_sel)
+{
+ struct sti_dwmac *dwmac = bsp_priv;
+ struct regmap *regmap;
+ u32 reg, val;
+
+ regmap = dwmac->regmap;
+ reg = dwmac->ctrl_reg;
+
+ if (dwmac->gmac_en)
+ regmap_update_bits(regmap, reg, EN_MASK, EN);
+
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_SGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII)
+ phy_intf_sel = PHY_INTF_SEL_GMII_MII;
+
+ regmap_update_bits(regmap, reg, MII_PHY_SEL_MASK,
+ FIELD_PREP(MII_PHY_SEL_MASK, phy_intf_sel));
+
+ val = (dwmac->interface == PHY_INTERFACE_MODE_REVMII) ? 0 : ENMII;
+ regmap_update_bits(regmap, reg, ENMII_MASK, val);
+
+ dwmac->fix_retime_src(dwmac, dwmac->interface, dwmac->speed, 0);
+
+ return 0;
+}
+
+static int sti_dwmac_parse_data(struct sti_dwmac *dwmac,
+ struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat)
+{
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct regmap *regmap;
+ int err;
+
+ /* clk selection from extra syscfg register */
+ dwmac->clk_sel_reg = -ENXIO;
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sti-clkconf");
+ if (res)
+ dwmac->clk_sel_reg = res->start;
+
+ regmap = syscon_regmap_lookup_by_phandle_args(np, "st,syscon",
+ 1, &dwmac->ctrl_reg);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ dwmac->interface = plat_dat->phy_interface;
+ dwmac->regmap = regmap;
+ dwmac->gmac_en = of_property_read_bool(np, "st,gmac_en");
+ dwmac->ext_phyclk = of_property_read_bool(np, "st,ext-phyclk");
+ dwmac->tx_retime_src = TX_RETIME_SRC_NA;
+ dwmac->speed = SPEED_100;
+
+ if (IS_PHY_IF_MODE_GBIT(dwmac->interface)) {
+ const char *rs;
+
+ dwmac->tx_retime_src = TX_RETIME_SRC_CLKGEN;
+
+ err = of_property_read_string(np, "st,tx-retime-src", &rs);
+ if (err < 0) {
+ dev_warn(dev, "Use internal clock source\n");
+ } else {
+ if (!strcasecmp(rs, "clk_125"))
+ dwmac->tx_retime_src = TX_RETIME_SRC_CLK_125;
+ else if (!strcasecmp(rs, "txclk"))
+ dwmac->tx_retime_src = TX_RETIME_SRC_TXCLK;
+ }
+ dwmac->speed = SPEED_1000;
+ }
+
+ dwmac->clk = devm_clk_get(dev, "sti-ethclk");
+ if (IS_ERR(dwmac->clk)) {
+ dev_warn(dev, "No phy clock provided...\n");
+ dwmac->clk = NULL;
+ }
+
+ return 0;
+}
+
+static int sti_dwmac_init(struct device *dev, void *bsp_priv)
+{
+ struct sti_dwmac *dwmac = bsp_priv;
+
+ return clk_prepare_enable(dwmac->clk);
+}
+
+static void sti_dwmac_exit(struct device *dev, void *bsp_priv)
+{
+ struct sti_dwmac *dwmac = bsp_priv;
+
+ clk_disable_unprepare(dwmac->clk);
+}
+
+static int sti_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ const struct sti_dwmac_of_data *data;
+ struct stmmac_resources stmmac_res;
+ struct sti_dwmac *dwmac;
+ int ret;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "No OF match data provided\n");
+ return -EINVAL;
+ }
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ ret = sti_dwmac_parse_data(dwmac, pdev, plat_dat);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to parse OF data\n");
+ return ret;
+ }
+
+ dwmac->fix_retime_src = data->fix_retime_src;
+
+ plat_dat->bsp_priv = dwmac;
+ plat_dat->set_phy_intf_sel = sti_set_phy_intf_sel;
+ plat_dat->fix_mac_speed = data->fix_retime_src;
+ plat_dat->init = sti_dwmac_init;
+ plat_dat->exit = sti_dwmac_exit;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct sti_dwmac_of_data stih4xx_dwmac_data = {
+ .fix_retime_src = stih4xx_fix_retime_src,
+};
+
+static const struct of_device_id sti_dwmac_match[] = {
+ { .compatible = "st,stih407-dwmac", .data = &stih4xx_dwmac_data},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sti_dwmac_match);
+
+static struct platform_driver sti_dwmac_driver = {
+ .probe = sti_dwmac_probe,
+ .driver = {
+ .name = "sti-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = sti_dwmac_match,
+ },
+};
+module_platform_driver(sti_dwmac_driver);
+
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics DWMAC Specific Glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c
new file mode 100644
index 000000000..e1b260ed4
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c
@@ -0,0 +1,685 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * dwmac-stm32.c - DWMAC Specific Glue layer for STM32 MCU
+ *
+ * Copyright (C) STMicroelectronics SA 2017
+ * Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+
+#define SYSCFG_MCU_ETH_MASK BIT(23)
+#define SYSCFG_MP1_ETH_MASK GENMASK(23, 16)
+#define SYSCFG_PMCCLRR_OFFSET 0x40
+
+#define SYSCFG_PMCR_ETH_CLK_SEL BIT(16)
+#define SYSCFG_PMCR_ETH_REF_CLK_SEL BIT(17)
+
+/* CLOCK feed to PHY*/
+#define ETH_CK_F_25M 25000000
+#define ETH_CK_F_50M 50000000
+#define ETH_CK_F_125M 125000000
+
+/* Ethernet PHY interface selection in register SYSCFG Configuration
+ *------------------------------------------
+ * src |BIT(23)| BIT(22)| BIT(21)|BIT(20)|
+ *------------------------------------------
+ * MII | 0 | 0 | 0 | 1 |
+ *------------------------------------------
+ * GMII | 0 | 0 | 0 | 0 |
+ *------------------------------------------
+ * RGMII | 0 | 0 | 1 | n/a |
+ *------------------------------------------
+ * RMII | 1 | 0 | 0 | n/a |
+ *------------------------------------------
+ */
+#define SYSCFG_PMCR_ETH_SEL_MII BIT(20)
+#define SYSCFG_PMCR_PHY_INTF_SEL_MASK GENMASK(23, 21)
+#define SYSCFG_MCU_ETH_SEL_MII 0
+#define SYSCFG_MCU_ETH_SEL_RMII 1
+
+/* STM32MP2 register definitions */
+#define SYSCFG_MP2_ETH_MASK GENMASK(31, 0)
+
+#define SYSCFG_ETHCR_ETH_SEL_MASK GENMASK(6, 4)
+#define SYSCFG_ETHCR_ETH_PTP_CLK_SEL BIT(2)
+#define SYSCFG_ETHCR_ETH_CLK_SEL BIT(1)
+#define SYSCFG_ETHCR_ETH_REF_CLK_SEL BIT(0)
+
+/* STM32MPx register definitions
+ *
+ * Below table summarizes the clock requirement and clock sources for
+ * supported phy interface modes.
+ * __________________________________________________________________________
+ *|PHY_MODE | Normal | PHY wo crystal| PHY wo crystal |No 125MHz from PHY|
+ *| | | 25MHz | 50MHz | |
+ * ---------------------------------------------------------------------------
+ *| MII | - | eth-ck | n/a | n/a |
+ *| | | st,ext-phyclk | | |
+ * ---------------------------------------------------------------------------
+ *| GMII | - | eth-ck | n/a | n/a |
+ *| | | st,ext-phyclk | | |
+ * ---------------------------------------------------------------------------
+ *| RGMII | - | eth-ck | n/a | eth-ck |
+ *| | | st,ext-phyclk | | st,eth-clk-sel or|
+ *| | | | | st,ext-phyclk |
+ * ---------------------------------------------------------------------------
+ *| RMII | - | eth-ck | eth-ck | n/a |
+ *| | | st,ext-phyclk | st,eth-ref-clk-sel | |
+ *| | | | or st,ext-phyclk | |
+ * ---------------------------------------------------------------------------
+ *
+ */
+
+struct stm32_dwmac {
+ struct clk *clk_tx;
+ struct clk *clk_rx;
+ struct clk *clk_eth_ck;
+ struct clk *clk_ethstp;
+ struct clk *syscfg_clk;
+ int ext_phyclk;
+ int enable_eth_ck;
+ int eth_clk_sel_reg;
+ int eth_ref_clk_sel_reg;
+ int irq_pwr_wakeup;
+ u32 mode_reg; /* MAC glue-logic mode register */
+ u32 mode_mask;
+ struct regmap *regmap;
+ u32 speed;
+ const struct stm32_ops *ops;
+ struct device *dev;
+};
+
+struct stm32_ops {
+ int (*set_mode)(struct plat_stmmacenet_data *plat_dat);
+ int (*suspend)(struct stm32_dwmac *dwmac);
+ void (*resume)(struct stm32_dwmac *dwmac);
+ int (*parse_data)(struct stm32_dwmac *dwmac,
+ struct device *dev);
+ bool clk_rx_enable_in_suspend;
+ bool is_mp13, is_mp2;
+ u32 syscfg_clr_off;
+};
+
+static int stm32_dwmac_clk_enable(struct stm32_dwmac *dwmac)
+{
+ int ret;
+
+ ret = clk_prepare_enable(dwmac->clk_tx);
+ if (ret)
+ goto err_clk_tx;
+
+ ret = clk_prepare_enable(dwmac->clk_rx);
+ if (ret)
+ goto err_clk_rx;
+
+ ret = clk_prepare_enable(dwmac->syscfg_clk);
+ if (ret)
+ goto err_syscfg_clk;
+
+ if (dwmac->enable_eth_ck) {
+ ret = clk_prepare_enable(dwmac->clk_eth_ck);
+ if (ret)
+ goto err_clk_eth_ck;
+ }
+
+ return ret;
+
+err_clk_eth_ck:
+ clk_disable_unprepare(dwmac->syscfg_clk);
+err_syscfg_clk:
+ clk_disable_unprepare(dwmac->clk_rx);
+err_clk_rx:
+ clk_disable_unprepare(dwmac->clk_tx);
+err_clk_tx:
+ return ret;
+}
+
+static int stm32_dwmac_init(struct plat_stmmacenet_data *plat_dat)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+ int ret;
+
+ if (dwmac->ops->set_mode) {
+ ret = dwmac->ops->set_mode(plat_dat);
+ if (ret)
+ return ret;
+ }
+
+ return stm32_dwmac_clk_enable(dwmac);
+}
+
+static int stm32mp1_select_ethck_external(struct plat_stmmacenet_data *plat_dat)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+
+ switch (plat_dat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ dwmac->enable_eth_ck = dwmac->ext_phyclk;
+ return 0;
+ case PHY_INTERFACE_MODE_GMII:
+ dwmac->enable_eth_ck = dwmac->eth_clk_sel_reg ||
+ dwmac->ext_phyclk;
+ return 0;
+ case PHY_INTERFACE_MODE_RMII:
+ dwmac->enable_eth_ck = dwmac->eth_ref_clk_sel_reg ||
+ dwmac->ext_phyclk;
+ return 0;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ dwmac->enable_eth_ck = dwmac->eth_clk_sel_reg ||
+ dwmac->ext_phyclk;
+ return 0;
+ default:
+ dwmac->enable_eth_ck = false;
+ dev_err(dwmac->dev, "Mode %s not supported",
+ phy_modes(plat_dat->phy_interface));
+ return -EINVAL;
+ }
+}
+
+static int stm32mp1_validate_ethck_rate(struct plat_stmmacenet_data *plat_dat)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+ const u32 clk_rate = clk_get_rate(dwmac->clk_eth_ck);
+
+ if (!dwmac->enable_eth_ck)
+ return 0;
+
+ switch (plat_dat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ case PHY_INTERFACE_MODE_GMII:
+ if (clk_rate == ETH_CK_F_25M)
+ return 0;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (clk_rate == ETH_CK_F_25M || clk_rate == ETH_CK_F_50M)
+ return 0;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ if (clk_rate == ETH_CK_F_25M || clk_rate == ETH_CK_F_125M)
+ return 0;
+ break;
+ default:
+ break;
+ }
+
+ dev_err(dwmac->dev, "Mode %s does not match eth-ck frequency %d Hz",
+ phy_modes(plat_dat->phy_interface), clk_rate);
+ return -EINVAL;
+}
+
+static int stm32mp1_configure_pmcr(struct plat_stmmacenet_data *plat_dat,
+ u8 phy_intf_sel)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+ u32 reg = dwmac->mode_reg;
+ int val;
+
+ val = FIELD_PREP(SYSCFG_PMCR_PHY_INTF_SEL_MASK, phy_intf_sel);
+
+ switch (plat_dat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ /*
+ * STM32MP15xx supports both MII and GMII, STM32MP13xx MII only.
+ * SYSCFG_PMCSETR ETH_SELMII is present only on STM32MP15xx and
+ * acts as a selector between 0:GMII and 1:MII. As STM32MP13xx
+ * supports only MII, ETH_SELMII is not present.
+ */
+ if (!dwmac->ops->is_mp13) /* Select MII mode on STM32MP15xx */
+ val |= SYSCFG_PMCR_ETH_SEL_MII;
+ break;
+ case PHY_INTERFACE_MODE_GMII:
+ if (dwmac->enable_eth_ck)
+ val |= SYSCFG_PMCR_ETH_CLK_SEL;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (dwmac->enable_eth_ck)
+ val |= SYSCFG_PMCR_ETH_REF_CLK_SEL;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ if (dwmac->enable_eth_ck)
+ val |= SYSCFG_PMCR_ETH_CLK_SEL;
+ break;
+ default:
+ dev_err(dwmac->dev, "Mode %s not supported",
+ phy_modes(plat_dat->phy_interface));
+ /* Do not manage others interfaces */
+ return -EINVAL;
+ }
+
+ dev_dbg(dwmac->dev, "Mode %s", phy_modes(plat_dat->phy_interface));
+
+ /* Shift value at correct ethernet MAC offset in SYSCFG_PMCSETR */
+ val <<= ffs(dwmac->mode_mask) - ffs(SYSCFG_MP1_ETH_MASK);
+
+ /* Need to update PMCCLRR (clear register) */
+ regmap_write(dwmac->regmap, dwmac->ops->syscfg_clr_off,
+ dwmac->mode_mask);
+
+ /* Update PMCSETR (set register) */
+ return regmap_update_bits(dwmac->regmap, reg,
+ dwmac->mode_mask, val);
+}
+
+static int stm32mp2_configure_syscfg(struct plat_stmmacenet_data *plat_dat,
+ u8 phy_intf_sel)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+ u32 reg = dwmac->mode_reg;
+ int val;
+
+ val = FIELD_PREP(SYSCFG_ETHCR_ETH_SEL_MASK, phy_intf_sel);
+
+ switch (plat_dat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ /* ETH_REF_CLK_SEL bit in SYSCFG register is not applicable in MII mode */
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (dwmac->enable_eth_ck) {
+ /* Internal clock ETH_CLK of 50MHz from RCC is used */
+ val |= SYSCFG_ETHCR_ETH_REF_CLK_SEL;
+ }
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_GMII:
+ if (dwmac->enable_eth_ck) {
+ /* Internal clock ETH_CLK of 125MHz from RCC is used */
+ val |= SYSCFG_ETHCR_ETH_CLK_SEL;
+ }
+ break;
+ default:
+ dev_err(dwmac->dev, "Mode %s not supported",
+ phy_modes(plat_dat->phy_interface));
+ /* Do not manage others interfaces */
+ return -EINVAL;
+ }
+
+ dev_dbg(dwmac->dev, "Mode %s", phy_modes(plat_dat->phy_interface));
+
+ /* Select PTP (IEEE1588) clock selection from RCC (ck_ker_ethxptp) */
+ val |= SYSCFG_ETHCR_ETH_PTP_CLK_SEL;
+
+ /* Update ETHCR (set register) */
+ return regmap_update_bits(dwmac->regmap, reg,
+ SYSCFG_MP2_ETH_MASK, val);
+}
+
+static int stm32mp1_set_mode(struct plat_stmmacenet_data *plat_dat)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+ int phy_intf_sel, ret;
+
+ ret = stm32mp1_select_ethck_external(plat_dat);
+ if (ret)
+ return ret;
+
+ ret = stm32mp1_validate_ethck_rate(plat_dat);
+ if (ret)
+ return ret;
+
+ phy_intf_sel = stmmac_get_phy_intf_sel(plat_dat->phy_interface);
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII) {
+ dev_err(dwmac->dev, "Mode %s not supported\n",
+ phy_modes(plat_dat->phy_interface));
+ return phy_intf_sel < 0 ? phy_intf_sel : -EINVAL;
+ }
+
+ if (!dwmac->ops->is_mp2)
+ return stm32mp1_configure_pmcr(plat_dat, phy_intf_sel);
+ else
+ return stm32mp2_configure_syscfg(plat_dat, phy_intf_sel);
+}
+
+static int stm32mcu_set_mode(struct plat_stmmacenet_data *plat_dat)
+{
+ struct stm32_dwmac *dwmac = plat_dat->bsp_priv;
+ u32 reg = dwmac->mode_reg;
+ int val;
+
+ switch (plat_dat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ val = SYSCFG_MCU_ETH_SEL_MII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ val = SYSCFG_MCU_ETH_SEL_RMII;
+ break;
+ default:
+ dev_err(dwmac->dev, "Mode %s not supported",
+ phy_modes(plat_dat->phy_interface));
+ /* Do not manage others interfaces */
+ return -EINVAL;
+ }
+
+ dev_dbg(dwmac->dev, "Mode %s", phy_modes(plat_dat->phy_interface));
+
+ return regmap_update_bits(dwmac->regmap, reg,
+ SYSCFG_MCU_ETH_MASK, val << 23);
+}
+
+static void stm32_dwmac_clk_disable(struct stm32_dwmac *dwmac)
+{
+ clk_disable_unprepare(dwmac->clk_tx);
+ clk_disable_unprepare(dwmac->clk_rx);
+ clk_disable_unprepare(dwmac->syscfg_clk);
+ if (dwmac->enable_eth_ck)
+ clk_disable_unprepare(dwmac->clk_eth_ck);
+}
+
+static int stm32_dwmac_parse_data(struct stm32_dwmac *dwmac,
+ struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ int err;
+
+ /* Get TX/RX clocks */
+ dwmac->clk_tx = devm_clk_get(dev, "mac-clk-tx");
+ if (IS_ERR(dwmac->clk_tx)) {
+ dev_err(dev, "No ETH Tx clock provided...\n");
+ return PTR_ERR(dwmac->clk_tx);
+ }
+
+ dwmac->clk_rx = devm_clk_get(dev, "mac-clk-rx");
+ if (IS_ERR(dwmac->clk_rx)) {
+ dev_err(dev, "No ETH Rx clock provided...\n");
+ return PTR_ERR(dwmac->clk_rx);
+ }
+
+ if (dwmac->ops->parse_data) {
+ err = dwmac->ops->parse_data(dwmac, dev);
+ if (err)
+ return err;
+ }
+
+ /* Get mode register */
+ dwmac->regmap = syscon_regmap_lookup_by_phandle_args(np, "st,syscon",
+ 1, &dwmac->mode_reg);
+ if (IS_ERR(dwmac->regmap))
+ return PTR_ERR(dwmac->regmap);
+
+ if (dwmac->ops->is_mp2)
+ return 0;
+
+ dwmac->mode_mask = SYSCFG_MP1_ETH_MASK;
+ err = of_property_read_u32_index(np, "st,syscon", 2, &dwmac->mode_mask);
+ if (err) {
+ if (dwmac->ops->is_mp13) {
+ dev_err(dev, "Sysconfig register mask must be set (%d)\n", err);
+ } else {
+ dev_dbg(dev, "Warning sysconfig register mask not set\n");
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+static int stm32mp1_parse_data(struct stm32_dwmac *dwmac,
+ struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+ int err = 0;
+
+ /* Ethernet PHY have no crystal */
+ dwmac->ext_phyclk = of_property_read_bool(np, "st,ext-phyclk");
+
+ /* Gigabit Ethernet 125MHz clock selection. */
+ dwmac->eth_clk_sel_reg = of_property_read_bool(np, "st,eth-clk-sel");
+
+ /* Ethernet 50MHz RMII clock selection */
+ dwmac->eth_ref_clk_sel_reg =
+ of_property_read_bool(np, "st,eth-ref-clk-sel");
+
+ /* Get ETH_CLK clocks */
+ dwmac->clk_eth_ck = devm_clk_get(dev, "eth-ck");
+ if (IS_ERR(dwmac->clk_eth_ck)) {
+ dev_info(dev, "No phy clock provided...\n");
+ dwmac->clk_eth_ck = NULL;
+ }
+
+ /* Clock used for low power mode */
+ dwmac->clk_ethstp = devm_clk_get(dev, "ethstp");
+ if (IS_ERR(dwmac->clk_ethstp)) {
+ dev_err(dev,
+ "No ETH peripheral clock provided for CStop mode ...\n");
+ return PTR_ERR(dwmac->clk_ethstp);
+ }
+
+ /* Optional Clock for sysconfig */
+ dwmac->syscfg_clk = devm_clk_get(dev, "syscfg-clk");
+ if (IS_ERR(dwmac->syscfg_clk))
+ dwmac->syscfg_clk = NULL;
+
+ /* Get IRQ information early to have an ability to ask for deferred
+ * probe if needed before we went too far with resource allocation.
+ */
+ dwmac->irq_pwr_wakeup = platform_get_irq_byname_optional(pdev,
+ "stm32_pwr_wakeup");
+ if (dwmac->irq_pwr_wakeup == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (!dwmac->clk_eth_ck && dwmac->irq_pwr_wakeup >= 0) {
+ err = device_init_wakeup(&pdev->dev, true);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to init wake up irq\n");
+ return err;
+ }
+ err = dev_pm_set_dedicated_wake_irq(&pdev->dev,
+ dwmac->irq_pwr_wakeup);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to set wake up irq\n");
+ device_init_wakeup(&pdev->dev, false);
+ }
+ device_set_wakeup_enable(&pdev->dev, false);
+ }
+ return err;
+}
+
+static int stm32_dwmac_suspend(struct device *dev, void *bsp_priv)
+{
+ struct stm32_dwmac *dwmac = bsp_priv;
+
+ stm32_dwmac_clk_disable(dwmac);
+
+ return dwmac->ops->suspend ? dwmac->ops->suspend(dwmac) : 0;
+}
+
+static int stm32_dwmac_resume(struct device *dev, void *bsp_priv)
+{
+ struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev));
+ struct stm32_dwmac *dwmac = bsp_priv;
+
+ if (dwmac->ops->resume)
+ dwmac->ops->resume(dwmac);
+
+ return stm32_dwmac_init(priv->plat);
+}
+
+static int stm32_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct stm32_dwmac *dwmac;
+ const struct stm32_ops *data;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "no of match data provided\n");
+ return -EINVAL;
+ }
+
+ dwmac->ops = data;
+ dwmac->dev = &pdev->dev;
+
+ ret = stm32_dwmac_parse_data(dwmac, &pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to parse OF data\n");
+ return ret;
+ }
+
+ plat_dat->flags |= STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP;
+ plat_dat->bsp_priv = dwmac;
+ plat_dat->suspend = stm32_dwmac_suspend;
+ plat_dat->resume = stm32_dwmac_resume;
+
+ ret = stm32_dwmac_init(plat_dat);
+ if (ret)
+ return ret;
+
+ /* If this platform requires the clock to be running in suspend,
+ * prepare and enable the receive clock an additional time to keep
+ * it running.
+ */
+ if (dwmac->ops->clk_rx_enable_in_suspend) {
+ ret = clk_prepare_enable(dwmac->clk_rx);
+ if (ret)
+ goto err_clk_disable;
+ }
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+ if (ret)
+ goto err_clk_disable_suspend;
+
+ return 0;
+
+err_clk_disable_suspend:
+ if (dwmac->ops->clk_rx_enable_in_suspend)
+ clk_disable_unprepare(dwmac->clk_rx);
+
+err_clk_disable:
+ stm32_dwmac_clk_disable(dwmac);
+
+ return ret;
+}
+
+static void stm32_dwmac_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct stm32_dwmac *dwmac = priv->plat->bsp_priv;
+
+ stmmac_dvr_remove(&pdev->dev);
+
+ /* If this platform requires the clock to be running in suspend,
+ * we need to disable and unprepare the receive clock an additional
+ * time to balance the extra clk_prepare_enable() in the probe
+ * function.
+ */
+ if (dwmac->ops->clk_rx_enable_in_suspend)
+ clk_disable_unprepare(dwmac->clk_rx);
+
+ stm32_dwmac_clk_disable(dwmac);
+
+ if (dwmac->irq_pwr_wakeup >= 0) {
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+ }
+}
+
+static int stm32mp1_suspend(struct stm32_dwmac *dwmac)
+{
+ return clk_prepare_enable(dwmac->clk_ethstp);
+}
+
+static void stm32mp1_resume(struct stm32_dwmac *dwmac)
+{
+ clk_disable_unprepare(dwmac->clk_ethstp);
+}
+
+static struct stm32_ops stm32mcu_dwmac_data = {
+ .set_mode = stm32mcu_set_mode
+};
+
+static struct stm32_ops stm32mp1_dwmac_data = {
+ .set_mode = stm32mp1_set_mode,
+ .suspend = stm32mp1_suspend,
+ .resume = stm32mp1_resume,
+ .parse_data = stm32mp1_parse_data,
+ .syscfg_clr_off = 0x44,
+ .is_mp13 = false,
+ .clk_rx_enable_in_suspend = true
+};
+
+static struct stm32_ops stm32mp13_dwmac_data = {
+ .set_mode = stm32mp1_set_mode,
+ .suspend = stm32mp1_suspend,
+ .resume = stm32mp1_resume,
+ .parse_data = stm32mp1_parse_data,
+ .syscfg_clr_off = 0x08,
+ .is_mp13 = true,
+ .clk_rx_enable_in_suspend = true
+};
+
+static struct stm32_ops stm32mp25_dwmac_data = {
+ .set_mode = stm32mp1_set_mode,
+ .suspend = stm32mp1_suspend,
+ .resume = stm32mp1_resume,
+ .parse_data = stm32mp1_parse_data,
+ .is_mp2 = true,
+ .clk_rx_enable_in_suspend = true
+};
+
+static const struct of_device_id stm32_dwmac_match[] = {
+ { .compatible = "st,stm32-dwmac", .data = &stm32mcu_dwmac_data},
+ { .compatible = "st,stm32mp1-dwmac", .data = &stm32mp1_dwmac_data},
+ { .compatible = "st,stm32mp13-dwmac", .data = &stm32mp13_dwmac_data},
+ { .compatible = "st,stm32mp25-dwmac", .data = &stm32mp25_dwmac_data},
+ { }
+};
+MODULE_DEVICE_TABLE(of, stm32_dwmac_match);
+
+static struct platform_driver stm32_dwmac_driver = {
+ .probe = stm32_dwmac_probe,
+ .remove = stm32_dwmac_remove,
+ .driver = {
+ .name = "stm32-dwmac",
+ .pm = &stmmac_simple_pm_ops,
+ .of_match_table = stm32_dwmac_match,
+ },
+};
+module_platform_driver(stm32_dwmac_driver);
+
+MODULE_AUTHOR("Alexandre Torgue <alexandre.torgue@gmail.com>");
+MODULE_AUTHOR("Christophe Roullier <christophe.roullier@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 DWMAC Specific Glue layer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun55i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun55i.c
new file mode 100644
index 000000000..862df173d
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun55i.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * dwmac-sun55i.c - Allwinner sun55i GMAC200 specific glue layer
+ *
+ * Copyright (C) 2025 Chen-Yu Tsai <wens@csie.org>
+ *
+ * syscon parts taken from dwmac-sun8i.c, which is
+ *
+ * Copyright (C) 2017 Corentin Labbe <clabbe.montjoie@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/stmmac.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+#define SYSCON_REG 0x34
+
+/* RMII specific bits */
+#define SYSCON_RMII_EN BIT(13) /* 1: enable RMII (overrides EPIT) */
+/* Generic system control EMAC_CLK bits */
+#define SYSCON_ETXDC_MASK GENMASK(12, 10)
+#define SYSCON_ERXDC_MASK GENMASK(9, 5)
+/* EMAC PHY Interface Type */
+#define SYSCON_EPIT BIT(2) /* 1: RGMII, 0: MII */
+#define SYSCON_ETCS_MASK GENMASK(1, 0)
+#define SYSCON_ETCS_MII 0x0
+#define SYSCON_ETCS_EXT_GMII 0x1
+#define SYSCON_ETCS_INT_GMII 0x2
+
+static int sun55i_gmac200_set_syscon(struct device *dev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct device_node *node = dev->of_node;
+ struct regmap *regmap;
+ u32 val, reg = 0;
+ int ret;
+
+ regmap = syscon_regmap_lookup_by_phandle(node, "syscon");
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Unable to map syscon\n");
+
+ if (!of_property_read_u32(node, "tx-internal-delay-ps", &val)) {
+ if (val % 100)
+ return dev_err_probe(dev, -EINVAL,
+ "tx-delay must be a multiple of 100ps\n");
+ val /= 100;
+ dev_dbg(dev, "set tx-delay to %x\n", val);
+ if (!FIELD_FIT(SYSCON_ETXDC_MASK, val))
+ return dev_err_probe(dev, -EINVAL,
+ "TX clock delay exceeds maximum (%u00ps > %lu00ps)\n",
+ val, FIELD_MAX(SYSCON_ETXDC_MASK));
+
+ reg |= FIELD_PREP(SYSCON_ETXDC_MASK, val);
+ }
+
+ if (!of_property_read_u32(node, "rx-internal-delay-ps", &val)) {
+ if (val % 100)
+ return dev_err_probe(dev, -EINVAL,
+ "rx-delay must be a multiple of 100ps\n");
+ val /= 100;
+ dev_dbg(dev, "set rx-delay to %x\n", val);
+ if (!FIELD_FIT(SYSCON_ERXDC_MASK, val))
+ return dev_err_probe(dev, -EINVAL,
+ "RX clock delay exceeds maximum (%u00ps > %lu00ps)\n",
+ val, FIELD_MAX(SYSCON_ERXDC_MASK));
+
+ reg |= FIELD_PREP(SYSCON_ERXDC_MASK, val);
+ }
+
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ /* default */
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ reg |= SYSCON_EPIT | SYSCON_ETCS_INT_GMII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ reg |= SYSCON_RMII_EN;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL, "Unsupported interface mode: %s",
+ phy_modes(plat->phy_interface));
+ }
+
+ ret = regmap_write(regmap, SYSCON_REG, reg);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to write to syscon\n");
+
+ return 0;
+}
+
+static int sun55i_gmac200_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct device *dev = &pdev->dev;
+ struct clk *clk;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ /* BSP disables it */
+ plat_dat->flags |= STMMAC_FLAG_SPH_DISABLE;
+ plat_dat->host_dma_width = 32;
+
+ ret = sun55i_gmac200_set_syscon(dev, plat_dat);
+ if (ret)
+ return ret;
+
+ clk = devm_clk_get_enabled(dev, "mbus");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "Failed to get or enable MBUS clock\n");
+
+ ret = devm_regulator_get_enable_optional(dev, "phy");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get or enable PHY supply\n");
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id sun55i_gmac200_match[] = {
+ { .compatible = "allwinner,sun55i-a523-gmac200" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sun55i_gmac200_match);
+
+static struct platform_driver sun55i_gmac200_driver = {
+ .probe = sun55i_gmac200_probe,
+ .driver = {
+ .name = "dwmac-sun55i",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = sun55i_gmac200_match,
+ },
+};
+module_platform_driver(sun55i_gmac200_driver);
+
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
+MODULE_DESCRIPTION("Allwinner sun55i GMAC200 specific glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
new file mode 100644
index 000000000..48c52eb96
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
@@ -0,0 +1,1299 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * dwmac-sun8i.c - Allwinner sun8i DWMAC specific glue layer
+ *
+ * Copyright (C) 2017 Corentin Labbe <clabbe.montjoie@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mdio-mux.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+/* General notes on dwmac-sun8i:
+ * Locking: no locking is necessary in this file because all necessary locking
+ * is done in the "stmmac files"
+ */
+
+/* struct emac_variant - Describe dwmac-sun8i hardware variant
+ * @syscon_field reg_field for the syscon's gmac register
+ * @soc_has_internal_phy: Does the MAC embed an internal PHY
+ * @support_mii: Does the MAC handle MII
+ * @support_rmii: Does the MAC handle RMII
+ * @support_rgmii: Does the MAC handle RGMII
+ *
+ * @rx_delay_max: Maximum raw value for RX delay chain
+ * @tx_delay_max: Maximum raw value for TX delay chain
+ * These two also indicate the bitmask for
+ * the RX and TX delay chain registers. A
+ * value of zero indicates this is not supported.
+ */
+struct emac_variant {
+ const struct reg_field *syscon_field;
+ bool soc_has_internal_phy;
+ bool support_mii;
+ bool support_rmii;
+ bool support_rgmii;
+ u8 rx_delay_max;
+ u8 tx_delay_max;
+};
+
+/* struct sunxi_priv_data - hold all sunxi private data
+ * @ephy_clk: reference to the optional EPHY clock for the internal PHY
+ * @regulator: reference to the optional regulator
+ * @rst_ephy: reference to the optional EPHY reset for the internal PHY
+ * @variant: reference to the current board variant
+ * @regmap: regmap for using the syscon
+ * @internal_phy_powered: Does the internal PHY is enabled
+ * @use_internal_phy: Is the internal PHY selected for use
+ * @mux_handle: Internal pointer used by mdio-mux lib
+ */
+struct sunxi_priv_data {
+ struct clk *ephy_clk;
+ struct regulator *regulator;
+ struct reset_control *rst_ephy;
+ const struct emac_variant *variant;
+ struct regmap_field *regmap_field;
+ bool internal_phy_powered;
+ bool use_internal_phy;
+ void *mux_handle;
+};
+
+/* EMAC clock register @ 0x30 in the "system control" address range */
+static const struct reg_field sun8i_syscon_reg_field = {
+ .reg = 0x30,
+ .lsb = 0,
+ .msb = 31,
+};
+
+/* EMAC clock register @ 0x164 in the CCU address range */
+static const struct reg_field sun8i_ccu_reg_field = {
+ .reg = 0x164,
+ .lsb = 0,
+ .msb = 31,
+};
+
+static const struct emac_variant emac_variant_h3 = {
+ .syscon_field = &sun8i_syscon_reg_field,
+ .soc_has_internal_phy = true,
+ .support_mii = true,
+ .support_rmii = true,
+ .support_rgmii = true,
+ .rx_delay_max = 31,
+ .tx_delay_max = 7,
+};
+
+static const struct emac_variant emac_variant_v3s = {
+ .syscon_field = &sun8i_syscon_reg_field,
+ .soc_has_internal_phy = true,
+ .support_mii = true
+};
+
+static const struct emac_variant emac_variant_a83t = {
+ .syscon_field = &sun8i_syscon_reg_field,
+ .soc_has_internal_phy = false,
+ .support_mii = true,
+ .support_rgmii = true,
+ .rx_delay_max = 31,
+ .tx_delay_max = 7,
+};
+
+static const struct emac_variant emac_variant_r40 = {
+ .syscon_field = &sun8i_ccu_reg_field,
+ .support_mii = true,
+ .support_rgmii = true,
+ .rx_delay_max = 7,
+};
+
+static const struct emac_variant emac_variant_a64 = {
+ .syscon_field = &sun8i_syscon_reg_field,
+ .soc_has_internal_phy = false,
+ .support_mii = true,
+ .support_rmii = true,
+ .support_rgmii = true,
+ .rx_delay_max = 31,
+ .tx_delay_max = 7,
+};
+
+static const struct emac_variant emac_variant_h6 = {
+ .syscon_field = &sun8i_syscon_reg_field,
+ /* The "Internal PHY" of H6 is not on the die. It's on the
+ * co-packaged AC200 chip instead.
+ */
+ .soc_has_internal_phy = false,
+ .support_mii = true,
+ .support_rmii = true,
+ .support_rgmii = true,
+ .rx_delay_max = 31,
+ .tx_delay_max = 7,
+};
+
+#define EMAC_BASIC_CTL0 0x00
+#define EMAC_BASIC_CTL1 0x04
+#define EMAC_INT_STA 0x08
+#define EMAC_INT_EN 0x0C
+#define EMAC_TX_CTL0 0x10
+#define EMAC_TX_CTL1 0x14
+#define EMAC_TX_FLOW_CTL 0x1C
+#define EMAC_TX_DESC_LIST 0x20
+#define EMAC_RX_CTL0 0x24
+#define EMAC_RX_CTL1 0x28
+#define EMAC_RX_DESC_LIST 0x34
+#define EMAC_RX_FRM_FLT 0x38
+#define EMAC_MDIO_CMD 0x48
+#define EMAC_MDIO_DATA 0x4C
+#define EMAC_MACADDR_HI(reg) (0x50 + (reg) * 8)
+#define EMAC_MACADDR_LO(reg) (0x54 + (reg) * 8)
+#define EMAC_TX_DMA_STA 0xB0
+#define EMAC_TX_CUR_DESC 0xB4
+#define EMAC_TX_CUR_BUF 0xB8
+#define EMAC_RX_DMA_STA 0xC0
+#define EMAC_RX_CUR_DESC 0xC4
+#define EMAC_RX_CUR_BUF 0xC8
+
+/* Use in EMAC_BASIC_CTL0 */
+#define EMAC_DUPLEX_FULL BIT(0)
+#define EMAC_LOOPBACK BIT(1)
+#define EMAC_SPEED_1000 0
+#define EMAC_SPEED_100 (0x03 << 2)
+#define EMAC_SPEED_10 (0x02 << 2)
+
+/* Use in EMAC_BASIC_CTL1 */
+#define EMAC_BURSTLEN_SHIFT 24
+
+/* Used in EMAC_RX_FRM_FLT */
+#define EMAC_FRM_FLT_RXALL BIT(0)
+#define EMAC_FRM_FLT_CTL BIT(13)
+#define EMAC_FRM_FLT_MULTICAST BIT(16)
+
+/* Used in RX_CTL1*/
+#define EMAC_RX_MD BIT(1)
+#define EMAC_RX_TH_MASK GENMASK(5, 4)
+#define EMAC_RX_TH_32 0
+#define EMAC_RX_TH_64 (0x1 << 4)
+#define EMAC_RX_TH_96 (0x2 << 4)
+#define EMAC_RX_TH_128 (0x3 << 4)
+#define EMAC_RX_DMA_EN BIT(30)
+#define EMAC_RX_DMA_START BIT(31)
+
+/* Used in TX_CTL1*/
+#define EMAC_TX_MD BIT(1)
+#define EMAC_TX_NEXT_FRM BIT(2)
+#define EMAC_TX_TH_MASK GENMASK(10, 8)
+#define EMAC_TX_TH_64 0
+#define EMAC_TX_TH_128 (0x1 << 8)
+#define EMAC_TX_TH_192 (0x2 << 8)
+#define EMAC_TX_TH_256 (0x3 << 8)
+#define EMAC_TX_DMA_EN BIT(30)
+#define EMAC_TX_DMA_START BIT(31)
+
+/* Used in RX_CTL0 */
+#define EMAC_RX_RECEIVER_EN BIT(31)
+#define EMAC_RX_DO_CRC BIT(27)
+#define EMAC_RX_FLOW_CTL_EN BIT(16)
+
+/* Used in TX_CTL0 */
+#define EMAC_TX_TRANSMITTER_EN BIT(31)
+
+/* Used in EMAC_TX_FLOW_CTL */
+#define EMAC_TX_FLOW_CTL_EN BIT(0)
+
+/* Used in EMAC_INT_STA */
+#define EMAC_TX_INT BIT(0)
+#define EMAC_TX_DMA_STOP_INT BIT(1)
+#define EMAC_TX_BUF_UA_INT BIT(2)
+#define EMAC_TX_TIMEOUT_INT BIT(3)
+#define EMAC_TX_UNDERFLOW_INT BIT(4)
+#define EMAC_TX_EARLY_INT BIT(5)
+#define EMAC_RX_INT BIT(8)
+#define EMAC_RX_BUF_UA_INT BIT(9)
+#define EMAC_RX_DMA_STOP_INT BIT(10)
+#define EMAC_RX_TIMEOUT_INT BIT(11)
+#define EMAC_RX_OVERFLOW_INT BIT(12)
+#define EMAC_RX_EARLY_INT BIT(13)
+#define EMAC_RGMII_STA_INT BIT(16)
+
+#define EMAC_INT_MSK_COMMON EMAC_RGMII_STA_INT
+#define EMAC_INT_MSK_TX (EMAC_TX_INT | \
+ EMAC_TX_DMA_STOP_INT | \
+ EMAC_TX_BUF_UA_INT | \
+ EMAC_TX_TIMEOUT_INT | \
+ EMAC_TX_UNDERFLOW_INT | \
+ EMAC_TX_EARLY_INT |\
+ EMAC_INT_MSK_COMMON)
+#define EMAC_INT_MSK_RX (EMAC_RX_INT | \
+ EMAC_RX_BUF_UA_INT | \
+ EMAC_RX_DMA_STOP_INT | \
+ EMAC_RX_TIMEOUT_INT | \
+ EMAC_RX_OVERFLOW_INT | \
+ EMAC_RX_EARLY_INT | \
+ EMAC_INT_MSK_COMMON)
+
+#define MAC_ADDR_TYPE_DST BIT(31)
+
+/* H3 specific bits for EPHY */
+#define H3_EPHY_ADDR_SHIFT 20
+#define H3_EPHY_CLK_SEL BIT(18) /* 1: 24MHz, 0: 25MHz */
+#define H3_EPHY_LED_POL BIT(17) /* 1: active low, 0: active high */
+#define H3_EPHY_SHUTDOWN BIT(16) /* 1: shutdown, 0: power up */
+#define H3_EPHY_SELECT BIT(15) /* 1: internal PHY, 0: external PHY */
+#define H3_EPHY_MUX_MASK (H3_EPHY_SHUTDOWN | H3_EPHY_SELECT)
+#define DWMAC_SUN8I_MDIO_MUX_INTERNAL_ID 1
+#define DWMAC_SUN8I_MDIO_MUX_EXTERNAL_ID 2
+
+/* H3/A64 specific bits */
+#define SYSCON_RMII_EN BIT(13) /* 1: enable RMII (overrides EPIT) */
+
+/* Generic system control EMAC_CLK bits */
+#define SYSCON_ETXDC_SHIFT 10
+#define SYSCON_ERXDC_SHIFT 5
+/* EMAC PHY Interface Type */
+#define SYSCON_EPIT BIT(2) /* 1: RGMII, 0: MII */
+#define SYSCON_ETCS_MASK GENMASK(1, 0)
+#define SYSCON_ETCS_MII 0x0
+#define SYSCON_ETCS_EXT_GMII 0x1
+#define SYSCON_ETCS_INT_GMII 0x2
+
+/* sun8i_dwmac_dma_reset() - reset the EMAC
+ * Called from stmmac via stmmac_dma_ops->reset
+ */
+static int sun8i_dwmac_dma_reset(void __iomem *ioaddr)
+{
+ writel(0, ioaddr + EMAC_RX_CTL1);
+ writel(0, ioaddr + EMAC_TX_CTL1);
+ writel(0, ioaddr + EMAC_RX_FRM_FLT);
+ writel(0, ioaddr + EMAC_RX_DESC_LIST);
+ writel(0, ioaddr + EMAC_TX_DESC_LIST);
+ writel(0, ioaddr + EMAC_INT_EN);
+ writel(0x1FFFFFF, ioaddr + EMAC_INT_STA);
+ return 0;
+}
+
+/* sun8i_dwmac_dma_init() - initialize the EMAC
+ * Called from stmmac via stmmac_dma_ops->init
+ */
+static void sun8i_dwmac_dma_init(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg)
+{
+ writel(EMAC_RX_INT | EMAC_TX_INT, ioaddr + EMAC_INT_EN);
+ writel(0x1FFFFFF, ioaddr + EMAC_INT_STA);
+}
+
+static void sun8i_dwmac_dma_init_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_rx_phy, u32 chan)
+{
+ /* Write RX descriptors address */
+ writel(lower_32_bits(dma_rx_phy), ioaddr + EMAC_RX_DESC_LIST);
+}
+
+static void sun8i_dwmac_dma_init_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_tx_phy, u32 chan)
+{
+ /* Write TX descriptors address */
+ writel(lower_32_bits(dma_tx_phy), ioaddr + EMAC_TX_DESC_LIST);
+}
+
+/* sun8i_dwmac_dump_regs() - Dump EMAC address space
+ * Called from stmmac_dma_ops->dump_regs
+ * Used for ethtool
+ */
+static void sun8i_dwmac_dump_regs(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 *reg_space)
+{
+ int i;
+
+ for (i = 0; i < 0xC8; i += 4) {
+ if (i == 0x32 || i == 0x3C)
+ continue;
+ reg_space[i / 4] = readl(ioaddr + i);
+ }
+}
+
+/* sun8i_dwmac_dump_mac_regs() - Dump EMAC address space
+ * Called from stmmac_ops->dump_regs
+ * Used for ethtool
+ */
+static void sun8i_dwmac_dump_mac_regs(struct mac_device_info *hw,
+ u32 *reg_space)
+{
+ int i;
+ void __iomem *ioaddr = hw->pcsr;
+
+ for (i = 0; i < 0xC8; i += 4) {
+ if (i == 0x32 || i == 0x3C)
+ continue;
+ reg_space[i / 4] = readl(ioaddr + i);
+ }
+}
+
+static void sun8i_dwmac_enable_dma_irq(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan,
+ bool rx, bool tx)
+{
+ u32 value = readl(ioaddr + EMAC_INT_EN);
+
+ if (rx)
+ value |= EMAC_RX_INT;
+ if (tx)
+ value |= EMAC_TX_INT;
+
+ writel(value, ioaddr + EMAC_INT_EN);
+}
+
+static void sun8i_dwmac_disable_dma_irq(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan,
+ bool rx, bool tx)
+{
+ u32 value = readl(ioaddr + EMAC_INT_EN);
+
+ if (rx)
+ value &= ~EMAC_RX_INT;
+ if (tx)
+ value &= ~EMAC_TX_INT;
+
+ writel(value, ioaddr + EMAC_INT_EN);
+}
+
+static void sun8i_dwmac_dma_start_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_TX_CTL1);
+ v |= EMAC_TX_DMA_START;
+ v |= EMAC_TX_DMA_EN;
+ writel(v, ioaddr + EMAC_TX_CTL1);
+}
+
+static void sun8i_dwmac_enable_dma_transmission(void __iomem *ioaddr, u32 chan)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_TX_CTL1);
+ v |= EMAC_TX_DMA_START;
+ v |= EMAC_TX_DMA_EN;
+ writel(v, ioaddr + EMAC_TX_CTL1);
+}
+
+static void sun8i_dwmac_dma_stop_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_TX_CTL1);
+ v &= ~EMAC_TX_DMA_EN;
+ writel(v, ioaddr + EMAC_TX_CTL1);
+}
+
+static void sun8i_dwmac_dma_start_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_RX_CTL1);
+ v |= EMAC_RX_DMA_START;
+ v |= EMAC_RX_DMA_EN;
+ writel(v, ioaddr + EMAC_RX_CTL1);
+}
+
+static void sun8i_dwmac_dma_stop_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_RX_CTL1);
+ v &= ~EMAC_RX_DMA_EN;
+ writel(v, ioaddr + EMAC_RX_CTL1);
+}
+
+static int sun8i_dwmac_dma_interrupt(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan,
+ u32 dir)
+{
+ struct stmmac_pcpu_stats *stats = this_cpu_ptr(priv->xstats.pcpu_stats);
+ int ret = 0;
+ u32 v;
+
+ v = readl(ioaddr + EMAC_INT_STA);
+
+ if (dir == DMA_DIR_RX)
+ v &= EMAC_INT_MSK_RX;
+ else if (dir == DMA_DIR_TX)
+ v &= EMAC_INT_MSK_TX;
+
+ if (v & EMAC_TX_INT) {
+ ret |= handle_tx;
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->tx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ }
+
+ if (v & EMAC_TX_DMA_STOP_INT)
+ x->tx_process_stopped_irq++;
+
+ if (v & EMAC_TX_BUF_UA_INT)
+ x->tx_process_stopped_irq++;
+
+ if (v & EMAC_TX_TIMEOUT_INT)
+ ret |= tx_hard_error;
+
+ if (v & EMAC_TX_UNDERFLOW_INT) {
+ ret |= tx_hard_error;
+ x->tx_undeflow_irq++;
+ }
+
+ if (v & EMAC_TX_EARLY_INT)
+ x->tx_early_irq++;
+
+ if (v & EMAC_RX_INT) {
+ ret |= handle_rx;
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->rx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ }
+
+ if (v & EMAC_RX_BUF_UA_INT)
+ x->rx_buf_unav_irq++;
+
+ if (v & EMAC_RX_DMA_STOP_INT)
+ x->rx_process_stopped_irq++;
+
+ if (v & EMAC_RX_TIMEOUT_INT)
+ ret |= tx_hard_error;
+
+ if (v & EMAC_RX_OVERFLOW_INT) {
+ ret |= tx_hard_error;
+ x->rx_overflow_irq++;
+ }
+
+ if (v & EMAC_RX_EARLY_INT)
+ x->rx_early_irq++;
+
+ if (v & EMAC_RGMII_STA_INT)
+ x->irq_rgmii_n++;
+
+ writel(v, ioaddr + EMAC_INT_STA);
+
+ return ret;
+}
+
+static void sun8i_dwmac_dma_operation_mode_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_RX_CTL1);
+ if (mode == SF_DMA_MODE) {
+ v |= EMAC_RX_MD;
+ } else {
+ v &= ~EMAC_RX_MD;
+ v &= ~EMAC_RX_TH_MASK;
+ if (mode < 32)
+ v |= EMAC_RX_TH_32;
+ else if (mode < 64)
+ v |= EMAC_RX_TH_64;
+ else if (mode < 96)
+ v |= EMAC_RX_TH_96;
+ else if (mode < 128)
+ v |= EMAC_RX_TH_128;
+ }
+ writel(v, ioaddr + EMAC_RX_CTL1);
+}
+
+static void sun8i_dwmac_dma_operation_mode_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ u32 v;
+
+ v = readl(ioaddr + EMAC_TX_CTL1);
+ if (mode == SF_DMA_MODE) {
+ v |= EMAC_TX_MD;
+ /* Undocumented bit (called TX_NEXT_FRM in BSP), the original
+ * comment is
+ * "Operating on second frame increase the performance
+ * especially when transmit store-and-forward is used."
+ */
+ v |= EMAC_TX_NEXT_FRM;
+ } else {
+ v &= ~EMAC_TX_MD;
+ v &= ~EMAC_TX_TH_MASK;
+ if (mode < 64)
+ v |= EMAC_TX_TH_64;
+ else if (mode < 128)
+ v |= EMAC_TX_TH_128;
+ else if (mode < 192)
+ v |= EMAC_TX_TH_192;
+ else if (mode < 256)
+ v |= EMAC_TX_TH_256;
+ }
+ writel(v, ioaddr + EMAC_TX_CTL1);
+}
+
+static const struct stmmac_dma_ops sun8i_dwmac_dma_ops = {
+ .reset = sun8i_dwmac_dma_reset,
+ .init = sun8i_dwmac_dma_init,
+ .init_rx_chan = sun8i_dwmac_dma_init_rx,
+ .init_tx_chan = sun8i_dwmac_dma_init_tx,
+ .dump_regs = sun8i_dwmac_dump_regs,
+ .dma_rx_mode = sun8i_dwmac_dma_operation_mode_rx,
+ .dma_tx_mode = sun8i_dwmac_dma_operation_mode_tx,
+ .enable_dma_transmission = sun8i_dwmac_enable_dma_transmission,
+ .enable_dma_irq = sun8i_dwmac_enable_dma_irq,
+ .disable_dma_irq = sun8i_dwmac_disable_dma_irq,
+ .start_tx = sun8i_dwmac_dma_start_tx,
+ .stop_tx = sun8i_dwmac_dma_stop_tx,
+ .start_rx = sun8i_dwmac_dma_start_rx,
+ .stop_rx = sun8i_dwmac_dma_stop_rx,
+ .dma_interrupt = sun8i_dwmac_dma_interrupt,
+};
+
+static int sun8i_dwmac_power_internal_phy(struct stmmac_priv *priv);
+
+static int sun8i_dwmac_init(struct device *dev, void *priv)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct sunxi_priv_data *gmac = priv;
+ int ret;
+
+ if (gmac->regulator) {
+ ret = regulator_enable(gmac->regulator);
+ if (ret) {
+ dev_err(dev, "Fail to enable regulator\n");
+ return ret;
+ }
+ }
+
+ if (gmac->use_internal_phy) {
+ ret = sun8i_dwmac_power_internal_phy(netdev_priv(ndev));
+ if (ret)
+ goto err_disable_regulator;
+ }
+
+ return 0;
+
+err_disable_regulator:
+ if (gmac->regulator)
+ regulator_disable(gmac->regulator);
+
+ return ret;
+}
+
+static void sun8i_dwmac_core_init(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 v;
+
+ v = (8 << EMAC_BURSTLEN_SHIFT); /* burst len */
+ writel(v, ioaddr + EMAC_BASIC_CTL1);
+}
+
+static void sun8i_dwmac_set_mac(void __iomem *ioaddr, bool enable)
+{
+ u32 t, r;
+
+ t = readl(ioaddr + EMAC_TX_CTL0);
+ r = readl(ioaddr + EMAC_RX_CTL0);
+ if (enable) {
+ t |= EMAC_TX_TRANSMITTER_EN;
+ r |= EMAC_RX_RECEIVER_EN;
+ } else {
+ t &= ~EMAC_TX_TRANSMITTER_EN;
+ r &= ~EMAC_RX_RECEIVER_EN;
+ }
+ writel(t, ioaddr + EMAC_TX_CTL0);
+ writel(r, ioaddr + EMAC_RX_CTL0);
+}
+
+/* Set MAC address at slot reg_n
+ * All slot > 0 need to be enabled with MAC_ADDR_TYPE_DST
+ * If addr is NULL, clear the slot
+ */
+static void sun8i_dwmac_set_umac_addr(struct mac_device_info *hw,
+ const unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 v;
+
+ if (!addr) {
+ writel(0, ioaddr + EMAC_MACADDR_HI(reg_n));
+ return;
+ }
+
+ stmmac_set_mac_addr(ioaddr, addr, EMAC_MACADDR_HI(reg_n),
+ EMAC_MACADDR_LO(reg_n));
+ if (reg_n > 0) {
+ v = readl(ioaddr + EMAC_MACADDR_HI(reg_n));
+ v |= MAC_ADDR_TYPE_DST;
+ writel(v, ioaddr + EMAC_MACADDR_HI(reg_n));
+ }
+}
+
+static void sun8i_dwmac_get_umac_addr(struct mac_device_info *hw,
+ unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+
+ stmmac_get_mac_addr(ioaddr, addr, EMAC_MACADDR_HI(reg_n),
+ EMAC_MACADDR_LO(reg_n));
+}
+
+/* caution this function must return non 0 to work */
+static int sun8i_dwmac_rx_ipc_enable(struct mac_device_info *hw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 v;
+
+ v = readl(ioaddr + EMAC_RX_CTL0);
+ v |= EMAC_RX_DO_CRC;
+ writel(v, ioaddr + EMAC_RX_CTL0);
+
+ return 1;
+}
+
+static void sun8i_dwmac_set_filter(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 v;
+ int i = 1;
+ struct netdev_hw_addr *ha;
+ int macaddrs = netdev_uc_count(dev) + netdev_mc_count(dev) + 1;
+
+ v = EMAC_FRM_FLT_CTL;
+
+ if (dev->flags & IFF_PROMISC) {
+ v = EMAC_FRM_FLT_RXALL;
+ } else if (dev->flags & IFF_ALLMULTI) {
+ v |= EMAC_FRM_FLT_MULTICAST;
+ } else if (macaddrs <= hw->unicast_filter_entries) {
+ if (!netdev_mc_empty(dev)) {
+ netdev_for_each_mc_addr(ha, dev) {
+ sun8i_dwmac_set_umac_addr(hw, ha->addr, i);
+ i++;
+ }
+ }
+ if (!netdev_uc_empty(dev)) {
+ netdev_for_each_uc_addr(ha, dev) {
+ sun8i_dwmac_set_umac_addr(hw, ha->addr, i);
+ i++;
+ }
+ }
+ } else {
+ if (!(readl(ioaddr + EMAC_RX_FRM_FLT) & EMAC_FRM_FLT_RXALL))
+ netdev_info(dev, "Too many address, switching to promiscuous\n");
+ v = EMAC_FRM_FLT_RXALL;
+ }
+
+ /* Disable unused address filter slots */
+ while (i < hw->unicast_filter_entries)
+ sun8i_dwmac_set_umac_addr(hw, NULL, i++);
+
+ writel(v, ioaddr + EMAC_RX_FRM_FLT);
+}
+
+static void sun8i_dwmac_flow_ctrl(struct mac_device_info *hw,
+ unsigned int duplex, unsigned int fc,
+ unsigned int pause_time, u8 tx_cnt)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 v;
+
+ v = readl(ioaddr + EMAC_RX_CTL0);
+ if (fc == FLOW_AUTO)
+ v |= EMAC_RX_FLOW_CTL_EN;
+ else
+ v &= ~EMAC_RX_FLOW_CTL_EN;
+ writel(v, ioaddr + EMAC_RX_CTL0);
+
+ v = readl(ioaddr + EMAC_TX_FLOW_CTL);
+ if (fc == FLOW_AUTO)
+ v |= EMAC_TX_FLOW_CTL_EN;
+ else
+ v &= ~EMAC_TX_FLOW_CTL_EN;
+ writel(v, ioaddr + EMAC_TX_FLOW_CTL);
+}
+
+static int sun8i_dwmac_reset(struct stmmac_priv *priv)
+{
+ u32 v;
+ int err;
+
+ v = readl(priv->ioaddr + EMAC_BASIC_CTL1);
+ writel(v | 0x01, priv->ioaddr + EMAC_BASIC_CTL1);
+
+ /* The timeout was previously set to 10ms, but some board (OrangePI0)
+ * need more if no cable plugged. 100ms seems OK
+ */
+ err = readl_poll_timeout(priv->ioaddr + EMAC_BASIC_CTL1, v,
+ !(v & 0x01), 100, 100000);
+
+ if (err) {
+ dev_err(priv->device, "EMAC reset timeout\n");
+ return err;
+ }
+ return 0;
+}
+
+/* Search in mdio-mux node for internal PHY node and get its clk/reset */
+static int get_ephy_nodes(struct stmmac_priv *priv)
+{
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
+ struct device_node *mdio_internal;
+ struct device_node *mdio_mux;
+ int ret;
+
+ mdio_mux = of_get_child_by_name(priv->device->of_node, "mdio-mux");
+ if (!mdio_mux) {
+ dev_err(priv->device, "Cannot get mdio-mux node\n");
+ return -ENODEV;
+ }
+
+ mdio_internal = of_get_compatible_child(mdio_mux,
+ "allwinner,sun8i-h3-mdio-internal");
+ of_node_put(mdio_mux);
+ if (!mdio_internal) {
+ dev_err(priv->device, "Cannot get internal_mdio node\n");
+ return -ENODEV;
+ }
+
+ /* Seek for internal PHY */
+ for_each_child_of_node_scoped(mdio_internal, iphynode) {
+ gmac->ephy_clk = of_clk_get(iphynode, 0);
+ if (IS_ERR(gmac->ephy_clk))
+ continue;
+ gmac->rst_ephy = of_reset_control_get_exclusive(iphynode, NULL);
+ if (IS_ERR(gmac->rst_ephy)) {
+ ret = PTR_ERR(gmac->rst_ephy);
+ if (ret == -EPROBE_DEFER) {
+ of_node_put(mdio_internal);
+ return ret;
+ }
+ continue;
+ }
+ dev_info(priv->device, "Found internal PHY node\n");
+ of_node_put(mdio_internal);
+ return 0;
+ }
+
+ of_node_put(mdio_internal);
+ return -ENODEV;
+}
+
+static int sun8i_dwmac_power_internal_phy(struct stmmac_priv *priv)
+{
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
+ int ret;
+
+ if (gmac->internal_phy_powered) {
+ dev_warn(priv->device, "Internal PHY already powered\n");
+ return 0;
+ }
+
+ dev_info(priv->device, "Powering internal PHY\n");
+ ret = clk_prepare_enable(gmac->ephy_clk);
+ if (ret) {
+ dev_err(priv->device, "Cannot enable internal PHY\n");
+ return ret;
+ }
+
+ /* Make sure the EPHY is properly reset, as U-Boot may leave
+ * it at deasserted state, and thus it may fail to reset EMAC.
+ *
+ * This assumes the driver has exclusive access to the EPHY reset.
+ */
+ ret = reset_control_reset(gmac->rst_ephy);
+ if (ret) {
+ dev_err(priv->device, "Cannot reset internal PHY\n");
+ clk_disable_unprepare(gmac->ephy_clk);
+ return ret;
+ }
+
+ gmac->internal_phy_powered = true;
+
+ return 0;
+}
+
+static void sun8i_dwmac_unpower_internal_phy(struct sunxi_priv_data *gmac)
+{
+ if (!gmac->internal_phy_powered)
+ return;
+
+ clk_disable_unprepare(gmac->ephy_clk);
+ reset_control_assert(gmac->rst_ephy);
+ gmac->internal_phy_powered = false;
+}
+
+/* MDIO multiplexing switch function
+ * This function is called by the mdio-mux layer when it thinks the mdio bus
+ * multiplexer needs to switch.
+ * 'current_child' is the current value of the mux register
+ * 'desired_child' is the value of the 'reg' property of the target child MDIO
+ * node.
+ * The first time this function is called, current_child == -1.
+ * If current_child == desired_child, then the mux is already set to the
+ * correct bus.
+ */
+static int mdio_mux_syscon_switch_fn(int current_child, int desired_child,
+ void *data)
+{
+ struct stmmac_priv *priv = data;
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
+ u32 reg, val;
+ int ret = 0;
+
+ if (current_child ^ desired_child) {
+ regmap_field_read(gmac->regmap_field, &reg);
+ switch (desired_child) {
+ case DWMAC_SUN8I_MDIO_MUX_INTERNAL_ID:
+ dev_info(priv->device, "Switch mux to internal PHY");
+ val = (reg & ~H3_EPHY_MUX_MASK) | H3_EPHY_SELECT;
+ gmac->use_internal_phy = true;
+ break;
+ case DWMAC_SUN8I_MDIO_MUX_EXTERNAL_ID:
+ dev_info(priv->device, "Switch mux to external PHY");
+ val = (reg & ~H3_EPHY_MUX_MASK) | H3_EPHY_SHUTDOWN;
+ gmac->use_internal_phy = false;
+ break;
+ default:
+ dev_err(priv->device, "Invalid child ID %x\n",
+ desired_child);
+ return -EINVAL;
+ }
+ regmap_field_write(gmac->regmap_field, val);
+ if (gmac->use_internal_phy) {
+ ret = sun8i_dwmac_power_internal_phy(priv);
+ if (ret)
+ return ret;
+ } else {
+ sun8i_dwmac_unpower_internal_phy(gmac);
+ }
+ /* After changing syscon value, the MAC need reset or it will
+ * use the last value (and so the last PHY set).
+ */
+ ret = sun8i_dwmac_reset(priv);
+ }
+ return ret;
+}
+
+static int sun8i_dwmac_register_mdio_mux(struct stmmac_priv *priv)
+{
+ int ret;
+ struct device_node *mdio_mux;
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
+
+ mdio_mux = of_get_child_by_name(priv->device->of_node, "mdio-mux");
+ if (!mdio_mux)
+ return -ENODEV;
+
+ ret = mdio_mux_init(priv->device, mdio_mux, mdio_mux_syscon_switch_fn,
+ &gmac->mux_handle, priv, priv->mii);
+ of_node_put(mdio_mux);
+ return ret;
+}
+
+static int sun8i_dwmac_set_syscon(struct device *dev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct sunxi_priv_data *gmac = plat->bsp_priv;
+ struct device_node *node = dev->of_node;
+ int ret;
+ u32 reg = 0, val;
+
+ if (gmac->variant->soc_has_internal_phy) {
+ if (of_property_read_bool(node, "allwinner,leds-active-low"))
+ reg |= H3_EPHY_LED_POL;
+
+ /* Force EPHY xtal frequency to 24MHz. */
+ reg |= H3_EPHY_CLK_SEL;
+
+ ret = of_mdio_parse_addr(dev, plat->phy_node);
+ if (ret < 0) {
+ dev_err(dev, "Could not parse MDIO addr\n");
+ return ret;
+ }
+ /* of_mdio_parse_addr returns a valid (0 ~ 31) PHY
+ * address. No need to mask it again.
+ */
+ reg |= ret << H3_EPHY_ADDR_SHIFT;
+ }
+
+ if (!of_property_read_u32(node, "allwinner,tx-delay-ps", &val)) {
+ if (val % 100) {
+ dev_err(dev, "tx-delay must be a multiple of 100\n");
+ return -EINVAL;
+ }
+ val /= 100;
+ dev_dbg(dev, "set tx-delay to %x\n", val);
+ if (val <= gmac->variant->tx_delay_max) {
+ reg |= (val << SYSCON_ETXDC_SHIFT);
+ } else {
+ dev_err(dev, "Invalid TX clock delay: %d\n",
+ val);
+ return -EINVAL;
+ }
+ }
+
+ if (!of_property_read_u32(node, "allwinner,rx-delay-ps", &val)) {
+ if (val % 100) {
+ dev_err(dev, "rx-delay must be a multiple of 100\n");
+ return -EINVAL;
+ }
+ val /= 100;
+ dev_dbg(dev, "set rx-delay to %x\n", val);
+ if (val <= gmac->variant->rx_delay_max) {
+ reg |= (val << SYSCON_ERXDC_SHIFT);
+ } else {
+ dev_err(dev, "Invalid RX clock delay: %d\n",
+ val);
+ return -EINVAL;
+ }
+ }
+
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ /* default */
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ reg |= SYSCON_EPIT | SYSCON_ETCS_INT_GMII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ reg |= SYSCON_RMII_EN | SYSCON_ETCS_EXT_GMII;
+ break;
+ default:
+ dev_err(dev, "Unsupported interface mode: %s",
+ phy_modes(plat->phy_interface));
+ return -EINVAL;
+ }
+
+ regmap_field_write(gmac->regmap_field, reg);
+
+ return 0;
+}
+
+static void sun8i_dwmac_unset_syscon(struct sunxi_priv_data *gmac)
+{
+ if (gmac->variant->soc_has_internal_phy)
+ regmap_field_write(gmac->regmap_field,
+ (H3_EPHY_SHUTDOWN | H3_EPHY_SELECT));
+}
+
+static void sun8i_dwmac_exit(struct device *dev, void *priv)
+{
+ struct sunxi_priv_data *gmac = priv;
+
+ if (gmac->variant->soc_has_internal_phy)
+ sun8i_dwmac_unpower_internal_phy(gmac);
+
+ if (gmac->regulator)
+ regulator_disable(gmac->regulator);
+}
+
+static void sun8i_dwmac_set_mac_loopback(void __iomem *ioaddr, bool enable)
+{
+ u32 value = readl(ioaddr + EMAC_BASIC_CTL0);
+
+ if (enable)
+ value |= EMAC_LOOPBACK;
+ else
+ value &= ~EMAC_LOOPBACK;
+
+ writel(value, ioaddr + EMAC_BASIC_CTL0);
+}
+
+static const struct stmmac_ops sun8i_dwmac_ops = {
+ .core_init = sun8i_dwmac_core_init,
+ .set_mac = sun8i_dwmac_set_mac,
+ .dump_regs = sun8i_dwmac_dump_mac_regs,
+ .rx_ipc = sun8i_dwmac_rx_ipc_enable,
+ .set_filter = sun8i_dwmac_set_filter,
+ .flow_ctrl = sun8i_dwmac_flow_ctrl,
+ .set_umac_addr = sun8i_dwmac_set_umac_addr,
+ .get_umac_addr = sun8i_dwmac_get_umac_addr,
+ .set_mac_loopback = sun8i_dwmac_set_mac_loopback,
+};
+
+static int sun8i_dwmac_setup(void *ppriv, struct mac_device_info *mac)
+{
+ struct stmmac_priv *priv = ppriv;
+
+ mac->pcsr = priv->ioaddr;
+ mac->mac = &sun8i_dwmac_ops;
+ mac->dma = &sun8i_dwmac_dma_ops;
+
+ priv->dev->priv_flags |= IFF_UNICAST_FLT;
+
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000;
+ /* The loopback bit seems to be re-set when link change
+ * Simply mask it each time
+ * Speed 10/100/1000 are set in BIT(2)/BIT(3)
+ */
+ mac->link.speed_mask = GENMASK(3, 2) | EMAC_LOOPBACK;
+ mac->link.speed10 = EMAC_SPEED_10;
+ mac->link.speed100 = EMAC_SPEED_100;
+ mac->link.speed1000 = EMAC_SPEED_1000;
+ mac->link.duplex = EMAC_DUPLEX_FULL;
+ mac->mii.addr = EMAC_MDIO_CMD;
+ mac->mii.data = EMAC_MDIO_DATA;
+ mac->mii.reg_mask = GENMASK_U32(8, 4);
+ mac->mii.addr_mask = GENMASK_U32(16, 12);
+ mac->mii.clk_csr_mask = GENMASK_U32(22, 20);
+ mac->unicast_filter_entries = 8;
+
+ /* Synopsys Id is not available */
+ priv->synopsys_id = 0;
+
+ return 0;
+}
+
+static struct regmap *sun8i_dwmac_get_syscon_from_dev(struct device_node *node)
+{
+ struct device_node *syscon_node;
+ struct platform_device *syscon_pdev;
+ struct regmap *regmap = NULL;
+
+ syscon_node = of_parse_phandle(node, "syscon", 0);
+ if (!syscon_node)
+ return ERR_PTR(-ENODEV);
+
+ syscon_pdev = of_find_device_by_node(syscon_node);
+ if (!syscon_pdev) {
+ /* platform device might not be probed yet */
+ regmap = ERR_PTR(-EPROBE_DEFER);
+ goto out_put_node;
+ }
+
+ /* If no regmap is found then the other device driver is at fault */
+ regmap = dev_get_regmap(&syscon_pdev->dev, NULL);
+ if (!regmap)
+ regmap = ERR_PTR(-EINVAL);
+
+ platform_device_put(syscon_pdev);
+out_put_node:
+ of_node_put(syscon_node);
+ return regmap;
+}
+
+static int sun8i_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct sunxi_priv_data *gmac;
+ struct device *dev = &pdev->dev;
+ struct stmmac_priv *priv;
+ struct net_device *ndev;
+ struct regmap *regmap;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac)
+ return -ENOMEM;
+
+ gmac->variant = of_device_get_match_data(&pdev->dev);
+ if (!gmac->variant) {
+ dev_err(&pdev->dev, "Missing dwmac-sun8i variant\n");
+ return -EINVAL;
+ }
+
+ /* Optional regulator for PHY */
+ gmac->regulator = devm_regulator_get_optional(dev, "phy");
+ if (IS_ERR(gmac->regulator)) {
+ if (PTR_ERR(gmac->regulator) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_info(dev, "No regulator found\n");
+ gmac->regulator = NULL;
+ }
+
+ /* The "GMAC clock control" register might be located in the
+ * CCU address range (on the R40), or the system control address
+ * range (on most other sun8i and later SoCs).
+ *
+ * The former controls most if not all clocks in the SoC. The
+ * latter has an SoC identification register, and on some SoCs,
+ * controls to map device specific SRAM to either the intended
+ * peripheral, or the CPU address space.
+ *
+ * In either case, there should be a coordinated and restricted
+ * method of accessing the register needed here. This is done by
+ * having the device export a custom regmap, instead of a generic
+ * syscon, which grants all access to all registers.
+ *
+ * To support old device trees, we fall back to using the syscon
+ * interface if possible.
+ */
+ regmap = sun8i_dwmac_get_syscon_from_dev(pdev->dev.of_node);
+ if (IS_ERR(regmap))
+ regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "syscon");
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(&pdev->dev, "Unable to map syscon: %d\n", ret);
+ return ret;
+ }
+
+ gmac->regmap_field = devm_regmap_field_alloc(dev, regmap,
+ *gmac->variant->syscon_field);
+ if (IS_ERR(gmac->regmap_field)) {
+ ret = PTR_ERR(gmac->regmap_field);
+ dev_err(dev, "Unable to map syscon register: %d\n", ret);
+ return ret;
+ }
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ /* platform data specifying hardware features and callbacks.
+ * hardware features were copied from Allwinner drivers.
+ */
+ plat_dat->rx_coe = STMMAC_RX_COE_TYPE2;
+ plat_dat->tx_coe = true;
+ plat_dat->flags |= STMMAC_FLAG_HAS_SUN8I;
+ plat_dat->bsp_priv = gmac;
+ plat_dat->init = sun8i_dwmac_init;
+ plat_dat->exit = sun8i_dwmac_exit;
+ plat_dat->mac_setup = sun8i_dwmac_setup;
+ plat_dat->tx_fifo_size = 4096;
+ plat_dat->rx_fifo_size = 16384;
+
+ ret = sun8i_dwmac_set_syscon(&pdev->dev, plat_dat);
+ if (ret)
+ return ret;
+
+ ret = stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+ if (ret)
+ goto dwmac_syscon;
+
+ ndev = dev_get_drvdata(&pdev->dev);
+ priv = netdev_priv(ndev);
+
+ /* the MAC is runtime suspended after stmmac_dvr_probe(), so we
+ * need to ensure the MAC resume back before other operations such
+ * as reset.
+ */
+ pm_runtime_get_sync(&pdev->dev);
+
+ /* The mux must be registered after parent MDIO
+ * so after stmmac_dvr_probe()
+ */
+ if (gmac->variant->soc_has_internal_phy) {
+ ret = get_ephy_nodes(priv);
+ if (ret)
+ goto dwmac_remove;
+ ret = sun8i_dwmac_register_mdio_mux(priv);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register mux\n");
+ goto dwmac_mux;
+ }
+ } else {
+ ret = sun8i_dwmac_reset(priv);
+ if (ret)
+ goto dwmac_remove;
+ }
+
+ pm_runtime_put(&pdev->dev);
+
+ return 0;
+
+dwmac_mux:
+ reset_control_put(gmac->rst_ephy);
+ clk_put(gmac->ephy_clk);
+dwmac_remove:
+ pm_runtime_put_noidle(&pdev->dev);
+ stmmac_pltfr_remove(pdev);
+dwmac_syscon:
+ sun8i_dwmac_unset_syscon(gmac);
+
+ return ret;
+}
+
+static void sun8i_dwmac_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
+
+ if (gmac->variant->soc_has_internal_phy) {
+ mdio_mux_uninit(gmac->mux_handle);
+ sun8i_dwmac_unpower_internal_phy(gmac);
+ reset_control_put(gmac->rst_ephy);
+ clk_put(gmac->ephy_clk);
+ }
+
+ stmmac_pltfr_remove(pdev);
+ sun8i_dwmac_unset_syscon(gmac);
+}
+
+static void sun8i_dwmac_shutdown(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
+
+ sun8i_dwmac_exit(&pdev->dev, gmac);
+}
+
+static const struct of_device_id sun8i_dwmac_match[] = {
+ { .compatible = "allwinner,sun8i-h3-emac",
+ .data = &emac_variant_h3 },
+ { .compatible = "allwinner,sun8i-v3s-emac",
+ .data = &emac_variant_v3s },
+ { .compatible = "allwinner,sun8i-a83t-emac",
+ .data = &emac_variant_a83t },
+ { .compatible = "allwinner,sun8i-r40-gmac",
+ .data = &emac_variant_r40 },
+ { .compatible = "allwinner,sun50i-a64-emac",
+ .data = &emac_variant_a64 },
+ { .compatible = "allwinner,sun50i-h6-emac",
+ .data = &emac_variant_h6 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sun8i_dwmac_match);
+
+static struct platform_driver sun8i_dwmac_driver = {
+ .probe = sun8i_dwmac_probe,
+ .remove = sun8i_dwmac_remove,
+ .shutdown = sun8i_dwmac_shutdown,
+ .driver = {
+ .name = "dwmac-sun8i",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = sun8i_dwmac_match,
+ },
+};
+module_platform_driver(sun8i_dwmac_driver);
+
+MODULE_AUTHOR("Corentin Labbe <clabbe.montjoie@gmail.com>");
+MODULE_DESCRIPTION("Allwinner sun8i DWMAC specific glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c
new file mode 100644
index 000000000..74bd996d9
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * dwmac-sunxi.c - Allwinner sunxi DWMAC specific glue layer
+ *
+ * Copyright (C) 2013 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ */
+
+#include <linux/stmmac.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/of_net.h>
+#include <linux/regulator/consumer.h>
+
+#include "stmmac_platform.h"
+
+struct sunxi_priv_data {
+ phy_interface_t interface;
+ int clk_enabled;
+ struct clk *tx_clk;
+ struct regulator *regulator;
+};
+
+#define SUN7I_GMAC_GMII_RGMII_RATE 125000000
+#define SUN7I_GMAC_MII_RATE 25000000
+
+static int sun7i_gmac_init(struct device *dev, void *priv)
+{
+ struct sunxi_priv_data *gmac = priv;
+ int ret = 0;
+
+ if (gmac->regulator) {
+ ret = regulator_enable(gmac->regulator);
+ if (ret)
+ return ret;
+ }
+
+ /* Set GMAC interface port mode
+ *
+ * The GMAC TX clock lines are configured by setting the clock
+ * rate, which then uses the auto-reparenting feature of the
+ * clock driver, and enabling/disabling the clock.
+ */
+ if (phy_interface_mode_is_rgmii(gmac->interface)) {
+ clk_set_rate(gmac->tx_clk, SUN7I_GMAC_GMII_RGMII_RATE);
+ clk_prepare_enable(gmac->tx_clk);
+ gmac->clk_enabled = 1;
+ } else {
+ clk_set_rate(gmac->tx_clk, SUN7I_GMAC_MII_RATE);
+ ret = clk_prepare(gmac->tx_clk);
+ if (ret && gmac->regulator)
+ regulator_disable(gmac->regulator);
+ }
+
+ return ret;
+}
+
+static void sun7i_gmac_exit(struct device *dev, void *priv)
+{
+ struct sunxi_priv_data *gmac = priv;
+
+ if (gmac->clk_enabled) {
+ clk_disable(gmac->tx_clk);
+ gmac->clk_enabled = 0;
+ }
+ clk_unprepare(gmac->tx_clk);
+
+ if (gmac->regulator)
+ regulator_disable(gmac->regulator);
+}
+
+static int sun7i_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct sunxi_priv_data *gmac = bsp_priv;
+
+ if (interface == PHY_INTERFACE_MODE_GMII) {
+ if (gmac->clk_enabled) {
+ clk_disable(gmac->tx_clk);
+ gmac->clk_enabled = 0;
+ }
+ clk_unprepare(gmac->tx_clk);
+
+ if (speed == 1000) {
+ clk_set_rate(gmac->tx_clk, SUN7I_GMAC_GMII_RGMII_RATE);
+ clk_prepare_enable(gmac->tx_clk);
+ gmac->clk_enabled = 1;
+ } else {
+ clk_set_rate(gmac->tx_clk, SUN7I_GMAC_MII_RATE);
+ clk_prepare(gmac->tx_clk);
+ }
+ }
+ return 0;
+}
+
+static int sun7i_gmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct sunxi_priv_data *gmac;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL);
+ if (!gmac)
+ return -ENOMEM;
+
+ gmac->interface = plat_dat->phy_interface;
+
+ gmac->tx_clk = devm_clk_get(dev, "allwinner_gmac_tx");
+ if (IS_ERR(gmac->tx_clk)) {
+ dev_err(dev, "could not get tx clock\n");
+ return PTR_ERR(gmac->tx_clk);
+ }
+
+ /* Optional regulator for PHY */
+ gmac->regulator = devm_regulator_get_optional(dev, "phy");
+ if (IS_ERR(gmac->regulator)) {
+ if (PTR_ERR(gmac->regulator) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_info(dev, "no regulator found\n");
+ gmac->regulator = NULL;
+ }
+
+ /* platform data specifying hardware features and callbacks.
+ * hardware features were copied from Allwinner drivers. */
+ plat_dat->tx_coe = true;
+ plat_dat->core_type = DWMAC_CORE_GMAC;
+ plat_dat->bsp_priv = gmac;
+ plat_dat->init = sun7i_gmac_init;
+ plat_dat->exit = sun7i_gmac_exit;
+ plat_dat->set_clk_tx_rate = sun7i_set_clk_tx_rate;
+ plat_dat->tx_fifo_size = 4096;
+ plat_dat->rx_fifo_size = 16384;
+
+ return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
+}
+
+static const struct of_device_id sun7i_dwmac_match[] = {
+ { .compatible = "allwinner,sun7i-a20-gmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sun7i_dwmac_match);
+
+static struct platform_driver sun7i_dwmac_driver = {
+ .probe = sun7i_gmac_probe,
+ .driver = {
+ .name = "sun7i-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = sun7i_dwmac_match,
+ },
+};
+module_platform_driver(sun7i_dwmac_driver);
+
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
+MODULE_DESCRIPTION("Allwinner sunxi DWMAC specific glue layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
new file mode 100644
index 000000000..4ede4420c
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/iommu.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/stmmac.h>
+#include <linux/clk.h>
+
+#include "stmmac_platform.h"
+
+static const char *const mgbe_clks[] = {
+ "rx-pcs", "tx", "tx-pcs", "mac-divider", "mac", "mgbe", "ptp_ref", "mac"
+};
+
+struct tegra_mgbe {
+ struct device *dev;
+
+ struct clk_bulk_data *clks;
+
+ struct reset_control *rst_mac;
+ struct reset_control *rst_pcs;
+
+ u32 iommu_sid;
+
+ void __iomem *hv;
+ void __iomem *regs;
+ void __iomem *xpcs;
+
+ struct mii_bus *mii;
+};
+
+#define XPCS_WRAP_UPHY_RX_CONTROL 0x801c
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD BIT(31)
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY BIT(10)
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET BIT(9)
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN BIT(8)
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP (BIT(7) | BIT(6))
+#define XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ BIT(5)
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ BIT(4)
+#define XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN BIT(0)
+#define XPCS_WRAP_UPHY_HW_INIT_CTRL 0x8020
+#define XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN BIT(0)
+#define XPCS_WRAP_UPHY_HW_INIT_CTRL_RX_EN BIT(2)
+#define XPCS_WRAP_UPHY_STATUS 0x8044
+#define XPCS_WRAP_UPHY_STATUS_TX_P_UP BIT(0)
+#define XPCS_WRAP_IRQ_STATUS 0x8050
+#define XPCS_WRAP_IRQ_STATUS_PCS_LINK_STS BIT(6)
+
+#define XPCS_REG_ADDR_SHIFT 10
+#define XPCS_REG_ADDR_MASK 0x1fff
+#define XPCS_ADDR 0x3fc
+
+#define MGBE_WRAP_COMMON_INTR_ENABLE 0x8704
+#define MAC_SBD_INTR BIT(2)
+#define MGBE_WRAP_AXI_ASID0_CTRL 0x8400
+
+static int __maybe_unused tegra_mgbe_suspend(struct device *dev)
+{
+ struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev);
+ int err;
+
+ err = stmmac_suspend(dev);
+ if (err)
+ return err;
+
+ clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+
+ return reset_control_assert(mgbe->rst_mac);
+}
+
+static int __maybe_unused tegra_mgbe_resume(struct device *dev)
+{
+ struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(dev);
+ u32 value;
+ int err;
+
+ err = clk_bulk_prepare_enable(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+ if (err < 0)
+ return err;
+
+ err = reset_control_deassert(mgbe->rst_mac);
+ if (err < 0)
+ return err;
+
+ /* Enable common interrupt at wrapper level */
+ writel(MAC_SBD_INTR, mgbe->regs + MGBE_WRAP_COMMON_INTR_ENABLE);
+
+ /* Program SID */
+ writel(mgbe->iommu_sid, mgbe->hv + MGBE_WRAP_AXI_ASID0_CTRL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_STATUS);
+ if ((value & XPCS_WRAP_UPHY_STATUS_TX_P_UP) == 0) {
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
+ value |= XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
+ }
+
+ err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL, value,
+ (value & XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN) == 0,
+ 500, 500 * 2000);
+ if (err < 0) {
+ dev_err(mgbe->dev, "timeout waiting for TX lane to become enabled\n");
+ clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+ return err;
+ }
+
+ err = stmmac_resume(dev);
+ if (err < 0)
+ clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+
+ return err;
+}
+
+static int mgbe_uphy_lane_bringup_serdes_up(struct net_device *ndev, void *mgbe_data)
+{
+ struct tegra_mgbe *mgbe = (struct tegra_mgbe *)mgbe_data;
+ u32 value;
+ int err;
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value &= ~XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ usleep_range(10, 20); /* 50ns min delay needed as per HW design */
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ usleep_range(10, 20); /* 500ns min delay needed as per HW design */
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL, value,
+ (value & XPCS_WRAP_UPHY_RX_CONTROL_RX_CAL_EN) == 0,
+ 1000, 1000 * 2000);
+ if (err < 0) {
+ dev_err(mgbe->dev, "timeout waiting for RX calibration to become enabled\n");
+ return err;
+ }
+
+ usleep_range(10, 20); /* 50ns min delay needed as per HW design */
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ usleep_range(10, 20); /* 50ns min delay needed as per HW design */
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ usleep_range(10, 20); /* 50ns min delay needed as per HW design */
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_PCS_PHY_RDY;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ msleep(30); /* 30ms delay needed as per HW design */
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_CDR_RESET;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_IRQ_STATUS, value,
+ value & XPCS_WRAP_IRQ_STATUS_PCS_LINK_STS,
+ 500, 500 * 2000);
+ if (err < 0) {
+ dev_err(mgbe->dev, "timeout waiting for link to become ready\n");
+ return err;
+ }
+
+ /* clear status */
+ writel(value, mgbe->xpcs + XPCS_WRAP_IRQ_STATUS);
+
+ return 0;
+}
+
+static void mgbe_uphy_lane_bringup_serdes_down(struct net_device *ndev, void *mgbe_data)
+{
+ struct tegra_mgbe *mgbe = (struct tegra_mgbe *)mgbe_data;
+ u32 value;
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SW_OVRD;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value &= ~XPCS_WRAP_UPHY_RX_CONTROL_RX_DATA_EN;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_SLEEP;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_AUX_RX_IDDQ;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+ value |= XPCS_WRAP_UPHY_RX_CONTROL_RX_IDDQ;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_RX_CONTROL);
+}
+
+static int tegra_mgbe_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat;
+ struct stmmac_resources res;
+ bool use_legacy_ptp = false;
+ struct tegra_mgbe *mgbe;
+ int irq, err, i;
+ u32 value;
+
+ mgbe = devm_kzalloc(&pdev->dev, sizeof(*mgbe), GFP_KERNEL);
+ if (!mgbe)
+ return -ENOMEM;
+
+ mgbe->dev = &pdev->dev;
+
+ memset(&res, 0, sizeof(res));
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ mgbe->hv = devm_platform_ioremap_resource_byname(pdev, "hypervisor");
+ if (IS_ERR(mgbe->hv))
+ return PTR_ERR(mgbe->hv);
+
+ mgbe->regs = devm_platform_ioremap_resource_byname(pdev, "mac");
+ if (IS_ERR(mgbe->regs))
+ return PTR_ERR(mgbe->regs);
+
+ mgbe->xpcs = devm_platform_ioremap_resource_byname(pdev, "xpcs");
+ if (IS_ERR(mgbe->xpcs))
+ return PTR_ERR(mgbe->xpcs);
+
+ /* get controller's stream id from iommu property in device tree */
+ if (!tegra_dev_iommu_get_stream_id(mgbe->dev, &mgbe->iommu_sid)) {
+ dev_err(mgbe->dev, "failed to get iommu stream id\n");
+ return -EINVAL;
+ }
+
+ res.addr = mgbe->regs;
+ res.irq = irq;
+
+ mgbe->clks = devm_kcalloc(&pdev->dev, ARRAY_SIZE(mgbe_clks),
+ sizeof(*mgbe->clks), GFP_KERNEL);
+ if (!mgbe->clks)
+ return -ENOMEM;
+
+ /* Older device-trees use 'ptp-ref' rather than 'ptp_ref'.
+ * Fall back when the legacy name is present.
+ */
+ if (of_property_match_string(pdev->dev.of_node, "clock-names",
+ "ptp-ref") >= 0)
+ use_legacy_ptp = true;
+
+ for (i = 0; i < ARRAY_SIZE(mgbe_clks); i++) {
+ mgbe->clks[i].id = mgbe_clks[i];
+
+ if (use_legacy_ptp && !strcmp(mgbe_clks[i], "ptp_ref")) {
+ dev_warn(mgbe->dev,
+ "Device-tree update needed for PTP clock!\n");
+ mgbe->clks[i].id = "ptp-ref";
+ }
+ }
+
+ err = devm_clk_bulk_get(mgbe->dev, ARRAY_SIZE(mgbe_clks), mgbe->clks);
+ if (err < 0)
+ return err;
+
+ err = clk_bulk_prepare_enable(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+ if (err < 0)
+ return err;
+
+ /* Perform MAC reset */
+ mgbe->rst_mac = devm_reset_control_get(&pdev->dev, "mac");
+ if (IS_ERR(mgbe->rst_mac)) {
+ err = PTR_ERR(mgbe->rst_mac);
+ goto disable_clks;
+ }
+
+ err = reset_control_assert(mgbe->rst_mac);
+ if (err < 0)
+ goto disable_clks;
+
+ usleep_range(2000, 4000);
+
+ err = reset_control_deassert(mgbe->rst_mac);
+ if (err < 0)
+ goto disable_clks;
+
+ /* Perform PCS reset */
+ mgbe->rst_pcs = devm_reset_control_get(&pdev->dev, "pcs");
+ if (IS_ERR(mgbe->rst_pcs)) {
+ err = PTR_ERR(mgbe->rst_pcs);
+ goto disable_clks;
+ }
+
+ err = reset_control_assert(mgbe->rst_pcs);
+ if (err < 0)
+ goto disable_clks;
+
+ usleep_range(2000, 4000);
+
+ err = reset_control_deassert(mgbe->rst_pcs);
+ if (err < 0)
+ goto disable_clks;
+
+ plat = devm_stmmac_probe_config_dt(pdev, res.mac);
+ if (IS_ERR(plat)) {
+ err = PTR_ERR(plat);
+ goto disable_clks;
+ }
+
+ plat->core_type = DWMAC_CORE_XGMAC;
+ plat->flags |= STMMAC_FLAG_TSO_EN;
+ plat->pmt = true;
+ plat->bsp_priv = mgbe;
+
+ if (!plat->mdio_node)
+ plat->mdio_node = of_get_child_by_name(pdev->dev.of_node, "mdio");
+
+ if (!plat->mdio_bus_data) {
+ plat->mdio_bus_data = devm_kzalloc(&pdev->dev, sizeof(*plat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat->mdio_bus_data) {
+ err = -ENOMEM;
+ goto disable_clks;
+ }
+ }
+
+ plat->mdio_bus_data->needs_reset = true;
+
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_STATUS);
+ if ((value & XPCS_WRAP_UPHY_STATUS_TX_P_UP) == 0) {
+ value = readl(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
+ value |= XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN;
+ writel(value, mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL);
+ }
+
+ err = readl_poll_timeout(mgbe->xpcs + XPCS_WRAP_UPHY_HW_INIT_CTRL, value,
+ (value & XPCS_WRAP_UPHY_HW_INIT_CTRL_TX_EN) == 0,
+ 500, 500 * 2000);
+ if (err < 0) {
+ dev_err(mgbe->dev, "timeout waiting for TX lane to become enabled\n");
+ goto disable_clks;
+ }
+
+ plat->serdes_powerup = mgbe_uphy_lane_bringup_serdes_up;
+ plat->serdes_powerdown = mgbe_uphy_lane_bringup_serdes_down;
+
+ /* Tx FIFO Size - 128KB */
+ plat->tx_fifo_size = 131072;
+ /* Rx FIFO Size - 192KB */
+ plat->rx_fifo_size = 196608;
+
+ /* Enable common interrupt at wrapper level */
+ writel(MAC_SBD_INTR, mgbe->regs + MGBE_WRAP_COMMON_INTR_ENABLE);
+
+ /* Program SID */
+ writel(mgbe->iommu_sid, mgbe->hv + MGBE_WRAP_AXI_ASID0_CTRL);
+
+ plat->flags |= STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP;
+
+ err = stmmac_dvr_probe(&pdev->dev, plat, &res);
+ if (err < 0)
+ goto disable_clks;
+
+ return 0;
+
+disable_clks:
+ clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+
+ return err;
+}
+
+static void tegra_mgbe_remove(struct platform_device *pdev)
+{
+ struct tegra_mgbe *mgbe = get_stmmac_bsp_priv(&pdev->dev);
+
+ clk_bulk_disable_unprepare(ARRAY_SIZE(mgbe_clks), mgbe->clks);
+
+ stmmac_pltfr_remove(pdev);
+}
+
+static const struct of_device_id tegra_mgbe_match[] = {
+ { .compatible = "nvidia,tegra234-mgbe", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tegra_mgbe_match);
+
+static SIMPLE_DEV_PM_OPS(tegra_mgbe_pm_ops, tegra_mgbe_suspend, tegra_mgbe_resume);
+
+static struct platform_driver tegra_mgbe_driver = {
+ .probe = tegra_mgbe_probe,
+ .remove = tegra_mgbe_remove,
+ .driver = {
+ .name = "tegra-mgbe",
+ .pm = &tegra_mgbe_pm_ops,
+ .of_match_table = tegra_mgbe_match,
+ },
+};
+module_platform_driver(tegra_mgbe_driver);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra MGBE driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
new file mode 100644
index 000000000..0d46a6c3f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-HEAD DWMAC platform driver
+ *
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ * Copyright (C) 2023 Jisheng Zhang <jszhang@kernel.org>
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_net.h>
+#include <linux/platform_device.h>
+
+#include "stmmac_platform.h"
+
+#define GMAC_CLK_EN 0x00
+#define GMAC_TX_CLK_EN BIT(1)
+#define GMAC_TX_CLK_N_EN BIT(2)
+#define GMAC_TX_CLK_OUT_EN BIT(3)
+#define GMAC_RX_CLK_EN BIT(4)
+#define GMAC_RX_CLK_N_EN BIT(5)
+#define GMAC_EPHY_REF_CLK_EN BIT(6)
+#define GMAC_RXCLK_DELAY_CTRL 0x04
+#define GMAC_RXCLK_BYPASS BIT(15)
+#define GMAC_RXCLK_INVERT BIT(14)
+#define GMAC_RXCLK_DELAY GENMASK(4, 0)
+#define GMAC_TXCLK_DELAY_CTRL 0x08
+#define GMAC_TXCLK_BYPASS BIT(15)
+#define GMAC_TXCLK_INVERT BIT(14)
+#define GMAC_TXCLK_DELAY GENMASK(4, 0)
+#define GMAC_PLLCLK_DIV 0x0c
+#define GMAC_PLLCLK_DIV_EN BIT(31)
+#define GMAC_PLLCLK_DIV_NUM GENMASK(7, 0)
+#define GMAC_GTXCLK_SEL 0x18
+#define GMAC_GTXCLK_SEL_PLL BIT(0)
+#define GMAC_INTF_CTRL 0x1c
+#define GMAC_INTF_MASK BIT(0)
+#define GMAC_INTF_RGMII FIELD_PREP(GMAC_INTF_MASK, 1)
+#define GMAC_INTF_MII_GMII FIELD_PREP(GMAC_INTF_MASK, 0)
+#define GMAC_TXCLK_OEN 0x20
+#define TXCLK_DIR_MASK BIT(0)
+#define TXCLK_DIR_OUTPUT FIELD_PREP(TXCLK_DIR_MASK, 0)
+#define TXCLK_DIR_INPUT FIELD_PREP(TXCLK_DIR_MASK, 1)
+
+struct thead_dwmac {
+ struct plat_stmmacenet_data *plat;
+ void __iomem *apb_base;
+ struct device *dev;
+};
+
+static int thead_dwmac_set_phy_if(struct plat_stmmacenet_data *plat)
+{
+ struct thead_dwmac *dwmac = plat->bsp_priv;
+ u32 phyif;
+
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ phyif = GMAC_INTF_MII_GMII;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ phyif = GMAC_INTF_RGMII;
+ break;
+ default:
+ dev_err(dwmac->dev, "unsupported phy interface %s\n",
+ phy_modes(plat->phy_interface));
+ return -EINVAL;
+ }
+
+ writel(phyif, dwmac->apb_base + GMAC_INTF_CTRL);
+ return 0;
+}
+
+static int thead_dwmac_set_txclk_dir(struct plat_stmmacenet_data *plat)
+{
+ struct thead_dwmac *dwmac = plat->bsp_priv;
+ u32 txclk_dir;
+
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ txclk_dir = TXCLK_DIR_INPUT;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ txclk_dir = TXCLK_DIR_OUTPUT;
+ break;
+ default:
+ dev_err(dwmac->dev, "unsupported phy interface %s\n",
+ phy_modes(plat->phy_interface));
+ return -EINVAL;
+ }
+
+ writel(txclk_dir, dwmac->apb_base + GMAC_TXCLK_OEN);
+ return 0;
+}
+
+static int thead_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct thead_dwmac *dwmac = bsp_priv;
+ struct plat_stmmacenet_data *plat;
+ unsigned long rate;
+ long tx_rate;
+ u32 div, reg;
+
+ plat = dwmac->plat;
+
+ switch (plat->phy_interface) {
+ /* For MII, rxc/txc is provided by phy */
+ case PHY_INTERFACE_MODE_MII:
+ return 0;
+
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ rate = clk_get_rate(plat->stmmac_clk);
+
+ writel(0, dwmac->apb_base + GMAC_PLLCLK_DIV);
+
+ tx_rate = rgmii_clock(speed);
+ if (tx_rate < 0) {
+ dev_err(dwmac->dev, "invalid speed %d\n", speed);
+ return tx_rate;
+ }
+
+ div = rate / tx_rate;
+ if (rate != tx_rate * div) {
+ dev_err(dwmac->dev, "invalid gmac rate %lu\n", rate);
+ return -EINVAL;
+ }
+
+ reg = FIELD_PREP(GMAC_PLLCLK_DIV_EN, 1) |
+ FIELD_PREP(GMAC_PLLCLK_DIV_NUM, div);
+ writel(reg, dwmac->apb_base + GMAC_PLLCLK_DIV);
+ return 0;
+
+ default:
+ dev_err(dwmac->dev, "unsupported phy interface %s\n",
+ phy_modes(plat->phy_interface));
+ return -EINVAL;
+ }
+}
+
+static int thead_dwmac_enable_clk(struct plat_stmmacenet_data *plat)
+{
+ struct thead_dwmac *dwmac = plat->bsp_priv;
+ u32 reg, div;
+
+ switch (plat->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ reg = GMAC_RX_CLK_EN | GMAC_TX_CLK_EN;
+ break;
+
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ /* use pll */
+ div = clk_get_rate(plat->stmmac_clk) / rgmii_clock(SPEED_1000);
+ reg = FIELD_PREP(GMAC_PLLCLK_DIV_EN, 1) |
+ FIELD_PREP(GMAC_PLLCLK_DIV_NUM, div);
+
+ writel(0, dwmac->apb_base + GMAC_PLLCLK_DIV);
+ writel(reg, dwmac->apb_base + GMAC_PLLCLK_DIV);
+
+ writel(GMAC_GTXCLK_SEL_PLL, dwmac->apb_base + GMAC_GTXCLK_SEL);
+ reg = GMAC_TX_CLK_EN | GMAC_TX_CLK_N_EN | GMAC_TX_CLK_OUT_EN |
+ GMAC_RX_CLK_EN | GMAC_RX_CLK_N_EN;
+ break;
+
+ default:
+ dev_err(dwmac->dev, "unsupported phy interface %s\n",
+ phy_modes(plat->phy_interface));
+ return -EINVAL;
+ }
+
+ writel(reg, dwmac->apb_base + GMAC_CLK_EN);
+ return 0;
+}
+
+static int thead_dwmac_init(struct device *dev, void *priv)
+{
+ struct thead_dwmac *dwmac = priv;
+ unsigned int reg;
+ int ret;
+
+ ret = thead_dwmac_set_phy_if(dwmac->plat);
+ if (ret)
+ return ret;
+
+ ret = thead_dwmac_set_txclk_dir(dwmac->plat);
+ if (ret)
+ return ret;
+
+ reg = readl(dwmac->apb_base + GMAC_RXCLK_DELAY_CTRL);
+ reg &= ~(GMAC_RXCLK_DELAY);
+ reg |= FIELD_PREP(GMAC_RXCLK_DELAY, 0);
+ writel(reg, dwmac->apb_base + GMAC_RXCLK_DELAY_CTRL);
+
+ reg = readl(dwmac->apb_base + GMAC_TXCLK_DELAY_CTRL);
+ reg &= ~(GMAC_TXCLK_DELAY);
+ reg |= FIELD_PREP(GMAC_TXCLK_DELAY, 0);
+ writel(reg, dwmac->apb_base + GMAC_TXCLK_DELAY_CTRL);
+
+ return thead_dwmac_enable_clk(dwmac->plat);
+}
+
+static int thead_dwmac_probe(struct platform_device *pdev)
+{
+ struct stmmac_resources stmmac_res;
+ struct plat_stmmacenet_data *plat;
+ struct thead_dwmac *dwmac;
+ struct clk *apb_clk;
+ void __iomem *apb;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to get resources\n");
+
+ plat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat))
+ return dev_err_probe(&pdev->dev, PTR_ERR(plat),
+ "dt configuration failed\n");
+
+ /*
+ * The APB clock is essential for accessing glue registers. However,
+ * old devicetrees don't describe it correctly. We continue to probe
+ * and emit a warning if it isn't present.
+ */
+ apb_clk = devm_clk_get_enabled(&pdev->dev, "apb");
+ if (PTR_ERR(apb_clk) == -ENOENT)
+ dev_warn(&pdev->dev,
+ "cannot get apb clock, link may break after speed changes\n");
+ else if (IS_ERR(apb_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk),
+ "failed to get apb clock\n");
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ apb = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(apb))
+ return dev_err_probe(&pdev->dev, PTR_ERR(apb),
+ "failed to remap gmac apb registers\n");
+
+ dwmac->dev = &pdev->dev;
+ dwmac->plat = plat;
+ dwmac->apb_base = apb;
+ plat->bsp_priv = dwmac;
+ plat->set_clk_tx_rate = thead_set_clk_tx_rate;
+ plat->init = thead_dwmac_init;
+
+ return devm_stmmac_pltfr_probe(pdev, plat, &stmmac_res);
+}
+
+static const struct of_device_id thead_dwmac_match[] = {
+ { .compatible = "thead,th1520-gmac" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, thead_dwmac_match);
+
+static struct platform_driver thead_dwmac_driver = {
+ .probe = thead_dwmac_probe,
+ .driver = {
+ .name = "thead-dwmac",
+ .pm = &stmmac_pltfr_pm_ops,
+ .of_match_table = thead_dwmac_match,
+ },
+};
+module_platform_driver(thead_dwmac_driver);
+
+MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
+MODULE_AUTHOR("Drew Fustini <drew@pdp7.com>");
+MODULE_DESCRIPTION("T-HEAD DWMAC platform driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c
new file mode 100644
index 000000000..9497b13a5
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Toshiba Visconti Ethernet Support
+ *
+ * (C) Copyright 2020 TOSHIBA CORPORATION
+ * (C) Copyright 2020 Toshiba Electronic Devices & Storage Corporation
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/of_net.h>
+#include <linux/stmmac.h>
+
+#include "stmmac_platform.h"
+#include "dwmac4.h"
+
+#define REG_ETHER_CONTROL 0x52D4
+#define ETHER_ETH_CONTROL_RESET BIT(17)
+
+#define REG_ETHER_CLOCK_SEL 0x52D0
+#define ETHER_CLK_SEL_TX_CLK_EN BIT(0)
+#define ETHER_CLK_SEL_RX_CLK_EN BIT(1)
+#define ETHER_CLK_SEL_RMII_CLK_EN BIT(2)
+#define ETHER_CLK_SEL_RMII_CLK_RST BIT(3)
+#define ETHER_CLK_SEL_DIV_SEL_2 BIT(4)
+#define ETHER_CLK_SEL_DIV_SEL_20 0
+#define ETHER_CLK_SEL_FREQ_SEL_125M (BIT(9) | BIT(8))
+#define ETHER_CLK_SEL_FREQ_SEL_50M BIT(9)
+#define ETHER_CLK_SEL_FREQ_SEL_25M BIT(8)
+#define ETHER_CLK_SEL_FREQ_SEL_2P5M 0
+#define ETHER_CLK_SEL_TX_CLK_EXT_SEL_IN 0
+#define ETHER_CLK_SEL_TX_CLK_EXT_SEL_TXC BIT(10)
+#define ETHER_CLK_SEL_TX_CLK_EXT_SEL_DIV BIT(11)
+#define ETHER_CLK_SEL_RX_CLK_EXT_SEL_IN 0
+#define ETHER_CLK_SEL_RX_CLK_EXT_SEL_RXC BIT(12)
+#define ETHER_CLK_SEL_RX_CLK_EXT_SEL_DIV BIT(13)
+#define ETHER_CLK_SEL_TX_CLK_O_TX_I 0
+#define ETHER_CLK_SEL_TX_CLK_O_RMII_I BIT(14)
+#define ETHER_CLK_SEL_TX_O_E_N_IN BIT(15)
+#define ETHER_CLK_SEL_RMII_CLK_SEL_IN 0
+#define ETHER_CLK_SEL_RMII_CLK_SEL_RX_C BIT(16)
+
+#define ETHER_CLK_SEL_RX_TX_CLK_EN (ETHER_CLK_SEL_RX_CLK_EN | ETHER_CLK_SEL_TX_CLK_EN)
+
+struct visconti_eth {
+ void __iomem *reg;
+ struct clk *phy_ref_clk;
+ struct device *dev;
+};
+
+static int visconti_eth_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ struct visconti_eth *dwmac = bsp_priv;
+ unsigned long clk_sel, val;
+
+ if (phy_interface_mode_is_rgmii(interface)) {
+ switch (speed) {
+ case SPEED_1000:
+ clk_sel = ETHER_CLK_SEL_FREQ_SEL_125M;
+ break;
+
+ case SPEED_100:
+ clk_sel = ETHER_CLK_SEL_FREQ_SEL_25M;
+ break;
+
+ case SPEED_10:
+ clk_sel = ETHER_CLK_SEL_FREQ_SEL_2P5M;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Stop internal clock */
+ val = readl(dwmac->reg + REG_ETHER_CLOCK_SEL);
+ val &= ~(ETHER_CLK_SEL_RMII_CLK_EN |
+ ETHER_CLK_SEL_RX_TX_CLK_EN);
+ val |= ETHER_CLK_SEL_TX_O_E_N_IN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ /* Set Clock-Mux, Start clock, Set TX_O direction */
+ val = clk_sel | ETHER_CLK_SEL_RX_CLK_EXT_SEL_RXC;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ val |= ETHER_CLK_SEL_RX_TX_CLK_EN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ val &= ~ETHER_CLK_SEL_TX_O_E_N_IN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+ } else if (interface == PHY_INTERFACE_MODE_RMII) {
+ switch (speed) {
+ case SPEED_100:
+ clk_sel = ETHER_CLK_SEL_DIV_SEL_2;
+ break;
+
+ case SPEED_10:
+ clk_sel = ETHER_CLK_SEL_DIV_SEL_20;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Stop internal clock */
+ val = readl(dwmac->reg + REG_ETHER_CLOCK_SEL);
+ val &= ~(ETHER_CLK_SEL_RMII_CLK_EN |
+ ETHER_CLK_SEL_RX_TX_CLK_EN);
+ val |= ETHER_CLK_SEL_TX_O_E_N_IN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ /* Set Clock-Mux, Start clock, Set TX_O direction */
+ val = clk_sel | ETHER_CLK_SEL_RX_CLK_EXT_SEL_DIV |
+ ETHER_CLK_SEL_TX_CLK_EXT_SEL_DIV |
+ ETHER_CLK_SEL_TX_O_E_N_IN |
+ ETHER_CLK_SEL_RMII_CLK_SEL_RX_C;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ val |= ETHER_CLK_SEL_RMII_CLK_RST;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ val |= ETHER_CLK_SEL_RMII_CLK_EN | ETHER_CLK_SEL_RX_TX_CLK_EN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+ } else {
+ /* Stop internal clock */
+ val = readl(dwmac->reg + REG_ETHER_CLOCK_SEL);
+ val &= ~(ETHER_CLK_SEL_RMII_CLK_EN |
+ ETHER_CLK_SEL_RX_TX_CLK_EN);
+ val |= ETHER_CLK_SEL_TX_O_E_N_IN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ /* Set Clock-Mux, Start clock, Set TX_O direction */
+ val = ETHER_CLK_SEL_RX_CLK_EXT_SEL_RXC |
+ ETHER_CLK_SEL_TX_CLK_EXT_SEL_TXC |
+ ETHER_CLK_SEL_TX_O_E_N_IN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ val |= ETHER_CLK_SEL_RX_TX_CLK_EN;
+ writel(val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+ }
+
+ return 0;
+}
+
+static int visconti_eth_init_hw(struct platform_device *pdev, struct plat_stmmacenet_data *plat_dat)
+{
+ struct visconti_eth *dwmac = plat_dat->bsp_priv;
+ unsigned int clk_sel_val;
+ int phy_intf_sel;
+
+ phy_intf_sel = stmmac_get_phy_intf_sel(plat_dat->phy_interface);
+ if (phy_intf_sel != PHY_INTF_SEL_GMII_MII &&
+ phy_intf_sel != PHY_INTF_SEL_RGMII &&
+ phy_intf_sel != PHY_INTF_SEL_RMII) {
+ dev_err(&pdev->dev, "Unsupported phy-mode (%d)\n", plat_dat->phy_interface);
+ return -EOPNOTSUPP;
+ }
+
+ writel(phy_intf_sel, dwmac->reg + REG_ETHER_CONTROL);
+
+ /* Enable TX/RX clock */
+ clk_sel_val = ETHER_CLK_SEL_FREQ_SEL_125M;
+ writel(clk_sel_val, dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ writel((clk_sel_val | ETHER_CLK_SEL_RMII_CLK_EN | ETHER_CLK_SEL_RX_TX_CLK_EN),
+ dwmac->reg + REG_ETHER_CLOCK_SEL);
+
+ /* release internal-reset */
+ phy_intf_sel |= ETHER_ETH_CONTROL_RESET;
+ writel(phy_intf_sel, dwmac->reg + REG_ETHER_CONTROL);
+
+ return 0;
+}
+
+static int visconti_eth_clock_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat_dat)
+{
+ struct visconti_eth *dwmac = plat_dat->bsp_priv;
+ int err;
+
+ dwmac->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref_clk");
+ if (IS_ERR(dwmac->phy_ref_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(dwmac->phy_ref_clk),
+ "phy_ref_clk clock not found.\n");
+
+ err = clk_prepare_enable(dwmac->phy_ref_clk);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to enable phy_ref clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static void visconti_eth_clock_remove(struct platform_device *pdev)
+{
+ struct visconti_eth *dwmac = get_stmmac_bsp_priv(&pdev->dev);
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ clk_disable_unprepare(dwmac->phy_ref_clk);
+ clk_disable_unprepare(priv->plat->stmmac_clk);
+}
+
+static int visconti_eth_dwmac_probe(struct platform_device *pdev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ struct stmmac_resources stmmac_res;
+ struct visconti_eth *dwmac;
+ int ret;
+
+ ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+ if (ret)
+ return ret;
+
+ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac);
+ if (IS_ERR(plat_dat))
+ return PTR_ERR(plat_dat);
+
+ dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+ if (!dwmac)
+ return -ENOMEM;
+
+ dwmac->reg = stmmac_res.addr;
+ dwmac->dev = &pdev->dev;
+ plat_dat->bsp_priv = dwmac;
+ plat_dat->set_clk_tx_rate = visconti_eth_set_clk_tx_rate;
+
+ ret = visconti_eth_clock_probe(pdev, plat_dat);
+ if (ret)
+ return ret;
+
+ visconti_eth_init_hw(pdev, plat_dat);
+
+ plat_dat->dma_cfg->aal = 1;
+
+ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+ if (ret)
+ goto remove;
+
+ return ret;
+
+remove:
+ visconti_eth_clock_remove(pdev);
+
+ return ret;
+}
+
+static void visconti_eth_dwmac_remove(struct platform_device *pdev)
+{
+ stmmac_pltfr_remove(pdev);
+ visconti_eth_clock_remove(pdev);
+}
+
+static const struct of_device_id visconti_eth_dwmac_match[] = {
+ { .compatible = "toshiba,visconti-dwmac" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, visconti_eth_dwmac_match);
+
+static struct platform_driver visconti_eth_dwmac_driver = {
+ .probe = visconti_eth_dwmac_probe,
+ .remove = visconti_eth_dwmac_remove,
+ .driver = {
+ .name = "visconti-eth-dwmac",
+ .of_match_table = visconti_eth_dwmac_match,
+ },
+};
+module_platform_driver(visconti_eth_dwmac_driver);
+
+MODULE_AUTHOR("Toshiba");
+MODULE_DESCRIPTION("Toshiba Visconti Ethernet DWMAC glue driver");
+MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100.h b/drivers/net/ethernet/stmicro/stmmac/dwmac100.h
new file mode 100644
index 000000000..547863cb9
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ MAC 10/100 Header File
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __DWMAC100_H__
+#define __DWMAC100_H__
+
+#include <linux/phy.h>
+#include "common.h"
+
+/*----------------------------------------------------------------------------
+ * MAC BLOCK defines
+ *---------------------------------------------------------------------------*/
+/* MAC CSR offset */
+#define MAC_CONTROL 0x00000000 /* MAC Control */
+#define MAC_ADDR_HIGH 0x00000004 /* MAC Address High */
+#define MAC_ADDR_LOW 0x00000008 /* MAC Address Low */
+#define MAC_HASH_HIGH 0x0000000c /* Multicast Hash Table High */
+#define MAC_HASH_LOW 0x00000010 /* Multicast Hash Table Low */
+#define MAC_MII_ADDR 0x00000014 /* MII Address */
+#define MAC_MII_DATA 0x00000018 /* MII Data */
+#define MAC_FLOW_CTRL 0x0000001c /* Flow Control */
+#define MAC_VLAN1 0x00000020 /* VLAN1 Tag */
+#define MAC_VLAN2 0x00000024 /* VLAN2 Tag */
+
+/* MAC CTRL defines */
+#define MAC_CONTROL_HBD 0x10000000 /* Heartbeat Disable */
+#define MAC_CONTROL_PS 0x08000000 /* Port Select */
+#define MAC_CONTROL_OM 0x00200000 /* Loopback Operating Mode */
+#define MAC_CONTROL_F 0x00100000 /* Full Duplex Mode */
+#define MAC_CONTROL_PM 0x00080000 /* Pass All Multicast */
+#define MAC_CONTROL_PR 0x00040000 /* Promiscuous Mode */
+#define MAC_CONTROL_IF 0x00020000 /* Inverse Filtering */
+#define MAC_CONTROL_HO 0x00008000 /* Hash Only Filtering Mode */
+#define MAC_CONTROL_HP 0x00002000 /* Hash/Perfect Filtering Mode */
+
+#define MAC_CORE_INIT (MAC_CONTROL_HBD)
+
+/* MAC FLOW CTRL defines */
+#define MAC_FLOW_CTRL_PT_MASK GENMASK(31, 16) /* Pause Time Mask */
+#define MAC_FLOW_CTRL_ENABLE 0x00000002 /* Flow Control Enable */
+
+/*----------------------------------------------------------------------------
+ * DMA BLOCK defines
+ *---------------------------------------------------------------------------*/
+
+/* DMA Bus Mode register defines */
+#define DMA_BUS_MODE_PBL_MASK GENMASK(13, 8) /* Programmable Burst Len */
+#define DMA_BUS_MODE_DEFAULT 0x00000000
+
+/* Transmit Threshold Control */
+enum ttc_control {
+ DMA_CONTROL_TTC_DEFAULT = 0x00000000, /* Threshold is 32 DWORDS */
+ DMA_CONTROL_TTC_64 = 0x00004000, /* Threshold is 64 DWORDS */
+ DMA_CONTROL_TTC_128 = 0x00008000, /* Threshold is 128 DWORDS */
+ DMA_CONTROL_TTC_256 = 0x0000c000, /* Threshold is 256 DWORDS */
+ DMA_CONTROL_TTC_18 = 0x00400000, /* Threshold is 18 DWORDS */
+ DMA_CONTROL_TTC_24 = 0x00404000, /* Threshold is 24 DWORDS */
+ DMA_CONTROL_TTC_32 = 0x00408000, /* Threshold is 32 DWORDS */
+ DMA_CONTROL_TTC_40 = 0x0040c000, /* Threshold is 40 DWORDS */
+ DMA_CONTROL_SE = 0x00000008, /* Stop On Empty */
+ DMA_CONTROL_OSF = 0x00000004, /* Operate On 2nd Frame */
+};
+
+/* STMAC110 DMA Missed Frame Counter register defines */
+#define DMA_MISSED_FRAME_OVE 0x10000000 /* FIFO Overflow Overflow */
+#define DMA_MISSED_FRAME_OVE_CNTR 0x0ffe0000 /* Overflow Frame Counter */
+#define DMA_MISSED_FRAME_OVE_M 0x00010000 /* Missed Frame Overflow */
+#define DMA_MISSED_FRAME_M_CNTR 0x0000ffff /* Missed Frame Couinter */
+
+extern const struct stmmac_dma_ops dwmac100_dma_ops;
+
+#endif /* __DWMAC100_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h
new file mode 100644
index 000000000..1de1f929d
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h
@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+#ifndef __DWMAC1000_H__
+#define __DWMAC1000_H__
+
+#include <linux/phy.h>
+#include "common.h"
+
+#define GMAC_CONTROL 0x00000000 /* Configuration */
+#define GMAC_FRAME_FILTER 0x00000004 /* Frame Filter */
+#define GMAC_HASH_HIGH 0x00000008 /* Multicast Hash Table High */
+#define GMAC_HASH_LOW 0x0000000c /* Multicast Hash Table Low */
+#define GMAC_MII_ADDR 0x00000010 /* MII Address */
+#define GMAC_MII_DATA 0x00000014 /* MII Data */
+#define GMAC_FLOW_CTRL 0x00000018 /* Flow Control */
+#define GMAC_VLAN_TAG 0x0000001c /* VLAN Tag */
+#define GMAC_DEBUG 0x00000024 /* GMAC debug register */
+
+#define GMAC_INT_STATUS 0x00000038 /* interrupt status register */
+#define GMAC_INT_STATUS_MMCRIS BIT(5)
+#define GMAC_INT_STATUS_MMCTIS BIT(6)
+#define GMAC_INT_STATUS_MMCCSUM BIT(7)
+#define GMAC_INT_STATUS_LPIIS BIT(10)
+
+/* interrupt mask register */
+#define GMAC_INT_MASK 0x0000003c
+#define GMAC_INT_DISABLE_RGMII BIT(0)
+#define GMAC_INT_DISABLE_PCSLINK BIT(1)
+#define GMAC_INT_DISABLE_PCSAN BIT(2)
+#define GMAC_INT_DISABLE_PMT BIT(3)
+#define GMAC_INT_DISABLE_TIMESTAMP BIT(9)
+#define GMAC_INT_DEFAULT_MASK (GMAC_INT_DISABLE_RGMII | \
+ GMAC_INT_DISABLE_PCSLINK | \
+ GMAC_INT_DISABLE_PCSAN | \
+ GMAC_INT_DISABLE_TIMESTAMP)
+
+/* PMT Control and Status */
+#define GMAC_PMT 0x0000002c
+enum power_event {
+ pointer_reset = 0x80000000,
+ global_unicast = 0x00000200,
+ wake_up_rx_frame = 0x00000040,
+ magic_frame = 0x00000020,
+ wake_up_frame_en = 0x00000004,
+ magic_pkt_en = 0x00000002,
+ power_down = 0x00000001,
+};
+
+/* Energy Efficient Ethernet (EEE)
+ *
+ * LPI status, timer and control register offset
+ * For LPI control and status bit definitions, see common.h.
+ */
+#define LPI_CTRL_STATUS 0x0030
+#define LPI_TIMER_CTRL 0x0034
+
+/* GMAC HW ADDR regs */
+#define GMAC_ADDR_HIGH(reg) ((reg > 15) ? 0x00000800 + (reg - 16) * 8 : \
+ 0x00000040 + (reg * 8))
+#define GMAC_ADDR_LOW(reg) ((reg > 15) ? 0x00000804 + (reg - 16) * 8 : \
+ 0x00000044 + (reg * 8))
+#define GMAC_MAX_PERFECT_ADDRESSES 1
+
+#define GMAC_PCS_BASE 0x000000c0 /* PCS register base */
+#define GMAC_RGSMIIIS 0x000000d8 /* RGMII/SMII status */
+
+/* SGMII/RGMII status register */
+#define GMAC_RSGMIIIS_MASK GENMASK(15, 0)
+#define GMAC_RGSMIIIS_SMIDRXS BIT(16)
+
+/* GMAC Configuration defines */
+#define GMAC_CONTROL_2K 0x08000000 /* IEEE 802.3as 2K packets */
+#define GMAC_CONTROL_JD 0x00400000 /* Jabber disable */
+#define GMAC_CONTROL_BE 0x00200000 /* Frame Burst Enable */
+#define GMAC_CONTROL_JE 0x00100000 /* Jumbo frame */
+enum inter_frame_gap {
+ GMAC_CONTROL_IFG_88 = 0x00040000,
+ GMAC_CONTROL_IFG_80 = 0x00020000,
+ GMAC_CONTROL_IFG_40 = 0x000e0000,
+};
+#define GMAC_CONTROL_DCRS 0x00010000 /* Disable carrier sense */
+#define GMAC_CONTROL_PS 0x00008000 /* Port Select 0:GMI 1:MII */
+#define GMAC_CONTROL_FES 0x00004000 /* Speed 0:10 1:100 */
+#define GMAC_CONTROL_LM 0x00001000 /* Loop-back mode */
+#define GMAC_CONTROL_DM 0x00000800 /* Duplex Mode */
+#define GMAC_CONTROL_IPC 0x00000400 /* Checksum Offload */
+
+#define GMAC_CORE_INIT (GMAC_CONTROL_JD | GMAC_CONTROL_PS | \
+ GMAC_CONTROL_BE | GMAC_CONTROL_DCRS)
+
+/* GMAC Frame Filter defines */
+#define GMAC_FRAME_FILTER_PR 0x00000001 /* Promiscuous Mode */
+#define GMAC_FRAME_FILTER_HMC 0x00000004 /* Hash Multicast */
+#define GMAC_FRAME_FILTER_PM 0x00000010 /* Pass all multicast */
+#define GMAC_FRAME_FILTER_PCF 0x00000080 /* Pass Control frames */
+#define GMAC_FRAME_FILTER_HPF 0x00000400 /* Hash or perfect Filter */
+#define GMAC_FRAME_FILTER_RA 0x80000000 /* Receive all mode */
+/* GMAC FLOW CTRL defines */
+#define GMAC_FLOW_CTRL_PT_MASK GENMASK(31, 16) /* Pause Time Mask */
+#define GMAC_FLOW_CTRL_UP 0x00000008 /* Unicast pause frame enable */
+#define GMAC_FLOW_CTRL_RFE 0x00000004 /* Rx Flow Control Enable */
+#define GMAC_FLOW_CTRL_TFE 0x00000002 /* Tx Flow Control Enable */
+
+/* DEBUG Register defines */
+/* MTL TxStatus FIFO */
+#define GMAC_DEBUG_TXSTSFSTS BIT(25) /* MTL TxStatus FIFO Full Status */
+#define GMAC_DEBUG_TXFSTS BIT(24) /* MTL Tx FIFO Not Empty Status */
+#define GMAC_DEBUG_TWCSTS BIT(22) /* MTL Tx FIFO Write Controller */
+/* MTL Tx FIFO Read Controller Status */
+#define GMAC_DEBUG_TRCSTS_MASK GENMASK(21, 20)
+#define GMAC_DEBUG_TRCSTS_READ 1
+#define GMAC_DEBUG_TRCSTS_TXW 2
+#define GMAC_DEBUG_TRCSTS_WRITE 3
+#define GMAC_DEBUG_TXPAUSED BIT(19) /* MAC Transmitter in PAUSE */
+/* MAC Transmit Frame Controller Status */
+#define GMAC_DEBUG_TFCSTS_MASK GENMASK(18, 17)
+#define GMAC_DEBUG_TFCSTS_WAIT 1
+#define GMAC_DEBUG_TFCSTS_GEN_PAUSE 2
+#define GMAC_DEBUG_TFCSTS_XFER 3
+/* MAC GMII or MII Transmit Protocol Engine Status */
+#define GMAC_DEBUG_TPESTS BIT(16)
+#define GMAC_DEBUG_RXFSTS_MASK GENMASK(9, 8) /* MTL Rx FIFO Fill-level */
+#define GMAC_DEBUG_RXFSTS_EMPTY 0
+#define GMAC_DEBUG_RXFSTS_BT 1
+#define GMAC_DEBUG_RXFSTS_AT 2
+#define GMAC_DEBUG_RXFSTS_FULL 3
+#define GMAC_DEBUG_RRCSTS_MASK GENMASK(6, 5) /* MTL Rx FIFO Read Controller */
+#define GMAC_DEBUG_RRCSTS_IDLE 0
+#define GMAC_DEBUG_RRCSTS_RDATA 1
+#define GMAC_DEBUG_RRCSTS_RSTAT 2
+#define GMAC_DEBUG_RRCSTS_FLUSH 3
+#define GMAC_DEBUG_RWCSTS BIT(4) /* MTL Rx FIFO Write Controller Active */
+/* MAC Receive Frame Controller FIFO Status */
+#define GMAC_DEBUG_RFCFCSTS_MASK GENMASK(2, 1)
+/* MAC GMII or MII Receive Protocol Engine Status */
+#define GMAC_DEBUG_RPESTS BIT(0)
+
+/*--- DMA BLOCK defines ---*/
+/* DMA Bus Mode register defines */
+/* Programmable burst length (passed through platform)*/
+#define DMA_BUS_MODE_PBL_MASK GENMASK(13, 8) /* Programmable Burst Len */
+#define DMA_BUS_MODE_ATDS 0x00000080 /* Alternate Descriptor Size */
+
+enum rx_tx_priority_ratio {
+ double_ratio = 0x00004000, /* 2:1 */
+ triple_ratio = 0x00008000, /* 3:1 */
+ quadruple_ratio = 0x0000c000, /* 4:1 */
+};
+
+#define DMA_BUS_MODE_FB 0x00010000 /* Fixed burst */
+#define DMA_BUS_MODE_MB 0x04000000 /* Mixed burst */
+#define DMA_BUS_MODE_RPBL_MASK GENMASK(22, 17) /* Rx-Programmable Burst Len */
+#define DMA_BUS_MODE_USP 0x00800000
+#define DMA_BUS_MODE_MAXPBL 0x01000000
+#define DMA_BUS_MODE_AAL 0x02000000
+
+/* DMA CRS Control and Status Register Mapping */
+
+/* DMA operation mode defines (start/stop tx/rx are placed in common header)*/
+/* Disable Drop TCP/IP csum error */
+#define DMA_CONTROL_RSF 0x02000000 /* Receive Store and Forward */
+#define DMA_CONTROL_DFF 0x01000000 /* Disaable flushing */
+/* Threshold for Activating the FC */
+enum rfa {
+ act_full_minus_1 = 0x00800000,
+ act_full_minus_2 = 0x00800200,
+ act_full_minus_3 = 0x00800400,
+ act_full_minus_4 = 0x00800600,
+};
+/* Threshold for Deactivating the FC */
+enum rfd {
+ deac_full_minus_1 = 0x00400000,
+ deac_full_minus_2 = 0x00400800,
+ deac_full_minus_3 = 0x00401000,
+ deac_full_minus_4 = 0x00401800,
+};
+#define DMA_CONTROL_TSF 0x00200000 /* Transmit Store and Forward */
+
+enum ttc_control {
+ DMA_CONTROL_TTC_64 = 0x00000000,
+ DMA_CONTROL_TTC_128 = 0x00004000,
+ DMA_CONTROL_TTC_192 = 0x00008000,
+ DMA_CONTROL_TTC_256 = 0x0000c000,
+ DMA_CONTROL_TTC_40 = 0x00010000,
+ DMA_CONTROL_TTC_32 = 0x00014000,
+ DMA_CONTROL_TTC_24 = 0x00018000,
+ DMA_CONTROL_TTC_16 = 0x0001c000,
+};
+#define DMA_CONTROL_TC_TX_MASK 0xfffe3fff
+
+#define DMA_CONTROL_EFC 0x00000100
+
+/* Receive flow control activation field
+ * RFA field in DMA control register, bits 23,10:9
+ */
+#define DMA_CONTROL_RFA_MASK 0x00800600
+
+/* Receive flow control deactivation field
+ * RFD field in DMA control register, bits 22,12:11
+ */
+#define DMA_CONTROL_RFD_MASK 0x00401800
+
+/* RFD and RFA fields are encoded as follows
+ *
+ * Bit Field
+ * 0,00 - Full minus 1KB (only valid when rxfifo >= 4KB and EFC enabled)
+ * 0,01 - Full minus 2KB (only valid when rxfifo >= 4KB and EFC enabled)
+ * 0,10 - Full minus 3KB (only valid when rxfifo >= 4KB and EFC enabled)
+ * 0,11 - Full minus 4KB (only valid when rxfifo > 4KB and EFC enabled)
+ * 1,00 - Full minus 5KB (only valid when rxfifo > 8KB and EFC enabled)
+ * 1,01 - Full minus 6KB (only valid when rxfifo > 8KB and EFC enabled)
+ * 1,10 - Full minus 7KB (only valid when rxfifo > 8KB and EFC enabled)
+ * 1,11 - Reserved
+ *
+ * RFD should always be > RFA for a given FIFO size. RFD == RFA may work,
+ * but packet throughput performance may not be as expected.
+ *
+ * Be sure that bit 3 in GMAC Register 6 is set for Unicast Pause frame
+ * detection (IEEE Specification Requirement, Annex 31B, 31B.1, Pause
+ * Description).
+ *
+ * Be sure that DZPA (bit 7 in Flow Control Register, GMAC Register 6),
+ * is set to 0. This allows pause frames with a quanta of 0 to be sent
+ * as an XOFF message to the link peer.
+ */
+
+#define RFA_FULL_MINUS_1K 0x00000000
+
+#define RFD_FULL_MINUS_2K 0x00000800
+
+enum rtc_control {
+ DMA_CONTROL_RTC_64 = 0x00000000,
+ DMA_CONTROL_RTC_32 = 0x00000008,
+ DMA_CONTROL_RTC_96 = 0x00000010,
+ DMA_CONTROL_RTC_128 = 0x00000018,
+};
+#define DMA_CONTROL_TC_RX_MASK 0xffffffe7
+
+#define DMA_CONTROL_OSF 0x00000004 /* Operate on second frame */
+
+/* MMC registers offset */
+#define GMAC_EXTHASH_BASE 0x500
+
+/* PTP and timestamping registers */
+
+#define GMAC3_X_ATSNS GENMASK(29, 25)
+
+#define GMAC_PTP_TCR_ATSFC BIT(24)
+#define GMAC_PTP_TCR_ATSEN0 BIT(25)
+
+#define GMAC3_X_TIMESTAMP_STATUS 0x28
+#define GMAC_PTP_ATNR 0x30
+#define GMAC_PTP_ATSR 0x34
+
+extern const struct stmmac_dma_ops dwmac1000_dma_ops;
+#endif /* __DWMAC1000_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
new file mode 100644
index 000000000..caac85fc0
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
@@ -0,0 +1,609 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This is the driver for the GMAC on-chip Ethernet controller for ST SoCs.
+ DWC Ether MAC 10/100/1000 Universal version 3.41a has been used for
+ developing this code.
+
+ This only implements the mac core functions for this chip.
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/crc32.h>
+#include <linux/slab.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/string_choices.h>
+#include "stmmac.h"
+#include "stmmac_pcs.h"
+#include "stmmac_ptp.h"
+#include "dwmac1000.h"
+
+static const struct stmmac_pcs_info dwmac1000_pcs_info = {
+ .pcs_offset = GMAC_PCS_BASE,
+ .rgsmii_offset = GMAC_RGSMIIIS,
+ .rgsmii_status_mask = GMAC_RSGMIIIS_MASK,
+ .int_mask = GMAC_INT_DISABLE_PCSLINK | GMAC_INT_DISABLE_PCSAN,
+};
+
+static int dwmac1000_pcs_init(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.pcs)
+ return 0;
+
+ return stmmac_integrated_pcs_init(priv, &dwmac1000_pcs_info);
+}
+
+static void dwmac1000_core_init(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int mtu = dev->mtu;
+ u32 value;
+
+ /* Configure GMAC core */
+ value = readl(ioaddr + GMAC_CONTROL);
+
+ if (mtu > 1500)
+ value |= GMAC_CONTROL_2K;
+ if (mtu > 2000)
+ value |= GMAC_CONTROL_JE;
+
+ writel(value | GMAC_CORE_INIT, ioaddr + GMAC_CONTROL);
+
+ /* Mask GMAC interrupts */
+ writel(GMAC_INT_DEFAULT_MASK, ioaddr + GMAC_INT_MASK);
+
+#ifdef STMMAC_VLAN_TAG_USED
+ /* Tag detection without filtering */
+ writel(0x0, ioaddr + GMAC_VLAN_TAG);
+#endif
+}
+
+static void dwmac1000_irq_modify(struct mac_device_info *hw, u32 disable,
+ u32 enable)
+{
+ void __iomem *int_mask = hw->pcsr + GMAC_INT_MASK;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&hw->irq_ctrl_lock, flags);
+ value = readl(int_mask) | disable;
+ value &= ~enable;
+ writel(value, int_mask);
+ spin_unlock_irqrestore(&hw->irq_ctrl_lock, flags);
+}
+
+static int dwmac1000_rx_ipc_enable(struct mac_device_info *hw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + GMAC_CONTROL);
+
+ if (hw->rx_csum)
+ value |= GMAC_CONTROL_IPC;
+ else
+ value &= ~GMAC_CONTROL_IPC;
+
+ writel(value, ioaddr + GMAC_CONTROL);
+
+ value = readl(ioaddr + GMAC_CONTROL);
+
+ return !!(value & GMAC_CONTROL_IPC);
+}
+
+static void dwmac1000_dump_regs(struct mac_device_info *hw, u32 *reg_space)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int i;
+
+ for (i = 0; i < 55; i++)
+ reg_space[i] = readl(ioaddr + i * 4);
+}
+
+static void dwmac1000_set_umac_addr(struct mac_device_info *hw,
+ const unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ stmmac_set_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
+ GMAC_ADDR_LOW(reg_n));
+}
+
+static void dwmac1000_get_umac_addr(struct mac_device_info *hw,
+ unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ stmmac_get_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
+ GMAC_ADDR_LOW(reg_n));
+}
+
+static void dwmac1000_set_mchash(void __iomem *ioaddr, u32 *mcfilterbits,
+ int mcbitslog2)
+{
+ int numhashregs, regs;
+
+ switch (mcbitslog2) {
+ case 6:
+ writel(mcfilterbits[0], ioaddr + GMAC_HASH_LOW);
+ writel(mcfilterbits[1], ioaddr + GMAC_HASH_HIGH);
+ return;
+ case 7:
+ numhashregs = 4;
+ break;
+ case 8:
+ numhashregs = 8;
+ break;
+ default:
+ pr_debug("STMMAC: err in setting multicast filter\n");
+ return;
+ }
+ for (regs = 0; regs < numhashregs; regs++)
+ writel(mcfilterbits[regs],
+ ioaddr + GMAC_EXTHASH_BASE + regs * 4);
+}
+
+static void dwmac1000_set_filter(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = (void __iomem *)dev->base_addr;
+ unsigned int value = 0;
+ unsigned int perfect_addr_number = hw->unicast_filter_entries;
+ u32 mc_filter[8];
+ int mcbitslog2 = hw->mcast_bits_log2;
+
+ pr_debug("%s: # mcasts %d, # unicast %d\n", __func__,
+ netdev_mc_count(dev), netdev_uc_count(dev));
+
+ memset(mc_filter, 0, sizeof(mc_filter));
+
+ if (dev->flags & IFF_PROMISC) {
+ value = GMAC_FRAME_FILTER_PR | GMAC_FRAME_FILTER_PCF;
+ } else if (dev->flags & IFF_ALLMULTI) {
+ value = GMAC_FRAME_FILTER_PM; /* pass all multi */
+ } else if (!netdev_mc_empty(dev) && (mcbitslog2 == 0)) {
+ /* Fall back to all multicast if we've no filter */
+ value = GMAC_FRAME_FILTER_PM;
+ } else if (!netdev_mc_empty(dev)) {
+ struct netdev_hw_addr *ha;
+
+ /* Hash filter for multicast */
+ value = GMAC_FRAME_FILTER_HMC;
+
+ netdev_for_each_mc_addr(ha, dev) {
+ /* The upper n bits of the calculated CRC are used to
+ * index the contents of the hash table. The number of
+ * bits used depends on the hardware configuration
+ * selected at core configuration time.
+ */
+ int bit_nr = bitrev32(~crc32_le(~0, ha->addr,
+ ETH_ALEN)) >>
+ (32 - mcbitslog2);
+ /* The most significant bit determines the register to
+ * use (H/L) while the other 5 bits determine the bit
+ * within the register.
+ */
+ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
+ }
+ }
+
+ value |= GMAC_FRAME_FILTER_HPF;
+ dwmac1000_set_mchash(ioaddr, mc_filter, mcbitslog2);
+
+ /* Handle multiple unicast addresses (perfect filtering) */
+ if (netdev_uc_count(dev) > perfect_addr_number)
+ /* Switch to promiscuous mode if more than unicast
+ * addresses are requested than supported by hardware.
+ */
+ value |= GMAC_FRAME_FILTER_PR;
+ else {
+ int reg = 1;
+ struct netdev_hw_addr *ha;
+
+ netdev_for_each_uc_addr(ha, dev) {
+ stmmac_set_mac_addr(ioaddr, ha->addr,
+ GMAC_ADDR_HIGH(reg),
+ GMAC_ADDR_LOW(reg));
+ reg++;
+ }
+
+ while (reg < perfect_addr_number) {
+ writel(0, ioaddr + GMAC_ADDR_HIGH(reg));
+ writel(0, ioaddr + GMAC_ADDR_LOW(reg));
+ reg++;
+ }
+ }
+
+#ifdef FRAME_FILTER_DEBUG
+ /* Enable Receive all mode (to debug filtering_fail errors) */
+ value |= GMAC_FRAME_FILTER_RA;
+#endif
+ writel(value, ioaddr + GMAC_FRAME_FILTER);
+}
+
+
+static void dwmac1000_flow_ctrl(struct mac_device_info *hw, unsigned int duplex,
+ unsigned int fc, unsigned int pause_time,
+ u8 tx_cnt)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ /* Set flow such that DZPQ in Mac Register 6 is 0,
+ * and unicast pause detect is enabled.
+ */
+ unsigned int flow = GMAC_FLOW_CTRL_UP;
+
+ pr_debug("GMAC Flow-Control:\n");
+ if (fc & FLOW_RX) {
+ pr_debug("\tReceive Flow-Control ON\n");
+ flow |= GMAC_FLOW_CTRL_RFE;
+ }
+ if (fc & FLOW_TX) {
+ pr_debug("\tTransmit Flow-Control ON\n");
+ flow |= GMAC_FLOW_CTRL_TFE;
+ }
+
+ if (duplex) {
+ pr_debug("\tduplex mode: PAUSE %d\n", pause_time);
+ flow |= FIELD_PREP(GMAC_FLOW_CTRL_PT_MASK, pause_time);
+ }
+
+ writel(flow, ioaddr + GMAC_FLOW_CTRL);
+}
+
+static void dwmac1000_pmt(struct mac_device_info *hw, unsigned long mode)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ unsigned int pmt = 0;
+
+ if (mode & WAKE_MAGIC) {
+ pr_debug("GMAC: WOL Magic frame\n");
+ pmt |= power_down | magic_pkt_en;
+ }
+ if (mode & WAKE_UCAST) {
+ pr_debug("GMAC: WOL on global unicast\n");
+ pmt |= power_down | global_unicast | wake_up_frame_en;
+ }
+
+ writel(pmt, ioaddr + GMAC_PMT);
+}
+
+static int dwmac1000_irq_status(struct stmmac_priv *priv,
+ struct stmmac_extra_stats *x)
+{
+ void __iomem *ioaddr = priv->hw->pcsr;
+ u32 intr_status = readl(ioaddr + GMAC_INT_STATUS);
+ u32 intr_mask = readl(ioaddr + GMAC_INT_MASK);
+ int ret = 0;
+
+ /* Discard masked bits */
+ intr_status &= ~intr_mask;
+
+ /* Not used events (e.g. MMC interrupts) are not handled. */
+ if ((intr_status & GMAC_INT_STATUS_MMCTIS))
+ x->mmc_tx_irq_n++;
+ if (unlikely(intr_status & GMAC_INT_STATUS_MMCRIS))
+ x->mmc_rx_irq_n++;
+ if (unlikely(intr_status & GMAC_INT_STATUS_MMCCSUM))
+ x->mmc_rx_csum_offload_irq_n++;
+ if (unlikely(intr_status & GMAC_INT_DISABLE_PMT)) {
+ /* clear the PMT bits 5 and 6 by reading the PMT status reg */
+ readl(ioaddr + GMAC_PMT);
+ x->irq_receive_pmt_irq_n++;
+ }
+
+ /* MAC tx/rx EEE LPI entry/exit interrupts */
+ if (intr_status & GMAC_INT_STATUS_LPIIS) {
+ /* Clean LPI interrupt by reading the Reg 12 */
+ ret = readl(ioaddr + LPI_CTRL_STATUS);
+
+ if (ret & LPI_CTRL_STATUS_TLPIEN)
+ x->irq_tx_path_in_lpi_mode_n++;
+ if (ret & LPI_CTRL_STATUS_TLPIEX)
+ x->irq_tx_path_exit_lpi_mode_n++;
+ if (ret & LPI_CTRL_STATUS_RLPIEN)
+ x->irq_rx_path_in_lpi_mode_n++;
+ if (ret & LPI_CTRL_STATUS_RLPIEX)
+ x->irq_rx_path_exit_lpi_mode_n++;
+ }
+
+ if (intr_status & (PCS_ANE_IRQ | PCS_LINK_IRQ))
+ stmmac_integrated_pcs_irq(priv, intr_status, x);
+
+ return ret;
+}
+
+static int dwmac1000_set_lpi_mode(struct mac_device_info *hw,
+ enum stmmac_lpi_mode mode,
+ bool en_tx_lpi_clockgating, u32 et)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ if (mode == STMMAC_LPI_TIMER)
+ return -EOPNOTSUPP;
+
+ value = readl(ioaddr + LPI_CTRL_STATUS);
+ if (mode == STMMAC_LPI_FORCED)
+ value |= LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA;
+ else
+ value &= ~(LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA);
+ writel(value, ioaddr + LPI_CTRL_STATUS);
+
+ return 0;
+}
+
+static void dwmac1000_set_eee_pls(struct mac_device_info *hw, int link)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + LPI_CTRL_STATUS);
+
+ if (link)
+ value |= LPI_CTRL_STATUS_PLS;
+ else
+ value &= ~LPI_CTRL_STATUS_PLS;
+
+ writel(value, ioaddr + LPI_CTRL_STATUS);
+}
+
+static void dwmac1000_set_eee_timer(struct mac_device_info *hw, int ls, int tw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int value = ((tw & 0xffff)) | ((ls & 0x7ff) << 16);
+
+ /* Program the timers in the LPI timer control register:
+ * LS: minimum time (ms) for which the link
+ * status from PHY should be ok before transmitting
+ * the LPI pattern.
+ * TW: minimum time (us) for which the core waits
+ * after it has stopped transmitting the LPI pattern.
+ */
+ writel(value, ioaddr + LPI_TIMER_CTRL);
+}
+
+static void dwmac1000_ctrl_ane(struct stmmac_priv *priv, bool ane,
+ bool srgmi_ral)
+{
+ dwmac_ctrl_ane(priv->ioaddr, GMAC_PCS_BASE, ane, srgmi_ral);
+}
+
+static void dwmac1000_debug(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x,
+ u32 rx_queues, u32 tx_queues)
+{
+ u32 value = readl(ioaddr + GMAC_DEBUG);
+
+ if (value & GMAC_DEBUG_TXSTSFSTS)
+ x->mtl_tx_status_fifo_full++;
+ if (value & GMAC_DEBUG_TXFSTS)
+ x->mtl_tx_fifo_not_empty++;
+ if (value & GMAC_DEBUG_TWCSTS)
+ x->mmtl_fifo_ctrl++;
+ if (value & GMAC_DEBUG_TRCSTS_MASK) {
+ u32 trcsts = FIELD_GET(GMAC_DEBUG_TRCSTS_MASK, value);
+
+ if (trcsts == GMAC_DEBUG_TRCSTS_WRITE)
+ x->mtl_tx_fifo_read_ctrl_write++;
+ else if (trcsts == GMAC_DEBUG_TRCSTS_TXW)
+ x->mtl_tx_fifo_read_ctrl_wait++;
+ else if (trcsts == GMAC_DEBUG_TRCSTS_READ)
+ x->mtl_tx_fifo_read_ctrl_read++;
+ else
+ x->mtl_tx_fifo_read_ctrl_idle++;
+ }
+ if (value & GMAC_DEBUG_TXPAUSED)
+ x->mac_tx_in_pause++;
+ if (value & GMAC_DEBUG_TFCSTS_MASK) {
+ u32 tfcsts = FIELD_GET(GMAC_DEBUG_TFCSTS_MASK, value);
+
+ if (tfcsts == GMAC_DEBUG_TFCSTS_XFER)
+ x->mac_tx_frame_ctrl_xfer++;
+ else if (tfcsts == GMAC_DEBUG_TFCSTS_GEN_PAUSE)
+ x->mac_tx_frame_ctrl_pause++;
+ else if (tfcsts == GMAC_DEBUG_TFCSTS_WAIT)
+ x->mac_tx_frame_ctrl_wait++;
+ else
+ x->mac_tx_frame_ctrl_idle++;
+ }
+ if (value & GMAC_DEBUG_TPESTS)
+ x->mac_gmii_tx_proto_engine++;
+ if (value & GMAC_DEBUG_RXFSTS_MASK) {
+ u32 rxfsts = FIELD_GET(GMAC_DEBUG_RXFSTS_MASK, value);
+
+ if (rxfsts == GMAC_DEBUG_RXFSTS_FULL)
+ x->mtl_rx_fifo_fill_level_full++;
+ else if (rxfsts == GMAC_DEBUG_RXFSTS_AT)
+ x->mtl_rx_fifo_fill_above_thresh++;
+ else if (rxfsts == GMAC_DEBUG_RXFSTS_BT)
+ x->mtl_rx_fifo_fill_below_thresh++;
+ else
+ x->mtl_rx_fifo_fill_level_empty++;
+ }
+ if (value & GMAC_DEBUG_RRCSTS_MASK) {
+ u32 rrcsts = FIELD_GET(GMAC_DEBUG_RRCSTS_MASK, value);
+
+ if (rrcsts == GMAC_DEBUG_RRCSTS_FLUSH)
+ x->mtl_rx_fifo_read_ctrl_flush++;
+ else if (rrcsts == GMAC_DEBUG_RRCSTS_RSTAT)
+ x->mtl_rx_fifo_read_ctrl_read_data++;
+ else if (rrcsts == GMAC_DEBUG_RRCSTS_RDATA)
+ x->mtl_rx_fifo_read_ctrl_status++;
+ else
+ x->mtl_rx_fifo_read_ctrl_idle++;
+ }
+ if (value & GMAC_DEBUG_RWCSTS)
+ x->mtl_rx_fifo_ctrl_active++;
+ if (value & GMAC_DEBUG_RFCFCSTS_MASK)
+ x->mac_rx_frame_ctrl_fifo = FIELD_GET(GMAC_DEBUG_RFCFCSTS_MASK,
+ value);
+ if (value & GMAC_DEBUG_RPESTS)
+ x->mac_gmii_rx_proto_engine++;
+}
+
+static void dwmac1000_set_mac_loopback(void __iomem *ioaddr, bool enable)
+{
+ u32 value = readl(ioaddr + GMAC_CONTROL);
+
+ if (enable)
+ value |= GMAC_CONTROL_LM;
+ else
+ value &= ~GMAC_CONTROL_LM;
+
+ writel(value, ioaddr + GMAC_CONTROL);
+}
+
+const struct stmmac_ops dwmac1000_ops = {
+ .pcs_init = dwmac1000_pcs_init,
+ .core_init = dwmac1000_core_init,
+ .irq_modify = dwmac1000_irq_modify,
+ .set_mac = stmmac_set_mac,
+ .rx_ipc = dwmac1000_rx_ipc_enable,
+ .dump_regs = dwmac1000_dump_regs,
+ .host_irq_status = dwmac1000_irq_status,
+ .set_filter = dwmac1000_set_filter,
+ .flow_ctrl = dwmac1000_flow_ctrl,
+ .pmt = dwmac1000_pmt,
+ .set_umac_addr = dwmac1000_set_umac_addr,
+ .get_umac_addr = dwmac1000_get_umac_addr,
+ .set_lpi_mode = dwmac1000_set_lpi_mode,
+ .set_eee_timer = dwmac1000_set_eee_timer,
+ .set_eee_pls = dwmac1000_set_eee_pls,
+ .debug = dwmac1000_debug,
+ .pcs_ctrl_ane = dwmac1000_ctrl_ane,
+ .set_mac_loopback = dwmac1000_set_mac_loopback,
+};
+
+int dwmac1000_setup(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ dev_info(priv->device, "\tDWMAC1000\n");
+
+ priv->dev->priv_flags |= IFF_UNICAST_FLT;
+ mac->pcsr = priv->ioaddr;
+ mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
+ mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
+ mac->mcast_bits_log2 = 0;
+
+ if (mac->multicast_filter_bins)
+ mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
+
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000;
+ mac->link.duplex = GMAC_CONTROL_DM;
+ mac->link.speed10 = GMAC_CONTROL_PS;
+ mac->link.speed100 = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
+ mac->link.speed1000 = 0;
+ mac->link.speed_mask = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
+ mac->mii.addr = GMAC_MII_ADDR;
+ mac->mii.data = GMAC_MII_DATA;
+ mac->mii.addr_mask = GENMASK_U32(15, 11);
+ mac->mii.reg_mask = GENMASK_U32(10, 6);
+ mac->mii.clk_csr_mask = GENMASK_U32(5, 2);
+
+ return 0;
+}
+
+/* DWMAC 1000 HW Timestaming ops */
+
+void dwmac1000_get_ptptime(void __iomem *ptpaddr, u64 *ptp_time)
+{
+ u64 ns;
+
+ ns = readl(ptpaddr + GMAC_PTP_ATNR);
+ ns += (u64)readl(ptpaddr + GMAC_PTP_ATSR) * NSEC_PER_SEC;
+
+ *ptp_time = ns;
+}
+
+void dwmac1000_timestamp_interrupt(struct stmmac_priv *priv)
+{
+ struct ptp_clock_event event;
+ u32 ts_status, num_snapshot;
+ unsigned long flags;
+ u64 ptp_time;
+ int i;
+
+ /* Clears the timestamp interrupt */
+ ts_status = readl(priv->ptpaddr + GMAC3_X_TIMESTAMP_STATUS);
+
+ if (!(priv->plat->flags & STMMAC_FLAG_EXT_SNAPSHOT_EN))
+ return;
+
+ num_snapshot = FIELD_GET(GMAC3_X_ATSNS, ts_status);
+
+ for (i = 0; i < num_snapshot; i++) {
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_get_ptptime(priv, priv->ptpaddr, &ptp_time);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ptp_time;
+ ptp_clock_event(priv->ptp_clock, &event);
+ }
+}
+
+/* DWMAC 1000 ptp_clock_info ops */
+
+static void dwmac1000_timestamp_interrupt_cfg(struct stmmac_priv *priv, bool en)
+{
+ void __iomem *ioaddr = priv->ioaddr;
+
+ u32 intr_mask = readl(ioaddr + GMAC_INT_MASK);
+
+ if (en)
+ intr_mask &= ~GMAC_INT_DISABLE_TIMESTAMP;
+ else
+ intr_mask |= GMAC_INT_DISABLE_TIMESTAMP;
+
+ writel(intr_mask, ioaddr + GMAC_INT_MASK);
+}
+
+int dwmac1000_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ void __iomem *ptpaddr = priv->ptpaddr;
+ int ret = -EOPNOTSUPP;
+ u32 tcr_val;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ mutex_lock(&priv->aux_ts_lock);
+ tcr_val = readl(ptpaddr + PTP_TCR);
+
+ if (on) {
+ tcr_val |= GMAC_PTP_TCR_ATSEN0;
+ tcr_val |= GMAC_PTP_TCR_ATSFC;
+ priv->plat->flags |= STMMAC_FLAG_EXT_SNAPSHOT_EN;
+ } else {
+ tcr_val &= ~GMAC_PTP_TCR_ATSEN0;
+ priv->plat->flags &= ~STMMAC_FLAG_EXT_SNAPSHOT_EN;
+ }
+
+ netdev_dbg(priv->dev, "Auxiliary Snapshot %s.\n",
+ str_enabled_disabled(on));
+ writel(tcr_val, ptpaddr + PTP_TCR);
+
+ /* wait for auxts fifo clear to finish */
+ ret = readl_poll_timeout(ptpaddr + PTP_TCR, tcr_val,
+ !(tcr_val & GMAC_PTP_TCR_ATSFC),
+ 10, 10000);
+
+ mutex_unlock(&priv->aux_ts_lock);
+
+ dwmac1000_timestamp_interrupt_cfg(priv, on);
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
new file mode 100644
index 000000000..3ac7a7949
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This is the driver for the GMAC on-chip Ethernet controller for ST SoCs.
+ DWC Ether MAC 10/100/1000 Universal version 3.41a has been used for
+ developing this code.
+
+ This contains the functions to handle the dma.
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/io.h>
+#include "dwmac1000.h"
+#include "dwmac_dma.h"
+
+static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
+{
+ u32 value = readl(ioaddr + DMA_AXI_BUS_MODE);
+
+ pr_info("dwmac1000: Master AXI performs %s burst length\n",
+ !(value & DMA_AXI_UNDEF) ? "fixed" : "any");
+
+ if (axi->axi_lpi_en)
+ value |= DMA_AXI_EN_LPI;
+ if (axi->axi_xit_frm)
+ value |= DMA_AXI_LPI_XIT_FRM;
+
+ value = u32_replace_bits(value, axi->axi_wr_osr_lmt,
+ DMA_AXI_WR_OSR_LMT);
+ value = u32_replace_bits(value, axi->axi_rd_osr_lmt,
+ DMA_AXI_RD_OSR_LMT);
+
+ /* Depending on the UNDEF bit the Master AXI will perform any burst
+ * length according to the BLEN programmed (by default all BLEN are
+ * set). Note that the UNDEF bit is readonly, and is the inverse of
+ * Bus Mode bit 16.
+ */
+ value = (value & ~DMA_AXI_BLEN_MASK) | axi->axi_blen_regval;
+
+ writel(value, ioaddr + DMA_AXI_BUS_MODE);
+}
+
+static void dwmac1000_dma_init_channel(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, u32 chan)
+{
+ int txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
+ int rxpbl = dma_cfg->rxpbl ?: dma_cfg->pbl;
+ u32 value;
+
+ value = readl(ioaddr + DMA_CHAN_BUS_MODE(chan));
+
+ /* Set the DMA PBL (Programmable Burst Length) mode.
+ *
+ * Note: before stmmac core 3.50 this mode bit was 4xPBL, and
+ * post 3.5 mode bit acts as 8*PBL.
+ */
+ if (dma_cfg->pblx8)
+ value |= DMA_BUS_MODE_MAXPBL;
+ value |= DMA_BUS_MODE_USP;
+ value = u32_replace_bits(value, txpbl, DMA_BUS_MODE_PBL_MASK);
+ value = u32_replace_bits(value, rxpbl, DMA_BUS_MODE_RPBL_MASK);
+
+ /* Set the Fixed burst mode */
+ if (dma_cfg->fixed_burst)
+ value |= DMA_BUS_MODE_FB;
+
+ /* Mixed Burst has no effect when fb is set */
+ if (dma_cfg->mixed_burst)
+ value |= DMA_BUS_MODE_MB;
+
+ if (dma_cfg->atds)
+ value |= DMA_BUS_MODE_ATDS;
+
+ if (dma_cfg->aal)
+ value |= DMA_BUS_MODE_AAL;
+
+ writel(value, ioaddr + DMA_CHAN_BUS_MODE(chan));
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(DMA_INTR_DEFAULT_MASK, ioaddr + DMA_CHAN_INTR_ENA(chan));
+}
+
+static void dwmac1000_dma_init_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_rx_phy, u32 chan)
+{
+ /* RX descriptor base address list must be written into DMA CSR3 */
+ writel(lower_32_bits(dma_rx_phy), ioaddr + DMA_CHAN_RCV_BASE_ADDR(chan));
+}
+
+static void dwmac1000_dma_init_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_tx_phy, u32 chan)
+{
+ /* TX descriptor base address list must be written into DMA CSR4 */
+ writel(lower_32_bits(dma_tx_phy), ioaddr + DMA_CHAN_TX_BASE_ADDR(chan));
+}
+
+static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz)
+{
+ csr6 &= ~DMA_CONTROL_RFA_MASK;
+ csr6 &= ~DMA_CONTROL_RFD_MASK;
+
+ /* Leave flow control disabled if receive fifo size is less than
+ * 4K or 0. Otherwise, send XOFF when fifo is 1K less than full,
+ * and send XON when 2K less than full.
+ */
+ if (rxfifosz < 4096) {
+ csr6 &= ~DMA_CONTROL_EFC;
+ pr_debug("GMAC: disabling flow control, rxfifo too small(%d)\n",
+ rxfifosz);
+ } else {
+ csr6 |= DMA_CONTROL_EFC;
+ csr6 |= RFA_FULL_MINUS_1K;
+ csr6 |= RFD_FULL_MINUS_2K;
+ }
+ return csr6;
+}
+
+static void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ u32 csr6 = readl(ioaddr + DMA_CHAN_CONTROL(channel));
+
+ if (mode == SF_DMA_MODE) {
+ pr_debug("GMAC: enable RX store and forward mode\n");
+ csr6 |= DMA_CONTROL_RSF | DMA_CONTROL_DFF;
+ } else {
+ pr_debug("GMAC: disable RX SF mode (threshold %d)\n", mode);
+ csr6 &= ~(DMA_CONTROL_RSF | DMA_CONTROL_DFF);
+ csr6 &= DMA_CONTROL_TC_RX_MASK;
+ if (mode <= 32)
+ csr6 |= DMA_CONTROL_RTC_32;
+ else if (mode <= 64)
+ csr6 |= DMA_CONTROL_RTC_64;
+ else if (mode <= 96)
+ csr6 |= DMA_CONTROL_RTC_96;
+ else
+ csr6 |= DMA_CONTROL_RTC_128;
+ }
+
+ /* Configure flow control based on rx fifo size */
+ csr6 = dwmac1000_configure_fc(csr6, fifosz);
+
+ writel(csr6, ioaddr + DMA_CHAN_CONTROL(channel));
+}
+
+static void dwmac1000_dma_operation_mode_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ u32 csr6 = readl(ioaddr + DMA_CHAN_CONTROL(channel));
+
+ if (mode == SF_DMA_MODE) {
+ pr_debug("GMAC: enable TX store and forward mode\n");
+ /* Transmit COE type 2 cannot be done in cut-through mode. */
+ csr6 |= DMA_CONTROL_TSF;
+ /* Operating on second frame increase the performance
+ * especially when transmit store-and-forward is used.
+ */
+ csr6 |= DMA_CONTROL_OSF;
+ } else {
+ pr_debug("GMAC: disabling TX SF (threshold %d)\n", mode);
+ csr6 &= ~DMA_CONTROL_TSF;
+ csr6 &= DMA_CONTROL_TC_TX_MASK;
+ /* Set the transmit threshold */
+ if (mode <= 32)
+ csr6 |= DMA_CONTROL_TTC_32;
+ else if (mode <= 64)
+ csr6 |= DMA_CONTROL_TTC_64;
+ else if (mode <= 128)
+ csr6 |= DMA_CONTROL_TTC_128;
+ else if (mode <= 192)
+ csr6 |= DMA_CONTROL_TTC_192;
+ else
+ csr6 |= DMA_CONTROL_TTC_256;
+ }
+
+ writel(csr6, ioaddr + DMA_CHAN_CONTROL(channel));
+}
+
+static void dwmac1000_dump_dma_regs(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 *reg_space)
+{
+ int i;
+
+ for (i = 0; i < NUM_DWMAC1000_DMA_REGS; i++)
+ if ((i < 12) || (i > 17))
+ reg_space[DMA_BUS_MODE / 4 + i] =
+ readl(ioaddr + DMA_BUS_MODE + i * 4);
+}
+
+static int dwmac1000_get_hw_feature(void __iomem *ioaddr,
+ struct dma_features *dma_cap)
+{
+ u32 hw_cap = readl(ioaddr + DMA_HW_FEATURE);
+
+ if (!hw_cap) {
+ /* 0x00000000 is the value read on old hardware that does not
+ * implement this register
+ */
+ return -EOPNOTSUPP;
+ }
+
+ dma_cap->mbps_10_100 = (hw_cap & DMA_HW_FEAT_MIISEL);
+ dma_cap->mbps_1000 = (hw_cap & DMA_HW_FEAT_GMIISEL) >> 1;
+ dma_cap->half_duplex = (hw_cap & DMA_HW_FEAT_HDSEL) >> 2;
+ dma_cap->hash_filter = (hw_cap & DMA_HW_FEAT_HASHSEL) >> 4;
+ dma_cap->multi_addr = (hw_cap & DMA_HW_FEAT_ADDMAC) >> 5;
+ dma_cap->pcs = (hw_cap & DMA_HW_FEAT_PCSSEL) >> 6;
+ dma_cap->sma_mdio = (hw_cap & DMA_HW_FEAT_SMASEL) >> 8;
+ dma_cap->pmt_remote_wake_up = (hw_cap & DMA_HW_FEAT_RWKSEL) >> 9;
+ dma_cap->pmt_magic_frame = (hw_cap & DMA_HW_FEAT_MGKSEL) >> 10;
+ /* MMC */
+ dma_cap->rmon = (hw_cap & DMA_HW_FEAT_MMCSEL) >> 11;
+ /* IEEE 1588-2002 */
+ dma_cap->time_stamp =
+ (hw_cap & DMA_HW_FEAT_TSVER1SEL) >> 12;
+ /* IEEE 1588-2008 */
+ dma_cap->atime_stamp = (hw_cap & DMA_HW_FEAT_TSVER2SEL) >> 13;
+ /* 802.3az - Energy-Efficient Ethernet (EEE) */
+ dma_cap->eee = (hw_cap & DMA_HW_FEAT_EEESEL) >> 14;
+ dma_cap->av = (hw_cap & DMA_HW_FEAT_AVSEL) >> 15;
+ /* TX and RX csum */
+ dma_cap->tx_coe = (hw_cap & DMA_HW_FEAT_TXCOESEL) >> 16;
+ dma_cap->rx_coe_type1 = (hw_cap & DMA_HW_FEAT_RXTYP1COE) >> 17;
+ dma_cap->rx_coe_type2 = (hw_cap & DMA_HW_FEAT_RXTYP2COE) >> 18;
+ dma_cap->rxfifo_over_2048 = (hw_cap & DMA_HW_FEAT_RXFIFOSIZE) >> 19;
+ /* TX and RX number of channels */
+ dma_cap->number_rx_channel = (hw_cap & DMA_HW_FEAT_RXCHCNT) >> 20;
+ dma_cap->number_tx_channel = (hw_cap & DMA_HW_FEAT_TXCHCNT) >> 22;
+ /* Alternate (enhanced) DESC mode */
+ dma_cap->enh_desc = (hw_cap & DMA_HW_FEAT_ENHDESSEL) >> 24;
+
+ dma_cap->actphyif = FIELD_GET(DMA_HW_FEAT_ACTPHYIF, hw_cap);
+
+ return 0;
+}
+
+static void dwmac1000_rx_watchdog(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 riwt, u32 queue)
+{
+ writel(riwt, ioaddr + DMA_CHAN_RX_WATCHDOG(queue));
+}
+
+const struct stmmac_dma_ops dwmac1000_dma_ops = {
+ .reset = dwmac_dma_reset,
+ .init_chan = dwmac1000_dma_init_channel,
+ .init_rx_chan = dwmac1000_dma_init_rx,
+ .init_tx_chan = dwmac1000_dma_init_tx,
+ .axi = dwmac1000_dma_axi,
+ .dump_regs = dwmac1000_dump_dma_regs,
+ .dma_rx_mode = dwmac1000_dma_operation_mode_rx,
+ .dma_tx_mode = dwmac1000_dma_operation_mode_tx,
+ .enable_dma_transmission = dwmac_enable_dma_transmission,
+ .enable_dma_reception = dwmac_enable_dma_reception,
+ .enable_dma_irq = dwmac_enable_dma_irq,
+ .disable_dma_irq = dwmac_disable_dma_irq,
+ .start_tx = dwmac_dma_start_tx,
+ .stop_tx = dwmac_dma_stop_tx,
+ .start_rx = dwmac_dma_start_rx,
+ .stop_rx = dwmac_dma_stop_rx,
+ .dma_interrupt = dwmac_dma_interrupt,
+ .get_hw_feature = dwmac1000_get_hw_feature,
+ .rx_watchdog = dwmac1000_rx_watchdog,
+};
+EXPORT_SYMBOL_GPL(dwmac1000_dma_ops);
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c
new file mode 100644
index 000000000..94d24d355
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This is the driver for the MAC 10/100 on-chip Ethernet controller
+ currently tested on all the ST boards based on STb7109 and stx7200 SoCs.
+
+ DWC Ether MAC 10/100 Universal version 4.0 has been used for developing
+ this code.
+
+ This only implements the mac core functions for this chip.
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/crc32.h>
+#include <linux/io.h>
+#include "stmmac.h"
+#include "dwmac100.h"
+
+static void dwmac100_core_init(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + MAC_CONTROL);
+
+ value |= MAC_CORE_INIT;
+
+ writel(value, ioaddr + MAC_CONTROL);
+
+#ifdef STMMAC_VLAN_TAG_USED
+ writel(ETH_P_8021Q, ioaddr + MAC_VLAN1);
+#endif
+}
+
+static void dwmac100_dump_mac_regs(struct mac_device_info *hw, u32 *reg_space)
+{
+ void __iomem *ioaddr = hw->pcsr;
+
+ reg_space[MAC_CONTROL / 4] = readl(ioaddr + MAC_CONTROL);
+ reg_space[MAC_ADDR_HIGH / 4] = readl(ioaddr + MAC_ADDR_HIGH);
+ reg_space[MAC_ADDR_LOW / 4] = readl(ioaddr + MAC_ADDR_LOW);
+ reg_space[MAC_HASH_HIGH / 4] = readl(ioaddr + MAC_HASH_HIGH);
+ reg_space[MAC_HASH_LOW / 4] = readl(ioaddr + MAC_HASH_LOW);
+ reg_space[MAC_FLOW_CTRL / 4] = readl(ioaddr + MAC_FLOW_CTRL);
+ reg_space[MAC_VLAN1 / 4] = readl(ioaddr + MAC_VLAN1);
+ reg_space[MAC_VLAN2 / 4] = readl(ioaddr + MAC_VLAN2);
+}
+
+static int dwmac100_rx_ipc_enable(struct mac_device_info *hw)
+{
+ return 0;
+}
+
+static int dwmac100_irq_status(struct stmmac_priv *priv,
+ struct stmmac_extra_stats *x)
+{
+ return 0;
+}
+
+static void dwmac100_set_umac_addr(struct mac_device_info *hw,
+ const unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ stmmac_set_mac_addr(ioaddr, addr, MAC_ADDR_HIGH, MAC_ADDR_LOW);
+}
+
+static void dwmac100_get_umac_addr(struct mac_device_info *hw,
+ unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ stmmac_get_mac_addr(ioaddr, addr, MAC_ADDR_HIGH, MAC_ADDR_LOW);
+}
+
+static void dwmac100_set_filter(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = (void __iomem *)dev->base_addr;
+ u32 value = readl(ioaddr + MAC_CONTROL);
+
+ if (dev->flags & IFF_PROMISC) {
+ value |= MAC_CONTROL_PR;
+ value &= ~(MAC_CONTROL_PM | MAC_CONTROL_IF | MAC_CONTROL_HO |
+ MAC_CONTROL_HP);
+ } else if ((netdev_mc_count(dev) > HASH_TABLE_SIZE)
+ || (dev->flags & IFF_ALLMULTI)) {
+ value |= MAC_CONTROL_PM;
+ value &= ~(MAC_CONTROL_PR | MAC_CONTROL_IF | MAC_CONTROL_HO);
+ writel(0xffffffff, ioaddr + MAC_HASH_HIGH);
+ writel(0xffffffff, ioaddr + MAC_HASH_LOW);
+ } else if (netdev_mc_empty(dev)) { /* no multicast */
+ value &= ~(MAC_CONTROL_PM | MAC_CONTROL_PR | MAC_CONTROL_IF |
+ MAC_CONTROL_HO | MAC_CONTROL_HP);
+ } else {
+ u32 mc_filter[2];
+ struct netdev_hw_addr *ha;
+
+ /* Perfect filter mode for physical address and Hash
+ * filter for multicast
+ */
+ value |= MAC_CONTROL_HP;
+ value &= ~(MAC_CONTROL_PM | MAC_CONTROL_PR |
+ MAC_CONTROL_IF | MAC_CONTROL_HO);
+
+ memset(mc_filter, 0, sizeof(mc_filter));
+ netdev_for_each_mc_addr(ha, dev) {
+ /* The upper 6 bits of the calculated CRC are used to
+ * index the contents of the hash table
+ */
+ int bit_nr = ether_crc(ETH_ALEN, ha->addr) >> 26;
+ /* The most significant bit determines the register to
+ * use (H/L) while the other 5 bits determine the bit
+ * within the register.
+ */
+ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
+ }
+ writel(mc_filter[0], ioaddr + MAC_HASH_LOW);
+ writel(mc_filter[1], ioaddr + MAC_HASH_HIGH);
+ }
+
+ writel(value, ioaddr + MAC_CONTROL);
+}
+
+static void dwmac100_flow_ctrl(struct mac_device_info *hw, unsigned int duplex,
+ unsigned int fc, unsigned int pause_time,
+ u8 tx_cnt)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ unsigned int flow = MAC_FLOW_CTRL_ENABLE;
+
+ if (duplex)
+ flow |= FIELD_PREP(MAC_FLOW_CTRL_PT_MASK, pause_time);
+ writel(flow, ioaddr + MAC_FLOW_CTRL);
+}
+
+/* No PMT module supported on ST boards with this Eth chip. */
+static void dwmac100_pmt(struct mac_device_info *hw, unsigned long mode)
+{
+ return;
+}
+
+static void dwmac100_set_mac_loopback(void __iomem *ioaddr, bool enable)
+{
+ u32 value = readl(ioaddr + MAC_CONTROL);
+
+ if (enable)
+ value |= MAC_CONTROL_OM;
+ else
+ value &= ~MAC_CONTROL_OM;
+
+ writel(value, ioaddr + MAC_CONTROL);
+}
+
+const struct stmmac_ops dwmac100_ops = {
+ .core_init = dwmac100_core_init,
+ .set_mac = stmmac_set_mac,
+ .rx_ipc = dwmac100_rx_ipc_enable,
+ .dump_regs = dwmac100_dump_mac_regs,
+ .host_irq_status = dwmac100_irq_status,
+ .set_filter = dwmac100_set_filter,
+ .flow_ctrl = dwmac100_flow_ctrl,
+ .pmt = dwmac100_pmt,
+ .set_umac_addr = dwmac100_set_umac_addr,
+ .get_umac_addr = dwmac100_get_umac_addr,
+ .set_mac_loopback = dwmac100_set_mac_loopback,
+};
+
+int dwmac100_setup(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ dev_info(priv->device, "\tDWMAC100\n");
+
+ mac->pcsr = priv->ioaddr;
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100;
+ mac->link.duplex = MAC_CONTROL_F;
+ mac->link.speed10 = 0;
+ mac->link.speed100 = 0;
+ mac->link.speed1000 = 0;
+ mac->link.speed_mask = MAC_CONTROL_PS;
+ mac->mii.addr = MAC_MII_ADDR;
+ mac->mii.data = MAC_MII_DATA;
+ mac->mii.addr_mask = GENMASK_U32(15, 11);
+ mac->mii.reg_mask = GENMASK_U32(10, 6);
+ mac->mii.clk_csr_mask = GENMASK_U32(5, 2);
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
new file mode 100644
index 000000000..12b2bf2d7
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This is the driver for the MAC 10/100 on-chip Ethernet controller
+ currently tested on all the ST boards based on STb7109 and stx7200 SoCs.
+
+ DWC Ether MAC 10/100 Universal version 4.0 has been used for developing
+ this code.
+
+ This contains the functions to handle the dma.
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/io.h>
+#include "dwmac100.h"
+#include "dwmac_dma.h"
+
+static void dwmac100_dma_init(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg)
+{
+ /* Enable Application Access by writing to DMA CSR0 */
+ writel(DMA_BUS_MODE_DEFAULT |
+ FIELD_PREP(DMA_BUS_MODE_PBL_MASK, dma_cfg->pbl),
+ ioaddr + DMA_BUS_MODE);
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(DMA_INTR_DEFAULT_MASK, ioaddr + DMA_INTR_ENA);
+}
+
+static void dwmac100_dma_init_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_rx_phy, u32 chan)
+{
+ /* RX descriptor base addr lists must be written into DMA CSR3 */
+ writel(lower_32_bits(dma_rx_phy), ioaddr + DMA_RCV_BASE_ADDR);
+}
+
+static void dwmac100_dma_init_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_tx_phy, u32 chan)
+{
+ /* TX descriptor base addr lists must be written into DMA CSR4 */
+ writel(lower_32_bits(dma_tx_phy), ioaddr + DMA_TX_BASE_ADDR);
+}
+
+/* Store and Forward capability is not used at all.
+ *
+ * The transmit threshold can be programmed by setting the TTC bits in the DMA
+ * control register.
+ */
+static void dwmac100_dma_operation_mode_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ u32 csr6 = readl(ioaddr + DMA_CONTROL);
+
+ if (mode <= 32)
+ csr6 |= DMA_CONTROL_TTC_32;
+ else if (mode <= 64)
+ csr6 |= DMA_CONTROL_TTC_64;
+ else
+ csr6 |= DMA_CONTROL_TTC_128;
+
+ writel(csr6, ioaddr + DMA_CONTROL);
+}
+
+static void dwmac100_dump_dma_regs(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 *reg_space)
+{
+ int i;
+
+ for (i = 0; i < NUM_DWMAC100_DMA_REGS; i++)
+ reg_space[DMA_BUS_MODE / 4 + i] =
+ readl(ioaddr + DMA_BUS_MODE + i * 4);
+
+ reg_space[DMA_CUR_TX_BUF_ADDR / 4] =
+ readl(ioaddr + DMA_CUR_TX_BUF_ADDR);
+ reg_space[DMA_CUR_RX_BUF_ADDR / 4] =
+ readl(ioaddr + DMA_CUR_RX_BUF_ADDR);
+}
+
+/* DMA controller has two counters to track the number of the missed frames. */
+static void dwmac100_dma_diagnostic_fr(struct stmmac_extra_stats *x,
+ void __iomem *ioaddr)
+{
+ u32 csr8 = readl(ioaddr + DMA_MISSED_FRAME_CTR);
+
+ if (unlikely(csr8)) {
+ if (csr8 & DMA_MISSED_FRAME_OVE) {
+ x->rx_overflow_cntr += 0x800;
+ } else {
+ unsigned int ove_cntr;
+ ove_cntr = ((csr8 & DMA_MISSED_FRAME_OVE_CNTR) >> 17);
+ x->rx_overflow_cntr += ove_cntr;
+ }
+
+ if (csr8 & DMA_MISSED_FRAME_OVE_M) {
+ x->rx_missed_cntr += 0xffff;
+ } else {
+ unsigned int miss_f = (csr8 & DMA_MISSED_FRAME_M_CNTR);
+ x->rx_missed_cntr += miss_f;
+ }
+ }
+}
+
+const struct stmmac_dma_ops dwmac100_dma_ops = {
+ .reset = dwmac_dma_reset,
+ .init = dwmac100_dma_init,
+ .init_rx_chan = dwmac100_dma_init_rx,
+ .init_tx_chan = dwmac100_dma_init_tx,
+ .dump_regs = dwmac100_dump_dma_regs,
+ .dma_tx_mode = dwmac100_dma_operation_mode_tx,
+ .dma_diagnostic_fr = dwmac100_dma_diagnostic_fr,
+ .enable_dma_transmission = dwmac_enable_dma_transmission,
+ .enable_dma_irq = dwmac_enable_dma_irq,
+ .disable_dma_irq = dwmac_disable_dma_irq,
+ .start_tx = dwmac_dma_start_tx,
+ .stop_tx = dwmac_dma_stop_tx,
+ .start_rx = dwmac_dma_start_rx,
+ .stop_rx = dwmac_dma_stop_rx,
+ .dma_interrupt = dwmac_dma_interrupt,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h
new file mode 100644
index 000000000..638283682
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h
@@ -0,0 +1,478 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * DWMAC4 Header file.
+ *
+ * Copyright (C) 2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#ifndef __DWMAC4_H__
+#define __DWMAC4_H__
+
+#include "common.h"
+
+/* MAC registers */
+#define GMAC_CONFIG 0x00000000
+#define GMAC_EXT_CONFIG 0x00000004
+#define GMAC_PACKET_FILTER 0x00000008
+#define GMAC_HASH_TAB(x) (0x10 + (x) * 4)
+#define GMAC_RX_FLOW_CTRL 0x00000090
+#define GMAC_QX_TX_FLOW_CTRL(x) (0x70 + x * 4)
+#define GMAC_TXQ_PRTY_MAP0 0x98
+#define GMAC_TXQ_PRTY_MAP1 0x9C
+#define GMAC_RXQ_CTRL0 0x000000a0
+#define GMAC_RXQ_CTRL1 0x000000a4
+#define GMAC_RXQ_CTRL2 0x000000a8
+#define GMAC_RXQ_CTRL3 0x000000ac
+#define GMAC_INT_STATUS 0x000000b0
+#define GMAC_INT_EN 0x000000b4
+#define GMAC_PCS_BASE 0x000000e0
+#define GMAC_PHYIF_CONTROL_STATUS 0x000000f8
+#define GMAC_PMT 0x000000c0
+#define GMAC_DEBUG 0x00000114
+#define GMAC_HW_FEATURE0 0x0000011c
+#define GMAC_HW_FEATURE1 0x00000120
+#define GMAC_HW_FEATURE2 0x00000124
+#define GMAC_HW_FEATURE3 0x00000128
+#define GMAC_MDIO_ADDR 0x00000200
+#define GMAC_MDIO_DATA 0x00000204
+#define GMAC_GPIO_STATUS 0x0000020C
+#define GMAC_ARP_ADDR 0x00000210
+#define GMAC_EXT_CFG1 0x00000238
+#define GMAC_ADDR_HIGH(reg) (0x300 + reg * 8)
+#define GMAC_ADDR_LOW(reg) (0x304 + reg * 8)
+#define GMAC_L3L4_CTRL(reg) (0x900 + (reg) * 0x30)
+#define GMAC_L4_ADDR(reg) (0x904 + (reg) * 0x30)
+#define GMAC_L3_ADDR0(reg) (0x910 + (reg) * 0x30)
+#define GMAC_L3_ADDR1(reg) (0x914 + (reg) * 0x30)
+#define GMAC_TIMESTAMP_STATUS 0x00000b20
+
+/* RX Queues Routing */
+#define GMAC_RXQCTRL_AVCPQ_MASK GENMASK(2, 0)
+#define GMAC_RXQCTRL_AVCPQ_SHIFT 0
+#define GMAC_RXQCTRL_PTPQ_MASK GENMASK(6, 4)
+#define GMAC_RXQCTRL_PTPQ_SHIFT 4
+#define GMAC_RXQCTRL_DCBCPQ_MASK GENMASK(10, 8)
+#define GMAC_RXQCTRL_DCBCPQ_SHIFT 8
+#define GMAC_RXQCTRL_UPQ_MASK GENMASK(14, 12)
+#define GMAC_RXQCTRL_UPQ_SHIFT 12
+#define GMAC_RXQCTRL_MCBCQ_MASK GENMASK(18, 16)
+#define GMAC_RXQCTRL_MCBCQ_SHIFT 16
+#define GMAC_RXQCTRL_MCBCQEN BIT(20)
+#define GMAC_RXQCTRL_MCBCQEN_SHIFT 20
+#define GMAC_RXQCTRL_TACPQE BIT(21)
+#define GMAC_RXQCTRL_TACPQE_SHIFT 21
+#define GMAC_RXQCTRL_FPRQ GENMASK(26, 24)
+
+/* MAC Packet Filtering */
+#define GMAC_PACKET_FILTER_PR BIT(0)
+#define GMAC_PACKET_FILTER_HMC BIT(2)
+#define GMAC_PACKET_FILTER_PM BIT(4)
+#define GMAC_PACKET_FILTER_PCF BIT(7)
+#define GMAC_PACKET_FILTER_HPF BIT(10)
+#define GMAC_PACKET_FILTER_VTFE BIT(16)
+#define GMAC_PACKET_FILTER_IPFE BIT(20)
+#define GMAC_PACKET_FILTER_RA BIT(31)
+
+#define GMAC_MAX_PERFECT_ADDRESSES 128
+
+/* MAC RX Queue Enable */
+#define GMAC_RX_QUEUE_CLEAR(queue) ~(GENMASK(1, 0) << ((queue) * 2))
+#define GMAC_RX_AV_QUEUE_ENABLE(queue) BIT((queue) * 2)
+#define GMAC_RX_DCB_QUEUE_ENABLE(queue) BIT(((queue) * 2) + 1)
+
+/* MAC Flow Control RX */
+#define GMAC_RX_FLOW_CTRL_RFE BIT(0)
+
+/* RX Queues Priorities */
+#define GMAC_RXQCTRL_PSRQX_MASK(x) GENMASK(7 + ((x) * 8), 0 + ((x) * 8))
+#define GMAC_RXQCTRL_PSRQX_SHIFT(x) ((x) * 8)
+
+/* TX Queues Priorities */
+#define GMAC_TXQCTRL_PSTQX_MASK(x) GENMASK(7 + ((x) * 8), 0 + ((x) * 8))
+#define GMAC_TXQCTRL_PSTQX_SHIFT(x) ((x) * 8)
+
+/* MAC Flow Control TX */
+#define GMAC_TX_FLOW_CTRL_TFE BIT(1)
+#define GMAC_TX_FLOW_CTRL_PT_MASK GENMASK(31, 16)
+
+/* MAC Interrupt bitmap*/
+#define GMAC_INT_RGSMIIS BIT(0)
+#define GMAC_INT_PCS_LINK BIT(1)
+#define GMAC_INT_PCS_ANE BIT(2)
+#define GMAC_INT_PCS_PHYIS BIT(3)
+#define GMAC_INT_PMT_EN BIT(4)
+#define GMAC_INT_LPI_EN BIT(5)
+#define GMAC_INT_TSIE BIT(12)
+
+#define GMAC_INT_DEFAULT_ENABLE (GMAC_INT_PMT_EN | GMAC_INT_LPI_EN | \
+ GMAC_INT_TSIE)
+
+enum dwmac4_irq_status {
+ time_stamp_irq = 0x00001000,
+ mmc_rx_csum_offload_irq = 0x00000800,
+ mmc_tx_irq = 0x00000400,
+ mmc_rx_irq = 0x00000200,
+ mmc_irq = 0x00000100,
+ lpi_irq = 0x00000020,
+ pmt_irq = 0x00000010,
+};
+
+/* MAC PMT bitmap */
+enum power_event {
+ pointer_reset = 0x80000000,
+ global_unicast = 0x00000200,
+ wake_up_rx_frame = 0x00000040,
+ magic_frame = 0x00000020,
+ wake_up_frame_en = 0x00000004,
+ magic_pkt_en = 0x00000002,
+ power_down = 0x00000001,
+};
+
+/* Energy Efficient Ethernet (EEE) for GMAC4
+ *
+ * LPI status, timer and control register offset
+ * For LPI control and status bit definitions, see common.h.
+ */
+#define GMAC4_LPI_CTRL_STATUS 0xd0
+#define GMAC4_LPI_TIMER_CTRL 0xd4
+#define GMAC4_LPI_ENTRY_TIMER 0xd8
+#define GMAC4_MAC_ONEUS_TIC_COUNTER 0xdc
+
+/* MAC Debug bitmap */
+#define GMAC_DEBUG_TFCSTS_MASK GENMASK(18, 17)
+#define GMAC_DEBUG_TFCSTS_IDLE 0
+#define GMAC_DEBUG_TFCSTS_WAIT 1
+#define GMAC_DEBUG_TFCSTS_GEN_PAUSE 2
+#define GMAC_DEBUG_TFCSTS_XFER 3
+#define GMAC_DEBUG_TPESTS BIT(16)
+#define GMAC_DEBUG_RFCFCSTS_MASK GENMASK(2, 1)
+#define GMAC_DEBUG_RPESTS BIT(0)
+
+/* MAC config */
+#define GMAC_CONFIG_ARPEN BIT(31)
+#define GMAC_CONFIG_SARC GENMASK(30, 28)
+#define GMAC_CONFIG_IPC BIT(27)
+#define GMAC_CONFIG_IPG GENMASK(26, 24)
+#define GMAC_CONFIG_2K BIT(22)
+#define GMAC_CONFIG_ACS BIT(20)
+#define GMAC_CONFIG_BE BIT(18)
+#define GMAC_CONFIG_JD BIT(17)
+#define GMAC_CONFIG_JE BIT(16)
+#define GMAC_CONFIG_PS BIT(15)
+#define GMAC_CONFIG_FES BIT(14)
+#define GMAC_CONFIG_DM BIT(13)
+#define GMAC_CONFIG_LM BIT(12)
+#define GMAC_CONFIG_DCRS BIT(9)
+#define GMAC_CONFIG_TE BIT(1)
+#define GMAC_CONFIG_RE BIT(0)
+
+/* MAC extended config */
+#define GMAC_CONFIG_EIPG GENMASK(29, 25)
+#define GMAC_CONFIG_EIPG_EN BIT(24)
+#define GMAC_CONFIG_HDSMS GENMASK(22, 20)
+#define GMAC_CONFIG_HDSMS_256 FIELD_PREP_CONST(GMAC_CONFIG_HDSMS, 0x2)
+
+/* MAC HW features0 bitmap */
+#define GMAC_HW_FEAT_SAVLANINS BIT(27)
+#define GMAC_HW_FEAT_ADDMAC BIT(18)
+#define GMAC_HW_FEAT_RXCOESEL BIT(16)
+#define GMAC_HW_FEAT_TXCOSEL BIT(14)
+#define GMAC_HW_FEAT_EEESEL BIT(13)
+#define GMAC_HW_FEAT_TSSEL BIT(12)
+#define GMAC_HW_FEAT_ARPOFFSEL BIT(9)
+#define GMAC_HW_FEAT_MMCSEL BIT(8)
+#define GMAC_HW_FEAT_MGKSEL BIT(7)
+#define GMAC_HW_FEAT_RWKSEL BIT(6)
+#define GMAC_HW_FEAT_SMASEL BIT(5)
+#define GMAC_HW_FEAT_VLHASH BIT(4)
+#define GMAC_HW_FEAT_PCSSEL BIT(3)
+#define GMAC_HW_FEAT_HDSEL BIT(2)
+#define GMAC_HW_FEAT_GMIISEL BIT(1)
+#define GMAC_HW_FEAT_MIISEL BIT(0)
+
+/* MAC HW features1 bitmap */
+#define GMAC_HW_FEAT_L3L4FNUM GENMASK(30, 27)
+#define GMAC_HW_HASH_TB_SZ GENMASK(25, 24)
+#define GMAC_HW_FEAT_AVSEL BIT(20)
+#define GMAC_HW_TSOEN BIT(18)
+#define GMAC_HW_FEAT_SPHEN BIT(17)
+#define GMAC_HW_FEAT_DCBEN BIT(16)
+#define GMAC_HW_ADDR64 GENMASK(15, 14)
+#define GMAC_HW_TXFIFOSIZE GENMASK(10, 6)
+#define GMAC_HW_RXFIFOSIZE GENMASK(4, 0)
+
+/* MAC HW features2 bitmap */
+#define GMAC_HW_FEAT_AUXSNAPNUM GENMASK(30, 28)
+#define GMAC_HW_FEAT_PPSOUTNUM GENMASK(26, 24)
+#define GMAC_HW_FEAT_TXCHCNT GENMASK(21, 18)
+#define GMAC_HW_FEAT_RXCHCNT GENMASK(15, 12)
+#define GMAC_HW_FEAT_TXQCNT GENMASK(9, 6)
+#define GMAC_HW_FEAT_RXQCNT GENMASK(3, 0)
+
+/* MAC HW features3 bitmap */
+#define GMAC_HW_FEAT_ASP GENMASK(29, 28)
+#define GMAC_HW_FEAT_TBSSEL BIT(27)
+#define GMAC_HW_FEAT_FPESEL BIT(26)
+#define GMAC_HW_FEAT_ESTWID GENMASK(21, 20)
+#define GMAC_HW_FEAT_ESTDEP GENMASK(19, 17)
+#define GMAC_HW_FEAT_ESTSEL BIT(16)
+#define GMAC_HW_FEAT_FRPES GENMASK(14, 13)
+#define GMAC_HW_FEAT_FRPBS GENMASK(12, 11)
+#define GMAC_HW_FEAT_FRPSEL BIT(10)
+#define GMAC_HW_FEAT_DVLAN BIT(5)
+#define GMAC_HW_FEAT_NRVF GENMASK(2, 0)
+
+/* MAC extended config 1 */
+#define GMAC_CONFIG1_SAVE_EN BIT(24)
+#define GMAC_CONFIG1_SPLM(v) FIELD_PREP(GENMASK(9, 8), v)
+
+/* GMAC GPIO Status reg */
+#define GMAC_GPO0 BIT(16)
+#define GMAC_GPO1 BIT(17)
+#define GMAC_GPO2 BIT(18)
+#define GMAC_GPO3 BIT(19)
+
+/* MAC HW ADDR regs */
+#define GMAC_HI_DCS GENMASK(18, 16)
+#define GMAC_HI_REG_AE BIT(31)
+
+/* L3/L4 Filters regs */
+#define GMAC_L4DPIM0 BIT(21)
+#define GMAC_L4DPM0 BIT(20)
+#define GMAC_L4SPIM0 BIT(19)
+#define GMAC_L4SPM0 BIT(18)
+#define GMAC_L4PEN0 BIT(16)
+#define GMAC_L3DAIM0 BIT(5)
+#define GMAC_L3DAM0 BIT(4)
+#define GMAC_L3SAIM0 BIT(3)
+#define GMAC_L3SAM0 BIT(2)
+#define GMAC_L3PEN0 BIT(0)
+#define GMAC_L4DP0 GENMASK(31, 16)
+#define GMAC_L4SP0 GENMASK(15, 0)
+
+/* MAC Timestamp Status */
+#define GMAC_TIMESTAMP_AUXTSTRIG BIT(2)
+#define GMAC_TIMESTAMP_ATSNS_MASK GENMASK(29, 25)
+#define GMAC_TIMESTAMP_ATSNS_SHIFT 25
+
+/* MTL registers */
+#define MTL_OPERATION_MODE 0x00000c00
+#define MTL_FRPE BIT(15)
+#define MTL_OPERATION_SCHALG_MASK GENMASK(6, 5)
+#define MTL_OPERATION_SCHALG_WRR (0x0 << 5)
+#define MTL_OPERATION_SCHALG_WFQ (0x1 << 5)
+#define MTL_OPERATION_SCHALG_DWRR (0x2 << 5)
+#define MTL_OPERATION_SCHALG_SP (0x3 << 5)
+#define MTL_OPERATION_RAA BIT(2)
+#define MTL_OPERATION_RAA_SP (0x0 << 2)
+#define MTL_OPERATION_RAA_WSP (0x1 << 2)
+
+#define MTL_INT_STATUS 0x00000c20
+#define MTL_INT_QX(x) BIT(x)
+
+#define MTL_RXQ_DMA_MAP0 0x00000c30 /* queue 0 to 3 */
+#define MTL_RXQ_DMA_MAP1 0x00000c34 /* queue 4 to 7 */
+#define MTL_RXQ_DMA_QXMDMACH_MASK(x) (0xf << 8 * (x))
+#define MTL_RXQ_DMA_QXMDMACH(chan, q) ((chan) << (8 * (q)))
+
+#define MTL_CHAN_BASE_ADDR 0x00000d00
+#define MTL_CHAN_BASE_OFFSET 0x40
+
+static inline u32 mtl_chanx_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->mtl_chan + (x * addrs->mtl_chan_offset);
+ else
+ addr = MTL_CHAN_BASE_ADDR + (x * MTL_CHAN_BASE_OFFSET);
+
+ return addr;
+}
+
+#define MTL_CHAN_TX_OP_MODE(addrs, x) mtl_chanx_base_addr(addrs, x)
+#define MTL_CHAN_TX_DEBUG(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x8)
+#define MTL_CHAN_INT_CTRL(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x2c)
+#define MTL_CHAN_RX_OP_MODE(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x30)
+#define MTL_CHAN_RX_DEBUG(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x38)
+
+#define MTL_OP_MODE_RSF BIT(5)
+#define MTL_OP_MODE_TXQEN_MASK GENMASK(3, 2)
+#define MTL_OP_MODE_TXQEN_AV BIT(2)
+#define MTL_OP_MODE_TXQEN BIT(3)
+#define MTL_OP_MODE_TSF BIT(1)
+
+#define MTL_OP_MODE_TQS_MASK GENMASK(24, 16)
+
+#define MTL_OP_MODE_TTC_MASK GENMASK(6, 4)
+#define MTL_OP_MODE_TTC_32 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 0)
+#define MTL_OP_MODE_TTC_64 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 1)
+#define MTL_OP_MODE_TTC_96 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 2)
+#define MTL_OP_MODE_TTC_128 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 3)
+#define MTL_OP_MODE_TTC_192 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 4)
+#define MTL_OP_MODE_TTC_256 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 5)
+#define MTL_OP_MODE_TTC_384 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 6)
+#define MTL_OP_MODE_TTC_512 FIELD_PREP(MTL_OP_MODE_TTC_MASK, 7)
+
+#define MTL_OP_MODE_RQS_MASK GENMASK(29, 20)
+
+#define MTL_OP_MODE_RFD_MASK GENMASK(19, 14)
+
+#define MTL_OP_MODE_RFA_MASK GENMASK(13, 8)
+
+#define MTL_OP_MODE_EHFC BIT(7)
+#define MTL_OP_MODE_DIS_TCP_EF BIT(6)
+
+#define MTL_OP_MODE_RTC_MASK GENMASK(1, 0)
+
+#define MTL_OP_MODE_RTC_32 FIELD_PREP(MTL_OP_MODE_RTC_MASK, 1)
+#define MTL_OP_MODE_RTC_64 FIELD_PREP(MTL_OP_MODE_RTC_MASK, 0)
+#define MTL_OP_MODE_RTC_96 FIELD_PREP(MTL_OP_MODE_RTC_MASK, 2)
+#define MTL_OP_MODE_RTC_128 FIELD_PREP(MTL_OP_MODE_RTC_MASK, 3)
+
+/* MTL ETS Control register */
+#define MTL_ETS_CTRL_BASE_ADDR 0x00000d10
+#define MTL_ETS_CTRL_BASE_OFFSET 0x40
+
+static inline u32 mtl_etsx_ctrl_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->mtl_ets_ctrl + (x * addrs->mtl_ets_ctrl_offset);
+ else
+ addr = MTL_ETS_CTRL_BASE_ADDR + (x * MTL_ETS_CTRL_BASE_OFFSET);
+
+ return addr;
+}
+
+#define MTL_ETS_CTRL_CC BIT(3)
+#define MTL_ETS_CTRL_AVALG BIT(2)
+
+/* MTL Queue Quantum Weight */
+#define MTL_TXQ_WEIGHT_BASE_ADDR 0x00000d18
+#define MTL_TXQ_WEIGHT_BASE_OFFSET 0x40
+
+static inline u32 mtl_txqx_weight_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->mtl_txq_weight + (x * addrs->mtl_txq_weight_offset);
+ else
+ addr = MTL_TXQ_WEIGHT_BASE_ADDR + (x * MTL_TXQ_WEIGHT_BASE_OFFSET);
+
+ return addr;
+}
+
+#define MTL_TXQ_WEIGHT_ISCQW_MASK GENMASK(20, 0)
+
+/* MTL sendSlopeCredit register */
+#define MTL_SEND_SLP_CRED_BASE_ADDR 0x00000d1c
+#define MTL_SEND_SLP_CRED_OFFSET 0x40
+
+static inline u32 mtl_send_slp_credx_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->mtl_send_slp_cred + (x * addrs->mtl_send_slp_cred_offset);
+ else
+ addr = MTL_SEND_SLP_CRED_BASE_ADDR + (x * MTL_SEND_SLP_CRED_OFFSET);
+
+ return addr;
+}
+
+#define MTL_SEND_SLP_CRED_SSC_MASK GENMASK(13, 0)
+
+/* MTL hiCredit register */
+#define MTL_HIGH_CRED_BASE_ADDR 0x00000d20
+#define MTL_HIGH_CRED_OFFSET 0x40
+
+static inline u32 mtl_high_credx_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->mtl_high_cred + (x * addrs->mtl_high_cred_offset);
+ else
+ addr = MTL_HIGH_CRED_BASE_ADDR + (x * MTL_HIGH_CRED_OFFSET);
+
+ return addr;
+}
+
+#define MTL_HIGH_CRED_HC_MASK GENMASK(28, 0)
+
+/* MTL loCredit register */
+#define MTL_LOW_CRED_BASE_ADDR 0x00000d24
+#define MTL_LOW_CRED_OFFSET 0x40
+
+static inline u32 mtl_low_credx_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->mtl_low_cred + (x * addrs->mtl_low_cred_offset);
+ else
+ addr = MTL_LOW_CRED_BASE_ADDR + (x * MTL_LOW_CRED_OFFSET);
+
+ return addr;
+}
+
+#define MTL_HIGH_CRED_LC_MASK GENMASK(28, 0)
+
+/* MTL debug */
+#define MTL_DEBUG_TXSTSFSTS BIT(5)
+#define MTL_DEBUG_TXFSTS BIT(4)
+#define MTL_DEBUG_TWCSTS BIT(3)
+
+/* MTL debug: Tx FIFO Read Controller Status */
+#define MTL_DEBUG_TRCSTS_MASK GENMASK(2, 1)
+#define MTL_DEBUG_TRCSTS_IDLE 0
+#define MTL_DEBUG_TRCSTS_READ 1
+#define MTL_DEBUG_TRCSTS_TXW 2
+#define MTL_DEBUG_TRCSTS_WRITE 3
+#define MTL_DEBUG_TXPAUSED BIT(0)
+
+/* MAC debug: GMII or MII Transmit Protocol Engine Status */
+#define MTL_DEBUG_RXFSTS_MASK GENMASK(5, 4)
+#define MTL_DEBUG_RXFSTS_EMPTY 0
+#define MTL_DEBUG_RXFSTS_BT 1
+#define MTL_DEBUG_RXFSTS_AT 2
+#define MTL_DEBUG_RXFSTS_FULL 3
+#define MTL_DEBUG_RRCSTS_MASK GENMASK(2, 1)
+#define MTL_DEBUG_RRCSTS_IDLE 0
+#define MTL_DEBUG_RRCSTS_RDATA 1
+#define MTL_DEBUG_RRCSTS_RSTAT 2
+#define MTL_DEBUG_RRCSTS_FLUSH 3
+#define MTL_DEBUG_RWCSTS BIT(0)
+
+/* MTL interrupt */
+#define MTL_RX_OVERFLOW_INT_EN BIT(24)
+#define MTL_RX_OVERFLOW_INT BIT(16)
+
+/* Default operating mode of the MAC */
+#define GMAC_CORE_INIT (GMAC_CONFIG_JD | GMAC_CONFIG_PS | \
+ GMAC_CONFIG_BE | GMAC_CONFIG_DCRS | \
+ GMAC_CONFIG_JE)
+
+/* To dump the core regs excluding the Address Registers */
+#define GMAC_REG_NUM 132
+
+/* SGMII/RGMII status register */
+#define GMAC_PHYIF_CTRLSTATUS_TC BIT(0)
+#define GMAC_PHYIF_CTRLSTATUS_LUD BIT(1)
+#define GMAC_PHYIF_CTRLSTATUS_SMIDRXS BIT(4)
+#define GMAC_PHYIF_CTRLSTATUS_RSGMII_MASK GENMASK(31, 16)
+
+extern const struct stmmac_dma_ops dwmac4_dma_ops;
+extern const struct stmmac_dma_ops dwmac410_dma_ops;
+#endif /* __DWMAC4_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c
new file mode 100644
index 000000000..c6fcfae27
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c
@@ -0,0 +1,1045 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This is the driver for the GMAC on-chip Ethernet controller for ST SoCs.
+ * DWC Ether MAC version 4.00 has been used for developing this code.
+ *
+ * This only implements the mac core functions for this chip.
+ *
+ * Copyright (C) 2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#include <linux/crc32.h>
+#include <linux/slab.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include "stmmac.h"
+#include "stmmac_fpe.h"
+#include "stmmac_pcs.h"
+#include "stmmac_vlan.h"
+#include "dwmac4.h"
+#include "dwmac5.h"
+
+static const struct stmmac_pcs_info dwmac4_pcs_info = {
+ .pcs_offset = GMAC_PCS_BASE,
+ .rgsmii_offset = GMAC_PHYIF_CONTROL_STATUS,
+ .rgsmii_status_mask = GMAC_PHYIF_CTRLSTATUS_RSGMII_MASK,
+ .int_mask = GMAC_INT_PCS_LINK | GMAC_INT_PCS_ANE,
+};
+
+static int dwmac4_pcs_init(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.pcs)
+ return 0;
+
+ return stmmac_integrated_pcs_init(priv, &dwmac4_pcs_info);
+}
+
+static void dwmac4_core_init(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ void __iomem *ioaddr = hw->pcsr;
+ unsigned long clk_rate;
+ u32 value;
+
+ value = readl(ioaddr + GMAC_CONFIG);
+ writel(value | GMAC_CORE_INIT, ioaddr + GMAC_CONFIG);
+
+ /* Configure LPI 1us counter to number of CSR clock ticks in 1us - 1 */
+ clk_rate = clk_get_rate(priv->plat->stmmac_clk);
+ writel((clk_rate / 1000000) - 1, ioaddr + GMAC4_MAC_ONEUS_TIC_COUNTER);
+
+ /* Enable GMAC interrupts */
+ writel(GMAC_INT_DEFAULT_ENABLE, ioaddr + GMAC_INT_EN);
+
+ if (GMAC_INT_DEFAULT_ENABLE & GMAC_INT_TSIE)
+ init_waitqueue_head(&priv->tstamp_busy_wait);
+}
+
+static void dwmac4_irq_modify(struct mac_device_info *hw, u32 disable,
+ u32 enable)
+{
+ void __iomem *int_mask = hw->pcsr + GMAC_INT_EN;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&hw->irq_ctrl_lock, flags);
+ value = readl(int_mask) & ~disable;
+ value |= enable;
+ writel(value, int_mask);
+ spin_unlock_irqrestore(&hw->irq_ctrl_lock, flags);
+}
+
+static void dwmac4_update_caps(struct stmmac_priv *priv)
+{
+ if (priv->plat->tx_queues_to_use > 1)
+ priv->hw->link.caps &= ~(MAC_10HD | MAC_100HD | MAC_1000HD);
+ else
+ priv->hw->link.caps |= (MAC_10HD | MAC_100HD | MAC_1000HD);
+}
+
+static void dwmac4_rx_queue_enable(struct mac_device_info *hw,
+ u8 mode, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + GMAC_RXQ_CTRL0);
+
+ value &= GMAC_RX_QUEUE_CLEAR(queue);
+ if (mode == MTL_QUEUE_AVB)
+ value |= GMAC_RX_AV_QUEUE_ENABLE(queue);
+ else if (mode == MTL_QUEUE_DCB)
+ value |= GMAC_RX_DCB_QUEUE_ENABLE(queue);
+
+ writel(value, ioaddr + GMAC_RXQ_CTRL0);
+}
+
+static void dwmac4_rx_queue_priority(struct mac_device_info *hw,
+ u32 prio, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 clear_mask = 0;
+ u32 ctrl2, ctrl3;
+ int i;
+
+ ctrl2 = readl(ioaddr + GMAC_RXQ_CTRL2);
+ ctrl3 = readl(ioaddr + GMAC_RXQ_CTRL3);
+
+ /* The software must ensure that the same priority
+ * is not mapped to multiple Rx queues
+ */
+ for (i = 0; i < 4; i++)
+ clear_mask |= ((prio << GMAC_RXQCTRL_PSRQX_SHIFT(i)) &
+ GMAC_RXQCTRL_PSRQX_MASK(i));
+
+ ctrl2 &= ~clear_mask;
+ ctrl3 &= ~clear_mask;
+
+ /* First assign new priorities to a queue, then
+ * clear them from others queues
+ */
+ if (queue < 4) {
+ ctrl2 |= (prio << GMAC_RXQCTRL_PSRQX_SHIFT(queue)) &
+ GMAC_RXQCTRL_PSRQX_MASK(queue);
+
+ writel(ctrl2, ioaddr + GMAC_RXQ_CTRL2);
+ writel(ctrl3, ioaddr + GMAC_RXQ_CTRL3);
+ } else {
+ queue -= 4;
+
+ ctrl3 |= (prio << GMAC_RXQCTRL_PSRQX_SHIFT(queue)) &
+ GMAC_RXQCTRL_PSRQX_MASK(queue);
+
+ writel(ctrl3, ioaddr + GMAC_RXQ_CTRL3);
+ writel(ctrl2, ioaddr + GMAC_RXQ_CTRL2);
+ }
+}
+
+static void dwmac4_tx_queue_priority(struct mac_device_info *hw,
+ u32 prio, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 base_register;
+ u32 value;
+
+ base_register = (queue < 4) ? GMAC_TXQ_PRTY_MAP0 : GMAC_TXQ_PRTY_MAP1;
+ if (queue >= 4)
+ queue -= 4;
+
+ value = readl(ioaddr + base_register);
+
+ value &= ~GMAC_TXQCTRL_PSTQX_MASK(queue);
+ value |= (prio << GMAC_TXQCTRL_PSTQX_SHIFT(queue)) &
+ GMAC_TXQCTRL_PSTQX_MASK(queue);
+
+ writel(value, ioaddr + base_register);
+}
+
+static void dwmac4_rx_queue_routing(struct mac_device_info *hw,
+ u8 packet, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ static const struct stmmac_rx_routing route_possibilities[] = {
+ { GMAC_RXQCTRL_AVCPQ_MASK, GMAC_RXQCTRL_AVCPQ_SHIFT },
+ { GMAC_RXQCTRL_PTPQ_MASK, GMAC_RXQCTRL_PTPQ_SHIFT },
+ { GMAC_RXQCTRL_DCBCPQ_MASK, GMAC_RXQCTRL_DCBCPQ_SHIFT },
+ { GMAC_RXQCTRL_UPQ_MASK, GMAC_RXQCTRL_UPQ_SHIFT },
+ { GMAC_RXQCTRL_MCBCQ_MASK, GMAC_RXQCTRL_MCBCQ_SHIFT },
+ };
+
+ value = readl(ioaddr + GMAC_RXQ_CTRL1);
+
+ /* routing configuration */
+ value &= ~route_possibilities[packet - 1].reg_mask;
+ value |= (queue << route_possibilities[packet-1].reg_shift) &
+ route_possibilities[packet - 1].reg_mask;
+
+ /* some packets require extra ops */
+ if (packet == PACKET_AVCPQ) {
+ value &= ~GMAC_RXQCTRL_TACPQE;
+ value |= 0x1 << GMAC_RXQCTRL_TACPQE_SHIFT;
+ } else if (packet == PACKET_MCBCQ) {
+ value &= ~GMAC_RXQCTRL_MCBCQEN;
+ value |= 0x1 << GMAC_RXQCTRL_MCBCQEN_SHIFT;
+ }
+
+ writel(value, ioaddr + GMAC_RXQ_CTRL1);
+}
+
+static void dwmac4_prog_mtl_rx_algorithms(struct mac_device_info *hw,
+ u32 rx_alg)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + MTL_OPERATION_MODE);
+
+ value &= ~MTL_OPERATION_RAA;
+ switch (rx_alg) {
+ case MTL_RX_ALGORITHM_SP:
+ value |= MTL_OPERATION_RAA_SP;
+ break;
+ case MTL_RX_ALGORITHM_WSP:
+ value |= MTL_OPERATION_RAA_WSP;
+ break;
+ default:
+ break;
+ }
+
+ writel(value, ioaddr + MTL_OPERATION_MODE);
+}
+
+static void dwmac4_prog_mtl_tx_algorithms(struct mac_device_info *hw,
+ u32 tx_alg)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + MTL_OPERATION_MODE);
+
+ value &= ~MTL_OPERATION_SCHALG_MASK;
+ switch (tx_alg) {
+ case MTL_TX_ALGORITHM_WRR:
+ value |= MTL_OPERATION_SCHALG_WRR;
+ break;
+ case MTL_TX_ALGORITHM_WFQ:
+ value |= MTL_OPERATION_SCHALG_WFQ;
+ break;
+ case MTL_TX_ALGORITHM_DWRR:
+ value |= MTL_OPERATION_SCHALG_DWRR;
+ break;
+ case MTL_TX_ALGORITHM_SP:
+ value |= MTL_OPERATION_SCHALG_SP;
+ break;
+ default:
+ break;
+ }
+
+ writel(value, ioaddr + MTL_OPERATION_MODE);
+}
+
+static void dwmac4_set_mtl_tx_queue_weight(struct stmmac_priv *priv,
+ struct mac_device_info *hw,
+ u32 weight, u32 queue)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + mtl_txqx_weight_base_addr(dwmac4_addrs,
+ queue));
+
+ value &= ~MTL_TXQ_WEIGHT_ISCQW_MASK;
+ value |= weight & MTL_TXQ_WEIGHT_ISCQW_MASK;
+ writel(value, ioaddr + mtl_txqx_weight_base_addr(dwmac4_addrs, queue));
+}
+
+static void dwmac4_map_mtl_dma(struct mac_device_info *hw, u32 queue, u32 chan)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ if (queue < 4) {
+ value = readl(ioaddr + MTL_RXQ_DMA_MAP0);
+ value &= ~MTL_RXQ_DMA_QXMDMACH_MASK(queue);
+ value |= MTL_RXQ_DMA_QXMDMACH(chan, queue);
+ writel(value, ioaddr + MTL_RXQ_DMA_MAP0);
+ } else {
+ value = readl(ioaddr + MTL_RXQ_DMA_MAP1);
+ value &= ~MTL_RXQ_DMA_QXMDMACH_MASK(queue - 4);
+ value |= MTL_RXQ_DMA_QXMDMACH(chan, queue - 4);
+ writel(value, ioaddr + MTL_RXQ_DMA_MAP1);
+ }
+}
+
+static void dwmac4_config_cbs(struct stmmac_priv *priv,
+ struct mac_device_info *hw,
+ u32 send_slope, u32 idle_slope,
+ u32 high_credit, u32 low_credit, u32 queue)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ pr_debug("Queue %d configured as AVB. Parameters:\n", queue);
+ pr_debug("\tsend_slope: 0x%08x\n", send_slope);
+ pr_debug("\tidle_slope: 0x%08x\n", idle_slope);
+ pr_debug("\thigh_credit: 0x%08x\n", high_credit);
+ pr_debug("\tlow_credit: 0x%08x\n", low_credit);
+
+ /* enable AV algorithm */
+ value = readl(ioaddr + mtl_etsx_ctrl_base_addr(dwmac4_addrs, queue));
+ value |= MTL_ETS_CTRL_AVALG;
+ value |= MTL_ETS_CTRL_CC;
+ writel(value, ioaddr + mtl_etsx_ctrl_base_addr(dwmac4_addrs, queue));
+
+ /* configure send slope */
+ value = readl(ioaddr + mtl_send_slp_credx_base_addr(dwmac4_addrs,
+ queue));
+ value &= ~MTL_SEND_SLP_CRED_SSC_MASK;
+ value |= send_slope & MTL_SEND_SLP_CRED_SSC_MASK;
+ writel(value, ioaddr + mtl_send_slp_credx_base_addr(dwmac4_addrs,
+ queue));
+
+ /* configure idle slope (same register as tx weight) */
+ dwmac4_set_mtl_tx_queue_weight(priv, hw, idle_slope, queue);
+
+ /* configure high credit */
+ value = readl(ioaddr + mtl_high_credx_base_addr(dwmac4_addrs, queue));
+ value &= ~MTL_HIGH_CRED_HC_MASK;
+ value |= high_credit & MTL_HIGH_CRED_HC_MASK;
+ writel(value, ioaddr + mtl_high_credx_base_addr(dwmac4_addrs, queue));
+
+ /* configure high credit */
+ value = readl(ioaddr + mtl_low_credx_base_addr(dwmac4_addrs, queue));
+ value &= ~MTL_HIGH_CRED_LC_MASK;
+ value |= low_credit & MTL_HIGH_CRED_LC_MASK;
+ writel(value, ioaddr + mtl_low_credx_base_addr(dwmac4_addrs, queue));
+}
+
+static void dwmac4_dump_regs(struct mac_device_info *hw, u32 *reg_space)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int i;
+
+ for (i = 0; i < GMAC_REG_NUM; i++)
+ reg_space[i] = readl(ioaddr + i * 4);
+}
+
+static int dwmac4_rx_ipc_enable(struct mac_device_info *hw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + GMAC_CONFIG);
+
+ if (hw->rx_csum)
+ value |= GMAC_CONFIG_IPC;
+ else
+ value &= ~GMAC_CONFIG_IPC;
+
+ writel(value, ioaddr + GMAC_CONFIG);
+
+ value = readl(ioaddr + GMAC_CONFIG);
+
+ return !!(value & GMAC_CONFIG_IPC);
+}
+
+static void dwmac4_pmt(struct mac_device_info *hw, unsigned long mode)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ unsigned int pmt = 0;
+ u32 config;
+
+ if (mode & WAKE_MAGIC) {
+ pr_debug("GMAC: WOL Magic frame\n");
+ pmt |= power_down | magic_pkt_en;
+ }
+ if (mode & WAKE_UCAST) {
+ pr_debug("GMAC: WOL on global unicast\n");
+ pmt |= power_down | global_unicast | wake_up_frame_en;
+ }
+
+ if (pmt) {
+ /* The receiver must be enabled for WOL before powering down */
+ config = readl(ioaddr + GMAC_CONFIG);
+ config |= GMAC_CONFIG_RE;
+ writel(config, ioaddr + GMAC_CONFIG);
+ }
+ writel(pmt, ioaddr + GMAC_PMT);
+}
+
+static void dwmac4_set_umac_addr(struct mac_device_info *hw,
+ const unsigned char *addr, unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+
+ stmmac_dwmac4_set_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
+ GMAC_ADDR_LOW(reg_n));
+}
+
+static void dwmac4_get_umac_addr(struct mac_device_info *hw,
+ unsigned char *addr, unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+
+ stmmac_get_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
+ GMAC_ADDR_LOW(reg_n));
+}
+
+static int dwmac4_set_lpi_mode(struct mac_device_info *hw,
+ enum stmmac_lpi_mode mode,
+ bool en_tx_lpi_clockgating, u32 et)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value, mask;
+
+ if (mode == STMMAC_LPI_DISABLE) {
+ value = 0;
+ } else {
+ value = LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA;
+
+ if (mode == STMMAC_LPI_TIMER) {
+ /* Return ERANGE if the timer is larger than the
+ * register field.
+ */
+ if (et > STMMAC_ET_MAX)
+ return -ERANGE;
+
+ /* Set the hardware LPI entry timer */
+ writel(et, ioaddr + GMAC4_LPI_ENTRY_TIMER);
+
+ /* Interpret a zero LPI entry timer to mean
+ * immediate entry into LPI mode.
+ */
+ if (et)
+ value |= LPI_CTRL_STATUS_LPIATE;
+ }
+
+ if (en_tx_lpi_clockgating)
+ value |= LPI_CTRL_STATUS_LPITCSE;
+ }
+
+ mask = LPI_CTRL_STATUS_LPIATE | LPI_CTRL_STATUS_LPIEN |
+ LPI_CTRL_STATUS_LPITXA | LPI_CTRL_STATUS_LPITCSE;
+
+ value |= readl(ioaddr + GMAC4_LPI_CTRL_STATUS) & ~mask;
+ writel(value, ioaddr + GMAC4_LPI_CTRL_STATUS);
+
+ return 0;
+}
+
+static void dwmac4_set_eee_pls(struct mac_device_info *hw, int link)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + GMAC4_LPI_CTRL_STATUS);
+
+ if (link)
+ value |= LPI_CTRL_STATUS_PLS;
+ else
+ value &= ~LPI_CTRL_STATUS_PLS;
+
+ writel(value, ioaddr + GMAC4_LPI_CTRL_STATUS);
+}
+
+static void dwmac4_set_eee_timer(struct mac_device_info *hw, int ls, int tw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int value = ((tw & 0xffff)) | ((ls & 0x3ff) << 16);
+
+ /* Program the timers in the LPI timer control register:
+ * LS: minimum time (ms) for which the link
+ * status from PHY should be ok before transmitting
+ * the LPI pattern.
+ * TW: minimum time (us) for which the core waits
+ * after it has stopped transmitting the LPI pattern.
+ */
+ writel(value, ioaddr + GMAC4_LPI_TIMER_CTRL);
+}
+
+static void dwmac4_set_filter(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = (void __iomem *)dev->base_addr;
+ int numhashregs = (hw->multicast_filter_bins >> 5);
+ int mcbitslog2 = hw->mcast_bits_log2;
+ unsigned int value;
+ u32 mc_filter[8];
+ int i;
+
+ memset(mc_filter, 0, sizeof(mc_filter));
+
+ value = readl(ioaddr + GMAC_PACKET_FILTER);
+ value &= ~GMAC_PACKET_FILTER_HMC;
+ value &= ~GMAC_PACKET_FILTER_HPF;
+ value &= ~GMAC_PACKET_FILTER_PCF;
+ value &= ~GMAC_PACKET_FILTER_PM;
+ value &= ~GMAC_PACKET_FILTER_PR;
+ value &= ~GMAC_PACKET_FILTER_RA;
+ if (dev->flags & IFF_PROMISC) {
+ /* VLAN Tag Filter Fail Packets Queuing */
+ if (hw->vlan_fail_q_en) {
+ value = readl(ioaddr + GMAC_RXQ_CTRL4);
+ value &= ~GMAC_RXQCTRL_VFFQ_MASK;
+ value |= GMAC_RXQCTRL_VFFQE |
+ (hw->vlan_fail_q << GMAC_RXQCTRL_VFFQ_SHIFT);
+ writel(value, ioaddr + GMAC_RXQ_CTRL4);
+ value = GMAC_PACKET_FILTER_PR | GMAC_PACKET_FILTER_RA;
+ } else {
+ value = GMAC_PACKET_FILTER_PR | GMAC_PACKET_FILTER_PCF;
+ }
+
+ } else if ((dev->flags & IFF_ALLMULTI) ||
+ (netdev_mc_count(dev) > hw->multicast_filter_bins)) {
+ /* Pass all multi */
+ value |= GMAC_PACKET_FILTER_PM;
+ /* Set all the bits of the HASH tab */
+ memset(mc_filter, 0xff, sizeof(mc_filter));
+ } else if (!netdev_mc_empty(dev) && (dev->flags & IFF_MULTICAST)) {
+ struct netdev_hw_addr *ha;
+
+ /* Hash filter for multicast */
+ value |= GMAC_PACKET_FILTER_HMC;
+
+ netdev_for_each_mc_addr(ha, dev) {
+ /* The upper n bits of the calculated CRC are used to
+ * index the contents of the hash table. The number of
+ * bits used depends on the hardware configuration
+ * selected at core configuration time.
+ */
+ u32 bit_nr = bitrev32(~crc32_le(~0, ha->addr,
+ ETH_ALEN)) >> (32 - mcbitslog2);
+ /* The most significant bit determines the register to
+ * use (H/L) while the other 5 bits determine the bit
+ * within the register.
+ */
+ mc_filter[bit_nr >> 5] |= (1 << (bit_nr & 0x1f));
+ }
+ }
+
+ for (i = 0; i < numhashregs; i++)
+ writel(mc_filter[i], ioaddr + GMAC_HASH_TAB(i));
+
+ value |= GMAC_PACKET_FILTER_HPF;
+
+ /* Handle multiple unicast addresses */
+ if (netdev_uc_count(dev) > hw->unicast_filter_entries) {
+ /* Switch to promiscuous mode if more than 128 addrs
+ * are required
+ */
+ value |= GMAC_PACKET_FILTER_PR;
+ } else {
+ struct netdev_hw_addr *ha;
+ int reg = 1;
+
+ netdev_for_each_uc_addr(ha, dev) {
+ dwmac4_set_umac_addr(hw, ha->addr, reg);
+ reg++;
+ }
+
+ while (reg < GMAC_MAX_PERFECT_ADDRESSES) {
+ writel(0, ioaddr + GMAC_ADDR_HIGH(reg));
+ writel(0, ioaddr + GMAC_ADDR_LOW(reg));
+ reg++;
+ }
+ }
+
+ /* VLAN filtering */
+ if (dev->flags & IFF_PROMISC && !hw->vlan_fail_q_en)
+ value &= ~GMAC_PACKET_FILTER_VTFE;
+ else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER)
+ value |= GMAC_PACKET_FILTER_VTFE;
+
+ writel(value, ioaddr + GMAC_PACKET_FILTER);
+}
+
+static void dwmac4_flow_ctrl(struct mac_device_info *hw, unsigned int duplex,
+ unsigned int fc, unsigned int pause_time,
+ u8 tx_cnt)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ unsigned int flow = 0;
+ u8 queue;
+
+ pr_debug("GMAC Flow-Control:\n");
+ if (fc & FLOW_RX) {
+ pr_debug("\tReceive Flow-Control ON\n");
+ flow |= GMAC_RX_FLOW_CTRL_RFE;
+ } else {
+ pr_debug("\tReceive Flow-Control OFF\n");
+ }
+ writel(flow, ioaddr + GMAC_RX_FLOW_CTRL);
+
+ if (fc & FLOW_TX) {
+ pr_debug("\tTransmit Flow-Control ON\n");
+
+ if (duplex)
+ pr_debug("\tduplex mode: PAUSE %d\n", pause_time);
+
+ for (queue = 0; queue < tx_cnt; queue++) {
+ flow = GMAC_TX_FLOW_CTRL_TFE;
+
+ if (duplex)
+ flow |= FIELD_PREP(GMAC_TX_FLOW_CTRL_PT_MASK,
+ pause_time);
+
+ writel(flow, ioaddr + GMAC_QX_TX_FLOW_CTRL(queue));
+ }
+ } else {
+ for (queue = 0; queue < tx_cnt; queue++)
+ writel(0, ioaddr + GMAC_QX_TX_FLOW_CTRL(queue));
+ }
+}
+
+static void dwmac4_ctrl_ane(struct stmmac_priv *priv, bool ane, bool srgmi_ral)
+{
+ dwmac_ctrl_ane(priv->ioaddr, GMAC_PCS_BASE, ane, srgmi_ral);
+}
+
+static int dwmac4_irq_mtl_status(struct stmmac_priv *priv,
+ struct mac_device_info *hw, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ void __iomem *ioaddr = hw->pcsr;
+ u32 mtl_int_qx_status;
+ int ret = 0;
+
+ mtl_int_qx_status = readl(ioaddr + MTL_INT_STATUS);
+
+ /* Check MTL Interrupt */
+ if (mtl_int_qx_status & MTL_INT_QX(chan)) {
+ /* read Queue x Interrupt status */
+ u32 status = readl(ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs,
+ chan));
+
+ if (status & MTL_RX_OVERFLOW_INT) {
+ /* clear Interrupt */
+ writel(status | MTL_RX_OVERFLOW_INT,
+ ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs, chan));
+ ret = CORE_IRQ_MTL_RX_OVERFLOW;
+ }
+ }
+
+ return ret;
+}
+
+static int dwmac4_irq_status(struct stmmac_priv *priv,
+ struct stmmac_extra_stats *x)
+{
+ void __iomem *ioaddr = priv->hw->pcsr;
+ u32 intr_status = readl(ioaddr + GMAC_INT_STATUS);
+ u32 intr_enable = readl(ioaddr + GMAC_INT_EN);
+ int ret = 0;
+
+ /* Discard disabled bits */
+ intr_status &= intr_enable;
+
+ /* Not used events (e.g. MMC interrupts) are not handled. */
+ if ((intr_status & mmc_tx_irq))
+ x->mmc_tx_irq_n++;
+ if (unlikely(intr_status & mmc_rx_irq))
+ x->mmc_rx_irq_n++;
+ if (unlikely(intr_status & mmc_rx_csum_offload_irq))
+ x->mmc_rx_csum_offload_irq_n++;
+ /* Clear the PMT bits 5 and 6 by reading the PMT status reg */
+ if (unlikely(intr_status & pmt_irq)) {
+ readl(ioaddr + GMAC_PMT);
+ x->irq_receive_pmt_irq_n++;
+ }
+
+ /* MAC tx/rx EEE LPI entry/exit interrupts */
+ if (intr_status & lpi_irq) {
+ /* Clear LPI interrupt by reading MAC_LPI_Control_Status */
+ u32 status = readl(ioaddr + GMAC4_LPI_CTRL_STATUS);
+
+ if (status & LPI_CTRL_STATUS_TLPIEN) {
+ ret |= CORE_IRQ_TX_PATH_IN_LPI_MODE;
+ x->irq_tx_path_in_lpi_mode_n++;
+ }
+ if (status & LPI_CTRL_STATUS_TLPIEX) {
+ ret |= CORE_IRQ_TX_PATH_EXIT_LPI_MODE;
+ x->irq_tx_path_exit_lpi_mode_n++;
+ }
+ if (status & LPI_CTRL_STATUS_RLPIEN)
+ x->irq_rx_path_in_lpi_mode_n++;
+ if (status & LPI_CTRL_STATUS_RLPIEX)
+ x->irq_rx_path_exit_lpi_mode_n++;
+ }
+
+ if (intr_status & (PCS_ANE_IRQ | PCS_LINK_IRQ))
+ stmmac_integrated_pcs_irq(priv, intr_status, x);
+
+ return ret;
+}
+
+static void dwmac4_debug(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x,
+ u32 rx_queues, u32 tx_queues)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value;
+ u32 queue;
+
+ for (queue = 0; queue < tx_queues; queue++) {
+ value = readl(ioaddr + MTL_CHAN_TX_DEBUG(dwmac4_addrs, queue));
+
+ if (value & MTL_DEBUG_TXSTSFSTS)
+ x->mtl_tx_status_fifo_full++;
+ if (value & MTL_DEBUG_TXFSTS)
+ x->mtl_tx_fifo_not_empty++;
+ if (value & MTL_DEBUG_TWCSTS)
+ x->mmtl_fifo_ctrl++;
+ if (value & MTL_DEBUG_TRCSTS_MASK) {
+ u32 trcsts = FIELD_GET(MTL_DEBUG_TRCSTS_MASK, value);
+
+ if (trcsts == MTL_DEBUG_TRCSTS_WRITE)
+ x->mtl_tx_fifo_read_ctrl_write++;
+ else if (trcsts == MTL_DEBUG_TRCSTS_TXW)
+ x->mtl_tx_fifo_read_ctrl_wait++;
+ else if (trcsts == MTL_DEBUG_TRCSTS_READ)
+ x->mtl_tx_fifo_read_ctrl_read++;
+ else
+ x->mtl_tx_fifo_read_ctrl_idle++;
+ }
+ if (value & MTL_DEBUG_TXPAUSED)
+ x->mac_tx_in_pause++;
+ }
+
+ for (queue = 0; queue < rx_queues; queue++) {
+ value = readl(ioaddr + MTL_CHAN_RX_DEBUG(dwmac4_addrs, queue));
+
+ if (value & MTL_DEBUG_RXFSTS_MASK) {
+ u32 rxfsts = FIELD_GET(MTL_DEBUG_RXFSTS_MASK, value);
+
+ if (rxfsts == MTL_DEBUG_RXFSTS_FULL)
+ x->mtl_rx_fifo_fill_level_full++;
+ else if (rxfsts == MTL_DEBUG_RXFSTS_AT)
+ x->mtl_rx_fifo_fill_above_thresh++;
+ else if (rxfsts == MTL_DEBUG_RXFSTS_BT)
+ x->mtl_rx_fifo_fill_below_thresh++;
+ else
+ x->mtl_rx_fifo_fill_level_empty++;
+ }
+ if (value & MTL_DEBUG_RRCSTS_MASK) {
+ u32 rrcsts = FIELD_GET(MTL_DEBUG_RRCSTS_MASK, value);
+
+ if (rrcsts == MTL_DEBUG_RRCSTS_FLUSH)
+ x->mtl_rx_fifo_read_ctrl_flush++;
+ else if (rrcsts == MTL_DEBUG_RRCSTS_RSTAT)
+ x->mtl_rx_fifo_read_ctrl_read_data++;
+ else if (rrcsts == MTL_DEBUG_RRCSTS_RDATA)
+ x->mtl_rx_fifo_read_ctrl_status++;
+ else
+ x->mtl_rx_fifo_read_ctrl_idle++;
+ }
+ if (value & MTL_DEBUG_RWCSTS)
+ x->mtl_rx_fifo_ctrl_active++;
+ }
+
+ /* GMAC debug */
+ value = readl(ioaddr + GMAC_DEBUG);
+
+ if (value & GMAC_DEBUG_TFCSTS_MASK) {
+ u32 tfcsts = FIELD_GET(GMAC_DEBUG_TFCSTS_MASK, value);
+
+ if (tfcsts == GMAC_DEBUG_TFCSTS_XFER)
+ x->mac_tx_frame_ctrl_xfer++;
+ else if (tfcsts == GMAC_DEBUG_TFCSTS_GEN_PAUSE)
+ x->mac_tx_frame_ctrl_pause++;
+ else if (tfcsts == GMAC_DEBUG_TFCSTS_WAIT)
+ x->mac_tx_frame_ctrl_wait++;
+ else
+ x->mac_tx_frame_ctrl_idle++;
+ }
+ if (value & GMAC_DEBUG_TPESTS)
+ x->mac_gmii_tx_proto_engine++;
+ if (value & GMAC_DEBUG_RFCFCSTS_MASK)
+ x->mac_rx_frame_ctrl_fifo = FIELD_GET(GMAC_DEBUG_RFCFCSTS_MASK,
+ value);
+ if (value & GMAC_DEBUG_RPESTS)
+ x->mac_gmii_rx_proto_engine++;
+}
+
+static void dwmac4_set_mac_loopback(void __iomem *ioaddr, bool enable)
+{
+ u32 value = readl(ioaddr + GMAC_CONFIG);
+
+ if (enable)
+ value |= GMAC_CONFIG_LM;
+ else
+ value &= ~GMAC_CONFIG_LM;
+
+ writel(value, ioaddr + GMAC_CONFIG);
+}
+
+static void dwmac4_sarc_configure(void __iomem *ioaddr, int val)
+{
+ u32 value = readl(ioaddr + GMAC_CONFIG);
+
+ value = u32_replace_bits(value, val, GMAC_CONFIG_SARC);
+
+ writel(value, ioaddr + GMAC_CONFIG);
+}
+
+static void dwmac4_set_arp_offload(struct mac_device_info *hw, bool en,
+ u32 addr)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ writel(addr, ioaddr + GMAC_ARP_ADDR);
+
+ value = readl(ioaddr + GMAC_CONFIG);
+ if (en)
+ value |= GMAC_CONFIG_ARPEN;
+ else
+ value &= ~GMAC_CONFIG_ARPEN;
+ writel(value, ioaddr + GMAC_CONFIG);
+}
+
+static int dwmac4_config_l3_filter(struct mac_device_info *hw, u32 filter_no,
+ bool en, bool ipv6, bool sa, bool inv,
+ u32 match)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + GMAC_PACKET_FILTER);
+ value |= GMAC_PACKET_FILTER_IPFE;
+ writel(value, ioaddr + GMAC_PACKET_FILTER);
+
+ value = readl(ioaddr + GMAC_L3L4_CTRL(filter_no));
+
+ /* For IPv6 not both SA/DA filters can be active */
+ if (ipv6) {
+ value |= GMAC_L3PEN0;
+ value &= ~(GMAC_L3SAM0 | GMAC_L3SAIM0);
+ value &= ~(GMAC_L3DAM0 | GMAC_L3DAIM0);
+ if (sa) {
+ value |= GMAC_L3SAM0;
+ if (inv)
+ value |= GMAC_L3SAIM0;
+ } else {
+ value |= GMAC_L3DAM0;
+ if (inv)
+ value |= GMAC_L3DAIM0;
+ }
+ } else {
+ value &= ~GMAC_L3PEN0;
+ if (sa) {
+ value |= GMAC_L3SAM0;
+ if (inv)
+ value |= GMAC_L3SAIM0;
+ } else {
+ value |= GMAC_L3DAM0;
+ if (inv)
+ value |= GMAC_L3DAIM0;
+ }
+ }
+
+ writel(value, ioaddr + GMAC_L3L4_CTRL(filter_no));
+
+ if (sa) {
+ writel(match, ioaddr + GMAC_L3_ADDR0(filter_no));
+ } else {
+ writel(match, ioaddr + GMAC_L3_ADDR1(filter_no));
+ }
+
+ if (!en)
+ writel(0, ioaddr + GMAC_L3L4_CTRL(filter_no));
+
+ return 0;
+}
+
+static int dwmac4_config_l4_filter(struct mac_device_info *hw, u32 filter_no,
+ bool en, bool udp, bool sa, bool inv,
+ u32 match)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + GMAC_PACKET_FILTER);
+ value |= GMAC_PACKET_FILTER_IPFE;
+ writel(value, ioaddr + GMAC_PACKET_FILTER);
+
+ value = readl(ioaddr + GMAC_L3L4_CTRL(filter_no));
+ if (udp) {
+ value |= GMAC_L4PEN0;
+ } else {
+ value &= ~GMAC_L4PEN0;
+ }
+
+ value &= ~(GMAC_L4SPM0 | GMAC_L4SPIM0);
+ value &= ~(GMAC_L4DPM0 | GMAC_L4DPIM0);
+ if (sa) {
+ value |= GMAC_L4SPM0;
+ if (inv)
+ value |= GMAC_L4SPIM0;
+ } else {
+ value |= GMAC_L4DPM0;
+ if (inv)
+ value |= GMAC_L4DPIM0;
+ }
+
+ writel(value, ioaddr + GMAC_L3L4_CTRL(filter_no));
+
+ if (sa) {
+ value = FIELD_PREP(GMAC_L4SP0, match);
+ } else {
+ value = FIELD_PREP(GMAC_L4DP0, match);
+ }
+
+ writel(value, ioaddr + GMAC_L4_ADDR(filter_no));
+
+ if (!en)
+ writel(0, ioaddr + GMAC_L3L4_CTRL(filter_no));
+
+ return 0;
+}
+
+const struct stmmac_ops dwmac4_ops = {
+ .pcs_init = dwmac4_pcs_init,
+ .core_init = dwmac4_core_init,
+ .irq_modify = dwmac4_irq_modify,
+ .update_caps = dwmac4_update_caps,
+ .set_mac = stmmac_set_mac,
+ .rx_ipc = dwmac4_rx_ipc_enable,
+ .rx_queue_enable = dwmac4_rx_queue_enable,
+ .rx_queue_prio = dwmac4_rx_queue_priority,
+ .tx_queue_prio = dwmac4_tx_queue_priority,
+ .rx_queue_routing = dwmac4_rx_queue_routing,
+ .prog_mtl_rx_algorithms = dwmac4_prog_mtl_rx_algorithms,
+ .prog_mtl_tx_algorithms = dwmac4_prog_mtl_tx_algorithms,
+ .set_mtl_tx_queue_weight = dwmac4_set_mtl_tx_queue_weight,
+ .map_mtl_to_dma = dwmac4_map_mtl_dma,
+ .config_cbs = dwmac4_config_cbs,
+ .dump_regs = dwmac4_dump_regs,
+ .host_irq_status = dwmac4_irq_status,
+ .host_mtl_irq_status = dwmac4_irq_mtl_status,
+ .flow_ctrl = dwmac4_flow_ctrl,
+ .pmt = dwmac4_pmt,
+ .set_umac_addr = dwmac4_set_umac_addr,
+ .get_umac_addr = dwmac4_get_umac_addr,
+ .set_lpi_mode = dwmac4_set_lpi_mode,
+ .set_eee_timer = dwmac4_set_eee_timer,
+ .set_eee_pls = dwmac4_set_eee_pls,
+ .pcs_ctrl_ane = dwmac4_ctrl_ane,
+ .debug = dwmac4_debug,
+ .set_filter = dwmac4_set_filter,
+ .set_mac_loopback = dwmac4_set_mac_loopback,
+ .sarc_configure = dwmac4_sarc_configure,
+ .set_arp_offload = dwmac4_set_arp_offload,
+ .config_l3_filter = dwmac4_config_l3_filter,
+ .config_l4_filter = dwmac4_config_l4_filter,
+};
+
+const struct stmmac_ops dwmac410_ops = {
+ .pcs_init = dwmac4_pcs_init,
+ .core_init = dwmac4_core_init,
+ .irq_modify = dwmac4_irq_modify,
+ .update_caps = dwmac4_update_caps,
+ .set_mac = stmmac_dwmac4_set_mac,
+ .rx_ipc = dwmac4_rx_ipc_enable,
+ .rx_queue_enable = dwmac4_rx_queue_enable,
+ .rx_queue_prio = dwmac4_rx_queue_priority,
+ .tx_queue_prio = dwmac4_tx_queue_priority,
+ .rx_queue_routing = dwmac4_rx_queue_routing,
+ .prog_mtl_rx_algorithms = dwmac4_prog_mtl_rx_algorithms,
+ .prog_mtl_tx_algorithms = dwmac4_prog_mtl_tx_algorithms,
+ .set_mtl_tx_queue_weight = dwmac4_set_mtl_tx_queue_weight,
+ .map_mtl_to_dma = dwmac4_map_mtl_dma,
+ .config_cbs = dwmac4_config_cbs,
+ .dump_regs = dwmac4_dump_regs,
+ .host_irq_status = dwmac4_irq_status,
+ .host_mtl_irq_status = dwmac4_irq_mtl_status,
+ .flow_ctrl = dwmac4_flow_ctrl,
+ .pmt = dwmac4_pmt,
+ .set_umac_addr = dwmac4_set_umac_addr,
+ .get_umac_addr = dwmac4_get_umac_addr,
+ .set_lpi_mode = dwmac4_set_lpi_mode,
+ .set_eee_timer = dwmac4_set_eee_timer,
+ .set_eee_pls = dwmac4_set_eee_pls,
+ .pcs_ctrl_ane = dwmac4_ctrl_ane,
+ .debug = dwmac4_debug,
+ .set_filter = dwmac4_set_filter,
+ .flex_pps_config = dwmac5_flex_pps_config,
+ .set_mac_loopback = dwmac4_set_mac_loopback,
+ .sarc_configure = dwmac4_sarc_configure,
+ .set_arp_offload = dwmac4_set_arp_offload,
+ .config_l3_filter = dwmac4_config_l3_filter,
+ .config_l4_filter = dwmac4_config_l4_filter,
+ .fpe_map_preemption_class = dwmac5_fpe_map_preemption_class,
+};
+
+const struct stmmac_ops dwmac510_ops = {
+ .pcs_init = dwmac4_pcs_init,
+ .core_init = dwmac4_core_init,
+ .irq_modify = dwmac4_irq_modify,
+ .update_caps = dwmac4_update_caps,
+ .set_mac = stmmac_dwmac4_set_mac,
+ .rx_ipc = dwmac4_rx_ipc_enable,
+ .rx_queue_enable = dwmac4_rx_queue_enable,
+ .rx_queue_prio = dwmac4_rx_queue_priority,
+ .tx_queue_prio = dwmac4_tx_queue_priority,
+ .rx_queue_routing = dwmac4_rx_queue_routing,
+ .prog_mtl_rx_algorithms = dwmac4_prog_mtl_rx_algorithms,
+ .prog_mtl_tx_algorithms = dwmac4_prog_mtl_tx_algorithms,
+ .set_mtl_tx_queue_weight = dwmac4_set_mtl_tx_queue_weight,
+ .map_mtl_to_dma = dwmac4_map_mtl_dma,
+ .config_cbs = dwmac4_config_cbs,
+ .dump_regs = dwmac4_dump_regs,
+ .host_irq_status = dwmac4_irq_status,
+ .host_mtl_irq_status = dwmac4_irq_mtl_status,
+ .flow_ctrl = dwmac4_flow_ctrl,
+ .pmt = dwmac4_pmt,
+ .set_umac_addr = dwmac4_set_umac_addr,
+ .get_umac_addr = dwmac4_get_umac_addr,
+ .set_lpi_mode = dwmac4_set_lpi_mode,
+ .set_eee_timer = dwmac4_set_eee_timer,
+ .set_eee_pls = dwmac4_set_eee_pls,
+ .pcs_ctrl_ane = dwmac4_ctrl_ane,
+ .debug = dwmac4_debug,
+ .set_filter = dwmac4_set_filter,
+ .safety_feat_config = dwmac5_safety_feat_config,
+ .safety_feat_irq_status = dwmac5_safety_feat_irq_status,
+ .safety_feat_dump = dwmac5_safety_feat_dump,
+ .rxp_config = dwmac5_rxp_config,
+ .flex_pps_config = dwmac5_flex_pps_config,
+ .set_mac_loopback = dwmac4_set_mac_loopback,
+ .sarc_configure = dwmac4_sarc_configure,
+ .set_arp_offload = dwmac4_set_arp_offload,
+ .config_l3_filter = dwmac4_config_l3_filter,
+ .config_l4_filter = dwmac4_config_l4_filter,
+ .fpe_map_preemption_class = dwmac5_fpe_map_preemption_class,
+};
+
+int dwmac4_setup(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ dev_info(priv->device, "\tDWMAC4/5\n");
+
+ priv->dev->priv_flags |= IFF_UNICAST_FLT;
+ mac->pcsr = priv->ioaddr;
+ mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
+ mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
+ mac->mcast_bits_log2 = 0;
+
+ if (mac->multicast_filter_bins)
+ mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
+
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
+ mac->link.duplex = GMAC_CONFIG_DM;
+ mac->link.speed10 = GMAC_CONFIG_PS;
+ mac->link.speed100 = GMAC_CONFIG_FES | GMAC_CONFIG_PS;
+ mac->link.speed1000 = 0;
+ mac->link.speed2500 = GMAC_CONFIG_FES;
+ mac->link.speed_mask = GMAC_CONFIG_FES | GMAC_CONFIG_PS;
+ mac->mii.addr = GMAC_MDIO_ADDR;
+ mac->mii.data = GMAC_MDIO_DATA;
+ mac->mii.addr_mask = GENMASK_U32(25, 21);
+ mac->mii.reg_mask = GENMASK_U32(20, 16);
+ mac->mii.clk_csr_mask = GENMASK_U32(11, 8);
+ mac->num_vlan = stmmac_get_num_vlan(priv->ioaddr);
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c
new file mode 100644
index 000000000..2994df41e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This contains the functions to handle the descriptors for DesignWare databook
+ * 4.xx.
+ *
+ * Copyright (C) 2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#include <linux/stmmac.h>
+#include "common.h"
+#include "dwmac4.h"
+#include "dwmac4_descs.h"
+
+static int dwmac4_wrback_get_tx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p,
+ void __iomem *ioaddr)
+{
+ u32 tdes3 = le32_to_cpu(p->des3);
+ int ret = tx_done;
+
+ /* Get tx owner first */
+ if (unlikely(tdes3 & TDES3_OWN))
+ return tx_dma_own;
+
+ /* Verify tx error by looking at the last segment. */
+ if (likely(!(tdes3 & TDES3_LAST_DESCRIPTOR)))
+ return tx_not_ls;
+
+ if (unlikely(tdes3 & TDES3_ERROR_SUMMARY)) {
+ ret = tx_err;
+
+ if (unlikely(tdes3 & TDES3_JABBER_TIMEOUT))
+ x->tx_jabber++;
+ if (unlikely(tdes3 & TDES3_PACKET_FLUSHED))
+ x->tx_frame_flushed++;
+ if (unlikely(tdes3 & TDES3_LOSS_CARRIER)) {
+ x->tx_losscarrier++;
+ }
+ if (unlikely(tdes3 & TDES3_NO_CARRIER)) {
+ x->tx_carrier++;
+ }
+ if (unlikely((tdes3 & TDES3_LATE_COLLISION) ||
+ (tdes3 & TDES3_EXCESSIVE_COLLISION)))
+ x->tx_collision +=
+ FIELD_GET(TDES3_COLLISION_COUNT_MASK, tdes3);
+
+ if (unlikely(tdes3 & TDES3_EXCESSIVE_DEFERRAL))
+ x->tx_deferred++;
+
+ if (unlikely(tdes3 & TDES3_UNDERFLOW_ERROR)) {
+ x->tx_underflow++;
+ ret |= tx_err_bump_tc;
+ }
+
+ if (unlikely(tdes3 & TDES3_IP_HDR_ERROR))
+ x->tx_ip_header_error++;
+
+ if (unlikely(tdes3 & TDES3_PAYLOAD_ERROR))
+ x->tx_payload_error++;
+ }
+
+ if (unlikely(tdes3 & TDES3_DEFERRED))
+ x->tx_deferred++;
+
+ return ret;
+}
+
+static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p)
+{
+ u32 rdes1 = le32_to_cpu(p->des1);
+ u32 rdes2 = le32_to_cpu(p->des2);
+ u32 rdes3 = le32_to_cpu(p->des3);
+ int message_type;
+ int ret = good_frame;
+
+ if (unlikely(rdes3 & RDES3_OWN))
+ return dma_own;
+
+ if (unlikely(rdes3 & RDES3_CONTEXT_DESCRIPTOR))
+ return discard_frame;
+ if (likely(!(rdes3 & RDES3_LAST_DESCRIPTOR)))
+ return rx_not_ls;
+
+ if (unlikely(rdes3 & RDES3_ERROR_SUMMARY)) {
+ if (unlikely(rdes3 & RDES3_GIANT_PACKET))
+ x->rx_length++;
+ if (unlikely(rdes3 & RDES3_OVERFLOW_ERROR))
+ x->rx_gmac_overflow++;
+
+ if (unlikely(rdes3 & RDES3_RECEIVE_WATCHDOG))
+ x->rx_watchdog++;
+
+ if (unlikely(rdes3 & RDES3_RECEIVE_ERROR))
+ x->rx_mii++;
+
+ if (unlikely(rdes3 & RDES3_CRC_ERROR))
+ x->rx_crc_errors++;
+
+ if (unlikely(rdes3 & RDES3_DRIBBLE_ERROR))
+ x->dribbling_bit++;
+
+ ret = discard_frame;
+ }
+
+ message_type = FIELD_GET(RDES1_PTP_MSG_TYPE_MASK, rdes1);
+
+ if (rdes1 & RDES1_IP_HDR_ERROR) {
+ x->ip_hdr_err++;
+ ret |= csum_none;
+ }
+ if (rdes1 & RDES1_IP_CSUM_BYPASSED)
+ x->ip_csum_bypassed++;
+ if (rdes1 & RDES1_IPV4_HEADER)
+ x->ipv4_pkt_rcvd++;
+ if (rdes1 & RDES1_IPV6_HEADER)
+ x->ipv6_pkt_rcvd++;
+ if (rdes1 & RDES1_IP_PAYLOAD_ERROR) {
+ x->ip_payload_err++;
+ ret |= csum_none;
+ }
+
+ if (message_type == RDES_EXT_NO_PTP)
+ x->no_ptp_rx_msg_type_ext++;
+ else if (message_type == RDES_EXT_SYNC)
+ x->ptp_rx_msg_type_sync++;
+ else if (message_type == RDES_EXT_FOLLOW_UP)
+ x->ptp_rx_msg_type_follow_up++;
+ else if (message_type == RDES_EXT_DELAY_REQ)
+ x->ptp_rx_msg_type_delay_req++;
+ else if (message_type == RDES_EXT_DELAY_RESP)
+ x->ptp_rx_msg_type_delay_resp++;
+ else if (message_type == RDES_EXT_PDELAY_REQ)
+ x->ptp_rx_msg_type_pdelay_req++;
+ else if (message_type == RDES_EXT_PDELAY_RESP)
+ x->ptp_rx_msg_type_pdelay_resp++;
+ else if (message_type == RDES_EXT_PDELAY_FOLLOW_UP)
+ x->ptp_rx_msg_type_pdelay_follow_up++;
+ else if (message_type == RDES_PTP_ANNOUNCE)
+ x->ptp_rx_msg_type_announce++;
+ else if (message_type == RDES_PTP_MANAGEMENT)
+ x->ptp_rx_msg_type_management++;
+ else if (message_type == RDES_PTP_PKT_RESERVED_TYPE)
+ x->ptp_rx_msg_pkt_reserved_type++;
+
+ if (rdes1 & RDES1_PTP_PACKET_TYPE)
+ x->ptp_frame_type++;
+ if (rdes1 & RDES1_PTP_VER)
+ x->ptp_ver++;
+ if (rdes1 & RDES1_TIMESTAMP_DROPPED)
+ x->timestamp_dropped++;
+
+ if (unlikely(rdes2 & RDES2_SA_FILTER_FAIL)) {
+ x->sa_rx_filter_fail++;
+ ret = discard_frame;
+ }
+ if (unlikely(rdes2 & RDES2_DA_FILTER_FAIL)) {
+ x->da_rx_filter_fail++;
+ ret = discard_frame;
+ }
+
+ if (rdes2 & RDES2_L3_FILTER_MATCH)
+ x->l3_filter_match++;
+ if (rdes2 & RDES2_L4_FILTER_MATCH)
+ x->l4_filter_match++;
+ if (rdes2 & RDES2_L3_L4_FILT_NB_MATCH_MASK)
+ x->l3_l4_filter_no_match++;
+
+ return ret;
+}
+
+static void dwmac4_set_tx_owner(struct dma_desc *p)
+{
+ p->des3 |= cpu_to_le32(TDES3_OWN);
+}
+
+static void dwmac4_set_rx_owner(struct dma_desc *p, int disable_rx_ic)
+{
+ u32 flags = (RDES3_OWN | RDES3_BUFFER1_VALID_ADDR);
+
+ if (!disable_rx_ic)
+ flags |= RDES3_INT_ON_COMPLETION_EN;
+
+ p->des3 |= cpu_to_le32(flags);
+}
+
+static u16 dwmac4_wrback_get_rx_vlan_tci(struct dma_desc *p)
+{
+ return (le32_to_cpu(p->des0) & RDES0_VLAN_TAG_MASK);
+}
+
+static bool dwmac4_wrback_get_rx_vlan_valid(struct dma_desc *p)
+{
+ return ((le32_to_cpu(p->des3) & RDES3_LAST_DESCRIPTOR) &&
+ (le32_to_cpu(p->des3) & RDES3_RDES0_VALID));
+}
+
+static int dwmac4_wrback_get_rx_frame_len(struct dma_desc *p, int rx_coe)
+{
+ return (le32_to_cpu(p->des3) & RDES3_PACKET_SIZE_MASK);
+}
+
+static void dwmac4_rd_enable_tx_timestamp(struct dma_desc *p)
+{
+ p->des2 |= cpu_to_le32(TDES2_TIMESTAMP_ENABLE);
+}
+
+static int dwmac4_wrback_get_tx_timestamp_status(struct dma_desc *p)
+{
+ /* Context type from W/B descriptor must be zero */
+ if (le32_to_cpu(p->des3) & TDES3_CONTEXT_TYPE)
+ return 0;
+
+ /* Tx Timestamp Status is 1 so des0 and des1'll have valid values */
+ if (le32_to_cpu(p->des3) & TDES3_TIMESTAMP_STATUS)
+ return 1;
+
+ return 0;
+}
+
+static inline void dwmac4_get_timestamp(void *desc, u32 ats, u64 *ts)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ u64 ns;
+
+ ns = le32_to_cpu(p->des0);
+ /* convert high/sec time stamp value to nanosecond */
+ ns += le32_to_cpu(p->des1) * 1000000000ULL;
+
+ *ts = ns;
+}
+
+static int dwmac4_rx_check_timestamp(void *desc)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ u32 rdes0 = le32_to_cpu(p->des0);
+ u32 rdes1 = le32_to_cpu(p->des1);
+ u32 rdes3 = le32_to_cpu(p->des3);
+ bool own, ctxt;
+ int ret = 1;
+
+ own = rdes3 & RDES3_OWN;
+ ctxt = rdes3 & RDES3_CONTEXT_DESCRIPTOR;
+
+ if (likely(!own && ctxt)) {
+ if ((rdes0 == 0xffffffff) && (rdes1 == 0xffffffff))
+ /* Corrupted value */
+ ret = -EINVAL;
+ else
+ /* A valid Timestamp is ready to be read */
+ ret = 0;
+ }
+
+ /* Timestamp not ready */
+ return ret;
+}
+
+static int dwmac4_wrback_get_rx_timestamp_status(void *desc, void *next_desc,
+ u32 ats)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ int ret = -EINVAL;
+
+ /* Get the status from normal w/b descriptor */
+ if (likely(le32_to_cpu(p->des3) & RDES3_RDES1_VALID)) {
+ if (likely(le32_to_cpu(p->des1) & RDES1_TIMESTAMP_AVAILABLE)) {
+ int i = 0;
+
+ /* Check if timestamp is OK from context descriptor */
+ do {
+ ret = dwmac4_rx_check_timestamp(next_desc);
+ if (ret < 0)
+ goto exit;
+ i++;
+
+ } while ((ret == 1) && (i < 10));
+
+ if (i == 10)
+ ret = -EBUSY;
+ }
+ }
+exit:
+ if (likely(ret == 0))
+ return 1;
+
+ return 0;
+}
+
+static void dwmac4_rd_init_rx_desc(struct dma_desc *p, int disable_rx_ic,
+ u8 descriptor_mode, int end, int bfsize)
+{
+ dwmac4_set_rx_owner(p, disable_rx_ic);
+}
+
+static void dwmac4_rd_init_tx_desc(struct dma_desc *p, u8 descriptor_mode,
+ int end)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+}
+
+static void dwmac4_rd_prepare_tx_desc(struct dma_desc *p, int is_fs, int len,
+ bool csum_flag, u8 descriptor_mode,
+ bool tx_own, bool ls,
+ unsigned int tot_pkt_len)
+{
+ u32 tdes3 = le32_to_cpu(p->des3);
+
+ p->des2 |= cpu_to_le32(len & TDES2_BUFFER1_SIZE_MASK);
+
+ tdes3 |= tot_pkt_len & TDES3_PACKET_SIZE_MASK;
+ if (is_fs)
+ tdes3 |= TDES3_FIRST_DESCRIPTOR;
+ else
+ tdes3 &= ~TDES3_FIRST_DESCRIPTOR;
+
+ tdes3 = u32_replace_bits(tdes3, csum_flag ? TX_CIC_FULL : 0,
+ TDES3_CHECKSUM_INSERTION_MASK);
+
+ if (ls)
+ tdes3 |= TDES3_LAST_DESCRIPTOR;
+ else
+ tdes3 &= ~TDES3_LAST_DESCRIPTOR;
+
+ /* Finally set the OWN bit. Later the DMA will start! */
+ if (tx_own)
+ tdes3 |= TDES3_OWN;
+
+ if (is_fs && tx_own)
+ /* When the own bit, for the first frame, has to be set, all
+ * descriptors for the same frame has to be set before, to
+ * avoid race condition.
+ */
+ dma_wmb();
+
+ p->des3 = cpu_to_le32(tdes3);
+}
+
+static void dwmac4_rd_prepare_tso_tx_desc(struct dma_desc *p, int is_fs,
+ int len1, int len2, bool tx_own,
+ bool ls, unsigned int tcphdrlen,
+ unsigned int tcppayloadlen)
+{
+ u32 tdes3 = le32_to_cpu(p->des3);
+
+ if (len1)
+ p->des2 |= cpu_to_le32(FIELD_PREP(TDES2_BUFFER1_SIZE_MASK,
+ len1));
+
+ if (len2)
+ p->des2 |= cpu_to_le32(FIELD_PREP(TDES2_BUFFER2_SIZE_MASK,
+ len2));
+
+ if (is_fs) {
+ tdes3 |= TDES3_FIRST_DESCRIPTOR |
+ TDES3_TCP_SEGMENTATION_ENABLE |
+ FIELD_PREP(TDES3_SLOT_NUMBER_MASK, tcphdrlen) |
+ FIELD_PREP(TDES3_TCP_PKT_PAYLOAD_MASK, tcppayloadlen);
+ } else {
+ tdes3 &= ~TDES3_FIRST_DESCRIPTOR;
+ }
+
+ if (ls)
+ tdes3 |= TDES3_LAST_DESCRIPTOR;
+ else
+ tdes3 &= ~TDES3_LAST_DESCRIPTOR;
+
+ /* Finally set the OWN bit. Later the DMA will start! */
+ if (tx_own)
+ tdes3 |= TDES3_OWN;
+
+ if (is_fs && tx_own)
+ /* When the own bit, for the first frame, has to be set, all
+ * descriptors for the same frame has to be set before, to
+ * avoid race condition.
+ */
+ dma_wmb();
+
+ p->des3 = cpu_to_le32(tdes3);
+}
+
+static void dwmac4_release_tx_desc(struct dma_desc *p, u8 descriptor_mode)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+}
+
+static void dwmac4_rd_set_tx_ic(struct dma_desc *p)
+{
+ p->des2 |= cpu_to_le32(TDES2_INTERRUPT_ON_COMPLETION);
+}
+
+static void dwmac4_display_ring(void *head, unsigned int size, bool rx,
+ dma_addr_t dma_rx_phy, unsigned int desc_size)
+{
+ dma_addr_t dma_addr;
+ int i;
+
+ pr_info("%s descriptor ring:\n", rx ? "RX" : "TX");
+
+ if (desc_size == sizeof(struct dma_desc)) {
+ struct dma_desc *p = (struct dma_desc *)head;
+
+ for (i = 0; i < size; i++) {
+ dma_addr = dma_rx_phy + i * sizeof(*p);
+ pr_info("%03d [%pad]: 0x%x 0x%x 0x%x 0x%x\n",
+ i, &dma_addr,
+ le32_to_cpu(p->des0), le32_to_cpu(p->des1),
+ le32_to_cpu(p->des2), le32_to_cpu(p->des3));
+ p++;
+ }
+ } else if (desc_size == sizeof(struct dma_extended_desc)) {
+ struct dma_extended_desc *extp = (struct dma_extended_desc *)head;
+
+ for (i = 0; i < size; i++) {
+ dma_addr = dma_rx_phy + i * sizeof(*extp);
+ pr_info("%03d [%pad]: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
+ i, &dma_addr,
+ le32_to_cpu(extp->basic.des0), le32_to_cpu(extp->basic.des1),
+ le32_to_cpu(extp->basic.des2), le32_to_cpu(extp->basic.des3),
+ le32_to_cpu(extp->des4), le32_to_cpu(extp->des5),
+ le32_to_cpu(extp->des6), le32_to_cpu(extp->des7));
+ extp++;
+ }
+ } else if (desc_size == sizeof(struct dma_edesc)) {
+ struct dma_edesc *ep = dma_desc_to_edesc(head);
+
+ for (i = 0; i < size; i++) {
+ dma_addr = dma_rx_phy + i * sizeof(*ep);
+ pr_info("%03d [%pad]: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
+ i, &dma_addr,
+ le32_to_cpu(ep->des4), le32_to_cpu(ep->des5),
+ le32_to_cpu(ep->des6), le32_to_cpu(ep->des7),
+ le32_to_cpu(ep->basic.des0), le32_to_cpu(ep->basic.des1),
+ le32_to_cpu(ep->basic.des2), le32_to_cpu(ep->basic.des3));
+ ep++;
+ }
+ } else {
+ pr_err("unsupported descriptor!");
+ }
+}
+
+static void dwmac4_set_mss_ctxt(struct dma_desc *p, unsigned int mss)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = cpu_to_le32(mss);
+ p->des3 = cpu_to_le32(TDES3_CONTEXT_TYPE | TDES3_CTXT_TCMSSV);
+}
+
+static void dwmac4_set_addr(struct dma_desc *p, dma_addr_t addr)
+{
+ p->des0 = cpu_to_le32(lower_32_bits(addr));
+ p->des1 = cpu_to_le32(upper_32_bits(addr));
+}
+
+static void dwmac4_clear(struct dma_desc *p)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+}
+
+static void dwmac4_set_sarc(struct dma_desc *p, u32 sarc_type)
+{
+ p->des3 |= cpu_to_le32(FIELD_PREP(TDES3_SA_INSERT_CTRL_MASK,
+ sarc_type));
+}
+
+static int set_16kib_bfsize(int mtu)
+{
+ int ret = 0;
+
+ if (unlikely(mtu >= BUF_SIZE_8KiB))
+ ret = BUF_SIZE_16KiB;
+ return ret;
+}
+
+static void dwmac4_set_vlan_tag(struct dma_desc *p, u16 tag, u16 inner_tag,
+ u32 inner_type)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+
+ /* Inner VLAN */
+ if (inner_type) {
+ p->des2 = cpu_to_le32(FIELD_PREP(TDES2_IVT_MASK, inner_tag));
+ p->des3 = cpu_to_le32(FIELD_PREP(TDES3_IVTIR_MASK, inner_type) |
+ TDES3_IVLTV);
+ }
+
+ /* Outer VLAN */
+ p->des3 |= cpu_to_le32(tag & TDES3_VLAN_TAG);
+ p->des3 |= cpu_to_le32(TDES3_VLTV);
+
+ p->des3 |= cpu_to_le32(TDES3_CONTEXT_TYPE);
+}
+
+static void dwmac4_set_vlan(struct dma_desc *p, u32 type)
+{
+ p->des2 |= cpu_to_le32(FIELD_PREP(TDES2_VLAN_TAG_MASK, type));
+}
+
+static void dwmac4_get_rx_header_len(struct dma_desc *p, unsigned int *len)
+{
+ *len = le32_to_cpu(p->des2) & RDES2_HL;
+}
+
+static void dwmac4_set_sec_addr(struct dma_desc *p, dma_addr_t addr, bool buf2_valid)
+{
+ p->des2 = cpu_to_le32(lower_32_bits(addr));
+ p->des3 = cpu_to_le32(upper_32_bits(addr));
+
+ if (buf2_valid)
+ p->des3 |= cpu_to_le32(RDES3_BUFFER2_VALID_ADDR);
+ else
+ p->des3 &= cpu_to_le32(~RDES3_BUFFER2_VALID_ADDR);
+}
+
+static void dwmac4_set_tbs(struct dma_edesc *p, u32 sec, u32 nsec)
+{
+ p->des4 = cpu_to_le32((sec & TDES4_LT) | TDES4_LTV);
+ p->des5 = cpu_to_le32(nsec & TDES5_LT);
+ p->des6 = 0;
+ p->des7 = 0;
+}
+
+const struct stmmac_desc_ops dwmac4_desc_ops = {
+ .tx_status = dwmac4_wrback_get_tx_status,
+ .rx_status = dwmac4_wrback_get_rx_status,
+ .set_tx_owner = dwmac4_set_tx_owner,
+ .set_rx_owner = dwmac4_set_rx_owner,
+ .get_rx_vlan_tci = dwmac4_wrback_get_rx_vlan_tci,
+ .get_rx_vlan_valid = dwmac4_wrback_get_rx_vlan_valid,
+ .get_rx_frame_len = dwmac4_wrback_get_rx_frame_len,
+ .enable_tx_timestamp = dwmac4_rd_enable_tx_timestamp,
+ .get_tx_timestamp_status = dwmac4_wrback_get_tx_timestamp_status,
+ .get_rx_timestamp_status = dwmac4_wrback_get_rx_timestamp_status,
+ .get_timestamp = dwmac4_get_timestamp,
+ .set_tx_ic = dwmac4_rd_set_tx_ic,
+ .prepare_tx_desc = dwmac4_rd_prepare_tx_desc,
+ .prepare_tso_tx_desc = dwmac4_rd_prepare_tso_tx_desc,
+ .release_tx_desc = dwmac4_release_tx_desc,
+ .init_rx_desc = dwmac4_rd_init_rx_desc,
+ .init_tx_desc = dwmac4_rd_init_tx_desc,
+ .display_ring = dwmac4_display_ring,
+ .set_mss = dwmac4_set_mss_ctxt,
+ .set_addr = dwmac4_set_addr,
+ .clear = dwmac4_clear,
+ .set_sarc = dwmac4_set_sarc,
+ .set_vlan_tag = dwmac4_set_vlan_tag,
+ .set_vlan = dwmac4_set_vlan,
+ .get_rx_header_len = dwmac4_get_rx_header_len,
+ .set_sec_addr = dwmac4_set_sec_addr,
+ .set_tbs = dwmac4_set_tbs,
+};
+
+const struct stmmac_mode_ops dwmac4_ring_mode_ops = {
+ .set_16kib_bfsize = set_16kib_bfsize,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h
new file mode 100644
index 000000000..fb1fea5b0
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Header File to describe the DMA descriptors and related definitions specific
+ * for DesignWare databook 4.xx.
+ *
+ * Copyright (C) 2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#ifndef __DWMAC4_DESCS_H__
+#define __DWMAC4_DESCS_H__
+
+#include <linux/bitops.h>
+
+/* Normal transmit descriptor defines (without split feature) */
+
+/* TDES2 (read format) */
+#define TDES2_BUFFER1_SIZE_MASK GENMASK(13, 0)
+#define TDES2_VLAN_TAG_MASK GENMASK(15, 14)
+#define TDES2_BUFFER2_SIZE_MASK GENMASK(29, 16)
+#define TDES3_IVTIR_MASK GENMASK(19, 18)
+#define TDES3_IVLTV BIT(17)
+#define TDES2_TIMESTAMP_ENABLE BIT(30)
+#define TDES2_IVT_MASK GENMASK(31, 16)
+#define TDES2_INTERRUPT_ON_COMPLETION BIT(31)
+
+/* TDES3 (read format) */
+#define TDES3_PACKET_SIZE_MASK GENMASK(14, 0)
+#define TDES3_VLAN_TAG GENMASK(15, 0)
+#define TDES3_VLTV BIT(16)
+#define TDES3_CHECKSUM_INSERTION_MASK GENMASK(17, 16)
+#define TDES3_TCP_PKT_PAYLOAD_MASK GENMASK(17, 0)
+#define TDES3_TCP_SEGMENTATION_ENABLE BIT(18)
+#define TDES3_SLOT_NUMBER_MASK GENMASK(22, 19)
+#define TDES3_SA_INSERT_CTRL_MASK GENMASK(25, 23)
+#define TDES3_CRC_PAD_CTRL_MASK GENMASK(27, 26)
+
+/* TDES3 (write back format) */
+#define TDES3_IP_HDR_ERROR BIT(0)
+#define TDES3_DEFERRED BIT(1)
+#define TDES3_UNDERFLOW_ERROR BIT(2)
+#define TDES3_EXCESSIVE_DEFERRAL BIT(3)
+#define TDES3_COLLISION_COUNT_MASK GENMASK(7, 4)
+#define TDES3_EXCESSIVE_COLLISION BIT(8)
+#define TDES3_LATE_COLLISION BIT(9)
+#define TDES3_NO_CARRIER BIT(10)
+#define TDES3_LOSS_CARRIER BIT(11)
+#define TDES3_PAYLOAD_ERROR BIT(12)
+#define TDES3_PACKET_FLUSHED BIT(13)
+#define TDES3_JABBER_TIMEOUT BIT(14)
+#define TDES3_ERROR_SUMMARY BIT(15)
+#define TDES3_TIMESTAMP_STATUS BIT(17)
+#define TDES3_TIMESTAMP_STATUS_SHIFT 17
+
+/* TDES3 context */
+#define TDES3_CTXT_TCMSSV BIT(26)
+
+/* TDES3 Common */
+#define TDES3_RS1V BIT(26)
+#define TDES3_RS1V_SHIFT 26
+#define TDES3_LAST_DESCRIPTOR BIT(28)
+#define TDES3_LAST_DESCRIPTOR_SHIFT 28
+#define TDES3_FIRST_DESCRIPTOR BIT(29)
+#define TDES3_CONTEXT_TYPE BIT(30)
+#define TDES3_CONTEXT_TYPE_SHIFT 30
+
+/* TDES4 */
+#define TDES4_LTV BIT(31)
+#define TDES4_LT GENMASK(7, 0)
+
+/* TDES5 */
+#define TDES5_LT GENMASK(31, 8)
+
+/* TDS3 use for both format (read and write back) */
+#define TDES3_OWN BIT(31)
+#define TDES3_OWN_SHIFT 31
+
+/* Normal receive descriptor defines (without split feature) */
+
+/* RDES0 (write back format) */
+#define RDES0_VLAN_TAG_MASK GENMASK(15, 0)
+
+/* RDES1 (write back format) */
+#define RDES1_IP_PAYLOAD_TYPE_MASK GENMASK(2, 0)
+#define RDES1_IP_HDR_ERROR BIT(3)
+#define RDES1_IPV4_HEADER BIT(4)
+#define RDES1_IPV6_HEADER BIT(5)
+#define RDES1_IP_CSUM_BYPASSED BIT(6)
+#define RDES1_IP_PAYLOAD_ERROR BIT(7)
+#define RDES1_PTP_MSG_TYPE_MASK GENMASK(11, 8)
+#define RDES1_PTP_PACKET_TYPE BIT(12)
+#define RDES1_PTP_VER BIT(13)
+#define RDES1_TIMESTAMP_AVAILABLE BIT(14)
+#define RDES1_TIMESTAMP_AVAILABLE_SHIFT 14
+#define RDES1_TIMESTAMP_DROPPED BIT(15)
+#define RDES1_IP_TYPE1_CSUM_MASK GENMASK(31, 16)
+
+/* RDES2 (write back format) */
+#define RDES2_L3_L4_HEADER_SIZE_MASK GENMASK(9, 0)
+#define RDES2_VLAN_FILTER_STATUS BIT(15)
+#define RDES2_SA_FILTER_FAIL BIT(16)
+#define RDES2_DA_FILTER_FAIL BIT(17)
+#define RDES2_HASH_FILTER_STATUS BIT(18)
+#define RDES2_MAC_ADDR_MATCH_MASK GENMASK(26, 19)
+#define RDES2_HASH_VALUE_MATCH_MASK GENMASK(26, 19)
+#define RDES2_L3_FILTER_MATCH BIT(27)
+#define RDES2_L4_FILTER_MATCH BIT(28)
+#define RDES2_L3_L4_FILT_NB_MATCH_MASK GENMASK(27, 26)
+#define RDES2_L3_L4_FILT_NB_MATCH_SHIFT 26
+#define RDES2_HL GENMASK(9, 0)
+
+/* RDES3 (write back format) */
+#define RDES3_PACKET_SIZE_MASK GENMASK(14, 0)
+#define RDES3_ERROR_SUMMARY BIT(15)
+#define RDES3_PACKET_LEN_TYPE_MASK GENMASK(18, 16)
+#define RDES3_DRIBBLE_ERROR BIT(19)
+#define RDES3_RECEIVE_ERROR BIT(20)
+#define RDES3_OVERFLOW_ERROR BIT(21)
+#define RDES3_RECEIVE_WATCHDOG BIT(22)
+#define RDES3_GIANT_PACKET BIT(23)
+#define RDES3_CRC_ERROR BIT(24)
+#define RDES3_RDES0_VALID BIT(25)
+#define RDES3_RDES1_VALID BIT(26)
+#define RDES3_RDES2_VALID BIT(27)
+#define RDES3_LAST_DESCRIPTOR BIT(28)
+#define RDES3_FIRST_DESCRIPTOR BIT(29)
+#define RDES3_CONTEXT_DESCRIPTOR BIT(30)
+#define RDES3_CONTEXT_DESCRIPTOR_SHIFT 30
+
+/* RDES3 (read format) */
+#define RDES3_BUFFER1_VALID_ADDR BIT(24)
+#define RDES3_BUFFER2_VALID_ADDR BIT(25)
+#define RDES3_INT_ON_COMPLETION_EN BIT(30)
+
+/* TDS3 use for both format (read and write back) */
+#define RDES3_OWN BIT(31)
+
+extern const struct stmmac_mode_ops dwmac4_ring_mode_ops;
+extern const struct stmmac_desc_ops dwmac4_desc_ops;
+
+#endif /* __DWMAC4_DESCS_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
new file mode 100644
index 000000000..829a23bda
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c
@@ -0,0 +1,604 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This is the driver for the GMAC on-chip Ethernet controller for ST SoCs.
+ * DWC Ether MAC version 4.xx has been used for developing this code.
+ *
+ * This contains the functions to handle the dma.
+ *
+ * Copyright (C) 2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#include <linux/io.h>
+#include "dwmac4.h"
+#include "dwmac4_dma.h"
+#include "stmmac.h"
+
+static void dwmac4_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
+{
+ u32 value = readl(ioaddr + DMA_SYS_BUS_MODE);
+
+ pr_info("dwmac4: Master AXI performs %s burst length\n",
+ (value & DMA_SYS_BUS_FB) ? "fixed" : "any");
+
+ if (axi->axi_lpi_en)
+ value |= DMA_AXI_EN_LPI;
+ if (axi->axi_xit_frm)
+ value |= DMA_AXI_LPI_XIT_FRM;
+
+ value = u32_replace_bits(value, axi->axi_wr_osr_lmt,
+ DMA_AXI_WR_OSR_LMT);
+ value = u32_replace_bits(value, axi->axi_rd_osr_lmt,
+ DMA_AXI_RD_OSR_LMT);
+
+ /* Depending on the UNDEF bit the Master AXI will perform any burst
+ * length according to the BLEN programmed (by default all BLEN are
+ * set). Note that the UNDEF bit is readonly, and is the inverse of
+ * Bus Mode bit 16.
+ */
+ value = (value & ~DMA_AXI_BLEN_MASK) | axi->axi_blen_regval;
+
+ writel(value, ioaddr + DMA_SYS_BUS_MODE);
+}
+
+static void dwmac4_dma_init_rx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_rx_phy, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value;
+ u32 rxpbl = dma_cfg->rxpbl ?: dma_cfg->pbl;
+
+ value = readl(ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+ value = value | FIELD_PREP(DMA_CHAN_RX_CTRL_RXPBL_MASK, rxpbl);
+ writel(value, ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+
+ if (IS_ENABLED(CONFIG_ARCH_DMA_ADDR_T_64BIT) && likely(dma_cfg->eame))
+ writel(upper_32_bits(dma_rx_phy),
+ ioaddr + DMA_CHAN_RX_BASE_ADDR_HI(dwmac4_addrs, chan));
+
+ writel(lower_32_bits(dma_rx_phy),
+ ioaddr + DMA_CHAN_RX_BASE_ADDR(dwmac4_addrs, chan));
+}
+
+static void dwmac4_dma_init_tx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t dma_tx_phy, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value;
+ u32 txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
+
+ value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+ value = value | FIELD_PREP(DMA_CHAN_TX_CTRL_TXPBL_MASK, txpbl);
+
+ /* Enable OSP to get best performance */
+ value |= DMA_CONTROL_OSP;
+
+ writel(value, ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+
+ if (IS_ENABLED(CONFIG_ARCH_DMA_ADDR_T_64BIT) && likely(dma_cfg->eame))
+ writel(upper_32_bits(dma_tx_phy),
+ ioaddr + DMA_CHAN_TX_BASE_ADDR_HI(dwmac4_addrs, chan));
+
+ writel(lower_32_bits(dma_tx_phy),
+ ioaddr + DMA_CHAN_TX_BASE_ADDR(dwmac4_addrs, chan));
+}
+
+static void dwmac4_dma_init_channel(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value;
+
+ /* common channel control register config */
+ value = readl(ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, chan));
+ if (dma_cfg->pblx8)
+ value = value | DMA_CHAN_CTRL_PBLX8;
+ writel(value, ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, chan));
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(DMA_CHAN_INTR_DEFAULT_MASK,
+ ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+}
+
+static void dwmac410_dma_init_channel(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value;
+
+ /* common channel control register config */
+ value = readl(ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, chan));
+ if (dma_cfg->pblx8)
+ value = value | DMA_CHAN_CTRL_PBLX8;
+
+ writel(value, ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, chan));
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(DMA_CHAN_INTR_DEFAULT_MASK_4_10,
+ ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+}
+
+static void dwmac4_dma_init(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg)
+{
+ u32 value = readl(ioaddr + DMA_SYS_BUS_MODE);
+
+ /* Set the Fixed burst mode */
+ if (dma_cfg->fixed_burst)
+ value |= DMA_SYS_BUS_FB;
+
+ /* Mixed Burst has no effect when fb is set */
+ if (dma_cfg->mixed_burst)
+ value |= DMA_SYS_BUS_MB;
+
+ if (dma_cfg->aal)
+ value |= DMA_SYS_BUS_AAL;
+
+ if (dma_cfg->eame)
+ value |= DMA_SYS_BUS_EAME;
+
+ writel(value, ioaddr + DMA_SYS_BUS_MODE);
+
+ value = readl(ioaddr + DMA_BUS_MODE);
+
+ if (dma_cfg->multi_msi_en)
+ value = u32_replace_bits(value, DMA_BUS_MODE_INTM_MODE1,
+ DMA_BUS_MODE_INTM_MASK);
+
+ if (dma_cfg->dche)
+ value |= DMA_BUS_MODE_DCHE;
+
+ writel(value, ioaddr + DMA_BUS_MODE);
+
+}
+
+static void _dwmac4_dump_dma_regs(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 channel,
+ u32 *reg_space)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ const struct dwmac4_addrs *default_addrs = NULL;
+
+ /* Purposely save the registers in the "normal" layout, regardless of
+ * platform modifications, to keep reg_space size constant
+ */
+ reg_space[DMA_CHAN_CONTROL(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_TX_CONTROL(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_RX_CONTROL(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_TX_BASE_ADDR_HI(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_TX_BASE_ADDR_HI(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_TX_BASE_ADDR(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_TX_BASE_ADDR(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_RX_BASE_ADDR_HI(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_RX_BASE_ADDR_HI(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_RX_BASE_ADDR(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_RX_BASE_ADDR(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_TX_END_ADDR(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_TX_END_ADDR(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_RX_END_ADDR(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_RX_END_ADDR(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_TX_RING_LEN(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_TX_RING_LEN(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_RX_RING_LEN(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_RX_RING_LEN(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_INTR_ENA(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_RX_WATCHDOG(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_RX_WATCHDOG(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_SLOT_CTRL_STATUS(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_SLOT_CTRL_STATUS(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_CUR_TX_DESC(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CUR_TX_DESC(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_CUR_RX_DESC(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CUR_RX_DESC(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_CUR_TX_BUF_ADDR_HI(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CUR_TX_BUF_ADDR_HI(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_CUR_TX_BUF_ADDR(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CUR_TX_BUF_ADDR(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_CUR_RX_BUF_ADDR_HI(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CUR_RX_BUF_ADDR_HI(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_CUR_RX_BUF_ADDR(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_CUR_RX_BUF_ADDR(dwmac4_addrs, channel));
+ reg_space[DMA_CHAN_STATUS(default_addrs, channel) / 4] =
+ readl(ioaddr + DMA_CHAN_STATUS(dwmac4_addrs, channel));
+}
+
+static void dwmac4_dump_dma_regs(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 *reg_space)
+{
+ int i;
+
+ for (i = 0; i < DMA_CHANNEL_NB_MAX; i++)
+ _dwmac4_dump_dma_regs(priv, ioaddr, i, reg_space);
+}
+
+static void dwmac4_rx_watchdog(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 riwt, u32 queue)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ writel(riwt, ioaddr + DMA_CHAN_RX_WATCHDOG(dwmac4_addrs, queue));
+}
+
+static void dwmac4_dma_rx_chan_op_mode(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ unsigned int rqs = fifosz / 256 - 1;
+ u32 mtl_rx_op;
+
+ mtl_rx_op = readl(ioaddr + MTL_CHAN_RX_OP_MODE(dwmac4_addrs, channel));
+
+ mtl_rx_op |= MTL_OP_MODE_DIS_TCP_EF;
+
+ if (mode == SF_DMA_MODE) {
+ pr_debug("GMAC: enable RX store and forward mode\n");
+ mtl_rx_op |= MTL_OP_MODE_RSF;
+ } else {
+ pr_debug("GMAC: disable RX SF mode (threshold %d)\n", mode);
+ mtl_rx_op &= ~MTL_OP_MODE_RSF;
+ mtl_rx_op &= ~MTL_OP_MODE_RTC_MASK;
+ if (mode <= 32)
+ mtl_rx_op |= MTL_OP_MODE_RTC_32;
+ else if (mode <= 64)
+ mtl_rx_op |= MTL_OP_MODE_RTC_64;
+ else if (mode <= 96)
+ mtl_rx_op |= MTL_OP_MODE_RTC_96;
+ else
+ mtl_rx_op |= MTL_OP_MODE_RTC_128;
+ }
+
+ mtl_rx_op &= ~MTL_OP_MODE_RQS_MASK;
+ mtl_rx_op |= FIELD_PREP(MTL_OP_MODE_RQS_MASK, rqs);
+
+ /* Enable flow control only if each channel gets 4 KiB or more FIFO and
+ * only if channel is not an AVB channel.
+ */
+ if ((fifosz >= 4096) && (qmode != MTL_QUEUE_AVB)) {
+ unsigned int rfd, rfa;
+
+ mtl_rx_op |= MTL_OP_MODE_EHFC;
+
+ /* Set Threshold for Activating Flow Control to min 2 frames,
+ * i.e. 1500 * 2 = 3000 bytes.
+ *
+ * Set Threshold for Deactivating Flow Control to min 1 frame,
+ * i.e. 1500 bytes.
+ */
+ switch (fifosz) {
+ case 4096:
+ /* This violates the above formula because of FIFO size
+ * limit therefore overflow may occur in spite of this.
+ */
+ rfd = 0x03; /* Full-2.5K */
+ rfa = 0x01; /* Full-1.5K */
+ break;
+
+ default:
+ rfd = 0x07; /* Full-4.5K */
+ rfa = 0x04; /* Full-3K */
+ break;
+ }
+
+ mtl_rx_op = u32_replace_bits(mtl_rx_op, rfd,
+ MTL_OP_MODE_RFD_MASK);
+ mtl_rx_op = u32_replace_bits(mtl_rx_op, rfa,
+ MTL_OP_MODE_RFA_MASK);
+ }
+
+ writel(mtl_rx_op, ioaddr + MTL_CHAN_RX_OP_MODE(dwmac4_addrs, channel));
+}
+
+static void dwmac4_dma_tx_chan_op_mode(struct stmmac_priv *priv,
+ void __iomem *ioaddr, int mode,
+ u32 channel, int fifosz, u8 qmode)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 mtl_tx_op = readl(ioaddr + MTL_CHAN_TX_OP_MODE(dwmac4_addrs,
+ channel));
+ unsigned int tqs = fifosz / 256 - 1;
+
+ if (mode == SF_DMA_MODE) {
+ pr_debug("GMAC: enable TX store and forward mode\n");
+ /* Transmit COE type 2 cannot be done in cut-through mode. */
+ mtl_tx_op |= MTL_OP_MODE_TSF;
+ } else {
+ pr_debug("GMAC: disabling TX SF (threshold %d)\n", mode);
+ mtl_tx_op &= ~MTL_OP_MODE_TSF;
+ mtl_tx_op &= ~MTL_OP_MODE_TTC_MASK;
+ /* Set the transmit threshold */
+ if (mode <= 32)
+ mtl_tx_op |= MTL_OP_MODE_TTC_32;
+ else if (mode <= 64)
+ mtl_tx_op |= MTL_OP_MODE_TTC_64;
+ else if (mode <= 96)
+ mtl_tx_op |= MTL_OP_MODE_TTC_96;
+ else if (mode <= 128)
+ mtl_tx_op |= MTL_OP_MODE_TTC_128;
+ else if (mode <= 192)
+ mtl_tx_op |= MTL_OP_MODE_TTC_192;
+ else if (mode <= 256)
+ mtl_tx_op |= MTL_OP_MODE_TTC_256;
+ else if (mode <= 384)
+ mtl_tx_op |= MTL_OP_MODE_TTC_384;
+ else
+ mtl_tx_op |= MTL_OP_MODE_TTC_512;
+ }
+ /* For an IP with DWC_EQOS_NUM_TXQ == 1, the fields TXQEN and TQS are RO
+ * with reset values: TXQEN on, TQS == DWC_EQOS_TXFIFO_SIZE.
+ * For an IP with DWC_EQOS_NUM_TXQ > 1, the fields TXQEN and TQS are R/W
+ * with reset values: TXQEN off, TQS 256 bytes.
+ *
+ * TXQEN must be written for multi-channel operation and TQS must
+ * reflect the available fifo size per queue (total fifo size / number
+ * of enabled queues).
+ */
+ mtl_tx_op &= ~MTL_OP_MODE_TXQEN_MASK;
+ if (qmode != MTL_QUEUE_AVB)
+ mtl_tx_op |= MTL_OP_MODE_TXQEN;
+ else
+ mtl_tx_op |= MTL_OP_MODE_TXQEN_AV;
+
+ mtl_tx_op = u32_replace_bits(mtl_tx_op, tqs, MTL_OP_MODE_TQS_MASK);
+
+ writel(mtl_tx_op, ioaddr + MTL_CHAN_TX_OP_MODE(dwmac4_addrs, channel));
+}
+
+static int dwmac4_get_hw_feature(void __iomem *ioaddr,
+ struct dma_features *dma_cap)
+{
+ u32 hw_cap = readl(ioaddr + GMAC_HW_FEATURE0);
+
+ /* MAC HW feature0 */
+ dma_cap->mbps_10_100 = (hw_cap & GMAC_HW_FEAT_MIISEL);
+ dma_cap->mbps_1000 = (hw_cap & GMAC_HW_FEAT_GMIISEL) >> 1;
+ dma_cap->half_duplex = (hw_cap & GMAC_HW_FEAT_HDSEL) >> 2;
+ dma_cap->vlhash = (hw_cap & GMAC_HW_FEAT_VLHASH) >> 4;
+ dma_cap->multi_addr = (hw_cap & GMAC_HW_FEAT_ADDMAC) >> 18;
+ dma_cap->pcs = (hw_cap & GMAC_HW_FEAT_PCSSEL) >> 3;
+ dma_cap->sma_mdio = (hw_cap & GMAC_HW_FEAT_SMASEL) >> 5;
+ dma_cap->pmt_remote_wake_up = (hw_cap & GMAC_HW_FEAT_RWKSEL) >> 6;
+ dma_cap->pmt_magic_frame = (hw_cap & GMAC_HW_FEAT_MGKSEL) >> 7;
+ /* MMC */
+ dma_cap->rmon = (hw_cap & GMAC_HW_FEAT_MMCSEL) >> 8;
+ /* IEEE 1588-2008 */
+ dma_cap->atime_stamp = (hw_cap & GMAC_HW_FEAT_TSSEL) >> 12;
+ /* 802.3az - Energy-Efficient Ethernet (EEE) */
+ dma_cap->eee = (hw_cap & GMAC_HW_FEAT_EEESEL) >> 13;
+ /* TX and RX csum */
+ dma_cap->tx_coe = (hw_cap & GMAC_HW_FEAT_TXCOSEL) >> 14;
+ dma_cap->rx_coe = (hw_cap & GMAC_HW_FEAT_RXCOESEL) >> 16;
+ dma_cap->vlins = (hw_cap & GMAC_HW_FEAT_SAVLANINS) >> 27;
+ dma_cap->arpoffsel = (hw_cap & GMAC_HW_FEAT_ARPOFFSEL) >> 9;
+
+ dma_cap->actphyif = FIELD_GET(DMA_HW_FEAT_ACTPHYIF, hw_cap);
+
+ /* MAC HW feature1 */
+ hw_cap = readl(ioaddr + GMAC_HW_FEATURE1);
+ dma_cap->l3l4fnum = (hw_cap & GMAC_HW_FEAT_L3L4FNUM) >> 27;
+ dma_cap->hash_tb_sz = (hw_cap & GMAC_HW_HASH_TB_SZ) >> 24;
+ dma_cap->av = (hw_cap & GMAC_HW_FEAT_AVSEL) >> 20;
+ dma_cap->tsoen = (hw_cap & GMAC_HW_TSOEN) >> 18;
+ dma_cap->sphen = (hw_cap & GMAC_HW_FEAT_SPHEN) >> 17;
+ dma_cap->dcben = (hw_cap & GMAC_HW_FEAT_DCBEN) >> 16;
+
+ dma_cap->addr64 = (hw_cap & GMAC_HW_ADDR64) >> 14;
+ switch (dma_cap->addr64) {
+ case 0:
+ dma_cap->addr64 = 32;
+ break;
+ case 1:
+ dma_cap->addr64 = 40;
+ break;
+ case 2:
+ dma_cap->addr64 = 48;
+ break;
+ default:
+ dma_cap->addr64 = 32;
+ break;
+ }
+
+ /* RX and TX FIFO sizes are encoded as log2(n / 128). Undo that by
+ * shifting and store the sizes in bytes.
+ */
+ dma_cap->tx_fifo_size = 128 << ((hw_cap & GMAC_HW_TXFIFOSIZE) >> 6);
+ dma_cap->rx_fifo_size = 128 << ((hw_cap & GMAC_HW_RXFIFOSIZE) >> 0);
+ /* MAC HW feature2 */
+ hw_cap = readl(ioaddr + GMAC_HW_FEATURE2);
+ /* TX and RX number of channels */
+ dma_cap->number_rx_channel =
+ ((hw_cap & GMAC_HW_FEAT_RXCHCNT) >> 12) + 1;
+ dma_cap->number_tx_channel =
+ ((hw_cap & GMAC_HW_FEAT_TXCHCNT) >> 18) + 1;
+ /* TX and RX number of queues */
+ dma_cap->number_rx_queues =
+ ((hw_cap & GMAC_HW_FEAT_RXQCNT) >> 0) + 1;
+ dma_cap->number_tx_queues =
+ ((hw_cap & GMAC_HW_FEAT_TXQCNT) >> 6) + 1;
+ /* PPS output */
+ dma_cap->pps_out_num = (hw_cap & GMAC_HW_FEAT_PPSOUTNUM) >> 24;
+
+ /* IEEE 1588-2002 */
+ dma_cap->time_stamp = 0;
+ /* Number of Auxiliary Snapshot Inputs */
+ dma_cap->aux_snapshot_n = (hw_cap & GMAC_HW_FEAT_AUXSNAPNUM) >> 28;
+
+ /* MAC HW feature3 */
+ hw_cap = readl(ioaddr + GMAC_HW_FEATURE3);
+
+ /* 5.10 Features */
+ dma_cap->asp = (hw_cap & GMAC_HW_FEAT_ASP) >> 28;
+ dma_cap->tbssel = (hw_cap & GMAC_HW_FEAT_TBSSEL) >> 27;
+ dma_cap->fpesel = (hw_cap & GMAC_HW_FEAT_FPESEL) >> 26;
+ dma_cap->estwid = (hw_cap & GMAC_HW_FEAT_ESTWID) >> 20;
+ dma_cap->estdep = (hw_cap & GMAC_HW_FEAT_ESTDEP) >> 17;
+ dma_cap->estsel = (hw_cap & GMAC_HW_FEAT_ESTSEL) >> 16;
+ dma_cap->frpes = (hw_cap & GMAC_HW_FEAT_FRPES) >> 13;
+ dma_cap->frpbs = (hw_cap & GMAC_HW_FEAT_FRPBS) >> 11;
+ dma_cap->frpsel = (hw_cap & GMAC_HW_FEAT_FRPSEL) >> 10;
+ dma_cap->dvlan = (hw_cap & GMAC_HW_FEAT_DVLAN) >> 5;
+
+ return 0;
+}
+
+/* Enable/disable TSO feature and set MSS */
+static void dwmac4_enable_tso(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value;
+
+ if (en) {
+ /* enable TSO */
+ value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+ writel(value | DMA_CONTROL_TSE,
+ ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+ } else {
+ /* enable TSO */
+ value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+ writel(value & ~DMA_CONTROL_TSE,
+ ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+ }
+}
+
+static void dwmac4_qmode(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 channel, u8 qmode)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 mtl_tx_op = readl(ioaddr + MTL_CHAN_TX_OP_MODE(dwmac4_addrs,
+ channel));
+
+ mtl_tx_op &= ~MTL_OP_MODE_TXQEN_MASK;
+ if (qmode != MTL_QUEUE_AVB)
+ mtl_tx_op |= MTL_OP_MODE_TXQEN;
+ else
+ mtl_tx_op |= MTL_OP_MODE_TXQEN_AV;
+
+ writel(mtl_tx_op, ioaddr + MTL_CHAN_TX_OP_MODE(dwmac4_addrs, channel));
+}
+
+static void dwmac4_set_bfsize(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int bfsize, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+
+ value = u32_replace_bits(value, bfsize, DMA_RBSZ_MASK);
+
+ writel(value, ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+}
+
+static void dwmac4_enable_sph(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + GMAC_EXT_CONFIG);
+
+ value &= ~GMAC_CONFIG_HDSMS;
+ value |= GMAC_CONFIG_HDSMS_256; /* Segment max 256 bytes */
+ writel(value, ioaddr + GMAC_EXT_CONFIG);
+
+ value = readl(ioaddr + GMAC_EXT_CFG1);
+ value |= GMAC_CONFIG1_SPLM(1); /* Split mode set to L2OFST */
+ value |= GMAC_CONFIG1_SAVE_EN; /* Enable Split AV mode */
+ writel(value, ioaddr + GMAC_EXT_CFG1);
+
+ value = readl(ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, chan));
+ if (en)
+ value |= DMA_CONTROL_SPH;
+ else
+ value &= ~DMA_CONTROL_SPH;
+ writel(value, ioaddr + DMA_CHAN_CONTROL(dwmac4_addrs, chan));
+}
+
+static int dwmac4_enable_tbs(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+
+ if (en)
+ value |= DMA_CONTROL_EDSE;
+ else
+ value &= ~DMA_CONTROL_EDSE;
+
+ writel(value, ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+
+ value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs,
+ chan)) & DMA_CONTROL_EDSE;
+ if (en && !value)
+ return -EIO;
+
+ writel(DMA_TBS_DEF_FTOS, ioaddr + DMA_TBS_CTRL);
+ return 0;
+}
+
+const struct stmmac_dma_ops dwmac4_dma_ops = {
+ .reset = dwmac4_dma_reset,
+ .init = dwmac4_dma_init,
+ .init_chan = dwmac4_dma_init_channel,
+ .init_rx_chan = dwmac4_dma_init_rx_chan,
+ .init_tx_chan = dwmac4_dma_init_tx_chan,
+ .axi = dwmac4_dma_axi,
+ .dump_regs = dwmac4_dump_dma_regs,
+ .dma_rx_mode = dwmac4_dma_rx_chan_op_mode,
+ .dma_tx_mode = dwmac4_dma_tx_chan_op_mode,
+ .enable_dma_irq = dwmac4_enable_dma_irq,
+ .disable_dma_irq = dwmac4_disable_dma_irq,
+ .start_tx = dwmac4_dma_start_tx,
+ .stop_tx = dwmac4_dma_stop_tx,
+ .start_rx = dwmac4_dma_start_rx,
+ .stop_rx = dwmac4_dma_stop_rx,
+ .dma_interrupt = dwmac4_dma_interrupt,
+ .get_hw_feature = dwmac4_get_hw_feature,
+ .rx_watchdog = dwmac4_rx_watchdog,
+ .set_rx_ring_len = dwmac4_set_rx_ring_len,
+ .set_tx_ring_len = dwmac4_set_tx_ring_len,
+ .set_rx_tail_ptr = dwmac4_set_rx_tail_ptr,
+ .set_tx_tail_ptr = dwmac4_set_tx_tail_ptr,
+ .enable_tso = dwmac4_enable_tso,
+ .qmode = dwmac4_qmode,
+ .set_bfsize = dwmac4_set_bfsize,
+ .enable_sph = dwmac4_enable_sph,
+};
+
+const struct stmmac_dma_ops dwmac410_dma_ops = {
+ .reset = dwmac4_dma_reset,
+ .init = dwmac4_dma_init,
+ .init_chan = dwmac410_dma_init_channel,
+ .init_rx_chan = dwmac4_dma_init_rx_chan,
+ .init_tx_chan = dwmac4_dma_init_tx_chan,
+ .axi = dwmac4_dma_axi,
+ .dump_regs = dwmac4_dump_dma_regs,
+ .dma_rx_mode = dwmac4_dma_rx_chan_op_mode,
+ .dma_tx_mode = dwmac4_dma_tx_chan_op_mode,
+ .enable_dma_irq = dwmac4_enable_dma_irq,
+ .disable_dma_irq = dwmac4_disable_dma_irq,
+ .start_tx = dwmac4_dma_start_tx,
+ .stop_tx = dwmac4_dma_stop_tx,
+ .start_rx = dwmac4_dma_start_rx,
+ .stop_rx = dwmac4_dma_stop_rx,
+ .dma_interrupt = dwmac4_dma_interrupt,
+ .get_hw_feature = dwmac4_get_hw_feature,
+ .rx_watchdog = dwmac4_rx_watchdog,
+ .set_rx_ring_len = dwmac4_set_rx_ring_len,
+ .set_tx_ring_len = dwmac4_set_tx_ring_len,
+ .set_rx_tail_ptr = dwmac4_set_rx_tail_ptr,
+ .set_tx_tail_ptr = dwmac4_set_tx_tail_ptr,
+ .enable_tso = dwmac4_enable_tso,
+ .qmode = dwmac4_qmode,
+ .set_bfsize = dwmac4_set_bfsize,
+ .enable_sph = dwmac4_enable_sph,
+ .enable_tbs = dwmac4_enable_tbs,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
new file mode 100644
index 000000000..43b036d4e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * DWMAC4 DMA Header file.
+ *
+ * Copyright (C) 2007-2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#ifndef __DWMAC4_DMA_H__
+#define __DWMAC4_DMA_H__
+
+/* Define the max channel number used for tx (also rx).
+ * dwmac4 accepts up to 8 channels for TX (and also 8 channels for RX
+ */
+#define DMA_CHANNEL_NB_MAX 1
+
+#define DMA_BUS_MODE 0x00001000
+
+#define DMA_BUS_MODE_DCHE BIT(19)
+#define DMA_BUS_MODE_INTM_MASK GENMASK(17, 16)
+#define DMA_BUS_MODE_INTM_MODE1 0x1
+#define DMA_BUS_MODE_SFT_RESET BIT(0)
+
+#define DMA_SYS_BUS_MODE 0x00001004
+
+#define DMA_BUS_MODE_MB BIT(14)
+#define DMA_BUS_MODE_FB BIT(0)
+
+#define DMA_STATUS 0x00001008
+
+#define DMA_AXI_BUS_MODE 0x00001028
+
+#define DMA_AXI_EN_LPI BIT(31)
+#define DMA_AXI_LPI_XIT_FRM BIT(30)
+#define DMA_AXI_WR_OSR_LMT GENMASK(27, 24)
+#define DMA_AXI_RD_OSR_LMT GENMASK(19, 16)
+
+#define DMA_SYS_BUS_MB BIT(14)
+#define DMA_SYS_BUS_AAL DMA_AXI_AAL
+#define DMA_SYS_BUS_EAME BIT(11)
+#define DMA_SYS_BUS_FB BIT(0)
+
+#define DMA_TBS_CTRL 0x00001050
+
+#define DMA_TBS_FTOS GENMASK(31, 8)
+#define DMA_TBS_FTOV BIT(0)
+#define DMA_TBS_DEF_FTOS (DMA_TBS_FTOS | DMA_TBS_FTOV)
+
+/* Following DMA defines are channel-oriented */
+#define DMA_CHAN_BASE_ADDR 0x00001100
+#define DMA_CHAN_BASE_OFFSET 0x80
+
+static inline u32 dma_chanx_base_addr(const struct dwmac4_addrs *addrs,
+ const u32 x)
+{
+ u32 addr;
+
+ if (addrs)
+ addr = addrs->dma_chan + (x * addrs->dma_chan_offset);
+ else
+ addr = DMA_CHAN_BASE_ADDR + (x * DMA_CHAN_BASE_OFFSET);
+
+ return addr;
+}
+
+#define DMA_CHAN_CONTROL(addrs, x) dma_chanx_base_addr(addrs, x)
+
+#define DMA_CHAN_CTRL_PBLX8 BIT(16)
+#define DMA_CONTROL_SPH BIT(24)
+
+#define DMA_CHAN_TX_CONTROL(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x4)
+
+#define DMA_CONTROL_EDSE BIT(28)
+#define DMA_CHAN_TX_CTRL_TXPBL_MASK GENMASK(21, 16)
+#define DMA_CONTROL_TSE BIT(12)
+#define DMA_CONTROL_OSP BIT(4)
+#define DMA_CONTROL_ST BIT(0)
+
+#define DMA_CHAN_RX_CONTROL(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x8)
+
+#define DMA_CHAN_RX_CTRL_RXPBL_MASK GENMASK(21, 16)
+#define DMA_RBSZ_MASK GENMASK(14, 1)
+#define DMA_CONTROL_SR BIT(0)
+
+#define DMA_CHAN_TX_BASE_ADDR_HI(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x10)
+#define DMA_CHAN_TX_BASE_ADDR(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x14)
+#define DMA_CHAN_RX_BASE_ADDR_HI(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x18)
+#define DMA_CHAN_RX_BASE_ADDR(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x1c)
+#define DMA_CHAN_TX_END_ADDR(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x20)
+#define DMA_CHAN_RX_END_ADDR(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x28)
+#define DMA_CHAN_TX_RING_LEN(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x2c)
+#define DMA_CHAN_RX_RING_LEN(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x30)
+
+#define DMA_CHAN_INTR_ENA(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x34)
+
+#define DMA_CHAN_INTR_ENA_NIE BIT(16)
+#define DMA_CHAN_INTR_ENA_AIE BIT(15)
+#define DMA_CHAN_INTR_ENA_NIE_4_10 BIT(15)
+#define DMA_CHAN_INTR_ENA_AIE_4_10 BIT(14)
+#define DMA_CHAN_INTR_ENA_FBE BIT(12)
+#define DMA_CHAN_INTR_ENA_RPS BIT(8)
+#define DMA_CHAN_INTR_ENA_RBU BIT(7)
+#define DMA_CHAN_INTR_ENA_RIE BIT(6)
+#define DMA_CHAN_INTR_ENA_TIE BIT(0)
+
+#define DMA_CHAN_INTR_NORMAL (DMA_CHAN_INTR_ENA_NIE | \
+ DMA_CHAN_INTR_ENA_RIE | \
+ DMA_CHAN_INTR_ENA_TIE)
+
+#define DMA_CHAN_INTR_ABNORMAL (DMA_CHAN_INTR_ENA_AIE | \
+ DMA_CHAN_INTR_ENA_RPS | \
+ DMA_CHAN_INTR_ENA_RBU | \
+ DMA_CHAN_INTR_ENA_FBE)
+/* DMA default interrupt mask for 4.00 */
+#define DMA_CHAN_INTR_DEFAULT_MASK (DMA_CHAN_INTR_NORMAL | \
+ DMA_CHAN_INTR_ABNORMAL)
+
+#define DMA_CHAN_INTR_NORMAL_4_10 (DMA_CHAN_INTR_ENA_NIE_4_10 | \
+ DMA_CHAN_INTR_ENA_RIE | \
+ DMA_CHAN_INTR_ENA_TIE)
+
+#define DMA_CHAN_INTR_ABNORMAL_4_10 (DMA_CHAN_INTR_ENA_AIE_4_10 | \
+ DMA_CHAN_INTR_ENA_RPS | \
+ DMA_CHAN_INTR_ENA_RBU | \
+ DMA_CHAN_INTR_ENA_FBE)
+/* DMA default interrupt mask for 4.10a */
+#define DMA_CHAN_INTR_DEFAULT_MASK_4_10 (DMA_CHAN_INTR_NORMAL_4_10 | \
+ DMA_CHAN_INTR_ABNORMAL_4_10)
+
+#define DMA_CHAN_RX_WATCHDOG(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x38)
+#define DMA_CHAN_SLOT_CTRL_STATUS(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x3c)
+#define DMA_CHAN_CUR_TX_DESC(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x44)
+#define DMA_CHAN_CUR_RX_DESC(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x4c)
+#define DMA_CHAN_CUR_TX_BUF_ADDR_HI(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x50)
+#define DMA_CHAN_CUR_TX_BUF_ADDR(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x54)
+#define DMA_CHAN_CUR_RX_BUF_ADDR_HI(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x58)
+#define DMA_CHAN_CUR_RX_BUF_ADDR(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x5c)
+#define DMA_CHAN_STATUS(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x60)
+
+/* Interrupt status per channel */
+#define DMA_CHAN_STATUS_REB GENMASK(21, 19)
+#define DMA_CHAN_STATUS_NIS BIT(15)
+#define DMA_CHAN_STATUS_AIS BIT(14)
+#define DMA_CHAN_STATUS_CDE BIT(13)
+#define DMA_CHAN_STATUS_FBE BIT(12)
+#define DMA_CHAN_STATUS_ERI BIT(11)
+#define DMA_CHAN_STATUS_ETI BIT(10)
+#define DMA_CHAN_STATUS_RWT BIT(9)
+#define DMA_CHAN_STATUS_RPS BIT(8)
+#define DMA_CHAN_STATUS_RBU BIT(7)
+#define DMA_CHAN_STATUS_RI BIT(6)
+#define DMA_CHAN_STATUS_TBU BIT(2)
+#define DMA_CHAN_STATUS_TPS BIT(1)
+#define DMA_CHAN_STATUS_TI BIT(0)
+
+#define DMA_CHAN_STATUS_MSK_COMMON (DMA_CHAN_STATUS_NIS | \
+ DMA_CHAN_STATUS_AIS | \
+ DMA_CHAN_STATUS_CDE | \
+ DMA_CHAN_STATUS_FBE)
+
+#define DMA_CHAN_STATUS_MSK_RX (DMA_CHAN_STATUS_REB | \
+ DMA_CHAN_STATUS_ERI | \
+ DMA_CHAN_STATUS_RWT | \
+ DMA_CHAN_STATUS_RPS | \
+ DMA_CHAN_STATUS_RBU | \
+ DMA_CHAN_STATUS_RI | \
+ DMA_CHAN_STATUS_MSK_COMMON)
+
+#define DMA_CHAN_STATUS_MSK_TX (DMA_CHAN_STATUS_ETI | \
+ DMA_CHAN_STATUS_TBU | \
+ DMA_CHAN_STATUS_TPS | \
+ DMA_CHAN_STATUS_TI | \
+ DMA_CHAN_STATUS_MSK_COMMON)
+
+int dwmac4_dma_reset(void __iomem *ioaddr);
+void dwmac4_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx);
+void dwmac4_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx);
+void dwmac4_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+void dwmac4_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+void dwmac4_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+void dwmac4_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+int dwmac4_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan, u32 dir);
+void dwmac4_set_rx_ring_len(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 len, u32 chan);
+void dwmac4_set_tx_ring_len(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 len, u32 chan);
+void dwmac4_set_rx_tail_ptr(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 tail_ptr, u32 chan);
+void dwmac4_set_tx_tail_ptr(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 tail_ptr, u32 chan);
+
+#endif /* __DWMAC4_DMA_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
new file mode 100644
index 000000000..a0249715f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2007-2015 STMicroelectronics Ltd
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/delay.h>
+#include "common.h"
+#include "dwmac4_dma.h"
+#include "dwmac4.h"
+#include "stmmac.h"
+
+int dwmac4_dma_reset(void __iomem *ioaddr)
+{
+ u32 value = readl(ioaddr + DMA_BUS_MODE);
+
+ /* DMA SW reset */
+ value |= DMA_BUS_MODE_SFT_RESET;
+ writel(value, ioaddr + DMA_BUS_MODE);
+
+ return readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+ !(value & DMA_BUS_MODE_SFT_RESET),
+ 10000, 1000000);
+}
+
+void dwmac4_set_rx_tail_ptr(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 tail_ptr, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ writel(tail_ptr, ioaddr + DMA_CHAN_RX_END_ADDR(dwmac4_addrs, chan));
+}
+
+void dwmac4_set_tx_tail_ptr(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 tail_ptr, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ writel(tail_ptr, ioaddr + DMA_CHAN_TX_END_ADDR(dwmac4_addrs, chan));
+}
+
+void dwmac4_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+
+ value |= DMA_CONTROL_ST;
+ writel(value, ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+
+ value = readl(ioaddr + GMAC_CONFIG);
+ value |= GMAC_CONFIG_TE;
+ writel(value, ioaddr + GMAC_CONFIG);
+}
+
+void dwmac4_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ u32 value = readl(ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+
+ value &= ~DMA_CONTROL_ST;
+ writel(value, ioaddr + DMA_CHAN_TX_CONTROL(dwmac4_addrs, chan));
+}
+
+void dwmac4_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ u32 value = readl(ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+
+ value |= DMA_CONTROL_SR;
+
+ writel(value, ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+
+ value = readl(ioaddr + GMAC_CONFIG);
+ value |= GMAC_CONFIG_RE;
+ writel(value, ioaddr + GMAC_CONFIG);
+}
+
+void dwmac4_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+
+ value &= ~DMA_CONTROL_SR;
+ writel(value, ioaddr + DMA_CHAN_RX_CONTROL(dwmac4_addrs, chan));
+}
+
+void dwmac4_set_tx_ring_len(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 len, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ writel(len, ioaddr + DMA_CHAN_TX_RING_LEN(dwmac4_addrs, chan));
+}
+
+void dwmac4_set_rx_ring_len(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 len, u32 chan)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+
+ writel(len, ioaddr + DMA_CHAN_RX_RING_LEN(dwmac4_addrs, chan));
+}
+
+void dwmac4_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+
+ if (rx)
+ value |= DMA_CHAN_INTR_ENA_RIE;
+ if (tx)
+ value |= DMA_CHAN_INTR_ENA_TIE;
+
+ writel(value, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+}
+
+void dwmac4_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 value = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+
+ if (rx)
+ value &= ~DMA_CHAN_INTR_ENA_RIE;
+ if (tx)
+ value &= ~DMA_CHAN_INTR_ENA_TIE;
+
+ writel(value, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+}
+
+int dwmac4_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan, u32 dir)
+{
+ const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs;
+ u32 intr_status = readl(ioaddr + DMA_CHAN_STATUS(dwmac4_addrs, chan));
+ u32 intr_en = readl(ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan));
+ struct stmmac_pcpu_stats *stats = this_cpu_ptr(priv->xstats.pcpu_stats);
+ int ret = 0;
+
+ if (dir == DMA_DIR_RX)
+ intr_status &= DMA_CHAN_STATUS_MSK_RX;
+ else if (dir == DMA_DIR_TX)
+ intr_status &= DMA_CHAN_STATUS_MSK_TX;
+
+ /* ABNORMAL interrupts */
+ if (unlikely(intr_status & DMA_CHAN_STATUS_AIS)) {
+ if (unlikely(intr_status & DMA_CHAN_STATUS_RBU))
+ x->rx_buf_unav_irq++;
+ if (unlikely(intr_status & DMA_CHAN_STATUS_RPS))
+ x->rx_process_stopped_irq++;
+ if (unlikely(intr_status & DMA_CHAN_STATUS_ETI))
+ x->tx_early_irq++;
+ if (unlikely(intr_status & DMA_CHAN_STATUS_TPS)) {
+ x->tx_process_stopped_irq++;
+ ret = tx_hard_error;
+ }
+ if (unlikely(intr_status & DMA_CHAN_STATUS_FBE)) {
+ x->fatal_bus_error_irq++;
+ ret = tx_hard_error;
+ }
+ }
+
+ if (unlikely(intr_status & DMA_CHAN_STATUS_RWT))
+ x->rx_watchdog_irq++;
+
+ /* TX/RX NORMAL interrupts */
+ if (likely(intr_status & DMA_CHAN_STATUS_RI)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->rx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_rx;
+ }
+ if (likely(intr_status & DMA_CHAN_STATUS_TI)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->tx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_tx;
+ }
+
+ if (unlikely(intr_status & DMA_CHAN_STATUS_TBU))
+ ret |= handle_tx;
+ if (unlikely(intr_status & DMA_CHAN_STATUS_ERI))
+ x->rx_early_irq++;
+
+ writel(intr_status & intr_en,
+ ioaddr + DMA_CHAN_STATUS(dwmac4_addrs, chan));
+ return ret;
+}
+
+void stmmac_dwmac4_set_mac_addr(void __iomem *ioaddr, const u8 addr[6],
+ unsigned int high, unsigned int low)
+{
+ unsigned long data;
+
+ data = (addr[5] << 8) | addr[4];
+ /* For MAC Addr registers se have to set the Address Enable (AE)
+ * bit that has no effect on the High Reg 0 where the bit 31 (MO)
+ * is RO.
+ */
+ data |= FIELD_PREP(GMAC_HI_DCS, STMMAC_CHAN0);
+ writel(data | GMAC_HI_REG_AE, ioaddr + high);
+ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ writel(data, ioaddr + low);
+}
+
+/* Enable disable MAC RX/TX */
+void stmmac_dwmac4_set_mac(void __iomem *ioaddr, bool enable)
+{
+ u32 value = readl(ioaddr + GMAC_CONFIG);
+ u32 old_val = value;
+
+ if (enable)
+ value |= GMAC_CONFIG_RE | GMAC_CONFIG_TE;
+ else
+ value &= ~(GMAC_CONFIG_TE | GMAC_CONFIG_RE);
+
+ if (value != old_val)
+ writel(value, ioaddr + GMAC_CONFIG);
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac5.c b/drivers/net/ethernet/stmicro/stmmac/dwmac5.c
new file mode 100644
index 000000000..f1bb981ca
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac5.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+// Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+// stmmac Support for 5.xx Ethernet QoS cores
+
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include "common.h"
+#include "dwmac4.h"
+#include "dwmac5.h"
+#include "stmmac.h"
+#include "stmmac_ptp.h"
+
+struct dwmac5_error_desc {
+ bool valid;
+ const char *desc;
+ const char *detailed_desc;
+};
+
+#define STAT_OFF(field) offsetof(struct stmmac_safety_stats, field)
+
+static void dwmac5_log_error(struct net_device *ndev, u32 value, bool corr,
+ const char *module_name, const struct dwmac5_error_desc *desc,
+ unsigned long field_offset, struct stmmac_safety_stats *stats)
+{
+ unsigned long loc, mask;
+ u8 *bptr = (u8 *)stats;
+ unsigned long *ptr;
+
+ ptr = (unsigned long *)(bptr + field_offset);
+
+ mask = value;
+ for_each_set_bit(loc, &mask, 32) {
+ netdev_err(ndev, "Found %s error in %s: '%s: %s'\n", corr ?
+ "correctable" : "uncorrectable", module_name,
+ desc[loc].desc, desc[loc].detailed_desc);
+
+ /* Update counters */
+ ptr[loc]++;
+ }
+}
+
+static const struct dwmac5_error_desc dwmac5_mac_errors[32]= {
+ { true, "ATPES", "Application Transmit Interface Parity Check Error" },
+ { true, "TPES", "TSO Data Path Parity Check Error" },
+ { true, "RDPES", "Read Descriptor Parity Check Error" },
+ { true, "MPES", "MTL Data Path Parity Check Error" },
+ { true, "MTSPES", "MTL TX Status Data Path Parity Check Error" },
+ { true, "ARPES", "Application Receive Interface Data Path Parity Check Error" },
+ { true, "CWPES", "CSR Write Data Path Parity Check Error" },
+ { true, "ASRPES", "AXI Slave Read Data Path Parity Check Error" },
+ { true, "TTES", "TX FSM Timeout Error" },
+ { true, "RTES", "RX FSM Timeout Error" },
+ { true, "CTES", "CSR FSM Timeout Error" },
+ { true, "ATES", "APP FSM Timeout Error" },
+ { true, "PTES", "PTP FSM Timeout Error" },
+ { true, "T125ES", "TX125 FSM Timeout Error" },
+ { true, "R125ES", "RX125 FSM Timeout Error" },
+ { true, "RVCTES", "REV MDC FSM Timeout Error" },
+ { true, "MSTTES", "Master Read/Write Timeout Error" },
+ { true, "SLVTES", "Slave Read/Write Timeout Error" },
+ { true, "ATITES", "Application Timeout on ATI Interface Error" },
+ { true, "ARITES", "Application Timeout on ARI Interface Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 20 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 21 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 22 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 23 */
+ { true, "FSMPES", "FSM State Parity Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 25 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 26 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 27 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 28 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 29 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 30 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 31 */
+};
+
+static void dwmac5_handle_mac_err(struct net_device *ndev,
+ void __iomem *ioaddr, bool correctable,
+ struct stmmac_safety_stats *stats)
+{
+ u32 value;
+
+ value = readl(ioaddr + MAC_DPP_FSM_INT_STATUS);
+ writel(value, ioaddr + MAC_DPP_FSM_INT_STATUS);
+
+ dwmac5_log_error(ndev, value, correctable, "MAC", dwmac5_mac_errors,
+ STAT_OFF(mac_errors), stats);
+}
+
+static const struct dwmac5_error_desc dwmac5_mtl_errors[32]= {
+ { true, "TXCES", "MTL TX Memory Error" },
+ { true, "TXAMS", "MTL TX Memory Address Mismatch Error" },
+ { true, "TXUES", "MTL TX Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 3 */
+ { true, "RXCES", "MTL RX Memory Error" },
+ { true, "RXAMS", "MTL RX Memory Address Mismatch Error" },
+ { true, "RXUES", "MTL RX Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 7 */
+ { true, "ECES", "MTL EST Memory Error" },
+ { true, "EAMS", "MTL EST Memory Address Mismatch Error" },
+ { true, "EUES", "MTL EST Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 11 */
+ { true, "RPCES", "MTL RX Parser Memory Error" },
+ { true, "RPAMS", "MTL RX Parser Memory Address Mismatch Error" },
+ { true, "RPUES", "MTL RX Parser Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 15 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 16 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 17 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 18 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 19 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 20 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 21 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 22 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 23 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 24 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 25 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 26 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 27 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 28 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 29 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 30 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 31 */
+};
+
+static void dwmac5_handle_mtl_err(struct net_device *ndev,
+ void __iomem *ioaddr, bool correctable,
+ struct stmmac_safety_stats *stats)
+{
+ u32 value;
+
+ value = readl(ioaddr + MTL_ECC_INT_STATUS);
+ writel(value, ioaddr + MTL_ECC_INT_STATUS);
+
+ dwmac5_log_error(ndev, value, correctable, "MTL", dwmac5_mtl_errors,
+ STAT_OFF(mtl_errors), stats);
+}
+
+static const struct dwmac5_error_desc dwmac5_dma_errors[32]= {
+ { true, "TCES", "DMA TSO Memory Error" },
+ { true, "TAMS", "DMA TSO Memory Address Mismatch Error" },
+ { true, "TUES", "DMA TSO Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 3 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 4 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 5 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 6 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 7 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 8 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 9 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 10 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 11 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 12 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 13 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 14 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 15 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 16 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 17 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 18 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 19 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 20 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 21 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 22 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 23 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 24 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 25 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 26 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 27 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 28 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 29 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 30 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 31 */
+};
+
+static void dwmac5_handle_dma_err(struct net_device *ndev,
+ void __iomem *ioaddr, bool correctable,
+ struct stmmac_safety_stats *stats)
+{
+ u32 value;
+
+ value = readl(ioaddr + DMA_ECC_INT_STATUS);
+ writel(value, ioaddr + DMA_ECC_INT_STATUS);
+
+ dwmac5_log_error(ndev, value, correctable, "DMA", dwmac5_dma_errors,
+ STAT_OFF(dma_errors), stats);
+}
+
+int dwmac5_safety_feat_config(void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_feature_cfg *safety_feat_cfg)
+{
+ struct stmmac_safety_feature_cfg all_safety_feats = {
+ .tsoee = 1,
+ .mrxpee = 1,
+ .mestee = 1,
+ .mrxee = 1,
+ .mtxee = 1,
+ .epsi = 1,
+ .edpp = 1,
+ .prtyen = 1,
+ .tmouten = 1,
+ };
+ u32 value;
+
+ if (!asp)
+ return -EINVAL;
+
+ if (!safety_feat_cfg)
+ safety_feat_cfg = &all_safety_feats;
+
+ /* 1. Enable Safety Features */
+ value = readl(ioaddr + MTL_ECC_CONTROL);
+ value |= MEEAO; /* MTL ECC Error Addr Status Override */
+ if (safety_feat_cfg->tsoee)
+ value |= TSOEE; /* TSO ECC */
+ if (safety_feat_cfg->mrxpee)
+ value |= MRXPEE; /* MTL RX Parser ECC */
+ if (safety_feat_cfg->mestee)
+ value |= MESTEE; /* MTL EST ECC */
+ if (safety_feat_cfg->mrxee)
+ value |= MRXEE; /* MTL RX FIFO ECC */
+ if (safety_feat_cfg->mtxee)
+ value |= MTXEE; /* MTL TX FIFO ECC */
+ writel(value, ioaddr + MTL_ECC_CONTROL);
+
+ /* 2. Enable MTL Safety Interrupts */
+ value = readl(ioaddr + MTL_ECC_INT_ENABLE);
+ value |= RPCEIE; /* RX Parser Memory Correctable Error */
+ value |= ECEIE; /* EST Memory Correctable Error */
+ value |= RXCEIE; /* RX Memory Correctable Error */
+ value |= TXCEIE; /* TX Memory Correctable Error */
+ writel(value, ioaddr + MTL_ECC_INT_ENABLE);
+
+ /* 3. Enable DMA Safety Interrupts */
+ value = readl(ioaddr + DMA_ECC_INT_ENABLE);
+ value |= TCEIE; /* TSO Memory Correctable Error */
+ writel(value, ioaddr + DMA_ECC_INT_ENABLE);
+
+ /* Only ECC Protection for External Memory feature is selected */
+ if (asp <= 0x1)
+ return 0;
+
+ /* 5. Enable Parity and Timeout for FSM */
+ value = readl(ioaddr + MAC_FSM_CONTROL);
+ if (safety_feat_cfg->prtyen)
+ value |= PRTYEN; /* FSM Parity Feature */
+ if (safety_feat_cfg->tmouten)
+ value |= TMOUTEN; /* FSM Timeout Feature */
+ writel(value, ioaddr + MAC_FSM_CONTROL);
+
+ /* 4. Enable Data Parity Protection */
+ value = readl(ioaddr + MTL_DPP_CONTROL);
+ if (safety_feat_cfg->edpp)
+ value |= EDPP;
+ writel(value, ioaddr + MTL_DPP_CONTROL);
+
+ /*
+ * All the Automotive Safety features are selected without the "Parity
+ * Port Enable for external interface" feature.
+ */
+ if (asp <= 0x2)
+ return 0;
+
+ if (safety_feat_cfg->epsi)
+ value |= EPSI;
+ writel(value, ioaddr + MTL_DPP_CONTROL);
+ return 0;
+}
+
+int dwmac5_safety_feat_irq_status(struct net_device *ndev,
+ void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_stats *stats)
+{
+ bool err, corr;
+ u32 mtl, dma;
+ int ret = 0;
+
+ if (!asp)
+ return -EINVAL;
+
+ mtl = readl(ioaddr + MTL_SAFETY_INT_STATUS);
+ dma = readl(ioaddr + DMA_SAFETY_INT_STATUS);
+
+ err = (mtl & MCSIS) || (dma & MCSIS);
+ corr = false;
+ if (err) {
+ dwmac5_handle_mac_err(ndev, ioaddr, corr, stats);
+ ret |= !corr;
+ }
+
+ err = (mtl & (MEUIS | MECIS)) || (dma & (MSUIS | MSCIS));
+ corr = (mtl & MECIS) || (dma & MSCIS);
+ if (err) {
+ dwmac5_handle_mtl_err(ndev, ioaddr, corr, stats);
+ ret |= !corr;
+ }
+
+ err = dma & (DEUIS | DECIS);
+ corr = dma & DECIS;
+ if (err) {
+ dwmac5_handle_dma_err(ndev, ioaddr, corr, stats);
+ ret |= !corr;
+ }
+
+ return ret;
+}
+
+static const struct dwmac5_error {
+ const struct dwmac5_error_desc *desc;
+} dwmac5_all_errors[] = {
+ { dwmac5_mac_errors },
+ { dwmac5_mtl_errors },
+ { dwmac5_dma_errors },
+};
+
+int dwmac5_safety_feat_dump(struct stmmac_safety_stats *stats,
+ int index, unsigned long *count, const char **desc)
+{
+ int module = index / 32, offset = index % 32;
+ unsigned long *ptr = (unsigned long *)stats;
+
+ if (module >= ARRAY_SIZE(dwmac5_all_errors))
+ return -EINVAL;
+ if (!dwmac5_all_errors[module].desc[offset].valid)
+ return -EINVAL;
+ if (count)
+ *count = *(ptr + index);
+ if (desc)
+ *desc = dwmac5_all_errors[module].desc[offset].desc;
+ return 0;
+}
+
+static int dwmac5_rxp_disable(void __iomem *ioaddr)
+{
+ u32 val;
+
+ val = readl(ioaddr + MTL_OPERATION_MODE);
+ val &= ~MTL_FRPE;
+ writel(val, ioaddr + MTL_OPERATION_MODE);
+
+ return readl_poll_timeout(ioaddr + MTL_RXP_CONTROL_STATUS, val,
+ val & RXPI, 1, 10000);
+}
+
+static void dwmac5_rxp_enable(void __iomem *ioaddr)
+{
+ u32 val;
+
+ val = readl(ioaddr + MTL_OPERATION_MODE);
+ val |= MTL_FRPE;
+ writel(val, ioaddr + MTL_OPERATION_MODE);
+}
+
+static int dwmac5_rxp_update_single_entry(void __iomem *ioaddr,
+ struct stmmac_tc_entry *entry,
+ int pos)
+{
+ int ret, i;
+
+ for (i = 0; i < (sizeof(entry->val) / sizeof(u32)); i++) {
+ int real_pos = pos * (sizeof(entry->val) / sizeof(u32)) + i;
+ u32 val;
+
+ /* Wait for ready */
+ ret = readl_poll_timeout(ioaddr + MTL_RXP_IACC_CTRL_STATUS,
+ val, !(val & STARTBUSY), 1, 10000);
+ if (ret)
+ return ret;
+
+ /* Write data */
+ val = *((u32 *)&entry->val + i);
+ writel(val, ioaddr + MTL_RXP_IACC_DATA);
+
+ /* Write pos */
+ val = real_pos & ADDR;
+ writel(val, ioaddr + MTL_RXP_IACC_CTRL_STATUS);
+
+ /* Write OP */
+ val |= WRRDN;
+ writel(val, ioaddr + MTL_RXP_IACC_CTRL_STATUS);
+
+ /* Start Write */
+ val |= STARTBUSY;
+ writel(val, ioaddr + MTL_RXP_IACC_CTRL_STATUS);
+
+ /* Wait for done */
+ ret = readl_poll_timeout(ioaddr + MTL_RXP_IACC_CTRL_STATUS,
+ val, !(val & STARTBUSY), 1, 10000);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct stmmac_tc_entry *
+dwmac5_rxp_get_next_entry(struct stmmac_tc_entry *entries, unsigned int count,
+ u32 curr_prio)
+{
+ struct stmmac_tc_entry *entry;
+ u32 min_prio = ~0x0;
+ int i, min_prio_idx;
+ bool found = false;
+
+ for (i = count - 1; i >= 0; i--) {
+ entry = &entries[i];
+
+ /* Do not update unused entries */
+ if (!entry->in_use)
+ continue;
+ /* Do not update already updated entries (i.e. fragments) */
+ if (entry->in_hw)
+ continue;
+ /* Let last entry be updated last */
+ if (entry->is_last)
+ continue;
+ /* Do not return fragments */
+ if (entry->is_frag)
+ continue;
+ /* Check if we already checked this prio */
+ if (entry->prio < curr_prio)
+ continue;
+ /* Check if this is the minimum prio */
+ if (entry->prio < min_prio) {
+ min_prio = entry->prio;
+ min_prio_idx = i;
+ found = true;
+ }
+ }
+
+ if (found)
+ return &entries[min_prio_idx];
+ return NULL;
+}
+
+int dwmac5_rxp_config(void __iomem *ioaddr, struct stmmac_tc_entry *entries,
+ unsigned int count)
+{
+ struct stmmac_tc_entry *entry, *frag;
+ int i, ret, nve = 0;
+ u32 curr_prio = 0;
+ u32 old_val, val;
+
+ /* Force disable RX */
+ old_val = readl(ioaddr + GMAC_CONFIG);
+ val = old_val & ~GMAC_CONFIG_RE;
+ writel(val, ioaddr + GMAC_CONFIG);
+
+ /* Disable RX Parser */
+ ret = dwmac5_rxp_disable(ioaddr);
+ if (ret)
+ goto re_enable;
+
+ /* Set all entries as NOT in HW */
+ for (i = 0; i < count; i++) {
+ entry = &entries[i];
+ entry->in_hw = false;
+ }
+
+ /* Update entries by reverse order */
+ while (1) {
+ entry = dwmac5_rxp_get_next_entry(entries, count, curr_prio);
+ if (!entry)
+ break;
+
+ curr_prio = entry->prio;
+ frag = entry->frag_ptr;
+
+ /* Set special fragment requirements */
+ if (frag) {
+ entry->val.af = 0;
+ entry->val.rf = 0;
+ entry->val.nc = 1;
+ entry->val.ok_index = nve + 2;
+ }
+
+ ret = dwmac5_rxp_update_single_entry(ioaddr, entry, nve);
+ if (ret)
+ goto re_enable;
+
+ entry->table_pos = nve++;
+ entry->in_hw = true;
+
+ if (frag && !frag->in_hw) {
+ ret = dwmac5_rxp_update_single_entry(ioaddr, frag, nve);
+ if (ret)
+ goto re_enable;
+ frag->table_pos = nve++;
+ frag->in_hw = true;
+ }
+ }
+
+ if (!nve)
+ goto re_enable;
+
+ /* Update all pass entry */
+ for (i = 0; i < count; i++) {
+ entry = &entries[i];
+ if (!entry->is_last)
+ continue;
+
+ ret = dwmac5_rxp_update_single_entry(ioaddr, entry, nve);
+ if (ret)
+ goto re_enable;
+
+ entry->table_pos = nve++;
+ }
+
+ /* Assume n. of parsable entries == n. of valid entries */
+ val = (nve << 16) & NPE;
+ val |= nve & NVE;
+ writel(val, ioaddr + MTL_RXP_CONTROL_STATUS);
+
+ /* Enable RX Parser */
+ dwmac5_rxp_enable(ioaddr);
+
+re_enable:
+ /* Re-enable RX */
+ writel(old_val, ioaddr + GMAC_CONFIG);
+ return ret;
+}
+
+int dwmac5_flex_pps_config(void __iomem *ioaddr, int index,
+ struct stmmac_pps_cfg *cfg, bool enable,
+ u32 sub_second_inc, u32 systime_flags)
+{
+ u32 tnsec = readl(ioaddr + MAC_PPSx_TARGET_TIME_NSEC(index));
+ u32 val = readl(ioaddr + MAC_PPS_CONTROL);
+ u64 period;
+
+ if (tnsec & TRGTBUSY0)
+ return -EBUSY;
+ if (!sub_second_inc || !systime_flags)
+ return -EINVAL;
+
+ val &= ~PPSx_MASK(index);
+
+ if (!enable) {
+ val |= PPSCMDx(index, 0x5);
+ val |= PPSEN0;
+ writel(val, ioaddr + MAC_PPS_CONTROL);
+ return 0;
+ }
+
+ val |= TRGTMODSELx(index, 0x2);
+ val |= PPSEN0;
+ writel(val, ioaddr + MAC_PPS_CONTROL);
+
+ writel(cfg->start.tv_sec, ioaddr + MAC_PPSx_TARGET_TIME_SEC(index));
+
+ if (!(systime_flags & PTP_TCR_TSCTRLSSR))
+ cfg->start.tv_nsec = (cfg->start.tv_nsec * 1000) / 465;
+ writel(cfg->start.tv_nsec, ioaddr + MAC_PPSx_TARGET_TIME_NSEC(index));
+
+ period = cfg->period.tv_sec * 1000000000;
+ period += cfg->period.tv_nsec;
+
+ do_div(period, sub_second_inc);
+
+ if (period <= 1)
+ return -EINVAL;
+
+ writel(period - 1, ioaddr + MAC_PPSx_INTERVAL(index));
+
+ period >>= 1;
+ if (period <= 1)
+ return -EINVAL;
+
+ writel(period - 1, ioaddr + MAC_PPSx_WIDTH(index));
+
+ /* Finally, activate it */
+ val |= PPSCMDx(index, 0x2);
+ writel(val, ioaddr + MAC_PPS_CONTROL);
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac5.h b/drivers/net/ethernet/stmicro/stmmac/dwmac5.h
new file mode 100644
index 000000000..00b151b3b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac5.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+// Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+// stmmac Support for 5.xx Ethernet QoS cores
+
+#ifndef __DWMAC5_H__
+#define __DWMAC5_H__
+
+#define MAC_DPP_FSM_INT_STATUS 0x00000140
+#define MAC_AXI_SLV_DPE_ADDR_STATUS 0x00000144
+#define MAC_FSM_CONTROL 0x00000148
+#define PRTYEN BIT(1)
+#define TMOUTEN BIT(0)
+
+#define MAC_PPS_CONTROL 0x00000b70
+#define PPS_MAXIDX(x) ((((x) + 1) * 8) - 1)
+#define PPS_MINIDX(x) ((x) * 8)
+#define PPSx_MASK(x) GENMASK(PPS_MAXIDX(x), PPS_MINIDX(x))
+#define MCGRENx(x) BIT(PPS_MAXIDX(x))
+#define TRGTMODSELx(x, val) \
+ GENMASK(PPS_MAXIDX(x) - 1, PPS_MAXIDX(x) - 2) & \
+ ((val) << (PPS_MAXIDX(x) - 2))
+#define PPSCMDx(x, val) \
+ GENMASK(PPS_MINIDX(x) + 3, PPS_MINIDX(x)) & \
+ ((val) << PPS_MINIDX(x))
+#define PPSEN0 BIT(4)
+#define MAC_PPSx_TARGET_TIME_SEC(x) (0x00000b80 + ((x) * 0x10))
+#define MAC_PPSx_TARGET_TIME_NSEC(x) (0x00000b84 + ((x) * 0x10))
+#define TRGTBUSY0 BIT(31)
+#define TTSL0 GENMASK(30, 0)
+#define MAC_PPSx_INTERVAL(x) (0x00000b88 + ((x) * 0x10))
+#define MAC_PPSx_WIDTH(x) (0x00000b8c + ((x) * 0x10))
+
+#define MTL_RXP_CONTROL_STATUS 0x00000ca0
+#define RXPI BIT(31)
+#define NPE GENMASK(23, 16)
+#define NVE GENMASK(7, 0)
+#define MTL_RXP_IACC_CTRL_STATUS 0x00000cb0
+#define STARTBUSY BIT(31)
+#define RXPEIEC GENMASK(22, 21)
+#define RXPEIEE BIT(20)
+#define WRRDN BIT(16)
+#define ADDR GENMASK(15, 0)
+#define MTL_RXP_IACC_DATA 0x00000cb4
+#define MTL_ECC_CONTROL 0x00000cc0
+#define MEEAO BIT(8)
+#define TSOEE BIT(4)
+#define MRXPEE BIT(3)
+#define MESTEE BIT(2)
+#define MRXEE BIT(1)
+#define MTXEE BIT(0)
+
+#define MTL_SAFETY_INT_STATUS 0x00000cc4
+#define MCSIS BIT(31)
+#define MEUIS BIT(1)
+#define MECIS BIT(0)
+#define MTL_ECC_INT_ENABLE 0x00000cc8
+#define RPCEIE BIT(12)
+#define ECEIE BIT(8)
+#define RXCEIE BIT(4)
+#define TXCEIE BIT(0)
+#define MTL_ECC_INT_STATUS 0x00000ccc
+#define MTL_DPP_CONTROL 0x00000ce0
+#define EPSI BIT(2)
+#define OPE BIT(1)
+#define EDPP BIT(0)
+
+#define DMA_SAFETY_INT_STATUS 0x00001080
+#define MSUIS BIT(29)
+#define MSCIS BIT(28)
+#define DEUIS BIT(1)
+#define DECIS BIT(0)
+#define DMA_ECC_INT_ENABLE 0x00001084
+#define TCEIE BIT(0)
+#define DMA_ECC_INT_STATUS 0x00001088
+
+/* EQoS version 5.xx VLAN Tag Filter Fail Packets Queuing */
+#define GMAC_RXQ_CTRL4 0x00000094
+#define GMAC_RXQCTRL_VFFQ_MASK GENMASK(19, 17)
+#define GMAC_RXQCTRL_VFFQ_SHIFT 17
+#define GMAC_RXQCTRL_VFFQE BIT(16)
+
+#define GMAC_INT_FPE_EN BIT(17)
+
+int dwmac5_safety_feat_config(void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_feature_cfg *safety_cfg);
+int dwmac5_safety_feat_irq_status(struct net_device *ndev,
+ void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_stats *stats);
+int dwmac5_safety_feat_dump(struct stmmac_safety_stats *stats,
+ int index, unsigned long *count, const char **desc);
+int dwmac5_rxp_config(void __iomem *ioaddr, struct stmmac_tc_entry *entries,
+ unsigned int count);
+int dwmac5_flex_pps_config(void __iomem *ioaddr, int index,
+ struct stmmac_pps_cfg *cfg, bool enable,
+ u32 sub_second_inc, u32 systime_flags);
+
+#endif /* __DWMAC5_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
new file mode 100644
index 000000000..e1c37ac2c
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ DWMAC DMA Header file.
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __DWMAC_DMA_H__
+#define __DWMAC_DMA_H__
+
+/* DMA CRS Control and Status Register Mapping */
+#define DMA_BUS_MODE 0x00001000 /* Bus Mode */
+
+#define DMA_BUS_MODE_SFT_RESET 0x00000001 /* Software Reset */
+
+#define DMA_XMT_POLL_DEMAND 0x00001004 /* Transmit Poll Demand */
+#define DMA_RCV_POLL_DEMAND 0x00001008 /* Received Poll Demand */
+#define DMA_RCV_BASE_ADDR 0x0000100c /* Receive List Base */
+#define DMA_TX_BASE_ADDR 0x00001010 /* Transmit List Base */
+
+#define DMA_STATUS 0x00001014 /* Status Register */
+#define DMA_STATUS_GPI 0x10000000 /* PMT interrupt */
+#define DMA_STATUS_GMI 0x08000000 /* MMC interrupt */
+#define DMA_STATUS_GLI 0x04000000 /* GMAC Line interface int */
+#define DMA_STATUS_TS_MASK GENMASK(22, 20) /* Transmit Process State */
+#define DMA_STATUS_RS_MASK GENMASK(19, 17) /* Receive Process State */
+#define DMA_STATUS_NIS 0x00010000 /* Normal Interrupt Summary */
+#define DMA_STATUS_AIS 0x00008000 /* Abnormal Interrupt Summary */
+#define DMA_STATUS_ERI 0x00004000 /* Early Receive Interrupt */
+#define DMA_STATUS_FBI 0x00002000 /* Fatal Bus Error Interrupt */
+#define DMA_STATUS_ETI 0x00000400 /* Early Transmit Interrupt */
+#define DMA_STATUS_RWT 0x00000200 /* Receive Watchdog Timeout */
+#define DMA_STATUS_RPS 0x00000100 /* Receive Process Stopped */
+#define DMA_STATUS_RU 0x00000080 /* Receive Buffer Unavailable */
+#define DMA_STATUS_RI 0x00000040 /* Receive Interrupt */
+#define DMA_STATUS_UNF 0x00000020 /* Transmit Underflow */
+#define DMA_STATUS_OVF 0x00000010 /* Receive Overflow */
+#define DMA_STATUS_TJT 0x00000008 /* Transmit Jabber Timeout */
+#define DMA_STATUS_TU 0x00000004 /* Transmit Buffer Unavailable */
+#define DMA_STATUS_TPS 0x00000002 /* Transmit Process Stopped */
+#define DMA_STATUS_TI 0x00000001 /* Transmit Interrupt */
+
+#define DMA_STATUS_MSK_COMMON (DMA_STATUS_NIS | \
+ DMA_STATUS_AIS | \
+ DMA_STATUS_FBI)
+
+#define DMA_STATUS_MSK_RX (DMA_STATUS_ERI | \
+ DMA_STATUS_RWT | \
+ DMA_STATUS_RPS | \
+ DMA_STATUS_RU | \
+ DMA_STATUS_RI | \
+ DMA_STATUS_OVF | \
+ DMA_STATUS_MSK_COMMON)
+
+#define DMA_STATUS_MSK_TX (DMA_STATUS_ETI | \
+ DMA_STATUS_UNF | \
+ DMA_STATUS_TJT | \
+ DMA_STATUS_TU | \
+ DMA_STATUS_TPS | \
+ DMA_STATUS_TI | \
+ DMA_STATUS_MSK_COMMON)
+
+#define DMA_CONTROL 0x00001018 /* Ctrl (Operational Mode) */
+
+/* DMA Control register defines */
+#define DMA_CONTROL_FTF 0x00100000 /* Flush transmit FIFO */
+#define DMA_CONTROL_ST 0x00002000 /* Start/Stop Transmission */
+#define DMA_CONTROL_SR 0x00000002 /* Start/Stop Receive */
+
+#define DMA_INTR_ENA 0x0000101c /* Interrupt Enable */
+
+/* DMA Normal interrupt */
+#define DMA_INTR_ENA_NIE 0x00010000 /* Normal Summary */
+#define DMA_INTR_ENA_TIE 0x00000001 /* Transmit Interrupt */
+#define DMA_INTR_ENA_RIE 0x00000040 /* Receive Interrupt */
+
+#define DMA_INTR_NORMAL (DMA_INTR_ENA_NIE | DMA_INTR_ENA_RIE | \
+ DMA_INTR_ENA_TIE)
+
+/* DMA Abnormal interrupt */
+#define DMA_INTR_ENA_AIE 0x00008000 /* Abnormal Summary */
+#define DMA_INTR_ENA_FBE 0x00002000 /* Fatal Bus Error */
+#define DMA_INTR_ENA_UNE 0x00000020 /* Tx Underflow */
+
+#define DMA_INTR_ABNORMAL (DMA_INTR_ENA_AIE | DMA_INTR_ENA_FBE | \
+ DMA_INTR_ENA_UNE)
+
+/* DMA default interrupt mask */
+#define DMA_INTR_DEFAULT_MASK (DMA_INTR_NORMAL | DMA_INTR_ABNORMAL)
+#define DMA_INTR_DEFAULT_RX (DMA_INTR_ENA_RIE)
+#define DMA_INTR_DEFAULT_TX (DMA_INTR_ENA_TIE)
+
+#define DMA_MISSED_FRAME_CTR 0x00001020 /* Missed Frame Counter */
+
+/* Following DMA defines are channels oriented */
+#define DMA_CHAN_BASE_OFFSET 0x100
+
+static inline u32 dma_chan_base_addr(u32 base, u32 chan)
+{
+ return base + chan * DMA_CHAN_BASE_OFFSET;
+}
+
+#define DMA_CHAN_BUS_MODE(chan) dma_chan_base_addr(DMA_BUS_MODE, chan)
+#define DMA_CHAN_XMT_POLL_DEMAND(chan) \
+ dma_chan_base_addr(DMA_XMT_POLL_DEMAND, chan)
+#define DMA_CHAN_RCV_POLL_DEMAND(chan) \
+ dma_chan_base_addr(DMA_RCV_POLL_DEMAND, chan)
+#define DMA_CHAN_RCV_BASE_ADDR(chan) \
+ dma_chan_base_addr(DMA_RCV_BASE_ADDR, chan)
+#define DMA_CHAN_TX_BASE_ADDR(chan) \
+ dma_chan_base_addr(DMA_TX_BASE_ADDR, chan)
+#define DMA_CHAN_STATUS(chan) dma_chan_base_addr(DMA_STATUS, chan)
+#define DMA_CHAN_CONTROL(chan) dma_chan_base_addr(DMA_CONTROL, chan)
+#define DMA_CHAN_INTR_ENA(chan) dma_chan_base_addr(DMA_INTR_ENA, chan)
+#define DMA_CHAN_RX_WATCHDOG(chan) \
+ dma_chan_base_addr(DMA_RX_WATCHDOG, chan)
+
+
+/* Rx watchdog register */
+#define DMA_RX_WATCHDOG 0x00001024
+
+/* AXI Master Bus Mode */
+#define DMA_AXI_BUS_MODE 0x00001028
+
+#define DMA_AXI_EN_LPI BIT(31)
+#define DMA_AXI_LPI_XIT_FRM BIT(30)
+#define DMA_AXI_WR_OSR_LMT GENMASK(23, 20)
+#define DMA_AXI_RD_OSR_LMT GENMASK(19, 16)
+
+#define DMA_AXI_1KBBE BIT(13)
+
+#define DMA_AXI_UNDEF BIT(0)
+
+#define DMA_CUR_TX_BUF_ADDR 0x00001050 /* Current Host Tx Buffer */
+#define DMA_CUR_RX_BUF_ADDR 0x00001054 /* Current Host Rx Buffer */
+#define DMA_HW_FEATURE 0x00001058 /* HW Feature Register */
+
+#define NUM_DWMAC100_DMA_REGS 9
+#define NUM_DWMAC1000_DMA_REGS 23
+#define NUM_DWMAC4_DMA_REGS 27
+
+void dwmac_enable_dma_transmission(void __iomem *ioaddr, u32 chan);
+void dwmac_enable_dma_reception(void __iomem *ioaddr, u32 chan);
+void dwmac_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx);
+void dwmac_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx);
+void dwmac_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+void dwmac_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+void dwmac_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+void dwmac_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan, u32 dir);
+int dwmac_dma_reset(void __iomem *ioaddr);
+
+#endif /* __DWMAC_DMA_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
new file mode 100644
index 000000000..a0383f948
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include "common.h"
+#include "dwmac_dma.h"
+#include "stmmac.h"
+
+#define GMAC_HI_REG_AE 0x80000000
+
+int dwmac_dma_reset(void __iomem *ioaddr)
+{
+ u32 value = readl(ioaddr + DMA_BUS_MODE);
+
+ /* DMA SW reset */
+ value |= DMA_BUS_MODE_SFT_RESET;
+ writel(value, ioaddr + DMA_BUS_MODE);
+
+ return readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+ !(value & DMA_BUS_MODE_SFT_RESET),
+ 10000, 200000);
+}
+
+/* CSR1 enables the transmit DMA to check for new descriptor */
+void dwmac_enable_dma_transmission(void __iomem *ioaddr, u32 chan)
+{
+ writel(1, ioaddr + DMA_CHAN_XMT_POLL_DEMAND(chan));
+}
+
+void dwmac_enable_dma_reception(void __iomem *ioaddr, u32 chan)
+{
+ writel(1, ioaddr + DMA_CHAN_RCV_POLL_DEMAND(chan));
+}
+
+void dwmac_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx)
+{
+ u32 value = readl(ioaddr + DMA_CHAN_INTR_ENA(chan));
+
+ if (rx)
+ value |= DMA_INTR_DEFAULT_RX;
+ if (tx)
+ value |= DMA_INTR_DEFAULT_TX;
+
+ writel(value, ioaddr + DMA_CHAN_INTR_ENA(chan));
+}
+
+void dwmac_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx)
+{
+ u32 value = readl(ioaddr + DMA_CHAN_INTR_ENA(chan));
+
+ if (rx)
+ value &= ~DMA_INTR_DEFAULT_RX;
+ if (tx)
+ value &= ~DMA_INTR_DEFAULT_TX;
+
+ writel(value, ioaddr + DMA_CHAN_INTR_ENA(chan));
+}
+
+void dwmac_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ u32 value = readl(ioaddr + DMA_CHAN_CONTROL(chan));
+ value |= DMA_CONTROL_ST;
+ writel(value, ioaddr + DMA_CHAN_CONTROL(chan));
+}
+
+void dwmac_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan)
+{
+ u32 value = readl(ioaddr + DMA_CHAN_CONTROL(chan));
+ value &= ~DMA_CONTROL_ST;
+ writel(value, ioaddr + DMA_CHAN_CONTROL(chan));
+}
+
+void dwmac_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ u32 value = readl(ioaddr + DMA_CHAN_CONTROL(chan));
+ value |= DMA_CONTROL_SR;
+ writel(value, ioaddr + DMA_CHAN_CONTROL(chan));
+}
+
+void dwmac_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan)
+{
+ u32 value = readl(ioaddr + DMA_CHAN_CONTROL(chan));
+ value &= ~DMA_CONTROL_SR;
+ writel(value, ioaddr + DMA_CHAN_CONTROL(chan));
+}
+
+#ifdef DWMAC_DMA_DEBUG
+static void show_tx_process_state(unsigned int status)
+{
+ switch (FIELD_GET(DMA_STATUS_TS_MASK, status)) {
+ case 0:
+ pr_debug("- TX (Stopped): Reset or Stop command\n");
+ break;
+ case 1:
+ pr_debug("- TX (Running): Fetching the Tx desc\n");
+ break;
+ case 2:
+ pr_debug("- TX (Running): Waiting for end of tx\n");
+ break;
+ case 3:
+ pr_debug("- TX (Running): Reading the data "
+ "and queuing the data into the Tx buf\n");
+ break;
+ case 6:
+ pr_debug("- TX (Suspended): Tx Buff Underflow "
+ "or an unavailable Transmit descriptor\n");
+ break;
+ case 7:
+ pr_debug("- TX (Running): Closing Tx descriptor\n");
+ break;
+ default:
+ break;
+ }
+}
+
+static void show_rx_process_state(unsigned int status)
+{
+ switch (FIELD_GET(DMA_STATUS_RS_MASK, status)) {
+ case 0:
+ pr_debug("- RX (Stopped): Reset or Stop command\n");
+ break;
+ case 1:
+ pr_debug("- RX (Running): Fetching the Rx desc\n");
+ break;
+ case 2:
+ pr_debug("- RX (Running): Checking for end of pkt\n");
+ break;
+ case 3:
+ pr_debug("- RX (Running): Waiting for Rx pkt\n");
+ break;
+ case 4:
+ pr_debug("- RX (Suspended): Unavailable Rx buf\n");
+ break;
+ case 5:
+ pr_debug("- RX (Running): Closing Rx descriptor\n");
+ break;
+ case 6:
+ pr_debug("- RX(Running): Flushing the current frame"
+ " from the Rx buf\n");
+ break;
+ case 7:
+ pr_debug("- RX (Running): Queuing the Rx frame"
+ " from the Rx buf into memory\n");
+ break;
+ default:
+ break;
+ }
+}
+#endif
+
+int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan, u32 dir)
+{
+ struct stmmac_pcpu_stats *stats = this_cpu_ptr(priv->xstats.pcpu_stats);
+ int ret = 0;
+ /* read the status register (CSR5) */
+ u32 intr_status = readl(ioaddr + DMA_CHAN_STATUS(chan));
+
+#ifdef DWMAC_DMA_DEBUG
+ /* Enable it to monitor DMA rx/tx status in case of critical problems */
+ pr_debug("%s: [CSR5: 0x%08x]\n", __func__, intr_status);
+ show_tx_process_state(intr_status);
+ show_rx_process_state(intr_status);
+#endif
+
+ if (dir == DMA_DIR_RX)
+ intr_status &= DMA_STATUS_MSK_RX;
+ else if (dir == DMA_DIR_TX)
+ intr_status &= DMA_STATUS_MSK_TX;
+
+ /* ABNORMAL interrupts */
+ if (unlikely(intr_status & DMA_STATUS_AIS)) {
+ if (unlikely(intr_status & DMA_STATUS_UNF)) {
+ ret = tx_hard_error_bump_tc;
+ x->tx_undeflow_irq++;
+ }
+ if (unlikely(intr_status & DMA_STATUS_TJT))
+ x->tx_jabber_irq++;
+
+ if (unlikely(intr_status & DMA_STATUS_OVF))
+ x->rx_overflow_irq++;
+
+ if (unlikely(intr_status & DMA_STATUS_RU))
+ x->rx_buf_unav_irq++;
+ if (unlikely(intr_status & DMA_STATUS_RPS))
+ x->rx_process_stopped_irq++;
+ if (unlikely(intr_status & DMA_STATUS_RWT))
+ x->rx_watchdog_irq++;
+ if (unlikely(intr_status & DMA_STATUS_ETI))
+ x->tx_early_irq++;
+ if (unlikely(intr_status & DMA_STATUS_TPS)) {
+ x->tx_process_stopped_irq++;
+ ret = tx_hard_error;
+ }
+ if (unlikely(intr_status & DMA_STATUS_FBI)) {
+ x->fatal_bus_error_irq++;
+ ret = tx_hard_error;
+ }
+ }
+ /* TX/RX NORMAL interrupts */
+ if (likely(intr_status & DMA_STATUS_NIS)) {
+ if (likely(intr_status & DMA_STATUS_RI)) {
+ u32 value = readl(ioaddr + DMA_INTR_ENA);
+ /* to schedule NAPI on real RIE event. */
+ if (likely(value & DMA_INTR_ENA_RIE)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->rx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_rx;
+ }
+ }
+ if (likely(intr_status & DMA_STATUS_TI)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->tx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_tx;
+ }
+ if (unlikely(intr_status & DMA_STATUS_ERI))
+ x->rx_early_irq++;
+ }
+ /* Optional hardware blocks, interrupts should be disabled */
+ if (unlikely(intr_status &
+ (DMA_STATUS_GPI | DMA_STATUS_GMI | DMA_STATUS_GLI)))
+ pr_warn("%s: unexpected status %08x\n", __func__, intr_status);
+
+ /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */
+ writel((intr_status & 0x1ffff), ioaddr + DMA_STATUS);
+
+ return ret;
+}
+
+void dwmac_dma_flush_tx_fifo(void __iomem *ioaddr)
+{
+ u32 csr6 = readl(ioaddr + DMA_CONTROL);
+ writel((csr6 | DMA_CONTROL_FTF), ioaddr + DMA_CONTROL);
+
+ do {} while ((readl(ioaddr + DMA_CONTROL) & DMA_CONTROL_FTF));
+}
+
+void stmmac_set_mac_addr(void __iomem *ioaddr, const u8 addr[6],
+ unsigned int high, unsigned int low)
+{
+ u32 data;
+
+ data = (addr[5] << 8) | addr[4];
+ /* For MAC Addr registers we have to set the Address Enable (AE)
+ * bit that has no effect on the High Reg 0 where the bit 31 (MO)
+ * is RO.
+ */
+ writel(data | GMAC_HI_REG_AE, ioaddr + high);
+ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ writel(data, ioaddr + low);
+}
+EXPORT_SYMBOL_GPL(stmmac_set_mac_addr);
+
+/* Enable disable MAC RX/TX */
+void stmmac_set_mac(void __iomem *ioaddr, bool enable)
+{
+ u32 old_val, value;
+
+ old_val = readl(ioaddr + MAC_CTRL_REG);
+ value = old_val;
+
+ if (enable)
+ value |= MAC_ENABLE_RX | MAC_ENABLE_TX;
+ else
+ value &= ~(MAC_ENABLE_TX | MAC_ENABLE_RX);
+
+ if (value != old_val)
+ writel(value, ioaddr + MAC_CTRL_REG);
+}
+
+void stmmac_get_mac_addr(void __iomem *ioaddr, unsigned char *addr,
+ unsigned int high, unsigned int low)
+{
+ unsigned int hi_addr, lo_addr;
+
+ /* Read the MAC address from the hardware */
+ hi_addr = readl(ioaddr + high);
+ lo_addr = readl(ioaddr + low);
+
+ /* Extract the MAC address from the high and low words */
+ addr[0] = lo_addr & 0xff;
+ addr[1] = (lo_addr >> 8) & 0xff;
+ addr[2] = (lo_addr >> 16) & 0xff;
+ addr[3] = (lo_addr >> 24) & 0xff;
+ addr[4] = hi_addr & 0xff;
+ addr[5] = (hi_addr >> 8) & 0xff;
+}
+EXPORT_SYMBOL_GPL(stmmac_get_mac_addr);
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
new file mode 100644
index 000000000..61b6d45a0
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
@@ -0,0 +1,468 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * stmmac XGMAC definitions.
+ */
+
+#ifndef __STMMAC_DWXGMAC2_H__
+#define __STMMAC_DWXGMAC2_H__
+
+#include "common.h"
+
+/* Misc */
+#define XGMAC_JUMBO_LEN 16368
+
+/* MAC Registers */
+#define XGMAC_TX_CONFIG 0x00000000
+#define XGMAC_CONFIG_SS_OFF 29
+#define XGMAC_CONFIG_SS_MASK GENMASK(31, 29)
+#define XGMAC_CONFIG_SS_10000 (0x0 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SS_2500_GMII (0x2 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SS_1000_GMII (0x3 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SS_100_MII (0x4 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SS_5000 (0x5 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SS_2500 (0x6 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SS_10_MII (0x7 << XGMAC_CONFIG_SS_OFF)
+#define XGMAC_CONFIG_SARC GENMASK(22, 20)
+#define XGMAC_CONFIG_JD BIT(16)
+#define XGMAC_CONFIG_TE BIT(0)
+#define XGMAC_CORE_INIT_TX (XGMAC_CONFIG_JD)
+#define XGMAC_RX_CONFIG 0x00000004
+#define XGMAC_CONFIG_ARPEN BIT(31)
+#define XGMAC_CONFIG_GPSL GENMASK(29, 16)
+#define XGMAC_CONFIG_HDSMS GENMASK(14, 12)
+#define XGMAC_CONFIG_HDSMS_SHIFT 12
+#define XGMAC_CONFIG_HDSMS_256 FIELD_PREP(XGMAC_CONFIG_HDSMS, 0x2)
+#define XGMAC_CONFIG_S2KP BIT(11)
+#define XGMAC_CONFIG_LM BIT(10)
+#define XGMAC_CONFIG_IPC BIT(9)
+#define XGMAC_CONFIG_JE BIT(8)
+#define XGMAC_CONFIG_WD BIT(7)
+#define XGMAC_CONFIG_GPSLCE BIT(6)
+#define XGMAC_CONFIG_CST BIT(2)
+#define XGMAC_CONFIG_ACS BIT(1)
+#define XGMAC_CONFIG_RE BIT(0)
+#define XGMAC_CORE_INIT_RX (XGMAC_CONFIG_GPSLCE | \
+ XGMAC_CONFIG_WD | \
+ FIELD_PREP(XGMAC_CONFIG_GPSL, \
+ XGMAC_JUMBO_LEN))
+#define XGMAC_PACKET_FILTER 0x00000008
+#define XGMAC_FILTER_RA BIT(31)
+#define XGMAC_FILTER_IPFE BIT(20)
+#define XGMAC_FILTER_VTFE BIT(16)
+#define XGMAC_FILTER_HPF BIT(10)
+#define XGMAC_FILTER_PCF BIT(7)
+#define XGMAC_FILTER_PM BIT(4)
+#define XGMAC_FILTER_HMC BIT(2)
+#define XGMAC_FILTER_PR BIT(0)
+#define XGMAC_HASH_TABLE(x) (0x00000010 + (x) * 4)
+#define XGMAC_MAX_HASH_TABLE 8
+#define XGMAC_RXQ_CTRL0 0x000000a0
+#define XGMAC_RXQEN(x) GENMASK((x) * 2 + 1, (x) * 2)
+#define XGMAC_RXQEN_SHIFT(x) ((x) * 2)
+#define XGMAC_RXQ_CTRL1 0x000000a4
+#define XGMAC_AVCPQ GENMASK(31, 28)
+#define XGMAC_AVCPQ_SHIFT 28
+#define XGMAC_PTPQ GENMASK(27, 24)
+#define XGMAC_PTPQ_SHIFT 24
+#define XGMAC_TACPQE BIT(23)
+#define XGMAC_DCBCPQ GENMASK(19, 16)
+#define XGMAC_DCBCPQ_SHIFT 16
+#define XGMAC_MCBCQEN BIT(15)
+#define XGMAC_MCBCQ GENMASK(11, 8)
+#define XGMAC_MCBCQ_SHIFT 8
+#define XGMAC_FPRQ GENMASK(7, 4)
+#define XGMAC_UPQ GENMASK(3, 0)
+#define XGMAC_UPQ_SHIFT 0
+#define XGMAC_RXQ_CTRL2 0x000000a8
+#define XGMAC_RXQ_CTRL3 0x000000ac
+#define XGMAC_PSRQ(x) GENMASK((x) * 8 + 7, (x) * 8)
+#define XGMAC_PSRQ_SHIFT(x) ((x) * 8)
+#define XGMAC_INT_STATUS 0x000000b0
+#define XGMAC_INT_TSIS BIT(12)
+#define XGMAC_LPIIS BIT(5)
+#define XGMAC_PMTIS BIT(4)
+#define XGMAC_INT_EN 0x000000b4
+#define XGMAC_FPEIE BIT(15)
+#define XGMAC_TSIE BIT(12)
+#define XGMAC_LPIIE BIT(5)
+#define XGMAC_PMTIE BIT(4)
+#define XGMAC_INT_DEFAULT_EN (XGMAC_LPIIE | XGMAC_PMTIE)
+#define XGMAC_Qx_TX_FLOW_CTRL(x) (0x00000070 + (x) * 4)
+#define XGMAC_PT GENMASK(31, 16)
+#define XGMAC_TFE BIT(1)
+#define XGMAC_RX_FLOW_CTRL 0x00000090
+#define XGMAC_RFE BIT(0)
+#define XGMAC_PMT 0x000000c0
+#define XGMAC_GLBLUCAST BIT(9)
+#define XGMAC_RWKPKTEN BIT(2)
+#define XGMAC_MGKPKTEN BIT(1)
+#define XGMAC_PWRDWN BIT(0)
+#define XGMAC_LPI_CTRL 0x000000d0
+/* For definitions, see LPI_CTRL_STATUS_xxx in common.h */
+#define XGMAC_LPI_TIMER_CTRL 0x000000d4
+#define XGMAC_HW_FEATURE0 0x0000011c
+#define XGMAC_HWFEAT_EDMA BIT(31)
+#define XGMAC_HWFEAT_EDIFFC BIT(30)
+#define XGMAC_HWFEAT_VXN BIT(29)
+#define XGMAC_HWFEAT_SAVLANINS BIT(27)
+#define XGMAC_HWFEAT_TSSTSSEL GENMASK(26, 25)
+#define XGMAC_HWFEAT_PHYSEL GENMASK(24, 23)
+#define XGMAC_HWFEAT_ADDMACADRSEL GENMASK(22, 18)
+#define XGMAC_HWFEAT_RXCOESEL BIT(16)
+#define XGMAC_HWFEAT_TXCOESEL BIT(14)
+#define XGMAC_HWFEAT_EEESEL BIT(13)
+#define XGMAC_HWFEAT_TSSEL BIT(12)
+#define XGMAC_HWFEAT_AVSEL BIT(11)
+#define XGMAC_HWFEAT_RAVSEL BIT(10)
+#define XGMAC_HWFEAT_ARPOFFSEL BIT(9)
+#define XGMAC_HWFEAT_MMCSEL BIT(8)
+#define XGMAC_HWFEAT_MGKSEL BIT(7)
+#define XGMAC_HWFEAT_RWKSEL BIT(6)
+#define XGMAC_HWFEAT_SMASEL BIT(5)
+#define XGMAC_HWFEAT_VLHASH BIT(4)
+#define XGMAC_HWFEAT_HDSEL BIT(3)
+#define XGMAC_HWFEAT_GMIISEL BIT(1)
+#define XGMAC_HW_FEATURE1 0x00000120
+#define XGMAC_HWFEAT_L3L4FNUM GENMASK(30, 27)
+#define XGMAC_HWFEAT_HASHTBLSZ GENMASK(25, 24)
+#define XGMAC_HWFEAT_NUMTC GENMASK(23, 21)
+#define XGMAC_HWFEAT_RSSEN BIT(20)
+#define XGMAC_HWFEAT_DBGMEMA BIT(19)
+#define XGMAC_HWFEAT_TSOEN BIT(18)
+#define XGMAC_HWFEAT_SPHEN BIT(17)
+#define XGMAC_HWFEAT_DCBEN BIT(16)
+#define XGMAC_HWFEAT_ADDR64 GENMASK(15, 14)
+#define XGMAC_HWFEAT_ADVTHWORD BIT(13)
+#define XGMAC_HWFEAT_PTOEN BIT(12)
+#define XGMAC_HWFEAT_OSTEN BIT(11)
+#define XGMAC_HWFEAT_TXFIFOSIZE GENMASK(10, 6)
+#define XGMAC_HWFEAT_PFCEN BIT(5)
+#define XGMAC_HWFEAT_RXFIFOSIZE GENMASK(4, 0)
+#define XGMAC_HW_FEATURE2 0x00000124
+#define XGMAC_HWFEAT_AUXSNAPNUM GENMASK(30, 28)
+#define XGMAC_HWFEAT_PPSOUTNUM GENMASK(26, 24)
+#define XGMAC_HWFEAT_TXCHCNT GENMASK(21, 18)
+#define XGMAC_HWFEAT_RXCHCNT GENMASK(15, 12)
+#define XGMAC_HWFEAT_TXQCNT GENMASK(9, 6)
+#define XGMAC_HWFEAT_RXQCNT GENMASK(3, 0)
+#define XGMAC_HW_FEATURE3 0x00000128
+#define XGMAC_HWFEAT_TBSCH GENMASK(31, 28)
+#define XGMAC_HWFEAT_TBSSEL BIT(27)
+#define XGMAC_HWFEAT_FPESEL BIT(26)
+#define XGMAC_HWFEAT_SGFSEL BIT(25)
+#define XGMAC_HWFEAT_ESTWID GENMASK(24, 23)
+#define XGMAC_HWFEAT_ESTDEP GENMASK(22, 20)
+#define XGMAC_HWFEAT_ESTSEL BIT(19)
+#define XGMAC_HWFEAT_TTSFD GENMASK(18, 16)
+#define XGMAC_HWFEAT_ASP GENMASK(15, 14)
+#define XGMAC_HWFEAT_DVLAN BIT(13)
+#define XGMAC_HWFEAT_FRPES GENMASK(12, 11)
+#define XGMAC_HWFEAT_FRPPB GENMASK(10, 9)
+#define XGMAC_HWFEAT_POUOST BIT(8)
+#define XGMAC_HWFEAT_FRPPIPE GENMASK(7, 5)
+#define XGMAC_HWFEAT_CBTISEL BIT(4)
+#define XGMAC_HWFEAT_FRPSEL BIT(3)
+#define XGMAC_HWFEAT_NRVF GENMASK(2, 0)
+#define XGMAC_HW_FEATURE4 0x0000012c
+#define XGMAC_HWFEAT_EASP BIT(4)
+#define XGMAC_HWFEAT_PCSEL GENMASK(1, 0)
+#define XGMAC_MAC_DPP_FSM_INT_STATUS 0x00000150
+#define XGMAC_MAC_FSM_CONTROL 0x00000158
+#define XGMAC_PRTYEN BIT(1)
+#define XGMAC_TMOUTEN BIT(0)
+#define XGMAC_MDIO_ADDR 0x00000200
+#define XGMAC_MDIO_DATA 0x00000204
+#define XGMAC_MDIO_C22P 0x00000220
+#define XGMAC_GPIO_STATUS 0x0000027c
+#define XGMAC_GPIO_GPO0 BIT(16)
+#define XGMAC_ADDRx_HIGH(x) (0x00000300 + (x) * 0x8)
+#define XGMAC_ADDR_MAX 32
+#define XGMAC_AE BIT(31)
+#define XGMAC_DCS GENMASK(19, 16)
+#define XGMAC_ADDRx_LOW(x) (0x00000304 + (x) * 0x8)
+#define XGMAC_L3L4_ADDR_CTRL 0x00000c00
+#define XGMAC_IDDR GENMASK(16, 8)
+#define XGMAC_IDDR_FNUM_MASK GENMASK(7, 4) /* FNUM within IDDR */
+#define XGMAC_IDDR_REG_MASK GENMASK(3, 0) /* REG within IDDR */
+#define XGMAC_TT BIT(1)
+#define XGMAC_XB BIT(0)
+#define XGMAC_L3L4_DATA 0x00000c04
+#define XGMAC_L3L4_CTRL 0x0
+#define XGMAC_L4DPIM0 BIT(21)
+#define XGMAC_L4DPM0 BIT(20)
+#define XGMAC_L4SPIM0 BIT(19)
+#define XGMAC_L4SPM0 BIT(18)
+#define XGMAC_L4PEN0 BIT(16)
+#define XGMAC_L3HDBM0 GENMASK(15, 11)
+#define XGMAC_L3HSBM0 GENMASK(10, 6)
+#define XGMAC_L3DAIM0 BIT(5)
+#define XGMAC_L3DAM0 BIT(4)
+#define XGMAC_L3SAIM0 BIT(3)
+#define XGMAC_L3SAM0 BIT(2)
+#define XGMAC_L3PEN0 BIT(0)
+#define XGMAC_L4_ADDR 0x1
+#define XGMAC_L4DP0 GENMASK(31, 16)
+#define XGMAC_L4SP0 GENMASK(15, 0)
+#define XGMAC_L3_ADDR0 0x4
+#define XGMAC_L3_ADDR1 0x5
+#define XGMAC_L3_ADDR2 0x6
+#define XMGAC_L3_ADDR3 0x7
+#define XGMAC_ARP_ADDR 0x00000c10
+#define XGMAC_RSS_CTRL 0x00000c80
+#define XGMAC_UDP4TE BIT(3)
+#define XGMAC_TCP4TE BIT(2)
+#define XGMAC_IP2TE BIT(1)
+#define XGMAC_RSSE BIT(0)
+#define XGMAC_RSS_ADDR 0x00000c88
+#define XGMAC_RSSIA_SHIFT 8
+#define XGMAC_ADDRT BIT(2)
+#define XGMAC_CT BIT(1)
+#define XGMAC_OB BIT(0)
+#define XGMAC_RSS_DATA 0x00000c8c
+#define XGMAC_TIMESTAMP_STATUS 0x00000d20
+#define XGMAC_TIMESTAMP_ATSNS_MASK GENMASK(29, 25)
+#define XGMAC_TXTSC BIT(15)
+#define XGMAC_TXTIMESTAMP_NSEC 0x00000d30
+#define XGMAC_TXTSSTSLO GENMASK(30, 0)
+#define XGMAC_TXTIMESTAMP_SEC 0x00000d34
+#define XGMAC_PPS_CONTROL 0x00000d70
+#define XGMAC_PPS_MAXIDX(x) ((((x) + 1) * 8) - 1)
+#define XGMAC_PPS_MINIDX(x) ((x) * 8)
+#define XGMAC_PPSx_MASK(x) \
+ GENMASK(XGMAC_PPS_MAXIDX(x), XGMAC_PPS_MINIDX(x))
+#define XGMAC_TRGTMODSELx(x, val) \
+ GENMASK(XGMAC_PPS_MAXIDX(x) - 1, XGMAC_PPS_MAXIDX(x) - 2) & \
+ ((val) << (XGMAC_PPS_MAXIDX(x) - 2))
+#define XGMAC_PPSCMDx(x, val) \
+ GENMASK(XGMAC_PPS_MINIDX(x) + 3, XGMAC_PPS_MINIDX(x)) & \
+ ((val) << XGMAC_PPS_MINIDX(x))
+#define XGMAC_PPSCMD_START 0x2
+#define XGMAC_PPSCMD_STOP 0x5
+#define XGMAC_PPSENx(x) BIT(4 + (x) * 8)
+#define XGMAC_PPSx_TARGET_TIME_SEC(x) (0x00000d80 + (x) * 0x10)
+#define XGMAC_PPSx_TARGET_TIME_NSEC(x) (0x00000d84 + (x) * 0x10)
+#define XGMAC_TRGTBUSY0 BIT(31)
+#define XGMAC_PPSx_INTERVAL(x) (0x00000d88 + (x) * 0x10)
+#define XGMAC_PPSx_WIDTH(x) (0x00000d8c + (x) * 0x10)
+
+/* MTL Registers */
+#define XGMAC_MTL_OPMODE 0x00001000
+#define XGMAC_FRPE BIT(15)
+#define XGMAC_ETSALG GENMASK(6, 5)
+#define XGMAC_WRR (0x0 << 5)
+#define XGMAC_WFQ (0x1 << 5)
+#define XGMAC_DWRR (0x2 << 5)
+#define XGMAC_RAA BIT(2)
+#define XGMAC_MTL_INT_STATUS 0x00001020
+#define XGMAC_MTL_RXQ_DMA_MAP0 0x00001030
+#define XGMAC_MTL_RXQ_DMA_MAP1 0x00001034
+#define XGMAC_QxMDMACH(x) GENMASK((x) * 8 + 7, (x) * 8)
+#define XGMAC_QxMDMACH_SHIFT(x) ((x) * 8)
+#define XGMAC_QDDMACH BIT(7)
+#define XGMAC_TC_PRTY_MAP0 0x00001040
+#define XGMAC_TC_PRTY_MAP1 0x00001044
+#define XGMAC_PSTC(x) GENMASK((x) * 8 + 7, (x) * 8)
+#define XGMAC_PSTC_SHIFT(x) ((x) * 8)
+#define XGMAC_MTL_RXP_CONTROL_STATUS 0x000010a0
+#define XGMAC_RXPI BIT(31)
+#define XGMAC_NPE GENMASK(23, 16)
+#define XGMAC_NVE GENMASK(7, 0)
+#define XGMAC_MTL_RXP_IACC_CTRL_ST 0x000010b0
+#define XGMAC_STARTBUSY BIT(31)
+#define XGMAC_WRRDN BIT(16)
+#define XGMAC_ADDR GENMASK(9, 0)
+#define XGMAC_MTL_RXP_IACC_DATA 0x000010b4
+#define XGMAC_MTL_ECC_CONTROL 0x000010c0
+#define XGMAC_MTL_SAFETY_INT_STATUS 0x000010c4
+#define XGMAC_MEUIS BIT(1)
+#define XGMAC_MECIS BIT(0)
+#define XGMAC_MTL_ECC_INT_ENABLE 0x000010c8
+#define XGMAC_RPCEIE BIT(12)
+#define XGMAC_ECEIE BIT(8)
+#define XGMAC_RXCEIE BIT(4)
+#define XGMAC_TXCEIE BIT(0)
+#define XGMAC_MTL_ECC_INT_STATUS 0x000010cc
+#define XGMAC_MTL_DPP_CONTROL 0x000010e0
+#define XGMAC_DPP_DISABLE BIT(0)
+#define XGMAC_MTL_TXQ_OPMODE(x) (0x00001100 + (0x80 * (x)))
+#define XGMAC_TQS GENMASK(25, 16)
+#define XGMAC_Q2TCMAP GENMASK(10, 8)
+#define XGMAC_TTC GENMASK(6, 4)
+#define XGMAC_TXQEN GENMASK(3, 2)
+#define XGMAC_TSF BIT(1)
+#define XGMAC_MTL_TCx_ETS_CONTROL(x) (0x00001110 + (0x80 * (x)))
+#define XGMAC_MTL_TCx_QUANTUM_WEIGHT(x) (0x00001118 + (0x80 * (x)))
+#define XGMAC_MTL_TCx_SENDSLOPE(x) (0x0000111c + (0x80 * (x)))
+#define XGMAC_MTL_TCx_HICREDIT(x) (0x00001120 + (0x80 * (x)))
+#define XGMAC_MTL_TCx_LOCREDIT(x) (0x00001124 + (0x80 * (x)))
+#define XGMAC_CC BIT(3)
+#define XGMAC_TSA GENMASK(1, 0)
+#define XGMAC_SP (0x0 << 0)
+#define XGMAC_CBS (0x1 << 0)
+#define XGMAC_ETS (0x2 << 0)
+#define XGMAC_MTL_RXQ_OPMODE(x) (0x00001140 + (0x80 * (x)))
+#define XGMAC_RQS GENMASK(25, 16)
+#define XGMAC_EHFC BIT(7)
+#define XGMAC_RSF BIT(5)
+#define XGMAC_RTC GENMASK(1, 0)
+#define XGMAC_MTL_RXQ_FLOW_CONTROL(x) (0x00001150 + (0x80 * (x)))
+#define XGMAC_RFD GENMASK(31, 17)
+#define XGMAC_RFA GENMASK(15, 1)
+#define XGMAC_MTL_QINTEN(x) (0x00001170 + (0x80 * (x)))
+#define XGMAC_RXOIE BIT(16)
+#define XGMAC_MTL_QINT_STATUS(x) (0x00001174 + (0x80 * (x)))
+#define XGMAC_RXOVFIS BIT(16)
+#define XGMAC_ABPSIS BIT(1)
+#define XGMAC_TXUNFIS BIT(0)
+#define XGMAC_MAC_REGSIZE (XGMAC_MTL_QINT_STATUS(15) / 4)
+
+/* DMA Registers */
+#define XGMAC_DMA_MODE 0x00003000
+#define XGMAC_SWR BIT(0)
+#define XGMAC_DMA_SYSBUS_MODE 0x00003004
+#define XGMAC_WR_OSR_LMT GENMASK(29, 24)
+#define XGMAC_RD_OSR_LMT GENMASK(21, 16)
+#define XGMAC_EN_LPI BIT(15)
+#define XGMAC_LPI_XIT_PKT BIT(14)
+#define XGMAC_AAL DMA_AXI_AAL
+#define XGMAC_EAME BIT(11)
+/* XGMAC_BLEN* are now defined as DMA_AXI_BLEN* in common.h */
+#define XGMAC_UNDEF BIT(0)
+#define XGMAC_TX_EDMA_CTRL 0x00003040
+#define XGMAC_TDPS GENMASK(29, 0)
+#define XGMAC_RX_EDMA_CTRL 0x00003044
+#define XGMAC_RDPS GENMASK(29, 0)
+#define XGMAC_DMA_TBS_CTRL0 0x00003054
+#define XGMAC_DMA_TBS_CTRL1 0x00003058
+#define XGMAC_DMA_TBS_CTRL2 0x0000305c
+#define XGMAC_DMA_TBS_CTRL3 0x00003060
+#define XGMAC_FTOS GENMASK(31, 8)
+#define XGMAC_FTOV BIT(0)
+#define XGMAC_DEF_FTOS (XGMAC_FTOS | XGMAC_FTOV)
+#define XGMAC_DMA_SAFETY_INT_STATUS 0x00003064
+#define XGMAC_MCSIS BIT(31)
+#define XGMAC_MSUIS BIT(29)
+#define XGMAC_MSCIS BIT(28)
+#define XGMAC_DEUIS BIT(1)
+#define XGMAC_DECIS BIT(0)
+#define XGMAC_DMA_ECC_INT_ENABLE 0x00003068
+#define XGMAC_DCEIE BIT(1)
+#define XGMAC_TCEIE BIT(0)
+#define XGMAC_DMA_ECC_INT_STATUS 0x0000306c
+#define XGMAC_DMA_DPP_INT_STATUS 0x00003074
+#define XGMAC_DMA_CH_CONTROL(x) (0x00003100 + (0x80 * (x)))
+#define XGMAC_SPH BIT(24)
+#define XGMAC_PBLx8 BIT(16)
+#define XGMAC_DMA_CH_TX_CONTROL(x) (0x00003104 + (0x80 * (x)))
+#define XGMAC_EDSE BIT(28)
+#define XGMAC_TxPBL GENMASK(21, 16)
+#define XGMAC_TSE BIT(12)
+#define XGMAC_OSP BIT(4)
+#define XGMAC_TXST BIT(0)
+#define XGMAC_DMA_CH_RX_CONTROL(x) (0x00003108 + (0x80 * (x)))
+#define XGMAC_RxPBL GENMASK(21, 16)
+#define XGMAC_RBSZ GENMASK(14, 1)
+#define XGMAC_RXST BIT(0)
+#define XGMAC_DMA_CH_TxDESC_HADDR(x) (0x00003110 + (0x80 * (x)))
+#define XGMAC_DMA_CH_TxDESC_LADDR(x) (0x00003114 + (0x80 * (x)))
+#define XGMAC_DMA_CH_RxDESC_HADDR(x) (0x00003118 + (0x80 * (x)))
+#define XGMAC_DMA_CH_RxDESC_LADDR(x) (0x0000311c + (0x80 * (x)))
+#define XGMAC_DMA_CH_TxDESC_TAIL_LPTR(x) (0x00003124 + (0x80 * (x)))
+#define XGMAC_DMA_CH_RxDESC_TAIL_LPTR(x) (0x0000312c + (0x80 * (x)))
+#define XGMAC_DMA_CH_TxDESC_RING_LEN(x) (0x00003130 + (0x80 * (x)))
+#define XGMAC_DMA_CH_RxDESC_RING_LEN(x) (0x00003134 + (0x80 * (x)))
+#define XGMAC_DMA_CH_INT_EN(x) (0x00003138 + (0x80 * (x)))
+#define XGMAC_NIE BIT(15)
+#define XGMAC_AIE BIT(14)
+#define XGMAC_RBUE BIT(7)
+#define XGMAC_RIE BIT(6)
+#define XGMAC_TBUE BIT(2)
+#define XGMAC_TIE BIT(0)
+#define XGMAC_DMA_INT_DEFAULT_EN (XGMAC_NIE | XGMAC_AIE | \
+ XGMAC_RIE | XGMAC_TIE)
+#define XGMAC_DMA_INT_DEFAULT_RX (XGMAC_RIE)
+#define XGMAC_DMA_INT_DEFAULT_TX (XGMAC_TIE)
+#define XGMAC_DMA_CH_Rx_WATCHDOG(x) (0x0000313c + (0x80 * (x)))
+#define XGMAC_RWT GENMASK(7, 0)
+#define XGMAC_DMA_CH_STATUS(x) (0x00003160 + (0x80 * (x)))
+#define XGMAC_NIS BIT(15)
+#define XGMAC_AIS BIT(14)
+#define XGMAC_FBE BIT(12)
+#define XGMAC_RBU BIT(7)
+#define XGMAC_RI BIT(6)
+#define XGMAC_TBU BIT(2)
+#define XGMAC_TPS BIT(1)
+#define XGMAC_TI BIT(0)
+#define XGMAC_REGSIZE ((0x0000317c + (0x80 * 15)) / 4)
+
+#define XGMAC_DMA_STATUS_MSK_COMMON (XGMAC_NIS | XGMAC_AIS | XGMAC_FBE)
+#define XGMAC_DMA_STATUS_MSK_RX (XGMAC_RBU | XGMAC_RI | \
+ XGMAC_DMA_STATUS_MSK_COMMON)
+#define XGMAC_DMA_STATUS_MSK_TX (XGMAC_TBU | XGMAC_TPS | XGMAC_TI | \
+ XGMAC_DMA_STATUS_MSK_COMMON)
+
+/* Descriptors */
+#define XGMAC_TDES0_LTV BIT(31)
+#define XGMAC_TDES0_LT GENMASK(7, 0)
+#define XGMAC_TDES1_LT GENMASK(31, 8)
+#define XGMAC_TDES2_IVT GENMASK(31, 16)
+#define XGMAC_TDES2_IOC BIT(31)
+#define XGMAC_TDES2_TTSE BIT(30)
+#define XGMAC_TDES2_B2L GENMASK(29, 16)
+#define XGMAC_TDES2_VTIR GENMASK(15, 14)
+#define XGMAC_TDES2_B1L GENMASK(13, 0)
+#define XGMAC_TDES3_OWN BIT(31)
+#define XGMAC_TDES3_CTXT BIT(30)
+#define XGMAC_TDES3_FD BIT(29)
+#define XGMAC_TDES3_LD BIT(28)
+#define XGMAC_TDES3_CPC GENMASK(27, 26)
+#define XGMAC_TDES3_TCMSSV BIT(26)
+#define XGMAC_TDES3_SAIC GENMASK(25, 23)
+#define XGMAC_TDES3_TBSV BIT(24)
+#define XGMAC_TDES3_THL GENMASK(22, 19)
+#define XGMAC_TDES3_IVTIR GENMASK(19, 18)
+#define XGMAC_TDES3_TSE BIT(18)
+#define XGMAC_TDES3_IVLTV BIT(17)
+#define XGMAC_TDES3_CIC GENMASK(17, 16)
+#define XGMAC_TDES3_TPL GENMASK(17, 0)
+#define XGMAC_TDES3_VLTV BIT(16)
+#define XGMAC_TDES3_VT GENMASK(15, 0)
+#define XGMAC_TDES3_FL GENMASK(14, 0)
+#define XGMAC_RDES2_L4FM BIT(28)
+#define XGMAC_RDES2_L3FM BIT(27)
+#define XGMAC_RDES2_HL GENMASK(9, 0)
+#define XGMAC_RDES3_OWN BIT(31)
+#define XGMAC_RDES3_CTXT BIT(30)
+#define XGMAC_RDES3_IOC BIT(30)
+#define XGMAC_RDES3_LD BIT(28)
+#define XGMAC_RDES3_CDA BIT(27)
+#define XGMAC_RDES3_RSV BIT(26)
+#define XGMAC_RDES3_L34T GENMASK(23, 20)
+#define XGMAC_RDES3_ET_LT GENMASK(19, 16)
+#define XGMAC_L34T_IP4TCP 0x1
+#define XGMAC_L34T_IP4UDP 0x2
+#define XGMAC_L34T_IP6TCP 0x9
+#define XGMAC_L34T_IP6UDP 0xA
+#define XGMAC_RDES3_ES BIT(15)
+#define XGMAC_RDES3_PL GENMASK(13, 0)
+#define XGMAC_RDES3_TSD BIT(6)
+#define XGMAC_RDES3_TSA BIT(4)
+
+/* RDES0 (write back format) */
+#define XGMAC_RDES0_VLAN_TAG_MASK GENMASK(15, 0)
+
+/* Error Type or L2 Type(ET/LT) Field Number */
+#define XGMAC_ET_LT_VLAN_STAG 8
+#define XGMAC_ET_LT_VLAN_CTAG 9
+#define XGMAC_ET_LT_DVLAN_CTAG_CTAG 10
+#define XGMAC_ET_LT_DVLAN_STAG_STAG 11
+#define XGMAC_ET_LT_DVLAN_CTAG_STAG 12
+#define XGMAC_ET_LT_DVLAN_STAG_CTAG 13
+
+extern const struct stmmac_ops dwxgmac210_ops;
+extern const struct stmmac_ops dwxlgmac2_ops;
+extern const struct stmmac_dma_ops dwxgmac210_dma_ops;
+extern const struct stmmac_desc_ops dwxgmac210_desc_ops;
+
+#endif /* __STMMAC_DWXGMAC2_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
new file mode 100644
index 000000000..f02b434bb
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
@@ -0,0 +1,1597 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * stmmac XGMAC support.
+ */
+
+#include <linux/bitrev.h>
+#include <linux/crc32.h>
+#include <linux/iopoll.h>
+#include "stmmac.h"
+#include "stmmac_fpe.h"
+#include "stmmac_ptp.h"
+#include "stmmac_vlan.h"
+#include "dwxlgmac2.h"
+#include "dwxgmac2.h"
+
+static void dwxgmac2_core_init(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 tx, rx;
+
+ tx = readl(ioaddr + XGMAC_TX_CONFIG);
+ rx = readl(ioaddr + XGMAC_RX_CONFIG);
+
+ writel(tx | XGMAC_CORE_INIT_TX, ioaddr + XGMAC_TX_CONFIG);
+ writel(rx | XGMAC_CORE_INIT_RX, ioaddr + XGMAC_RX_CONFIG);
+ writel(XGMAC_INT_DEFAULT_EN, ioaddr + XGMAC_INT_EN);
+}
+
+static void dwxgmac2_irq_modify(struct mac_device_info *hw, u32 disable,
+ u32 enable)
+{
+ void __iomem *int_mask = hw->pcsr + XGMAC_INT_EN;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&hw->irq_ctrl_lock, flags);
+ value = readl(int_mask) & ~disable;
+ value |= enable;
+ writel(value, int_mask);
+ spin_unlock_irqrestore(&hw->irq_ctrl_lock, flags);
+}
+
+static void dwxgmac2_update_caps(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.mbps_10_100)
+ priv->hw->link.caps &= ~(MAC_10 | MAC_100);
+}
+
+static void dwxgmac2_set_mac(void __iomem *ioaddr, bool enable)
+{
+ u32 tx = readl(ioaddr + XGMAC_TX_CONFIG);
+ u32 rx = readl(ioaddr + XGMAC_RX_CONFIG);
+
+ if (enable) {
+ tx |= XGMAC_CONFIG_TE;
+ rx |= XGMAC_CONFIG_RE;
+ } else {
+ tx &= ~XGMAC_CONFIG_TE;
+ rx &= ~XGMAC_CONFIG_RE;
+ }
+
+ writel(tx, ioaddr + XGMAC_TX_CONFIG);
+ writel(rx, ioaddr + XGMAC_RX_CONFIG);
+}
+
+static int dwxgmac2_rx_ipc(struct mac_device_info *hw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_RX_CONFIG);
+ if (hw->rx_csum)
+ value |= XGMAC_CONFIG_IPC;
+ else
+ value &= ~XGMAC_CONFIG_IPC;
+ writel(value, ioaddr + XGMAC_RX_CONFIG);
+
+ return !!(readl(ioaddr + XGMAC_RX_CONFIG) & XGMAC_CONFIG_IPC);
+}
+
+static void dwxgmac2_rx_queue_enable(struct mac_device_info *hw, u8 mode,
+ u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_RXQ_CTRL0) & ~XGMAC_RXQEN(queue);
+ if (mode == MTL_QUEUE_AVB)
+ value |= 0x1 << XGMAC_RXQEN_SHIFT(queue);
+ else if (mode == MTL_QUEUE_DCB)
+ value |= 0x2 << XGMAC_RXQEN_SHIFT(queue);
+ writel(value, ioaddr + XGMAC_RXQ_CTRL0);
+}
+
+static void dwxgmac2_rx_queue_prio(struct mac_device_info *hw, u32 prio,
+ u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 clear_mask = 0;
+ u32 ctrl2, ctrl3;
+ int i;
+
+ ctrl2 = readl(ioaddr + XGMAC_RXQ_CTRL2);
+ ctrl3 = readl(ioaddr + XGMAC_RXQ_CTRL3);
+
+ /* The software must ensure that the same priority
+ * is not mapped to multiple Rx queues
+ */
+ for (i = 0; i < 4; i++)
+ clear_mask |= ((prio << XGMAC_PSRQ_SHIFT(i)) &
+ XGMAC_PSRQ(i));
+
+ ctrl2 &= ~clear_mask;
+ ctrl3 &= ~clear_mask;
+
+ /* First assign new priorities to a queue, then
+ * clear them from others queues
+ */
+ if (queue < 4) {
+ ctrl2 |= (prio << XGMAC_PSRQ_SHIFT(queue)) &
+ XGMAC_PSRQ(queue);
+
+ writel(ctrl2, ioaddr + XGMAC_RXQ_CTRL2);
+ writel(ctrl3, ioaddr + XGMAC_RXQ_CTRL3);
+ } else {
+ queue -= 4;
+
+ ctrl3 |= (prio << XGMAC_PSRQ_SHIFT(queue)) &
+ XGMAC_PSRQ(queue);
+
+ writel(ctrl3, ioaddr + XGMAC_RXQ_CTRL3);
+ writel(ctrl2, ioaddr + XGMAC_RXQ_CTRL2);
+ }
+}
+
+static void dwxgmac2_tx_queue_prio(struct mac_device_info *hw, u32 prio,
+ u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value, reg;
+
+ reg = (queue < 4) ? XGMAC_TC_PRTY_MAP0 : XGMAC_TC_PRTY_MAP1;
+ if (queue >= 4)
+ queue -= 4;
+
+ value = readl(ioaddr + reg);
+ value &= ~XGMAC_PSTC(queue);
+ value |= (prio << XGMAC_PSTC_SHIFT(queue)) & XGMAC_PSTC(queue);
+
+ writel(value, ioaddr + reg);
+}
+
+static void dwxgmac2_rx_queue_routing(struct mac_device_info *hw,
+ u8 packet, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ static const struct stmmac_rx_routing dwxgmac2_route_possibilities[] = {
+ { XGMAC_AVCPQ, XGMAC_AVCPQ_SHIFT },
+ { XGMAC_PTPQ, XGMAC_PTPQ_SHIFT },
+ { XGMAC_DCBCPQ, XGMAC_DCBCPQ_SHIFT },
+ { XGMAC_UPQ, XGMAC_UPQ_SHIFT },
+ { XGMAC_MCBCQ, XGMAC_MCBCQ_SHIFT },
+ };
+
+ value = readl(ioaddr + XGMAC_RXQ_CTRL1);
+
+ /* routing configuration */
+ value &= ~dwxgmac2_route_possibilities[packet - 1].reg_mask;
+ value |= (queue << dwxgmac2_route_possibilities[packet - 1].reg_shift) &
+ dwxgmac2_route_possibilities[packet - 1].reg_mask;
+
+ /* some packets require extra ops */
+ if (packet == PACKET_AVCPQ)
+ value |= FIELD_PREP(XGMAC_TACPQE, 1);
+ else if (packet == PACKET_MCBCQ)
+ value |= FIELD_PREP(XGMAC_MCBCQEN, 1);
+
+ writel(value, ioaddr + XGMAC_RXQ_CTRL1);
+}
+
+static void dwxgmac2_prog_mtl_rx_algorithms(struct mac_device_info *hw,
+ u32 rx_alg)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_MTL_OPMODE);
+ value &= ~XGMAC_RAA;
+
+ switch (rx_alg) {
+ case MTL_RX_ALGORITHM_SP:
+ break;
+ case MTL_RX_ALGORITHM_WSP:
+ value |= XGMAC_RAA;
+ break;
+ default:
+ break;
+ }
+
+ writel(value, ioaddr + XGMAC_MTL_OPMODE);
+}
+
+static void dwxgmac2_prog_mtl_tx_algorithms(struct mac_device_info *hw,
+ u32 tx_alg)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ bool ets = true;
+ u32 value;
+ int i;
+
+ value = readl(ioaddr + XGMAC_MTL_OPMODE);
+ value &= ~XGMAC_ETSALG;
+
+ switch (tx_alg) {
+ case MTL_TX_ALGORITHM_WRR:
+ value |= XGMAC_WRR;
+ break;
+ case MTL_TX_ALGORITHM_WFQ:
+ value |= XGMAC_WFQ;
+ break;
+ case MTL_TX_ALGORITHM_DWRR:
+ value |= XGMAC_DWRR;
+ break;
+ default:
+ ets = false;
+ break;
+ }
+
+ writel(value, ioaddr + XGMAC_MTL_OPMODE);
+
+ /* Set ETS if desired */
+ for (i = 0; i < MTL_MAX_TX_QUEUES; i++) {
+ value = readl(ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(i));
+ value &= ~XGMAC_TSA;
+ if (ets)
+ value |= XGMAC_ETS;
+ writel(value, ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(i));
+ }
+}
+
+static void dwxgmac2_set_mtl_tx_queue_weight(struct stmmac_priv *priv,
+ struct mac_device_info *hw,
+ u32 weight, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+
+ writel(weight, ioaddr + XGMAC_MTL_TCx_QUANTUM_WEIGHT(queue));
+}
+
+static void dwxgmac2_map_mtl_to_dma(struct mac_device_info *hw, u32 queue,
+ u32 chan)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value, reg;
+
+ reg = (queue < 4) ? XGMAC_MTL_RXQ_DMA_MAP0 : XGMAC_MTL_RXQ_DMA_MAP1;
+ if (queue >= 4)
+ queue -= 4;
+
+ value = readl(ioaddr + reg);
+ value &= ~XGMAC_QxMDMACH(queue);
+ value |= (chan << XGMAC_QxMDMACH_SHIFT(queue)) & XGMAC_QxMDMACH(queue);
+
+ writel(value, ioaddr + reg);
+}
+
+static void dwxgmac2_config_cbs(struct stmmac_priv *priv,
+ struct mac_device_info *hw,
+ u32 send_slope, u32 idle_slope,
+ u32 high_credit, u32 low_credit, u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ writel(send_slope, ioaddr + XGMAC_MTL_TCx_SENDSLOPE(queue));
+ writel(idle_slope, ioaddr + XGMAC_MTL_TCx_QUANTUM_WEIGHT(queue));
+ writel(high_credit, ioaddr + XGMAC_MTL_TCx_HICREDIT(queue));
+ writel(low_credit, ioaddr + XGMAC_MTL_TCx_LOCREDIT(queue));
+
+ value = readl(ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(queue));
+ value &= ~XGMAC_TSA;
+ value |= XGMAC_CC | XGMAC_CBS;
+ writel(value, ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(queue));
+}
+
+static void dwxgmac2_dump_regs(struct mac_device_info *hw, u32 *reg_space)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int i;
+
+ for (i = 0; i < XGMAC_MAC_REGSIZE; i++)
+ reg_space[i] = readl(ioaddr + i * 4);
+}
+
+static int dwxgmac2_host_irq_status(struct stmmac_priv *priv,
+ struct stmmac_extra_stats *x)
+{
+ void __iomem *ioaddr = priv->hw->pcsr;
+ u32 stat, en;
+ int ret = 0;
+
+ en = readl(ioaddr + XGMAC_INT_EN);
+ stat = readl(ioaddr + XGMAC_INT_STATUS);
+
+ stat &= en;
+
+ if (stat & XGMAC_PMTIS) {
+ x->irq_receive_pmt_irq_n++;
+ readl(ioaddr + XGMAC_PMT);
+ }
+
+ if (stat & XGMAC_LPIIS) {
+ u32 lpi = readl(ioaddr + XGMAC_LPI_CTRL);
+
+ if (lpi & LPI_CTRL_STATUS_TLPIEN) {
+ ret |= CORE_IRQ_TX_PATH_IN_LPI_MODE;
+ x->irq_tx_path_in_lpi_mode_n++;
+ }
+ if (lpi & LPI_CTRL_STATUS_TLPIEX) {
+ ret |= CORE_IRQ_TX_PATH_EXIT_LPI_MODE;
+ x->irq_tx_path_exit_lpi_mode_n++;
+ }
+ if (lpi & LPI_CTRL_STATUS_RLPIEN)
+ x->irq_rx_path_in_lpi_mode_n++;
+ if (lpi & LPI_CTRL_STATUS_RLPIEX)
+ x->irq_rx_path_exit_lpi_mode_n++;
+ }
+
+ return ret;
+}
+
+static int dwxgmac2_host_mtl_irq_status(struct stmmac_priv *priv,
+ struct mac_device_info *hw, u32 chan)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ int ret = 0;
+ u32 status;
+
+ status = readl(ioaddr + XGMAC_MTL_INT_STATUS);
+ if (status & BIT(chan)) {
+ u32 chan_status = readl(ioaddr + XGMAC_MTL_QINT_STATUS(chan));
+
+ if (chan_status & XGMAC_RXOVFIS)
+ ret |= CORE_IRQ_MTL_RX_OVERFLOW;
+
+ writel(~0x0, ioaddr + XGMAC_MTL_QINT_STATUS(chan));
+ }
+
+ return ret;
+}
+
+static void dwxgmac2_flow_ctrl(struct mac_device_info *hw, unsigned int duplex,
+ unsigned int fc, unsigned int pause_time,
+ u8 tx_cnt)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u8 i;
+
+ if (fc & FLOW_RX)
+ writel(XGMAC_RFE, ioaddr + XGMAC_RX_FLOW_CTRL);
+ if (fc & FLOW_TX) {
+ for (i = 0; i < tx_cnt; i++) {
+ u32 value = XGMAC_TFE;
+
+ if (duplex)
+ value |= FIELD_PREP(XGMAC_PT, pause_time);
+
+ writel(value, ioaddr + XGMAC_Qx_TX_FLOW_CTRL(i));
+ }
+ }
+}
+
+static void dwxgmac2_pmt(struct mac_device_info *hw, unsigned long mode)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 val = 0x0;
+
+ if (mode & WAKE_MAGIC)
+ val |= XGMAC_PWRDWN | XGMAC_MGKPKTEN;
+ if (mode & WAKE_UCAST)
+ val |= XGMAC_PWRDWN | XGMAC_GLBLUCAST | XGMAC_RWKPKTEN;
+ if (val) {
+ u32 cfg = readl(ioaddr + XGMAC_RX_CONFIG);
+ cfg |= XGMAC_CONFIG_RE;
+ writel(cfg, ioaddr + XGMAC_RX_CONFIG);
+ }
+
+ writel(val, ioaddr + XGMAC_PMT);
+}
+
+static void dwxgmac2_set_umac_addr(struct mac_device_info *hw,
+ const unsigned char *addr,
+ unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = (addr[5] << 8) | addr[4];
+ writel(value | XGMAC_AE, ioaddr + XGMAC_ADDRx_HIGH(reg_n));
+
+ value = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ writel(value, ioaddr + XGMAC_ADDRx_LOW(reg_n));
+}
+
+static void dwxgmac2_get_umac_addr(struct mac_device_info *hw,
+ unsigned char *addr, unsigned int reg_n)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 hi_addr, lo_addr;
+
+ /* Read the MAC address from the hardware */
+ hi_addr = readl(ioaddr + XGMAC_ADDRx_HIGH(reg_n));
+ lo_addr = readl(ioaddr + XGMAC_ADDRx_LOW(reg_n));
+
+ /* Extract the MAC address from the high and low words */
+ addr[0] = lo_addr & 0xff;
+ addr[1] = (lo_addr >> 8) & 0xff;
+ addr[2] = (lo_addr >> 16) & 0xff;
+ addr[3] = (lo_addr >> 24) & 0xff;
+ addr[4] = hi_addr & 0xff;
+ addr[5] = (hi_addr >> 8) & 0xff;
+}
+
+static int dwxgmac2_set_lpi_mode(struct mac_device_info *hw,
+ enum stmmac_lpi_mode mode,
+ bool en_tx_lpi_clockgating, u32 et)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ if (mode == STMMAC_LPI_TIMER)
+ return -EOPNOTSUPP;
+
+ value = readl(ioaddr + XGMAC_LPI_CTRL);
+ if (mode == STMMAC_LPI_FORCED) {
+ value |= LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA;
+ if (en_tx_lpi_clockgating)
+ value |= LPI_CTRL_STATUS_LPITCSE;
+ } else {
+ value &= ~(LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA |
+ LPI_CTRL_STATUS_LPITCSE);
+ }
+ writel(value, ioaddr + XGMAC_LPI_CTRL);
+
+ return 0;
+}
+
+static void dwxgmac2_set_eee_pls(struct mac_device_info *hw, int link)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_LPI_CTRL);
+ if (link)
+ value |= LPI_CTRL_STATUS_PLS;
+ else
+ value &= ~LPI_CTRL_STATUS_PLS;
+ writel(value, ioaddr + XGMAC_LPI_CTRL);
+}
+
+static void dwxgmac2_set_eee_timer(struct mac_device_info *hw, int ls, int tw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = (tw & 0xffff) | ((ls & 0x3ff) << 16);
+ writel(value, ioaddr + XGMAC_LPI_TIMER_CTRL);
+}
+
+static void dwxgmac2_set_mchash(void __iomem *ioaddr, u32 *mcfilterbits,
+ int mcbitslog2)
+{
+ int numhashregs, regs;
+
+ switch (mcbitslog2) {
+ case 6:
+ numhashregs = 2;
+ break;
+ case 7:
+ numhashregs = 4;
+ break;
+ case 8:
+ numhashregs = 8;
+ break;
+ default:
+ return;
+ }
+
+ for (regs = 0; regs < numhashregs; regs++)
+ writel(mcfilterbits[regs], ioaddr + XGMAC_HASH_TABLE(regs));
+}
+
+static void dwxgmac2_set_filter(struct mac_device_info *hw,
+ struct net_device *dev)
+{
+ void __iomem *ioaddr = (void __iomem *)dev->base_addr;
+ u32 value = readl(ioaddr + XGMAC_PACKET_FILTER);
+ int mcbitslog2 = hw->mcast_bits_log2;
+ u32 mc_filter[8];
+ int i;
+
+ value &= ~(XGMAC_FILTER_PR | XGMAC_FILTER_HMC | XGMAC_FILTER_PM);
+ value |= XGMAC_FILTER_HPF;
+
+ memset(mc_filter, 0, sizeof(mc_filter));
+
+ if (dev->flags & IFF_PROMISC) {
+ value |= XGMAC_FILTER_PR;
+ value |= XGMAC_FILTER_PCF;
+ } else if ((dev->flags & IFF_ALLMULTI) ||
+ (netdev_mc_count(dev) > hw->multicast_filter_bins)) {
+ value |= XGMAC_FILTER_PM;
+
+ for (i = 0; i < XGMAC_MAX_HASH_TABLE; i++)
+ writel(~0x0, ioaddr + XGMAC_HASH_TABLE(i));
+ } else if (!netdev_mc_empty(dev) && (dev->flags & IFF_MULTICAST)) {
+ struct netdev_hw_addr *ha;
+
+ value |= XGMAC_FILTER_HMC;
+
+ netdev_for_each_mc_addr(ha, dev) {
+ u32 nr = (bitrev32(~crc32_le(~0, ha->addr, 6)) >>
+ (32 - mcbitslog2));
+ mc_filter[nr >> 5] |= (1 << (nr & 0x1F));
+ }
+ }
+
+ dwxgmac2_set_mchash(ioaddr, mc_filter, mcbitslog2);
+
+ /* Handle multiple unicast addresses */
+ if (netdev_uc_count(dev) > hw->unicast_filter_entries) {
+ value |= XGMAC_FILTER_PR;
+ } else {
+ struct netdev_hw_addr *ha;
+ int reg = 1;
+
+ netdev_for_each_uc_addr(ha, dev) {
+ dwxgmac2_set_umac_addr(hw, ha->addr, reg);
+ reg++;
+ }
+
+ for ( ; reg < XGMAC_ADDR_MAX; reg++) {
+ writel(0, ioaddr + XGMAC_ADDRx_HIGH(reg));
+ writel(0, ioaddr + XGMAC_ADDRx_LOW(reg));
+ }
+ }
+
+ writel(value, ioaddr + XGMAC_PACKET_FILTER);
+}
+
+static void dwxgmac2_set_mac_loopback(void __iomem *ioaddr, bool enable)
+{
+ u32 value = readl(ioaddr + XGMAC_RX_CONFIG);
+
+ if (enable)
+ value |= XGMAC_CONFIG_LM;
+ else
+ value &= ~XGMAC_CONFIG_LM;
+
+ writel(value, ioaddr + XGMAC_RX_CONFIG);
+}
+
+static int dwxgmac2_rss_write_reg(void __iomem *ioaddr, bool is_key, int idx,
+ u32 val)
+{
+ u32 ctrl = 0;
+
+ writel(val, ioaddr + XGMAC_RSS_DATA);
+ ctrl |= idx << XGMAC_RSSIA_SHIFT;
+ ctrl |= is_key ? XGMAC_ADDRT : 0x0;
+ ctrl |= XGMAC_OB;
+ writel(ctrl, ioaddr + XGMAC_RSS_ADDR);
+
+ return readl_poll_timeout(ioaddr + XGMAC_RSS_ADDR, ctrl,
+ !(ctrl & XGMAC_OB), 100, 10000);
+}
+
+static int dwxgmac2_rss_configure(struct mac_device_info *hw,
+ struct stmmac_rss *cfg, u32 num_rxq)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value, *key;
+ int i, ret;
+
+ value = readl(ioaddr + XGMAC_RSS_CTRL);
+ if (!cfg || !cfg->enable) {
+ value &= ~XGMAC_RSSE;
+ writel(value, ioaddr + XGMAC_RSS_CTRL);
+ return 0;
+ }
+
+ key = (u32 *)cfg->key;
+ for (i = 0; i < (ARRAY_SIZE(cfg->key) / sizeof(u32)); i++) {
+ ret = dwxgmac2_rss_write_reg(ioaddr, true, i, key[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cfg->table); i++) {
+ ret = dwxgmac2_rss_write_reg(ioaddr, false, i, cfg->table[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < num_rxq; i++)
+ dwxgmac2_map_mtl_to_dma(hw, i, XGMAC_QDDMACH);
+
+ value |= XGMAC_UDP4TE | XGMAC_TCP4TE | XGMAC_IP2TE | XGMAC_RSSE;
+ writel(value, ioaddr + XGMAC_RSS_CTRL);
+ return 0;
+}
+
+struct dwxgmac3_error_desc {
+ bool valid;
+ const char *desc;
+ const char *detailed_desc;
+};
+
+#define STAT_OFF(field) offsetof(struct stmmac_safety_stats, field)
+
+static void dwxgmac3_log_error(struct net_device *ndev, u32 value, bool corr,
+ const char *module_name,
+ const struct dwxgmac3_error_desc *desc,
+ unsigned long field_offset,
+ struct stmmac_safety_stats *stats)
+{
+ unsigned long loc, mask;
+ u8 *bptr = (u8 *)stats;
+ unsigned long *ptr;
+
+ ptr = (unsigned long *)(bptr + field_offset);
+
+ mask = value;
+ for_each_set_bit(loc, &mask, 32) {
+ netdev_err(ndev, "Found %s error in %s: '%s: %s'\n", corr ?
+ "correctable" : "uncorrectable", module_name,
+ desc[loc].desc, desc[loc].detailed_desc);
+
+ /* Update counters */
+ ptr[loc]++;
+ }
+}
+
+static const struct dwxgmac3_error_desc dwxgmac3_mac_errors[32]= {
+ { true, "ATPES", "Application Transmit Interface Parity Check Error" },
+ { true, "DPES", "Descriptor Cache Data Path Parity Check Error" },
+ { true, "TPES", "TSO Data Path Parity Check Error" },
+ { true, "TSOPES", "TSO Header Data Path Parity Check Error" },
+ { true, "MTPES", "MTL Data Path Parity Check Error" },
+ { true, "MTSPES", "MTL TX Status Data Path Parity Check Error" },
+ { true, "MTBUPES", "MAC TBU Data Path Parity Check Error" },
+ { true, "MTFCPES", "MAC TFC Data Path Parity Check Error" },
+ { true, "ARPES", "Application Receive Interface Data Path Parity Check Error" },
+ { true, "MRWCPES", "MTL RWC Data Path Parity Check Error" },
+ { true, "MRRCPES", "MTL RCC Data Path Parity Check Error" },
+ { true, "CWPES", "CSR Write Data Path Parity Check Error" },
+ { true, "ASRPES", "AXI Slave Read Data Path Parity Check Error" },
+ { true, "TTES", "TX FSM Timeout Error" },
+ { true, "RTES", "RX FSM Timeout Error" },
+ { true, "CTES", "CSR FSM Timeout Error" },
+ { true, "ATES", "APP FSM Timeout Error" },
+ { true, "PTES", "PTP FSM Timeout Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 18 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 19 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 20 */
+ { true, "MSTTES", "Master Read/Write Timeout Error" },
+ { true, "SLVTES", "Slave Read/Write Timeout Error" },
+ { true, "ATITES", "Application Timeout on ATI Interface Error" },
+ { true, "ARITES", "Application Timeout on ARI Interface Error" },
+ { true, "FSMPES", "FSM State Parity Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 26 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 27 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 28 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 29 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 30 */
+ { true, "CPI", "Control Register Parity Check Error" },
+};
+
+static void dwxgmac3_handle_mac_err(struct net_device *ndev,
+ void __iomem *ioaddr, bool correctable,
+ struct stmmac_safety_stats *stats)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_MAC_DPP_FSM_INT_STATUS);
+ writel(value, ioaddr + XGMAC_MAC_DPP_FSM_INT_STATUS);
+
+ dwxgmac3_log_error(ndev, value, correctable, "MAC",
+ dwxgmac3_mac_errors, STAT_OFF(mac_errors), stats);
+}
+
+static const struct dwxgmac3_error_desc dwxgmac3_mtl_errors[32]= {
+ { true, "TXCES", "MTL TX Memory Error" },
+ { true, "TXAMS", "MTL TX Memory Address Mismatch Error" },
+ { true, "TXUES", "MTL TX Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 3 */
+ { true, "RXCES", "MTL RX Memory Error" },
+ { true, "RXAMS", "MTL RX Memory Address Mismatch Error" },
+ { true, "RXUES", "MTL RX Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 7 */
+ { true, "ECES", "MTL EST Memory Error" },
+ { true, "EAMS", "MTL EST Memory Address Mismatch Error" },
+ { true, "EUES", "MTL EST Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 11 */
+ { true, "RPCES", "MTL RX Parser Memory Error" },
+ { true, "RPAMS", "MTL RX Parser Memory Address Mismatch Error" },
+ { true, "RPUES", "MTL RX Parser Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 15 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 16 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 17 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 18 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 19 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 20 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 21 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 22 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 23 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 24 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 25 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 26 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 27 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 28 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 29 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 30 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 31 */
+};
+
+static void dwxgmac3_handle_mtl_err(struct net_device *ndev,
+ void __iomem *ioaddr, bool correctable,
+ struct stmmac_safety_stats *stats)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_MTL_ECC_INT_STATUS);
+ writel(value, ioaddr + XGMAC_MTL_ECC_INT_STATUS);
+
+ dwxgmac3_log_error(ndev, value, correctable, "MTL",
+ dwxgmac3_mtl_errors, STAT_OFF(mtl_errors), stats);
+}
+
+static const struct dwxgmac3_error_desc dwxgmac3_dma_errors[32]= {
+ { true, "TCES", "DMA TSO Memory Error" },
+ { true, "TAMS", "DMA TSO Memory Address Mismatch Error" },
+ { true, "TUES", "DMA TSO Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 3 */
+ { true, "DCES", "DMA DCACHE Memory Error" },
+ { true, "DAMS", "DMA DCACHE Address Mismatch Error" },
+ { true, "DUES", "DMA DCACHE Memory Error" },
+ { false, "UNKNOWN", "Unknown Error" }, /* 7 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 8 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 9 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 10 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 11 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 12 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 13 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 14 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 15 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 16 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 17 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 18 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 19 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 20 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 21 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 22 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 23 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 24 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 25 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 26 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 27 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 28 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 29 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 30 */
+ { false, "UNKNOWN", "Unknown Error" }, /* 31 */
+};
+
+static const char dpp_rx_err[] = "Read Rx Descriptor Parity checker Error";
+static const char dpp_tx_err[] = "Read Tx Descriptor Parity checker Error";
+static const struct dwxgmac3_error_desc dwxgmac3_dma_dpp_errors[32] = {
+ { true, "TDPES0", dpp_tx_err },
+ { true, "TDPES1", dpp_tx_err },
+ { true, "TDPES2", dpp_tx_err },
+ { true, "TDPES3", dpp_tx_err },
+ { true, "TDPES4", dpp_tx_err },
+ { true, "TDPES5", dpp_tx_err },
+ { true, "TDPES6", dpp_tx_err },
+ { true, "TDPES7", dpp_tx_err },
+ { true, "TDPES8", dpp_tx_err },
+ { true, "TDPES9", dpp_tx_err },
+ { true, "TDPES10", dpp_tx_err },
+ { true, "TDPES11", dpp_tx_err },
+ { true, "TDPES12", dpp_tx_err },
+ { true, "TDPES13", dpp_tx_err },
+ { true, "TDPES14", dpp_tx_err },
+ { true, "TDPES15", dpp_tx_err },
+ { true, "RDPES0", dpp_rx_err },
+ { true, "RDPES1", dpp_rx_err },
+ { true, "RDPES2", dpp_rx_err },
+ { true, "RDPES3", dpp_rx_err },
+ { true, "RDPES4", dpp_rx_err },
+ { true, "RDPES5", dpp_rx_err },
+ { true, "RDPES6", dpp_rx_err },
+ { true, "RDPES7", dpp_rx_err },
+ { true, "RDPES8", dpp_rx_err },
+ { true, "RDPES9", dpp_rx_err },
+ { true, "RDPES10", dpp_rx_err },
+ { true, "RDPES11", dpp_rx_err },
+ { true, "RDPES12", dpp_rx_err },
+ { true, "RDPES13", dpp_rx_err },
+ { true, "RDPES14", dpp_rx_err },
+ { true, "RDPES15", dpp_rx_err },
+};
+
+static void dwxgmac3_handle_dma_err(struct net_device *ndev,
+ void __iomem *ioaddr, bool correctable,
+ struct stmmac_safety_stats *stats)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_ECC_INT_STATUS);
+ writel(value, ioaddr + XGMAC_DMA_ECC_INT_STATUS);
+
+ dwxgmac3_log_error(ndev, value, correctable, "DMA",
+ dwxgmac3_dma_errors, STAT_OFF(dma_errors), stats);
+
+ value = readl(ioaddr + XGMAC_DMA_DPP_INT_STATUS);
+ writel(value, ioaddr + XGMAC_DMA_DPP_INT_STATUS);
+
+ dwxgmac3_log_error(ndev, value, false, "DMA_DPP",
+ dwxgmac3_dma_dpp_errors,
+ STAT_OFF(dma_dpp_errors), stats);
+}
+
+static int
+dwxgmac3_safety_feat_config(void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_feature_cfg *safety_cfg)
+{
+ u32 value;
+
+ if (!asp)
+ return -EINVAL;
+
+ /* 1. Enable Safety Features */
+ writel(0x0, ioaddr + XGMAC_MTL_ECC_CONTROL);
+
+ /* 2. Enable MTL Safety Interrupts */
+ value = readl(ioaddr + XGMAC_MTL_ECC_INT_ENABLE);
+ value |= XGMAC_RPCEIE; /* RX Parser Memory Correctable Error */
+ value |= XGMAC_ECEIE; /* EST Memory Correctable Error */
+ value |= XGMAC_RXCEIE; /* RX Memory Correctable Error */
+ value |= XGMAC_TXCEIE; /* TX Memory Correctable Error */
+ writel(value, ioaddr + XGMAC_MTL_ECC_INT_ENABLE);
+
+ /* 3. Enable DMA Safety Interrupts */
+ value = readl(ioaddr + XGMAC_DMA_ECC_INT_ENABLE);
+ value |= XGMAC_DCEIE; /* Descriptor Cache Memory Correctable Error */
+ value |= XGMAC_TCEIE; /* TSO Memory Correctable Error */
+ writel(value, ioaddr + XGMAC_DMA_ECC_INT_ENABLE);
+
+ /* 0x2: Without ECC or Parity Ports on External Application Interface
+ * 0x4: Only ECC Protection for External Memory feature is selected
+ */
+ if (asp == 0x2 || asp == 0x4)
+ return 0;
+
+ /* 4. Enable Parity and Timeout for FSM */
+ value = readl(ioaddr + XGMAC_MAC_FSM_CONTROL);
+ value |= XGMAC_PRTYEN; /* FSM Parity Feature */
+ value |= XGMAC_TMOUTEN; /* FSM Timeout Feature */
+ writel(value, ioaddr + XGMAC_MAC_FSM_CONTROL);
+
+ /* 5. Enable Data Path Parity Protection */
+ value = readl(ioaddr + XGMAC_MTL_DPP_CONTROL);
+ /* already enabled by default, explicit enable it again */
+ value &= ~XGMAC_DPP_DISABLE;
+ writel(value, ioaddr + XGMAC_MTL_DPP_CONTROL);
+
+ return 0;
+}
+
+static int dwxgmac3_safety_feat_irq_status(struct net_device *ndev,
+ void __iomem *ioaddr,
+ unsigned int asp,
+ struct stmmac_safety_stats *stats)
+{
+ bool err, corr;
+ u32 mtl, dma;
+ int ret = 0;
+
+ if (!asp)
+ return -EINVAL;
+
+ mtl = readl(ioaddr + XGMAC_MTL_SAFETY_INT_STATUS);
+ dma = readl(ioaddr + XGMAC_DMA_SAFETY_INT_STATUS);
+
+ err = (mtl & XGMAC_MCSIS) || (dma & XGMAC_MCSIS);
+ corr = false;
+ if (err) {
+ dwxgmac3_handle_mac_err(ndev, ioaddr, corr, stats);
+ ret |= !corr;
+ }
+
+ err = (mtl & (XGMAC_MEUIS | XGMAC_MECIS)) ||
+ (dma & (XGMAC_MSUIS | XGMAC_MSCIS));
+ corr = (mtl & XGMAC_MECIS) || (dma & XGMAC_MSCIS);
+ if (err) {
+ dwxgmac3_handle_mtl_err(ndev, ioaddr, corr, stats);
+ ret |= !corr;
+ }
+
+ /* DMA_DPP_Interrupt_Status is indicated by MCSIS bit in
+ * DMA_Safety_Interrupt_Status, so we handle DMA Data Path
+ * Parity Errors here
+ */
+ err = dma & (XGMAC_DEUIS | XGMAC_DECIS | XGMAC_MCSIS);
+ corr = dma & XGMAC_DECIS;
+ if (err) {
+ dwxgmac3_handle_dma_err(ndev, ioaddr, corr, stats);
+ ret |= !corr;
+ }
+
+ return ret;
+}
+
+static const struct dwxgmac3_error {
+ const struct dwxgmac3_error_desc *desc;
+} dwxgmac3_all_errors[] = {
+ { dwxgmac3_mac_errors },
+ { dwxgmac3_mtl_errors },
+ { dwxgmac3_dma_errors },
+ { dwxgmac3_dma_dpp_errors },
+};
+
+static int dwxgmac3_safety_feat_dump(struct stmmac_safety_stats *stats,
+ int index, unsigned long *count,
+ const char **desc)
+{
+ int module = index / 32, offset = index % 32;
+ unsigned long *ptr = (unsigned long *)stats;
+
+ if (module >= ARRAY_SIZE(dwxgmac3_all_errors))
+ return -EINVAL;
+ if (!dwxgmac3_all_errors[module].desc[offset].valid)
+ return -EINVAL;
+ if (count)
+ *count = *(ptr + index);
+ if (desc)
+ *desc = dwxgmac3_all_errors[module].desc[offset].desc;
+ return 0;
+}
+
+static int dwxgmac3_rxp_disable(void __iomem *ioaddr)
+{
+ u32 val = readl(ioaddr + XGMAC_MTL_OPMODE);
+
+ val &= ~XGMAC_FRPE;
+ writel(val, ioaddr + XGMAC_MTL_OPMODE);
+
+ return 0;
+}
+
+static void dwxgmac3_rxp_enable(void __iomem *ioaddr)
+{
+ u32 val;
+
+ val = readl(ioaddr + XGMAC_MTL_OPMODE);
+ val |= XGMAC_FRPE;
+ writel(val, ioaddr + XGMAC_MTL_OPMODE);
+}
+
+static int dwxgmac3_rxp_update_single_entry(void __iomem *ioaddr,
+ struct stmmac_tc_entry *entry,
+ int pos)
+{
+ int ret, i;
+
+ for (i = 0; i < (sizeof(entry->val) / sizeof(u32)); i++) {
+ int real_pos = pos * (sizeof(entry->val) / sizeof(u32)) + i;
+ u32 val;
+
+ /* Wait for ready */
+ ret = readl_poll_timeout(ioaddr + XGMAC_MTL_RXP_IACC_CTRL_ST,
+ val, !(val & XGMAC_STARTBUSY), 1, 10000);
+ if (ret)
+ return ret;
+
+ /* Write data */
+ val = *((u32 *)&entry->val + i);
+ writel(val, ioaddr + XGMAC_MTL_RXP_IACC_DATA);
+
+ /* Write pos */
+ val = real_pos & XGMAC_ADDR;
+ writel(val, ioaddr + XGMAC_MTL_RXP_IACC_CTRL_ST);
+
+ /* Write OP */
+ val |= XGMAC_WRRDN;
+ writel(val, ioaddr + XGMAC_MTL_RXP_IACC_CTRL_ST);
+
+ /* Start Write */
+ val |= XGMAC_STARTBUSY;
+ writel(val, ioaddr + XGMAC_MTL_RXP_IACC_CTRL_ST);
+
+ /* Wait for done */
+ ret = readl_poll_timeout(ioaddr + XGMAC_MTL_RXP_IACC_CTRL_ST,
+ val, !(val & XGMAC_STARTBUSY), 1, 10000);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct stmmac_tc_entry *
+dwxgmac3_rxp_get_next_entry(struct stmmac_tc_entry *entries,
+ unsigned int count, u32 curr_prio)
+{
+ struct stmmac_tc_entry *entry;
+ u32 min_prio = ~0x0;
+ int i, min_prio_idx;
+ bool found = false;
+
+ for (i = count - 1; i >= 0; i--) {
+ entry = &entries[i];
+
+ /* Do not update unused entries */
+ if (!entry->in_use)
+ continue;
+ /* Do not update already updated entries (i.e. fragments) */
+ if (entry->in_hw)
+ continue;
+ /* Let last entry be updated last */
+ if (entry->is_last)
+ continue;
+ /* Do not return fragments */
+ if (entry->is_frag)
+ continue;
+ /* Check if we already checked this prio */
+ if (entry->prio < curr_prio)
+ continue;
+ /* Check if this is the minimum prio */
+ if (entry->prio < min_prio) {
+ min_prio = entry->prio;
+ min_prio_idx = i;
+ found = true;
+ }
+ }
+
+ if (found)
+ return &entries[min_prio_idx];
+ return NULL;
+}
+
+static int dwxgmac3_rxp_config(void __iomem *ioaddr,
+ struct stmmac_tc_entry *entries,
+ unsigned int count)
+{
+ struct stmmac_tc_entry *entry, *frag;
+ int i, ret, nve = 0;
+ u32 curr_prio = 0;
+ u32 old_val, val;
+
+ /* Force disable RX */
+ old_val = readl(ioaddr + XGMAC_RX_CONFIG);
+ val = old_val & ~XGMAC_CONFIG_RE;
+ writel(val, ioaddr + XGMAC_RX_CONFIG);
+
+ /* Disable RX Parser */
+ ret = dwxgmac3_rxp_disable(ioaddr);
+ if (ret)
+ goto re_enable;
+
+ /* Set all entries as NOT in HW */
+ for (i = 0; i < count; i++) {
+ entry = &entries[i];
+ entry->in_hw = false;
+ }
+
+ /* Update entries by reverse order */
+ while (1) {
+ entry = dwxgmac3_rxp_get_next_entry(entries, count, curr_prio);
+ if (!entry)
+ break;
+
+ curr_prio = entry->prio;
+ frag = entry->frag_ptr;
+
+ /* Set special fragment requirements */
+ if (frag) {
+ entry->val.af = 0;
+ entry->val.rf = 0;
+ entry->val.nc = 1;
+ entry->val.ok_index = nve + 2;
+ }
+
+ ret = dwxgmac3_rxp_update_single_entry(ioaddr, entry, nve);
+ if (ret)
+ goto re_enable;
+
+ entry->table_pos = nve++;
+ entry->in_hw = true;
+
+ if (frag && !frag->in_hw) {
+ ret = dwxgmac3_rxp_update_single_entry(ioaddr, frag, nve);
+ if (ret)
+ goto re_enable;
+ frag->table_pos = nve++;
+ frag->in_hw = true;
+ }
+ }
+
+ if (!nve)
+ goto re_enable;
+
+ /* Update all pass entry */
+ for (i = 0; i < count; i++) {
+ entry = &entries[i];
+ if (!entry->is_last)
+ continue;
+
+ ret = dwxgmac3_rxp_update_single_entry(ioaddr, entry, nve);
+ if (ret)
+ goto re_enable;
+
+ entry->table_pos = nve++;
+ }
+
+ /* Assume n. of parsable entries == n. of valid entries */
+ val = (nve << 16) & XGMAC_NPE;
+ val |= nve & XGMAC_NVE;
+ writel(val, ioaddr + XGMAC_MTL_RXP_CONTROL_STATUS);
+
+ /* Enable RX Parser */
+ dwxgmac3_rxp_enable(ioaddr);
+
+re_enable:
+ /* Re-enable RX */
+ writel(old_val, ioaddr + XGMAC_RX_CONFIG);
+ return ret;
+}
+
+static int dwxgmac2_get_mac_tx_timestamp(struct mac_device_info *hw, u64 *ts)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ if (readl_poll_timeout_atomic(ioaddr + XGMAC_TIMESTAMP_STATUS,
+ value, value & XGMAC_TXTSC, 100, 10000))
+ return -EBUSY;
+
+ *ts = readl(ioaddr + XGMAC_TXTIMESTAMP_NSEC) & XGMAC_TXTSSTSLO;
+ *ts += readl(ioaddr + XGMAC_TXTIMESTAMP_SEC) * 1000000000ULL;
+ return 0;
+}
+
+static int dwxgmac2_flex_pps_config(void __iomem *ioaddr, int index,
+ struct stmmac_pps_cfg *cfg, bool enable,
+ u32 sub_second_inc, u32 systime_flags)
+{
+ u32 tnsec = readl(ioaddr + XGMAC_PPSx_TARGET_TIME_NSEC(index));
+ u32 val = readl(ioaddr + XGMAC_PPS_CONTROL);
+ u64 period;
+
+ if (tnsec & XGMAC_TRGTBUSY0)
+ return -EBUSY;
+ if (!sub_second_inc || !systime_flags)
+ return -EINVAL;
+
+ val &= ~XGMAC_PPSx_MASK(index);
+
+ if (!enable) {
+ val |= XGMAC_PPSCMDx(index, XGMAC_PPSCMD_STOP);
+ writel(val, ioaddr + XGMAC_PPS_CONTROL);
+ return 0;
+ }
+
+ val |= XGMAC_PPSCMDx(index, XGMAC_PPSCMD_START);
+ val |= XGMAC_TRGTMODSELx(index, XGMAC_PPSCMD_START);
+
+ /* XGMAC Core has 4 PPS outputs at most.
+ *
+ * Prior XGMAC Core 3.20, Fixed mode or Flexible mode are selectable for
+ * PPS0 only via PPSEN0. PPS{1,2,3} are in Flexible mode by default,
+ * and can not be switched to Fixed mode, since PPSEN{1,2,3} are
+ * read-only reserved to 0.
+ * But we always set PPSEN{1,2,3} do not make things worse ;-)
+ *
+ * From XGMAC Core 3.20 and later, PPSEN{0,1,2,3} are writable and must
+ * be set, or the PPS outputs stay in Fixed PPS mode by default.
+ */
+ val |= XGMAC_PPSENx(index);
+
+ writel(cfg->start.tv_sec, ioaddr + XGMAC_PPSx_TARGET_TIME_SEC(index));
+
+ if (!(systime_flags & PTP_TCR_TSCTRLSSR))
+ cfg->start.tv_nsec = (cfg->start.tv_nsec * 1000) / 465;
+ writel(cfg->start.tv_nsec, ioaddr + XGMAC_PPSx_TARGET_TIME_NSEC(index));
+
+ period = cfg->period.tv_sec * 1000000000;
+ period += cfg->period.tv_nsec;
+
+ do_div(period, sub_second_inc);
+
+ if (period <= 1)
+ return -EINVAL;
+
+ writel(period - 1, ioaddr + XGMAC_PPSx_INTERVAL(index));
+
+ period >>= 1;
+ if (period <= 1)
+ return -EINVAL;
+
+ writel(period - 1, ioaddr + XGMAC_PPSx_WIDTH(index));
+
+ /* Finally, activate it */
+ writel(val, ioaddr + XGMAC_PPS_CONTROL);
+ return 0;
+}
+
+static void dwxgmac2_sarc_configure(void __iomem *ioaddr, int val)
+{
+ u32 value = readl(ioaddr + XGMAC_TX_CONFIG);
+
+ value = u32_replace_bits(value, val, XGMAC_CONFIG_SARC);
+
+ writel(value, ioaddr + XGMAC_TX_CONFIG);
+}
+
+static int dwxgmac2_filter_wait(struct mac_device_info *hw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ if (readl_poll_timeout(ioaddr + XGMAC_L3L4_ADDR_CTRL, value,
+ !(value & XGMAC_XB), 100, 10000))
+ return -EBUSY;
+ return 0;
+}
+
+static int dwxgmac2_filter_read(struct mac_device_info *hw, u32 filter_no,
+ u8 reg, u32 *data)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value, iddr;
+ int ret;
+
+ ret = dwxgmac2_filter_wait(hw);
+ if (ret)
+ return ret;
+
+ iddr = FIELD_PREP(XGMAC_IDDR_FNUM_MASK, filter_no) |
+ FIELD_PREP(XGMAC_IDDR_REG_MASK, reg);
+ value = FIELD_PREP(XGMAC_IDDR, iddr);
+ value |= XGMAC_TT | XGMAC_XB;
+ writel(value, ioaddr + XGMAC_L3L4_ADDR_CTRL);
+
+ ret = dwxgmac2_filter_wait(hw);
+ if (ret)
+ return ret;
+
+ *data = readl(ioaddr + XGMAC_L3L4_DATA);
+ return 0;
+}
+
+static int dwxgmac2_filter_write(struct mac_device_info *hw, u32 filter_no,
+ u8 reg, u32 data)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value, iddr;
+ int ret;
+
+ ret = dwxgmac2_filter_wait(hw);
+ if (ret)
+ return ret;
+
+ writel(data, ioaddr + XGMAC_L3L4_DATA);
+
+ iddr = FIELD_PREP(XGMAC_IDDR_FNUM_MASK, filter_no) |
+ FIELD_PREP(XGMAC_IDDR_REG_MASK, reg);
+ value = FIELD_PREP(XGMAC_IDDR, iddr);
+ value |= XGMAC_XB;
+ writel(value, ioaddr + XGMAC_L3L4_ADDR_CTRL);
+
+ return dwxgmac2_filter_wait(hw);
+}
+
+static int dwxgmac2_config_l3_filter(struct mac_device_info *hw, u32 filter_no,
+ bool en, bool ipv6, bool sa, bool inv,
+ u32 match)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+ int ret;
+
+ value = readl(ioaddr + XGMAC_PACKET_FILTER);
+ value |= XGMAC_FILTER_IPFE;
+ writel(value, ioaddr + XGMAC_PACKET_FILTER);
+
+ ret = dwxgmac2_filter_read(hw, filter_no, XGMAC_L3L4_CTRL, &value);
+ if (ret)
+ return ret;
+
+ /* For IPv6 not both SA/DA filters can be active */
+ if (ipv6) {
+ value |= XGMAC_L3PEN0;
+ value &= ~(XGMAC_L3SAM0 | XGMAC_L3SAIM0);
+ value &= ~(XGMAC_L3DAM0 | XGMAC_L3DAIM0);
+ if (sa) {
+ value |= XGMAC_L3SAM0;
+ if (inv)
+ value |= XGMAC_L3SAIM0;
+ } else {
+ value |= XGMAC_L3DAM0;
+ if (inv)
+ value |= XGMAC_L3DAIM0;
+ }
+ } else {
+ value &= ~XGMAC_L3PEN0;
+ if (sa) {
+ value |= XGMAC_L3SAM0;
+ if (inv)
+ value |= XGMAC_L3SAIM0;
+ } else {
+ value |= XGMAC_L3DAM0;
+ if (inv)
+ value |= XGMAC_L3DAIM0;
+ }
+ }
+
+ ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L3L4_CTRL, value);
+ if (ret)
+ return ret;
+
+ if (sa) {
+ ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L3_ADDR0, match);
+ if (ret)
+ return ret;
+ } else {
+ ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L3_ADDR1, match);
+ if (ret)
+ return ret;
+ }
+
+ if (!en)
+ return dwxgmac2_filter_write(hw, filter_no, XGMAC_L3L4_CTRL, 0);
+
+ return 0;
+}
+
+static int dwxgmac2_config_l4_filter(struct mac_device_info *hw, u32 filter_no,
+ bool en, bool udp, bool sa, bool inv,
+ u32 match)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+ int ret;
+
+ value = readl(ioaddr + XGMAC_PACKET_FILTER);
+ value |= XGMAC_FILTER_IPFE;
+ writel(value, ioaddr + XGMAC_PACKET_FILTER);
+
+ ret = dwxgmac2_filter_read(hw, filter_no, XGMAC_L3L4_CTRL, &value);
+ if (ret)
+ return ret;
+
+ if (udp) {
+ value |= XGMAC_L4PEN0;
+ } else {
+ value &= ~XGMAC_L4PEN0;
+ }
+
+ value &= ~(XGMAC_L4SPM0 | XGMAC_L4SPIM0);
+ value &= ~(XGMAC_L4DPM0 | XGMAC_L4DPIM0);
+ if (sa) {
+ value |= XGMAC_L4SPM0;
+ if (inv)
+ value |= XGMAC_L4SPIM0;
+ } else {
+ value |= XGMAC_L4DPM0;
+ if (inv)
+ value |= XGMAC_L4DPIM0;
+ }
+
+ ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L3L4_CTRL, value);
+ if (ret)
+ return ret;
+
+ if (sa) {
+ value = FIELD_PREP(XGMAC_L4SP0, match);
+
+ ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L4_ADDR, value);
+ if (ret)
+ return ret;
+ } else {
+ value = FIELD_PREP(XGMAC_L4DP0, match);
+
+ ret = dwxgmac2_filter_write(hw, filter_no, XGMAC_L4_ADDR, value);
+ if (ret)
+ return ret;
+ }
+
+ if (!en)
+ return dwxgmac2_filter_write(hw, filter_no, XGMAC_L3L4_CTRL, 0);
+
+ return 0;
+}
+
+static void dwxgmac2_set_arp_offload(struct mac_device_info *hw, bool en,
+ u32 addr)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ writel(addr, ioaddr + XGMAC_ARP_ADDR);
+
+ value = readl(ioaddr + XGMAC_RX_CONFIG);
+ if (en)
+ value |= XGMAC_CONFIG_ARPEN;
+ else
+ value &= ~XGMAC_CONFIG_ARPEN;
+ writel(value, ioaddr + XGMAC_RX_CONFIG);
+}
+
+const struct stmmac_ops dwxgmac210_ops = {
+ .core_init = dwxgmac2_core_init,
+ .irq_modify = dwxgmac2_irq_modify,
+ .update_caps = dwxgmac2_update_caps,
+ .set_mac = dwxgmac2_set_mac,
+ .rx_ipc = dwxgmac2_rx_ipc,
+ .rx_queue_enable = dwxgmac2_rx_queue_enable,
+ .rx_queue_prio = dwxgmac2_rx_queue_prio,
+ .tx_queue_prio = dwxgmac2_tx_queue_prio,
+ .rx_queue_routing = dwxgmac2_rx_queue_routing,
+ .prog_mtl_rx_algorithms = dwxgmac2_prog_mtl_rx_algorithms,
+ .prog_mtl_tx_algorithms = dwxgmac2_prog_mtl_tx_algorithms,
+ .set_mtl_tx_queue_weight = dwxgmac2_set_mtl_tx_queue_weight,
+ .map_mtl_to_dma = dwxgmac2_map_mtl_to_dma,
+ .config_cbs = dwxgmac2_config_cbs,
+ .dump_regs = dwxgmac2_dump_regs,
+ .host_irq_status = dwxgmac2_host_irq_status,
+ .host_mtl_irq_status = dwxgmac2_host_mtl_irq_status,
+ .flow_ctrl = dwxgmac2_flow_ctrl,
+ .pmt = dwxgmac2_pmt,
+ .set_umac_addr = dwxgmac2_set_umac_addr,
+ .get_umac_addr = dwxgmac2_get_umac_addr,
+ .set_lpi_mode = dwxgmac2_set_lpi_mode,
+ .set_eee_timer = dwxgmac2_set_eee_timer,
+ .set_eee_pls = dwxgmac2_set_eee_pls,
+ .debug = NULL,
+ .set_filter = dwxgmac2_set_filter,
+ .safety_feat_config = dwxgmac3_safety_feat_config,
+ .safety_feat_irq_status = dwxgmac3_safety_feat_irq_status,
+ .safety_feat_dump = dwxgmac3_safety_feat_dump,
+ .set_mac_loopback = dwxgmac2_set_mac_loopback,
+ .rss_configure = dwxgmac2_rss_configure,
+ .rxp_config = dwxgmac3_rxp_config,
+ .get_mac_tx_timestamp = dwxgmac2_get_mac_tx_timestamp,
+ .flex_pps_config = dwxgmac2_flex_pps_config,
+ .sarc_configure = dwxgmac2_sarc_configure,
+ .config_l3_filter = dwxgmac2_config_l3_filter,
+ .config_l4_filter = dwxgmac2_config_l4_filter,
+ .set_arp_offload = dwxgmac2_set_arp_offload,
+ .fpe_map_preemption_class = dwxgmac3_fpe_map_preemption_class,
+};
+
+static void dwxlgmac2_rx_queue_enable(struct mac_device_info *hw, u8 mode,
+ u32 queue)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + XLGMAC_RXQ_ENABLE_CTRL0) & ~XGMAC_RXQEN(queue);
+ if (mode == MTL_QUEUE_AVB)
+ value |= 0x1 << XGMAC_RXQEN_SHIFT(queue);
+ else if (mode == MTL_QUEUE_DCB)
+ value |= 0x2 << XGMAC_RXQEN_SHIFT(queue);
+ writel(value, ioaddr + XLGMAC_RXQ_ENABLE_CTRL0);
+}
+
+const struct stmmac_ops dwxlgmac2_ops = {
+ .core_init = dwxgmac2_core_init,
+ .irq_modify = dwxgmac2_irq_modify,
+ .set_mac = dwxgmac2_set_mac,
+ .rx_ipc = dwxgmac2_rx_ipc,
+ .rx_queue_enable = dwxlgmac2_rx_queue_enable,
+ .rx_queue_prio = dwxgmac2_rx_queue_prio,
+ .tx_queue_prio = dwxgmac2_tx_queue_prio,
+ .rx_queue_routing = dwxgmac2_rx_queue_routing,
+ .prog_mtl_rx_algorithms = dwxgmac2_prog_mtl_rx_algorithms,
+ .prog_mtl_tx_algorithms = dwxgmac2_prog_mtl_tx_algorithms,
+ .set_mtl_tx_queue_weight = dwxgmac2_set_mtl_tx_queue_weight,
+ .map_mtl_to_dma = dwxgmac2_map_mtl_to_dma,
+ .config_cbs = dwxgmac2_config_cbs,
+ .dump_regs = dwxgmac2_dump_regs,
+ .host_irq_status = dwxgmac2_host_irq_status,
+ .host_mtl_irq_status = dwxgmac2_host_mtl_irq_status,
+ .flow_ctrl = dwxgmac2_flow_ctrl,
+ .pmt = dwxgmac2_pmt,
+ .set_umac_addr = dwxgmac2_set_umac_addr,
+ .get_umac_addr = dwxgmac2_get_umac_addr,
+ .set_lpi_mode = dwxgmac2_set_lpi_mode,
+ .set_eee_timer = dwxgmac2_set_eee_timer,
+ .set_eee_pls = dwxgmac2_set_eee_pls,
+ .debug = NULL,
+ .set_filter = dwxgmac2_set_filter,
+ .safety_feat_config = dwxgmac3_safety_feat_config,
+ .safety_feat_irq_status = dwxgmac3_safety_feat_irq_status,
+ .safety_feat_dump = dwxgmac3_safety_feat_dump,
+ .set_mac_loopback = dwxgmac2_set_mac_loopback,
+ .rss_configure = dwxgmac2_rss_configure,
+ .rxp_config = dwxgmac3_rxp_config,
+ .get_mac_tx_timestamp = dwxgmac2_get_mac_tx_timestamp,
+ .flex_pps_config = dwxgmac2_flex_pps_config,
+ .sarc_configure = dwxgmac2_sarc_configure,
+ .config_l3_filter = dwxgmac2_config_l3_filter,
+ .config_l4_filter = dwxgmac2_config_l4_filter,
+ .set_arp_offload = dwxgmac2_set_arp_offload,
+ .fpe_map_preemption_class = dwxgmac3_fpe_map_preemption_class,
+};
+
+int dwxgmac2_setup(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ dev_info(priv->device, "\tXGMAC2\n");
+
+ priv->dev->priv_flags |= IFF_UNICAST_FLT;
+ mac->pcsr = priv->ioaddr;
+ mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
+ mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
+ mac->mcast_bits_log2 = 0;
+
+ if (mac->multicast_filter_bins)
+ mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
+
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000FD |
+ MAC_2500FD | MAC_5000FD | MAC_10000FD;
+ mac->link.duplex = 0;
+ mac->link.speed10 = XGMAC_CONFIG_SS_10_MII;
+ mac->link.speed100 = XGMAC_CONFIG_SS_100_MII;
+ mac->link.speed1000 = XGMAC_CONFIG_SS_1000_GMII;
+ mac->link.speed2500 = XGMAC_CONFIG_SS_2500_GMII;
+ mac->link.xgmii.speed2500 = XGMAC_CONFIG_SS_2500;
+ mac->link.xgmii.speed5000 = XGMAC_CONFIG_SS_5000;
+ mac->link.xgmii.speed10000 = XGMAC_CONFIG_SS_10000;
+ mac->link.speed_mask = XGMAC_CONFIG_SS_MASK;
+
+ mac->mii.addr = XGMAC_MDIO_ADDR;
+ mac->mii.data = XGMAC_MDIO_DATA;
+ mac->mii.addr_mask = GENMASK_U32(20, 16);
+ mac->mii.reg_mask = GENMASK_U32(15, 0);
+ mac->mii.clk_csr_mask = GENMASK_U32(21, 19);
+ mac->num_vlan = stmmac_get_num_vlan(priv->ioaddr);
+
+ return 0;
+}
+
+int dwxlgmac2_setup(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ dev_info(priv->device, "\tXLGMAC\n");
+
+ priv->dev->priv_flags |= IFF_UNICAST_FLT;
+ mac->pcsr = priv->ioaddr;
+ mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
+ mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
+ mac->mcast_bits_log2 = 0;
+
+ if (mac->multicast_filter_bins)
+ mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
+
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_1000FD | MAC_2500FD | MAC_5000FD |
+ MAC_10000FD | MAC_25000FD |
+ MAC_40000FD | MAC_50000FD |
+ MAC_100000FD;
+ mac->link.duplex = 0;
+ mac->link.speed1000 = XLGMAC_CONFIG_SS_1000;
+ mac->link.speed2500 = XLGMAC_CONFIG_SS_2500;
+ mac->link.xgmii.speed10000 = XLGMAC_CONFIG_SS_10G;
+ mac->link.xlgmii.speed25000 = XLGMAC_CONFIG_SS_25G;
+ mac->link.xlgmii.speed40000 = XLGMAC_CONFIG_SS_40G;
+ mac->link.xlgmii.speed50000 = XLGMAC_CONFIG_SS_50G;
+ mac->link.xlgmii.speed100000 = XLGMAC_CONFIG_SS_100G;
+ mac->link.speed_mask = XLGMAC_CONFIG_SS;
+
+ mac->mii.addr = XGMAC_MDIO_ADDR;
+ mac->mii.data = XGMAC_MDIO_DATA;
+ mac->mii.addr_mask = GENMASK_U32(20, 16);
+ mac->mii.reg_mask = GENMASK_U32(15, 0);
+ mac->mii.clk_csr_mask = GENMASK_U32(21, 19);
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c
new file mode 100644
index 000000000..6719ac6e3
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * stmmac XGMAC support.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/stmmac.h>
+#include "common.h"
+#include "dwxgmac2.h"
+
+static int dwxgmac2_get_tx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p, void __iomem *ioaddr)
+{
+ u32 tdes3 = le32_to_cpu(p->des3);
+ int ret = tx_done;
+
+ if (unlikely(tdes3 & XGMAC_TDES3_OWN))
+ return tx_dma_own;
+ if (likely(!(tdes3 & XGMAC_TDES3_LD)))
+ return tx_not_ls;
+
+ return ret;
+}
+
+static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p)
+{
+ u32 rdes3 = le32_to_cpu(p->des3);
+ u32 rdes2 = le32_to_cpu(p->des2);
+
+ if (unlikely(rdes3 & XGMAC_RDES3_OWN))
+ return dma_own;
+ if (unlikely(rdes3 & XGMAC_RDES3_CTXT))
+ return discard_frame;
+ if (likely(!(rdes3 & XGMAC_RDES3_LD)))
+ return rx_not_ls;
+ if (unlikely((rdes3 & XGMAC_RDES3_ES) && (rdes3 & XGMAC_RDES3_LD)))
+ return discard_frame;
+
+ if (rdes2 & XGMAC_RDES2_L3FM)
+ x->l3_filter_match++;
+ if (rdes2 & XGMAC_RDES2_L4FM)
+ x->l4_filter_match++;
+
+ return good_frame;
+}
+
+static void dwxgmac2_set_tx_owner(struct dma_desc *p)
+{
+ p->des3 |= cpu_to_le32(XGMAC_TDES3_OWN);
+}
+
+static void dwxgmac2_set_rx_owner(struct dma_desc *p, int disable_rx_ic)
+{
+ u32 flags = XGMAC_RDES3_OWN;
+
+ if (!disable_rx_ic)
+ flags |= XGMAC_RDES3_IOC;
+
+ p->des3 |= cpu_to_le32(flags);
+}
+
+static u16 dwxgmac2_wrback_get_rx_vlan_tci(struct dma_desc *p)
+{
+ return le32_to_cpu(p->des0) & XGMAC_RDES0_VLAN_TAG_MASK;
+}
+
+static bool dwxgmac2_wrback_get_rx_vlan_valid(struct dma_desc *p)
+{
+ u32 et_lt;
+
+ et_lt = FIELD_GET(XGMAC_RDES3_ET_LT, le32_to_cpu(p->des3));
+
+ return et_lt >= XGMAC_ET_LT_VLAN_STAG &&
+ et_lt <= XGMAC_ET_LT_DVLAN_STAG_CTAG;
+}
+
+static int dwxgmac2_get_rx_frame_len(struct dma_desc *p, int rx_coe)
+{
+ return (le32_to_cpu(p->des3) & XGMAC_RDES3_PL);
+}
+
+static void dwxgmac2_enable_tx_timestamp(struct dma_desc *p)
+{
+ p->des2 |= cpu_to_le32(XGMAC_TDES2_TTSE);
+}
+
+static int dwxgmac2_get_tx_timestamp_status(struct dma_desc *p)
+{
+ return 0; /* Not supported */
+}
+
+static inline void dwxgmac2_get_timestamp(void *desc, u32 ats, u64 *ts)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ u64 ns = 0;
+
+ ns += le32_to_cpu(p->des1) * 1000000000ULL;
+ ns += le32_to_cpu(p->des0);
+
+ *ts = ns;
+}
+
+static int dwxgmac2_rx_check_timestamp(void *desc)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ u32 rdes3 = le32_to_cpu(p->des3);
+ bool desc_valid, ts_valid;
+
+ dma_rmb();
+
+ desc_valid = !(rdes3 & XGMAC_RDES3_OWN) && (rdes3 & XGMAC_RDES3_CTXT);
+ ts_valid = !(rdes3 & XGMAC_RDES3_TSD) && (rdes3 & XGMAC_RDES3_TSA);
+
+ if (likely(desc_valid && ts_valid)) {
+ if ((p->des0 == 0xffffffff) && (p->des1 == 0xffffffff))
+ return -EINVAL;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int dwxgmac2_get_rx_timestamp_status(void *desc, void *next_desc,
+ u32 ats)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ u32 rdes3 = le32_to_cpu(p->des3);
+ int ret = -EBUSY;
+
+ if (likely(rdes3 & XGMAC_RDES3_CDA))
+ ret = dwxgmac2_rx_check_timestamp(next_desc);
+
+ return !ret;
+}
+
+static void dwxgmac2_init_rx_desc(struct dma_desc *p, int disable_rx_ic,
+ u8 descriptor_mode, int end, int bfsize)
+{
+ dwxgmac2_set_rx_owner(p, disable_rx_ic);
+}
+
+static void dwxgmac2_init_tx_desc(struct dma_desc *p, u8 descriptor_mode,
+ int end)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+}
+
+static void dwxgmac2_prepare_tx_desc(struct dma_desc *p, int is_fs, int len,
+ bool csum_flag, u8 descriptor_mode,
+ bool tx_own, bool ls,
+ unsigned int tot_pkt_len)
+{
+ u32 tdes3 = le32_to_cpu(p->des3);
+
+ p->des2 |= cpu_to_le32(len & XGMAC_TDES2_B1L);
+
+ tdes3 |= tot_pkt_len & XGMAC_TDES3_FL;
+ if (is_fs)
+ tdes3 |= XGMAC_TDES3_FD;
+ else
+ tdes3 &= ~XGMAC_TDES3_FD;
+
+ if (csum_flag)
+ tdes3 |= FIELD_PREP(XGMAC_TDES3_CIC, 0x3);
+ else
+ tdes3 &= ~XGMAC_TDES3_CIC;
+
+ if (ls)
+ tdes3 |= XGMAC_TDES3_LD;
+ else
+ tdes3 &= ~XGMAC_TDES3_LD;
+
+ /* Finally set the OWN bit. Later the DMA will start! */
+ if (tx_own)
+ tdes3 |= XGMAC_TDES3_OWN;
+
+ if (is_fs && tx_own)
+ /* When the own bit, for the first frame, has to be set, all
+ * descriptors for the same frame has to be set before, to
+ * avoid race condition.
+ */
+ dma_wmb();
+
+ p->des3 = cpu_to_le32(tdes3);
+}
+
+static void dwxgmac2_prepare_tso_tx_desc(struct dma_desc *p, int is_fs,
+ int len1, int len2, bool tx_own,
+ bool ls, unsigned int tcphdrlen,
+ unsigned int tcppayloadlen)
+{
+ u32 tdes3 = le32_to_cpu(p->des3);
+
+ if (len1)
+ p->des2 |= cpu_to_le32(len1 & XGMAC_TDES2_B1L);
+ if (len2)
+ p->des2 |= cpu_to_le32(FIELD_PREP(XGMAC_TDES2_B2L, len2));
+ if (is_fs) {
+ tdes3 |= XGMAC_TDES3_FD | XGMAC_TDES3_TSE;
+ tdes3 |= FIELD_PREP(XGMAC_TDES3_THL, tcphdrlen);
+ tdes3 |= FIELD_PREP(XGMAC_TDES3_TPL, tcppayloadlen);
+ } else {
+ tdes3 &= ~XGMAC_TDES3_FD;
+ }
+
+ if (ls)
+ tdes3 |= XGMAC_TDES3_LD;
+ else
+ tdes3 &= ~XGMAC_TDES3_LD;
+
+ /* Finally set the OWN bit. Later the DMA will start! */
+ if (tx_own)
+ tdes3 |= XGMAC_TDES3_OWN;
+
+ if (is_fs && tx_own)
+ /* When the own bit, for the first frame, has to be set, all
+ * descriptors for the same frame has to be set before, to
+ * avoid race condition.
+ */
+ dma_wmb();
+
+ p->des3 = cpu_to_le32(tdes3);
+}
+
+static void dwxgmac2_release_tx_desc(struct dma_desc *p, u8 descriptor_mode)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+}
+
+static void dwxgmac2_set_tx_ic(struct dma_desc *p)
+{
+ p->des2 |= cpu_to_le32(XGMAC_TDES2_IOC);
+}
+
+static void dwxgmac2_set_mss(struct dma_desc *p, unsigned int mss)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = cpu_to_le32(mss);
+ p->des3 = cpu_to_le32(XGMAC_TDES3_CTXT | XGMAC_TDES3_TCMSSV);
+}
+
+static void dwxgmac2_set_addr(struct dma_desc *p, dma_addr_t addr)
+{
+ p->des0 = cpu_to_le32(lower_32_bits(addr));
+ p->des1 = cpu_to_le32(upper_32_bits(addr));
+}
+
+static void dwxgmac2_clear(struct dma_desc *p)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+}
+
+static int dwxgmac2_get_rx_hash(struct dma_desc *p, u32 *hash,
+ enum pkt_hash_types *type)
+{
+ u32 rdes3 = le32_to_cpu(p->des3);
+ u32 ptype;
+
+ if (rdes3 & XGMAC_RDES3_RSV) {
+ ptype = FIELD_GET(XGMAC_RDES3_L34T, rdes3);
+
+ switch (ptype) {
+ case XGMAC_L34T_IP4TCP:
+ case XGMAC_L34T_IP4UDP:
+ case XGMAC_L34T_IP6TCP:
+ case XGMAC_L34T_IP6UDP:
+ *type = PKT_HASH_TYPE_L4;
+ break;
+ default:
+ *type = PKT_HASH_TYPE_L3;
+ break;
+ }
+
+ *hash = le32_to_cpu(p->des1);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static void dwxgmac2_get_rx_header_len(struct dma_desc *p, unsigned int *len)
+{
+ if (le32_to_cpu(p->des3) & XGMAC_RDES3_L34T)
+ *len = le32_to_cpu(p->des2) & XGMAC_RDES2_HL;
+}
+
+static void dwxgmac2_set_sec_addr(struct dma_desc *p, dma_addr_t addr, bool is_valid)
+{
+ p->des2 = cpu_to_le32(lower_32_bits(addr));
+ p->des3 = cpu_to_le32(upper_32_bits(addr));
+}
+
+static void dwxgmac2_set_sarc(struct dma_desc *p, u32 sarc_type)
+{
+ p->des3 |= cpu_to_le32(FIELD_PREP(XGMAC_TDES3_SAIC, sarc_type));
+}
+
+static void dwxgmac2_set_vlan_tag(struct dma_desc *p, u16 tag, u16 inner_tag,
+ u32 inner_type)
+{
+ p->des0 = 0;
+ p->des1 = 0;
+ p->des2 = 0;
+ p->des3 = 0;
+
+ /* Inner VLAN */
+ if (inner_type) {
+ u32 des = FIELD_PREP(XGMAC_TDES2_IVT, inner_tag);
+
+ p->des2 = cpu_to_le32(des);
+
+ des = FIELD_PREP(XGMAC_TDES3_IVTIR, inner_type);
+ p->des3 = cpu_to_le32(des | XGMAC_TDES3_IVLTV);
+ }
+
+ /* Outer VLAN */
+ p->des3 |= cpu_to_le32(tag & XGMAC_TDES3_VT);
+ p->des3 |= cpu_to_le32(XGMAC_TDES3_VLTV);
+
+ p->des3 |= cpu_to_le32(XGMAC_TDES3_CTXT);
+}
+
+static void dwxgmac2_set_vlan(struct dma_desc *p, u32 type)
+{
+ p->des2 |= cpu_to_le32(FIELD_PREP(XGMAC_TDES2_VTIR, type));
+}
+
+static void dwxgmac2_set_tbs(struct dma_edesc *p, u32 sec, u32 nsec)
+{
+ p->des4 = cpu_to_le32((sec & XGMAC_TDES0_LT) | XGMAC_TDES0_LTV);
+ p->des5 = cpu_to_le32(nsec & XGMAC_TDES1_LT);
+ p->des6 = 0;
+ p->des7 = 0;
+}
+
+const struct stmmac_desc_ops dwxgmac210_desc_ops = {
+ .tx_status = dwxgmac2_get_tx_status,
+ .rx_status = dwxgmac2_get_rx_status,
+ .set_tx_owner = dwxgmac2_set_tx_owner,
+ .set_rx_owner = dwxgmac2_set_rx_owner,
+ .get_rx_vlan_tci = dwxgmac2_wrback_get_rx_vlan_tci,
+ .get_rx_vlan_valid = dwxgmac2_wrback_get_rx_vlan_valid,
+ .get_rx_frame_len = dwxgmac2_get_rx_frame_len,
+ .enable_tx_timestamp = dwxgmac2_enable_tx_timestamp,
+ .get_tx_timestamp_status = dwxgmac2_get_tx_timestamp_status,
+ .get_rx_timestamp_status = dwxgmac2_get_rx_timestamp_status,
+ .get_timestamp = dwxgmac2_get_timestamp,
+ .set_tx_ic = dwxgmac2_set_tx_ic,
+ .prepare_tx_desc = dwxgmac2_prepare_tx_desc,
+ .prepare_tso_tx_desc = dwxgmac2_prepare_tso_tx_desc,
+ .release_tx_desc = dwxgmac2_release_tx_desc,
+ .init_rx_desc = dwxgmac2_init_rx_desc,
+ .init_tx_desc = dwxgmac2_init_tx_desc,
+ .set_mss = dwxgmac2_set_mss,
+ .set_addr = dwxgmac2_set_addr,
+ .clear = dwxgmac2_clear,
+ .get_rx_hash = dwxgmac2_get_rx_hash,
+ .get_rx_header_len = dwxgmac2_get_rx_header_len,
+ .set_sec_addr = dwxgmac2_set_sec_addr,
+ .set_sarc = dwxgmac2_set_sarc,
+ .set_vlan_tag = dwxgmac2_set_vlan_tag,
+ .set_vlan = dwxgmac2_set_vlan,
+ .set_tbs = dwxgmac2_set_tbs,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
new file mode 100644
index 000000000..03437f1cf
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * stmmac XGMAC support.
+ */
+
+#include <linux/iopoll.h>
+#include "stmmac.h"
+#include "dwxgmac2.h"
+
+static int dwxgmac2_dma_reset(void __iomem *ioaddr)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_MODE);
+
+ /* DMA SW reset */
+ writel(value | XGMAC_SWR, ioaddr + XGMAC_DMA_MODE);
+
+ return readl_poll_timeout(ioaddr + XGMAC_DMA_MODE, value,
+ !(value & XGMAC_SWR), 0, 100000);
+}
+
+static void dwxgmac2_dma_init(void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_SYSBUS_MODE);
+
+ if (dma_cfg->aal)
+ value |= XGMAC_AAL;
+
+ if (dma_cfg->eame)
+ value |= XGMAC_EAME;
+
+ writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE);
+}
+
+static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, u32 chan)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_CH_CONTROL(chan));
+
+ if (dma_cfg->pblx8)
+ value |= XGMAC_PBLx8;
+
+ writel(value, ioaddr + XGMAC_DMA_CH_CONTROL(chan));
+ writel(XGMAC_DMA_INT_DEFAULT_EN, ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+}
+
+static void dwxgmac2_dma_init_rx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan)
+{
+ u32 rxpbl = dma_cfg->rxpbl ?: dma_cfg->pbl;
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+ value = u32_replace_bits(value, rxpbl, XGMAC_RxPBL);
+ writel(value, ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+
+ writel(upper_32_bits(phy), ioaddr + XGMAC_DMA_CH_RxDESC_HADDR(chan));
+ writel(lower_32_bits(phy), ioaddr + XGMAC_DMA_CH_RxDESC_LADDR(chan));
+}
+
+static void dwxgmac2_dma_init_tx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan)
+{
+ u32 txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+ value = u32_replace_bits(value, txpbl, XGMAC_TxPBL);
+ writel(value, ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+
+ writel(upper_32_bits(phy), ioaddr + XGMAC_DMA_CH_TxDESC_HADDR(chan));
+ writel(lower_32_bits(phy), ioaddr + XGMAC_DMA_CH_TxDESC_LADDR(chan));
+}
+
+static void dwxgmac2_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_SYSBUS_MODE);
+
+ if (axi->axi_lpi_en)
+ value |= XGMAC_EN_LPI;
+ if (axi->axi_xit_frm)
+ value |= XGMAC_LPI_XIT_PKT;
+
+ value = u32_replace_bits(value, axi->axi_wr_osr_lmt, XGMAC_WR_OSR_LMT);
+ value = u32_replace_bits(value, axi->axi_rd_osr_lmt, XGMAC_RD_OSR_LMT);
+
+ if (!axi->axi_fb)
+ value |= XGMAC_UNDEF;
+
+ /* Depending on the UNDEF bit the Master AXI will perform any burst
+ * length according to the BLEN programmed (by default all BLEN are
+ * set). Note that the UNDEF bit is readonly, and is the inverse of
+ * Bus Mode bit 16.
+ */
+ value = (value & ~DMA_AXI_BLEN_MASK) | axi->axi_blen_regval;
+
+ writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE);
+ writel(XGMAC_TDPS, ioaddr + XGMAC_TX_EDMA_CTRL);
+ writel(XGMAC_RDPS, ioaddr + XGMAC_RX_EDMA_CTRL);
+}
+
+static void dwxgmac2_dma_dump_regs(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 *reg_space)
+{
+ int i;
+
+ for (i = (XGMAC_DMA_MODE / 4); i < XGMAC_REGSIZE; i++)
+ reg_space[i] = readl(ioaddr + i * 4);
+}
+
+static void dwxgmac2_dma_rx_mode(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int mode, u32 channel, int fifosz, u8 qmode)
+{
+ u32 value = readl(ioaddr + XGMAC_MTL_RXQ_OPMODE(channel));
+ unsigned int rqs = fifosz / 256 - 1;
+ unsigned int rtc;
+
+ if (mode == SF_DMA_MODE) {
+ value |= XGMAC_RSF;
+ } else {
+ value &= ~XGMAC_RSF;
+
+ if (mode <= 64)
+ rtc = 0x0;
+ else if (mode <= 96)
+ rtc = 0x2;
+ else
+ rtc = 0x3;
+
+ value = u32_replace_bits(value, rtc, XGMAC_RTC);
+ }
+
+ value = u32_replace_bits(value, rqs, XGMAC_RQS);
+
+ if ((fifosz >= 4096) && (qmode != MTL_QUEUE_AVB)) {
+ u32 flow = readl(ioaddr + XGMAC_MTL_RXQ_FLOW_CONTROL(channel));
+ unsigned int rfd, rfa;
+
+ value |= XGMAC_EHFC;
+
+ /* Set Threshold for Activating Flow Control to min 2 frames,
+ * i.e. 1500 * 2 = 3000 bytes.
+ *
+ * Set Threshold for Deactivating Flow Control to min 1 frame,
+ * i.e. 1500 bytes.
+ */
+ switch (fifosz) {
+ case 4096:
+ /* This violates the above formula because of FIFO size
+ * limit therefore overflow may occur in spite of this.
+ */
+ rfd = 0x03; /* Full-2.5K */
+ rfa = 0x01; /* Full-1.5K */
+ break;
+
+ default:
+ rfd = 0x07; /* Full-4.5K */
+ rfa = 0x04; /* Full-3K */
+ break;
+ }
+
+ flow = u32_replace_bits(flow, rfd, XGMAC_RFD);
+ flow = u32_replace_bits(flow, rfa, XGMAC_RFA);
+
+ writel(flow, ioaddr + XGMAC_MTL_RXQ_FLOW_CONTROL(channel));
+ }
+
+ writel(value, ioaddr + XGMAC_MTL_RXQ_OPMODE(channel));
+}
+
+static void dwxgmac2_dma_tx_mode(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int mode, u32 channel, int fifosz, u8 qmode)
+{
+ u32 value = readl(ioaddr + XGMAC_MTL_TXQ_OPMODE(channel));
+ unsigned int tqs = fifosz / 256 - 1;
+ unsigned int ttc, txqen;
+
+ if (mode == SF_DMA_MODE) {
+ value |= XGMAC_TSF;
+ } else {
+ value &= ~XGMAC_TSF;
+
+ if (mode <= 64)
+ ttc = 0x0;
+ else if (mode <= 96)
+ ttc = 0x2;
+ else if (mode <= 128)
+ ttc = 0x3;
+ else if (mode <= 192)
+ ttc = 0x4;
+ else if (mode <= 256)
+ ttc = 0x5;
+ else if (mode <= 384)
+ ttc = 0x6;
+ else
+ ttc = 0x7;
+
+ value = u32_replace_bits(value, ttc, XGMAC_TTC);
+ }
+
+ /* Use static TC to Queue mapping */
+ value |= FIELD_PREP(XGMAC_Q2TCMAP, channel);
+
+ if (qmode != MTL_QUEUE_AVB)
+ txqen = 0x2;
+ else
+ txqen = 0x1;
+
+ value = u32_replace_bits(value, txqen, XGMAC_TXQEN);
+ value = u32_replace_bits(value, tqs, XGMAC_TQS);
+
+ writel(value, ioaddr + XGMAC_MTL_TXQ_OPMODE(channel));
+}
+
+static void dwxgmac2_enable_dma_irq(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan,
+ bool rx, bool tx)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+
+ if (rx)
+ value |= XGMAC_DMA_INT_DEFAULT_RX;
+ if (tx)
+ value |= XGMAC_DMA_INT_DEFAULT_TX;
+
+ writel(value, ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+}
+
+static void dwxgmac2_disable_dma_irq(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan,
+ bool rx, bool tx)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+
+ if (rx)
+ value &= ~XGMAC_DMA_INT_DEFAULT_RX;
+ if (tx)
+ value &= ~XGMAC_DMA_INT_DEFAULT_TX;
+
+ writel(value, ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+}
+
+static void dwxgmac2_dma_start_tx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+ value |= XGMAC_TXST;
+ writel(value, ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+
+ value = readl(ioaddr + XGMAC_TX_CONFIG);
+ value |= XGMAC_CONFIG_TE;
+ writel(value, ioaddr + XGMAC_TX_CONFIG);
+}
+
+static void dwxgmac2_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+ value &= ~XGMAC_TXST;
+ writel(value, ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+
+ value = readl(ioaddr + XGMAC_TX_CONFIG);
+ value &= ~XGMAC_CONFIG_TE;
+ writel(value, ioaddr + XGMAC_TX_CONFIG);
+}
+
+static void dwxgmac2_dma_start_rx(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 chan)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+ value |= XGMAC_RXST;
+ writel(value, ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+
+ value = readl(ioaddr + XGMAC_RX_CONFIG);
+ value |= XGMAC_CONFIG_RE;
+ writel(value, ioaddr + XGMAC_RX_CONFIG);
+}
+
+static void dwxgmac2_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+ value &= ~XGMAC_RXST;
+ writel(value, ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+}
+
+static int dwxgmac2_dma_interrupt(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan,
+ u32 dir)
+{
+ struct stmmac_pcpu_stats *stats = this_cpu_ptr(priv->xstats.pcpu_stats);
+ u32 intr_status = readl(ioaddr + XGMAC_DMA_CH_STATUS(chan));
+ u32 intr_en = readl(ioaddr + XGMAC_DMA_CH_INT_EN(chan));
+ int ret = 0;
+
+ if (dir == DMA_DIR_RX)
+ intr_status &= XGMAC_DMA_STATUS_MSK_RX;
+ else if (dir == DMA_DIR_TX)
+ intr_status &= XGMAC_DMA_STATUS_MSK_TX;
+
+ /* ABNORMAL interrupts */
+ if (unlikely(intr_status & XGMAC_AIS)) {
+ if (unlikely(intr_status & XGMAC_RBU)) {
+ x->rx_buf_unav_irq++;
+ ret |= handle_rx;
+ }
+ if (unlikely(intr_status & XGMAC_TPS)) {
+ x->tx_process_stopped_irq++;
+ ret |= tx_hard_error;
+ }
+ if (unlikely(intr_status & XGMAC_FBE)) {
+ x->fatal_bus_error_irq++;
+ ret |= tx_hard_error;
+ }
+ }
+
+ /* TX/RX NORMAL interrupts */
+ if (likely(intr_status & XGMAC_RI)) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->rx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_rx;
+ }
+ if (likely(intr_status & (XGMAC_TI | XGMAC_TBU))) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->tx_normal_irq_n[chan]);
+ u64_stats_update_end(&stats->syncp);
+ ret |= handle_tx;
+ }
+
+ /* Clear interrupts */
+ writel(intr_en & intr_status, ioaddr + XGMAC_DMA_CH_STATUS(chan));
+
+ return ret;
+}
+
+static int dwxgmac2_get_hw_feature(void __iomem *ioaddr,
+ struct dma_features *dma_cap)
+{
+ struct stmmac_priv *priv;
+ u32 hw_cap;
+
+ priv = container_of(dma_cap, struct stmmac_priv, dma_cap);
+
+ /* MAC HW feature 0 */
+ hw_cap = readl(ioaddr + XGMAC_HW_FEATURE0);
+ dma_cap->edma = (hw_cap & XGMAC_HWFEAT_EDMA) >> 31;
+ dma_cap->ediffc = (hw_cap & XGMAC_HWFEAT_EDIFFC) >> 30;
+ dma_cap->vxn = (hw_cap & XGMAC_HWFEAT_VXN) >> 29;
+ dma_cap->vlins = (hw_cap & XGMAC_HWFEAT_SAVLANINS) >> 27;
+ dma_cap->tssrc = (hw_cap & XGMAC_HWFEAT_TSSTSSEL) >> 25;
+ dma_cap->actphyif = FIELD_GET(XGMAC_HWFEAT_PHYSEL, hw_cap);
+ dma_cap->multi_addr = (hw_cap & XGMAC_HWFEAT_ADDMACADRSEL) >> 18;
+ dma_cap->rx_coe = (hw_cap & XGMAC_HWFEAT_RXCOESEL) >> 16;
+ dma_cap->tx_coe = (hw_cap & XGMAC_HWFEAT_TXCOESEL) >> 14;
+ dma_cap->eee = (hw_cap & XGMAC_HWFEAT_EEESEL) >> 13;
+ dma_cap->atime_stamp = (hw_cap & XGMAC_HWFEAT_TSSEL) >> 12;
+ dma_cap->av = (hw_cap & XGMAC_HWFEAT_AVSEL) >> 11;
+ dma_cap->av &= !((hw_cap & XGMAC_HWFEAT_RAVSEL) >> 10);
+ dma_cap->arpoffsel = (hw_cap & XGMAC_HWFEAT_ARPOFFSEL) >> 9;
+ dma_cap->rmon = (hw_cap & XGMAC_HWFEAT_MMCSEL) >> 8;
+ dma_cap->pmt_magic_frame = (hw_cap & XGMAC_HWFEAT_MGKSEL) >> 7;
+ dma_cap->pmt_remote_wake_up = (hw_cap & XGMAC_HWFEAT_RWKSEL) >> 6;
+ dma_cap->sma_mdio = (hw_cap & XGMAC_HWFEAT_SMASEL) >> 5;
+ dma_cap->vlhash = (hw_cap & XGMAC_HWFEAT_VLHASH) >> 4;
+ dma_cap->half_duplex = (hw_cap & XGMAC_HWFEAT_HDSEL) >> 3;
+ dma_cap->mbps_1000 = (hw_cap & XGMAC_HWFEAT_GMIISEL) >> 1;
+ if (dma_cap->mbps_1000 && priv->synopsys_id >= DWXGMAC_CORE_2_20)
+ dma_cap->mbps_10_100 = 1;
+
+ /* MAC HW feature 1 */
+ hw_cap = readl(ioaddr + XGMAC_HW_FEATURE1);
+ dma_cap->l3l4fnum = (hw_cap & XGMAC_HWFEAT_L3L4FNUM) >> 27;
+ /* If L3L4FNUM < 8, then the number of L3L4 filters supported by
+ * XGMAC is equal to L3L4FNUM. From L3L4FNUM >= 8 the number of
+ * L3L4 filters goes on like 8, 16, 32, ... Current maximum of
+ * L3L4FNUM = 10.
+ */
+ if (dma_cap->l3l4fnum >= 8 && dma_cap->l3l4fnum <= 10)
+ dma_cap->l3l4fnum = 8 << (dma_cap->l3l4fnum - 8);
+ else if (dma_cap->l3l4fnum > 10)
+ dma_cap->l3l4fnum = 32;
+
+ dma_cap->hash_tb_sz = (hw_cap & XGMAC_HWFEAT_HASHTBLSZ) >> 24;
+ dma_cap->numtc = ((hw_cap & XGMAC_HWFEAT_NUMTC) >> 21) + 1;
+ dma_cap->rssen = (hw_cap & XGMAC_HWFEAT_RSSEN) >> 20;
+ dma_cap->dbgmem = (hw_cap & XGMAC_HWFEAT_DBGMEMA) >> 19;
+ dma_cap->tsoen = (hw_cap & XGMAC_HWFEAT_TSOEN) >> 18;
+ dma_cap->sphen = (hw_cap & XGMAC_HWFEAT_SPHEN) >> 17;
+ dma_cap->dcben = (hw_cap & XGMAC_HWFEAT_DCBEN) >> 16;
+
+ dma_cap->addr64 = (hw_cap & XGMAC_HWFEAT_ADDR64) >> 14;
+ switch (dma_cap->addr64) {
+ case 0:
+ dma_cap->addr64 = 32;
+ break;
+ case 1:
+ dma_cap->addr64 = 40;
+ break;
+ case 2:
+ dma_cap->addr64 = 48;
+ break;
+ default:
+ dma_cap->addr64 = 32;
+ break;
+ }
+
+ dma_cap->advthword = (hw_cap & XGMAC_HWFEAT_ADVTHWORD) >> 13;
+ dma_cap->ptoen = (hw_cap & XGMAC_HWFEAT_PTOEN) >> 12;
+ dma_cap->osten = (hw_cap & XGMAC_HWFEAT_OSTEN) >> 11;
+ dma_cap->tx_fifo_size =
+ 128 << ((hw_cap & XGMAC_HWFEAT_TXFIFOSIZE) >> 6);
+ dma_cap->pfcen = (hw_cap & XGMAC_HWFEAT_PFCEN) >> 5;
+ dma_cap->rx_fifo_size =
+ 128 << ((hw_cap & XGMAC_HWFEAT_RXFIFOSIZE) >> 0);
+
+ /* MAC HW feature 2 */
+ hw_cap = readl(ioaddr + XGMAC_HW_FEATURE2);
+ dma_cap->aux_snapshot_n = (hw_cap & XGMAC_HWFEAT_AUXSNAPNUM) >> 28;
+ dma_cap->pps_out_num = (hw_cap & XGMAC_HWFEAT_PPSOUTNUM) >> 24;
+ dma_cap->number_tx_channel =
+ ((hw_cap & XGMAC_HWFEAT_TXCHCNT) >> 18) + 1;
+ dma_cap->number_rx_channel =
+ ((hw_cap & XGMAC_HWFEAT_RXCHCNT) >> 12) + 1;
+ dma_cap->number_tx_queues =
+ ((hw_cap & XGMAC_HWFEAT_TXQCNT) >> 6) + 1;
+ dma_cap->number_rx_queues =
+ ((hw_cap & XGMAC_HWFEAT_RXQCNT) >> 0) + 1;
+
+ /* MAC HW feature 3 */
+ hw_cap = readl(ioaddr + XGMAC_HW_FEATURE3);
+ dma_cap->tbs_ch_num = ((hw_cap & XGMAC_HWFEAT_TBSCH) >> 28) + 1;
+ dma_cap->tbssel = (hw_cap & XGMAC_HWFEAT_TBSSEL) >> 27;
+ dma_cap->fpesel = (hw_cap & XGMAC_HWFEAT_FPESEL) >> 26;
+ dma_cap->sgfsel = (hw_cap & XGMAC_HWFEAT_SGFSEL) >> 25;
+ dma_cap->estwid = (hw_cap & XGMAC_HWFEAT_ESTWID) >> 23;
+ dma_cap->estdep = (hw_cap & XGMAC_HWFEAT_ESTDEP) >> 20;
+ dma_cap->estsel = (hw_cap & XGMAC_HWFEAT_ESTSEL) >> 19;
+ dma_cap->ttsfd = (hw_cap & XGMAC_HWFEAT_TTSFD) >> 16;
+ dma_cap->asp = (hw_cap & XGMAC_HWFEAT_ASP) >> 14;
+ dma_cap->dvlan = (hw_cap & XGMAC_HWFEAT_DVLAN) >> 13;
+ dma_cap->frpes = (hw_cap & XGMAC_HWFEAT_FRPES) >> 11;
+ dma_cap->frpbs = (hw_cap & XGMAC_HWFEAT_FRPPB) >> 9;
+ dma_cap->pou_ost_en = (hw_cap & XGMAC_HWFEAT_POUOST) >> 8;
+ dma_cap->frppipe_num = ((hw_cap & XGMAC_HWFEAT_FRPPIPE) >> 5) + 1;
+ dma_cap->cbtisel = (hw_cap & XGMAC_HWFEAT_CBTISEL) >> 4;
+ dma_cap->frpsel = (hw_cap & XGMAC_HWFEAT_FRPSEL) >> 3;
+ dma_cap->nrvf_num = (hw_cap & XGMAC_HWFEAT_NRVF) >> 0;
+
+ /* MAC HW feature 4 */
+ hw_cap = readl(ioaddr + XGMAC_HW_FEATURE4);
+ dma_cap->asp |= (hw_cap & XGMAC_HWFEAT_EASP) >> 2;
+ dma_cap->pcsel = (hw_cap & XGMAC_HWFEAT_PCSEL) >> 0;
+
+ return 0;
+}
+
+static void dwxgmac2_rx_watchdog(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 riwt, u32 queue)
+{
+ writel(riwt & XGMAC_RWT, ioaddr + XGMAC_DMA_CH_Rx_WATCHDOG(queue));
+}
+
+static void dwxgmac2_set_rx_ring_len(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 len, u32 chan)
+{
+ writel(len, ioaddr + XGMAC_DMA_CH_RxDESC_RING_LEN(chan));
+}
+
+static void dwxgmac2_set_tx_ring_len(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 len, u32 chan)
+{
+ writel(len, ioaddr + XGMAC_DMA_CH_TxDESC_RING_LEN(chan));
+}
+
+static void dwxgmac2_set_rx_tail_ptr(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 ptr, u32 chan)
+{
+ writel(ptr, ioaddr + XGMAC_DMA_CH_RxDESC_TAIL_LPTR(chan));
+}
+
+static void dwxgmac2_set_tx_tail_ptr(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 ptr, u32 chan)
+{
+ writel(ptr, ioaddr + XGMAC_DMA_CH_TxDESC_TAIL_LPTR(chan));
+}
+
+static void dwxgmac2_enable_tso(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+
+ if (en)
+ value |= XGMAC_TSE;
+ else
+ value &= ~XGMAC_TSE;
+
+ writel(value, ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+}
+
+static void dwxgmac2_qmode(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 channel, u8 qmode)
+{
+ u32 value = readl(ioaddr + XGMAC_MTL_TXQ_OPMODE(channel));
+ u32 flow = readl(ioaddr + XGMAC_RX_FLOW_CTRL);
+ unsigned int txqen;
+
+ if (qmode != MTL_QUEUE_AVB) {
+ txqen = 0x2;
+ writel(0, ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(channel));
+ } else {
+ txqen = 0x1;
+ writel(flow & (~XGMAC_RFE), ioaddr + XGMAC_RX_FLOW_CTRL);
+ }
+
+ value = u32_replace_bits(value, txqen, XGMAC_TXQEN);
+ writel(value, ioaddr + XGMAC_MTL_TXQ_OPMODE(channel));
+}
+
+static void dwxgmac2_set_bfsize(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int bfsize, u32 chan)
+{
+ u32 value;
+
+ value = readl(ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+ value = u32_replace_bits(value, bfsize, XGMAC_RBSZ);
+ writel(value, ioaddr + XGMAC_DMA_CH_RX_CONTROL(chan));
+}
+
+static void dwxgmac2_enable_sph(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan)
+{
+ u32 value = readl(ioaddr + XGMAC_RX_CONFIG);
+
+ value &= ~XGMAC_CONFIG_HDSMS;
+ value |= XGMAC_CONFIG_HDSMS_256; /* Segment max 256 bytes */
+ writel(value, ioaddr + XGMAC_RX_CONFIG);
+
+ value = readl(ioaddr + XGMAC_DMA_CH_CONTROL(chan));
+ if (en)
+ value |= XGMAC_SPH;
+ else
+ value &= ~XGMAC_SPH;
+ writel(value, ioaddr + XGMAC_DMA_CH_CONTROL(chan));
+}
+
+static int dwxgmac2_enable_tbs(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan)
+{
+ u32 value = readl(ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+
+ if (en)
+ value |= XGMAC_EDSE;
+ else
+ value &= ~XGMAC_EDSE;
+
+ writel(value, ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan));
+
+ value = readl(ioaddr + XGMAC_DMA_CH_TX_CONTROL(chan)) & XGMAC_EDSE;
+ if (en && !value)
+ return -EIO;
+
+ writel(XGMAC_DEF_FTOS, ioaddr + XGMAC_DMA_TBS_CTRL0);
+ writel(XGMAC_DEF_FTOS, ioaddr + XGMAC_DMA_TBS_CTRL1);
+ writel(XGMAC_DEF_FTOS, ioaddr + XGMAC_DMA_TBS_CTRL2);
+ writel(XGMAC_DEF_FTOS, ioaddr + XGMAC_DMA_TBS_CTRL3);
+ return 0;
+}
+
+const struct stmmac_dma_ops dwxgmac210_dma_ops = {
+ .reset = dwxgmac2_dma_reset,
+ .init = dwxgmac2_dma_init,
+ .init_chan = dwxgmac2_dma_init_chan,
+ .init_rx_chan = dwxgmac2_dma_init_rx_chan,
+ .init_tx_chan = dwxgmac2_dma_init_tx_chan,
+ .axi = dwxgmac2_dma_axi,
+ .dump_regs = dwxgmac2_dma_dump_regs,
+ .dma_rx_mode = dwxgmac2_dma_rx_mode,
+ .dma_tx_mode = dwxgmac2_dma_tx_mode,
+ .enable_dma_irq = dwxgmac2_enable_dma_irq,
+ .disable_dma_irq = dwxgmac2_disable_dma_irq,
+ .start_tx = dwxgmac2_dma_start_tx,
+ .stop_tx = dwxgmac2_dma_stop_tx,
+ .start_rx = dwxgmac2_dma_start_rx,
+ .stop_rx = dwxgmac2_dma_stop_rx,
+ .dma_interrupt = dwxgmac2_dma_interrupt,
+ .get_hw_feature = dwxgmac2_get_hw_feature,
+ .rx_watchdog = dwxgmac2_rx_watchdog,
+ .set_rx_ring_len = dwxgmac2_set_rx_ring_len,
+ .set_tx_ring_len = dwxgmac2_set_tx_ring_len,
+ .set_rx_tail_ptr = dwxgmac2_set_rx_tail_ptr,
+ .set_tx_tail_ptr = dwxgmac2_set_tx_tail_ptr,
+ .enable_tso = dwxgmac2_enable_tso,
+ .qmode = dwxgmac2_qmode,
+ .set_bfsize = dwxgmac2_set_bfsize,
+ .enable_sph = dwxgmac2_enable_sph,
+ .enable_tbs = dwxgmac2_enable_tbs,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxlgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxlgmac2.h
new file mode 100644
index 000000000..726090d49
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxlgmac2.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare XLGMAC definitions.
+ */
+
+#ifndef __STMMAC_DWXLGMAC2_H__
+#define __STMMAC_DWXLGMAC2_H__
+
+/* MAC Registers */
+#define XLGMAC_CONFIG_SS GENMASK(30, 28)
+#define XLGMAC_CONFIG_SS_SHIFT 28
+#define XLGMAC_CONFIG_SS_40G (0x0 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_CONFIG_SS_25G (0x1 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_CONFIG_SS_50G (0x2 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_CONFIG_SS_100G (0x3 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_CONFIG_SS_10G (0x4 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_CONFIG_SS_2500 (0x6 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_CONFIG_SS_1000 (0x7 << XLGMAC_CONFIG_SS_SHIFT)
+#define XLGMAC_RXQ_ENABLE_CTRL0 0x00000140
+
+#endif /* __STMMAC_DWXLGMAC2_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c
new file mode 100644
index 000000000..051253601
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This contains the functions to handle the enhanced descriptors.
+
+ Copyright (C) 2007-2014 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/stmmac.h>
+#include "common.h"
+#include "descs_com.h"
+
+static int enh_desc_get_tx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p, void __iomem *ioaddr)
+{
+ u32 tdes0 = le32_to_cpu(p->des0);
+ int ret = tx_done;
+
+ /* Get tx owner first */
+ if (unlikely(tdes0 & ETDES0_OWN))
+ return tx_dma_own;
+
+ /* Verify tx error by looking at the last segment. */
+ if (likely(!(tdes0 & ETDES0_LAST_SEGMENT)))
+ return tx_not_ls;
+
+ if (unlikely(tdes0 & ETDES0_ERROR_SUMMARY)) {
+ if (unlikely(tdes0 & ETDES0_JABBER_TIMEOUT))
+ x->tx_jabber++;
+
+ if (unlikely(tdes0 & ETDES0_FRAME_FLUSHED)) {
+ x->tx_frame_flushed++;
+ dwmac_dma_flush_tx_fifo(ioaddr);
+ }
+
+ if (unlikely(tdes0 & ETDES0_LOSS_CARRIER)) {
+ x->tx_losscarrier++;
+ }
+ if (unlikely(tdes0 & ETDES0_NO_CARRIER)) {
+ x->tx_carrier++;
+ }
+ if (unlikely((tdes0 & ETDES0_LATE_COLLISION) ||
+ (tdes0 & ETDES0_EXCESSIVE_COLLISIONS)))
+ x->tx_collision +=
+ FIELD_GET(ETDES0_COLLISION_COUNT_MASK, tdes0);
+
+ if (unlikely(tdes0 & ETDES0_EXCESSIVE_DEFERRAL))
+ x->tx_deferred++;
+
+ if (unlikely(tdes0 & ETDES0_UNDERFLOW_ERROR)) {
+ dwmac_dma_flush_tx_fifo(ioaddr);
+ x->tx_underflow++;
+ }
+
+ if (unlikely(tdes0 & ETDES0_IP_HEADER_ERROR))
+ x->tx_ip_header_error++;
+
+ if (unlikely(tdes0 & ETDES0_PAYLOAD_ERROR)) {
+ x->tx_payload_error++;
+ dwmac_dma_flush_tx_fifo(ioaddr);
+ }
+
+ ret = tx_err;
+ }
+
+ if (unlikely(tdes0 & ETDES0_DEFERRED))
+ x->tx_deferred++;
+
+#ifdef STMMAC_VLAN_TAG_USED
+ if (tdes0 & ETDES0_VLAN_FRAME)
+ x->tx_vlan++;
+#endif
+
+ return ret;
+}
+
+static int enh_desc_coe_rdes0(int ipc_err, int type, int payload_err)
+{
+ int ret = good_frame;
+ u32 status = (type << 2 | ipc_err << 1 | payload_err) & 0x7;
+
+ /* bits 5 7 0 | Frame status
+ * ----------------------------------------------------------
+ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octets)
+ * 1 0 0 | IPv4/6 No CSUM errorS.
+ * 1 0 1 | IPv4/6 CSUM PAYLOAD error
+ * 1 1 0 | IPv4/6 CSUM IP HR error
+ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS
+ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD
+ * 0 1 1 | COE bypassed.. no IPv4/6 frame
+ * 0 1 0 | Reserved.
+ */
+ if (status == 0x0)
+ ret = llc_snap;
+ else if (status == 0x4)
+ ret = good_frame;
+ else if (status == 0x5)
+ ret = csum_none;
+ else if (status == 0x6)
+ ret = csum_none;
+ else if (status == 0x7)
+ ret = csum_none;
+ else if (status == 0x1)
+ ret = discard_frame;
+ else if (status == 0x3)
+ ret = discard_frame;
+ return ret;
+}
+
+static void enh_desc_get_ext_status(struct stmmac_extra_stats *x,
+ struct dma_extended_desc *p)
+{
+ u32 rdes0 = le32_to_cpu(p->basic.des0);
+ u32 rdes4 = le32_to_cpu(p->des4);
+
+ if (unlikely(rdes0 & ERDES0_RX_MAC_ADDR)) {
+ int message_type = FIELD_GET(ERDES4_MSG_TYPE_MASK, rdes4);
+
+ if (rdes4 & ERDES4_IP_HDR_ERR)
+ x->ip_hdr_err++;
+ if (rdes4 & ERDES4_IP_PAYLOAD_ERR)
+ x->ip_payload_err++;
+ if (rdes4 & ERDES4_IP_CSUM_BYPASSED)
+ x->ip_csum_bypassed++;
+ if (rdes4 & ERDES4_IPV4_PKT_RCVD)
+ x->ipv4_pkt_rcvd++;
+ if (rdes4 & ERDES4_IPV6_PKT_RCVD)
+ x->ipv6_pkt_rcvd++;
+
+ if (message_type == RDES_EXT_NO_PTP)
+ x->no_ptp_rx_msg_type_ext++;
+ else if (message_type == RDES_EXT_SYNC)
+ x->ptp_rx_msg_type_sync++;
+ else if (message_type == RDES_EXT_FOLLOW_UP)
+ x->ptp_rx_msg_type_follow_up++;
+ else if (message_type == RDES_EXT_DELAY_REQ)
+ x->ptp_rx_msg_type_delay_req++;
+ else if (message_type == RDES_EXT_DELAY_RESP)
+ x->ptp_rx_msg_type_delay_resp++;
+ else if (message_type == RDES_EXT_PDELAY_REQ)
+ x->ptp_rx_msg_type_pdelay_req++;
+ else if (message_type == RDES_EXT_PDELAY_RESP)
+ x->ptp_rx_msg_type_pdelay_resp++;
+ else if (message_type == RDES_EXT_PDELAY_FOLLOW_UP)
+ x->ptp_rx_msg_type_pdelay_follow_up++;
+ else if (message_type == RDES_PTP_ANNOUNCE)
+ x->ptp_rx_msg_type_announce++;
+ else if (message_type == RDES_PTP_MANAGEMENT)
+ x->ptp_rx_msg_type_management++;
+ else if (message_type == RDES_PTP_PKT_RESERVED_TYPE)
+ x->ptp_rx_msg_pkt_reserved_type++;
+
+ if (rdes4 & ERDES4_PTP_FRAME_TYPE)
+ x->ptp_frame_type++;
+ if (rdes4 & ERDES4_PTP_VER)
+ x->ptp_ver++;
+ if (rdes4 & ERDES4_TIMESTAMP_DROPPED)
+ x->timestamp_dropped++;
+ if (rdes4 & ERDES4_AV_PKT_RCVD)
+ x->av_pkt_rcvd++;
+ if (rdes4 & ERDES4_AV_TAGGED_PKT_RCVD)
+ x->av_tagged_pkt_rcvd++;
+ if (rdes4 & ERDES4_VLAN_TAG_PRI_VAL_MASK)
+ x->vlan_tag_priority_val++;
+ if (rdes4 & ERDES4_L3_FILTER_MATCH)
+ x->l3_filter_match++;
+ if (rdes4 & ERDES4_L4_FILTER_MATCH)
+ x->l4_filter_match++;
+ if (rdes4 & ERDES4_L3_L4_FILT_NO_MATCH_MASK)
+ x->l3_l4_filter_no_match++;
+ }
+}
+
+static int enh_desc_get_rx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p)
+{
+ u32 rdes0 = le32_to_cpu(p->des0);
+ int ret = good_frame;
+
+ if (unlikely(rdes0 & RDES0_OWN))
+ return dma_own;
+
+ if (unlikely(!(rdes0 & RDES0_LAST_DESCRIPTOR))) {
+ x->rx_length++;
+ return discard_frame;
+ }
+
+ if (unlikely(rdes0 & RDES0_ERROR_SUMMARY)) {
+ if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR)) {
+ x->rx_desc++;
+ x->rx_length++;
+ }
+ if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR))
+ x->rx_gmac_overflow++;
+
+ if (unlikely(rdes0 & RDES0_IPC_CSUM_ERROR))
+ pr_err("\tIPC Csum Error/Giant frame\n");
+
+ if (unlikely(rdes0 & RDES0_COLLISION))
+ x->rx_collision++;
+ if (unlikely(rdes0 & RDES0_RECEIVE_WATCHDOG))
+ x->rx_watchdog++;
+
+ if (unlikely(rdes0 & RDES0_MII_ERROR)) /* GMII */
+ x->rx_mii++;
+
+ if (unlikely(rdes0 & RDES0_CRC_ERROR)) {
+ x->rx_crc_errors++;
+ }
+ ret = discard_frame;
+ }
+
+ /* After a payload csum error, the ES bit is set.
+ * It doesn't match with the information reported into the databook.
+ * At any rate, we need to understand if the CSUM hw computation is ok
+ * and report this info to the upper layers. */
+ if (likely(ret == good_frame))
+ ret = enh_desc_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR),
+ !!(rdes0 & RDES0_FRAME_TYPE),
+ !!(rdes0 & ERDES0_RX_MAC_ADDR));
+
+ if (unlikely(rdes0 & RDES0_DRIBBLING))
+ x->dribbling_bit++;
+
+ if (unlikely(rdes0 & RDES0_SA_FILTER_FAIL)) {
+ x->sa_rx_filter_fail++;
+ ret = discard_frame;
+ }
+ if (unlikely(rdes0 & RDES0_DA_FILTER_FAIL)) {
+ x->da_rx_filter_fail++;
+ ret = discard_frame;
+ }
+ if (unlikely(rdes0 & RDES0_LENGTH_ERROR)) {
+ x->rx_length++;
+ ret = discard_frame;
+ }
+#ifdef STMMAC_VLAN_TAG_USED
+ if (rdes0 & RDES0_VLAN_TAG)
+ x->rx_vlan++;
+#endif
+
+ return ret;
+}
+
+static void enh_desc_init_rx_desc(struct dma_desc *p, int disable_rx_ic,
+ u8 descriptor_mode, int end, int bfsize)
+{
+ int bfsize1;
+
+ p->des0 |= cpu_to_le32(RDES0_OWN);
+
+ bfsize1 = min(bfsize, BUF_SIZE_8KiB);
+ p->des1 |= cpu_to_le32(bfsize1 & ERDES1_BUFFER1_SIZE_MASK);
+
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ ehn_desc_rx_set_on_chain(p);
+ else
+ ehn_desc_rx_set_on_ring(p, end, bfsize);
+
+ if (disable_rx_ic)
+ p->des1 |= cpu_to_le32(ERDES1_DISABLE_IC);
+}
+
+static void enh_desc_init_tx_desc(struct dma_desc *p, u8 descriptor_mode,
+ int end)
+{
+ p->des0 &= cpu_to_le32(~ETDES0_OWN);
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ enh_desc_end_tx_desc_on_chain(p);
+ else
+ enh_desc_end_tx_desc_on_ring(p, end);
+}
+
+static void enh_desc_set_tx_owner(struct dma_desc *p)
+{
+ p->des0 |= cpu_to_le32(ETDES0_OWN);
+}
+
+static void enh_desc_set_rx_owner(struct dma_desc *p, int disable_rx_ic)
+{
+ p->des0 |= cpu_to_le32(RDES0_OWN);
+}
+
+static void enh_desc_release_tx_desc(struct dma_desc *p, u8 descriptor_mode)
+{
+ int ter = (le32_to_cpu(p->des0) & ETDES0_END_RING) >> 21;
+
+ memset(p, 0, offsetof(struct dma_desc, des2));
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ enh_desc_end_tx_desc_on_chain(p);
+ else
+ enh_desc_end_tx_desc_on_ring(p, ter);
+}
+
+static void enh_desc_prepare_tx_desc(struct dma_desc *p, int is_fs, int len,
+ bool csum_flag, u8 descriptor_mode,
+ bool tx_own, bool ls,
+ unsigned int tot_pkt_len)
+{
+ u32 tdes0 = le32_to_cpu(p->des0);
+
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ enh_set_tx_desc_len_on_chain(p, len);
+ else
+ enh_set_tx_desc_len_on_ring(p, len);
+
+ if (is_fs)
+ tdes0 |= ETDES0_FIRST_SEGMENT;
+ else
+ tdes0 &= ~ETDES0_FIRST_SEGMENT;
+
+ tdes0 = u32_replace_bits(tdes0, csum_flag ? TX_CIC_FULL : 0,
+ ETDES0_CHECKSUM_INSERTION_MASK);
+
+ if (ls)
+ tdes0 |= ETDES0_LAST_SEGMENT;
+
+ /* Finally set the OWN bit. Later the DMA will start! */
+ if (tx_own)
+ tdes0 |= ETDES0_OWN;
+
+ if (is_fs && tx_own)
+ /* When the own bit, for the first frame, has to be set, all
+ * descriptors for the same frame has to be set before, to
+ * avoid race condition.
+ */
+ dma_wmb();
+
+ p->des0 = cpu_to_le32(tdes0);
+}
+
+static void enh_desc_set_tx_ic(struct dma_desc *p)
+{
+ p->des0 |= cpu_to_le32(ETDES0_INTERRUPT);
+}
+
+static int enh_desc_get_rx_frame_len(struct dma_desc *p, int rx_coe_type)
+{
+ unsigned int csum = 0;
+ /* The type-1 checksum offload engines append the checksum at
+ * the end of frame and the two bytes of checksum are added in
+ * the length.
+ * Adjust for that in the framelen for type-1 checksum offload
+ * engines.
+ */
+ if (rx_coe_type == STMMAC_RX_COE_TYPE1)
+ csum = 2;
+
+ return FIELD_GET(RDES0_FRAME_LEN_MASK, le32_to_cpu(p->des0)) - csum;
+}
+
+static void enh_desc_enable_tx_timestamp(struct dma_desc *p)
+{
+ p->des0 |= cpu_to_le32(ETDES0_TIME_STAMP_ENABLE);
+}
+
+static int enh_desc_get_tx_timestamp_status(struct dma_desc *p)
+{
+ return (le32_to_cpu(p->des0) & ETDES0_TIME_STAMP_STATUS) >> 17;
+}
+
+static void enh_desc_get_timestamp(void *desc, u32 ats, u64 *ts)
+{
+ u64 ns;
+
+ if (ats) {
+ struct dma_extended_desc *p = (struct dma_extended_desc *)desc;
+ ns = le32_to_cpu(p->des6);
+ /* convert high/sec time stamp value to nanosecond */
+ ns += le32_to_cpu(p->des7) * 1000000000ULL;
+ } else {
+ struct dma_desc *p = (struct dma_desc *)desc;
+ ns = le32_to_cpu(p->des2);
+ ns += le32_to_cpu(p->des3) * 1000000000ULL;
+ }
+
+ *ts = ns;
+}
+
+static int enh_desc_get_rx_timestamp_status(void *desc, void *next_desc,
+ u32 ats)
+{
+ if (ats) {
+ struct dma_extended_desc *p = (struct dma_extended_desc *)desc;
+ return (le32_to_cpu(p->basic.des0) & RDES0_IPC_CSUM_ERROR) >> 7;
+ } else {
+ struct dma_desc *p = (struct dma_desc *)desc;
+ if ((le32_to_cpu(p->des2) == 0xffffffff) &&
+ (le32_to_cpu(p->des3) == 0xffffffff))
+ /* timestamp is corrupted, hence don't store it */
+ return 0;
+ else
+ return 1;
+ }
+}
+
+static void enh_desc_display_ring(void *head, unsigned int size, bool rx,
+ dma_addr_t dma_rx_phy, unsigned int desc_size)
+{
+ struct dma_extended_desc *ep = (struct dma_extended_desc *)head;
+ dma_addr_t dma_addr;
+ int i;
+
+ pr_info("Extended %s descriptor ring:\n", rx ? "RX" : "TX");
+
+ for (i = 0; i < size; i++) {
+ u64 x;
+ dma_addr = dma_rx_phy + i * sizeof(*ep);
+
+ x = *(u64 *)ep;
+ pr_info("%03d [%pad]: 0x%x 0x%x 0x%x 0x%x\n",
+ i, &dma_addr,
+ (unsigned int)x, (unsigned int)(x >> 32),
+ ep->basic.des2, ep->basic.des3);
+ ep++;
+ }
+ pr_info("\n");
+}
+
+static void enh_desc_set_addr(struct dma_desc *p, dma_addr_t addr)
+{
+ p->des2 = cpu_to_le32(addr);
+}
+
+static void enh_desc_clear(struct dma_desc *p)
+{
+ p->des2 = 0;
+}
+
+const struct stmmac_desc_ops enh_desc_ops = {
+ .tx_status = enh_desc_get_tx_status,
+ .rx_status = enh_desc_get_rx_status,
+ .init_rx_desc = enh_desc_init_rx_desc,
+ .init_tx_desc = enh_desc_init_tx_desc,
+ .release_tx_desc = enh_desc_release_tx_desc,
+ .prepare_tx_desc = enh_desc_prepare_tx_desc,
+ .set_tx_ic = enh_desc_set_tx_ic,
+ .set_tx_owner = enh_desc_set_tx_owner,
+ .set_rx_owner = enh_desc_set_rx_owner,
+ .get_rx_frame_len = enh_desc_get_rx_frame_len,
+ .rx_extended_status = enh_desc_get_ext_status,
+ .enable_tx_timestamp = enh_desc_enable_tx_timestamp,
+ .get_tx_timestamp_status = enh_desc_get_tx_timestamp_status,
+ .get_timestamp = enh_desc_get_timestamp,
+ .get_rx_timestamp_status = enh_desc_get_rx_timestamp_status,
+ .display_ring = enh_desc_display_ring,
+ .set_addr = enh_desc_set_addr,
+ .clear = enh_desc_clear,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c
new file mode 100644
index 000000000..511b0fd5e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * stmmac HW Interface Handling
+ */
+
+#include "common.h"
+#include "stmmac.h"
+#include "stmmac_fpe.h"
+#include "stmmac_ptp.h"
+#include "stmmac_est.h"
+#include "stmmac_vlan.h"
+#include "dwmac4_descs.h"
+#include "dwxgmac2.h"
+
+struct stmmac_version {
+ u8 snpsver;
+ u8 dev_id;
+};
+
+static void stmmac_get_version(struct stmmac_priv *priv,
+ struct stmmac_version *ver)
+{
+ enum dwmac_core_type core_type = priv->plat->core_type;
+ unsigned int version_offset;
+ u32 version;
+
+ ver->snpsver = 0;
+ ver->dev_id = 0;
+
+ if (core_type == DWMAC_CORE_MAC100)
+ return;
+
+ if (core_type == DWMAC_CORE_GMAC)
+ version_offset = GMAC_VERSION;
+ else
+ version_offset = GMAC4_VERSION;
+
+ version = readl(priv->ioaddr + version_offset);
+ if (version == 0) {
+ dev_info(priv->device, "Version ID not available\n");
+ return;
+ }
+
+ dev_info(priv->device, "User ID: 0x%x, Synopsys ID: 0x%x\n",
+ FIELD_GET(DWMAC_USERVER, version),
+ FIELD_GET(DWMAC_SNPSVER, version));
+
+ ver->snpsver = FIELD_GET(DWMAC_SNPSVER, version);
+ if (core_type == DWMAC_CORE_XGMAC)
+ ver->dev_id = FIELD_GET(DWMAC_USERVER, version);
+}
+
+static void stmmac_dwmac_mode_quirk(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ if (priv->chain_mode) {
+ dev_info(priv->device, "Chain mode enabled\n");
+ priv->descriptor_mode = STMMAC_CHAIN_MODE;
+ mac->mode = &chain_mode_ops;
+ } else {
+ dev_info(priv->device, "Ring mode enabled\n");
+ priv->descriptor_mode = STMMAC_RING_MODE;
+ mac->mode = &ring_mode_ops;
+ }
+}
+
+static int stmmac_dwmac1_quirks(struct stmmac_priv *priv)
+{
+ struct mac_device_info *mac = priv->hw;
+
+ if (priv->plat->enh_desc) {
+ dev_info(priv->device, "Enhanced/Alternate descriptors\n");
+
+ /* GMAC older than 3.50 has no extended descriptors */
+ if (priv->synopsys_id >= DWMAC_CORE_3_50) {
+ dev_info(priv->device, "Enabled extended descriptors\n");
+ priv->extend_desc = true;
+ } else {
+ dev_warn(priv->device, "Extended descriptors not supported\n");
+ }
+
+ mac->desc = &enh_desc_ops;
+ } else {
+ dev_info(priv->device, "Normal descriptors\n");
+ mac->desc = &ndesc_ops;
+ }
+
+ stmmac_dwmac_mode_quirk(priv);
+ return 0;
+}
+
+static int stmmac_dwmac4_quirks(struct stmmac_priv *priv)
+{
+ stmmac_dwmac_mode_quirk(priv);
+ return 0;
+}
+
+int stmmac_reset(struct stmmac_priv *priv)
+{
+ struct plat_stmmacenet_data *plat = priv->plat;
+ void __iomem *ioaddr = priv->ioaddr;
+
+ if (plat && plat->fix_soc_reset)
+ return plat->fix_soc_reset(priv);
+
+ return stmmac_do_callback(priv, dma, reset, ioaddr);
+}
+
+static const struct stmmac_hwif_entry {
+ enum dwmac_core_type core_type;
+ u32 min_id;
+ u32 dev_id;
+ const struct stmmac_regs_off regs;
+ const void *desc;
+ const void *dma;
+ const void *mac;
+ const void *hwtimestamp;
+ const void *ptp;
+ const void *mode;
+ const void *tc;
+ const void *mmc;
+ const void *est;
+ const void *vlan;
+ int (*setup)(struct stmmac_priv *priv);
+ int (*quirks)(struct stmmac_priv *priv);
+} stmmac_hw[] = {
+ /* NOTE: New HW versions shall go to the end of this table */
+ {
+ .core_type = DWMAC_CORE_MAC100,
+ .min_id = 0,
+ .regs = {
+ .ptp_off = PTP_GMAC3_X_OFFSET,
+ .mmc_off = MMC_GMAC3_X_OFFSET,
+ },
+ .desc = NULL,
+ .dma = &dwmac100_dma_ops,
+ .mac = &dwmac100_ops,
+ .hwtimestamp = &dwmac1000_ptp,
+ .ptp = &dwmac1000_ptp_clock_ops,
+ .mode = NULL,
+ .tc = NULL,
+ .mmc = &dwmac_mmc_ops,
+ .setup = dwmac100_setup,
+ .quirks = stmmac_dwmac1_quirks,
+ }, {
+ .core_type = DWMAC_CORE_GMAC,
+ .min_id = 0,
+ .regs = {
+ .ptp_off = PTP_GMAC3_X_OFFSET,
+ .mmc_off = MMC_GMAC3_X_OFFSET,
+ },
+ .desc = NULL,
+ .dma = &dwmac1000_dma_ops,
+ .mac = &dwmac1000_ops,
+ .hwtimestamp = &dwmac1000_ptp,
+ .ptp = &dwmac1000_ptp_clock_ops,
+ .mode = NULL,
+ .tc = NULL,
+ .mmc = &dwmac_mmc_ops,
+ .setup = dwmac1000_setup,
+ .quirks = stmmac_dwmac1_quirks,
+ }, {
+ .core_type = DWMAC_CORE_GMAC4,
+ .min_id = 0,
+ .regs = {
+ .ptp_off = PTP_GMAC4_OFFSET,
+ .mmc_off = MMC_GMAC4_OFFSET,
+ .est_off = EST_GMAC4_OFFSET,
+ },
+ .desc = &dwmac4_desc_ops,
+ .dma = &dwmac4_dma_ops,
+ .mac = &dwmac4_ops,
+ .vlan = &dwmac_vlan_ops,
+ .hwtimestamp = &stmmac_ptp,
+ .ptp = &stmmac_ptp_clock_ops,
+ .mode = NULL,
+ .tc = &dwmac4_tc_ops,
+ .mmc = &dwmac_mmc_ops,
+ .est = &dwmac510_est_ops,
+ .setup = dwmac4_setup,
+ .quirks = stmmac_dwmac4_quirks,
+ }, {
+ .core_type = DWMAC_CORE_GMAC4,
+ .min_id = DWMAC_CORE_4_00,
+ .regs = {
+ .ptp_off = PTP_GMAC4_OFFSET,
+ .mmc_off = MMC_GMAC4_OFFSET,
+ .est_off = EST_GMAC4_OFFSET,
+ .fpe_reg = &dwmac5_fpe_reg,
+ },
+ .desc = &dwmac4_desc_ops,
+ .dma = &dwmac4_dma_ops,
+ .mac = &dwmac410_ops,
+ .vlan = &dwmac_vlan_ops,
+ .hwtimestamp = &stmmac_ptp,
+ .ptp = &stmmac_ptp_clock_ops,
+ .mode = &dwmac4_ring_mode_ops,
+ .tc = &dwmac510_tc_ops,
+ .mmc = &dwmac_mmc_ops,
+ .est = &dwmac510_est_ops,
+ .setup = dwmac4_setup,
+ .quirks = NULL,
+ }, {
+ .core_type = DWMAC_CORE_GMAC4,
+ .min_id = DWMAC_CORE_4_10,
+ .regs = {
+ .ptp_off = PTP_GMAC4_OFFSET,
+ .mmc_off = MMC_GMAC4_OFFSET,
+ .est_off = EST_GMAC4_OFFSET,
+ .fpe_reg = &dwmac5_fpe_reg,
+ },
+ .desc = &dwmac4_desc_ops,
+ .dma = &dwmac410_dma_ops,
+ .mac = &dwmac410_ops,
+ .vlan = &dwmac_vlan_ops,
+ .hwtimestamp = &stmmac_ptp,
+ .ptp = &stmmac_ptp_clock_ops,
+ .mode = &dwmac4_ring_mode_ops,
+ .tc = &dwmac510_tc_ops,
+ .mmc = &dwmac_mmc_ops,
+ .est = &dwmac510_est_ops,
+ .setup = dwmac4_setup,
+ .quirks = NULL,
+ }, {
+ .core_type = DWMAC_CORE_GMAC4,
+ .min_id = DWMAC_CORE_5_10,
+ .regs = {
+ .ptp_off = PTP_GMAC4_OFFSET,
+ .mmc_off = MMC_GMAC4_OFFSET,
+ .est_off = EST_GMAC4_OFFSET,
+ .fpe_reg = &dwmac5_fpe_reg,
+ },
+ .desc = &dwmac4_desc_ops,
+ .dma = &dwmac410_dma_ops,
+ .mac = &dwmac510_ops,
+ .vlan = &dwmac_vlan_ops,
+ .hwtimestamp = &stmmac_ptp,
+ .ptp = &stmmac_ptp_clock_ops,
+ .mode = &dwmac4_ring_mode_ops,
+ .tc = &dwmac510_tc_ops,
+ .mmc = &dwmac_mmc_ops,
+ .est = &dwmac510_est_ops,
+ .setup = dwmac4_setup,
+ .quirks = NULL,
+ }, {
+ .core_type = DWMAC_CORE_XGMAC,
+ .min_id = DWXGMAC_CORE_2_10,
+ .dev_id = DWXGMAC_ID,
+ .regs = {
+ .ptp_off = PTP_XGMAC_OFFSET,
+ .mmc_off = MMC_XGMAC_OFFSET,
+ .est_off = EST_XGMAC_OFFSET,
+ .fpe_reg = &dwxgmac3_fpe_reg,
+ },
+ .desc = &dwxgmac210_desc_ops,
+ .dma = &dwxgmac210_dma_ops,
+ .mac = &dwxgmac210_ops,
+ .vlan = &dwxgmac210_vlan_ops,
+ .hwtimestamp = &stmmac_ptp,
+ .ptp = &stmmac_ptp_clock_ops,
+ .mode = NULL,
+ .tc = &dwmac510_tc_ops,
+ .mmc = &dwxgmac_mmc_ops,
+ .est = &dwmac510_est_ops,
+ .setup = dwxgmac2_setup,
+ .quirks = NULL,
+ }, {
+ .core_type = DWMAC_CORE_XGMAC,
+ .min_id = DWXLGMAC_CORE_2_00,
+ .dev_id = DWXLGMAC_ID,
+ .regs = {
+ .ptp_off = PTP_XGMAC_OFFSET,
+ .mmc_off = MMC_XGMAC_OFFSET,
+ .est_off = EST_XGMAC_OFFSET,
+ .fpe_reg = &dwxgmac3_fpe_reg,
+ },
+ .desc = &dwxgmac210_desc_ops,
+ .dma = &dwxgmac210_dma_ops,
+ .mac = &dwxlgmac2_ops,
+ .vlan = &dwxlgmac2_vlan_ops,
+ .hwtimestamp = &stmmac_ptp,
+ .ptp = &stmmac_ptp_clock_ops,
+ .mode = NULL,
+ .tc = &dwmac510_tc_ops,
+ .mmc = &dwxgmac_mmc_ops,
+ .est = &dwmac510_est_ops,
+ .setup = dwxlgmac2_setup,
+ },
+};
+
+static const struct stmmac_hwif_entry *
+stmmac_hwif_find(enum dwmac_core_type core_type, u8 snpsver, u8 dev_id)
+{
+ const struct stmmac_hwif_entry *entry;
+ int i;
+
+ for (i = ARRAY_SIZE(stmmac_hw) - 1; i >= 0; i--) {
+ entry = &stmmac_hw[i];
+
+ if (core_type != entry->core_type)
+ continue;
+ /* Use synopsys_id var because some setups can override this */
+ if (snpsver < entry->min_id)
+ continue;
+ if (core_type == DWMAC_CORE_XGMAC &&
+ dev_id != entry->dev_id)
+ continue;
+
+ return entry;
+ }
+
+ return NULL;
+}
+
+int stmmac_hwif_init(struct stmmac_priv *priv)
+{
+ enum dwmac_core_type core_type = priv->plat->core_type;
+ const struct stmmac_hwif_entry *entry;
+ struct stmmac_version version;
+ struct mac_device_info *mac;
+ bool needs_setup = true;
+ int ret;
+
+ stmmac_get_version(priv, &version);
+
+ /* Save ID for later use */
+ priv->synopsys_id = version.snpsver;
+
+ /* Lets assume some safe values first */
+ if (core_type == DWMAC_CORE_GMAC4) {
+ priv->ptpaddr = priv->ioaddr + PTP_GMAC4_OFFSET;
+ priv->mmcaddr = priv->ioaddr + MMC_GMAC4_OFFSET;
+ priv->estaddr = priv->ioaddr + EST_GMAC4_OFFSET;
+ } else {
+ priv->ptpaddr = priv->ioaddr + PTP_GMAC3_X_OFFSET;
+ priv->mmcaddr = priv->ioaddr + MMC_GMAC3_X_OFFSET;
+ if (core_type == DWMAC_CORE_XGMAC)
+ priv->estaddr = priv->ioaddr + EST_XGMAC_OFFSET;
+ }
+
+ mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL);
+ if (!mac)
+ return -ENOMEM;
+
+ /* Check for HW specific setup first */
+ if (priv->plat->mac_setup) {
+ ret = priv->plat->mac_setup(priv, mac);
+ if (ret)
+ return ret;
+
+ needs_setup = false;
+ }
+
+ spin_lock_init(&mac->irq_ctrl_lock);
+
+ /* Fallback to generic HW */
+
+ /* Use synopsys_id var because some setups can override this */
+ entry = stmmac_hwif_find(core_type, priv->synopsys_id, version.dev_id);
+ if (!entry) {
+ dev_err(priv->device,
+ "Failed to find HW IF (id=0x%x, gmac=%d/%d)\n",
+ version.snpsver, core_type == DWMAC_CORE_GMAC,
+ core_type == DWMAC_CORE_GMAC4);
+
+ return -EINVAL;
+ }
+
+ /* Only use generic HW helpers if needed */
+ mac->desc = mac->desc ? : entry->desc;
+ mac->dma = mac->dma ? : entry->dma;
+ mac->mac = mac->mac ? : entry->mac;
+ mac->ptp = mac->ptp ? : entry->hwtimestamp;
+ mac->mode = mac->mode ? : entry->mode;
+ mac->tc = mac->tc ? : entry->tc;
+ mac->mmc = mac->mmc ? : entry->mmc;
+ mac->est = mac->est ? : entry->est;
+ mac->vlan = mac->vlan ? : entry->vlan;
+
+ priv->hw = mac;
+ priv->fpe_cfg.reg = entry->regs.fpe_reg;
+ priv->ptpaddr = priv->ioaddr + entry->regs.ptp_off;
+ priv->mmcaddr = priv->ioaddr + entry->regs.mmc_off;
+ memcpy(&priv->ptp_clock_ops, entry->ptp,
+ sizeof(struct ptp_clock_info));
+
+ if (entry->est)
+ priv->estaddr = priv->ioaddr + entry->regs.est_off;
+
+ /* Entry found */
+ if (needs_setup) {
+ ret = entry->setup(priv);
+ if (ret)
+ return ret;
+ }
+
+ /* Save quirks, if needed for posterior use */
+ priv->hwif_quirks = entry->quirks;
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h
new file mode 100644
index 000000000..e6317b94f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h
@@ -0,0 +1,696 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+// Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+// stmmac HW Interface Callbacks
+
+#ifndef __STMMAC_HWIF_H__
+#define __STMMAC_HWIF_H__
+
+#include <linux/netdevice.h>
+#include <linux/stmmac.h>
+#include <net/pkt_cls.h>
+
+#define stmmac_do_void_callback(__priv, __module, __cname, __arg0, __args...) \
+({ \
+ int __result = -EINVAL; \
+ if ((__priv)->hw->__module && (__priv)->hw->__module->__cname) { \
+ (__priv)->hw->__module->__cname((__arg0), ##__args); \
+ __result = 0; \
+ } \
+ __result; \
+})
+#define stmmac_do_callback(__priv, __module, __cname, __arg0, __args...) \
+({ \
+ int __result = -EINVAL; \
+ if ((__priv)->hw->__module && (__priv)->hw->__module->__cname) \
+ __result = (__priv)->hw->__module->__cname((__arg0), ##__args); \
+ __result; \
+})
+
+struct stmmac_extra_stats;
+struct stmmac_priv;
+struct stmmac_safety_stats;
+struct stmmac_fpe_cfg;
+enum stmmac_mpacket_type;
+struct dma_desc;
+struct dma_extended_desc;
+struct dma_edesc;
+
+/* Descriptors helpers */
+struct stmmac_desc_ops {
+ /* DMA RX descriptor ring initialization */
+ void (*init_rx_desc)(struct dma_desc *p, int disable_rx_ic,
+ u8 descriptor_mode, int end, int bfsize);
+ /* DMA TX descriptor ring initialization */
+ void (*init_tx_desc)(struct dma_desc *p, u8 descriptor_mode, int end);
+ /* Invoked by the xmit function to prepare the tx descriptor */
+ void (*prepare_tx_desc)(struct dma_desc *p, int is_fs, int len,
+ bool csum_flag, u8 descriptor_mode, bool tx_own,
+ bool ls, unsigned int tot_pkt_len);
+ void (*prepare_tso_tx_desc)(struct dma_desc *p, int is_fs, int len1,
+ int len2, bool tx_own, bool ls, unsigned int tcphdrlen,
+ unsigned int tcppayloadlen);
+ /* Set/get the owner of the descriptor */
+ void (*set_tx_owner)(struct dma_desc *p);
+ /* Clean the tx descriptor as soon as the tx irq is received */
+ void (*release_tx_desc)(struct dma_desc *p, u8 descriptor_mode);
+ /* Clear interrupt on tx frame completion. When this bit is
+ * set an interrupt happens as soon as the frame is transmitted */
+ void (*set_tx_ic)(struct dma_desc *p);
+ /* Get the tag of the descriptor */
+ u16 (*get_rx_vlan_tci)(struct dma_desc *p);
+ /* Get the valid status of descriptor */
+ bool (*get_rx_vlan_valid)(struct dma_desc *p);
+ /* Return the transmit status looking at the TDES1 */
+ int (*tx_status)(struct stmmac_extra_stats *x,
+ struct dma_desc *p, void __iomem *ioaddr);
+ /* Handle extra events on specific interrupts hw dependent */
+ void (*set_rx_owner)(struct dma_desc *p, int disable_rx_ic);
+ /* Get the receive frame size */
+ int (*get_rx_frame_len)(struct dma_desc *p, int rx_coe_type);
+ /* Return the reception status looking at the RDES1 */
+ int (*rx_status)(struct stmmac_extra_stats *x,
+ struct dma_desc *p);
+ void (*rx_extended_status)(struct stmmac_extra_stats *x,
+ struct dma_extended_desc *p);
+ /* Set tx timestamp enable bit */
+ void (*enable_tx_timestamp) (struct dma_desc *p);
+ /* get tx timestamp status */
+ int (*get_tx_timestamp_status) (struct dma_desc *p);
+ /* get timestamp value */
+ void (*get_timestamp)(void *desc, u32 ats, u64 *ts);
+ /* get rx timestamp status */
+ int (*get_rx_timestamp_status)(void *desc, void *next_desc, u32 ats);
+ /* Display ring */
+ void (*display_ring)(void *head, unsigned int size, bool rx,
+ dma_addr_t dma_rx_phy, unsigned int desc_size);
+ /* set MSS via context descriptor */
+ void (*set_mss)(struct dma_desc *p, unsigned int mss);
+ /* set descriptor skbuff address */
+ void (*set_addr)(struct dma_desc *p, dma_addr_t addr);
+ /* clear descriptor */
+ void (*clear)(struct dma_desc *p);
+ /* RSS */
+ int (*get_rx_hash)(struct dma_desc *p, u32 *hash,
+ enum pkt_hash_types *type);
+ void (*get_rx_header_len)(struct dma_desc *p, unsigned int *len);
+ void (*set_sec_addr)(struct dma_desc *p, dma_addr_t addr, bool buf2_valid);
+ void (*set_sarc)(struct dma_desc *p, u32 sarc_type);
+ void (*set_vlan_tag)(struct dma_desc *p, u16 tag, u16 inner_tag,
+ u32 inner_type);
+ void (*set_vlan)(struct dma_desc *p, u32 type);
+ void (*set_tbs)(struct dma_edesc *p, u32 sec, u32 nsec);
+};
+
+#define stmmac_init_rx_desc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, init_rx_desc, __args)
+#define stmmac_init_tx_desc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, init_tx_desc, __args)
+#define stmmac_prepare_tx_desc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, prepare_tx_desc, __args)
+#define stmmac_prepare_tso_tx_desc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, prepare_tso_tx_desc, __args)
+#define stmmac_set_tx_owner(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_tx_owner, __args)
+#define stmmac_release_tx_desc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, release_tx_desc, __args)
+#define stmmac_set_tx_ic(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_tx_ic, __args)
+#define stmmac_get_rx_vlan_tci(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, get_rx_vlan_tci, __args)
+#define stmmac_get_rx_vlan_valid(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, get_rx_vlan_valid, __args)
+#define stmmac_tx_status(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, tx_status, __args)
+#define stmmac_set_rx_owner(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_rx_owner, __args)
+#define stmmac_get_rx_frame_len(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, get_rx_frame_len, __args)
+#define stmmac_rx_status(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, rx_status, __args)
+#define stmmac_rx_extended_status(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, rx_extended_status, __args)
+#define stmmac_enable_tx_timestamp(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, enable_tx_timestamp, __args)
+#define stmmac_get_tx_timestamp_status(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, get_tx_timestamp_status, __args)
+#define stmmac_get_timestamp(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, get_timestamp, __args)
+#define stmmac_get_rx_timestamp_status(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, get_rx_timestamp_status, __args)
+#define stmmac_display_ring(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, display_ring, __args)
+#define stmmac_set_mss(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_mss, __args)
+#define stmmac_set_desc_addr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_addr, __args)
+#define stmmac_clear_desc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, clear, __args)
+#define stmmac_get_rx_hash(__priv, __args...) \
+ stmmac_do_callback(__priv, desc, get_rx_hash, __args)
+#define stmmac_get_rx_header_len(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, get_rx_header_len, __args)
+#define stmmac_set_desc_sec_addr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_sec_addr, __args)
+#define stmmac_set_desc_sarc(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_sarc, __args)
+#define stmmac_set_desc_vlan_tag(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_vlan_tag, __args)
+#define stmmac_set_desc_vlan(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_vlan, __args)
+#define stmmac_set_desc_tbs(__priv, __args...) \
+ stmmac_do_void_callback(__priv, desc, set_tbs, __args)
+
+struct stmmac_dma_cfg;
+struct dma_features;
+
+/* Specific DMA helpers */
+struct stmmac_dma_ops {
+ /* DMA core initialization */
+ int (*reset)(void __iomem *ioaddr);
+ void (*init)(void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg);
+ void (*init_chan)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, u32 chan);
+ void (*init_rx_chan)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan);
+ void (*init_tx_chan)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan);
+ /* Configure the AXI Bus Mode Register */
+ void (*axi)(void __iomem *ioaddr, struct stmmac_axi *axi);
+ /* Dump DMA registers */
+ void (*dump_regs)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 *reg_space);
+ void (*dma_rx_mode)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int mode, u32 channel,
+ int fifosz, u8 qmode);
+ void (*dma_tx_mode)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int mode, u32 channel, int fifosz, u8 qmode);
+ /* To track extra statistic (if supported) */
+ void (*dma_diagnostic_fr)(struct stmmac_extra_stats *x,
+ void __iomem *ioaddr);
+ void (*enable_dma_transmission)(void __iomem *ioaddr, u32 chan);
+ void (*enable_dma_reception)(void __iomem *ioaddr, u32 chan);
+ void (*enable_dma_irq)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx);
+ void (*disable_dma_irq)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan, bool rx, bool tx);
+ void (*start_tx)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+ void (*stop_tx)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+ void (*start_rx)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+ void (*stop_rx)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 chan);
+ int (*dma_interrupt)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 chan, u32 dir);
+ /* If supported then get the optional core features */
+ int (*get_hw_feature)(void __iomem *ioaddr,
+ struct dma_features *dma_cap);
+ /* Program the HW RX Watchdog */
+ void (*rx_watchdog)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 riwt, u32 queue);
+ void (*set_tx_ring_len)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 len, u32 chan);
+ void (*set_rx_ring_len)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 len, u32 chan);
+ void (*set_rx_tail_ptr)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 tail_ptr, u32 chan);
+ void (*set_tx_tail_ptr)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 tail_ptr, u32 chan);
+ void (*enable_tso)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan);
+ void (*qmode)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ u32 channel, u8 qmode);
+ void (*set_bfsize)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ int bfsize, u32 chan);
+ void (*enable_sph)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan);
+ int (*enable_tbs)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ bool en, u32 chan);
+};
+
+#define stmmac_dma_init(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, init, __args)
+#define stmmac_init_chan(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, init_chan, __priv, __args)
+#define stmmac_init_rx_chan(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, init_rx_chan, __priv, __args)
+#define stmmac_init_tx_chan(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, init_tx_chan, __priv, __args)
+#define stmmac_axi(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, axi, __args)
+#define stmmac_dump_dma_regs(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, dump_regs, __priv, __args)
+#define stmmac_dma_rx_mode(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, dma_rx_mode, __priv, __args)
+#define stmmac_dma_tx_mode(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, dma_tx_mode, __priv, __args)
+#define stmmac_dma_diagnostic_fr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, dma_diagnostic_fr, __args)
+#define stmmac_enable_dma_transmission(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, enable_dma_transmission, __args)
+#define stmmac_enable_dma_reception(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, enable_dma_reception, __args)
+#define stmmac_enable_dma_irq(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, enable_dma_irq, __priv, __args)
+#define stmmac_disable_dma_irq(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, disable_dma_irq, __priv, __args)
+#define stmmac_start_tx(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, start_tx, __priv, __args)
+#define stmmac_stop_tx(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, stop_tx, __priv, __args)
+#define stmmac_start_rx(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, start_rx, __priv, __args)
+#define stmmac_stop_rx(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, stop_rx, __priv, __args)
+#define stmmac_dma_interrupt_status(__priv, __args...) \
+ stmmac_do_callback(__priv, dma, dma_interrupt, __priv, __args)
+#define stmmac_get_hw_feature(__priv, __args...) \
+ stmmac_do_callback(__priv, dma, get_hw_feature, __args)
+#define stmmac_rx_watchdog(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, rx_watchdog, __priv, __args)
+#define stmmac_set_tx_ring_len(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, set_tx_ring_len, __priv, __args)
+#define stmmac_set_rx_ring_len(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, set_rx_ring_len, __priv, __args)
+#define stmmac_set_rx_tail_ptr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, set_rx_tail_ptr, __priv, __args)
+#define stmmac_set_tx_tail_ptr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, set_tx_tail_ptr, __priv, __args)
+#define stmmac_enable_tso(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, enable_tso, __priv, __args)
+#define stmmac_dma_qmode(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, qmode, __priv, __args)
+#define stmmac_set_dma_bfsize(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, set_bfsize, __priv, __args)
+#define stmmac_enable_sph(__priv, __args...) \
+ stmmac_do_void_callback(__priv, dma, enable_sph, __priv, __args)
+#define stmmac_enable_tbs(__priv, __args...) \
+ stmmac_do_callback(__priv, dma, enable_tbs, __priv, __args)
+
+struct mac_device_info;
+struct net_device;
+struct stmmac_tc_entry;
+struct stmmac_pps_cfg;
+struct stmmac_rss;
+struct stmmac_est;
+
+enum stmmac_lpi_mode {
+ STMMAC_LPI_DISABLE,
+ STMMAC_LPI_FORCED,
+ STMMAC_LPI_TIMER,
+};
+
+/* Helpers to program the MAC core */
+struct stmmac_ops {
+ /* Initialise any PCS instances */
+ int (*pcs_init)(struct stmmac_priv *priv);
+ /* MAC core initialization */
+ void (*core_init)(struct mac_device_info *hw, struct net_device *dev);
+ /* Update MAC capabilities */
+ void (*update_caps)(struct stmmac_priv *priv);
+ /* Change the interrupt enable setting. Enable takes precedence. */
+ void (*irq_modify)(struct mac_device_info *hw, u32 disable, u32 enable);
+ /* Enable the MAC RX/TX */
+ void (*set_mac)(void __iomem *ioaddr, bool enable);
+ /* Enable and verify that the IPC module is supported */
+ int (*rx_ipc)(struct mac_device_info *hw);
+ /* Enable RX Queues */
+ void (*rx_queue_enable)(struct mac_device_info *hw, u8 mode, u32 queue);
+ /* RX Queues Priority */
+ void (*rx_queue_prio)(struct mac_device_info *hw, u32 prio, u32 queue);
+ /* TX Queues Priority */
+ void (*tx_queue_prio)(struct mac_device_info *hw, u32 prio, u32 queue);
+ /* RX Queues Routing */
+ void (*rx_queue_routing)(struct mac_device_info *hw, u8 packet,
+ u32 queue);
+ /* Program RX Algorithms */
+ void (*prog_mtl_rx_algorithms)(struct mac_device_info *hw, u32 rx_alg);
+ /* Program TX Algorithms */
+ void (*prog_mtl_tx_algorithms)(struct mac_device_info *hw, u32 tx_alg);
+ /* Set MTL TX queues weight */
+ void (*set_mtl_tx_queue_weight)(struct stmmac_priv *priv,
+ struct mac_device_info *hw,
+ u32 weight, u32 queue);
+ /* RX MTL queue to RX dma mapping */
+ void (*map_mtl_to_dma)(struct mac_device_info *hw, u32 queue, u32 chan);
+ /* Configure AV Algorithm */
+ void (*config_cbs)(struct stmmac_priv *priv, struct mac_device_info *hw,
+ u32 send_slope, u32 idle_slope, u32 high_credit,
+ u32 low_credit, u32 queue);
+ /* Dump MAC registers */
+ void (*dump_regs)(struct mac_device_info *hw, u32 *reg_space);
+ /* Handle extra events on specific interrupts hw dependent */
+ int (*host_irq_status)(struct stmmac_priv *priv,
+ struct stmmac_extra_stats *x);
+ /* Handle MTL interrupts */
+ int (*host_mtl_irq_status)(struct stmmac_priv *priv,
+ struct mac_device_info *hw, u32 chan);
+ /* Multicast filter setting */
+ void (*set_filter)(struct mac_device_info *hw, struct net_device *dev);
+ /* Flow control setting */
+ void (*flow_ctrl)(struct mac_device_info *hw, unsigned int duplex,
+ unsigned int fc, unsigned int pause_time, u8 tx_cnt);
+ /* Set power management mode (e.g. magic frame) */
+ void (*pmt)(struct mac_device_info *hw, unsigned long mode);
+ /* Set/Get Unicast MAC addresses */
+ void (*set_umac_addr)(struct mac_device_info *hw,
+ const unsigned char *addr,
+ unsigned int reg_n);
+ void (*get_umac_addr)(struct mac_device_info *hw, unsigned char *addr,
+ unsigned int reg_n);
+ int (*set_lpi_mode)(struct mac_device_info *hw,
+ enum stmmac_lpi_mode mode,
+ bool en_tx_lpi_clockgating, u32 et);
+ void (*set_eee_timer)(struct mac_device_info *hw, int ls, int tw);
+ void (*set_eee_pls)(struct mac_device_info *hw, int link);
+ void (*debug)(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_extra_stats *x, u32 rx_queues,
+ u32 tx_queues);
+ /* PCS calls */
+ void (*pcs_ctrl_ane)(struct stmmac_priv *priv, bool ane,
+ bool srgmi_ral);
+ /* Safety Features */
+ int (*safety_feat_config)(void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_feature_cfg *safety_cfg);
+ int (*safety_feat_irq_status)(struct net_device *ndev,
+ void __iomem *ioaddr, unsigned int asp,
+ struct stmmac_safety_stats *stats);
+ int (*safety_feat_dump)(struct stmmac_safety_stats *stats,
+ int index, unsigned long *count, const char **desc);
+ /* Flexible RX Parser */
+ int (*rxp_config)(void __iomem *ioaddr, struct stmmac_tc_entry *entries,
+ unsigned int count);
+ /* Flexible PPS */
+ int (*flex_pps_config)(void __iomem *ioaddr, int index,
+ struct stmmac_pps_cfg *cfg, bool enable,
+ u32 sub_second_inc, u32 systime_flags);
+ /* Loopback for selftests */
+ void (*set_mac_loopback)(void __iomem *ioaddr, bool enable);
+ /* RSS */
+ int (*rss_configure)(struct mac_device_info *hw,
+ struct stmmac_rss *cfg, u32 num_rxq);
+ /* TX Timestamp */
+ int (*get_mac_tx_timestamp)(struct mac_device_info *hw, u64 *ts);
+ /* Source Address Insertion / Replacement */
+ void (*sarc_configure)(void __iomem *ioaddr, int val);
+ /* Filtering */
+ int (*config_l3_filter)(struct mac_device_info *hw, u32 filter_no,
+ bool en, bool ipv6, bool sa, bool inv,
+ u32 match);
+ int (*config_l4_filter)(struct mac_device_info *hw, u32 filter_no,
+ bool en, bool udp, bool sa, bool inv,
+ u32 match);
+ void (*set_arp_offload)(struct mac_device_info *hw, bool en, u32 addr);
+ int (*fpe_map_preemption_class)(struct net_device *ndev,
+ struct netlink_ext_ack *extack,
+ u32 pclass);
+};
+
+#define stmmac_mac_pcs_init(__priv) \
+ stmmac_do_callback(__priv, mac, pcs_init, __priv)
+#define stmmac_core_init(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, core_init, __args)
+#define stmmac_mac_update_caps(__priv) \
+ stmmac_do_void_callback(__priv, mac, update_caps, __priv)
+#define stmmac_mac_irq_modify(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, irq_modify, (__priv)->hw, __args)
+#define stmmac_mac_set(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_mac, __args)
+#define stmmac_rx_ipc(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, rx_ipc, __args)
+#define stmmac_rx_queue_enable(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, rx_queue_enable, __args)
+#define stmmac_rx_queue_prio(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, rx_queue_prio, __args)
+#define stmmac_tx_queue_prio(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, tx_queue_prio, __args)
+#define stmmac_rx_queue_routing(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, rx_queue_routing, __args)
+#define stmmac_prog_mtl_rx_algorithms(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, prog_mtl_rx_algorithms, __args)
+#define stmmac_prog_mtl_tx_algorithms(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, prog_mtl_tx_algorithms, __args)
+#define stmmac_set_mtl_tx_queue_weight(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_mtl_tx_queue_weight, __priv, __args)
+#define stmmac_map_mtl_to_dma(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, map_mtl_to_dma, __args)
+#define stmmac_config_cbs(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, config_cbs, __priv, __args)
+#define stmmac_dump_mac_regs(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, dump_regs, __args)
+#define stmmac_host_irq_status(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, host_irq_status, __priv, __args)
+#define stmmac_host_mtl_irq_status(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, host_mtl_irq_status, __priv, __args)
+#define stmmac_set_filter(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_filter, __args)
+#define stmmac_flow_ctrl(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, flow_ctrl, __args)
+#define stmmac_pmt(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, pmt, __args)
+#define stmmac_set_umac_addr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_umac_addr, __args)
+#define stmmac_get_umac_addr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, get_umac_addr, __args)
+#define stmmac_set_lpi_mode(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, set_lpi_mode, __args)
+#define stmmac_set_eee_timer(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_eee_timer, __args)
+#define stmmac_set_eee_pls(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_eee_pls, __args)
+#define stmmac_mac_debug(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, debug, __priv, __args)
+#define stmmac_pcs_ctrl_ane(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, pcs_ctrl_ane, __priv, __args)
+#define stmmac_safety_feat_config(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, safety_feat_config, __args)
+#define stmmac_safety_feat_irq_status(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, safety_feat_irq_status, __args)
+#define stmmac_safety_feat_dump(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, safety_feat_dump, __args)
+#define stmmac_rxp_config(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, rxp_config, __args)
+#define stmmac_flex_pps_config(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, flex_pps_config, __args)
+#define stmmac_set_mac_loopback(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_mac_loopback, __args)
+#define stmmac_rss_configure(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, rss_configure, __args)
+#define stmmac_get_mac_tx_timestamp(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, get_mac_tx_timestamp, __args)
+#define stmmac_sarc_configure(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, sarc_configure, __args)
+#define stmmac_config_l3_filter(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, config_l3_filter, __args)
+#define stmmac_config_l4_filter(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, config_l4_filter, __args)
+#define stmmac_set_arp_offload(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, set_arp_offload, __args)
+#define stmmac_fpe_map_preemption_class(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, fpe_map_preemption_class, __args)
+
+/* PTP and HW Timer helpers */
+struct stmmac_hwtimestamp {
+ void (*config_hw_tstamping) (void __iomem *ioaddr, u32 data);
+ void (*config_sub_second_increment)(void __iomem *ioaddr, u32 ptp_clock,
+ int gmac4, u32 *ssinc);
+ int (*init_systime) (void __iomem *ioaddr, u32 sec, u32 nsec);
+ int (*config_addend) (void __iomem *ioaddr, u32 addend);
+ int (*adjust_systime) (void __iomem *ioaddr, u32 sec, u32 nsec,
+ int add_sub, int gmac4);
+ void (*get_systime) (void __iomem *ioaddr, u64 *systime);
+ void (*get_ptptime)(void __iomem *ioaddr, u64 *ptp_time);
+ void (*timestamp_interrupt)(struct stmmac_priv *priv);
+ void (*hwtstamp_correct_latency)(struct stmmac_priv *priv);
+};
+
+#define stmmac_config_hw_tstamping(__priv, __args...) \
+ stmmac_do_void_callback(__priv, ptp, config_hw_tstamping, __args)
+#define stmmac_config_sub_second_increment(__priv, __args...) \
+ stmmac_do_void_callback(__priv, ptp, config_sub_second_increment, __args)
+#define stmmac_init_systime(__priv, __args...) \
+ stmmac_do_callback(__priv, ptp, init_systime, __args)
+#define stmmac_config_addend(__priv, __args...) \
+ stmmac_do_callback(__priv, ptp, config_addend, __args)
+#define stmmac_adjust_systime(__priv, __args...) \
+ stmmac_do_callback(__priv, ptp, adjust_systime, __args)
+#define stmmac_get_systime(__priv, __args...) \
+ stmmac_do_void_callback(__priv, ptp, get_systime, __args)
+#define stmmac_get_ptptime(__priv, __args...) \
+ stmmac_do_void_callback(__priv, ptp, get_ptptime, __args)
+#define stmmac_timestamp_interrupt(__priv, __args...) \
+ stmmac_do_void_callback(__priv, ptp, timestamp_interrupt, __args)
+#define stmmac_hwtstamp_correct_latency(__priv, __args...) \
+ stmmac_do_void_callback(__priv, ptp, hwtstamp_correct_latency, __args)
+
+struct stmmac_tx_queue;
+struct stmmac_rx_queue;
+
+/* Helpers to manage the descriptors for chain and ring modes */
+struct stmmac_mode_ops {
+ void (*init) (void *des, dma_addr_t phy_addr, unsigned int size,
+ unsigned int extend_desc);
+ bool (*is_jumbo_frm)(unsigned int len, bool enh_desc);
+ int (*jumbo_frm)(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
+ int csum);
+ int (*set_16kib_bfsize)(int mtu);
+ void (*init_desc3)(struct dma_desc *p);
+ void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p);
+ void (*clean_desc3)(struct stmmac_tx_queue *tx_q, struct dma_desc *p);
+};
+
+#define stmmac_mode_init(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mode, init, __args)
+#define stmmac_is_jumbo_frm(__priv, __args...) \
+ stmmac_do_callback(__priv, mode, is_jumbo_frm, __args)
+#define stmmac_jumbo_frm(__priv, __args...) \
+ stmmac_do_callback(__priv, mode, jumbo_frm, __args)
+#define stmmac_set_16kib_bfsize(__priv, __args...) \
+ stmmac_do_callback(__priv, mode, set_16kib_bfsize, __args)
+#define stmmac_init_desc3(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mode, init_desc3, __args)
+#define stmmac_refill_desc3(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mode, refill_desc3, __args)
+#define stmmac_clean_desc3(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mode, clean_desc3, __args)
+
+struct tc_cls_u32_offload;
+struct tc_cbs_qopt_offload;
+struct flow_cls_offload;
+struct tc_taprio_qopt_offload;
+struct tc_etf_qopt_offload;
+struct tc_query_caps_base;
+
+struct stmmac_tc_ops {
+ int (*init)(struct stmmac_priv *priv);
+ int (*setup_cls_u32)(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls);
+ int (*setup_cbs)(struct stmmac_priv *priv,
+ struct tc_cbs_qopt_offload *qopt);
+ int (*setup_cls)(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls);
+ int (*setup_taprio)(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt);
+ int (*setup_etf)(struct stmmac_priv *priv,
+ struct tc_etf_qopt_offload *qopt);
+ int (*query_caps)(struct stmmac_priv *priv,
+ struct tc_query_caps_base *base);
+ int (*setup_mqprio)(struct stmmac_priv *priv,
+ struct tc_mqprio_qopt_offload *qopt);
+};
+
+#define stmmac_tc_init(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, init, __args)
+#define stmmac_tc_setup_cls_u32(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, setup_cls_u32, __args)
+#define stmmac_tc_setup_cbs(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, setup_cbs, __args)
+#define stmmac_tc_setup_cls(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, setup_cls, __args)
+#define stmmac_tc_setup_taprio(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, setup_taprio, __args)
+#define stmmac_tc_setup_etf(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, setup_etf, __args)
+#define stmmac_tc_query_caps(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, query_caps, __args)
+#define stmmac_tc_setup_mqprio(__priv, __args...) \
+ stmmac_do_callback(__priv, tc, setup_mqprio, __args)
+
+struct stmmac_counters;
+
+struct stmmac_mmc_ops {
+ void (*ctrl)(void __iomem *ioaddr, unsigned int mode);
+ void (*intr_all_mask)(void __iomem *ioaddr);
+ void (*read)(void __iomem *ioaddr, struct stmmac_counters *mmc);
+};
+
+#define stmmac_mmc_ctrl(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mmc, ctrl, __args)
+#define stmmac_mmc_intr_all_mask(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mmc, intr_all_mask, __args)
+#define stmmac_mmc_read(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mmc, read, __args)
+
+struct stmmac_est_ops {
+ int (*configure)(struct stmmac_priv *priv, struct stmmac_est *cfg,
+ unsigned int ptp_rate);
+ void (*irq_status)(struct stmmac_priv *priv, struct net_device *dev,
+ struct stmmac_extra_stats *x, u32 txqcnt);
+};
+
+#define stmmac_est_configure(__priv, __args...) \
+ stmmac_do_callback(__priv, est, configure, __args)
+#define stmmac_est_irq_status(__priv, __args...) \
+ stmmac_do_void_callback(__priv, est, irq_status, __args)
+
+struct stmmac_vlan_ops {
+ /* VLAN */
+ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash,
+ u16 perfect_match, bool is_double);
+ void (*enable_vlan)(struct mac_device_info *hw, u32 type);
+ void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc,
+ struct sk_buff *skb);
+ void (*set_hw_vlan_mode)(struct mac_device_info *hw);
+ int (*add_hw_vlan_rx_fltr)(struct net_device *dev,
+ struct mac_device_info *hw,
+ __be16 proto, u16 vid);
+ int (*del_hw_vlan_rx_fltr)(struct net_device *dev,
+ struct mac_device_info *hw,
+ __be16 proto, u16 vid);
+ void (*restore_hw_vlan_rx_fltr)(struct net_device *dev,
+ struct mac_device_info *hw);
+};
+
+#define stmmac_update_vlan_hash(__priv, __args...) \
+ stmmac_do_void_callback(__priv, vlan, update_vlan_hash, __args)
+#define stmmac_enable_vlan(__priv, __args...) \
+ stmmac_do_void_callback(__priv, vlan, enable_vlan, __args)
+#define stmmac_rx_hw_vlan(__priv, __args...) \
+ stmmac_do_void_callback(__priv, vlan, rx_hw_vlan, __args)
+#define stmmac_set_hw_vlan_mode(__priv, __args...) \
+ stmmac_do_void_callback(__priv, vlan, set_hw_vlan_mode, __args)
+#define stmmac_add_hw_vlan_rx_fltr(__priv, __args...) \
+ stmmac_do_callback(__priv, vlan, add_hw_vlan_rx_fltr, __args)
+#define stmmac_del_hw_vlan_rx_fltr(__priv, __args...) \
+ stmmac_do_callback(__priv, vlan, del_hw_vlan_rx_fltr, __args)
+#define stmmac_restore_hw_vlan_rx_fltr(__priv, __args...) \
+ stmmac_do_void_callback(__priv, vlan, restore_hw_vlan_rx_fltr, __args)
+
+struct stmmac_regs_off {
+ const struct stmmac_fpe_reg *fpe_reg;
+ u32 ptp_off;
+ u32 mmc_off;
+ u32 est_off;
+};
+
+extern const struct stmmac_desc_ops enh_desc_ops;
+extern const struct stmmac_desc_ops ndesc_ops;
+
+extern const struct stmmac_hwtimestamp stmmac_ptp;
+extern const struct stmmac_hwtimestamp dwmac1000_ptp;
+
+extern const struct stmmac_mode_ops ring_mode_ops;
+extern const struct stmmac_mode_ops chain_mode_ops;
+
+extern const struct stmmac_ops dwmac100_ops;
+extern const struct stmmac_dma_ops dwmac100_dma_ops;
+extern const struct stmmac_ops dwmac1000_ops;
+extern const struct stmmac_dma_ops dwmac1000_dma_ops;
+extern const struct stmmac_ops dwmac4_ops;
+extern const struct stmmac_dma_ops dwmac4_dma_ops;
+extern const struct stmmac_ops dwmac410_ops;
+extern const struct stmmac_dma_ops dwmac410_dma_ops;
+extern const struct stmmac_ops dwmac510_ops;
+extern const struct stmmac_tc_ops dwmac4_tc_ops;
+extern const struct stmmac_tc_ops dwmac510_tc_ops;
+
+#define GMAC_VERSION 0x00000020 /* GMAC CORE Version */
+#define GMAC4_VERSION 0x00000110 /* GMAC4+ CORE Version */
+
+int stmmac_reset(struct stmmac_priv *priv);
+int stmmac_hwif_init(struct stmmac_priv *priv);
+
+#endif /* __STMMAC_HWIF_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/mmc.h b/drivers/net/ethernet/stmicro/stmmac/mmc.h
new file mode 100644
index 000000000..1cba39fb2
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/mmc.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ MMC Header file
+
+ Copyright (C) 2011 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __MMC_H__
+#define __MMC_H__
+
+/* MMC control register */
+/* When set, all counter are reset */
+#define MMC_CNTRL_COUNTER_RESET 0x1
+/* When set, do not roll over zero after reaching the max value*/
+#define MMC_CNTRL_COUNTER_STOP_ROLLOVER 0x2
+#define MMC_CNTRL_RESET_ON_READ 0x4 /* Reset after reading */
+#define MMC_CNTRL_COUNTER_FREEZER 0x8 /* Freeze counter values to the
+ * current value.*/
+#define MMC_CNTRL_PRESET 0x10
+#define MMC_CNTRL_FULL_HALF_PRESET 0x20
+
+#define MMC_GMAC4_OFFSET 0x700
+#define MMC_GMAC3_X_OFFSET 0x100
+#define MMC_XGMAC_OFFSET 0x800
+
+struct stmmac_counters {
+ unsigned int mmc_tx_octetcount_gb;
+ unsigned int mmc_tx_framecount_gb;
+ unsigned int mmc_tx_broadcastframe_g;
+ unsigned int mmc_tx_multicastframe_g;
+ unsigned int mmc_tx_64_octets_gb;
+ unsigned int mmc_tx_65_to_127_octets_gb;
+ unsigned int mmc_tx_128_to_255_octets_gb;
+ unsigned int mmc_tx_256_to_511_octets_gb;
+ unsigned int mmc_tx_512_to_1023_octets_gb;
+ unsigned int mmc_tx_1024_to_max_octets_gb;
+ unsigned int mmc_tx_unicast_gb;
+ unsigned int mmc_tx_multicast_gb;
+ unsigned int mmc_tx_broadcast_gb;
+ unsigned int mmc_tx_underflow_error;
+ unsigned int mmc_tx_singlecol_g;
+ unsigned int mmc_tx_multicol_g;
+ unsigned int mmc_tx_deferred;
+ unsigned int mmc_tx_latecol;
+ unsigned int mmc_tx_exesscol;
+ unsigned int mmc_tx_carrier_error;
+ unsigned int mmc_tx_octetcount_g;
+ unsigned int mmc_tx_framecount_g;
+ unsigned int mmc_tx_excessdef;
+ unsigned int mmc_tx_pause_frame;
+ unsigned int mmc_tx_vlan_frame_g;
+ unsigned int mmc_tx_oversize_g;
+ unsigned int mmc_tx_lpi_usec;
+ unsigned int mmc_tx_lpi_tran;
+
+ /* MMC RX counter registers */
+ unsigned int mmc_rx_framecount_gb;
+ unsigned int mmc_rx_octetcount_gb;
+ unsigned int mmc_rx_octetcount_g;
+ unsigned int mmc_rx_broadcastframe_g;
+ unsigned int mmc_rx_multicastframe_g;
+ unsigned int mmc_rx_crc_error;
+ unsigned int mmc_rx_align_error;
+ unsigned int mmc_rx_run_error;
+ unsigned int mmc_rx_jabber_error;
+ unsigned int mmc_rx_undersize_g;
+ unsigned int mmc_rx_oversize_g;
+ unsigned int mmc_rx_64_octets_gb;
+ unsigned int mmc_rx_65_to_127_octets_gb;
+ unsigned int mmc_rx_128_to_255_octets_gb;
+ unsigned int mmc_rx_256_to_511_octets_gb;
+ unsigned int mmc_rx_512_to_1023_octets_gb;
+ unsigned int mmc_rx_1024_to_max_octets_gb;
+ unsigned int mmc_rx_unicast_g;
+ unsigned int mmc_rx_length_error;
+ unsigned int mmc_rx_autofrangetype;
+ unsigned int mmc_rx_pause_frames;
+ unsigned int mmc_rx_fifo_overflow;
+ unsigned int mmc_rx_vlan_frames_gb;
+ unsigned int mmc_rx_watchdog_error;
+ unsigned int mmc_rx_error;
+ unsigned int mmc_rx_lpi_usec;
+ unsigned int mmc_rx_lpi_tran;
+ unsigned int mmc_rx_discard_frames_gb;
+ unsigned int mmc_rx_discard_octets_gb;
+ unsigned int mmc_rx_align_err_frames;
+
+ /* IPv4 */
+ unsigned int mmc_rx_ipv4_gd;
+ unsigned int mmc_rx_ipv4_hderr;
+ unsigned int mmc_rx_ipv4_nopay;
+ unsigned int mmc_rx_ipv4_frag;
+ unsigned int mmc_rx_ipv4_udsbl;
+
+ unsigned int mmc_rx_ipv4_gd_octets;
+ unsigned int mmc_rx_ipv4_hderr_octets;
+ unsigned int mmc_rx_ipv4_nopay_octets;
+ unsigned int mmc_rx_ipv4_frag_octets;
+ unsigned int mmc_rx_ipv4_udsbl_octets;
+
+ /* IPV6 */
+ unsigned int mmc_rx_ipv6_gd_octets;
+ unsigned int mmc_rx_ipv6_hderr_octets;
+ unsigned int mmc_rx_ipv6_nopay_octets;
+
+ unsigned int mmc_rx_ipv6_gd;
+ unsigned int mmc_rx_ipv6_hderr;
+ unsigned int mmc_rx_ipv6_nopay;
+
+ /* Protocols */
+ unsigned int mmc_rx_udp_gd;
+ unsigned int mmc_rx_udp_err;
+ unsigned int mmc_rx_tcp_gd;
+ unsigned int mmc_rx_tcp_err;
+ unsigned int mmc_rx_icmp_gd;
+ unsigned int mmc_rx_icmp_err;
+
+ unsigned int mmc_rx_udp_gd_octets;
+ unsigned int mmc_rx_udp_err_octets;
+ unsigned int mmc_rx_tcp_gd_octets;
+ unsigned int mmc_rx_tcp_err_octets;
+ unsigned int mmc_rx_icmp_gd_octets;
+ unsigned int mmc_rx_icmp_err_octets;
+
+ /* Stream-Gate Filter */
+ unsigned int mmc_sgf_pass_fragment_cntr;
+ unsigned int mmc_sgf_fail_fragment_cntr;
+
+ /* FPE */
+ unsigned int mmc_tx_fpe_fragment_cntr;
+ unsigned int mmc_tx_hold_req_cntr;
+ unsigned int mmc_tx_gate_overrun_cntr;
+ unsigned int mmc_rx_packet_assembly_err_cntr;
+ unsigned int mmc_rx_packet_smd_err_cntr;
+ unsigned int mmc_rx_packet_assembly_ok_cntr;
+ unsigned int mmc_rx_fpe_fragment_cntr;
+};
+
+extern const struct stmmac_mmc_ops dwmac_mmc_ops;
+extern const struct stmmac_mmc_ops dwxgmac_mmc_ops;
+
+#endif /* __MMC_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/mmc_core.c b/drivers/net/ethernet/stmicro/stmmac/mmc_core.c
new file mode 100644
index 000000000..d81581dfa
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/mmc_core.c
@@ -0,0 +1,604 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ DWMAC Management Counters
+
+ Copyright (C) 2011 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include "hwif.h"
+#include "mmc.h"
+
+/* MAC Management Counters register offset */
+
+#define MMC_CNTRL 0x00 /* MMC Control */
+#define MMC_RX_INTR 0x04 /* MMC RX Interrupt */
+#define MMC_TX_INTR 0x08 /* MMC TX Interrupt */
+#define MMC_RX_INTR_MASK 0x0c /* MMC Interrupt Mask */
+#define MMC_TX_INTR_MASK 0x10 /* MMC Interrupt Mask */
+#define MMC_DEFAULT_MASK 0xffffffff
+
+/* MMC TX counter registers */
+
+/* Note:
+ * _GB register stands for good and bad frames
+ * _G is for good only.
+ */
+#define MMC_TX_OCTETCOUNT_GB 0x14
+#define MMC_TX_FRAMECOUNT_GB 0x18
+#define MMC_TX_BROADCASTFRAME_G 0x1c
+#define MMC_TX_MULTICASTFRAME_G 0x20
+#define MMC_TX_64_OCTETS_GB 0x24
+#define MMC_TX_65_TO_127_OCTETS_GB 0x28
+#define MMC_TX_128_TO_255_OCTETS_GB 0x2c
+#define MMC_TX_256_TO_511_OCTETS_GB 0x30
+#define MMC_TX_512_TO_1023_OCTETS_GB 0x34
+#define MMC_TX_1024_TO_MAX_OCTETS_GB 0x38
+#define MMC_TX_UNICAST_GB 0x3c
+#define MMC_TX_MULTICAST_GB 0x40
+#define MMC_TX_BROADCAST_GB 0x44
+#define MMC_TX_UNDERFLOW_ERROR 0x48
+#define MMC_TX_SINGLECOL_G 0x4c
+#define MMC_TX_MULTICOL_G 0x50
+#define MMC_TX_DEFERRED 0x54
+#define MMC_TX_LATECOL 0x58
+#define MMC_TX_EXESSCOL 0x5c
+#define MMC_TX_CARRIER_ERROR 0x60
+#define MMC_TX_OCTETCOUNT_G 0x64
+#define MMC_TX_FRAMECOUNT_G 0x68
+#define MMC_TX_EXCESSDEF 0x6c
+#define MMC_TX_PAUSE_FRAME 0x70
+#define MMC_TX_VLAN_FRAME_G 0x74
+#define MMC_TX_OVERSIZE_G 0x78
+
+/* MMC RX counter registers */
+#define MMC_RX_FRAMECOUNT_GB 0x80
+#define MMC_RX_OCTETCOUNT_GB 0x84
+#define MMC_RX_OCTETCOUNT_G 0x88
+#define MMC_RX_BROADCASTFRAME_G 0x8c
+#define MMC_RX_MULTICASTFRAME_G 0x90
+#define MMC_RX_CRC_ERROR 0x94
+#define MMC_RX_ALIGN_ERROR 0x98
+#define MMC_RX_RUN_ERROR 0x9C
+#define MMC_RX_JABBER_ERROR 0xA0
+#define MMC_RX_UNDERSIZE_G 0xA4
+#define MMC_RX_OVERSIZE_G 0xA8
+#define MMC_RX_64_OCTETS_GB 0xAC
+#define MMC_RX_65_TO_127_OCTETS_GB 0xb0
+#define MMC_RX_128_TO_255_OCTETS_GB 0xb4
+#define MMC_RX_256_TO_511_OCTETS_GB 0xb8
+#define MMC_RX_512_TO_1023_OCTETS_GB 0xbc
+#define MMC_RX_1024_TO_MAX_OCTETS_GB 0xc0
+#define MMC_RX_UNICAST_G 0xc4
+#define MMC_RX_LENGTH_ERROR 0xc8
+#define MMC_RX_AUTOFRANGETYPE 0xcc
+#define MMC_RX_PAUSE_FRAMES 0xd0
+#define MMC_RX_FIFO_OVERFLOW 0xd4
+#define MMC_RX_VLAN_FRAMES_GB 0xd8
+#define MMC_RX_WATCHDOG_ERROR 0xdc
+#define MMC_RX_ERROR 0xe0
+
+#define MMC_TX_LPI_USEC 0xec
+#define MMC_TX_LPI_TRAN 0xf0
+#define MMC_RX_LPI_USEC 0xf4
+#define MMC_RX_LPI_TRAN 0xf8
+
+/* IPC*/
+#define MMC_RX_IPC_INTR_MASK 0x100
+#define MMC_RX_IPC_INTR 0x108
+/* IPv4*/
+#define MMC_RX_IPV4_GD 0x110
+#define MMC_RX_IPV4_HDERR 0x114
+#define MMC_RX_IPV4_NOPAY 0x118
+#define MMC_RX_IPV4_FRAG 0x11C
+#define MMC_RX_IPV4_UDSBL 0x120
+
+#define MMC_RX_IPV4_GD_OCTETS 0x150
+#define MMC_RX_IPV4_HDERR_OCTETS 0x154
+#define MMC_RX_IPV4_NOPAY_OCTETS 0x158
+#define MMC_RX_IPV4_FRAG_OCTETS 0x15c
+#define MMC_RX_IPV4_UDSBL_OCTETS 0x160
+
+/* IPV6*/
+#define MMC_RX_IPV6_GD_OCTETS 0x164
+#define MMC_RX_IPV6_HDERR_OCTETS 0x168
+#define MMC_RX_IPV6_NOPAY_OCTETS 0x16c
+
+#define MMC_RX_IPV6_GD 0x124
+#define MMC_RX_IPV6_HDERR 0x128
+#define MMC_RX_IPV6_NOPAY 0x12c
+
+/* Protocols*/
+#define MMC_RX_UDP_GD 0x130
+#define MMC_RX_UDP_ERR 0x134
+#define MMC_RX_TCP_GD 0x138
+#define MMC_RX_TCP_ERR 0x13c
+#define MMC_RX_ICMP_GD 0x140
+#define MMC_RX_ICMP_ERR 0x144
+
+#define MMC_RX_UDP_GD_OCTETS 0x170
+#define MMC_RX_UDP_ERR_OCTETS 0x174
+#define MMC_RX_TCP_GD_OCTETS 0x178
+#define MMC_RX_TCP_ERR_OCTETS 0x17c
+#define MMC_RX_ICMP_GD_OCTETS 0x180
+#define MMC_RX_ICMP_ERR_OCTETS 0x184
+
+#define MMC_TX_FPE_FRAG 0x1a8
+#define MMC_TX_HOLD_REQ 0x1ac
+#define MMC_RX_PKT_ASSEMBLY_ERR 0x1c8
+#define MMC_RX_PKT_SMD_ERR 0x1cc
+#define MMC_RX_PKT_ASSEMBLY_OK 0x1d0
+#define MMC_RX_FPE_FRAG 0x1d4
+
+/* XGMAC MMC Registers */
+#define MMC_XGMAC_TX_OCTET_GB 0x14
+#define MMC_XGMAC_TX_PKT_GB 0x1c
+#define MMC_XGMAC_TX_BROAD_PKT_G 0x24
+#define MMC_XGMAC_TX_MULTI_PKT_G 0x2c
+#define MMC_XGMAC_TX_64OCT_GB 0x34
+#define MMC_XGMAC_TX_65OCT_GB 0x3c
+#define MMC_XGMAC_TX_128OCT_GB 0x44
+#define MMC_XGMAC_TX_256OCT_GB 0x4c
+#define MMC_XGMAC_TX_512OCT_GB 0x54
+#define MMC_XGMAC_TX_1024OCT_GB 0x5c
+#define MMC_XGMAC_TX_UNI_PKT_GB 0x64
+#define MMC_XGMAC_TX_MULTI_PKT_GB 0x6c
+#define MMC_XGMAC_TX_BROAD_PKT_GB 0x74
+#define MMC_XGMAC_TX_UNDER 0x7c
+#define MMC_XGMAC_TX_OCTET_G 0x84
+#define MMC_XGMAC_TX_PKT_G 0x8c
+#define MMC_XGMAC_TX_PAUSE 0x94
+#define MMC_XGMAC_TX_VLAN_PKT_G 0x9c
+#define MMC_XGMAC_TX_LPI_USEC 0xa4
+#define MMC_XGMAC_TX_LPI_TRAN 0xa8
+
+#define MMC_XGMAC_RX_PKT_GB 0x100
+#define MMC_XGMAC_RX_OCTET_GB 0x108
+#define MMC_XGMAC_RX_OCTET_G 0x110
+#define MMC_XGMAC_RX_BROAD_PKT_G 0x118
+#define MMC_XGMAC_RX_MULTI_PKT_G 0x120
+#define MMC_XGMAC_RX_CRC_ERR 0x128
+#define MMC_XGMAC_RX_RUNT_ERR 0x130
+#define MMC_XGMAC_RX_JABBER_ERR 0x134
+#define MMC_XGMAC_RX_UNDER 0x138
+#define MMC_XGMAC_RX_OVER 0x13c
+#define MMC_XGMAC_RX_64OCT_GB 0x140
+#define MMC_XGMAC_RX_65OCT_GB 0x148
+#define MMC_XGMAC_RX_128OCT_GB 0x150
+#define MMC_XGMAC_RX_256OCT_GB 0x158
+#define MMC_XGMAC_RX_512OCT_GB 0x160
+#define MMC_XGMAC_RX_1024OCT_GB 0x168
+#define MMC_XGMAC_RX_UNI_PKT_G 0x170
+#define MMC_XGMAC_RX_LENGTH_ERR 0x178
+#define MMC_XGMAC_RX_RANGE 0x180
+#define MMC_XGMAC_RX_PAUSE 0x188
+#define MMC_XGMAC_RX_FIFOOVER_PKT 0x190
+#define MMC_XGMAC_RX_VLAN_PKT_GB 0x198
+#define MMC_XGMAC_RX_WATCHDOG_ERR 0x1a0
+#define MMC_XGMAC_RX_LPI_USEC 0x1a4
+#define MMC_XGMAC_RX_LPI_TRAN 0x1a8
+#define MMC_XGMAC_RX_DISCARD_PKT_GB 0x1ac
+#define MMC_XGMAC_RX_DISCARD_OCT_GB 0x1b4
+#define MMC_XGMAC_RX_ALIGN_ERR_PKT 0x1bc
+
+#define MMC_XGMAC_SGF_PASS_PKT 0x1f0
+#define MMC_XGMAC_SGF_FAIL_PKT 0x1f4
+#define MMC_XGMAC_TX_FPE_INTR_MASK 0x204
+#define MMC_XGMAC_TX_FPE_FRAG 0x208
+#define MMC_XGMAC_TX_HOLD_REQ 0x20c
+#define MMC_XGMAC_TX_GATE_OVERRUN 0x210
+#define MMC_XGMAC_RX_FPE_INTR_MASK 0x224
+#define MMC_XGMAC_RX_PKT_ASSEMBLY_ERR 0x228
+#define MMC_XGMAC_RX_PKT_SMD_ERR 0x22c
+#define MMC_XGMAC_RX_PKT_ASSEMBLY_OK 0x230
+#define MMC_XGMAC_RX_FPE_FRAG 0x234
+#define MMC_XGMAC_RX_IPC_INTR_MASK 0x25c
+
+#define MMC_XGMAC_RX_IPV4_GD 0x264
+#define MMC_XGMAC_RX_IPV4_HDERR 0x26c
+#define MMC_XGMAC_RX_IPV4_NOPAY 0x274
+#define MMC_XGMAC_RX_IPV4_FRAG 0x27c
+#define MMC_XGMAC_RX_IPV4_UDSBL 0x284
+
+#define MMC_XGMAC_RX_IPV6_GD 0x28c
+#define MMC_XGMAC_RX_IPV6_HDERR 0x294
+#define MMC_XGMAC_RX_IPV6_NOPAY 0x29c
+
+#define MMC_XGMAC_RX_UDP_GD 0x2a4
+#define MMC_XGMAC_RX_UDP_ERR 0x2ac
+#define MMC_XGMAC_RX_TCP_GD 0x2b4
+#define MMC_XGMAC_RX_TCP_ERR 0x2bc
+#define MMC_XGMAC_RX_ICMP_GD 0x2c4
+#define MMC_XGMAC_RX_ICMP_ERR 0x2cc
+
+#define MMC_XGMAC_RX_IPV4_GD_OCTETS 0x2d4
+#define MMC_XGMAC_RX_IPV4_HDERR_OCTETS 0x2dc
+#define MMC_XGMAC_RX_IPV4_NOPAY_OCTETS 0x2e4
+#define MMC_XGMAC_RX_IPV4_FRAG_OCTETS 0x2ec
+#define MMC_XGMAC_RX_IPV4_UDSBL_OCTETS 0x2f4
+
+#define MMC_XGMAC_RX_IPV6_GD_OCTETS 0x2fc
+#define MMC_XGMAC_RX_IPV6_HDERR_OCTETS 0x304
+#define MMC_XGMAC_RX_IPV6_NOPAY_OCTETS 0x30c
+
+#define MMC_XGMAC_RX_UDP_GD_OCTETS 0x314
+#define MMC_XGMAC_RX_UDP_ERR_OCTETS 0x31c
+#define MMC_XGMAC_RX_TCP_GD_OCTETS 0x324
+#define MMC_XGMAC_RX_TCP_ERR_OCTETS 0x32c
+#define MMC_XGMAC_RX_ICMP_GD_OCTETS 0x334
+#define MMC_XGMAC_RX_ICMP_ERR_OCTETS 0x33c
+
+static void dwmac_mmc_ctrl(void __iomem *mmcaddr, unsigned int mode)
+{
+ u32 value = readl(mmcaddr + MMC_CNTRL);
+
+ value |= (mode & 0x3F);
+
+ writel(value, mmcaddr + MMC_CNTRL);
+
+ pr_debug("stmmac: MMC ctrl register (offset 0x%x): 0x%08x\n",
+ MMC_CNTRL, value);
+}
+
+/* To mask all interrupts.*/
+static void dwmac_mmc_intr_all_mask(void __iomem *mmcaddr)
+{
+ writel(MMC_DEFAULT_MASK, mmcaddr + MMC_RX_INTR_MASK);
+ writel(MMC_DEFAULT_MASK, mmcaddr + MMC_TX_INTR_MASK);
+ writel(MMC_DEFAULT_MASK, mmcaddr + MMC_RX_IPC_INTR_MASK);
+}
+
+/* This reads the MAC core counters (if actually supported).
+ * by default the MMC core is programmed to reset each
+ * counter after a read. So all the field of the mmc struct
+ * have to be incremented.
+ */
+static void dwmac_mmc_read(void __iomem *mmcaddr, struct stmmac_counters *mmc)
+{
+ mmc->mmc_tx_octetcount_gb += readl(mmcaddr + MMC_TX_OCTETCOUNT_GB);
+ mmc->mmc_tx_framecount_gb += readl(mmcaddr + MMC_TX_FRAMECOUNT_GB);
+ mmc->mmc_tx_broadcastframe_g += readl(mmcaddr +
+ MMC_TX_BROADCASTFRAME_G);
+ mmc->mmc_tx_multicastframe_g += readl(mmcaddr +
+ MMC_TX_MULTICASTFRAME_G);
+ mmc->mmc_tx_64_octets_gb += readl(mmcaddr + MMC_TX_64_OCTETS_GB);
+ mmc->mmc_tx_65_to_127_octets_gb +=
+ readl(mmcaddr + MMC_TX_65_TO_127_OCTETS_GB);
+ mmc->mmc_tx_128_to_255_octets_gb +=
+ readl(mmcaddr + MMC_TX_128_TO_255_OCTETS_GB);
+ mmc->mmc_tx_256_to_511_octets_gb +=
+ readl(mmcaddr + MMC_TX_256_TO_511_OCTETS_GB);
+ mmc->mmc_tx_512_to_1023_octets_gb +=
+ readl(mmcaddr + MMC_TX_512_TO_1023_OCTETS_GB);
+ mmc->mmc_tx_1024_to_max_octets_gb +=
+ readl(mmcaddr + MMC_TX_1024_TO_MAX_OCTETS_GB);
+ mmc->mmc_tx_unicast_gb += readl(mmcaddr + MMC_TX_UNICAST_GB);
+ mmc->mmc_tx_multicast_gb += readl(mmcaddr + MMC_TX_MULTICAST_GB);
+ mmc->mmc_tx_broadcast_gb += readl(mmcaddr + MMC_TX_BROADCAST_GB);
+ mmc->mmc_tx_underflow_error += readl(mmcaddr + MMC_TX_UNDERFLOW_ERROR);
+ mmc->mmc_tx_singlecol_g += readl(mmcaddr + MMC_TX_SINGLECOL_G);
+ mmc->mmc_tx_multicol_g += readl(mmcaddr + MMC_TX_MULTICOL_G);
+ mmc->mmc_tx_deferred += readl(mmcaddr + MMC_TX_DEFERRED);
+ mmc->mmc_tx_latecol += readl(mmcaddr + MMC_TX_LATECOL);
+ mmc->mmc_tx_exesscol += readl(mmcaddr + MMC_TX_EXESSCOL);
+ mmc->mmc_tx_carrier_error += readl(mmcaddr + MMC_TX_CARRIER_ERROR);
+ mmc->mmc_tx_octetcount_g += readl(mmcaddr + MMC_TX_OCTETCOUNT_G);
+ mmc->mmc_tx_framecount_g += readl(mmcaddr + MMC_TX_FRAMECOUNT_G);
+ mmc->mmc_tx_excessdef += readl(mmcaddr + MMC_TX_EXCESSDEF);
+ mmc->mmc_tx_pause_frame += readl(mmcaddr + MMC_TX_PAUSE_FRAME);
+ mmc->mmc_tx_vlan_frame_g += readl(mmcaddr + MMC_TX_VLAN_FRAME_G);
+ mmc->mmc_tx_oversize_g += readl(mmcaddr + MMC_TX_OVERSIZE_G);
+ mmc->mmc_tx_lpi_usec += readl(mmcaddr + MMC_TX_LPI_USEC);
+ mmc->mmc_tx_lpi_tran += readl(mmcaddr + MMC_TX_LPI_TRAN);
+
+ /* MMC RX counter registers */
+ mmc->mmc_rx_framecount_gb += readl(mmcaddr + MMC_RX_FRAMECOUNT_GB);
+ mmc->mmc_rx_octetcount_gb += readl(mmcaddr + MMC_RX_OCTETCOUNT_GB);
+ mmc->mmc_rx_octetcount_g += readl(mmcaddr + MMC_RX_OCTETCOUNT_G);
+ mmc->mmc_rx_broadcastframe_g += readl(mmcaddr +
+ MMC_RX_BROADCASTFRAME_G);
+ mmc->mmc_rx_multicastframe_g += readl(mmcaddr +
+ MMC_RX_MULTICASTFRAME_G);
+ mmc->mmc_rx_crc_error += readl(mmcaddr + MMC_RX_CRC_ERROR);
+ mmc->mmc_rx_align_error += readl(mmcaddr + MMC_RX_ALIGN_ERROR);
+ mmc->mmc_rx_run_error += readl(mmcaddr + MMC_RX_RUN_ERROR);
+ mmc->mmc_rx_jabber_error += readl(mmcaddr + MMC_RX_JABBER_ERROR);
+ mmc->mmc_rx_undersize_g += readl(mmcaddr + MMC_RX_UNDERSIZE_G);
+ mmc->mmc_rx_oversize_g += readl(mmcaddr + MMC_RX_OVERSIZE_G);
+ mmc->mmc_rx_64_octets_gb += readl(mmcaddr + MMC_RX_64_OCTETS_GB);
+ mmc->mmc_rx_65_to_127_octets_gb +=
+ readl(mmcaddr + MMC_RX_65_TO_127_OCTETS_GB);
+ mmc->mmc_rx_128_to_255_octets_gb +=
+ readl(mmcaddr + MMC_RX_128_TO_255_OCTETS_GB);
+ mmc->mmc_rx_256_to_511_octets_gb +=
+ readl(mmcaddr + MMC_RX_256_TO_511_OCTETS_GB);
+ mmc->mmc_rx_512_to_1023_octets_gb +=
+ readl(mmcaddr + MMC_RX_512_TO_1023_OCTETS_GB);
+ mmc->mmc_rx_1024_to_max_octets_gb +=
+ readl(mmcaddr + MMC_RX_1024_TO_MAX_OCTETS_GB);
+ mmc->mmc_rx_unicast_g += readl(mmcaddr + MMC_RX_UNICAST_G);
+ mmc->mmc_rx_length_error += readl(mmcaddr + MMC_RX_LENGTH_ERROR);
+ mmc->mmc_rx_autofrangetype += readl(mmcaddr + MMC_RX_AUTOFRANGETYPE);
+ mmc->mmc_rx_pause_frames += readl(mmcaddr + MMC_RX_PAUSE_FRAMES);
+ mmc->mmc_rx_fifo_overflow += readl(mmcaddr + MMC_RX_FIFO_OVERFLOW);
+ mmc->mmc_rx_vlan_frames_gb += readl(mmcaddr + MMC_RX_VLAN_FRAMES_GB);
+ mmc->mmc_rx_watchdog_error += readl(mmcaddr + MMC_RX_WATCHDOG_ERROR);
+ mmc->mmc_rx_error += readl(mmcaddr + MMC_RX_ERROR);
+ mmc->mmc_rx_lpi_usec += readl(mmcaddr + MMC_RX_LPI_USEC);
+ mmc->mmc_rx_lpi_tran += readl(mmcaddr + MMC_RX_LPI_TRAN);
+
+ /* IPv4 */
+ mmc->mmc_rx_ipv4_gd += readl(mmcaddr + MMC_RX_IPV4_GD);
+ mmc->mmc_rx_ipv4_hderr += readl(mmcaddr + MMC_RX_IPV4_HDERR);
+ mmc->mmc_rx_ipv4_nopay += readl(mmcaddr + MMC_RX_IPV4_NOPAY);
+ mmc->mmc_rx_ipv4_frag += readl(mmcaddr + MMC_RX_IPV4_FRAG);
+ mmc->mmc_rx_ipv4_udsbl += readl(mmcaddr + MMC_RX_IPV4_UDSBL);
+
+ mmc->mmc_rx_ipv4_gd_octets += readl(mmcaddr + MMC_RX_IPV4_GD_OCTETS);
+ mmc->mmc_rx_ipv4_hderr_octets +=
+ readl(mmcaddr + MMC_RX_IPV4_HDERR_OCTETS);
+ mmc->mmc_rx_ipv4_nopay_octets +=
+ readl(mmcaddr + MMC_RX_IPV4_NOPAY_OCTETS);
+ mmc->mmc_rx_ipv4_frag_octets += readl(mmcaddr +
+ MMC_RX_IPV4_FRAG_OCTETS);
+ mmc->mmc_rx_ipv4_udsbl_octets +=
+ readl(mmcaddr + MMC_RX_IPV4_UDSBL_OCTETS);
+
+ /* IPV6 */
+ mmc->mmc_rx_ipv6_gd_octets += readl(mmcaddr + MMC_RX_IPV6_GD_OCTETS);
+ mmc->mmc_rx_ipv6_hderr_octets +=
+ readl(mmcaddr + MMC_RX_IPV6_HDERR_OCTETS);
+ mmc->mmc_rx_ipv6_nopay_octets +=
+ readl(mmcaddr + MMC_RX_IPV6_NOPAY_OCTETS);
+
+ mmc->mmc_rx_ipv6_gd += readl(mmcaddr + MMC_RX_IPV6_GD);
+ mmc->mmc_rx_ipv6_hderr += readl(mmcaddr + MMC_RX_IPV6_HDERR);
+ mmc->mmc_rx_ipv6_nopay += readl(mmcaddr + MMC_RX_IPV6_NOPAY);
+
+ /* Protocols */
+ mmc->mmc_rx_udp_gd += readl(mmcaddr + MMC_RX_UDP_GD);
+ mmc->mmc_rx_udp_err += readl(mmcaddr + MMC_RX_UDP_ERR);
+ mmc->mmc_rx_tcp_gd += readl(mmcaddr + MMC_RX_TCP_GD);
+ mmc->mmc_rx_tcp_err += readl(mmcaddr + MMC_RX_TCP_ERR);
+ mmc->mmc_rx_icmp_gd += readl(mmcaddr + MMC_RX_ICMP_GD);
+ mmc->mmc_rx_icmp_err += readl(mmcaddr + MMC_RX_ICMP_ERR);
+
+ mmc->mmc_rx_udp_gd_octets += readl(mmcaddr + MMC_RX_UDP_GD_OCTETS);
+ mmc->mmc_rx_udp_err_octets += readl(mmcaddr + MMC_RX_UDP_ERR_OCTETS);
+ mmc->mmc_rx_tcp_gd_octets += readl(mmcaddr + MMC_RX_TCP_GD_OCTETS);
+ mmc->mmc_rx_tcp_err_octets += readl(mmcaddr + MMC_RX_TCP_ERR_OCTETS);
+ mmc->mmc_rx_icmp_gd_octets += readl(mmcaddr + MMC_RX_ICMP_GD_OCTETS);
+ mmc->mmc_rx_icmp_err_octets += readl(mmcaddr + MMC_RX_ICMP_ERR_OCTETS);
+
+ mmc->mmc_tx_fpe_fragment_cntr += readl(mmcaddr + MMC_TX_FPE_FRAG);
+ mmc->mmc_tx_hold_req_cntr += readl(mmcaddr + MMC_TX_HOLD_REQ);
+ mmc->mmc_rx_packet_assembly_err_cntr +=
+ readl(mmcaddr + MMC_RX_PKT_ASSEMBLY_ERR);
+ mmc->mmc_rx_packet_smd_err_cntr += readl(mmcaddr + MMC_RX_PKT_SMD_ERR);
+ mmc->mmc_rx_packet_assembly_ok_cntr +=
+ readl(mmcaddr + MMC_RX_PKT_ASSEMBLY_OK);
+ mmc->mmc_rx_fpe_fragment_cntr += readl(mmcaddr + MMC_RX_FPE_FRAG);
+}
+
+const struct stmmac_mmc_ops dwmac_mmc_ops = {
+ .ctrl = dwmac_mmc_ctrl,
+ .intr_all_mask = dwmac_mmc_intr_all_mask,
+ .read = dwmac_mmc_read,
+};
+
+static void dwxgmac_mmc_ctrl(void __iomem *mmcaddr, unsigned int mode)
+{
+ u32 value = readl(mmcaddr + MMC_CNTRL);
+
+ value |= (mode & 0x3F);
+
+ writel(value, mmcaddr + MMC_CNTRL);
+}
+
+static void dwxgmac_mmc_intr_all_mask(void __iomem *mmcaddr)
+{
+ writel(0x0, mmcaddr + MMC_RX_INTR_MASK);
+ writel(0x0, mmcaddr + MMC_TX_INTR_MASK);
+ writel(MMC_DEFAULT_MASK, mmcaddr + MMC_XGMAC_TX_FPE_INTR_MASK);
+ writel(MMC_DEFAULT_MASK, mmcaddr + MMC_XGMAC_RX_FPE_INTR_MASK);
+ writel(MMC_DEFAULT_MASK, mmcaddr + MMC_XGMAC_RX_IPC_INTR_MASK);
+}
+
+static void dwxgmac_read_mmc_reg(void __iomem *addr, u32 reg, u32 *dest)
+{
+ u64 tmp = 0;
+
+ tmp += readl(addr + reg);
+ tmp += ((u64 )readl(addr + reg + 0x4)) << 32;
+ if (tmp > GENMASK(31, 0))
+ *dest = ~0x0;
+ else
+ *dest = *dest + tmp;
+}
+
+/* This reads the MAC core counters (if actually supported).
+ * by default the MMC core is programmed to reset each
+ * counter after a read. So all the field of the mmc struct
+ * have to be incremented.
+ */
+static void dwxgmac_mmc_read(void __iomem *mmcaddr, struct stmmac_counters *mmc)
+{
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_OCTET_GB,
+ &mmc->mmc_tx_octetcount_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_PKT_GB,
+ &mmc->mmc_tx_framecount_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_BROAD_PKT_G,
+ &mmc->mmc_tx_broadcastframe_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_MULTI_PKT_G,
+ &mmc->mmc_tx_multicastframe_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_64OCT_GB,
+ &mmc->mmc_tx_64_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_65OCT_GB,
+ &mmc->mmc_tx_65_to_127_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_128OCT_GB,
+ &mmc->mmc_tx_128_to_255_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_256OCT_GB,
+ &mmc->mmc_tx_256_to_511_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_512OCT_GB,
+ &mmc->mmc_tx_512_to_1023_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_1024OCT_GB,
+ &mmc->mmc_tx_1024_to_max_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_UNI_PKT_GB,
+ &mmc->mmc_tx_unicast_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_MULTI_PKT_GB,
+ &mmc->mmc_tx_multicast_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_BROAD_PKT_GB,
+ &mmc->mmc_tx_broadcast_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_UNDER,
+ &mmc->mmc_tx_underflow_error);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_OCTET_G,
+ &mmc->mmc_tx_octetcount_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_PKT_G,
+ &mmc->mmc_tx_framecount_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_PAUSE,
+ &mmc->mmc_tx_pause_frame);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_VLAN_PKT_G,
+ &mmc->mmc_tx_vlan_frame_g);
+ mmc->mmc_tx_lpi_usec += readl(mmcaddr + MMC_XGMAC_TX_LPI_USEC);
+ mmc->mmc_tx_lpi_tran += readl(mmcaddr + MMC_XGMAC_TX_LPI_TRAN);
+
+ /* MMC RX counter registers */
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_PKT_GB,
+ &mmc->mmc_rx_framecount_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_OCTET_GB,
+ &mmc->mmc_rx_octetcount_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_OCTET_G,
+ &mmc->mmc_rx_octetcount_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_BROAD_PKT_G,
+ &mmc->mmc_rx_broadcastframe_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_MULTI_PKT_G,
+ &mmc->mmc_rx_multicastframe_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_CRC_ERR,
+ &mmc->mmc_rx_crc_error);
+ mmc->mmc_rx_run_error += readl(mmcaddr + MMC_XGMAC_RX_RUNT_ERR);
+ mmc->mmc_rx_jabber_error += readl(mmcaddr + MMC_XGMAC_RX_JABBER_ERR);
+ mmc->mmc_rx_undersize_g += readl(mmcaddr + MMC_XGMAC_RX_UNDER);
+ mmc->mmc_rx_oversize_g += readl(mmcaddr + MMC_XGMAC_RX_OVER);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_64OCT_GB,
+ &mmc->mmc_rx_64_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_65OCT_GB,
+ &mmc->mmc_rx_65_to_127_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_128OCT_GB,
+ &mmc->mmc_rx_128_to_255_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_256OCT_GB,
+ &mmc->mmc_rx_256_to_511_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_512OCT_GB,
+ &mmc->mmc_rx_512_to_1023_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_1024OCT_GB,
+ &mmc->mmc_rx_1024_to_max_octets_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_UNI_PKT_G,
+ &mmc->mmc_rx_unicast_g);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_LENGTH_ERR,
+ &mmc->mmc_rx_length_error);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_RANGE,
+ &mmc->mmc_rx_autofrangetype);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_PAUSE,
+ &mmc->mmc_rx_pause_frames);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_FIFOOVER_PKT,
+ &mmc->mmc_rx_fifo_overflow);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_VLAN_PKT_GB,
+ &mmc->mmc_rx_vlan_frames_gb);
+ mmc->mmc_rx_watchdog_error += readl(mmcaddr + MMC_XGMAC_RX_WATCHDOG_ERR);
+ mmc->mmc_rx_lpi_usec += readl(mmcaddr + MMC_XGMAC_RX_LPI_USEC);
+ mmc->mmc_rx_lpi_tran += readl(mmcaddr + MMC_XGMAC_RX_LPI_TRAN);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_DISCARD_PKT_GB,
+ &mmc->mmc_rx_discard_frames_gb);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_DISCARD_OCT_GB,
+ &mmc->mmc_rx_discard_octets_gb);
+ mmc->mmc_rx_align_err_frames +=
+ readl(mmcaddr + MMC_XGMAC_RX_ALIGN_ERR_PKT);
+
+ mmc->mmc_sgf_pass_fragment_cntr +=
+ readl(mmcaddr + MMC_XGMAC_SGF_PASS_PKT);
+ mmc->mmc_sgf_fail_fragment_cntr +=
+ readl(mmcaddr + MMC_XGMAC_SGF_FAIL_PKT);
+ mmc->mmc_tx_fpe_fragment_cntr += readl(mmcaddr + MMC_XGMAC_TX_FPE_FRAG);
+ mmc->mmc_tx_hold_req_cntr += readl(mmcaddr + MMC_XGMAC_TX_HOLD_REQ);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_TX_GATE_OVERRUN,
+ &mmc->mmc_tx_gate_overrun_cntr);
+ mmc->mmc_rx_packet_assembly_err_cntr +=
+ readl(mmcaddr + MMC_XGMAC_RX_PKT_ASSEMBLY_ERR);
+ mmc->mmc_rx_packet_smd_err_cntr +=
+ readl(mmcaddr + MMC_XGMAC_RX_PKT_SMD_ERR);
+ mmc->mmc_rx_packet_assembly_ok_cntr +=
+ readl(mmcaddr + MMC_XGMAC_RX_PKT_ASSEMBLY_OK);
+ mmc->mmc_rx_fpe_fragment_cntr +=
+ readl(mmcaddr + MMC_XGMAC_RX_FPE_FRAG);
+
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_GD,
+ &mmc->mmc_rx_ipv4_gd);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_HDERR,
+ &mmc->mmc_rx_ipv4_hderr);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_NOPAY,
+ &mmc->mmc_rx_ipv4_nopay);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_FRAG,
+ &mmc->mmc_rx_ipv4_frag);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_UDSBL,
+ &mmc->mmc_rx_ipv4_udsbl);
+
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV6_GD,
+ &mmc->mmc_rx_ipv6_gd);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV6_HDERR,
+ &mmc->mmc_rx_ipv6_hderr);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV6_NOPAY,
+ &mmc->mmc_rx_ipv6_nopay);
+
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_UDP_GD,
+ &mmc->mmc_rx_udp_gd);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_UDP_ERR,
+ &mmc->mmc_rx_udp_err);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_TCP_GD,
+ &mmc->mmc_rx_tcp_gd);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_TCP_ERR,
+ &mmc->mmc_rx_tcp_err);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_ICMP_GD,
+ &mmc->mmc_rx_icmp_gd);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_ICMP_ERR,
+ &mmc->mmc_rx_icmp_err);
+
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_GD_OCTETS,
+ &mmc->mmc_rx_ipv4_gd_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_HDERR_OCTETS,
+ &mmc->mmc_rx_ipv4_hderr_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_NOPAY_OCTETS,
+ &mmc->mmc_rx_ipv4_nopay_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_FRAG_OCTETS,
+ &mmc->mmc_rx_ipv4_frag_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV4_UDSBL_OCTETS,
+ &mmc->mmc_rx_ipv4_udsbl_octets);
+
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV6_GD_OCTETS,
+ &mmc->mmc_rx_ipv6_gd_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV6_HDERR_OCTETS,
+ &mmc->mmc_rx_ipv6_hderr_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_IPV6_NOPAY_OCTETS,
+ &mmc->mmc_rx_ipv6_nopay_octets);
+
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_UDP_GD_OCTETS,
+ &mmc->mmc_rx_udp_gd_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_UDP_ERR_OCTETS,
+ &mmc->mmc_rx_udp_err_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_TCP_GD_OCTETS,
+ &mmc->mmc_rx_tcp_gd_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_TCP_ERR_OCTETS,
+ &mmc->mmc_rx_tcp_err_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_ICMP_GD_OCTETS,
+ &mmc->mmc_rx_icmp_gd_octets);
+ dwxgmac_read_mmc_reg(mmcaddr, MMC_XGMAC_RX_ICMP_ERR_OCTETS,
+ &mmc->mmc_rx_icmp_err_octets);
+}
+
+const struct stmmac_mmc_ops dwxgmac_mmc_ops = {
+ .ctrl = dwxgmac_mmc_ctrl,
+ .intr_all_mask = dwxgmac_mmc_intr_all_mask,
+ .read = dwxgmac_mmc_read,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c
new file mode 100644
index 000000000..c4b613564
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This contains the functions to handle the normal descriptors.
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/stmmac.h>
+#include "common.h"
+#include "descs_com.h"
+
+static int ndesc_get_tx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p, void __iomem *ioaddr)
+{
+ u32 tdes0 = le32_to_cpu(p->des0);
+ u32 tdes1 = le32_to_cpu(p->des1);
+ int ret = tx_done;
+
+ /* Get tx owner first */
+ if (unlikely(tdes0 & TDES0_OWN))
+ return tx_dma_own;
+
+ /* Verify tx error by looking at the last segment. */
+ if (likely(!(tdes1 & TDES1_LAST_SEGMENT)))
+ return tx_not_ls;
+
+ if (unlikely(tdes0 & TDES0_ERROR_SUMMARY)) {
+ if (unlikely(tdes0 & TDES0_UNDERFLOW_ERROR)) {
+ x->tx_underflow++;
+ }
+ if (unlikely(tdes0 & TDES0_NO_CARRIER)) {
+ x->tx_carrier++;
+ }
+ if (unlikely(tdes0 & TDES0_LOSS_CARRIER)) {
+ x->tx_losscarrier++;
+ }
+ if (unlikely((tdes0 & TDES0_EXCESSIVE_DEFERRAL) ||
+ (tdes0 & TDES0_EXCESSIVE_COLLISIONS) ||
+ (tdes0 & TDES0_LATE_COLLISION))) {
+ x->tx_collision +=
+ FIELD_GET(TDES0_COLLISION_COUNT_MASK, tdes0);
+ }
+ ret = tx_err;
+ }
+
+ if (tdes0 & TDES0_VLAN_FRAME)
+ x->tx_vlan++;
+
+ if (unlikely(tdes0 & TDES0_DEFERRED))
+ x->tx_deferred++;
+
+ return ret;
+}
+
+/* This function verifies if each incoming frame has some errors
+ * and, if required, updates the multicast statistics.
+ * In case of success, it returns good_frame because the GMAC device
+ * is supposed to be able to compute the csum in HW. */
+static int ndesc_get_rx_status(struct stmmac_extra_stats *x,
+ struct dma_desc *p)
+{
+ u32 rdes0 = le32_to_cpu(p->des0);
+ int ret = good_frame;
+
+ if (unlikely(rdes0 & RDES0_OWN))
+ return dma_own;
+
+ if (unlikely(!(rdes0 & RDES0_LAST_DESCRIPTOR))) {
+ x->rx_length++;
+ return discard_frame;
+ }
+
+ if (unlikely(rdes0 & RDES0_ERROR_SUMMARY)) {
+ if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR))
+ x->rx_desc++;
+ if (unlikely(rdes0 & RDES0_SA_FILTER_FAIL))
+ x->sa_filter_fail++;
+ if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR))
+ x->overflow_error++;
+ if (unlikely(rdes0 & RDES0_IPC_CSUM_ERROR))
+ x->ipc_csum_error++;
+ if (unlikely(rdes0 & RDES0_COLLISION)) {
+ x->rx_collision++;
+ }
+ if (unlikely(rdes0 & RDES0_CRC_ERROR)) {
+ x->rx_crc_errors++;
+ }
+ ret = discard_frame;
+ }
+ if (unlikely(rdes0 & RDES0_DRIBBLING))
+ x->dribbling_bit++;
+
+ if (unlikely(rdes0 & RDES0_LENGTH_ERROR)) {
+ x->rx_length++;
+ ret = discard_frame;
+ }
+ if (unlikely(rdes0 & RDES0_MII_ERROR)) {
+ x->rx_mii++;
+ ret = discard_frame;
+ }
+#ifdef STMMAC_VLAN_TAG_USED
+ if (rdes0 & RDES0_VLAN_TAG)
+ x->vlan_tag++;
+#endif
+ return ret;
+}
+
+static void ndesc_init_rx_desc(struct dma_desc *p, int disable_rx_ic,
+ u8 descriptor_mode, int end, int bfsize)
+{
+ int bfsize1;
+
+ p->des0 |= cpu_to_le32(RDES0_OWN);
+
+ bfsize1 = min(bfsize, BUF_SIZE_2KiB - 1);
+ p->des1 |= cpu_to_le32(bfsize1 & RDES1_BUFFER1_SIZE_MASK);
+
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ ndesc_rx_set_on_chain(p, end);
+ else
+ ndesc_rx_set_on_ring(p, end, bfsize);
+
+ if (disable_rx_ic)
+ p->des1 |= cpu_to_le32(RDES1_DISABLE_IC);
+}
+
+static void ndesc_init_tx_desc(struct dma_desc *p, u8 descriptor_mode, int end)
+{
+ p->des0 &= cpu_to_le32(~TDES0_OWN);
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ ndesc_tx_set_on_chain(p);
+ else
+ ndesc_end_tx_desc_on_ring(p, end);
+}
+
+static void ndesc_set_tx_owner(struct dma_desc *p)
+{
+ p->des0 |= cpu_to_le32(TDES0_OWN);
+}
+
+static void ndesc_set_rx_owner(struct dma_desc *p, int disable_rx_ic)
+{
+ p->des0 |= cpu_to_le32(RDES0_OWN);
+}
+
+static void ndesc_release_tx_desc(struct dma_desc *p, u8 descriptor_mode)
+{
+ int ter = (le32_to_cpu(p->des1) & TDES1_END_RING) >> 25;
+
+ memset(p, 0, offsetof(struct dma_desc, des2));
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ ndesc_tx_set_on_chain(p);
+ else
+ ndesc_end_tx_desc_on_ring(p, ter);
+}
+
+static void ndesc_prepare_tx_desc(struct dma_desc *p, int is_fs, int len,
+ bool csum_flag, u8 descriptor_mode,
+ bool tx_own, bool ls,
+ unsigned int tot_pkt_len)
+{
+ u32 tdes1 = le32_to_cpu(p->des1);
+
+ if (is_fs)
+ tdes1 |= TDES1_FIRST_SEGMENT;
+ else
+ tdes1 &= ~TDES1_FIRST_SEGMENT;
+
+ tdes1 = u32_replace_bits(tdes1, csum_flag ? TX_CIC_FULL : 0,
+ TDES1_CHECKSUM_INSERTION_MASK);
+
+ if (ls)
+ tdes1 |= TDES1_LAST_SEGMENT;
+
+ p->des1 = cpu_to_le32(tdes1);
+
+ if (descriptor_mode == STMMAC_CHAIN_MODE)
+ norm_set_tx_desc_len_on_chain(p, len);
+ else
+ norm_set_tx_desc_len_on_ring(p, len);
+
+ if (tx_own)
+ p->des0 |= cpu_to_le32(TDES0_OWN);
+}
+
+static void ndesc_set_tx_ic(struct dma_desc *p)
+{
+ p->des1 |= cpu_to_le32(TDES1_INTERRUPT);
+}
+
+static int ndesc_get_rx_frame_len(struct dma_desc *p, int rx_coe_type)
+{
+ unsigned int csum = 0;
+
+ /* The type-1 checksum offload engines append the checksum at
+ * the end of frame and the two bytes of checksum are added in
+ * the length.
+ * Adjust for that in the framelen for type-1 checksum offload
+ * engines
+ */
+ if (rx_coe_type == STMMAC_RX_COE_TYPE1)
+ csum = 2;
+
+ return FIELD_GET(RDES0_FRAME_LEN_MASK, le32_to_cpu(p->des0)) - csum;
+}
+
+static void ndesc_enable_tx_timestamp(struct dma_desc *p)
+{
+ p->des1 |= cpu_to_le32(TDES1_TIME_STAMP_ENABLE);
+}
+
+static int ndesc_get_tx_timestamp_status(struct dma_desc *p)
+{
+ return (le32_to_cpu(p->des0) & TDES0_TIME_STAMP_STATUS) >> 17;
+}
+
+static void ndesc_get_timestamp(void *desc, u32 ats, u64 *ts)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+ u64 ns;
+
+ ns = le32_to_cpu(p->des2);
+ /* convert high/sec time stamp value to nanosecond */
+ ns += le32_to_cpu(p->des3) * 1000000000ULL;
+
+ *ts = ns;
+}
+
+static int ndesc_get_rx_timestamp_status(void *desc, void *next_desc, u32 ats)
+{
+ struct dma_desc *p = (struct dma_desc *)desc;
+
+ if ((le32_to_cpu(p->des2) == 0xffffffff) &&
+ (le32_to_cpu(p->des3) == 0xffffffff))
+ /* timestamp is corrupted, hence don't store it */
+ return 0;
+ else
+ return 1;
+}
+
+static void ndesc_display_ring(void *head, unsigned int size, bool rx,
+ dma_addr_t dma_rx_phy, unsigned int desc_size)
+{
+ struct dma_desc *p = (struct dma_desc *)head;
+ dma_addr_t dma_addr;
+ int i;
+
+ pr_info("%s descriptor ring:\n", rx ? "RX" : "TX");
+
+ for (i = 0; i < size; i++) {
+ u64 x;
+ dma_addr = dma_rx_phy + i * sizeof(*p);
+
+ x = *(u64 *)p;
+ pr_info("%03d [%pad]: 0x%x 0x%x 0x%x 0x%x",
+ i, &dma_addr,
+ (unsigned int)x, (unsigned int)(x >> 32),
+ p->des2, p->des3);
+ p++;
+ }
+ pr_info("\n");
+}
+
+static void ndesc_set_addr(struct dma_desc *p, dma_addr_t addr)
+{
+ p->des2 = cpu_to_le32(addr);
+}
+
+static void ndesc_clear(struct dma_desc *p)
+{
+ p->des2 = 0;
+}
+
+const struct stmmac_desc_ops ndesc_ops = {
+ .tx_status = ndesc_get_tx_status,
+ .rx_status = ndesc_get_rx_status,
+ .init_rx_desc = ndesc_init_rx_desc,
+ .init_tx_desc = ndesc_init_tx_desc,
+ .release_tx_desc = ndesc_release_tx_desc,
+ .prepare_tx_desc = ndesc_prepare_tx_desc,
+ .set_tx_ic = ndesc_set_tx_ic,
+ .set_tx_owner = ndesc_set_tx_owner,
+ .set_rx_owner = ndesc_set_rx_owner,
+ .get_rx_frame_len = ndesc_get_rx_frame_len,
+ .enable_tx_timestamp = ndesc_enable_tx_timestamp,
+ .get_tx_timestamp_status = ndesc_get_tx_timestamp_status,
+ .get_timestamp = ndesc_get_timestamp,
+ .get_rx_timestamp_status = ndesc_get_rx_timestamp_status,
+ .display_ring = ndesc_display_ring,
+ .set_addr = ndesc_set_addr,
+ .clear = ndesc_clear,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
new file mode 100644
index 000000000..78fc6aa5b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ Specialised functions for managing Ring mode
+
+ Copyright(C) 2011 STMicroelectronics Ltd
+
+ It defines all the functions used to handle the normal/enhanced
+ descriptors in case of the DMA is configured to work in chained or
+ in ring mode.
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include "stmmac.h"
+
+static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
+ int csum)
+{
+ unsigned int nopaged_len = skb_headlen(skb);
+ struct stmmac_priv *priv = tx_q->priv_data;
+ unsigned int entry = tx_q->cur_tx;
+ unsigned int bmax, len, des2;
+ struct dma_desc *desc;
+
+ if (priv->extend_desc)
+ desc = (struct dma_desc *)(tx_q->dma_etx + entry);
+ else
+ desc = tx_q->dma_tx + entry;
+
+ if (priv->plat->enh_desc)
+ bmax = BUF_SIZE_8KiB;
+ else
+ bmax = BUF_SIZE_2KiB;
+
+ len = nopaged_len - bmax;
+
+ if (nopaged_len > BUF_SIZE_8KiB) {
+
+ des2 = dma_map_single(priv->device, skb->data, bmax,
+ DMA_TO_DEVICE);
+ desc->des2 = cpu_to_le32(des2);
+ if (dma_mapping_error(priv->device, des2))
+ return -1;
+
+ tx_q->tx_skbuff_dma[entry].buf = des2;
+ tx_q->tx_skbuff_dma[entry].len = bmax;
+ tx_q->tx_skbuff_dma[entry].is_jumbo = true;
+
+ desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB);
+ stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum,
+ STMMAC_RING_MODE, 0, false, skb->len);
+ tx_q->tx_skbuff[entry] = NULL;
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+
+ if (priv->extend_desc)
+ desc = (struct dma_desc *)(tx_q->dma_etx + entry);
+ else
+ desc = tx_q->dma_tx + entry;
+
+ des2 = dma_map_single(priv->device, skb->data + bmax, len,
+ DMA_TO_DEVICE);
+ desc->des2 = cpu_to_le32(des2);
+ if (dma_mapping_error(priv->device, des2))
+ return -1;
+ tx_q->tx_skbuff_dma[entry].buf = des2;
+ tx_q->tx_skbuff_dma[entry].len = len;
+ tx_q->tx_skbuff_dma[entry].is_jumbo = true;
+
+ desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB);
+ stmmac_prepare_tx_desc(priv, desc, 0, len, csum,
+ STMMAC_RING_MODE, 1, !skb_is_nonlinear(skb),
+ skb->len);
+ } else {
+ des2 = dma_map_single(priv->device, skb->data,
+ nopaged_len, DMA_TO_DEVICE);
+ desc->des2 = cpu_to_le32(des2);
+ if (dma_mapping_error(priv->device, des2))
+ return -1;
+ tx_q->tx_skbuff_dma[entry].buf = des2;
+ tx_q->tx_skbuff_dma[entry].len = nopaged_len;
+ tx_q->tx_skbuff_dma[entry].is_jumbo = true;
+ desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB);
+ stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len, csum,
+ STMMAC_RING_MODE, 0, !skb_is_nonlinear(skb),
+ skb->len);
+ }
+
+ tx_q->cur_tx = entry;
+
+ return entry;
+}
+
+static bool is_jumbo_frm(unsigned int len, bool enh_desc)
+{
+ return len >= BUF_SIZE_4KiB;
+}
+
+static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p)
+{
+ struct stmmac_priv *priv = rx_q->priv_data;
+
+ /* Fill DES3 in case of RING mode */
+ if (priv->dma_conf.dma_buf_sz == BUF_SIZE_16KiB)
+ p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB);
+}
+
+/* In ring mode we need to fill the desc3 because it is used as buffer */
+static void init_desc3(struct dma_desc *p)
+{
+ p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB);
+}
+
+static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p)
+{
+ struct stmmac_priv *priv = tx_q->priv_data;
+ unsigned int entry = tx_q->dirty_tx;
+
+ /* des3 is only used for jumbo frames tx or time stamping */
+ if (unlikely(tx_q->tx_skbuff_dma[entry].is_jumbo ||
+ (tx_q->tx_skbuff_dma[entry].last_segment &&
+ !priv->extend_desc && priv->hwts_tx_en)))
+ p->des3 = 0;
+}
+
+static int set_16kib_bfsize(int mtu)
+{
+ int ret = 0;
+ if (unlikely(mtu > BUF_SIZE_8KiB))
+ ret = BUF_SIZE_16KiB;
+ return ret;
+}
+
+const struct stmmac_mode_ops ring_mode_ops = {
+ .is_jumbo_frm = is_jumbo_frm,
+ .jumbo_frm = jumbo_frm,
+ .refill_desc3 = refill_desc3,
+ .init_desc3 = init_desc3,
+ .clean_desc3 = clean_desc3,
+ .set_16kib_bfsize = set_16kib_bfsize,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
new file mode 100644
index 000000000..8ba8f03e1
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -0,0 +1,460 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __STMMAC_H__
+#define __STMMAC_H__
+
+#define STMMAC_RESOURCE_NAME "stmmaceth"
+
+#include <linux/clk.h>
+#include <linux/hrtimer.h>
+#include <linux/if_vlan.h>
+#include <linux/stmmac.h>
+#include <linux/phylink.h>
+#include <linux/pci.h>
+#include "common.h"
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/reset.h>
+#include <net/page_pool/types.h>
+#include <net/xdp.h>
+#include <uapi/linux/bpf.h>
+
+struct stmmac_pcs;
+
+struct stmmac_resources {
+ void __iomem *addr;
+ u8 mac[ETH_ALEN];
+ int wol_irq;
+ int irq;
+ int sfty_irq;
+ int sfty_ce_irq;
+ int sfty_ue_irq;
+ int rx_irq[MTL_MAX_RX_QUEUES];
+ int tx_irq[MTL_MAX_TX_QUEUES];
+};
+
+enum stmmac_txbuf_type {
+ STMMAC_TXBUF_T_SKB,
+ STMMAC_TXBUF_T_XDP_TX,
+ STMMAC_TXBUF_T_XDP_NDO,
+ STMMAC_TXBUF_T_XSK_TX,
+};
+
+struct stmmac_tx_info {
+ struct xsk_tx_metadata_compl xsk_meta;
+ dma_addr_t buf;
+ unsigned len;
+ enum stmmac_txbuf_type buf_type;
+ bool map_as_page;
+ bool last_segment;
+ bool is_jumbo;
+};
+
+#define STMMAC_TBS_AVAIL BIT(0)
+#define STMMAC_TBS_EN BIT(1)
+
+/* Frequently used values are kept adjacent for cache effect */
+struct stmmac_tx_queue {
+ u32 tx_count_frames;
+ int tbs;
+ struct hrtimer txtimer;
+ u32 queue_index;
+ struct stmmac_priv *priv_data;
+ struct dma_extended_desc *dma_etx ____cacheline_aligned_in_smp;
+ struct dma_edesc *dma_entx;
+ struct dma_desc *dma_tx;
+ union {
+ struct sk_buff **tx_skbuff;
+ struct xdp_frame **xdpf;
+ };
+ struct stmmac_tx_info *tx_skbuff_dma;
+ struct xsk_buff_pool *xsk_pool;
+ u32 xsk_frames_done;
+ unsigned int cur_tx;
+ unsigned int dirty_tx;
+ dma_addr_t dma_tx_phy;
+ u32 mss;
+};
+
+struct stmmac_rx_buffer {
+ union {
+ struct {
+ struct page *page;
+ dma_addr_t addr;
+ __u32 page_offset;
+ };
+ struct xdp_buff *xdp;
+ };
+ struct page *sec_page;
+ dma_addr_t sec_addr;
+};
+
+struct stmmac_xdp_buff {
+ struct xdp_buff xdp;
+ struct stmmac_priv *priv;
+ struct dma_desc *desc;
+ struct dma_desc *ndesc;
+};
+
+struct stmmac_metadata_request {
+ struct stmmac_priv *priv;
+ struct dma_desc *tx_desc;
+ bool *set_ic;
+ struct dma_edesc *edesc;
+ int tbs;
+};
+
+struct stmmac_xsk_tx_complete {
+ struct stmmac_priv *priv;
+ struct dma_desc *desc;
+};
+
+struct stmmac_rx_queue {
+ u32 rx_count_frames;
+ u32 queue_index;
+ struct xdp_rxq_info xdp_rxq;
+ struct xsk_buff_pool *xsk_pool;
+ struct page_pool *page_pool;
+ struct stmmac_rx_buffer *buf_pool;
+ struct stmmac_priv *priv_data;
+ struct dma_extended_desc *dma_erx;
+ struct dma_desc *dma_rx ____cacheline_aligned_in_smp;
+ unsigned int cur_rx;
+ unsigned int dirty_rx;
+ unsigned int buf_alloc_num;
+ unsigned int napi_skb_frag_size;
+ dma_addr_t dma_rx_phy;
+ unsigned int state_saved;
+ struct {
+ struct sk_buff *skb;
+ unsigned int len;
+ unsigned int error;
+ } state;
+};
+
+struct stmmac_channel {
+ struct napi_struct rx_napi ____cacheline_aligned_in_smp;
+ struct napi_struct tx_napi ____cacheline_aligned_in_smp;
+ struct napi_struct rxtx_napi ____cacheline_aligned_in_smp;
+ struct stmmac_priv *priv_data;
+ spinlock_t lock;
+ u32 index;
+};
+
+struct stmmac_fpe_cfg {
+ struct ethtool_mmsv mmsv;
+ const struct stmmac_fpe_reg *reg;
+ u32 fpe_csr; /* MAC_FPE_CTRL_STS reg cache */
+};
+
+struct stmmac_tc_entry {
+ bool in_use;
+ bool in_hw;
+ bool is_last;
+ bool is_frag;
+ void *frag_ptr;
+ unsigned int table_pos;
+ u32 handle;
+ u32 prio;
+ struct {
+ u32 match_data;
+ u32 match_en;
+ u8 af:1;
+ u8 rf:1;
+ u8 im:1;
+ u8 nc:1;
+ u8 res1:4;
+ u8 frame_offset;
+ u8 ok_index;
+ u8 dma_ch_no;
+ u32 res2;
+ } __packed val;
+};
+
+#define STMMAC_PPS_MAX 4
+struct stmmac_pps_cfg {
+ struct timespec64 start;
+ struct timespec64 period;
+};
+
+struct stmmac_rss {
+ int enable;
+ u8 key[STMMAC_RSS_HASH_KEY_SIZE];
+ u32 table[STMMAC_RSS_MAX_TABLE_SIZE];
+};
+
+#define STMMAC_FLOW_ACTION_DROP BIT(0)
+struct stmmac_flow_entry {
+ unsigned long cookie;
+ unsigned long action;
+ u8 ip_proto;
+ int in_use;
+ int idx;
+ int is_l4;
+};
+
+/* Rx Frame Steering */
+enum stmmac_rfs_type {
+ STMMAC_RFS_T_VLAN,
+ STMMAC_RFS_T_LLDP,
+ STMMAC_RFS_T_1588,
+ STMMAC_RFS_T_MAX,
+};
+
+struct stmmac_rfs_entry {
+ unsigned long cookie;
+ u16 etype;
+ int in_use;
+ int type;
+ int tc;
+};
+
+struct stmmac_dma_conf {
+ unsigned int dma_buf_sz;
+
+ /* RX Queue */
+ struct stmmac_rx_queue rx_queue[MTL_MAX_RX_QUEUES];
+ unsigned int dma_rx_size;
+
+ /* TX Queue */
+ struct stmmac_tx_queue tx_queue[MTL_MAX_TX_QUEUES];
+ unsigned int dma_tx_size;
+};
+
+#define EST_GCL 1024
+struct stmmac_est {
+ int enable;
+ u32 btr_reserve[2];
+ u32 btr_offset[2];
+ u32 btr[2];
+ u32 ctr[2];
+ u32 ter;
+ u32 gcl_unaligned[EST_GCL];
+ u32 gcl[EST_GCL];
+ u32 gcl_size;
+ u32 max_sdu[MTL_MAX_TX_QUEUES];
+};
+
+struct stmmac_msi {
+ int sfty_ce_irq;
+ int sfty_ue_irq;
+ int rx_irq[MTL_MAX_RX_QUEUES];
+ int tx_irq[MTL_MAX_TX_QUEUES];
+
+ /*irq name */
+ char int_name_mac[IFNAMSIZ + 9];
+ char int_name_wol[IFNAMSIZ + 9];
+ char int_name_lpi[IFNAMSIZ + 9];
+ char int_name_sfty[IFNAMSIZ + 10];
+ char int_name_sfty_ce[IFNAMSIZ + 10];
+ char int_name_sfty_ue[IFNAMSIZ + 10];
+ char int_name_rx_irq[MTL_MAX_RX_QUEUES][IFNAMSIZ + 14];
+ char int_name_tx_irq[MTL_MAX_TX_QUEUES][IFNAMSIZ + 18];
+};
+
+struct stmmac_priv {
+ /* Frequently used values are kept adjacent for cache effect */
+ u32 tx_coal_frames[MTL_MAX_TX_QUEUES];
+ u32 tx_coal_timer[MTL_MAX_TX_QUEUES];
+ u32 rx_coal_frames[MTL_MAX_RX_QUEUES];
+
+ int hwts_tx_en;
+ /* skb_shinfo(skb)->gso_type types that we handle */
+ unsigned int gso_enabled_types;
+ bool tx_path_in_lpi_mode;
+ bool sph_active;
+ bool sph_capable;
+ u32 sarc_type;
+ u32 rx_riwt[MTL_MAX_RX_QUEUES];
+ int hwts_rx_en;
+ bool tsfupdt_coarse;
+
+ void __iomem *ioaddr;
+ struct net_device *dev;
+ struct device *device;
+ struct mac_device_info *hw;
+ int (*hwif_quirks)(struct stmmac_priv *priv);
+ struct mutex lock;
+
+ struct stmmac_dma_conf dma_conf;
+
+ /* Generic channel for NAPI */
+ struct stmmac_channel channel[STMMAC_CH_MAX];
+
+ unsigned int pause_time;
+ struct mii_bus *mii;
+
+ struct stmmac_pcs *integrated_pcs;
+
+ struct phylink_config phylink_config;
+ struct phylink *phylink;
+
+ struct stmmac_extra_stats xstats ____cacheline_aligned_in_smp;
+ struct stmmac_safety_stats sstats;
+ struct plat_stmmacenet_data *plat;
+ /* Protect est parameters */
+ struct mutex est_lock;
+ struct stmmac_est *est;
+ struct dma_features dma_cap;
+ struct stmmac_counters mmc;
+ int hw_cap_support;
+ int synopsys_id;
+ u32 msg_enable;
+ /* Our MAC Wake-on-Lan options */
+ int wolopts;
+ int wol_irq;
+ u32 gmii_address_bus_config;
+ struct timer_list eee_ctrl_timer;
+ u32 tx_lpi_timer;
+ bool tx_lpi_clk_stop;
+ bool eee_enabled;
+ bool eee_active;
+ bool eee_sw_timer_en;
+ bool legacy_serdes_is_powered;
+ /* descriptor format:
+ * when clear: struct dma_desc or for tx TBS struct dma_edesc
+ * when set, struct dma_extended_desc
+ */
+ bool extend_desc;
+ /* chain_mode: requested descriptor mode */
+ bool chain_mode;
+ /* descriptor_mode: actual descriptor mode,
+ * see STMMAC_CHAIN_MODE or STMMAC_RING_MODE
+ */
+ u8 descriptor_mode;
+ struct kernel_hwtstamp_config tstamp_config;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_ops;
+ unsigned int default_addend;
+ u32 sub_second_inc;
+ u32 systime_flags;
+ u32 adv_ts;
+ int use_riwt;
+ int irq_wake;
+ rwlock_t ptp_lock;
+ /* Protects auxiliary snapshot registers from concurrent access. */
+ struct mutex aux_ts_lock;
+ wait_queue_head_t tstamp_busy_wait;
+
+ void __iomem *mmcaddr;
+ void __iomem *ptpaddr;
+ void __iomem *estaddr;
+ unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)];
+ unsigned int num_double_vlans;
+ int sfty_irq;
+ struct stmmac_msi *msi;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dbgfs_dir;
+#endif
+
+ unsigned long state;
+ struct workqueue_struct *wq;
+ struct work_struct service_task;
+
+ /* Frame Preemption feature (FPE) */
+ struct stmmac_fpe_cfg fpe_cfg;
+
+ /* TC Handling */
+ unsigned int tc_entries_max;
+ unsigned int tc_off_max;
+ struct stmmac_tc_entry *tc_entries;
+ unsigned int flow_entries_max;
+ struct stmmac_flow_entry *flow_entries;
+ unsigned int rfs_entries_max[STMMAC_RFS_T_MAX];
+ unsigned int rfs_entries_cnt[STMMAC_RFS_T_MAX];
+ unsigned int rfs_entries_total;
+ struct stmmac_rfs_entry *rfs_entries;
+
+ /* Pulse Per Second output */
+ struct stmmac_pps_cfg pps[STMMAC_PPS_MAX];
+
+ /* Receive Side Scaling */
+ struct stmmac_rss rss;
+
+ /* XDP BPF Program */
+ unsigned long *af_xdp_zc_qps;
+ struct bpf_prog *xdp_prog;
+
+ struct devlink *devlink;
+};
+
+enum stmmac_state {
+ STMMAC_DOWN,
+ STMMAC_RESET_REQUESTED,
+ STMMAC_RESETING,
+ STMMAC_SERVICE_SCHED,
+};
+
+extern const struct dev_pm_ops stmmac_simple_pm_ops;
+
+int stmmac_mdio_unregister(struct net_device *ndev);
+int stmmac_mdio_register(struct net_device *ndev);
+int stmmac_mdio_reset(struct mii_bus *mii);
+void stmmac_mdio_lock(struct stmmac_priv *priv);
+void stmmac_mdio_unlock(struct stmmac_priv *priv);
+int stmmac_pcs_setup(struct net_device *ndev);
+void stmmac_pcs_clean(struct net_device *ndev);
+void stmmac_set_ethtool_ops(struct net_device *netdev);
+
+void stmmac_ptp_register(struct stmmac_priv *priv);
+void stmmac_ptp_unregister(struct stmmac_priv *priv);
+int stmmac_xdp_open(struct net_device *dev);
+void stmmac_xdp_release(struct net_device *dev);
+int stmmac_get_phy_intf_sel(phy_interface_t interface);
+int stmmac_resume(struct device *dev);
+int stmmac_suspend(struct device *dev);
+void stmmac_dvr_remove(struct device *dev);
+int stmmac_dvr_probe(struct device *device,
+ struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *res);
+int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt);
+int stmmac_reinit_ringparam(struct net_device *dev, u32 rx_size, u32 tx_size);
+int stmmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed);
+
+struct plat_stmmacenet_data *stmmac_plat_dat_alloc(struct device *dev);
+
+static inline bool stmmac_xdp_is_enabled(struct stmmac_priv *priv)
+{
+ return !!priv->xdp_prog;
+}
+
+void stmmac_disable_rx_queue(struct stmmac_priv *priv, u32 queue);
+void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue);
+void stmmac_disable_tx_queue(struct stmmac_priv *priv, u32 queue);
+void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue);
+int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags);
+struct timespec64 stmmac_calc_tas_basetime(ktime_t old_base_time,
+ ktime_t current_time,
+ u64 cycle_time);
+
+#if IS_ENABLED(CONFIG_STMMAC_SELFTESTS)
+void stmmac_selftest_run(struct net_device *dev,
+ struct ethtool_test *etest, u64 *buf);
+void stmmac_selftest_get_strings(struct stmmac_priv *priv, u8 *data);
+int stmmac_selftest_get_count(struct stmmac_priv *priv);
+#else
+static inline void stmmac_selftest_run(struct net_device *dev,
+ struct ethtool_test *etest, u64 *buf)
+{
+ /* Not enabled */
+}
+static inline void stmmac_selftest_get_strings(struct stmmac_priv *priv,
+ u8 *data)
+{
+ /* Not enabled */
+}
+static inline int stmmac_selftest_get_count(struct stmmac_priv *priv)
+{
+ return -EOPNOTSUPP;
+}
+#endif /* CONFIG_STMMAC_SELFTESTS */
+
+#endif /* __STMMAC_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c
new file mode 100644
index 000000000..afc516059
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023, Intel Corporation
+ * stmmac EST(802.3 Qbv) handling
+ */
+#include <linux/iopoll.h>
+#include <linux/types.h>
+#include "stmmac.h"
+#include "stmmac_est.h"
+
+static int est_write(void __iomem *est_addr, u32 reg, u32 val, bool gcl)
+{
+ u32 ctrl;
+
+ writel(val, est_addr + EST_GCL_DATA);
+
+ ctrl = (reg << EST_ADDR_SHIFT);
+ ctrl |= gcl ? 0 : EST_GCRR;
+ writel(ctrl, est_addr + EST_GCL_CONTROL);
+
+ ctrl |= EST_SRWO;
+ writel(ctrl, est_addr + EST_GCL_CONTROL);
+
+ return readl_poll_timeout(est_addr + EST_GCL_CONTROL, ctrl,
+ !(ctrl & EST_SRWO), 100, 5000);
+}
+
+static int est_configure(struct stmmac_priv *priv, struct stmmac_est *cfg,
+ unsigned int ptp_rate)
+{
+ void __iomem *est_addr = priv->estaddr;
+ int i, ret = 0;
+ u32 ctrl;
+
+ if (!ptp_rate) {
+ netdev_warn(priv->dev, "Invalid PTP rate");
+ return -EINVAL;
+ }
+
+ ret |= est_write(est_addr, EST_BTR_LOW, cfg->btr[0], false);
+ ret |= est_write(est_addr, EST_BTR_HIGH, cfg->btr[1], false);
+ ret |= est_write(est_addr, EST_TER, cfg->ter, false);
+ ret |= est_write(est_addr, EST_LLR, cfg->gcl_size, false);
+ ret |= est_write(est_addr, EST_CTR_LOW, cfg->ctr[0], false);
+ ret |= est_write(est_addr, EST_CTR_HIGH, cfg->ctr[1], false);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < cfg->gcl_size; i++) {
+ ret = est_write(est_addr, i, cfg->gcl[i], true);
+ if (ret)
+ return ret;
+ }
+
+ ctrl = readl(est_addr + EST_CONTROL);
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC) {
+ ctrl &= ~EST_XGMAC_PTOV;
+ ctrl |= ((NSEC_PER_SEC / ptp_rate) * EST_XGMAC_PTOV_MUL) <<
+ EST_XGMAC_PTOV_SHIFT;
+ } else {
+ ctrl &= ~EST_GMAC5_PTOV;
+ ctrl |= ((NSEC_PER_SEC / ptp_rate) * EST_GMAC5_PTOV_MUL) <<
+ EST_GMAC5_PTOV_SHIFT;
+ }
+ if (cfg->enable)
+ ctrl |= EST_EEST | EST_SSWL | EST_DFBS;
+ else
+ ctrl &= ~EST_EEST;
+
+ writel(ctrl, est_addr + EST_CONTROL);
+
+ /* Configure EST interrupt */
+ if (cfg->enable)
+ ctrl = EST_IECGCE | EST_IEHS | EST_IEHF | EST_IEBE | EST_IECC;
+ else
+ ctrl = 0;
+
+ writel(ctrl, est_addr + EST_INT_EN);
+
+ return 0;
+}
+
+static void est_irq_status(struct stmmac_priv *priv, struct net_device *dev,
+ struct stmmac_extra_stats *x, u32 txqcnt)
+{
+ u32 status, value, feqn, hbfq, hbfs, btrl, btrl_max;
+ void __iomem *est_addr = priv->estaddr;
+ u32 txqcnt_mask = BIT(txqcnt) - 1;
+ int i;
+
+ status = readl(est_addr + EST_STATUS);
+
+ value = EST_CGCE | EST_HLBS | EST_HLBF | EST_BTRE | EST_SWLC;
+
+ /* Return if there is no error */
+ if (!(status & value))
+ return;
+
+ if (status & EST_CGCE) {
+ /* Clear Interrupt */
+ writel(EST_CGCE, est_addr + EST_STATUS);
+
+ x->mtl_est_cgce++;
+ }
+
+ if (status & EST_HLBS) {
+ value = readl(est_addr + EST_SCH_ERR);
+ value &= txqcnt_mask;
+
+ x->mtl_est_hlbs++;
+
+ for (i = 0; i < txqcnt; i++)
+ if (value & BIT(i))
+ x->mtl_est_txq_hlbs[i]++;
+
+ /* Clear Interrupt */
+ writel(value, est_addr + EST_SCH_ERR);
+
+ /* Collecting info to shows all the queues that has HLBS
+ * issue. The only way to clear this is to clear the
+ * statistic
+ */
+ if (net_ratelimit())
+ netdev_err(dev, "EST: HLB(sched) Queue 0x%x\n", value);
+ }
+
+ if (status & EST_HLBF) {
+ value = readl(est_addr + EST_FRM_SZ_ERR);
+ feqn = value & txqcnt_mask;
+
+ value = readl(est_addr + EST_FRM_SZ_CAP);
+ hbfq = (value & EST_SZ_CAP_HBFQ_MASK(txqcnt)) >>
+ EST_SZ_CAP_HBFQ_SHIFT;
+ hbfs = value & EST_SZ_CAP_HBFS_MASK;
+
+ x->mtl_est_hlbf++;
+
+ for (i = 0; i < txqcnt; i++)
+ if (feqn & BIT(i))
+ x->mtl_est_txq_hlbf[i]++;
+
+ /* Clear Interrupt */
+ writel(feqn, est_addr + EST_FRM_SZ_ERR);
+
+ if (net_ratelimit())
+ netdev_err(dev, "EST: HLB(size) Queue %u Size %u\n",
+ hbfq, hbfs);
+ }
+
+ if (status & EST_BTRE) {
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC) {
+ btrl = FIELD_GET(EST_XGMAC_BTRL, status);
+ btrl_max = FIELD_MAX(EST_XGMAC_BTRL);
+ } else {
+ btrl = FIELD_GET(EST_GMAC5_BTRL, status);
+ btrl_max = FIELD_MAX(EST_GMAC5_BTRL);
+ }
+ if (btrl == btrl_max)
+ x->mtl_est_btrlm++;
+ else
+ x->mtl_est_btre++;
+
+ if (net_ratelimit())
+ netdev_info(dev, "EST: BTR Error Loop Count %u\n",
+ btrl);
+
+ writel(EST_BTRE, est_addr + EST_STATUS);
+ }
+
+ if (status & EST_SWLC) {
+ writel(EST_SWLC, est_addr + EST_STATUS);
+ netdev_info(dev, "EST: SWOL has been switched\n");
+ }
+}
+
+const struct stmmac_est_ops dwmac510_est_ops = {
+ .configure = est_configure,
+ .irq_status = est_irq_status,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_est.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_est.h
new file mode 100644
index 000000000..f70221c9c
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_est.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023, Intel Corporation
+ * stmmac EST(802.3 Qbv) handling
+ */
+
+#define EST_GMAC4_OFFSET 0x00000c50
+#define EST_XGMAC_OFFSET 0x00001050
+
+#define EST_CONTROL 0x00000000
+#define EST_GMAC5_PTOV GENMASK(31, 24)
+#define EST_GMAC5_PTOV_SHIFT 24
+#define EST_GMAC5_PTOV_MUL 6
+#define EST_XGMAC_PTOV GENMASK(31, 23)
+#define EST_XGMAC_PTOV_SHIFT 23
+#define EST_XGMAC_PTOV_MUL 9
+#define EST_SSWL BIT(1)
+#define EST_EEST BIT(0)
+#define EST_DFBS BIT(5)
+
+#define EST_STATUS 0x00000008
+#define EST_GMAC5_BTRL GENMASK(11, 8)
+#define EST_XGMAC_BTRL GENMASK(15, 8)
+#define EST_SWOL BIT(7)
+#define EST_SWOL_SHIFT 7
+#define EST_CGCE BIT(4)
+#define EST_HLBS BIT(3)
+#define EST_HLBF BIT(2)
+#define EST_BTRE BIT(1)
+#define EST_SWLC BIT(0)
+
+#define EST_SCH_ERR 0x00000010
+
+#define EST_FRM_SZ_ERR 0x00000014
+
+#define EST_FRM_SZ_CAP 0x00000018
+#define EST_SZ_CAP_HBFS_MASK GENMASK(14, 0)
+#define EST_SZ_CAP_HBFQ_SHIFT 16
+#define EST_SZ_CAP_HBFQ_MASK(val) \
+ ({ \
+ typeof(val) _val = (val); \
+ (_val > 4 ? GENMASK(18, 16) : \
+ _val > 2 ? GENMASK(17, 16) : \
+ BIT(16)); \
+ })
+
+#define EST_INT_EN 0x00000020
+#define EST_IECGCE EST_CGCE
+#define EST_IEHS EST_HLBS
+#define EST_IEHF EST_HLBF
+#define EST_IEBE EST_BTRE
+#define EST_IECC EST_SWLC
+
+#define EST_GCL_CONTROL 0x00000030
+#define EST_BTR_LOW 0x0
+#define EST_BTR_HIGH 0x1
+#define EST_CTR_LOW 0x2
+#define EST_CTR_HIGH 0x3
+#define EST_TER 0x4
+#define EST_LLR 0x5
+#define EST_ADDR_SHIFT 8
+#define EST_GCRR BIT(2)
+#define EST_SRWO BIT(0)
+
+#define EST_GCL_DATA 0x00000034
+
+extern const struct stmmac_est_ops dwmac510_est_ops;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
new file mode 100644
index 000000000..92585d27a
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
@@ -0,0 +1,1136 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ STMMAC Ethtool support
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mii.h>
+#include <linux/phylink.h>
+#include <linux/net_tstamp.h>
+
+#include "stmmac.h"
+#include "stmmac_fpe.h"
+#include "dwmac_dma.h"
+#include "dwxgmac2.h"
+
+#define REG_SPACE_SIZE 0x1060
+#define GMAC4_REG_SPACE_SIZE 0x116C
+#define MAC100_ETHTOOL_NAME "st_mac100"
+#define GMAC_ETHTOOL_NAME "st_gmac"
+#define XGMAC_ETHTOOL_NAME "st_xgmac"
+
+/* Same as DMA_CHAN_BASE_ADDR defined in dwmac4_dma.h
+ *
+ * It is here because dwmac_dma.h and dwmac4_dam.h can not be included at the
+ * same time due to the conflicting macro names.
+ */
+#define GMAC4_DMA_CHAN_BASE_ADDR 0x00001100
+
+#define ETHTOOL_DMA_OFFSET 55
+
+struct stmmac_stats {
+ char stat_string[ETH_GSTRING_LEN] __nonstring;
+ int sizeof_stat;
+ int stat_offset;
+};
+
+#define STMMAC_STAT(m) \
+ { #m, sizeof_field(struct stmmac_extra_stats, m), \
+ offsetof(struct stmmac_priv, xstats.m)}
+
+static const struct stmmac_stats stmmac_gstrings_stats[] = {
+ /* Transmit errors */
+ STMMAC_STAT(tx_underflow),
+ STMMAC_STAT(tx_carrier),
+ STMMAC_STAT(tx_losscarrier),
+ STMMAC_STAT(vlan_tag),
+ STMMAC_STAT(tx_deferred),
+ STMMAC_STAT(tx_vlan),
+ STMMAC_STAT(tx_jabber),
+ STMMAC_STAT(tx_frame_flushed),
+ STMMAC_STAT(tx_payload_error),
+ STMMAC_STAT(tx_ip_header_error),
+ /* Receive errors */
+ STMMAC_STAT(rx_desc),
+ STMMAC_STAT(sa_filter_fail),
+ STMMAC_STAT(overflow_error),
+ STMMAC_STAT(ipc_csum_error),
+ STMMAC_STAT(rx_collision),
+ STMMAC_STAT(rx_crc_errors),
+ STMMAC_STAT(dribbling_bit),
+ STMMAC_STAT(rx_length),
+ STMMAC_STAT(rx_mii),
+ STMMAC_STAT(rx_multicast),
+ STMMAC_STAT(rx_gmac_overflow),
+ STMMAC_STAT(rx_watchdog),
+ STMMAC_STAT(da_rx_filter_fail),
+ STMMAC_STAT(sa_rx_filter_fail),
+ STMMAC_STAT(rx_missed_cntr),
+ STMMAC_STAT(rx_overflow_cntr),
+ STMMAC_STAT(rx_vlan),
+ STMMAC_STAT(rx_split_hdr_pkt_n),
+ /* Tx/Rx IRQ error info */
+ STMMAC_STAT(tx_undeflow_irq),
+ STMMAC_STAT(tx_process_stopped_irq),
+ STMMAC_STAT(tx_jabber_irq),
+ STMMAC_STAT(rx_overflow_irq),
+ STMMAC_STAT(rx_buf_unav_irq),
+ STMMAC_STAT(rx_process_stopped_irq),
+ STMMAC_STAT(rx_watchdog_irq),
+ STMMAC_STAT(tx_early_irq),
+ STMMAC_STAT(fatal_bus_error_irq),
+ /* Tx/Rx IRQ Events */
+ STMMAC_STAT(rx_early_irq),
+ STMMAC_STAT(threshold),
+ STMMAC_STAT(irq_receive_pmt_irq_n),
+ /* MMC info */
+ STMMAC_STAT(mmc_tx_irq_n),
+ STMMAC_STAT(mmc_rx_irq_n),
+ STMMAC_STAT(mmc_rx_csum_offload_irq_n),
+ /* EEE */
+ STMMAC_STAT(irq_tx_path_in_lpi_mode_n),
+ STMMAC_STAT(irq_tx_path_exit_lpi_mode_n),
+ STMMAC_STAT(irq_rx_path_in_lpi_mode_n),
+ STMMAC_STAT(irq_rx_path_exit_lpi_mode_n),
+ STMMAC_STAT(phy_eee_wakeup_error_n),
+ /* Extended RDES status */
+ STMMAC_STAT(ip_hdr_err),
+ STMMAC_STAT(ip_payload_err),
+ STMMAC_STAT(ip_csum_bypassed),
+ STMMAC_STAT(ipv4_pkt_rcvd),
+ STMMAC_STAT(ipv6_pkt_rcvd),
+ STMMAC_STAT(no_ptp_rx_msg_type_ext),
+ STMMAC_STAT(ptp_rx_msg_type_sync),
+ STMMAC_STAT(ptp_rx_msg_type_follow_up),
+ STMMAC_STAT(ptp_rx_msg_type_delay_req),
+ STMMAC_STAT(ptp_rx_msg_type_delay_resp),
+ STMMAC_STAT(ptp_rx_msg_type_pdelay_req),
+ STMMAC_STAT(ptp_rx_msg_type_pdelay_resp),
+ STMMAC_STAT(ptp_rx_msg_type_pdelay_follow_up),
+ STMMAC_STAT(ptp_rx_msg_type_announce),
+ STMMAC_STAT(ptp_rx_msg_type_management),
+ STMMAC_STAT(ptp_rx_msg_pkt_reserved_type),
+ STMMAC_STAT(ptp_frame_type),
+ STMMAC_STAT(ptp_ver),
+ STMMAC_STAT(timestamp_dropped),
+ STMMAC_STAT(av_pkt_rcvd),
+ STMMAC_STAT(av_tagged_pkt_rcvd),
+ STMMAC_STAT(vlan_tag_priority_val),
+ STMMAC_STAT(l3_filter_match),
+ STMMAC_STAT(l4_filter_match),
+ STMMAC_STAT(l3_l4_filter_no_match),
+ /* PCS */
+ STMMAC_STAT(irq_pcs_ane_n),
+ STMMAC_STAT(irq_pcs_link_n),
+ STMMAC_STAT(irq_rgmii_n),
+ /* DEBUG */
+ STMMAC_STAT(mtl_tx_status_fifo_full),
+ STMMAC_STAT(mtl_tx_fifo_not_empty),
+ STMMAC_STAT(mmtl_fifo_ctrl),
+ STMMAC_STAT(mtl_tx_fifo_read_ctrl_write),
+ STMMAC_STAT(mtl_tx_fifo_read_ctrl_wait),
+ STMMAC_STAT(mtl_tx_fifo_read_ctrl_read),
+ STMMAC_STAT(mtl_tx_fifo_read_ctrl_idle),
+ STMMAC_STAT(mac_tx_in_pause),
+ STMMAC_STAT(mac_tx_frame_ctrl_xfer),
+ STMMAC_STAT(mac_tx_frame_ctrl_idle),
+ STMMAC_STAT(mac_tx_frame_ctrl_wait),
+ STMMAC_STAT(mac_tx_frame_ctrl_pause),
+ STMMAC_STAT(mac_gmii_tx_proto_engine),
+ STMMAC_STAT(mtl_rx_fifo_fill_level_full),
+ STMMAC_STAT(mtl_rx_fifo_fill_above_thresh),
+ STMMAC_STAT(mtl_rx_fifo_fill_below_thresh),
+ STMMAC_STAT(mtl_rx_fifo_fill_level_empty),
+ STMMAC_STAT(mtl_rx_fifo_read_ctrl_flush),
+ STMMAC_STAT(mtl_rx_fifo_read_ctrl_read_data),
+ STMMAC_STAT(mtl_rx_fifo_read_ctrl_status),
+ STMMAC_STAT(mtl_rx_fifo_read_ctrl_idle),
+ STMMAC_STAT(mtl_rx_fifo_ctrl_active),
+ STMMAC_STAT(mac_rx_frame_ctrl_fifo),
+ STMMAC_STAT(mac_gmii_rx_proto_engine),
+ /* EST */
+ STMMAC_STAT(mtl_est_cgce),
+ STMMAC_STAT(mtl_est_hlbs),
+ STMMAC_STAT(mtl_est_hlbf),
+ STMMAC_STAT(mtl_est_btre),
+ STMMAC_STAT(mtl_est_btrlm),
+};
+#define STMMAC_STATS_LEN ARRAY_SIZE(stmmac_gstrings_stats)
+
+/* statistics collected in queue which will be summed up for all TX or RX
+ * queues, or summed up for both TX and RX queues(napi_poll, normal_irq_n).
+ */
+static const char stmmac_qstats_string[][ETH_GSTRING_LEN] = {
+ "rx_pkt_n",
+ "rx_normal_irq_n",
+ "tx_pkt_n",
+ "tx_normal_irq_n",
+ "tx_clean",
+ "tx_set_ic_bit",
+ "tx_tso_frames",
+ "tx_tso_nfrags",
+ "normal_irq_n",
+ "napi_poll",
+};
+#define STMMAC_QSTATS ARRAY_SIZE(stmmac_qstats_string)
+
+/* HW MAC Management counters (if supported) */
+#define STMMAC_MMC_STAT(m) \
+ { #m, sizeof_field(struct stmmac_counters, m), \
+ offsetof(struct stmmac_priv, mmc.m)}
+
+static const struct stmmac_stats stmmac_mmc[] = {
+ STMMAC_MMC_STAT(mmc_tx_octetcount_gb),
+ STMMAC_MMC_STAT(mmc_tx_framecount_gb),
+ STMMAC_MMC_STAT(mmc_tx_broadcastframe_g),
+ STMMAC_MMC_STAT(mmc_tx_multicastframe_g),
+ STMMAC_MMC_STAT(mmc_tx_64_octets_gb),
+ STMMAC_MMC_STAT(mmc_tx_65_to_127_octets_gb),
+ STMMAC_MMC_STAT(mmc_tx_128_to_255_octets_gb),
+ STMMAC_MMC_STAT(mmc_tx_256_to_511_octets_gb),
+ STMMAC_MMC_STAT(mmc_tx_512_to_1023_octets_gb),
+ STMMAC_MMC_STAT(mmc_tx_1024_to_max_octets_gb),
+ STMMAC_MMC_STAT(mmc_tx_unicast_gb),
+ STMMAC_MMC_STAT(mmc_tx_multicast_gb),
+ STMMAC_MMC_STAT(mmc_tx_broadcast_gb),
+ STMMAC_MMC_STAT(mmc_tx_underflow_error),
+ STMMAC_MMC_STAT(mmc_tx_singlecol_g),
+ STMMAC_MMC_STAT(mmc_tx_multicol_g),
+ STMMAC_MMC_STAT(mmc_tx_deferred),
+ STMMAC_MMC_STAT(mmc_tx_latecol),
+ STMMAC_MMC_STAT(mmc_tx_exesscol),
+ STMMAC_MMC_STAT(mmc_tx_carrier_error),
+ STMMAC_MMC_STAT(mmc_tx_octetcount_g),
+ STMMAC_MMC_STAT(mmc_tx_framecount_g),
+ STMMAC_MMC_STAT(mmc_tx_excessdef),
+ STMMAC_MMC_STAT(mmc_tx_pause_frame),
+ STMMAC_MMC_STAT(mmc_tx_vlan_frame_g),
+ STMMAC_MMC_STAT(mmc_tx_oversize_g),
+ STMMAC_MMC_STAT(mmc_tx_lpi_usec),
+ STMMAC_MMC_STAT(mmc_tx_lpi_tran),
+ STMMAC_MMC_STAT(mmc_rx_framecount_gb),
+ STMMAC_MMC_STAT(mmc_rx_octetcount_gb),
+ STMMAC_MMC_STAT(mmc_rx_octetcount_g),
+ STMMAC_MMC_STAT(mmc_rx_broadcastframe_g),
+ STMMAC_MMC_STAT(mmc_rx_multicastframe_g),
+ STMMAC_MMC_STAT(mmc_rx_crc_error),
+ STMMAC_MMC_STAT(mmc_rx_align_error),
+ STMMAC_MMC_STAT(mmc_rx_run_error),
+ STMMAC_MMC_STAT(mmc_rx_jabber_error),
+ STMMAC_MMC_STAT(mmc_rx_undersize_g),
+ STMMAC_MMC_STAT(mmc_rx_oversize_g),
+ STMMAC_MMC_STAT(mmc_rx_64_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_65_to_127_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_128_to_255_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_256_to_511_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_512_to_1023_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_1024_to_max_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_unicast_g),
+ STMMAC_MMC_STAT(mmc_rx_length_error),
+ STMMAC_MMC_STAT(mmc_rx_autofrangetype),
+ STMMAC_MMC_STAT(mmc_rx_pause_frames),
+ STMMAC_MMC_STAT(mmc_rx_fifo_overflow),
+ STMMAC_MMC_STAT(mmc_rx_vlan_frames_gb),
+ STMMAC_MMC_STAT(mmc_rx_watchdog_error),
+ STMMAC_MMC_STAT(mmc_rx_error),
+ STMMAC_MMC_STAT(mmc_rx_lpi_usec),
+ STMMAC_MMC_STAT(mmc_rx_lpi_tran),
+ STMMAC_MMC_STAT(mmc_rx_discard_frames_gb),
+ STMMAC_MMC_STAT(mmc_rx_discard_octets_gb),
+ STMMAC_MMC_STAT(mmc_rx_align_err_frames),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_gd),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_hderr),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_nopay),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_frag),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_udsbl),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_gd_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_hderr_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_nopay_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_frag_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv4_udsbl_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv6_gd_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv6_hderr_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv6_nopay_octets),
+ STMMAC_MMC_STAT(mmc_rx_ipv6_gd),
+ STMMAC_MMC_STAT(mmc_rx_ipv6_hderr),
+ STMMAC_MMC_STAT(mmc_rx_ipv6_nopay),
+ STMMAC_MMC_STAT(mmc_rx_udp_gd),
+ STMMAC_MMC_STAT(mmc_rx_udp_err),
+ STMMAC_MMC_STAT(mmc_rx_tcp_gd),
+ STMMAC_MMC_STAT(mmc_rx_tcp_err),
+ STMMAC_MMC_STAT(mmc_rx_icmp_gd),
+ STMMAC_MMC_STAT(mmc_rx_icmp_err),
+ STMMAC_MMC_STAT(mmc_rx_udp_gd_octets),
+ STMMAC_MMC_STAT(mmc_rx_udp_err_octets),
+ STMMAC_MMC_STAT(mmc_rx_tcp_gd_octets),
+ STMMAC_MMC_STAT(mmc_rx_tcp_err_octets),
+ STMMAC_MMC_STAT(mmc_rx_icmp_gd_octets),
+ STMMAC_MMC_STAT(mmc_rx_icmp_err_octets),
+ STMMAC_MMC_STAT(mmc_sgf_pass_fragment_cntr),
+ STMMAC_MMC_STAT(mmc_sgf_fail_fragment_cntr),
+ STMMAC_MMC_STAT(mmc_tx_fpe_fragment_cntr),
+ STMMAC_MMC_STAT(mmc_tx_hold_req_cntr),
+ STMMAC_MMC_STAT(mmc_tx_gate_overrun_cntr),
+ STMMAC_MMC_STAT(mmc_rx_packet_assembly_err_cntr),
+ STMMAC_MMC_STAT(mmc_rx_packet_smd_err_cntr),
+ STMMAC_MMC_STAT(mmc_rx_packet_assembly_ok_cntr),
+ STMMAC_MMC_STAT(mmc_rx_fpe_fragment_cntr),
+};
+#define STMMAC_MMC_STATS_LEN ARRAY_SIZE(stmmac_mmc)
+
+static const char stmmac_qstats_tx_string[][ETH_GSTRING_LEN] = {
+ "tx_pkt_n",
+ "tx_irq_n",
+#define STMMAC_TXQ_STATS ARRAY_SIZE(stmmac_qstats_tx_string)
+};
+
+static const char stmmac_qstats_rx_string[][ETH_GSTRING_LEN] = {
+ "rx_pkt_n",
+ "rx_irq_n",
+#define STMMAC_RXQ_STATS ARRAY_SIZE(stmmac_qstats_rx_string)
+};
+
+static void stmmac_ethtool_getdrvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (priv->plat->core_type == DWMAC_CORE_GMAC ||
+ priv->plat->core_type == DWMAC_CORE_GMAC4)
+ strscpy(info->driver, GMAC_ETHTOOL_NAME, sizeof(info->driver));
+ else if (priv->plat->core_type == DWMAC_CORE_XGMAC)
+ strscpy(info->driver, XGMAC_ETHTOOL_NAME, sizeof(info->driver));
+ else
+ strscpy(info->driver, MAC100_ETHTOOL_NAME,
+ sizeof(info->driver));
+
+ if (priv->plat->provide_bus_info)
+ strscpy(info->bus_info, dev_name(priv->device),
+ sizeof(info->bus_info));
+}
+
+static int stmmac_ethtool_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_ksettings_get(priv->phylink, cmd);
+}
+
+static int
+stmmac_ethtool_set_link_ksettings(struct net_device *dev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_ksettings_set(priv->phylink, cmd);
+}
+
+static u32 stmmac_ethtool_getmsglevel(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ return priv->msg_enable;
+}
+
+static void stmmac_ethtool_setmsglevel(struct net_device *dev, u32 level)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ priv->msg_enable = level;
+
+}
+
+static int stmmac_ethtool_get_regs_len(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC)
+ return XGMAC_REGSIZE * 4;
+ else if (priv->plat->core_type == DWMAC_CORE_GMAC4)
+ return GMAC4_REG_SPACE_SIZE;
+ return REG_SPACE_SIZE;
+}
+
+static void stmmac_ethtool_gregs(struct net_device *dev,
+ struct ethtool_regs *regs, void *space)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u32 *reg_space = (u32 *) space;
+
+ stmmac_dump_mac_regs(priv, priv->hw, reg_space);
+ stmmac_dump_dma_regs(priv, priv->ioaddr, reg_space);
+
+ /* Copy DMA registers to where ethtool expects them */
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4) {
+ /* GMAC4 dumps its DMA registers at its DMA_CHAN_BASE_ADDR */
+ memcpy(&reg_space[ETHTOOL_DMA_OFFSET],
+ &reg_space[GMAC4_DMA_CHAN_BASE_ADDR / 4],
+ NUM_DWMAC4_DMA_REGS * 4);
+ } else if (priv->plat->core_type != DWMAC_CORE_XGMAC) {
+ memcpy(&reg_space[ETHTOOL_DMA_OFFSET],
+ &reg_space[DMA_BUS_MODE / 4],
+ NUM_DWMAC1000_DMA_REGS * 4);
+ }
+}
+
+static int stmmac_nway_reset(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_nway_reset(priv->phylink);
+}
+
+static void stmmac_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_priv *priv = netdev_priv(netdev);
+
+ ring->rx_max_pending = DMA_MAX_RX_SIZE;
+ ring->tx_max_pending = DMA_MAX_TX_SIZE;
+ ring->rx_pending = priv->dma_conf.dma_rx_size;
+ ring->tx_pending = priv->dma_conf.dma_tx_size;
+}
+
+static int stmmac_set_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ if (ring->rx_mini_pending || ring->rx_jumbo_pending ||
+ ring->rx_pending < DMA_MIN_RX_SIZE ||
+ ring->rx_pending > DMA_MAX_RX_SIZE ||
+ !is_power_of_2(ring->rx_pending) ||
+ ring->tx_pending < DMA_MIN_TX_SIZE ||
+ ring->tx_pending > DMA_MAX_TX_SIZE ||
+ !is_power_of_2(ring->tx_pending))
+ return -EINVAL;
+
+ return stmmac_reinit_ringparam(netdev, ring->rx_pending,
+ ring->tx_pending);
+}
+
+static void
+stmmac_get_pauseparam(struct net_device *netdev,
+ struct ethtool_pauseparam *pause)
+{
+ struct stmmac_priv *priv = netdev_priv(netdev);
+
+ phylink_ethtool_get_pauseparam(priv->phylink, pause);
+}
+
+static int
+stmmac_set_pauseparam(struct net_device *netdev,
+ struct ethtool_pauseparam *pause)
+{
+ struct stmmac_priv *priv = netdev_priv(netdev);
+
+ return phylink_ethtool_set_pauseparam(priv->phylink, pause);
+}
+
+static u64 stmmac_get_rx_normal_irq_n(struct stmmac_priv *priv, int q)
+{
+ u64 total;
+ int cpu;
+
+ total = 0;
+ for_each_possible_cpu(cpu) {
+ struct stmmac_pcpu_stats *pcpu;
+ unsigned int start;
+ u64 irq_n;
+
+ pcpu = per_cpu_ptr(priv->xstats.pcpu_stats, cpu);
+ do {
+ start = u64_stats_fetch_begin(&pcpu->syncp);
+ irq_n = u64_stats_read(&pcpu->rx_normal_irq_n[q]);
+ } while (u64_stats_fetch_retry(&pcpu->syncp, start));
+ total += irq_n;
+ }
+ return total;
+}
+
+static u64 stmmac_get_tx_normal_irq_n(struct stmmac_priv *priv, int q)
+{
+ u64 total;
+ int cpu;
+
+ total = 0;
+ for_each_possible_cpu(cpu) {
+ struct stmmac_pcpu_stats *pcpu;
+ unsigned int start;
+ u64 irq_n;
+
+ pcpu = per_cpu_ptr(priv->xstats.pcpu_stats, cpu);
+ do {
+ start = u64_stats_fetch_begin(&pcpu->syncp);
+ irq_n = u64_stats_read(&pcpu->tx_normal_irq_n[q]);
+ } while (u64_stats_fetch_retry(&pcpu->syncp, start));
+ total += irq_n;
+ }
+ return total;
+}
+
+static void stmmac_get_per_qstats(struct stmmac_priv *priv, u64 *data)
+{
+ u32 tx_cnt = priv->plat->tx_queues_to_use;
+ u32 rx_cnt = priv->plat->rx_queues_to_use;
+ unsigned int start;
+ int q;
+
+ for (q = 0; q < tx_cnt; q++) {
+ struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[q];
+ u64 pkt_n;
+
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->napi_syncp);
+ pkt_n = u64_stats_read(&txq_stats->napi.tx_pkt_n);
+ } while (u64_stats_fetch_retry(&txq_stats->napi_syncp, start));
+
+ *data++ = pkt_n;
+ *data++ = stmmac_get_tx_normal_irq_n(priv, q);
+ }
+
+ for (q = 0; q < rx_cnt; q++) {
+ struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[q];
+ u64 pkt_n;
+
+ do {
+ start = u64_stats_fetch_begin(&rxq_stats->napi_syncp);
+ pkt_n = u64_stats_read(&rxq_stats->napi.rx_pkt_n);
+ } while (u64_stats_fetch_retry(&rxq_stats->napi_syncp, start));
+
+ *data++ = pkt_n;
+ *data++ = stmmac_get_rx_normal_irq_n(priv, q);
+ }
+}
+
+static void stmmac_get_ethtool_stats(struct net_device *dev,
+ struct ethtool_stats *dummy, u64 *data)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u32 rx_queues_count = priv->plat->rx_queues_to_use;
+ u32 tx_queues_count = priv->plat->tx_queues_to_use;
+ u64 napi_poll = 0, normal_irq_n = 0;
+ int i, j = 0, pos, ret;
+ unsigned long count;
+ unsigned int start;
+
+ if (priv->dma_cap.asp) {
+ for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) {
+ if (!stmmac_safety_feat_dump(priv, &priv->sstats, i,
+ &count, NULL))
+ data[j++] = count;
+ }
+ }
+
+ /* Update the DMA HW counters for dwmac10/100 */
+ ret = stmmac_dma_diagnostic_fr(priv, &priv->xstats, priv->ioaddr);
+ if (ret) {
+ /* If supported, for new GMAC chips expose the MMC counters */
+ if (priv->dma_cap.rmon) {
+ stmmac_mmc_read(priv, priv->mmcaddr, &priv->mmc);
+
+ for (i = 0; i < STMMAC_MMC_STATS_LEN; i++) {
+ char *p;
+ p = (char *)priv + stmmac_mmc[i].stat_offset;
+
+ data[j++] = (stmmac_mmc[i].sizeof_stat ==
+ sizeof(u64)) ? (*(u64 *)p) :
+ (*(u32 *)p);
+ }
+ }
+ if (priv->dma_cap.eee) {
+ int val = phylink_get_eee_err(priv->phylink);
+ if (val)
+ priv->xstats.phy_eee_wakeup_error_n = val;
+ }
+
+ if (priv->synopsys_id >= DWMAC_CORE_3_50)
+ stmmac_mac_debug(priv, priv->ioaddr,
+ (void *)&priv->xstats,
+ rx_queues_count, tx_queues_count);
+ }
+ for (i = 0; i < STMMAC_STATS_LEN; i++) {
+ char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset;
+ data[j++] = (stmmac_gstrings_stats[i].sizeof_stat ==
+ sizeof(u64)) ? (*(u64 *)p) : (*(u32 *)p);
+ }
+
+ pos = j;
+ for (i = 0; i < rx_queues_count; i++) {
+ struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[i];
+ struct stmmac_napi_rx_stats snapshot;
+ u64 n_irq;
+
+ j = pos;
+ do {
+ start = u64_stats_fetch_begin(&rxq_stats->napi_syncp);
+ snapshot = rxq_stats->napi;
+ } while (u64_stats_fetch_retry(&rxq_stats->napi_syncp, start));
+
+ data[j++] += u64_stats_read(&snapshot.rx_pkt_n);
+ n_irq = stmmac_get_rx_normal_irq_n(priv, i);
+ data[j++] += n_irq;
+ normal_irq_n += n_irq;
+ napi_poll += u64_stats_read(&snapshot.poll);
+ }
+
+ pos = j;
+ for (i = 0; i < tx_queues_count; i++) {
+ struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[i];
+ struct stmmac_napi_tx_stats napi_snapshot;
+ struct stmmac_q_tx_stats q_snapshot;
+ u64 n_irq;
+
+ j = pos;
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->q_syncp);
+ q_snapshot = txq_stats->q;
+ } while (u64_stats_fetch_retry(&txq_stats->q_syncp, start));
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->napi_syncp);
+ napi_snapshot = txq_stats->napi;
+ } while (u64_stats_fetch_retry(&txq_stats->napi_syncp, start));
+
+ data[j++] += u64_stats_read(&napi_snapshot.tx_pkt_n);
+ n_irq = stmmac_get_tx_normal_irq_n(priv, i);
+ data[j++] += n_irq;
+ normal_irq_n += n_irq;
+ data[j++] += u64_stats_read(&napi_snapshot.tx_clean);
+ data[j++] += u64_stats_read(&q_snapshot.tx_set_ic_bit) +
+ u64_stats_read(&napi_snapshot.tx_set_ic_bit);
+ data[j++] += u64_stats_read(&q_snapshot.tx_tso_frames);
+ data[j++] += u64_stats_read(&q_snapshot.tx_tso_nfrags);
+ napi_poll += u64_stats_read(&napi_snapshot.poll);
+ }
+ normal_irq_n += priv->xstats.rx_early_irq;
+ data[j++] = normal_irq_n;
+ data[j++] = napi_poll;
+
+ stmmac_get_per_qstats(priv, &data[j]);
+}
+
+static int stmmac_get_sset_count(struct net_device *netdev, int sset)
+{
+ struct stmmac_priv *priv = netdev_priv(netdev);
+ u32 tx_cnt = priv->plat->tx_queues_to_use;
+ u32 rx_cnt = priv->plat->rx_queues_to_use;
+ int i, len, safety_len = 0;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ len = STMMAC_STATS_LEN + STMMAC_QSTATS +
+ STMMAC_TXQ_STATS * tx_cnt +
+ STMMAC_RXQ_STATS * rx_cnt;
+
+ if (priv->dma_cap.rmon)
+ len += STMMAC_MMC_STATS_LEN;
+ if (priv->dma_cap.asp) {
+ for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) {
+ if (!stmmac_safety_feat_dump(priv,
+ &priv->sstats, i,
+ NULL, NULL))
+ safety_len++;
+ }
+
+ len += safety_len;
+ }
+
+ return len;
+ case ETH_SS_TEST:
+ return stmmac_selftest_get_count(priv);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void stmmac_get_qstats_string(struct stmmac_priv *priv, u8 *data)
+{
+ u32 tx_cnt = priv->plat->tx_queues_to_use;
+ u32 rx_cnt = priv->plat->rx_queues_to_use;
+ int q, stat;
+
+ for (q = 0; q < tx_cnt; q++) {
+ for (stat = 0; stat < STMMAC_TXQ_STATS; stat++) {
+ snprintf(data, ETH_GSTRING_LEN, "q%d_%s", q,
+ stmmac_qstats_tx_string[stat]);
+ data += ETH_GSTRING_LEN;
+ }
+ }
+ for (q = 0; q < rx_cnt; q++) {
+ for (stat = 0; stat < STMMAC_RXQ_STATS; stat++) {
+ snprintf(data, ETH_GSTRING_LEN, "q%d_%s", q,
+ stmmac_qstats_rx_string[stat]);
+ data += ETH_GSTRING_LEN;
+ }
+ }
+}
+
+static void stmmac_get_strings(struct net_device *dev, u32 stringset, u8 *data)
+{
+ int i;
+ u8 *p = data;
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ if (priv->dma_cap.asp) {
+ for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) {
+ const char *desc;
+ if (!stmmac_safety_feat_dump(priv,
+ &priv->sstats, i,
+ NULL, &desc)) {
+ memcpy(p, desc, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ }
+ }
+ }
+ if (priv->dma_cap.rmon)
+ for (i = 0; i < STMMAC_MMC_STATS_LEN; i++) {
+ memcpy(p, stmmac_mmc[i].stat_string,
+ ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ }
+ for (i = 0; i < STMMAC_STATS_LEN; i++) {
+ memcpy(p, stmmac_gstrings_stats[i].stat_string, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ }
+ for (i = 0; i < STMMAC_QSTATS; i++) {
+ memcpy(p, stmmac_qstats_string[i], ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ }
+ stmmac_get_qstats_string(priv, p);
+ break;
+ case ETH_SS_TEST:
+ stmmac_selftest_get_strings(priv, p);
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+}
+
+/* Currently only support WOL through Magic packet. */
+static void stmmac_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_get_wol(priv->phylink, wol);
+}
+
+static int stmmac_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_set_wol(priv->phylink, wol);
+}
+
+static int stmmac_ethtool_op_get_eee(struct net_device *dev,
+ struct ethtool_keee *edata)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_get_eee(priv->phylink, edata);
+}
+
+static int stmmac_ethtool_op_set_eee(struct net_device *dev,
+ struct ethtool_keee *edata)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return phylink_ethtool_set_eee(priv->phylink, edata);
+}
+
+static u32 stmmac_usec2riwt(u32 usec, struct stmmac_priv *priv)
+{
+ unsigned long clk = clk_get_rate(priv->plat->stmmac_clk);
+
+ if (!clk) {
+ clk = priv->plat->clk_ref_rate;
+ if (!clk)
+ return 0;
+ }
+
+ return (usec * (clk / 1000000)) / 256;
+}
+
+static u32 stmmac_riwt2usec(u32 riwt, struct stmmac_priv *priv)
+{
+ unsigned long clk = clk_get_rate(priv->plat->stmmac_clk);
+
+ if (!clk) {
+ clk = priv->plat->clk_ref_rate;
+ if (!clk)
+ return 0;
+ }
+
+ return (riwt * 256) / (clk / 1000000);
+}
+
+static int __stmmac_get_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *ec,
+ int queue)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u32 max_cnt;
+ u32 rx_cnt;
+ u32 tx_cnt;
+
+ rx_cnt = priv->plat->rx_queues_to_use;
+ tx_cnt = priv->plat->tx_queues_to_use;
+ max_cnt = max(rx_cnt, tx_cnt);
+
+ if (queue < 0)
+ queue = 0;
+ else if (queue >= max_cnt)
+ return -EINVAL;
+
+ if (queue < tx_cnt) {
+ ec->tx_coalesce_usecs = priv->tx_coal_timer[queue];
+ ec->tx_max_coalesced_frames = priv->tx_coal_frames[queue];
+ } else {
+ ec->tx_coalesce_usecs = 0;
+ ec->tx_max_coalesced_frames = 0;
+ }
+
+ if (priv->use_riwt && queue < rx_cnt) {
+ ec->rx_max_coalesced_frames = priv->rx_coal_frames[queue];
+ ec->rx_coalesce_usecs = stmmac_riwt2usec(priv->rx_riwt[queue],
+ priv);
+ } else {
+ ec->rx_max_coalesced_frames = 0;
+ ec->rx_coalesce_usecs = 0;
+ }
+
+ return 0;
+}
+
+static int stmmac_get_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *ec,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ return __stmmac_get_coalesce(dev, ec, -1);
+}
+
+static int stmmac_get_per_queue_coalesce(struct net_device *dev, u32 queue,
+ struct ethtool_coalesce *ec)
+{
+ return __stmmac_get_coalesce(dev, ec, queue);
+}
+
+static int __stmmac_set_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *ec,
+ int queue)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ bool all_queues = false;
+ unsigned int rx_riwt;
+ u32 max_cnt;
+ u32 rx_cnt;
+ u32 tx_cnt;
+
+ rx_cnt = priv->plat->rx_queues_to_use;
+ tx_cnt = priv->plat->tx_queues_to_use;
+ max_cnt = max(rx_cnt, tx_cnt);
+
+ if (queue < 0)
+ all_queues = true;
+ else if (queue >= max_cnt)
+ return -EINVAL;
+
+ if (priv->use_riwt) {
+ rx_riwt = stmmac_usec2riwt(ec->rx_coalesce_usecs, priv);
+
+ if ((rx_riwt > MAX_DMA_RIWT) || (rx_riwt < MIN_DMA_RIWT))
+ return -EINVAL;
+
+ if (all_queues) {
+ int i;
+
+ for (i = 0; i < rx_cnt; i++) {
+ priv->rx_riwt[i] = rx_riwt;
+ stmmac_rx_watchdog(priv, priv->ioaddr,
+ rx_riwt, i);
+ priv->rx_coal_frames[i] =
+ ec->rx_max_coalesced_frames;
+ }
+ } else if (queue < rx_cnt) {
+ priv->rx_riwt[queue] = rx_riwt;
+ stmmac_rx_watchdog(priv, priv->ioaddr,
+ rx_riwt, queue);
+ priv->rx_coal_frames[queue] =
+ ec->rx_max_coalesced_frames;
+ }
+ }
+
+ if ((ec->tx_coalesce_usecs == 0) &&
+ (ec->tx_max_coalesced_frames == 0))
+ return -EINVAL;
+
+ if ((ec->tx_coalesce_usecs > STMMAC_MAX_COAL_TX_TICK) ||
+ (ec->tx_max_coalesced_frames > STMMAC_TX_MAX_FRAMES))
+ return -EINVAL;
+
+ if (all_queues) {
+ int i;
+
+ for (i = 0; i < tx_cnt; i++) {
+ priv->tx_coal_frames[i] =
+ ec->tx_max_coalesced_frames;
+ priv->tx_coal_timer[i] =
+ ec->tx_coalesce_usecs;
+ }
+ } else if (queue < tx_cnt) {
+ priv->tx_coal_frames[queue] =
+ ec->tx_max_coalesced_frames;
+ priv->tx_coal_timer[queue] =
+ ec->tx_coalesce_usecs;
+ }
+
+ return 0;
+}
+
+static int stmmac_set_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *ec,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ return __stmmac_set_coalesce(dev, ec, -1);
+}
+
+static int stmmac_set_per_queue_coalesce(struct net_device *dev, u32 queue,
+ struct ethtool_coalesce *ec)
+{
+ return __stmmac_set_coalesce(dev, ec, queue);
+}
+
+static u32 stmmac_get_rx_ring_count(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return priv->plat->rx_queues_to_use;
+}
+
+static u32 stmmac_get_rxfh_key_size(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return sizeof(priv->rss.key);
+}
+
+static u32 stmmac_get_rxfh_indir_size(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ return ARRAY_SIZE(priv->rss.table);
+}
+
+static int stmmac_get_rxfh(struct net_device *dev,
+ struct ethtool_rxfh_param *rxfh)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int i;
+
+ if (rxfh->indir) {
+ for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++)
+ rxfh->indir[i] = priv->rss.table[i];
+ }
+
+ if (rxfh->key)
+ memcpy(rxfh->key, priv->rss.key, sizeof(priv->rss.key));
+ rxfh->hfunc = ETH_RSS_HASH_TOP;
+
+ return 0;
+}
+
+static int stmmac_set_rxfh(struct net_device *dev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int i;
+
+ if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
+ rxfh->hfunc != ETH_RSS_HASH_TOP)
+ return -EOPNOTSUPP;
+
+ if (rxfh->indir) {
+ for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++)
+ priv->rss.table[i] = rxfh->indir[i];
+ }
+
+ if (rxfh->key)
+ memcpy(priv->rss.key, rxfh->key, sizeof(priv->rss.key));
+
+ return stmmac_rss_configure(priv, priv->hw, &priv->rss,
+ priv->plat->rx_queues_to_use);
+}
+
+static void stmmac_get_channels(struct net_device *dev,
+ struct ethtool_channels *chan)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ chan->rx_count = priv->plat->rx_queues_to_use;
+ chan->tx_count = priv->plat->tx_queues_to_use;
+ chan->max_rx = priv->dma_cap.number_rx_queues;
+ chan->max_tx = priv->dma_cap.number_tx_queues;
+}
+
+static int stmmac_set_channels(struct net_device *dev,
+ struct ethtool_channels *chan)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (chan->rx_count > priv->dma_cap.number_rx_queues ||
+ chan->tx_count > priv->dma_cap.number_tx_queues ||
+ !chan->rx_count || !chan->tx_count)
+ return -EINVAL;
+
+ return stmmac_reinit_queues(dev, chan->rx_count, chan->tx_count);
+}
+
+static int stmmac_get_ts_info(struct net_device *dev,
+ struct kernel_ethtool_ts_info *info)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if ((priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp)) {
+
+ info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ if (priv->ptp_clock)
+ info->phc_index = ptp_clock_index(priv->ptp_clock);
+ else
+ info->phc_index = 0;
+
+ info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON);
+
+ info->rx_filters = ((1 << HWTSTAMP_FILTER_NONE) |
+ (1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V1_L4_SYNC) |
+ (1 << HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_SYNC) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
+ (1 << HWTSTAMP_FILTER_ALL));
+ return 0;
+ } else
+ return ethtool_op_get_ts_info(dev, info);
+}
+
+static int stmmac_get_mm(struct net_device *ndev,
+ struct ethtool_mm_state *state)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ u32 frag_size;
+
+ if (!stmmac_fpe_supported(priv))
+ return -EOPNOTSUPP;
+
+ state->rx_min_frag_size = ETH_ZLEN;
+ frag_size = stmmac_fpe_get_add_frag_size(priv);
+ state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(frag_size);
+
+ ethtool_mmsv_get_mm(&priv->fpe_cfg.mmsv, state);
+
+ return 0;
+}
+
+static int stmmac_set_mm(struct net_device *ndev, struct ethtool_mm_cfg *cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ u32 frag_size;
+ int err;
+
+ err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size,
+ &frag_size, extack);
+ if (err)
+ return err;
+
+ stmmac_fpe_set_add_frag_size(priv, frag_size);
+ ethtool_mmsv_set_mm(&priv->fpe_cfg.mmsv, cfg);
+
+ return 0;
+}
+
+static void stmmac_get_mm_stats(struct net_device *ndev,
+ struct ethtool_mm_stats *s)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct stmmac_counters *mmc = &priv->mmc;
+
+ if (!priv->dma_cap.rmon)
+ return;
+
+ stmmac_mmc_read(priv, priv->mmcaddr, mmc);
+
+ s->MACMergeFrameAssErrorCount = mmc->mmc_rx_packet_assembly_err_cntr;
+ s->MACMergeFrameAssOkCount = mmc->mmc_rx_packet_assembly_ok_cntr;
+ s->MACMergeFrameSmdErrorCount = mmc->mmc_rx_packet_smd_err_cntr;
+ s->MACMergeFragCountRx = mmc->mmc_rx_fpe_fragment_cntr;
+ s->MACMergeFragCountTx = mmc->mmc_tx_fpe_fragment_cntr;
+ s->MACMergeHoldCount = mmc->mmc_tx_hold_req_cntr;
+}
+
+static const struct ethtool_ops stmmac_ethtool_ops = {
+ .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
+ ETHTOOL_COALESCE_MAX_FRAMES,
+ .get_drvinfo = stmmac_ethtool_getdrvinfo,
+ .get_msglevel = stmmac_ethtool_getmsglevel,
+ .set_msglevel = stmmac_ethtool_setmsglevel,
+ .get_regs = stmmac_ethtool_gregs,
+ .get_regs_len = stmmac_ethtool_get_regs_len,
+ .get_link = ethtool_op_get_link,
+ .nway_reset = stmmac_nway_reset,
+ .get_ringparam = stmmac_get_ringparam,
+ .set_ringparam = stmmac_set_ringparam,
+ .get_pauseparam = stmmac_get_pauseparam,
+ .set_pauseparam = stmmac_set_pauseparam,
+ .self_test = stmmac_selftest_run,
+ .get_ethtool_stats = stmmac_get_ethtool_stats,
+ .get_strings = stmmac_get_strings,
+ .get_wol = stmmac_get_wol,
+ .set_wol = stmmac_set_wol,
+ .get_eee = stmmac_ethtool_op_get_eee,
+ .set_eee = stmmac_ethtool_op_set_eee,
+ .get_sset_count = stmmac_get_sset_count,
+ .get_rx_ring_count = stmmac_get_rx_ring_count,
+ .get_rxfh_key_size = stmmac_get_rxfh_key_size,
+ .get_rxfh_indir_size = stmmac_get_rxfh_indir_size,
+ .get_rxfh = stmmac_get_rxfh,
+ .set_rxfh = stmmac_set_rxfh,
+ .get_ts_info = stmmac_get_ts_info,
+ .get_coalesce = stmmac_get_coalesce,
+ .set_coalesce = stmmac_set_coalesce,
+ .get_per_queue_coalesce = stmmac_get_per_queue_coalesce,
+ .set_per_queue_coalesce = stmmac_set_per_queue_coalesce,
+ .get_channels = stmmac_get_channels,
+ .set_channels = stmmac_set_channels,
+ .get_link_ksettings = stmmac_ethtool_get_link_ksettings,
+ .set_link_ksettings = stmmac_ethtool_set_link_ksettings,
+ .get_mm = stmmac_get_mm,
+ .set_mm = stmmac_set_mm,
+ .get_mm_stats = stmmac_get_mm_stats,
+};
+
+void stmmac_set_ethtool_ops(struct net_device *netdev)
+{
+ netdev->ethtool_ops = &stmmac_ethtool_ops;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
new file mode 100644
index 000000000..c54c70224
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Furong Xu <0x1207@gmail.com>
+ * stmmac FPE(802.3 Qbu) handling
+ */
+#include "stmmac.h"
+#include "stmmac_fpe.h"
+#include "dwmac4.h"
+#include "dwmac5.h"
+#include "dwxgmac2.h"
+
+#define GMAC5_MAC_FPE_CTRL_STS 0x00000234
+#define XGMAC_MAC_FPE_CTRL_STS 0x00000280
+
+#define GMAC5_MTL_FPE_CTRL_STS 0x00000c90
+#define XGMAC_MTL_FPE_CTRL_STS 0x00001090
+/* Preemption Classification */
+#define FPE_MTL_PREEMPTION_CLASS GENMASK(15, 8)
+/* Additional Fragment Size of preempted frames */
+#define FPE_MTL_ADD_FRAG_SZ GENMASK(1, 0)
+
+#define STMMAC_MAC_FPE_CTRL_STS_TRSP BIT(19)
+#define STMMAC_MAC_FPE_CTRL_STS_TVER BIT(18)
+#define STMMAC_MAC_FPE_CTRL_STS_RRSP BIT(17)
+#define STMMAC_MAC_FPE_CTRL_STS_RVER BIT(16)
+#define STMMAC_MAC_FPE_CTRL_STS_SRSP BIT(2)
+#define STMMAC_MAC_FPE_CTRL_STS_SVER BIT(1)
+#define STMMAC_MAC_FPE_CTRL_STS_EFPE BIT(0)
+
+struct stmmac_fpe_reg {
+ const u32 mac_fpe_reg; /* offset of MAC_FPE_CTRL_STS */
+ const u32 mtl_fpe_reg; /* offset of MTL_FPE_CTRL_STS */
+ const u32 rxq_ctrl1_reg; /* offset of MAC_RxQ_Ctrl1 */
+ const u32 fprq_mask; /* Frame Preemption Residue Queue */
+ const u32 int_en_reg; /* offset of MAC_Interrupt_Enable */
+ const u32 int_en_bit; /* Frame Preemption Interrupt Enable */
+};
+
+bool stmmac_fpe_supported(struct stmmac_priv *priv)
+{
+ return priv->dma_cap.fpesel && priv->fpe_cfg.reg &&
+ priv->hw->mac->fpe_map_preemption_class;
+}
+
+static void stmmac_fpe_configure_tx(struct ethtool_mmsv *mmsv, bool tx_enable)
+{
+ struct stmmac_fpe_cfg *cfg = container_of(mmsv, struct stmmac_fpe_cfg, mmsv);
+ struct stmmac_priv *priv = container_of(cfg, struct stmmac_priv, fpe_cfg);
+ const struct stmmac_fpe_reg *reg = cfg->reg;
+ u32 num_rxq = priv->plat->rx_queues_to_use;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 value;
+
+ if (tx_enable) {
+ cfg->fpe_csr = STMMAC_MAC_FPE_CTRL_STS_EFPE;
+ value = readl(ioaddr + reg->rxq_ctrl1_reg);
+ value &= ~reg->fprq_mask;
+ /* Keep this SHIFT, FIELD_PREP() expects a constant mask :-/ */
+ value |= (num_rxq - 1) << __ffs(reg->fprq_mask);
+ writel(value, ioaddr + reg->rxq_ctrl1_reg);
+ } else {
+ cfg->fpe_csr = 0;
+ }
+ writel(cfg->fpe_csr, ioaddr + reg->mac_fpe_reg);
+}
+
+static void stmmac_fpe_configure_pmac(struct ethtool_mmsv *mmsv, bool pmac_enable)
+{
+ struct stmmac_fpe_cfg *cfg = container_of(mmsv, struct stmmac_fpe_cfg, mmsv);
+ struct stmmac_priv *priv = container_of(cfg, struct stmmac_priv, fpe_cfg);
+ const struct stmmac_fpe_reg *reg = cfg->reg;
+ void __iomem *ioaddr = priv->ioaddr;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&priv->hw->irq_ctrl_lock, flags);
+ value = readl(ioaddr + reg->int_en_reg);
+
+ if (pmac_enable) {
+ if (!(value & reg->int_en_bit)) {
+ /* Dummy read to clear any pending masked interrupts */
+ readl(ioaddr + reg->mac_fpe_reg);
+
+ value |= reg->int_en_bit;
+ }
+ } else {
+ value &= ~reg->int_en_bit;
+ }
+
+ writel(value, ioaddr + reg->int_en_reg);
+ spin_unlock_irqrestore(&priv->hw->irq_ctrl_lock, flags);
+}
+
+static void stmmac_fpe_send_mpacket(struct ethtool_mmsv *mmsv,
+ enum ethtool_mpacket type)
+{
+ struct stmmac_fpe_cfg *cfg = container_of(mmsv, struct stmmac_fpe_cfg, mmsv);
+ struct stmmac_priv *priv = container_of(cfg, struct stmmac_priv, fpe_cfg);
+ const struct stmmac_fpe_reg *reg = cfg->reg;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 value = cfg->fpe_csr;
+
+ if (type == ETHTOOL_MPACKET_VERIFY)
+ value |= STMMAC_MAC_FPE_CTRL_STS_SVER;
+ else if (type == ETHTOOL_MPACKET_RESPONSE)
+ value |= STMMAC_MAC_FPE_CTRL_STS_SRSP;
+
+ writel(value, ioaddr + reg->mac_fpe_reg);
+}
+
+static const struct ethtool_mmsv_ops stmmac_mmsv_ops = {
+ .configure_tx = stmmac_fpe_configure_tx,
+ .configure_pmac = stmmac_fpe_configure_pmac,
+ .send_mpacket = stmmac_fpe_send_mpacket,
+};
+
+static void stmmac_fpe_event_status(struct stmmac_priv *priv, int status)
+{
+ struct stmmac_fpe_cfg *fpe_cfg = &priv->fpe_cfg;
+ struct ethtool_mmsv *mmsv = &fpe_cfg->mmsv;
+
+ if (status == FPE_EVENT_UNKNOWN)
+ return;
+
+ if ((status & FPE_EVENT_RVER) == FPE_EVENT_RVER)
+ ethtool_mmsv_event_handle(mmsv, ETHTOOL_MMSV_LP_SENT_VERIFY_MPACKET);
+
+ if ((status & FPE_EVENT_TVER) == FPE_EVENT_TVER)
+ ethtool_mmsv_event_handle(mmsv, ETHTOOL_MMSV_LD_SENT_VERIFY_MPACKET);
+
+ if ((status & FPE_EVENT_RRSP) == FPE_EVENT_RRSP)
+ ethtool_mmsv_event_handle(mmsv, ETHTOOL_MMSV_LP_SENT_RESPONSE_MPACKET);
+}
+
+void stmmac_fpe_irq_status(struct stmmac_priv *priv)
+{
+ const struct stmmac_fpe_reg *reg = priv->fpe_cfg.reg;
+ void __iomem *ioaddr = priv->ioaddr;
+ struct net_device *dev = priv->dev;
+ int status = FPE_EVENT_UNKNOWN;
+ u32 value;
+
+ /* Reads from the MAC_FPE_CTRL_STS register should only be performed
+ * here, since the status flags of MAC_FPE_CTRL_STS are "clear on read"
+ */
+ value = readl(ioaddr + reg->mac_fpe_reg);
+
+ if (value & STMMAC_MAC_FPE_CTRL_STS_TRSP) {
+ status |= FPE_EVENT_TRSP;
+ netdev_dbg(dev, "FPE: Respond mPacket is transmitted\n");
+ }
+
+ if (value & STMMAC_MAC_FPE_CTRL_STS_TVER) {
+ status |= FPE_EVENT_TVER;
+ netdev_dbg(dev, "FPE: Verify mPacket is transmitted\n");
+ }
+
+ if (value & STMMAC_MAC_FPE_CTRL_STS_RRSP) {
+ status |= FPE_EVENT_RRSP;
+ netdev_dbg(dev, "FPE: Respond mPacket is received\n");
+ }
+
+ if (value & STMMAC_MAC_FPE_CTRL_STS_RVER) {
+ status |= FPE_EVENT_RVER;
+ netdev_dbg(dev, "FPE: Verify mPacket is received\n");
+ }
+
+ stmmac_fpe_event_status(priv, status);
+}
+
+void stmmac_fpe_init(struct stmmac_priv *priv)
+{
+ ethtool_mmsv_init(&priv->fpe_cfg.mmsv, priv->dev,
+ &stmmac_mmsv_ops);
+
+ if ((!priv->fpe_cfg.reg || !priv->hw->mac->fpe_map_preemption_class) &&
+ priv->dma_cap.fpesel)
+ dev_info(priv->device, "FPE is not supported by driver.\n");
+}
+
+int stmmac_fpe_get_add_frag_size(struct stmmac_priv *priv)
+{
+ const struct stmmac_fpe_reg *reg = priv->fpe_cfg.reg;
+ void __iomem *ioaddr = priv->ioaddr;
+
+ return FIELD_GET(FPE_MTL_ADD_FRAG_SZ, readl(ioaddr + reg->mtl_fpe_reg));
+}
+
+void stmmac_fpe_set_add_frag_size(struct stmmac_priv *priv, u32 add_frag_size)
+{
+ const struct stmmac_fpe_reg *reg = priv->fpe_cfg.reg;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 value;
+
+ value = readl(ioaddr + reg->mtl_fpe_reg);
+ writel(u32_replace_bits(value, add_frag_size, FPE_MTL_ADD_FRAG_SZ),
+ ioaddr + reg->mtl_fpe_reg);
+}
+
+#define ALG_ERR_MSG "TX algorithm SP is not suitable for one-to-many mapping"
+#define WEIGHT_ERR_MSG "TXQ weight %u differs across other TXQs in TC: [%u]"
+
+int dwmac5_fpe_map_preemption_class(struct net_device *ndev,
+ struct netlink_ext_ack *extack, u32 pclass)
+{
+ u32 val, offset, count, queue_weight, preemptible_txqs = 0;
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int num_tc = netdev_get_num_tc(ndev);
+
+ if (!pclass)
+ goto update_mapping;
+
+ /* DWMAC CORE4+ can not program TC:TXQ mapping to hardware.
+ *
+ * Synopsys Databook:
+ * "The number of Tx DMA channels is equal to the number of Tx queues,
+ * and is direct one-to-one mapping."
+ */
+ for (u32 tc = 0; tc < num_tc; tc++) {
+ count = ndev->tc_to_txq[tc].count;
+ offset = ndev->tc_to_txq[tc].offset;
+
+ if (pclass & BIT(tc))
+ preemptible_txqs |= GENMASK(offset + count - 1, offset);
+
+ /* This is 1:1 mapping, go to next TC */
+ if (count == 1)
+ continue;
+
+ if (priv->plat->tx_sched_algorithm == MTL_TX_ALGORITHM_SP) {
+ NL_SET_ERR_MSG_MOD(extack, ALG_ERR_MSG);
+ return -EINVAL;
+ }
+
+ queue_weight = priv->plat->tx_queues_cfg[offset].weight;
+
+ for (u32 i = 1; i < count; i++) {
+ if (priv->plat->tx_queues_cfg[offset + i].weight !=
+ queue_weight) {
+ NL_SET_ERR_MSG_FMT_MOD(extack, WEIGHT_ERR_MSG,
+ queue_weight, tc);
+ return -EINVAL;
+ }
+ }
+ }
+
+update_mapping:
+ val = readl(priv->ioaddr + GMAC5_MTL_FPE_CTRL_STS);
+ writel(u32_replace_bits(val, preemptible_txqs, FPE_MTL_PREEMPTION_CLASS),
+ priv->ioaddr + GMAC5_MTL_FPE_CTRL_STS);
+
+ return 0;
+}
+
+int dwxgmac3_fpe_map_preemption_class(struct net_device *ndev,
+ struct netlink_ext_ack *extack, u32 pclass)
+{
+ u32 val, offset, count, preemptible_txqs = 0;
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int num_tc = netdev_get_num_tc(ndev);
+
+ if (!num_tc) {
+ /* Restore default TC:Queue mapping */
+ for (u32 i = 0; i < priv->plat->tx_queues_to_use; i++) {
+ val = readl(priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(i));
+ writel(u32_replace_bits(val, i, XGMAC_Q2TCMAP),
+ priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(i));
+ }
+ }
+
+ /* Synopsys Databook:
+ * "All Queues within a traffic class are selected in a round robin
+ * fashion (when packets are available) when the traffic class is
+ * selected by the scheduler for packet transmission. This is true for
+ * any of the scheduling algorithms."
+ */
+ for (u32 tc = 0; tc < num_tc; tc++) {
+ count = ndev->tc_to_txq[tc].count;
+ offset = ndev->tc_to_txq[tc].offset;
+
+ if (pclass & BIT(tc))
+ preemptible_txqs |= GENMASK(offset + count - 1, offset);
+
+ for (u32 i = 0; i < count; i++) {
+ val = readl(priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(offset + i));
+ writel(u32_replace_bits(val, tc, XGMAC_Q2TCMAP),
+ priv->ioaddr + XGMAC_MTL_TXQ_OPMODE(offset + i));
+ }
+ }
+
+ val = readl(priv->ioaddr + XGMAC_MTL_FPE_CTRL_STS);
+ writel(u32_replace_bits(val, preemptible_txqs, FPE_MTL_PREEMPTION_CLASS),
+ priv->ioaddr + XGMAC_MTL_FPE_CTRL_STS);
+
+ return 0;
+}
+
+const struct stmmac_fpe_reg dwmac5_fpe_reg = {
+ .mac_fpe_reg = GMAC5_MAC_FPE_CTRL_STS,
+ .mtl_fpe_reg = GMAC5_MTL_FPE_CTRL_STS,
+ .rxq_ctrl1_reg = GMAC_RXQ_CTRL1,
+ .fprq_mask = GMAC_RXQCTRL_FPRQ,
+ .int_en_reg = GMAC_INT_EN,
+ .int_en_bit = GMAC_INT_FPE_EN,
+};
+
+const struct stmmac_fpe_reg dwxgmac3_fpe_reg = {
+ .mac_fpe_reg = XGMAC_MAC_FPE_CTRL_STS,
+ .mtl_fpe_reg = XGMAC_MTL_FPE_CTRL_STS,
+ .rxq_ctrl1_reg = XGMAC_RXQ_CTRL1,
+ .fprq_mask = XGMAC_FPRQ,
+ .int_en_reg = XGMAC_INT_EN,
+ .int_en_bit = XGMAC_FPEIE,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.h
new file mode 100644
index 000000000..3fc46acf7
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Furong Xu <0x1207@gmail.com>
+ * stmmac FPE(802.3 Qbu) handling
+ */
+#ifndef _STMMAC_FPE_H_
+#define _STMMAC_FPE_H_
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+
+struct stmmac_priv;
+
+bool stmmac_fpe_supported(struct stmmac_priv *priv);
+void stmmac_fpe_init(struct stmmac_priv *priv);
+void stmmac_fpe_irq_status(struct stmmac_priv *priv);
+int stmmac_fpe_get_add_frag_size(struct stmmac_priv *priv);
+void stmmac_fpe_set_add_frag_size(struct stmmac_priv *priv, u32 add_frag_size);
+
+int dwmac5_fpe_map_preemption_class(struct net_device *ndev,
+ struct netlink_ext_ack *extack, u32 pclass);
+int dwxgmac3_fpe_map_preemption_class(struct net_device *ndev,
+ struct netlink_ext_ack *extack, u32 pclass);
+
+extern const struct stmmac_fpe_reg dwmac5_fpe_reg;
+extern const struct stmmac_fpe_reg dwxgmac3_fpe_reg;
+
+#endif
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c
new file mode 100644
index 000000000..b9a985fa7
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ Copyright (C) 2013 Vayavya Labs Pvt Ltd
+
+ This implements all the API for managing HW timestamp & PTP.
+
+
+ Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/delay.h>
+#include <linux/ptp_clock_kernel.h>
+#include "common.h"
+#include "stmmac_ptp.h"
+#include "dwmac4.h"
+#include "stmmac.h"
+
+#define STMMAC_HWTS_CFG_MASK (PTP_TCR_TSENA | PTP_TCR_TSCFUPDT | \
+ PTP_TCR_TSINIT | PTP_TCR_TSUPDT | \
+ PTP_TCR_TSCTRLSSR | PTP_TCR_SNAPTYPSEL_1 | \
+ PTP_TCR_TSIPV4ENA | PTP_TCR_TSIPV6ENA | \
+ PTP_TCR_TSEVNTENA | PTP_TCR_TSMSTRENA | \
+ PTP_TCR_TSVER2ENA | PTP_TCR_TSIPENA | \
+ PTP_TCR_TSTRIG | PTP_TCR_TSENALL)
+
+static void config_hw_tstamping(void __iomem *ioaddr, u32 data)
+{
+ u32 regval = readl(ioaddr + PTP_TCR);
+
+ regval &= ~STMMAC_HWTS_CFG_MASK;
+ regval |= data;
+
+ writel(regval, ioaddr + PTP_TCR);
+}
+
+static void config_sub_second_increment(void __iomem *ioaddr,
+ u32 ptp_clock, int gmac4, u32 *ssinc)
+{
+ u32 value = readl(ioaddr + PTP_TCR);
+ unsigned long data;
+ u32 reg_value;
+
+ /* For GMAC3.x, 4.x versions, in "fine adjustment mode" set sub-second
+ * increment to twice the number of nanoseconds of a clock cycle.
+ * The calculation of the default_addend value by the caller will set it
+ * to mid-range = 2^31 when the remainder of this division is zero,
+ * which will make the accumulator overflow once every 2 ptp_clock
+ * cycles, adding twice the number of nanoseconds of a clock cycle :
+ * 2000000000ULL / ptp_clock.
+ */
+ if (value & PTP_TCR_TSCFUPDT)
+ data = (2000000000ULL / ptp_clock);
+ else
+ data = (1000000000ULL / ptp_clock);
+
+ /* 0.465ns accuracy */
+ if (!(value & PTP_TCR_TSCTRLSSR))
+ data = (data * 1000) / 465;
+
+ if (data > PTP_SSIR_SSINC_MAX)
+ data = PTP_SSIR_SSINC_MAX;
+
+ reg_value = data;
+ if (gmac4)
+ reg_value <<= GMAC4_PTP_SSIR_SSINC_SHIFT;
+
+ writel(reg_value, ioaddr + PTP_SSIR);
+
+ if (ssinc)
+ *ssinc = data;
+}
+
+static void hwtstamp_correct_latency(struct stmmac_priv *priv)
+{
+ void __iomem *ioaddr = priv->ptpaddr;
+ u32 reg_tsic, reg_tsicsns;
+ u32 reg_tsec, reg_tsecsns;
+ u64 scaled_ns;
+ u32 val;
+
+ /* MAC-internal ingress latency */
+ scaled_ns = readl(ioaddr + PTP_TS_INGR_LAT);
+
+ /* See section 11.7.2.5.3.1 "Ingress Correction" on page 4001 of
+ * i.MX8MP Applications Processor Reference Manual Rev. 1, 06/2021
+ */
+ val = readl(ioaddr + PTP_TCR);
+ if (val & PTP_TCR_TSCTRLSSR)
+ /* nanoseconds field is in decimal format with granularity of 1ns/bit */
+ scaled_ns = ((u64)NSEC_PER_SEC << 16) - scaled_ns;
+ else
+ /* nanoseconds field is in binary format with granularity of ~0.466ns/bit */
+ scaled_ns = ((1ULL << 31) << 16) -
+ DIV_U64_ROUND_CLOSEST(scaled_ns * PSEC_PER_NSEC, 466U);
+
+ reg_tsic = scaled_ns >> 16;
+ reg_tsicsns = scaled_ns & 0xff00;
+
+ /* set bit 31 for 2's compliment */
+ reg_tsic |= BIT(31);
+
+ writel(reg_tsic, ioaddr + PTP_TS_INGR_CORR_NS);
+ writel(reg_tsicsns, ioaddr + PTP_TS_INGR_CORR_SNS);
+
+ /* MAC-internal egress latency */
+ scaled_ns = readl(ioaddr + PTP_TS_EGR_LAT);
+
+ reg_tsec = scaled_ns >> 16;
+ reg_tsecsns = scaled_ns & 0xff00;
+
+ writel(reg_tsec, ioaddr + PTP_TS_EGR_CORR_NS);
+ writel(reg_tsecsns, ioaddr + PTP_TS_EGR_CORR_SNS);
+}
+
+static int init_systime(void __iomem *ioaddr, u32 sec, u32 nsec)
+{
+ u32 value;
+
+ writel(sec, ioaddr + PTP_STSUR);
+ writel(nsec, ioaddr + PTP_STNSUR);
+ /* issue command to initialize the system time value */
+ value = readl(ioaddr + PTP_TCR);
+ value |= PTP_TCR_TSINIT;
+ writel(value, ioaddr + PTP_TCR);
+
+ /* wait for present system time initialize to complete */
+ return readl_poll_timeout_atomic(ioaddr + PTP_TCR, value,
+ !(value & PTP_TCR_TSINIT),
+ 10, 100000);
+}
+
+static int config_addend(void __iomem *ioaddr, u32 addend)
+{
+ u32 value;
+
+ writel(addend, ioaddr + PTP_TAR);
+ /* issue command to update the addend value */
+ value = readl(ioaddr + PTP_TCR);
+ value |= PTP_TCR_TSADDREG;
+ writel(value, ioaddr + PTP_TCR);
+
+ /* wait for present addend update to complete */
+ return readl_poll_timeout_atomic(ioaddr + PTP_TCR, value,
+ !(value & PTP_TCR_TSADDREG),
+ 10, 100000);
+}
+
+static int adjust_systime(void __iomem *ioaddr, u32 sec, u32 nsec,
+ int add_sub, int gmac4)
+{
+ u32 value;
+
+ if (add_sub) {
+ /* If the new sec value needs to be subtracted with
+ * the system time, then MAC_STSUR reg should be
+ * programmed with (2^32 – <new_sec_value>)
+ */
+ if (gmac4)
+ sec = -sec;
+
+ value = readl(ioaddr + PTP_TCR);
+ if (value & PTP_TCR_TSCTRLSSR)
+ nsec = (PTP_DIGITAL_ROLLOVER_MODE - nsec);
+ else
+ nsec = (PTP_BINARY_ROLLOVER_MODE - nsec);
+ }
+
+ writel(sec, ioaddr + PTP_STSUR);
+ value = (add_sub << PTP_STNSUR_ADDSUB_SHIFT) | nsec;
+ writel(value, ioaddr + PTP_STNSUR);
+
+ /* issue command to initialize the system time value */
+ value = readl(ioaddr + PTP_TCR);
+ value |= PTP_TCR_TSUPDT;
+ writel(value, ioaddr + PTP_TCR);
+
+ /* wait for present system time adjust/update to complete */
+ return readl_poll_timeout_atomic(ioaddr + PTP_TCR, value,
+ !(value & PTP_TCR_TSUPDT),
+ 10, 100000);
+}
+
+static void get_systime(void __iomem *ioaddr, u64 *systime)
+{
+ u64 ns, sec0, sec1;
+
+ /* Get the TSS value */
+ sec1 = readl_relaxed(ioaddr + PTP_STSR);
+ do {
+ sec0 = sec1;
+ /* Get the TSSS value */
+ ns = readl_relaxed(ioaddr + PTP_STNSR);
+ /* Get the TSS value */
+ sec1 = readl_relaxed(ioaddr + PTP_STSR);
+ } while (sec0 != sec1);
+
+ if (systime)
+ *systime = ns + (sec1 * 1000000000ULL);
+}
+
+static void get_ptptime(void __iomem *ptpaddr, u64 *ptp_time)
+{
+ u64 ns;
+
+ ns = readl(ptpaddr + PTP_ATNR);
+ ns += (u64)readl(ptpaddr + PTP_ATSR) * NSEC_PER_SEC;
+
+ *ptp_time = ns;
+}
+
+static void timestamp_interrupt(struct stmmac_priv *priv)
+{
+ u32 num_snapshot, ts_status, tsync_int;
+ struct ptp_clock_event event;
+ u32 acr_value, channel;
+ unsigned long flags;
+ u64 ptp_time;
+ int i;
+
+ if (priv->plat->flags & STMMAC_FLAG_INT_SNAPSHOT_EN) {
+ wake_up(&priv->tstamp_busy_wait);
+ return;
+ }
+
+ tsync_int = readl(priv->ioaddr + GMAC_INT_STATUS) & GMAC_INT_TSIE;
+
+ if (!tsync_int)
+ return;
+
+ /* Read timestamp status to clear interrupt from either external
+ * timestamp or start/end of PPS.
+ */
+ ts_status = readl(priv->ioaddr + GMAC_TIMESTAMP_STATUS);
+
+ if (!(priv->plat->flags & STMMAC_FLAG_EXT_SNAPSHOT_EN))
+ return;
+
+ num_snapshot = (ts_status & GMAC_TIMESTAMP_ATSNS_MASK) >>
+ GMAC_TIMESTAMP_ATSNS_SHIFT;
+
+ acr_value = readl(priv->ptpaddr + PTP_ACR);
+ channel = ilog2(FIELD_GET(PTP_ACR_MASK, acr_value));
+
+ for (i = 0; i < num_snapshot; i++) {
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ get_ptptime(priv->ptpaddr, &ptp_time);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = channel;
+ event.timestamp = ptp_time;
+ ptp_clock_event(priv->ptp_clock, &event);
+ }
+}
+
+const struct stmmac_hwtimestamp stmmac_ptp = {
+ .config_hw_tstamping = config_hw_tstamping,
+ .init_systime = init_systime,
+ .config_sub_second_increment = config_sub_second_increment,
+ .config_addend = config_addend,
+ .adjust_systime = adjust_systime,
+ .get_systime = get_systime,
+ .get_ptptime = get_ptptime,
+ .timestamp_interrupt = timestamp_interrupt,
+ .hwtstamp_correct_latency = hwtstamp_correct_latency,
+};
+
+const struct stmmac_hwtimestamp dwmac1000_ptp = {
+ .config_hw_tstamping = config_hw_tstamping,
+ .init_systime = init_systime,
+ .config_sub_second_increment = config_sub_second_increment,
+ .config_addend = config_addend,
+ .adjust_systime = adjust_systime,
+ .get_systime = get_systime,
+ .get_ptptime = dwmac1000_get_ptptime,
+ .timestamp_interrupt = dwmac1000_timestamp_interrupt,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.c
new file mode 100644
index 000000000..5c5dd502f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCI bus helpers for STMMAC driver
+ * Copyright (C) 2025 Yao Zi <ziyao@disroot.org>
+ */
+
+#include <linux/device.h>
+#include <linux/pci.h>
+
+#include "stmmac_libpci.h"
+
+int stmmac_pci_plat_suspend(struct device *dev, void *bsp_priv)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int ret;
+
+ ret = pci_save_state(pdev);
+ if (ret)
+ return ret;
+
+ pci_disable_device(pdev);
+ pci_wake_from_d3(pdev, true);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(stmmac_pci_plat_suspend);
+
+int stmmac_pci_plat_resume(struct device *dev, void *bsp_priv)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int ret;
+
+ pci_restore_state(pdev);
+ pci_set_power_state(pdev, PCI_D0);
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(stmmac_pci_plat_resume);
+
+MODULE_DESCRIPTION("STMMAC PCI helper library");
+MODULE_AUTHOR("Yao Zi <ziyao@disroot.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.h
new file mode 100644
index 000000000..71553184f
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_libpci.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Yao Zi <ziyao@disroot.org>
+ */
+
+#ifndef __STMMAC_LIBPCI_H__
+#define __STMMAC_LIBPCI_H__
+
+int stmmac_pci_plat_suspend(struct device *dev, void *bsp_priv);
+int stmmac_pci_plat_resume(struct device *dev, void *bsp_priv);
+
+#endif /* __STMMAC_LIBPCI_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
new file mode 100644
index 000000000..2a0d7eff8
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -0,0 +1,8435 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This is the driver for the ST MAC 10/100/1000 on-chip Ethernet controllers.
+ ST Ethernet IPs are built around a Synopsys IP Core.
+
+ Copyright(C) 2007-2011 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+
+ Documentation available at:
+ http://www.stlinux.com
+ Support available at:
+ https://bugzilla.stlinux.com/
+*******************************************************************************/
+
+#include <linux/circ_buf.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/ethtool.h>
+#include <linux/if_ether.h>
+#include <linux/crc32.h>
+#include <linux/mii.h>
+#include <linux/if.h>
+#include <linux/if_vlan.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/prefetch.h>
+#include <linux/pinctrl/consumer.h>
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#endif /* CONFIG_DEBUG_FS */
+#include <linux/net_tstamp.h>
+#include <linux/phylink.h>
+#include <linux/udp.h>
+#include <linux/bpf_trace.h>
+#include <net/devlink.h>
+#include <net/page_pool/helpers.h>
+#include <net/pkt_cls.h>
+#include <net/xdp_sock_drv.h>
+#include "stmmac_ptp.h"
+#include "stmmac_fpe.h"
+#include "stmmac.h"
+#include "stmmac_pcs.h"
+#include "stmmac_xdp.h"
+#include <linux/reset.h>
+#include <linux/of_mdio.h>
+#include "dwmac1000.h"
+#include "dwxgmac2.h"
+#include "hwif.h"
+
+/* As long as the interface is active, we keep the timestamping counter enabled
+ * with fine resolution and binary rollover. This avoid non-monotonic behavior
+ * (clock jumps) when changing timestamping settings at runtime.
+ */
+#define STMMAC_HWTS_ACTIVE (PTP_TCR_TSENA | PTP_TCR_TSCTRLSSR)
+
+#define STMMAC_ALIGN(x) ALIGN(ALIGN(x, SMP_CACHE_BYTES), 16)
+#define TSO_MAX_BUFF_SIZE (SZ_16K - 1)
+
+/* Module parameters */
+#define TX_TIMEO 5000
+static int watchdog = TX_TIMEO;
+module_param(watchdog, int, 0644);
+MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds (default 5s)");
+
+static int debug = -1;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Message Level (-1: default, 0: no output, 16: all)");
+
+static int phyaddr = -1;
+module_param(phyaddr, int, 0444);
+MODULE_PARM_DESC(phyaddr, "Physical device address");
+
+#define STMMAC_TX_THRESH(x) ((x)->dma_conf.dma_tx_size / 4)
+
+/* Limit to make sure XDP TX and slow path can coexist */
+#define STMMAC_XSK_TX_BUDGET_MAX 256
+#define STMMAC_TX_XSK_AVAIL 16
+#define STMMAC_RX_FILL_BATCH 16
+
+#define STMMAC_XDP_PASS 0
+#define STMMAC_XDP_CONSUMED BIT(0)
+#define STMMAC_XDP_TX BIT(1)
+#define STMMAC_XDP_REDIRECT BIT(2)
+#define STMMAC_XSK_CONSUMED BIT(3)
+
+static int flow_ctrl = 0xdead;
+module_param(flow_ctrl, int, 0644);
+MODULE_PARM_DESC(flow_ctrl, "Flow control ability [on/off] (obsolete)");
+
+static int pause = PAUSE_TIME;
+module_param(pause, int, 0644);
+MODULE_PARM_DESC(pause, "Flow Control Pause Time (units of 512 bit times)");
+
+#define TC_DEFAULT 64
+static int tc = TC_DEFAULT;
+module_param(tc, int, 0644);
+MODULE_PARM_DESC(tc, "DMA threshold control value");
+
+/* This is unused */
+#define DEFAULT_BUFSIZE 1536
+static int buf_sz = DEFAULT_BUFSIZE;
+module_param(buf_sz, int, 0644);
+MODULE_PARM_DESC(buf_sz, "DMA buffer size");
+
+static const u32 default_msg_level = (NETIF_MSG_DRV | NETIF_MSG_PROBE |
+ NETIF_MSG_LINK | NETIF_MSG_IFUP |
+ NETIF_MSG_IFDOWN | NETIF_MSG_TIMER);
+
+#define STMMAC_DEFAULT_LPI_TIMER 1000
+static unsigned int eee_timer = STMMAC_DEFAULT_LPI_TIMER;
+module_param(eee_timer, uint, 0644);
+MODULE_PARM_DESC(eee_timer, "LPI tx expiration time in msec");
+#define STMMAC_LPI_T(x) (jiffies + usecs_to_jiffies(x))
+
+/* By default the driver will use the ring mode to manage tx and rx descriptors,
+ * but allow user to force to use the chain instead of the ring
+ */
+static unsigned int chain_mode;
+module_param(chain_mode, int, 0444);
+MODULE_PARM_DESC(chain_mode, "To use chain instead of ring mode");
+
+static const char *stmmac_dwmac_actphyif[8] = {
+ [PHY_INTF_SEL_GMII_MII] = "GMII/MII",
+ [PHY_INTF_SEL_RGMII] = "RGMII",
+ [PHY_INTF_SEL_SGMII] = "SGMII",
+ [PHY_INTF_SEL_TBI] = "TBI",
+ [PHY_INTF_SEL_RMII] = "RMII",
+ [PHY_INTF_SEL_RTBI] = "RTBI",
+ [PHY_INTF_SEL_SMII] = "SMII",
+ [PHY_INTF_SEL_REVMII] = "REVMII",
+};
+
+static const char *stmmac_dwxgmac_phyif[4] = {
+ [PHY_INTF_GMII] = "GMII",
+ [PHY_INTF_RGMII] = "RGMII",
+};
+
+static irqreturn_t stmmac_interrupt(int irq, void *dev_id);
+/* For MSI interrupts handling */
+static irqreturn_t stmmac_mac_interrupt(int irq, void *dev_id);
+static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id);
+static irqreturn_t stmmac_msi_intr_tx(int irq, void *data);
+static irqreturn_t stmmac_msi_intr_rx(int irq, void *data);
+static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue);
+static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue);
+static void stmmac_reset_queues_param(struct stmmac_priv *priv);
+static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue);
+static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue);
+static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode,
+ u32 rxmode, u32 chan);
+static void stmmac_vlan_restore(struct stmmac_priv *priv);
+
+#ifdef CONFIG_DEBUG_FS
+static const struct net_device_ops stmmac_netdev_ops;
+static void stmmac_init_fs(struct net_device *dev);
+static void stmmac_exit_fs(struct net_device *dev);
+#endif
+
+#define STMMAC_COAL_TIMER(x) (ns_to_ktime((x) * NSEC_PER_USEC))
+
+struct stmmac_devlink_priv {
+ struct stmmac_priv *stmmac_priv;
+};
+
+enum stmmac_dl_param_id {
+ STMMAC_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
+ STMMAC_DEVLINK_PARAM_ID_TS_COARSE,
+};
+
+/**
+ * stmmac_set_clk_tx_rate() - set the clock rate for the MAC transmit clock
+ * @bsp_priv: BSP private data structure (unused)
+ * @clk_tx_i: the transmit clock
+ * @interface: the selected interface mode
+ * @speed: the speed that the MAC will be operating at
+ *
+ * Set the transmit clock rate for the MAC, normally 2.5MHz for 10Mbps,
+ * 25MHz for 100Mbps and 125MHz for 1Gbps. This is suitable for at least
+ * MII, GMII, RGMII and RMII interface modes. Platforms can hook this into
+ * the plat_data->set_clk_tx_rate method directly, call it via their own
+ * implementation, or implement their own method should they have more
+ * complex requirements. It is intended to only be used in this method.
+ *
+ * plat_data->clk_tx_i must be filled in.
+ */
+int stmmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i,
+ phy_interface_t interface, int speed)
+{
+ long rate = rgmii_clock(speed);
+
+ /* Silently ignore unsupported speeds as rgmii_clock() only
+ * supports 10, 100 and 1000Mbps. We do not want to spit
+ * errors for 2500 and higher speeds here.
+ */
+ if (rate < 0)
+ return 0;
+
+ return clk_set_rate(clk_tx_i, rate);
+}
+EXPORT_SYMBOL_GPL(stmmac_set_clk_tx_rate);
+
+/**
+ * stmmac_axi_blen_to_mask() - convert a burst length array to reg value
+ * @regval: pointer to a u32 for the resulting register value
+ * @blen: pointer to an array of u32 containing the burst length values in bytes
+ * @len: the number of entries in the @blen array
+ */
+void stmmac_axi_blen_to_mask(u32 *regval, const u32 *blen, size_t len)
+{
+ size_t i;
+ u32 val;
+
+ for (val = i = 0; i < len; i++) {
+ u32 burst = blen[i];
+
+ /* Burst values of zero must be skipped. */
+ if (!burst)
+ continue;
+
+ /* The valid range for the burst length is 4 to 256 inclusive,
+ * and it must be a power of two.
+ */
+ if (burst < 4 || burst > 256 || !is_power_of_2(burst)) {
+ pr_err("stmmac: invalid burst length %u at index %zu\n",
+ burst, i);
+ continue;
+ }
+
+ /* Since burst is a power of two, and the register field starts
+ * with burst = 4, shift right by two bits so bit 0 of the field
+ * corresponds with the minimum value.
+ */
+ val |= burst >> 2;
+ }
+
+ *regval = FIELD_PREP(DMA_AXI_BLEN_MASK, val);
+}
+EXPORT_SYMBOL_GPL(stmmac_axi_blen_to_mask);
+
+/**
+ * stmmac_verify_args - verify the driver parameters.
+ * Description: it checks the driver parameters and set a default in case of
+ * errors.
+ */
+static void stmmac_verify_args(void)
+{
+ if (unlikely(watchdog < 0))
+ watchdog = TX_TIMEO;
+ if (unlikely((pause < 0) || (pause > 0xffff)))
+ pause = PAUSE_TIME;
+
+ if (flow_ctrl != 0xdead)
+ pr_warn("stmmac: module parameter 'flow_ctrl' is obsolete - please remove from your module configuration\n");
+}
+
+static void __stmmac_disable_all_queues(struct stmmac_priv *priv)
+{
+ u8 rx_queues_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_queues_cnt = priv->plat->tx_queues_to_use;
+ u8 maxq = max(rx_queues_cnt, tx_queues_cnt);
+ u8 queue;
+
+ for (queue = 0; queue < maxq; queue++) {
+ struct stmmac_channel *ch = &priv->channel[queue];
+
+ if (stmmac_xdp_is_enabled(priv) &&
+ test_bit(queue, priv->af_xdp_zc_qps)) {
+ napi_disable(&ch->rxtx_napi);
+ continue;
+ }
+
+ if (queue < rx_queues_cnt)
+ napi_disable(&ch->rx_napi);
+ if (queue < tx_queues_cnt)
+ napi_disable(&ch->tx_napi);
+ }
+}
+
+/**
+ * stmmac_disable_all_queues - Disable all queues
+ * @priv: driver private structure
+ */
+static void stmmac_disable_all_queues(struct stmmac_priv *priv)
+{
+ u8 rx_queues_cnt = priv->plat->rx_queues_to_use;
+ struct stmmac_rx_queue *rx_q;
+ u8 queue;
+
+ /* synchronize_rcu() needed for pending XDP buffers to drain */
+ for (queue = 0; queue < rx_queues_cnt; queue++) {
+ rx_q = &priv->dma_conf.rx_queue[queue];
+ if (rx_q->xsk_pool) {
+ synchronize_rcu();
+ break;
+ }
+ }
+
+ __stmmac_disable_all_queues(priv);
+}
+
+/**
+ * stmmac_enable_all_queues - Enable all queues
+ * @priv: driver private structure
+ */
+static void stmmac_enable_all_queues(struct stmmac_priv *priv)
+{
+ u8 rx_queues_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_queues_cnt = priv->plat->tx_queues_to_use;
+ u8 maxq = max(rx_queues_cnt, tx_queues_cnt);
+ u8 queue;
+
+ for (queue = 0; queue < maxq; queue++) {
+ struct stmmac_channel *ch = &priv->channel[queue];
+
+ if (stmmac_xdp_is_enabled(priv) &&
+ test_bit(queue, priv->af_xdp_zc_qps)) {
+ napi_enable(&ch->rxtx_napi);
+ continue;
+ }
+
+ if (queue < rx_queues_cnt)
+ napi_enable(&ch->rx_napi);
+ if (queue < tx_queues_cnt)
+ napi_enable(&ch->tx_napi);
+ }
+}
+
+static void stmmac_service_event_schedule(struct stmmac_priv *priv)
+{
+ if (!test_bit(STMMAC_DOWN, &priv->state) &&
+ !test_and_set_bit(STMMAC_SERVICE_SCHED, &priv->state))
+ queue_work(priv->wq, &priv->service_task);
+}
+
+static void stmmac_global_err(struct stmmac_priv *priv)
+{
+ netif_carrier_off(priv->dev);
+ set_bit(STMMAC_RESET_REQUESTED, &priv->state);
+ stmmac_service_event_schedule(priv);
+}
+
+static void print_pkt(unsigned char *buf, int len)
+{
+ pr_debug("len = %d byte, buf addr: 0x%p\n", len, buf);
+ print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len);
+}
+
+static inline u32 stmmac_tx_avail(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+
+ return CIRC_SPACE(tx_q->cur_tx, tx_q->dirty_tx,
+ priv->dma_conf.dma_tx_size);
+}
+
+static size_t stmmac_get_tx_desc_size(struct stmmac_priv *priv,
+ struct stmmac_tx_queue *tx_q)
+{
+ if (priv->extend_desc)
+ return sizeof(struct dma_extended_desc);
+ else if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ return sizeof(struct dma_edesc);
+ else
+ return sizeof(struct dma_desc);
+}
+
+static struct dma_desc *stmmac_get_tx_desc(struct stmmac_priv *priv,
+ struct stmmac_tx_queue *tx_q,
+ unsigned int index)
+{
+ if (priv->extend_desc)
+ return &tx_q->dma_etx[index].basic;
+ else if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ return &tx_q->dma_entx[index].basic;
+ else
+ return &tx_q->dma_tx[index];
+}
+
+static void stmmac_set_queue_tx_tail_ptr(struct stmmac_priv *priv,
+ struct stmmac_tx_queue *tx_q,
+ unsigned int chan, unsigned int index)
+{
+ size_t desc_size;
+ u32 tx_tail_addr;
+
+ desc_size = stmmac_get_tx_desc_size(priv, tx_q);
+
+ tx_tail_addr = tx_q->dma_tx_phy + index * desc_size;
+ stmmac_set_tx_tail_ptr(priv, priv->ioaddr, tx_tail_addr, chan);
+}
+
+static size_t stmmac_get_rx_desc_size(struct stmmac_priv *priv)
+{
+ if (priv->extend_desc)
+ return sizeof(struct dma_extended_desc);
+ else
+ return sizeof(struct dma_desc);
+}
+
+static struct dma_desc *stmmac_get_rx_desc(struct stmmac_priv *priv,
+ struct stmmac_rx_queue *rx_q,
+ unsigned int index)
+{
+ if (priv->extend_desc)
+ return &rx_q->dma_erx[index].basic;
+ else
+ return &rx_q->dma_rx[index];
+}
+
+static void stmmac_set_queue_rx_tail_ptr(struct stmmac_priv *priv,
+ struct stmmac_rx_queue *rx_q,
+ unsigned int chan, unsigned int index)
+{
+ /* This only needs to deal with normal descriptors as enhanced
+ * descriptiors are only supported with dwmac1000 (<v4.0) which
+ * does not implement .set_rx_tail_ptr
+ */
+ u32 rx_tail_addr = rx_q->dma_rx_phy + index * sizeof(struct dma_desc);
+
+ stmmac_set_rx_tail_ptr(priv, priv->ioaddr, rx_tail_addr, chan);
+}
+
+static void stmmac_set_queue_rx_buf_size(struct stmmac_priv *priv,
+ struct stmmac_rx_queue *rx_q,
+ unsigned int chan)
+{
+ u32 buf_size;
+
+ if (rx_q->xsk_pool && rx_q->buf_alloc_num)
+ buf_size = xsk_pool_get_rx_frame_size(rx_q->xsk_pool);
+ else
+ buf_size = priv->dma_conf.dma_buf_sz;
+
+ stmmac_set_dma_bfsize(priv, priv->ioaddr, buf_size, chan);
+}
+
+/**
+ * stmmac_rx_dirty - Get RX queue dirty
+ * @priv: driver private structure
+ * @queue: RX queue index
+ */
+static inline u32 stmmac_rx_dirty(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+
+ return CIRC_CNT(rx_q->cur_rx, rx_q->dirty_rx,
+ priv->dma_conf.dma_rx_size);
+}
+
+static bool stmmac_eee_tx_busy(struct stmmac_priv *priv)
+{
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ u8 queue;
+
+ /* check if all TX queues have the work finished */
+ for (queue = 0; queue < tx_cnt; queue++) {
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+
+ if (tx_q->dirty_tx != tx_q->cur_tx)
+ return true; /* still unfinished work */
+ }
+
+ return false;
+}
+
+static void stmmac_restart_sw_lpi_timer(struct stmmac_priv *priv)
+{
+ mod_timer(&priv->eee_ctrl_timer, STMMAC_LPI_T(priv->tx_lpi_timer));
+}
+
+/**
+ * stmmac_try_to_start_sw_lpi - check and enter in LPI mode
+ * @priv: driver private structure
+ * Description: this function is to verify and enter in LPI mode in case of
+ * EEE.
+ */
+static void stmmac_try_to_start_sw_lpi(struct stmmac_priv *priv)
+{
+ if (stmmac_eee_tx_busy(priv)) {
+ stmmac_restart_sw_lpi_timer(priv);
+ return;
+ }
+
+ /* Check and enter in LPI mode */
+ if (!priv->tx_path_in_lpi_mode)
+ stmmac_set_lpi_mode(priv, priv->hw, STMMAC_LPI_FORCED,
+ priv->tx_lpi_clk_stop, 0);
+}
+
+/**
+ * stmmac_stop_sw_lpi - stop transmitting LPI
+ * @priv: driver private structure
+ * Description: When using software-controlled LPI, stop transmitting LPI state.
+ */
+static void stmmac_stop_sw_lpi(struct stmmac_priv *priv)
+{
+ timer_delete_sync(&priv->eee_ctrl_timer);
+ stmmac_set_lpi_mode(priv, priv->hw, STMMAC_LPI_DISABLE, false, 0);
+ priv->tx_path_in_lpi_mode = false;
+}
+
+/**
+ * stmmac_eee_ctrl_timer - EEE TX SW timer.
+ * @t: timer_list struct containing private info
+ * Description:
+ * if there is no data transfer and if we are not in LPI state,
+ * then MAC Transmitter can be moved to LPI state.
+ */
+static void stmmac_eee_ctrl_timer(struct timer_list *t)
+{
+ struct stmmac_priv *priv = timer_container_of(priv, t, eee_ctrl_timer);
+
+ stmmac_try_to_start_sw_lpi(priv);
+}
+
+/* stmmac_get_tx_hwtstamp - get HW TX timestamps
+ * @priv: driver private structure
+ * @p : descriptor pointer
+ * @skb : the socket buffer
+ * Description :
+ * This function will read timestamp from the descriptor & pass it to stack.
+ * and also perform some sanity checks.
+ */
+static void stmmac_get_tx_hwtstamp(struct stmmac_priv *priv,
+ struct dma_desc *p, struct sk_buff *skb)
+{
+ struct skb_shared_hwtstamps shhwtstamp;
+ bool found = false;
+ u64 ns = 0;
+
+ if (!priv->hwts_tx_en)
+ return;
+
+ /* exit if skb doesn't support hw tstamp */
+ if (likely(!skb || !(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)))
+ return;
+
+ /* check tx tstamp status */
+ if (stmmac_get_tx_timestamp_status(priv, p)) {
+ stmmac_get_timestamp(priv, p, priv->adv_ts, &ns);
+ found = true;
+ } else if (!stmmac_get_mac_tx_timestamp(priv, priv->hw, &ns)) {
+ found = true;
+ }
+
+ if (found) {
+ ns -= priv->plat->cdc_error_adj;
+
+ memset(&shhwtstamp, 0, sizeof(struct skb_shared_hwtstamps));
+ shhwtstamp.hwtstamp = ns_to_ktime(ns);
+
+ netdev_dbg(priv->dev, "get valid TX hw timestamp %llu\n", ns);
+ /* pass tstamp to stack */
+ skb_tstamp_tx(skb, &shhwtstamp);
+ }
+}
+
+/* stmmac_get_rx_hwtstamp - get HW RX timestamps
+ * @priv: driver private structure
+ * @p : descriptor pointer
+ * @np : next descriptor pointer
+ * @skb : the socket buffer
+ * Description :
+ * This function will read received packet's timestamp from the descriptor
+ * and pass it to stack. It also perform some sanity checks.
+ */
+static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p,
+ struct dma_desc *np, struct sk_buff *skb)
+{
+ struct skb_shared_hwtstamps *shhwtstamp = NULL;
+ struct dma_desc *desc = p;
+ u64 ns = 0;
+
+ if (!priv->hwts_rx_en)
+ return;
+ /* For GMAC4, the valid timestamp is from CTX next desc. */
+ if (dwmac_is_xmac(priv->plat->core_type))
+ desc = np;
+
+ /* Check if timestamp is available */
+ if (stmmac_get_rx_timestamp_status(priv, p, np, priv->adv_ts)) {
+ stmmac_get_timestamp(priv, desc, priv->adv_ts, &ns);
+
+ ns -= priv->plat->cdc_error_adj;
+
+ netdev_dbg(priv->dev, "get valid RX hw timestamp %llu\n", ns);
+ shhwtstamp = skb_hwtstamps(skb);
+ memset(shhwtstamp, 0, sizeof(struct skb_shared_hwtstamps));
+ shhwtstamp->hwtstamp = ns_to_ktime(ns);
+ } else {
+ netdev_dbg(priv->dev, "cannot get RX hw timestamp\n");
+ }
+}
+
+static void stmmac_update_subsecond_increment(struct stmmac_priv *priv)
+{
+ bool xmac = dwmac_is_xmac(priv->plat->core_type);
+ u32 sec_inc = 0;
+ u64 temp = 0;
+
+ stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags);
+
+ /* program Sub Second Increment reg */
+ stmmac_config_sub_second_increment(priv, priv->ptpaddr,
+ priv->plat->clk_ptp_rate,
+ xmac, &sec_inc);
+ temp = div_u64(1000000000ULL, sec_inc);
+
+ /* Store sub second increment for later use */
+ priv->sub_second_inc = sec_inc;
+
+ /* calculate default added value:
+ * formula is :
+ * addend = (2^32)/freq_div_ratio;
+ * where, freq_div_ratio = 1e9ns/sec_inc
+ */
+ temp = (u64)(temp << 32);
+ priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate);
+ stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend);
+}
+
+/**
+ * stmmac_hwtstamp_set - control hardware timestamping.
+ * @dev: device pointer.
+ * @config: the timestamping configuration.
+ * @extack: netlink extended ack structure for error reporting.
+ * Description:
+ * This function configures the MAC to enable/disable both outgoing(TX)
+ * and incoming(RX) packets time stamping based on user input.
+ * Return Value:
+ * 0 on success and an appropriate -ve integer on failure.
+ */
+static int stmmac_hwtstamp_set(struct net_device *dev,
+ struct kernel_hwtstamp_config *config,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u32 ptp_v2 = 0;
+ u32 tstamp_all = 0;
+ u32 ptp_over_ipv4_udp = 0;
+ u32 ptp_over_ipv6_udp = 0;
+ u32 ptp_over_ethernet = 0;
+ u32 snap_type_sel = 0;
+ u32 ts_master_en = 0;
+ u32 ts_event_en = 0;
+
+ if (!(priv->dma_cap.time_stamp || priv->adv_ts)) {
+ NL_SET_ERR_MSG_MOD(extack, "No support for HW time stamping");
+ priv->hwts_tx_en = 0;
+ priv->hwts_rx_en = 0;
+
+ return -EOPNOTSUPP;
+ }
+
+ if (!netif_running(dev)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Cannot change timestamping configuration while down");
+ return -ENODEV;
+ }
+
+ netdev_dbg(priv->dev, "%s config flags:0x%x, tx_type:0x%x, rx_filter:0x%x\n",
+ __func__, config->flags, config->tx_type, config->rx_filter);
+
+ if (config->tx_type != HWTSTAMP_TX_OFF &&
+ config->tx_type != HWTSTAMP_TX_ON)
+ return -ERANGE;
+
+ if (priv->adv_ts) {
+ switch (config->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ /* time stamp no incoming packet at all */
+ config->rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ /* PTP v1, UDP, any kind of event packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
+ /* 'xmac' hardware can support Sync, Pdelay_Req and
+ * Pdelay_resp by setting bit14 and bits17/16 to 01
+ * This leaves Delay_Req timestamps out.
+ * Enable all events *and* general purpose message
+ * timestamping
+ */
+ snap_type_sel = PTP_TCR_SNAPTYPSEL_1;
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ /* PTP v1, UDP, Sync packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_SYNC;
+ /* take time stamp for SYNC messages only */
+ ts_event_en = PTP_TCR_TSEVNTENA;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ /* PTP v1, UDP, Delay_req packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ;
+ /* take time stamp for Delay_Req messages only */
+ ts_master_en = PTP_TCR_TSMSTRENA;
+ ts_event_en = PTP_TCR_TSEVNTENA;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ /* PTP v2, UDP, any kind of event packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
+ ptp_v2 = PTP_TCR_TSVER2ENA;
+ /* take time stamp for all event messages */
+ snap_type_sel = PTP_TCR_SNAPTYPSEL_1;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ /* PTP v2, UDP, Sync packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_SYNC;
+ ptp_v2 = PTP_TCR_TSVER2ENA;
+ /* take time stamp for SYNC messages only */
+ ts_event_en = PTP_TCR_TSEVNTENA;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ /* PTP v2, UDP, Delay_req packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ;
+ ptp_v2 = PTP_TCR_TSVER2ENA;
+ /* take time stamp for Delay_Req messages only */
+ ts_master_en = PTP_TCR_TSMSTRENA;
+ ts_event_en = PTP_TCR_TSEVNTENA;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ /* PTP v2/802.AS1 any layer, any kind of event packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+ ptp_v2 = PTP_TCR_TSVER2ENA;
+ snap_type_sel = PTP_TCR_SNAPTYPSEL_1;
+ if (priv->synopsys_id < DWMAC_CORE_4_10)
+ ts_event_en = PTP_TCR_TSEVNTENA;
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ ptp_over_ethernet = PTP_TCR_TSIPENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ /* PTP v2/802.AS1, any layer, Sync packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_SYNC;
+ ptp_v2 = PTP_TCR_TSVER2ENA;
+ /* take time stamp for SYNC messages only */
+ ts_event_en = PTP_TCR_TSEVNTENA;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ ptp_over_ethernet = PTP_TCR_TSIPENA;
+ break;
+
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ /* PTP v2/802.AS1, any layer, Delay_req packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_DELAY_REQ;
+ ptp_v2 = PTP_TCR_TSVER2ENA;
+ /* take time stamp for Delay_Req messages only */
+ ts_master_en = PTP_TCR_TSMSTRENA;
+ ts_event_en = PTP_TCR_TSEVNTENA;
+
+ ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+ ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+ ptp_over_ethernet = PTP_TCR_TSIPENA;
+ break;
+
+ case HWTSTAMP_FILTER_NTP_ALL:
+ case HWTSTAMP_FILTER_ALL:
+ /* time stamp any incoming packet */
+ config->rx_filter = HWTSTAMP_FILTER_ALL;
+ tstamp_all = PTP_TCR_TSENALL;
+ break;
+
+ default:
+ return -ERANGE;
+ }
+ } else {
+ switch (config->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ config->rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+ default:
+ /* PTP v1, UDP, any kind of event packet */
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
+ break;
+ }
+ }
+ priv->hwts_rx_en = config->rx_filter != HWTSTAMP_FILTER_NONE;
+ priv->hwts_tx_en = config->tx_type == HWTSTAMP_TX_ON;
+
+ priv->systime_flags = STMMAC_HWTS_ACTIVE;
+ if (!priv->tsfupdt_coarse)
+ priv->systime_flags |= PTP_TCR_TSCFUPDT;
+
+ if (priv->hwts_tx_en || priv->hwts_rx_en) {
+ priv->systime_flags |= tstamp_all | ptp_v2 |
+ ptp_over_ethernet | ptp_over_ipv6_udp |
+ ptp_over_ipv4_udp | ts_event_en |
+ ts_master_en | snap_type_sel;
+ }
+
+ stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags);
+
+ priv->tstamp_config = *config;
+
+ return 0;
+}
+
+/**
+ * stmmac_hwtstamp_get - read hardware timestamping.
+ * @dev: device pointer.
+ * @config: the timestamping configuration.
+ * Description:
+ * This function obtain the current hardware timestamping settings
+ * as requested.
+ */
+static int stmmac_hwtstamp_get(struct net_device *dev,
+ struct kernel_hwtstamp_config *config)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (!(priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp))
+ return -EOPNOTSUPP;
+
+ *config = priv->tstamp_config;
+
+ return 0;
+}
+
+/**
+ * stmmac_init_tstamp_counter - init hardware timestamping counter
+ * @priv: driver private structure
+ * @systime_flags: timestamping flags
+ * Description:
+ * Initialize hardware counter for packet timestamping.
+ * This is valid as long as the interface is open and not suspended.
+ * Will be rerun after resuming from suspend, case in which the timestamping
+ * flags updated by stmmac_hwtstamp_set() also need to be restored.
+ */
+static int stmmac_init_tstamp_counter(struct stmmac_priv *priv,
+ u32 systime_flags)
+{
+ struct timespec64 now;
+
+ if (!priv->plat->clk_ptp_rate) {
+ netdev_err(priv->dev, "Invalid PTP clock rate");
+ return -EINVAL;
+ }
+
+ stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags);
+ priv->systime_flags = systime_flags;
+
+ stmmac_update_subsecond_increment(priv);
+
+ /* initialize system time */
+ ktime_get_real_ts64(&now);
+
+ /* lower 32 bits of tv_sec are safe until y2106 */
+ stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec);
+
+ return 0;
+}
+
+/**
+ * stmmac_init_timestamping - initialise timestamping
+ * @priv: driver private structure
+ * Description: this is to verify if the HW supports the PTPv1 or PTPv2.
+ * This is done by looking at the HW cap. register.
+ * This function also registers the ptp driver.
+ */
+static int stmmac_init_timestamping(struct stmmac_priv *priv)
+{
+ bool xmac = dwmac_is_xmac(priv->plat->core_type);
+ int ret;
+
+ if (priv->plat->ptp_clk_freq_config)
+ priv->plat->ptp_clk_freq_config(priv);
+
+ if (!(priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp)) {
+ netdev_info(priv->dev, "PTP not supported by HW\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE |
+ PTP_TCR_TSCFUPDT);
+ if (ret) {
+ netdev_warn(priv->dev, "PTP init failed\n");
+ return ret;
+ }
+
+ priv->adv_ts = 0;
+ /* Check if adv_ts can be enabled for dwmac 4.x / xgmac core */
+ if (xmac && priv->dma_cap.atime_stamp)
+ priv->adv_ts = 1;
+ /* Dwmac 3.x core with extend_desc can support adv_ts */
+ else if (priv->extend_desc && priv->dma_cap.atime_stamp)
+ priv->adv_ts = 1;
+
+ if (priv->dma_cap.time_stamp)
+ netdev_info(priv->dev, "IEEE 1588-2002 Timestamp supported\n");
+
+ if (priv->adv_ts)
+ netdev_info(priv->dev,
+ "IEEE 1588-2008 Advanced Timestamp supported\n");
+
+ memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config));
+ priv->hwts_tx_en = 0;
+ priv->hwts_rx_en = 0;
+
+ if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY)
+ stmmac_hwtstamp_correct_latency(priv, priv);
+
+ return 0;
+}
+
+static void stmmac_setup_ptp(struct stmmac_priv *priv)
+{
+ int ret;
+
+ ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
+ if (ret < 0)
+ netdev_warn(priv->dev,
+ "failed to enable PTP reference clock: %pe\n",
+ ERR_PTR(ret));
+
+ if (stmmac_init_timestamping(priv) == 0)
+ stmmac_ptp_register(priv);
+}
+
+static void stmmac_release_ptp(struct stmmac_priv *priv)
+{
+ stmmac_ptp_unregister(priv);
+ clk_disable_unprepare(priv->plat->clk_ptp_ref);
+}
+
+static void stmmac_legacy_serdes_power_down(struct stmmac_priv *priv)
+{
+ if (priv->plat->serdes_powerdown && priv->legacy_serdes_is_powered)
+ priv->plat->serdes_powerdown(priv->dev, priv->plat->bsp_priv);
+
+ priv->legacy_serdes_is_powered = false;
+}
+
+static int stmmac_legacy_serdes_power_up(struct stmmac_priv *priv)
+{
+ int ret;
+
+ if (!priv->plat->serdes_powerup)
+ return 0;
+
+ ret = priv->plat->serdes_powerup(priv->dev, priv->plat->bsp_priv);
+ if (ret < 0)
+ netdev_err(priv->dev, "SerDes powerup failed\n");
+ else
+ priv->legacy_serdes_is_powered = true;
+
+ return ret;
+}
+
+/**
+ * stmmac_mac_flow_ctrl - Configure flow control in all queues
+ * @priv: driver private structure
+ * @duplex: duplex passed to the next function
+ * @flow_ctrl: desired flow control modes
+ * Description: It is used for configuring the flow control in all queues
+ */
+static void stmmac_mac_flow_ctrl(struct stmmac_priv *priv, u32 duplex,
+ unsigned int flow_ctrl)
+{
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+
+ stmmac_flow_ctrl(priv, priv->hw, duplex, flow_ctrl, priv->pause_time,
+ tx_cnt);
+}
+
+static unsigned long stmmac_mac_get_caps(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+
+ /* Refresh the MAC-specific capabilities */
+ stmmac_mac_update_caps(priv);
+
+ if (priv->hw_cap_support && !priv->dma_cap.half_duplex)
+ priv->hw->link.caps &= ~(MAC_1000HD | MAC_100HD | MAC_10HD);
+
+ config->mac_capabilities = priv->hw->link.caps;
+
+ if (priv->plat->max_speed)
+ phylink_limit_mac_speed(config, priv->plat->max_speed);
+
+ return config->mac_capabilities;
+}
+
+static struct phylink_pcs *stmmac_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+ struct phylink_pcs *pcs;
+
+ if (priv->plat->select_pcs) {
+ pcs = priv->plat->select_pcs(priv, interface);
+ if (!IS_ERR(pcs))
+ return pcs;
+ }
+
+ if (priv->integrated_pcs &&
+ test_bit(interface, priv->integrated_pcs->pcs.supported_interfaces))
+ return &priv->integrated_pcs->pcs;
+
+ return NULL;
+}
+
+static void stmmac_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ /* Nothing to do, xpcs_config() handles everything */
+}
+
+static int stmmac_mac_finish(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct net_device *ndev = to_net_dev(config->dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ if (priv->plat->mac_finish)
+ priv->plat->mac_finish(ndev, priv->plat->bsp_priv, mode,
+ interface);
+
+ return 0;
+}
+
+static void stmmac_mac_link_down(struct phylink_config *config,
+ unsigned int mode, phy_interface_t interface)
+{
+ struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ if (priv->dma_cap.eee)
+ stmmac_set_eee_pls(priv, priv->hw, false);
+
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_link_state_handle(&priv->fpe_cfg.mmsv, false);
+}
+
+static void stmmac_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 stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+ unsigned int flow_ctrl;
+ u32 old_ctrl, ctrl;
+ int ret;
+
+ if (priv->plat->flags & STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP)
+ stmmac_legacy_serdes_power_up(priv);
+
+ old_ctrl = readl(priv->ioaddr + MAC_CTRL_REG);
+ ctrl = old_ctrl & ~priv->hw->link.speed_mask;
+
+ if (interface == PHY_INTERFACE_MODE_USXGMII) {
+ switch (speed) {
+ case SPEED_10000:
+ ctrl |= priv->hw->link.xgmii.speed10000;
+ break;
+ case SPEED_5000:
+ ctrl |= priv->hw->link.xgmii.speed5000;
+ break;
+ case SPEED_2500:
+ ctrl |= priv->hw->link.xgmii.speed2500;
+ break;
+ default:
+ return;
+ }
+ } else if (interface == PHY_INTERFACE_MODE_XLGMII) {
+ switch (speed) {
+ case SPEED_100000:
+ ctrl |= priv->hw->link.xlgmii.speed100000;
+ break;
+ case SPEED_50000:
+ ctrl |= priv->hw->link.xlgmii.speed50000;
+ break;
+ case SPEED_40000:
+ ctrl |= priv->hw->link.xlgmii.speed40000;
+ break;
+ case SPEED_25000:
+ ctrl |= priv->hw->link.xlgmii.speed25000;
+ break;
+ case SPEED_10000:
+ ctrl |= priv->hw->link.xgmii.speed10000;
+ break;
+ case SPEED_2500:
+ ctrl |= priv->hw->link.speed2500;
+ break;
+ case SPEED_1000:
+ ctrl |= priv->hw->link.speed1000;
+ break;
+ default:
+ return;
+ }
+ } else {
+ switch (speed) {
+ case SPEED_2500:
+ ctrl |= priv->hw->link.speed2500;
+ break;
+ case SPEED_1000:
+ ctrl |= priv->hw->link.speed1000;
+ break;
+ case SPEED_100:
+ ctrl |= priv->hw->link.speed100;
+ break;
+ case SPEED_10:
+ ctrl |= priv->hw->link.speed10;
+ break;
+ default:
+ return;
+ }
+ }
+
+ if (priv->plat->fix_mac_speed)
+ priv->plat->fix_mac_speed(priv->plat->bsp_priv, interface,
+ speed, mode);
+
+ if (!duplex)
+ ctrl &= ~priv->hw->link.duplex;
+ else
+ ctrl |= priv->hw->link.duplex;
+
+ /* Flow Control operation */
+ if (rx_pause && tx_pause)
+ flow_ctrl = FLOW_AUTO;
+ else if (rx_pause && !tx_pause)
+ flow_ctrl = FLOW_RX;
+ else if (!rx_pause && tx_pause)
+ flow_ctrl = FLOW_TX;
+ else
+ flow_ctrl = FLOW_OFF;
+
+ stmmac_mac_flow_ctrl(priv, duplex, flow_ctrl);
+
+ if (ctrl != old_ctrl)
+ writel(ctrl, priv->ioaddr + MAC_CTRL_REG);
+
+ if (priv->plat->set_clk_tx_rate) {
+ ret = priv->plat->set_clk_tx_rate(priv->plat->bsp_priv,
+ priv->plat->clk_tx_i,
+ interface, speed);
+ if (ret < 0)
+ netdev_err(priv->dev,
+ "failed to configure %s transmit clock for %dMbps: %pe\n",
+ phy_modes(interface), speed, ERR_PTR(ret));
+ }
+
+ stmmac_mac_set(priv, priv->ioaddr, true);
+ if (priv->dma_cap.eee)
+ stmmac_set_eee_pls(priv, priv->hw, true);
+
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_link_state_handle(&priv->fpe_cfg.mmsv, true);
+
+ if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY)
+ stmmac_hwtstamp_correct_latency(priv, priv);
+}
+
+static void stmmac_mac_disable_tx_lpi(struct phylink_config *config)
+{
+ struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+
+ priv->eee_active = false;
+
+ mutex_lock(&priv->lock);
+
+ priv->eee_enabled = false;
+
+ netdev_dbg(priv->dev, "disable EEE\n");
+ priv->eee_sw_timer_en = false;
+ timer_delete_sync(&priv->eee_ctrl_timer);
+ stmmac_set_lpi_mode(priv, priv->hw, STMMAC_LPI_DISABLE, false, 0);
+ priv->tx_path_in_lpi_mode = false;
+
+ stmmac_set_eee_timer(priv, priv->hw, 0, STMMAC_DEFAULT_TWT_LS);
+ mutex_unlock(&priv->lock);
+}
+
+static int stmmac_mac_enable_tx_lpi(struct phylink_config *config, u32 timer,
+ bool tx_clk_stop)
+{
+ struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+ int ret;
+
+ priv->tx_lpi_timer = timer;
+ priv->eee_active = true;
+
+ mutex_lock(&priv->lock);
+
+ priv->eee_enabled = true;
+
+ /* Update the transmit clock stop according to PHY capability if
+ * the platform allows
+ */
+ if (priv->plat->flags & STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP)
+ priv->tx_lpi_clk_stop = tx_clk_stop;
+
+ stmmac_set_eee_timer(priv, priv->hw, STMMAC_DEFAULT_LIT_LS,
+ STMMAC_DEFAULT_TWT_LS);
+
+ /* Try to configure the hardware timer. */
+ ret = stmmac_set_lpi_mode(priv, priv->hw, STMMAC_LPI_TIMER,
+ priv->tx_lpi_clk_stop, priv->tx_lpi_timer);
+
+ if (ret) {
+ /* Hardware timer mode not supported, or value out of range.
+ * Fall back to using software LPI mode
+ */
+ priv->eee_sw_timer_en = true;
+ stmmac_restart_sw_lpi_timer(priv);
+ }
+
+ mutex_unlock(&priv->lock);
+ netdev_dbg(priv->dev, "Energy-Efficient Ethernet initialized\n");
+
+ return 0;
+}
+
+static int stmmac_mac_wol_set(struct phylink_config *config, u32 wolopts,
+ const u8 *sopass)
+{
+ struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
+
+ device_set_wakeup_enable(priv->device, !!wolopts);
+
+ mutex_lock(&priv->lock);
+ priv->wolopts = wolopts;
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
+ .mac_get_caps = stmmac_mac_get_caps,
+ .mac_select_pcs = stmmac_mac_select_pcs,
+ .mac_config = stmmac_mac_config,
+ .mac_finish = stmmac_mac_finish,
+ .mac_link_down = stmmac_mac_link_down,
+ .mac_link_up = stmmac_mac_link_up,
+ .mac_disable_tx_lpi = stmmac_mac_disable_tx_lpi,
+ .mac_enable_tx_lpi = stmmac_mac_enable_tx_lpi,
+ .mac_wol_set = stmmac_mac_wol_set,
+};
+
+/**
+ * stmmac_check_pcs_mode - verify if RGMII/SGMII is supported
+ * @priv: driver private structure
+ * Description: this is to verify if the HW supports the PCS.
+ * Physical Coding Sublayer (PCS) interface that can be used when the MAC is
+ * configured for the TBI, RTBI, or SGMII PHY interface.
+ */
+static void stmmac_check_pcs_mode(struct stmmac_priv *priv)
+{
+ int interface = priv->plat->phy_interface;
+ int speed = priv->plat->mac_port_sel_speed;
+
+ if (priv->dma_cap.pcs && interface == PHY_INTERFACE_MODE_SGMII) {
+ netdev_dbg(priv->dev, "PCS SGMII support enabled\n");
+
+ switch (speed) {
+ case SPEED_10:
+ case SPEED_100:
+ case SPEED_1000:
+ priv->hw->reverse_sgmii_enable = true;
+ break;
+
+ default:
+ dev_warn(priv->device, "invalid port speed\n");
+ fallthrough;
+ case 0:
+ priv->hw->reverse_sgmii_enable = false;
+ break;
+ }
+ }
+}
+
+/**
+ * stmmac_init_phy - PHY initialization
+ * @dev: net device structure
+ * Description: it initializes the driver's PHY state, and attaches the PHY
+ * to the mac driver.
+ * Return value:
+ * 0 on success
+ */
+static int stmmac_init_phy(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int mode = priv->plat->phy_interface;
+ struct fwnode_handle *phy_fwnode;
+ struct fwnode_handle *fwnode;
+ struct ethtool_keee eee;
+ u32 dev_flags = 0;
+ int ret;
+
+ if (!phylink_expects_phy(priv->phylink))
+ return 0;
+
+ if (priv->hw->xpcs &&
+ xpcs_get_an_mode(priv->hw->xpcs, mode) == DW_AN_C73)
+ return 0;
+
+ fwnode = dev_fwnode(priv->device);
+ if (fwnode)
+ phy_fwnode = fwnode_get_phy_node(fwnode);
+ else
+ phy_fwnode = NULL;
+
+ if (priv->plat->flags & STMMAC_FLAG_KEEP_PREAMBLE_BEFORE_SFD)
+ dev_flags |= PHY_F_KEEP_PREAMBLE_BEFORE_SFD;
+
+ /* Some DT bindings do not set-up the PHY handle. Let's try to
+ * manually parse it
+ */
+ if (!phy_fwnode || IS_ERR(phy_fwnode)) {
+ int addr = priv->plat->phy_addr;
+ struct phy_device *phydev;
+
+ if (addr < 0) {
+ netdev_err(priv->dev, "no phy found\n");
+ return -ENODEV;
+ }
+
+ phydev = mdiobus_get_phy(priv->mii, addr);
+ if (!phydev) {
+ netdev_err(priv->dev, "no phy at addr %d\n", addr);
+ return -ENODEV;
+ }
+
+ phydev->dev_flags |= dev_flags;
+
+ ret = phylink_connect_phy(priv->phylink, phydev);
+ } else {
+ fwnode_handle_put(phy_fwnode);
+ ret = phylink_fwnode_phy_connect(priv->phylink, fwnode, dev_flags);
+ }
+
+ if (ret) {
+ netdev_err(priv->dev, "cannot attach to PHY (error: %pe)\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /* Configure phylib's copy of the LPI timer. Normally,
+ * phylink_config.lpi_timer_default would do this, but there is a
+ * chance that userspace could change the eee_timer setting via sysfs
+ * before the first open. Thus, preserve existing behaviour.
+ */
+ if (!phylink_ethtool_get_eee(priv->phylink, &eee)) {
+ eee.tx_lpi_timer = priv->tx_lpi_timer;
+ phylink_ethtool_set_eee(priv->phylink, &eee);
+ }
+
+ return 0;
+}
+
+static int stmmac_phylink_setup(struct stmmac_priv *priv)
+{
+ struct phylink_config *config;
+ struct phylink_pcs *pcs;
+ struct phylink *phylink;
+
+ config = &priv->phylink_config;
+
+ config->dev = &priv->dev->dev;
+ config->type = PHYLINK_NETDEV;
+ config->mac_managed_pm = true;
+
+ /* Stmmac always requires an RX clock for hardware initialization */
+ config->mac_requires_rxc = true;
+
+ /* Disable EEE RX clock stop to ensure VLAN register access works
+ * correctly.
+ */
+ if (!(priv->plat->flags & STMMAC_FLAG_RX_CLK_RUNS_IN_LPI) &&
+ !(priv->dev->features & NETIF_F_VLAN_FEATURES))
+ config->eee_rx_clk_stop_enable = true;
+
+ /* Set the default transmit clock stop bit based on the platform glue */
+ priv->tx_lpi_clk_stop = priv->plat->flags &
+ STMMAC_FLAG_EN_TX_LPI_CLOCKGATING;
+
+ /* Get the PHY interface modes (at the PHY end of the link) that
+ * are supported by the platform.
+ */
+ if (priv->plat->get_interfaces)
+ priv->plat->get_interfaces(priv, priv->plat->bsp_priv,
+ config->supported_interfaces);
+
+ config->default_an_inband = priv->plat->default_an_inband;
+
+ /* Set the platform/firmware specified interface mode if the
+ * supported interfaces have not already been provided using
+ * phy_interface as a last resort.
+ */
+ if (phy_interface_empty(config->supported_interfaces))
+ __set_bit(priv->plat->phy_interface,
+ config->supported_interfaces);
+
+ /* If we have an xpcs, it defines which PHY interfaces are supported. */
+ if (priv->hw->xpcs)
+ pcs = xpcs_to_phylink_pcs(priv->hw->xpcs);
+ else
+ pcs = priv->hw->phylink_pcs;
+
+ if (pcs)
+ phy_interface_or(config->supported_interfaces,
+ config->supported_interfaces,
+ pcs->supported_interfaces);
+
+ /* Some platforms, e.g. iMX8MP, wire lpi_intr_o to the same interrupt
+ * used for stmmac's main interrupts, which leads to interrupt storms.
+ * STMMAC_FLAG_EEE_DISABLE allows EEE to be disabled on such platforms.
+ */
+ if (priv->dma_cap.eee &&
+ !(priv->plat->flags & STMMAC_FLAG_EEE_DISABLE)) {
+ /* The GMAC 3.74a databook states that EEE is only supported
+ * in MII, GMII, and RGMII interfaces.
+ */
+ __set_bit(PHY_INTERFACE_MODE_MII, config->lpi_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_GMII, config->lpi_interfaces);
+ phy_interface_set_rgmii(config->lpi_interfaces);
+
+ /* If we have a non-integrated PCS, assume that it is connected
+ * to the GMAC using GMII or another EEE compatible interface,
+ * and thus all PCS-supported interfaces support LPI.
+ */
+ if (pcs)
+ phy_interface_or(config->lpi_interfaces,
+ config->lpi_interfaces,
+ pcs->supported_interfaces);
+
+ /* All full duplex speeds above 100Mbps are supported */
+ config->lpi_capabilities = ~(MAC_1000FD - 1) | MAC_100FD;
+ config->lpi_timer_default = eee_timer * 1000;
+ config->eee_enabled_default = true;
+ }
+
+ config->wol_phy_speed_ctrl = true;
+ if (priv->plat->flags & STMMAC_FLAG_USE_PHY_WOL) {
+ config->wol_phy_legacy = true;
+ } else {
+ if (priv->dma_cap.pmt_remote_wake_up)
+ config->wol_mac_support |= WAKE_UCAST;
+ if (priv->dma_cap.pmt_magic_frame)
+ config->wol_mac_support |= WAKE_MAGIC;
+ }
+
+ phylink = phylink_create(config, dev_fwnode(priv->device),
+ priv->plat->phy_interface,
+ &stmmac_phylink_mac_ops);
+ if (IS_ERR(phylink))
+ return PTR_ERR(phylink);
+
+ priv->phylink = phylink;
+ return 0;
+}
+
+static void stmmac_display_rx_rings(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 rx_cnt = priv->plat->rx_queues_to_use;
+ unsigned int desc_size;
+ void *head_rx;
+ u8 queue;
+
+ /* Display RX rings */
+ for (queue = 0; queue < rx_cnt; queue++) {
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+
+ pr_info("\tRX Queue %u rings\n", queue);
+
+ head_rx = stmmac_get_rx_desc(priv, rx_q, 0);
+ desc_size = stmmac_get_rx_desc_size(priv);
+
+ /* Display RX ring */
+ stmmac_display_ring(priv, head_rx, dma_conf->dma_rx_size, true,
+ rx_q->dma_rx_phy, desc_size);
+ }
+}
+
+static void stmmac_display_tx_rings(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ unsigned int desc_size;
+ void *head_tx;
+ u8 queue;
+
+ /* Display TX rings */
+ for (queue = 0; queue < tx_cnt; queue++) {
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+
+ pr_info("\tTX Queue %d rings\n", queue);
+
+ head_tx = stmmac_get_tx_desc(priv, tx_q, 0);
+ desc_size = stmmac_get_tx_desc_size(priv, tx_q);
+
+ stmmac_display_ring(priv, head_tx, dma_conf->dma_tx_size, false,
+ tx_q->dma_tx_phy, desc_size);
+ }
+}
+
+static void stmmac_display_rings(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ /* Display RX ring */
+ stmmac_display_rx_rings(priv, dma_conf);
+
+ /* Display TX ring */
+ stmmac_display_tx_rings(priv, dma_conf);
+}
+
+static unsigned int stmmac_rx_offset(struct stmmac_priv *priv)
+{
+ if (stmmac_xdp_is_enabled(priv))
+ return XDP_PACKET_HEADROOM;
+
+ return NET_SKB_PAD;
+}
+
+static int stmmac_set_bfsize(int mtu)
+{
+ int ret;
+
+ if (mtu >= BUF_SIZE_8KiB)
+ ret = BUF_SIZE_16KiB;
+ else if (mtu >= BUF_SIZE_4KiB)
+ ret = BUF_SIZE_8KiB;
+ else if (mtu >= BUF_SIZE_2KiB)
+ ret = BUF_SIZE_4KiB;
+ else if (mtu > DEFAULT_BUFSIZE)
+ ret = BUF_SIZE_2KiB;
+ else
+ ret = DEFAULT_BUFSIZE;
+
+ return ret;
+}
+
+/**
+ * stmmac_clear_rx_descriptors - clear RX descriptors
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ * Description: this function is called to clear the RX descriptors
+ * in case of both basic and extended descriptors are used.
+ */
+static void stmmac_clear_rx_descriptors(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ struct dma_desc *desc;
+ int i;
+
+ /* Clear the RX descriptors */
+ for (i = 0; i < dma_conf->dma_rx_size; i++) {
+ desc = stmmac_get_rx_desc(priv, rx_q, i);
+
+ stmmac_init_rx_desc(priv, desc, priv->use_riwt,
+ priv->descriptor_mode,
+ (i == dma_conf->dma_rx_size - 1),
+ dma_conf->dma_buf_sz);
+ }
+}
+
+/**
+ * stmmac_clear_tx_descriptors - clear tx descriptors
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: TX queue index.
+ * Description: this function is called to clear the TX descriptors
+ * in case of both basic and extended descriptors are used.
+ */
+static void stmmac_clear_tx_descriptors(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+ int i;
+
+ /* Clear the TX descriptors */
+ for (i = 0; i < dma_conf->dma_tx_size; i++) {
+ int last = (i == (dma_conf->dma_tx_size - 1));
+ struct dma_desc *p;
+
+ p = stmmac_get_tx_desc(priv, tx_q, i);
+ stmmac_init_tx_desc(priv, p, priv->descriptor_mode, last);
+ }
+}
+
+/**
+ * stmmac_clear_descriptors - clear descriptors
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * Description: this function is called to clear the TX and RX descriptors
+ * in case of both basic and extended descriptors are used.
+ */
+static void stmmac_clear_descriptors(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 rx_queue_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_queue_cnt = priv->plat->tx_queues_to_use;
+ u8 queue;
+
+ /* Clear the RX descriptors */
+ for (queue = 0; queue < rx_queue_cnt; queue++)
+ stmmac_clear_rx_descriptors(priv, dma_conf, queue);
+
+ /* Clear the TX descriptors */
+ for (queue = 0; queue < tx_queue_cnt; queue++)
+ stmmac_clear_tx_descriptors(priv, dma_conf, queue);
+}
+
+/**
+ * stmmac_init_rx_buffers - init the RX descriptor buffer.
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * @p: descriptor pointer
+ * @i: descriptor index
+ * @flags: gfp flag
+ * @queue: RX queue index
+ * Description: this function is called to allocate a receive buffer, perform
+ * the DMA mapping and init the descriptor.
+ */
+static int stmmac_init_rx_buffers(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ struct dma_desc *p,
+ int i, gfp_t flags, u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
+ gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN);
+
+ if (priv->dma_cap.host_dma_width <= 32)
+ gfp |= GFP_DMA32;
+
+ if (!buf->page) {
+ buf->page = page_pool_alloc_pages(rx_q->page_pool, gfp);
+ if (!buf->page)
+ return -ENOMEM;
+ buf->page_offset = stmmac_rx_offset(priv);
+ }
+
+ if (priv->sph_active && !buf->sec_page) {
+ buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp);
+ if (!buf->sec_page)
+ return -ENOMEM;
+
+ buf->sec_addr = page_pool_get_dma_addr(buf->sec_page);
+ stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true);
+ } else {
+ buf->sec_page = NULL;
+ stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false);
+ }
+
+ buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset;
+
+ stmmac_set_desc_addr(priv, p, buf->addr);
+ if (dma_conf->dma_buf_sz == BUF_SIZE_16KiB)
+ stmmac_init_desc3(priv, p);
+
+ return 0;
+}
+
+/**
+ * stmmac_free_rx_buffer - free RX dma buffers
+ * @priv: private structure
+ * @rx_q: RX queue
+ * @i: buffer index.
+ */
+static void stmmac_free_rx_buffer(struct stmmac_priv *priv,
+ struct stmmac_rx_queue *rx_q,
+ int i)
+{
+ struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
+
+ if (buf->page)
+ page_pool_put_full_page(rx_q->page_pool, buf->page, false);
+ buf->page = NULL;
+
+ if (buf->sec_page)
+ page_pool_put_full_page(rx_q->page_pool, buf->sec_page, false);
+ buf->sec_page = NULL;
+}
+
+/**
+ * stmmac_free_tx_buffer - free RX dma buffers
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ * @i: buffer index.
+ */
+static void stmmac_free_tx_buffer(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue, int i)
+{
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+
+ if (tx_q->tx_skbuff_dma[i].buf &&
+ tx_q->tx_skbuff_dma[i].buf_type != STMMAC_TXBUF_T_XDP_TX) {
+ if (tx_q->tx_skbuff_dma[i].map_as_page)
+ dma_unmap_page(priv->device,
+ tx_q->tx_skbuff_dma[i].buf,
+ tx_q->tx_skbuff_dma[i].len,
+ DMA_TO_DEVICE);
+ else
+ dma_unmap_single(priv->device,
+ tx_q->tx_skbuff_dma[i].buf,
+ tx_q->tx_skbuff_dma[i].len,
+ DMA_TO_DEVICE);
+ }
+
+ if (tx_q->xdpf[i] &&
+ (tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_XDP_TX ||
+ tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_XDP_NDO)) {
+ xdp_return_frame(tx_q->xdpf[i]);
+ tx_q->xdpf[i] = NULL;
+ }
+
+ if (tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_XSK_TX)
+ tx_q->xsk_frames_done++;
+
+ if (tx_q->tx_skbuff[i] &&
+ tx_q->tx_skbuff_dma[i].buf_type == STMMAC_TXBUF_T_SKB) {
+ dev_kfree_skb_any(tx_q->tx_skbuff[i]);
+ tx_q->tx_skbuff[i] = NULL;
+ }
+
+ tx_q->tx_skbuff_dma[i].buf = 0;
+ tx_q->tx_skbuff_dma[i].map_as_page = false;
+}
+
+/**
+ * dma_free_rx_skbufs - free RX dma buffers
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ */
+static void dma_free_rx_skbufs(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ int i;
+
+ for (i = 0; i < dma_conf->dma_rx_size; i++)
+ stmmac_free_rx_buffer(priv, rx_q, i);
+}
+
+static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue, gfp_t flags)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ int i;
+
+ for (i = 0; i < dma_conf->dma_rx_size; i++) {
+ struct dma_desc *p;
+ int ret;
+
+ p = stmmac_get_rx_desc(priv, rx_q, i);
+
+ ret = stmmac_init_rx_buffers(priv, dma_conf, p, i, flags,
+ queue);
+ if (ret)
+ return ret;
+
+ rx_q->buf_alloc_num++;
+ }
+
+ return 0;
+}
+
+/**
+ * dma_free_rx_xskbufs - free RX dma buffers from XSK pool
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ */
+static void dma_free_rx_xskbufs(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ int i;
+
+ for (i = 0; i < dma_conf->dma_rx_size; i++) {
+ struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
+
+ if (!buf->xdp)
+ continue;
+
+ xsk_buff_free(buf->xdp);
+ buf->xdp = NULL;
+ }
+}
+
+static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ int i;
+
+ /* struct stmmac_xdp_buff is using cb field (maximum size of 24 bytes)
+ * in struct xdp_buff_xsk to stash driver specific information. Thus,
+ * use this macro to make sure no size violations.
+ */
+ XSK_CHECK_PRIV_TYPE(struct stmmac_xdp_buff);
+
+ for (i = 0; i < dma_conf->dma_rx_size; i++) {
+ struct stmmac_rx_buffer *buf;
+ dma_addr_t dma_addr;
+ struct dma_desc *p;
+
+ p = stmmac_get_rx_desc(priv, rx_q, i);
+
+ buf = &rx_q->buf_pool[i];
+
+ buf->xdp = xsk_buff_alloc(rx_q->xsk_pool);
+ if (!buf->xdp)
+ return -ENOMEM;
+
+ dma_addr = xsk_buff_xdp_get_dma(buf->xdp);
+ stmmac_set_desc_addr(priv, p, dma_addr);
+ rx_q->buf_alloc_num++;
+ }
+
+ return 0;
+}
+
+static struct xsk_buff_pool *stmmac_get_xsk_pool(struct stmmac_priv *priv, u32 queue)
+{
+ if (!stmmac_xdp_is_enabled(priv) || !test_bit(queue, priv->af_xdp_zc_qps))
+ return NULL;
+
+ return xsk_get_pool_from_qid(priv->dev, queue);
+}
+
+/**
+ * __init_dma_rx_desc_rings - init the RX descriptor ring (per queue)
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ * @flags: gfp flag.
+ * Description: this function initializes the DMA RX descriptors
+ * and allocates the socket buffers. It supports the chained and ring
+ * modes.
+ */
+static int __init_dma_rx_desc_rings(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue, gfp_t flags)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ void *des;
+ int ret;
+
+ netif_dbg(priv, probe, priv->dev,
+ "(%s) dma_rx_phy=0x%08x\n", __func__,
+ (u32)rx_q->dma_rx_phy);
+
+ stmmac_clear_rx_descriptors(priv, dma_conf, queue);
+
+ xdp_rxq_info_unreg_mem_model(&rx_q->xdp_rxq);
+
+ rx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue);
+
+ if (rx_q->xsk_pool) {
+ WARN_ON(xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
+ MEM_TYPE_XSK_BUFF_POOL,
+ NULL));
+ netdev_info(priv->dev,
+ "Register MEM_TYPE_XSK_BUFF_POOL RxQ-%d\n",
+ queue);
+ xsk_pool_set_rxq_info(rx_q->xsk_pool, &rx_q->xdp_rxq);
+ } else {
+ WARN_ON(xdp_rxq_info_reg_mem_model(&rx_q->xdp_rxq,
+ MEM_TYPE_PAGE_POOL,
+ rx_q->page_pool));
+ netdev_info(priv->dev,
+ "Register MEM_TYPE_PAGE_POOL RxQ-%d\n",
+ queue);
+ }
+
+ if (rx_q->xsk_pool) {
+ /* RX XDP ZC buffer pool may not be populated, e.g.
+ * xdpsock TX-only.
+ */
+ stmmac_alloc_rx_buffers_zc(priv, dma_conf, queue);
+ } else {
+ ret = stmmac_alloc_rx_buffers(priv, dma_conf, queue, flags);
+ if (ret < 0)
+ return -ENOMEM;
+ }
+
+ /* Setup the chained descriptor addresses */
+ if (priv->descriptor_mode == STMMAC_CHAIN_MODE) {
+ if (priv->extend_desc)
+ des = rx_q->dma_erx;
+ else
+ des = rx_q->dma_rx;
+
+ stmmac_mode_init(priv, des, rx_q->dma_rx_phy,
+ dma_conf->dma_rx_size, priv->extend_desc);
+ }
+
+ return 0;
+}
+
+static int init_dma_rx_desc_rings(struct net_device *dev,
+ struct stmmac_dma_conf *dma_conf,
+ gfp_t flags)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 rx_count = priv->plat->rx_queues_to_use;
+ int queue;
+ int ret;
+
+ /* RX INITIALIZATION */
+ netif_dbg(priv, probe, priv->dev,
+ "SKB addresses:\nskb\t\tskb data\tdma data\n");
+
+ for (queue = 0; queue < rx_count; queue++) {
+ ret = __init_dma_rx_desc_rings(priv, dma_conf, queue, flags);
+ if (ret)
+ goto err_init_rx_buffers;
+ }
+
+ return 0;
+
+err_init_rx_buffers:
+ while (queue >= 0) {
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+
+ if (rx_q->xsk_pool)
+ dma_free_rx_xskbufs(priv, dma_conf, queue);
+ else
+ dma_free_rx_skbufs(priv, dma_conf, queue);
+
+ rx_q->buf_alloc_num = 0;
+ rx_q->xsk_pool = NULL;
+
+ queue--;
+ }
+
+ return ret;
+}
+
+static void stmmac_set_tx_dma_entry(struct stmmac_tx_queue *tx_q,
+ unsigned int entry,
+ enum stmmac_txbuf_type type,
+ dma_addr_t addr, size_t len,
+ bool map_as_page)
+{
+ tx_q->tx_skbuff_dma[entry].buf = addr;
+ tx_q->tx_skbuff_dma[entry].len = len;
+ tx_q->tx_skbuff_dma[entry].buf_type = type;
+ tx_q->tx_skbuff_dma[entry].map_as_page = map_as_page;
+ tx_q->tx_skbuff_dma[entry].last_segment = false;
+ tx_q->tx_skbuff_dma[entry].is_jumbo = false;
+}
+
+static void stmmac_set_tx_skb_dma_entry(struct stmmac_tx_queue *tx_q,
+ unsigned int entry, dma_addr_t addr,
+ size_t len, bool map_as_page)
+{
+ stmmac_set_tx_dma_entry(tx_q, entry, STMMAC_TXBUF_T_SKB, addr, len,
+ map_as_page);
+}
+
+static void stmmac_set_tx_dma_last_segment(struct stmmac_tx_queue *tx_q,
+ unsigned int entry)
+{
+ tx_q->tx_skbuff_dma[entry].last_segment = true;
+}
+
+/**
+ * __init_dma_tx_desc_rings - init the TX descriptor ring (per queue)
+ * @priv: driver private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: TX queue index
+ * Description: this function initializes the DMA TX descriptors
+ * and allocates the socket buffers. It supports the chained and ring
+ * modes.
+ */
+static int __init_dma_tx_desc_rings(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+ int i;
+
+ netif_dbg(priv, probe, priv->dev,
+ "(%s) dma_tx_phy=0x%08x\n", __func__,
+ (u32)tx_q->dma_tx_phy);
+
+ /* Setup the chained descriptor addresses */
+ if (priv->descriptor_mode == STMMAC_CHAIN_MODE) {
+ if (priv->extend_desc)
+ stmmac_mode_init(priv, tx_q->dma_etx,
+ tx_q->dma_tx_phy,
+ dma_conf->dma_tx_size, 1);
+ else if (!(tx_q->tbs & STMMAC_TBS_AVAIL))
+ stmmac_mode_init(priv, tx_q->dma_tx,
+ tx_q->dma_tx_phy,
+ dma_conf->dma_tx_size, 0);
+ }
+
+ tx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue);
+
+ for (i = 0; i < dma_conf->dma_tx_size; i++) {
+ struct dma_desc *p;
+
+ p = stmmac_get_tx_desc(priv, tx_q, i);
+ stmmac_clear_desc(priv, p);
+ stmmac_set_tx_skb_dma_entry(tx_q, i, 0, 0, false);
+
+ tx_q->tx_skbuff[i] = NULL;
+ }
+
+ return 0;
+}
+
+static int init_dma_tx_desc_rings(struct net_device *dev,
+ struct stmmac_dma_conf *dma_conf)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 tx_queue_cnt;
+ u8 queue;
+
+ tx_queue_cnt = priv->plat->tx_queues_to_use;
+
+ for (queue = 0; queue < tx_queue_cnt; queue++)
+ __init_dma_tx_desc_rings(priv, dma_conf, queue);
+
+ return 0;
+}
+
+/**
+ * init_dma_desc_rings - init the RX/TX descriptor rings
+ * @dev: net device structure
+ * @dma_conf: structure to take the dma data
+ * @flags: gfp flag.
+ * Description: this function initializes the DMA RX/TX descriptors
+ * and allocates the socket buffers. It supports the chained and ring
+ * modes.
+ */
+static int init_dma_desc_rings(struct net_device *dev,
+ struct stmmac_dma_conf *dma_conf,
+ gfp_t flags)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int ret;
+
+ ret = init_dma_rx_desc_rings(dev, dma_conf, flags);
+ if (ret)
+ return ret;
+
+ ret = init_dma_tx_desc_rings(dev, dma_conf);
+
+ stmmac_clear_descriptors(priv, dma_conf);
+
+ if (netif_msg_hw(priv))
+ stmmac_display_rings(priv, dma_conf);
+
+ return ret;
+}
+
+/**
+ * dma_free_tx_skbufs - free TX dma buffers
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: TX queue index
+ */
+static void dma_free_tx_skbufs(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+ int i;
+
+ tx_q->xsk_frames_done = 0;
+
+ for (i = 0; i < dma_conf->dma_tx_size; i++)
+ stmmac_free_tx_buffer(priv, dma_conf, queue, i);
+
+ if (tx_q->xsk_pool && tx_q->xsk_frames_done) {
+ xsk_tx_completed(tx_q->xsk_pool, tx_q->xsk_frames_done);
+ tx_q->xsk_frames_done = 0;
+ tx_q->xsk_pool = NULL;
+ }
+}
+
+/**
+ * stmmac_free_tx_skbufs - free TX skb buffers
+ * @priv: private structure
+ */
+static void stmmac_free_tx_skbufs(struct stmmac_priv *priv)
+{
+ u8 tx_queue_cnt = priv->plat->tx_queues_to_use;
+ u8 queue;
+
+ for (queue = 0; queue < tx_queue_cnt; queue++)
+ dma_free_tx_skbufs(priv, &priv->dma_conf, queue);
+}
+
+/**
+ * __free_dma_rx_desc_resources - free RX dma desc resources (per queue)
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ */
+static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ size_t size;
+ void *addr;
+
+ /* Release the DMA RX socket buffers */
+ if (rx_q->xsk_pool)
+ dma_free_rx_xskbufs(priv, dma_conf, queue);
+ else
+ dma_free_rx_skbufs(priv, dma_conf, queue);
+
+ rx_q->buf_alloc_num = 0;
+ rx_q->xsk_pool = NULL;
+
+ /* Free DMA regions of consistent memory previously allocated */
+ if (priv->extend_desc)
+ addr = rx_q->dma_erx;
+ else
+ addr = rx_q->dma_rx;
+
+ size = stmmac_get_rx_desc_size(priv) * dma_conf->dma_rx_size;
+
+ dma_free_coherent(priv->device, size, addr, rx_q->dma_rx_phy);
+
+ if (xdp_rxq_info_is_reg(&rx_q->xdp_rxq))
+ xdp_rxq_info_unreg(&rx_q->xdp_rxq);
+
+ kfree(rx_q->buf_pool);
+ if (rx_q->page_pool)
+ page_pool_destroy(rx_q->page_pool);
+}
+
+static void free_dma_rx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 rx_count = priv->plat->rx_queues_to_use;
+ u8 queue;
+
+ /* Free RX queue resources */
+ for (queue = 0; queue < rx_count; queue++)
+ __free_dma_rx_desc_resources(priv, dma_conf, queue);
+}
+
+/**
+ * __free_dma_tx_desc_resources - free TX dma desc resources (per queue)
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: TX queue index
+ */
+static void __free_dma_tx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+ size_t size;
+ void *addr;
+
+ /* Release the DMA TX socket buffers */
+ dma_free_tx_skbufs(priv, dma_conf, queue);
+
+ if (priv->extend_desc) {
+ addr = tx_q->dma_etx;
+ } else if (tx_q->tbs & STMMAC_TBS_AVAIL) {
+ addr = tx_q->dma_entx;
+ } else {
+ addr = tx_q->dma_tx;
+ }
+
+ size = stmmac_get_tx_desc_size(priv, tx_q) * dma_conf->dma_tx_size;
+
+ dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy);
+
+ kfree(tx_q->tx_skbuff_dma);
+ kfree(tx_q->tx_skbuff);
+}
+
+static void free_dma_tx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 tx_count = priv->plat->tx_queues_to_use;
+ u8 queue;
+
+ /* Free TX queue resources */
+ for (queue = 0; queue < tx_count; queue++)
+ __free_dma_tx_desc_resources(priv, dma_conf, queue);
+}
+
+/**
+ * __alloc_dma_rx_desc_resources - alloc RX resources (per queue).
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: RX queue index
+ * Description: according to which descriptor can be used (extend or basic)
+ * this function allocates the resources for TX and RX paths. In case of
+ * reception, for example, it pre-allocated the RX socket buffer in order to
+ * allow zero-copy mechanism.
+ */
+static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue];
+ struct stmmac_channel *ch = &priv->channel[queue];
+ bool xdp_prog = stmmac_xdp_is_enabled(priv);
+ struct page_pool_params pp_params = { 0 };
+ unsigned int dma_buf_sz_pad, num_pages;
+ unsigned int napi_id;
+ size_t size;
+ void *addr;
+ int ret;
+
+ dma_buf_sz_pad = stmmac_rx_offset(priv) + dma_conf->dma_buf_sz +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ num_pages = DIV_ROUND_UP(dma_buf_sz_pad, PAGE_SIZE);
+
+ rx_q->queue_index = queue;
+ rx_q->priv_data = priv;
+ rx_q->napi_skb_frag_size = num_pages * PAGE_SIZE;
+
+ pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+ pp_params.pool_size = dma_conf->dma_rx_size;
+ pp_params.order = order_base_2(num_pages);
+ pp_params.nid = dev_to_node(priv->device);
+ pp_params.dev = priv->device;
+ pp_params.dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE;
+ pp_params.offset = stmmac_rx_offset(priv);
+ pp_params.max_len = dma_conf->dma_buf_sz;
+
+ if (priv->sph_active) {
+ pp_params.offset = 0;
+ pp_params.max_len += stmmac_rx_offset(priv);
+ }
+
+ rx_q->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(rx_q->page_pool)) {
+ ret = PTR_ERR(rx_q->page_pool);
+ rx_q->page_pool = NULL;
+ return ret;
+ }
+
+ rx_q->buf_pool = kzalloc_objs(*rx_q->buf_pool, dma_conf->dma_rx_size);
+ if (!rx_q->buf_pool)
+ return -ENOMEM;
+
+ size = stmmac_get_rx_desc_size(priv) * dma_conf->dma_rx_size;
+
+ addr = dma_alloc_coherent(priv->device, size, &rx_q->dma_rx_phy,
+ GFP_KERNEL);
+ if (!addr)
+ return -ENOMEM;
+
+ if (priv->extend_desc)
+ rx_q->dma_erx = addr;
+ else
+ rx_q->dma_rx = addr;
+
+ if (stmmac_xdp_is_enabled(priv) &&
+ test_bit(queue, priv->af_xdp_zc_qps))
+ napi_id = ch->rxtx_napi.napi_id;
+ else
+ napi_id = ch->rx_napi.napi_id;
+
+ ret = xdp_rxq_info_reg(&rx_q->xdp_rxq, priv->dev, queue, napi_id);
+ if (ret) {
+ netdev_err(priv->dev, "Failed to register xdp rxq info\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int alloc_dma_rx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 rx_count = priv->plat->rx_queues_to_use;
+ u8 queue;
+ int ret;
+
+ /* RX queues buffers and DMA */
+ for (queue = 0; queue < rx_count; queue++) {
+ ret = __alloc_dma_rx_desc_resources(priv, dma_conf, queue);
+ if (ret)
+ goto err_dma;
+ }
+
+ return 0;
+
+err_dma:
+ free_dma_rx_desc_resources(priv, dma_conf);
+
+ return ret;
+}
+
+/**
+ * __alloc_dma_tx_desc_resources - alloc TX resources (per queue).
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * @queue: TX queue index
+ * Description: according to which descriptor can be used (extend or basic)
+ * this function allocates the resources for TX and RX paths. In case of
+ * reception, for example, it pre-allocated the RX socket buffer in order to
+ * allow zero-copy mechanism.
+ */
+static int __alloc_dma_tx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf,
+ u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[queue];
+ size_t size;
+ void *addr;
+
+ tx_q->queue_index = queue;
+ tx_q->priv_data = priv;
+
+ tx_q->tx_skbuff_dma = kzalloc_objs(*tx_q->tx_skbuff_dma,
+ dma_conf->dma_tx_size);
+ if (!tx_q->tx_skbuff_dma)
+ return -ENOMEM;
+
+ tx_q->tx_skbuff = kzalloc_objs(struct sk_buff *, dma_conf->dma_tx_size);
+ if (!tx_q->tx_skbuff)
+ return -ENOMEM;
+
+ size = stmmac_get_tx_desc_size(priv, tx_q) * dma_conf->dma_tx_size;
+
+ addr = dma_alloc_coherent(priv->device, size,
+ &tx_q->dma_tx_phy, GFP_KERNEL);
+ if (!addr)
+ return -ENOMEM;
+
+ if (priv->extend_desc)
+ tx_q->dma_etx = addr;
+ else if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ tx_q->dma_entx = addr;
+ else
+ tx_q->dma_tx = addr;
+
+ return 0;
+}
+
+static int alloc_dma_tx_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ u8 tx_count = priv->plat->tx_queues_to_use;
+ u8 queue;
+ int ret;
+
+ /* TX queues buffers and DMA */
+ for (queue = 0; queue < tx_count; queue++) {
+ ret = __alloc_dma_tx_desc_resources(priv, dma_conf, queue);
+ if (ret)
+ goto err_dma;
+ }
+
+ return 0;
+
+err_dma:
+ free_dma_tx_desc_resources(priv, dma_conf);
+ return ret;
+}
+
+/**
+ * alloc_dma_desc_resources - alloc TX/RX resources.
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ * Description: according to which descriptor can be used (extend or basic)
+ * this function allocates the resources for TX and RX paths. In case of
+ * reception, for example, it pre-allocated the RX socket buffer in order to
+ * allow zero-copy mechanism.
+ */
+static int alloc_dma_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ /* RX Allocation */
+ int ret = alloc_dma_rx_desc_resources(priv, dma_conf);
+
+ if (ret)
+ return ret;
+
+ ret = alloc_dma_tx_desc_resources(priv, dma_conf);
+
+ return ret;
+}
+
+/**
+ * free_dma_desc_resources - free dma desc resources
+ * @priv: private structure
+ * @dma_conf: structure to take the dma data
+ */
+static void free_dma_desc_resources(struct stmmac_priv *priv,
+ struct stmmac_dma_conf *dma_conf)
+{
+ /* Release the DMA TX socket buffers */
+ free_dma_tx_desc_resources(priv, dma_conf);
+
+ /* Release the DMA RX socket buffers later
+ * to ensure all pending XDP_TX buffers are returned.
+ */
+ free_dma_rx_desc_resources(priv, dma_conf);
+}
+
+/**
+ * stmmac_mac_enable_rx_queues - Enable MAC rx queues
+ * @priv: driver private structure
+ * Description: It is used for enabling the rx queues in the MAC
+ */
+static void stmmac_mac_enable_rx_queues(struct stmmac_priv *priv)
+{
+ u8 rx_queues_count = priv->plat->rx_queues_to_use;
+ u8 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);
+ }
+}
+
+/**
+ * stmmac_start_rx_dma - start RX DMA channel
+ * @priv: driver private structure
+ * @chan: RX channel index
+ * Description:
+ * This starts a RX DMA channel
+ */
+static void stmmac_start_rx_dma(struct stmmac_priv *priv, u32 chan)
+{
+ netdev_dbg(priv->dev, "DMA RX processes started in channel %d\n", chan);
+ stmmac_start_rx(priv, priv->ioaddr, chan);
+}
+
+/**
+ * stmmac_start_tx_dma - start TX DMA channel
+ * @priv: driver private structure
+ * @chan: TX channel index
+ * Description:
+ * This starts a TX DMA channel
+ */
+static void stmmac_start_tx_dma(struct stmmac_priv *priv, u32 chan)
+{
+ netdev_dbg(priv->dev, "DMA TX processes started in channel %d\n", chan);
+ stmmac_start_tx(priv, priv->ioaddr, chan);
+}
+
+/**
+ * stmmac_stop_rx_dma - stop RX DMA channel
+ * @priv: driver private structure
+ * @chan: RX channel index
+ * Description:
+ * This stops a RX DMA channel
+ */
+static void stmmac_stop_rx_dma(struct stmmac_priv *priv, u32 chan)
+{
+ netdev_dbg(priv->dev, "DMA RX processes stopped in channel %d\n", chan);
+ stmmac_stop_rx(priv, priv->ioaddr, chan);
+}
+
+/**
+ * stmmac_stop_tx_dma - stop TX DMA channel
+ * @priv: driver private structure
+ * @chan: TX channel index
+ * Description:
+ * This stops a TX DMA channel
+ */
+static void stmmac_stop_tx_dma(struct stmmac_priv *priv, u32 chan)
+{
+ netdev_dbg(priv->dev, "DMA TX processes stopped in channel %d\n", chan);
+ stmmac_stop_tx(priv, priv->ioaddr, chan);
+}
+
+static void stmmac_enable_all_dma_irq(struct stmmac_priv *priv)
+{
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ u8 dma_csr_ch = max(rx_channels_count, tx_channels_count);
+ u8 chan;
+
+ for (chan = 0; chan < dma_csr_ch; chan++) {
+ struct stmmac_channel *ch = &priv->channel[chan];
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_enable_dma_irq(priv, priv->ioaddr, chan, 1, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+}
+
+/**
+ * stmmac_start_all_dma - start all RX and TX DMA channels
+ * @priv: driver private structure
+ * Description:
+ * This starts all the RX and TX DMA channels
+ */
+static void stmmac_start_all_dma(struct stmmac_priv *priv)
+{
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ u8 chan;
+
+ for (chan = 0; chan < rx_channels_count; chan++)
+ stmmac_start_rx_dma(priv, chan);
+
+ for (chan = 0; chan < tx_channels_count; chan++)
+ stmmac_start_tx_dma(priv, chan);
+}
+
+/**
+ * stmmac_stop_all_dma - stop all RX and TX DMA channels
+ * @priv: driver private structure
+ * Description:
+ * This stops the RX and TX DMA channels
+ */
+static void stmmac_stop_all_dma(struct stmmac_priv *priv)
+{
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ u8 chan;
+
+ for (chan = 0; chan < rx_channels_count; chan++)
+ stmmac_stop_rx_dma(priv, chan);
+
+ for (chan = 0; chan < tx_channels_count; chan++)
+ stmmac_stop_tx_dma(priv, chan);
+}
+
+/**
+ * stmmac_dma_operation_mode - HW DMA operation mode
+ * @priv: driver private structure
+ * Description: it is used for configuring the DMA operation mode register in
+ * order to program the tx/rx DMA thresholds or Store-And-Forward mode.
+ */
+static void stmmac_dma_operation_mode(struct stmmac_priv *priv)
+{
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ int rxfifosz = priv->plat->rx_fifo_size;
+ int txfifosz = priv->plat->tx_fifo_size;
+ u32 txmode = 0;
+ u32 rxmode = 0;
+ u8 qmode = 0;
+ u8 chan;
+
+ if (rxfifosz == 0)
+ rxfifosz = priv->dma_cap.rx_fifo_size;
+ if (txfifosz == 0)
+ txfifosz = priv->dma_cap.tx_fifo_size;
+
+ /* Split up the shared Tx/Rx FIFO memory on DW QoS Eth and DW XGMAC */
+ if (dwmac_is_xmac(priv->plat->core_type)) {
+ rxfifosz /= rx_channels_count;
+ txfifosz /= tx_channels_count;
+ }
+
+ if (priv->plat->force_thresh_dma_mode) {
+ txmode = tc;
+ rxmode = tc;
+ } else if (priv->plat->force_sf_dma_mode || priv->plat->tx_coe) {
+ /*
+ * In case of GMAC, SF mode can be enabled
+ * to perform the TX COE in HW. This depends on:
+ * 1) TX COE if actually supported
+ * 2) There is no bugged Jumbo frame support
+ * that needs to not insert csum in the TDES.
+ */
+ txmode = SF_DMA_MODE;
+ rxmode = SF_DMA_MODE;
+ priv->xstats.threshold = SF_DMA_MODE;
+ } else {
+ txmode = tc;
+ rxmode = SF_DMA_MODE;
+ }
+
+ /* configure all channels */
+ for (chan = 0; chan < rx_channels_count; chan++) {
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan];
+
+ qmode = priv->plat->rx_queues_cfg[chan].mode_to_use;
+
+ stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan,
+ rxfifosz, qmode);
+
+ stmmac_set_queue_rx_buf_size(priv, rx_q, 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 stmmac_xsk_request_timestamp(void *_priv)
+{
+ struct stmmac_metadata_request *meta_req = _priv;
+
+ stmmac_enable_tx_timestamp(meta_req->priv, meta_req->tx_desc);
+ *meta_req->set_ic = true;
+}
+
+static u64 stmmac_xsk_fill_timestamp(void *_priv)
+{
+ struct stmmac_xsk_tx_complete *tx_compl = _priv;
+ struct stmmac_priv *priv = tx_compl->priv;
+ struct dma_desc *desc = tx_compl->desc;
+ bool found = false;
+ u64 ns = 0;
+
+ if (!priv->hwts_tx_en)
+ return 0;
+
+ /* check tx tstamp status */
+ if (stmmac_get_tx_timestamp_status(priv, desc)) {
+ stmmac_get_timestamp(priv, desc, priv->adv_ts, &ns);
+ found = true;
+ } else if (!stmmac_get_mac_tx_timestamp(priv, priv->hw, &ns)) {
+ found = true;
+ }
+
+ if (found) {
+ ns -= priv->plat->cdc_error_adj;
+ return ns_to_ktime(ns);
+ }
+
+ return 0;
+}
+
+static void stmmac_xsk_request_launch_time(u64 launch_time, void *_priv)
+{
+ struct timespec64 ts = ns_to_timespec64(launch_time);
+ struct stmmac_metadata_request *meta_req = _priv;
+
+ if (meta_req->tbs & STMMAC_TBS_EN)
+ stmmac_set_desc_tbs(meta_req->priv, meta_req->edesc, ts.tv_sec,
+ ts.tv_nsec);
+}
+
+static const struct xsk_tx_metadata_ops stmmac_xsk_tx_metadata_ops = {
+ .tmo_request_timestamp = stmmac_xsk_request_timestamp,
+ .tmo_fill_timestamp = stmmac_xsk_fill_timestamp,
+ .tmo_request_launch_time = stmmac_xsk_request_launch_time,
+};
+
+static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
+{
+ struct netdev_queue *nq = netdev_get_tx_queue(priv->dev, queue);
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[queue];
+ bool csum = !priv->plat->tx_queues_cfg[queue].coe_unsupported;
+ struct xsk_buff_pool *pool = tx_q->xsk_pool;
+ unsigned int entry = tx_q->cur_tx;
+ struct dma_desc *tx_desc = NULL;
+ struct xdp_desc xdp_desc;
+ bool work_done = true;
+ u32 tx_set_ic_bit = 0;
+
+ /* Avoids TX time-out as we are sharing with slow path */
+ txq_trans_cond_update(nq);
+
+ budget = min(budget, stmmac_tx_avail(priv, queue));
+
+ for (; budget > 0; budget--) {
+ struct stmmac_metadata_request meta_req;
+ struct xsk_tx_metadata *meta = NULL;
+ dma_addr_t dma_addr;
+ bool set_ic;
+
+ /* We are sharing with slow path and stop XSK TX desc submission when
+ * available TX ring is less than threshold.
+ */
+ if (unlikely(stmmac_tx_avail(priv, queue) < STMMAC_TX_XSK_AVAIL) ||
+ !netif_carrier_ok(priv->dev)) {
+ work_done = false;
+ break;
+ }
+
+ if (!xsk_tx_peek_desc(pool, &xdp_desc))
+ break;
+
+ if (priv->est && priv->est->enable &&
+ priv->est->max_sdu[queue] &&
+ xdp_desc.len > priv->est->max_sdu[queue]) {
+ priv->xstats.max_sdu_txq_drop[queue]++;
+ continue;
+ }
+
+ tx_desc = stmmac_get_tx_desc(priv, tx_q, entry);
+ dma_addr = xsk_buff_raw_get_dma(pool, xdp_desc.addr);
+ meta = xsk_buff_get_metadata(pool, xdp_desc.addr);
+ xsk_buff_raw_dma_sync_for_device(pool, dma_addr, xdp_desc.len);
+
+ /* To return XDP buffer to XSK pool, we simple call
+ * xsk_tx_completed(), so we don't need to fill up
+ * 'buf' and 'xdpf'.
+ */
+ stmmac_set_tx_dma_entry(tx_q, entry, STMMAC_TXBUF_T_XSK_TX,
+ 0, xdp_desc.len, false);
+ stmmac_set_tx_dma_last_segment(tx_q, entry);
+
+ tx_q->xdpf[entry] = NULL;
+
+ stmmac_set_desc_addr(priv, tx_desc, dma_addr);
+
+ tx_q->tx_count_frames++;
+
+ if (!priv->tx_coal_frames[queue])
+ set_ic = false;
+ else if (tx_q->tx_count_frames % priv->tx_coal_frames[queue] == 0)
+ set_ic = true;
+ else
+ set_ic = false;
+
+ meta_req.priv = priv;
+ meta_req.tx_desc = tx_desc;
+ meta_req.set_ic = &set_ic;
+ meta_req.tbs = tx_q->tbs;
+ meta_req.edesc = &tx_q->dma_entx[entry];
+ xsk_tx_metadata_request(meta, &stmmac_xsk_tx_metadata_ops,
+ &meta_req);
+ if (set_ic) {
+ tx_q->tx_count_frames = 0;
+ stmmac_set_tx_ic(priv, tx_desc);
+ tx_set_ic_bit++;
+ }
+
+ stmmac_prepare_tx_desc(priv, tx_desc, 1, xdp_desc.len,
+ csum, priv->descriptor_mode, true, true,
+ xdp_desc.len);
+
+ stmmac_enable_dma_transmission(priv, priv->ioaddr, queue);
+
+ xsk_tx_metadata_to_compl(meta,
+ &tx_q->tx_skbuff_dma[entry].xsk_meta);
+
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
+ entry = tx_q->cur_tx;
+ }
+ u64_stats_update_begin(&txq_stats->napi_syncp);
+ u64_stats_add(&txq_stats->napi.tx_set_ic_bit, tx_set_ic_bit);
+ u64_stats_update_end(&txq_stats->napi_syncp);
+
+ if (tx_desc) {
+ stmmac_flush_tx_descriptors(priv, queue);
+ xsk_tx_release(pool);
+ }
+
+ /* Return true if all of the 3 conditions are met
+ * a) TX Budget is still available
+ * b) work_done = true when XSK TX desc peek is empty (no more
+ * pending XSK TX for transmission)
+ */
+ return !!budget && work_done;
+}
+
+static void stmmac_bump_dma_threshold(struct stmmac_priv *priv, u32 chan)
+{
+ if (unlikely(priv->xstats.threshold != SF_DMA_MODE) && tc <= 256) {
+ tc += 64;
+
+ if (priv->plat->force_thresh_dma_mode)
+ stmmac_set_dma_operation_mode(priv, tc, tc, chan);
+ else
+ stmmac_set_dma_operation_mode(priv, tc, SF_DMA_MODE,
+ chan);
+
+ priv->xstats.threshold = tc;
+ }
+}
+
+/**
+ * stmmac_tx_clean - to manage the transmission completion
+ * @priv: driver private structure
+ * @budget: napi budget limiting this functions packet handling
+ * @queue: TX queue index
+ * @pending_packets: signal to arm the TX coal timer
+ * Description: it reclaims the transmit resources after transmission completes.
+ * If some packets still needs to be handled, due to TX coalesce, set
+ * pending_packets to true to make NAPI arm the TX coal timer.
+ */
+static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
+ bool *pending_packets)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[queue];
+ unsigned int bytes_compl = 0, pkts_compl = 0;
+ unsigned int entry, xmits = 0, count = 0;
+ u32 tx_packets = 0, tx_errors = 0;
+
+ __netif_tx_lock_bh(netdev_get_tx_queue(priv->dev, queue));
+
+ tx_q->xsk_frames_done = 0;
+
+ entry = tx_q->dirty_tx;
+
+ /* Try to clean all TX complete frame in 1 shot */
+ while ((entry != tx_q->cur_tx) && count < priv->dma_conf.dma_tx_size) {
+ struct xdp_frame *xdpf;
+ struct sk_buff *skb;
+ struct dma_desc *p;
+ int status;
+
+ if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XDP_TX ||
+ tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XDP_NDO) {
+ xdpf = tx_q->xdpf[entry];
+ skb = NULL;
+ } else if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_SKB) {
+ xdpf = NULL;
+ skb = tx_q->tx_skbuff[entry];
+ } else {
+ xdpf = NULL;
+ skb = NULL;
+ }
+
+ p = stmmac_get_tx_desc(priv, tx_q, entry);
+ status = stmmac_tx_status(priv, &priv->xstats, p, priv->ioaddr);
+ /* Check if the descriptor is owned by the DMA */
+ if (unlikely(status & tx_dma_own))
+ break;
+
+ count++;
+
+ /* Make sure descriptor fields are read after reading
+ * the own bit.
+ */
+ dma_rmb();
+
+ /* Just consider the last segment and ...*/
+ if (likely(!(status & tx_not_ls))) {
+ /* ... verify the status error condition */
+ if (unlikely(status & tx_err)) {
+ tx_errors++;
+ if (unlikely(status & tx_err_bump_tc))
+ stmmac_bump_dma_threshold(priv, queue);
+ } else {
+ tx_packets++;
+ }
+ if (skb) {
+ stmmac_get_tx_hwtstamp(priv, p, skb);
+ } else if (tx_q->xsk_pool &&
+ xp_tx_metadata_enabled(tx_q->xsk_pool)) {
+ struct stmmac_xsk_tx_complete tx_compl = {
+ .priv = priv,
+ .desc = p,
+ };
+
+ xsk_tx_metadata_complete(&tx_q->tx_skbuff_dma[entry].xsk_meta,
+ &stmmac_xsk_tx_metadata_ops,
+ &tx_compl);
+ }
+ }
+
+ if (likely(tx_q->tx_skbuff_dma[entry].buf &&
+ tx_q->tx_skbuff_dma[entry].buf_type != STMMAC_TXBUF_T_XDP_TX)) {
+ if (tx_q->tx_skbuff_dma[entry].map_as_page)
+ dma_unmap_page(priv->device,
+ tx_q->tx_skbuff_dma[entry].buf,
+ tx_q->tx_skbuff_dma[entry].len,
+ DMA_TO_DEVICE);
+ else
+ dma_unmap_single(priv->device,
+ tx_q->tx_skbuff_dma[entry].buf,
+ tx_q->tx_skbuff_dma[entry].len,
+ DMA_TO_DEVICE);
+ tx_q->tx_skbuff_dma[entry].buf = 0;
+ tx_q->tx_skbuff_dma[entry].len = 0;
+ tx_q->tx_skbuff_dma[entry].map_as_page = false;
+ }
+
+ /* This looks at tx_q->tx_skbuff_dma[tx_q->dirty_tx].is_jumbo
+ * and tx_q->tx_skbuff_dma[tx_q->dirty_tx].last_segment
+ */
+ stmmac_clean_desc3(priv, tx_q, p);
+
+ tx_q->tx_skbuff_dma[entry].last_segment = false;
+ tx_q->tx_skbuff_dma[entry].is_jumbo = false;
+
+ if (xdpf &&
+ tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XDP_TX) {
+ xdp_return_frame_rx_napi(xdpf);
+ tx_q->xdpf[entry] = NULL;
+ }
+
+ if (xdpf &&
+ tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XDP_NDO) {
+ xdp_return_frame(xdpf);
+ tx_q->xdpf[entry] = NULL;
+ }
+
+ if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XSK_TX)
+ tx_q->xsk_frames_done++;
+
+ if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_SKB) {
+ if (likely(skb)) {
+ pkts_compl++;
+ bytes_compl += skb->len;
+ dev_consume_skb_any(skb);
+ tx_q->tx_skbuff[entry] = NULL;
+ }
+ }
+
+ stmmac_release_tx_desc(priv, p, priv->descriptor_mode);
+
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ }
+ tx_q->dirty_tx = entry;
+
+ netdev_tx_completed_queue(netdev_get_tx_queue(priv->dev, queue),
+ pkts_compl, bytes_compl);
+
+ if (unlikely(netif_tx_queue_stopped(netdev_get_tx_queue(priv->dev,
+ queue))) &&
+ stmmac_tx_avail(priv, queue) > STMMAC_TX_THRESH(priv)) {
+
+ netif_dbg(priv, tx_done, priv->dev,
+ "%s: restart transmit\n", __func__);
+ netif_tx_wake_queue(netdev_get_tx_queue(priv->dev, queue));
+ }
+
+ if (tx_q->xsk_pool) {
+ bool work_done;
+
+ if (tx_q->xsk_frames_done)
+ xsk_tx_completed(tx_q->xsk_pool, tx_q->xsk_frames_done);
+
+ if (xsk_uses_need_wakeup(tx_q->xsk_pool))
+ xsk_set_tx_need_wakeup(tx_q->xsk_pool);
+
+ /* For XSK TX, we try to send as many as possible.
+ * If XSK work done (XSK TX desc empty and budget still
+ * available), return "budget - 1" to reenable TX IRQ.
+ * Else, return "budget" to make NAPI continue polling.
+ */
+ work_done = stmmac_xdp_xmit_zc(priv, queue,
+ STMMAC_XSK_TX_BUDGET_MAX);
+ if (work_done)
+ xmits = budget - 1;
+ else
+ xmits = budget;
+ }
+
+ if (priv->eee_sw_timer_en && !priv->tx_path_in_lpi_mode)
+ stmmac_restart_sw_lpi_timer(priv);
+
+ /* We still have pending packets, let's call for a new scheduling */
+ if (tx_q->dirty_tx != tx_q->cur_tx)
+ *pending_packets = true;
+
+ u64_stats_update_begin(&txq_stats->napi_syncp);
+ u64_stats_add(&txq_stats->napi.tx_packets, tx_packets);
+ u64_stats_add(&txq_stats->napi.tx_pkt_n, tx_packets);
+ u64_stats_inc(&txq_stats->napi.tx_clean);
+ u64_stats_update_end(&txq_stats->napi_syncp);
+
+ priv->xstats.tx_errors += tx_errors;
+
+ __netif_tx_unlock_bh(netdev_get_tx_queue(priv->dev, queue));
+
+ /* Combine decisions from TX clean and XSK TX */
+ return max(count, xmits);
+}
+
+/**
+ * stmmac_tx_err - to manage the tx error
+ * @priv: driver private structure
+ * @chan: channel index
+ * Description: it cleans the descriptors and restarts the transmission
+ * in case of transmission errors.
+ */
+static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+
+ netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, chan));
+
+ stmmac_stop_tx_dma(priv, chan);
+ dma_free_tx_skbufs(priv, &priv->dma_conf, chan);
+ stmmac_clear_tx_descriptors(priv, &priv->dma_conf, chan);
+ stmmac_reset_tx_queue(priv, chan);
+ stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ tx_q->dma_tx_phy, chan);
+ stmmac_start_tx_dma(priv, chan);
+
+ priv->xstats.tx_errors++;
+ netif_tx_wake_queue(netdev_get_tx_queue(priv->dev, chan));
+}
+
+/**
+ * stmmac_set_dma_operation_mode - Set DMA operation mode by channel
+ * @priv: driver private structure
+ * @txmode: TX operating mode
+ * @rxmode: RX operating mode
+ * @chan: channel index
+ * Description: it is used for configuring of the DMA operation mode in
+ * runtime in order to program the tx/rx DMA thresholds or Store-And-Forward
+ * mode.
+ */
+static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode,
+ u32 rxmode, u32 chan)
+{
+ u8 rxqmode = priv->plat->rx_queues_cfg[chan].mode_to_use;
+ u8 txqmode = priv->plat->tx_queues_cfg[chan].mode_to_use;
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ int rxfifosz = priv->plat->rx_fifo_size;
+ int txfifosz = priv->plat->tx_fifo_size;
+
+ 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;
+
+ stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, rxfifosz, rxqmode);
+ stmmac_dma_tx_mode(priv, priv->ioaddr, txmode, chan, txfifosz, txqmode);
+}
+
+static bool stmmac_safety_feat_interrupt(struct stmmac_priv *priv)
+{
+ int ret;
+
+ ret = stmmac_safety_feat_irq_status(priv, priv->dev,
+ priv->ioaddr, priv->dma_cap.asp, &priv->sstats);
+ if (ret && (ret != -EINVAL)) {
+ stmmac_global_err(priv);
+ return true;
+ }
+
+ return false;
+}
+
+static int stmmac_napi_check(struct stmmac_priv *priv, u32 chan, u32 dir)
+{
+ int status = stmmac_dma_interrupt_status(priv, priv->ioaddr,
+ &priv->xstats, chan, dir);
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+ struct stmmac_channel *ch = &priv->channel[chan];
+ struct napi_struct *rx_napi;
+ struct napi_struct *tx_napi;
+ unsigned long flags;
+
+ rx_napi = rx_q->xsk_pool ? &ch->rxtx_napi : &ch->rx_napi;
+ tx_napi = tx_q->xsk_pool ? &ch->rxtx_napi : &ch->tx_napi;
+
+ if ((status & handle_rx) && (chan < priv->plat->rx_queues_to_use)) {
+ if (napi_schedule_prep(rx_napi)) {
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, chan, 1, 0);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ __napi_schedule(rx_napi);
+ }
+ }
+
+ if ((status & handle_tx) && (chan < priv->plat->tx_queues_to_use)) {
+ if (napi_schedule_prep(tx_napi)) {
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, chan, 0, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ __napi_schedule(tx_napi);
+ }
+ }
+
+ return status;
+}
+
+/**
+ * stmmac_dma_interrupt - DMA ISR
+ * @priv: driver private structure
+ * Description: this is the DMA ISR. It is called by the main ISR.
+ * It calls the dwmac dma routine and schedule poll method in case of some
+ * work can be done.
+ */
+static void stmmac_dma_interrupt(struct stmmac_priv *priv)
+{
+ u8 tx_channel_count = priv->plat->tx_queues_to_use;
+ u8 rx_channel_count = priv->plat->rx_queues_to_use;
+ u8 channels_to_check = tx_channel_count > rx_channel_count ?
+ tx_channel_count : rx_channel_count;
+ int status[MAX_T(u32, MTL_MAX_TX_QUEUES, MTL_MAX_RX_QUEUES)];
+ u8 chan;
+
+ /* Make sure we never check beyond our status buffer. */
+ if (WARN_ON_ONCE(channels_to_check > ARRAY_SIZE(status)))
+ channels_to_check = ARRAY_SIZE(status);
+
+ for (chan = 0; chan < channels_to_check; chan++)
+ status[chan] = stmmac_napi_check(priv, chan,
+ DMA_DIR_RXTX);
+
+ for (chan = 0; chan < tx_channel_count; chan++) {
+ if (unlikely(status[chan] & tx_hard_error_bump_tc)) {
+ /* Try to bump up the dma threshold on this failure */
+ stmmac_bump_dma_threshold(priv, chan);
+ } else if (unlikely(status[chan] == tx_hard_error)) {
+ stmmac_tx_err(priv, chan);
+ }
+ }
+}
+
+/**
+ * stmmac_mmc_setup: setup the Mac Management Counters (MMC)
+ * @priv: driver private structure
+ * Description: this masks the MMC irq, in fact, the counters are managed in SW.
+ */
+static void stmmac_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");
+}
+
+/**
+ * stmmac_get_hw_features - get MAC capabilities from the HW cap. register.
+ * @priv: driver private structure
+ * Description:
+ * new GMAC chip generations have a new register to indicate the
+ * presence of the optional feature/functions.
+ * This can be also used to override the value passed through the
+ * platform and necessary for old MAC10/100 and GMAC chips.
+ */
+static int stmmac_get_hw_features(struct stmmac_priv *priv)
+{
+ return stmmac_get_hw_feature(priv, priv->ioaddr, &priv->dma_cap) == 0;
+}
+
+/**
+ * stmmac_check_ether_addr - check if the MAC addr is valid
+ * @priv: driver private structure
+ * Description:
+ * it is to verify if the MAC address is valid, in case of failures it
+ * generates a random MAC address
+ */
+static void stmmac_check_ether_addr(struct stmmac_priv *priv)
+{
+ u8 addr[ETH_ALEN];
+
+ if (!is_valid_ether_addr(priv->dev->dev_addr)) {
+ stmmac_get_umac_addr(priv, priv->hw, addr, 0);
+ if (is_valid_ether_addr(addr))
+ eth_hw_addr_set(priv->dev, addr);
+ else
+ eth_hw_addr_random(priv->dev);
+ dev_info(priv->device, "device MAC address %pM\n",
+ priv->dev->dev_addr);
+ }
+}
+
+int stmmac_get_phy_intf_sel(phy_interface_t interface)
+{
+ int phy_intf_sel = -EINVAL;
+
+ if (interface == PHY_INTERFACE_MODE_MII ||
+ interface == PHY_INTERFACE_MODE_GMII)
+ phy_intf_sel = PHY_INTF_SEL_GMII_MII;
+ else if (phy_interface_mode_is_rgmii(interface))
+ phy_intf_sel = PHY_INTF_SEL_RGMII;
+ else if (interface == PHY_INTERFACE_MODE_RMII)
+ phy_intf_sel = PHY_INTF_SEL_RMII;
+ else if (interface == PHY_INTERFACE_MODE_REVMII)
+ phy_intf_sel = PHY_INTF_SEL_REVMII;
+
+ return phy_intf_sel;
+}
+EXPORT_SYMBOL_GPL(stmmac_get_phy_intf_sel);
+
+static int stmmac_prereset_configure(struct stmmac_priv *priv)
+{
+ struct plat_stmmacenet_data *plat_dat = priv->plat;
+ phy_interface_t interface;
+ struct phylink_pcs *pcs;
+ int phy_intf_sel, ret;
+
+ if (!plat_dat->set_phy_intf_sel)
+ return 0;
+
+ interface = plat_dat->phy_interface;
+
+ /* Check whether this mode uses a PCS */
+ pcs = stmmac_mac_select_pcs(&priv->phylink_config, interface);
+ if (priv->integrated_pcs && pcs == &priv->integrated_pcs->pcs) {
+ /* Request the phy_intf_sel from the integrated PCS */
+ phy_intf_sel = stmmac_integrated_pcs_get_phy_intf_sel(pcs,
+ interface);
+ } else {
+ phy_intf_sel = stmmac_get_phy_intf_sel(interface);
+ }
+
+ if (phy_intf_sel < 0) {
+ netdev_err(priv->dev,
+ "failed to get phy_intf_sel for %s: %pe\n",
+ phy_modes(interface), ERR_PTR(phy_intf_sel));
+ return phy_intf_sel;
+ }
+
+ ret = plat_dat->set_phy_intf_sel(plat_dat->bsp_priv, phy_intf_sel);
+ if (ret == -EINVAL)
+ netdev_err(priv->dev, "platform does not support %s\n",
+ phy_modes(interface));
+ else if (ret < 0)
+ netdev_err(priv->dev,
+ "platform failed to set interface %s: %pe\n",
+ phy_modes(interface), ERR_PTR(ret));
+
+ return ret;
+}
+
+/**
+ * stmmac_init_dma_engine - DMA init.
+ * @priv: driver private structure
+ * Description:
+ * It inits the DMA invoking the specific MAC/GMAC callback.
+ * Some DMA parameters can be passed from the platform;
+ * in case of these are not passed a default is kept for the MAC or GMAC.
+ */
+static int stmmac_init_dma_engine(struct stmmac_priv *priv)
+{
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ u8 dma_csr_ch = max(rx_channels_count, tx_channels_count);
+ struct stmmac_rx_queue *rx_q;
+ struct stmmac_tx_queue *tx_q;
+ int ret = 0;
+ u8 chan;
+
+ ret = stmmac_prereset_configure(priv);
+ if (ret)
+ return ret;
+
+ ret = stmmac_reset(priv);
+ if (ret) {
+ netdev_err(priv->dev, "Failed to reset the dma\n");
+ return ret;
+ }
+
+ /* DMA Configuration */
+ stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg);
+
+ if (priv->plat->axi)
+ stmmac_axi(priv, priv->ioaddr, priv->plat->axi);
+
+ /* DMA CSR Channel configuration */
+ for (chan = 0; chan < dma_csr_ch; chan++) {
+ stmmac_init_chan(priv, priv->ioaddr, priv->plat->dma_cfg, chan);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, chan, 1, 1);
+ }
+
+ /* DMA RX Channel Configuration */
+ for (chan = 0; chan < rx_channels_count; chan++) {
+ rx_q = &priv->dma_conf.rx_queue[chan];
+
+ stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ rx_q->dma_rx_phy, chan);
+
+ stmmac_set_queue_rx_tail_ptr(priv, rx_q, chan,
+ rx_q->buf_alloc_num);
+ }
+
+ /* DMA TX Channel Configuration */
+ for (chan = 0; chan < tx_channels_count; chan++) {
+ tx_q = &priv->dma_conf.tx_queue[chan];
+
+ stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ tx_q->dma_tx_phy, chan);
+
+ stmmac_set_queue_tx_tail_ptr(priv, tx_q, chan, 0);
+ }
+
+ return ret;
+}
+
+static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ u32 tx_coal_timer = priv->tx_coal_timer[queue];
+ struct stmmac_channel *ch;
+ struct napi_struct *napi;
+
+ if (!tx_coal_timer)
+ return;
+
+ ch = &priv->channel[queue];
+ napi = tx_q->xsk_pool ? &ch->rxtx_napi : &ch->tx_napi;
+
+ /* Arm timer only if napi is not already scheduled.
+ * Try to cancel any timer if napi is scheduled, timer will be armed
+ * again in the next scheduled napi.
+ */
+ if (unlikely(!napi_is_scheduled(napi))) {
+ if (unlikely(!(hrtimer_active(&tx_q->txtimer))))
+ hrtimer_start(&tx_q->txtimer,
+ STMMAC_COAL_TIMER(tx_coal_timer),
+ HRTIMER_MODE_REL);
+ } else {
+ hrtimer_try_to_cancel(&tx_q->txtimer);
+ }
+}
+
+/**
+ * stmmac_tx_timer - mitigation sw timer for tx.
+ * @t: data pointer
+ * Description:
+ * This is the timer handler to directly invoke the stmmac_tx_clean.
+ */
+static enum hrtimer_restart stmmac_tx_timer(struct hrtimer *t)
+{
+ struct stmmac_tx_queue *tx_q = container_of(t, struct stmmac_tx_queue, txtimer);
+ struct stmmac_priv *priv = tx_q->priv_data;
+ struct stmmac_channel *ch;
+ struct napi_struct *napi;
+
+ ch = &priv->channel[tx_q->queue_index];
+ napi = tx_q->xsk_pool ? &ch->rxtx_napi : &ch->tx_napi;
+
+ if (likely(napi_schedule_prep(napi))) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, ch->index, 0, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ __napi_schedule(napi);
+ }
+
+ return HRTIMER_NORESTART;
+}
+
+/**
+ * stmmac_init_coalesce - init mitigation options.
+ * @priv: driver private structure
+ * Description:
+ * This inits the coalesce parameters: i.e. timer rate,
+ * timer handler and default threshold used for enabling the
+ * interrupt on completion bit.
+ */
+static void stmmac_init_coalesce(struct stmmac_priv *priv)
+{
+ u8 tx_channel_count = priv->plat->tx_queues_to_use;
+ u8 rx_channel_count = priv->plat->rx_queues_to_use;
+ u8 chan;
+
+ for (chan = 0; chan < tx_channel_count; chan++) {
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+
+ priv->tx_coal_frames[chan] = STMMAC_TX_FRAMES;
+ priv->tx_coal_timer[chan] = STMMAC_COAL_TX_TIMER;
+
+ hrtimer_setup(&tx_q->txtimer, stmmac_tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ }
+
+ for (chan = 0; chan < rx_channel_count; chan++)
+ priv->rx_coal_frames[chan] = STMMAC_RX_FRAMES;
+}
+
+static void stmmac_set_rings_length(struct stmmac_priv *priv)
+{
+ u8 rx_channels_count = priv->plat->rx_queues_to_use;
+ u8 tx_channels_count = priv->plat->tx_queues_to_use;
+ u8 chan;
+
+ /* set TX ring length */
+ for (chan = 0; chan < tx_channels_count; chan++)
+ stmmac_set_tx_ring_len(priv, priv->ioaddr,
+ (priv->dma_conf.dma_tx_size - 1), chan);
+
+ /* set RX ring length */
+ for (chan = 0; chan < rx_channels_count; chan++)
+ stmmac_set_rx_ring_len(priv, priv->ioaddr,
+ (priv->dma_conf.dma_rx_size - 1), chan);
+}
+
+/**
+ * stmmac_set_tx_queue_weight - Set TX queue weight
+ * @priv: driver private structure
+ * Description: It is used for setting TX queues weight
+ */
+static void stmmac_set_tx_queue_weight(struct stmmac_priv *priv)
+{
+ u8 tx_queues_count = priv->plat->tx_queues_to_use;
+ u32 weight;
+ u8 queue;
+
+ for (queue = 0; queue < tx_queues_count; queue++) {
+ weight = priv->plat->tx_queues_cfg[queue].weight;
+ stmmac_set_mtl_tx_queue_weight(priv, priv->hw, weight, queue);
+ }
+}
+
+/**
+ * stmmac_configure_cbs - Configure CBS in TX queue
+ * @priv: driver private structure
+ * Description: It is used for configuring CBS in AVB TX queues
+ */
+static void stmmac_configure_cbs(struct stmmac_priv *priv)
+{
+ u8 tx_queues_count = priv->plat->tx_queues_to_use;
+ u32 mode_to_use;
+ u8 queue;
+
+ /* queue 0 is reserved for legacy traffic */
+ for (queue = 1; queue < tx_queues_count; queue++) {
+ mode_to_use = priv->plat->tx_queues_cfg[queue].mode_to_use;
+ if (mode_to_use == MTL_QUEUE_DCB)
+ continue;
+
+ stmmac_config_cbs(priv, priv->hw,
+ priv->plat->tx_queues_cfg[queue].send_slope,
+ priv->plat->tx_queues_cfg[queue].idle_slope,
+ priv->plat->tx_queues_cfg[queue].high_credit,
+ priv->plat->tx_queues_cfg[queue].low_credit,
+ queue);
+ }
+}
+
+/**
+ * stmmac_rx_queue_dma_chan_map - Map RX queue to RX dma channel
+ * @priv: driver private structure
+ * Description: It is used for mapping RX queues to RX dma channels
+ */
+static void stmmac_rx_queue_dma_chan_map(struct stmmac_priv *priv)
+{
+ u8 rx_queues_count = priv->plat->rx_queues_to_use;
+ u8 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);
+ }
+}
+
+/**
+ * stmmac_mac_config_rx_queues_prio - Configure RX Queue priority
+ * @priv: driver private structure
+ * Description: It is used for configuring the RX Queue Priority
+ */
+static void stmmac_mac_config_rx_queues_prio(struct stmmac_priv *priv)
+{
+ u8 rx_queues_count = priv->plat->rx_queues_to_use;
+ u8 queue;
+ u32 prio;
+
+ for (queue = 0; queue < rx_queues_count; queue++) {
+ if (!priv->plat->rx_queues_cfg[queue].use_prio)
+ continue;
+
+ prio = priv->plat->rx_queues_cfg[queue].prio;
+ stmmac_rx_queue_prio(priv, priv->hw, prio, queue);
+ }
+}
+
+/**
+ * stmmac_mac_config_tx_queues_prio - Configure TX Queue priority
+ * @priv: driver private structure
+ * Description: It is used for configuring the TX Queue Priority
+ */
+static void stmmac_mac_config_tx_queues_prio(struct stmmac_priv *priv)
+{
+ u8 tx_queues_count = priv->plat->tx_queues_to_use;
+ u8 queue;
+ u32 prio;
+
+ for (queue = 0; queue < tx_queues_count; queue++) {
+ if (!priv->plat->tx_queues_cfg[queue].use_prio)
+ continue;
+
+ prio = priv->plat->tx_queues_cfg[queue].prio;
+ stmmac_tx_queue_prio(priv, priv->hw, prio, queue);
+ }
+}
+
+/**
+ * stmmac_mac_config_rx_queues_routing - Configure RX Queue Routing
+ * @priv: driver private structure
+ * Description: It is used for configuring the RX queue routing
+ */
+static void stmmac_mac_config_rx_queues_routing(struct stmmac_priv *priv)
+{
+ u8 rx_queues_count = priv->plat->rx_queues_to_use;
+ u8 packet;
+ u8 queue;
+
+ for (queue = 0; queue < rx_queues_count; queue++) {
+ /* no specific packet type routing specified for the queue */
+ if (priv->plat->rx_queues_cfg[queue].pkt_route == 0x0)
+ continue;
+
+ packet = priv->plat->rx_queues_cfg[queue].pkt_route;
+ stmmac_rx_queue_routing(priv, priv->hw, packet, queue);
+ }
+}
+
+static void stmmac_mac_config_rss(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.rssen || !priv->plat->rss_en) {
+ priv->rss.enable = false;
+ return;
+ }
+
+ if (priv->dev->features & NETIF_F_RXHASH)
+ priv->rss.enable = true;
+ else
+ priv->rss.enable = false;
+
+ stmmac_rss_configure(priv, priv->hw, &priv->rss,
+ priv->plat->rx_queues_to_use);
+}
+
+/**
+ * stmmac_mtl_configuration - Configure MTL
+ * @priv: driver private structure
+ * Description: It is used for configuring MTL
+ */
+static void stmmac_mtl_configuration(struct stmmac_priv *priv)
+{
+ u8 rx_queues_count = priv->plat->rx_queues_to_use;
+ u8 tx_queues_count = priv->plat->tx_queues_to_use;
+
+ if (tx_queues_count > 1)
+ stmmac_set_tx_queue_weight(priv);
+
+ /* Configure MTL RX algorithms */
+ if (rx_queues_count > 1)
+ stmmac_prog_mtl_rx_algorithms(priv, priv->hw,
+ priv->plat->rx_sched_algorithm);
+
+ /* Configure MTL TX algorithms */
+ if (tx_queues_count > 1)
+ stmmac_prog_mtl_tx_algorithms(priv, priv->hw,
+ priv->plat->tx_sched_algorithm);
+
+ /* Configure CBS in AVB TX queues */
+ if (tx_queues_count > 1)
+ stmmac_configure_cbs(priv);
+
+ /* Map RX MTL to DMA channels */
+ stmmac_rx_queue_dma_chan_map(priv);
+
+ /* Enable MAC RX Queues */
+ stmmac_mac_enable_rx_queues(priv);
+
+ /* Set RX priorities */
+ if (rx_queues_count > 1)
+ stmmac_mac_config_rx_queues_prio(priv);
+
+ /* Set TX priorities */
+ if (tx_queues_count > 1)
+ stmmac_mac_config_tx_queues_prio(priv);
+
+ /* Set RX routing */
+ if (rx_queues_count > 1)
+ stmmac_mac_config_rx_queues_routing(priv);
+
+ /* Receive Side Scaling */
+ if (rx_queues_count > 1)
+ stmmac_mac_config_rss(priv);
+}
+
+static void stmmac_safety_feat_configuration(struct stmmac_priv *priv)
+{
+ if (priv->dma_cap.asp) {
+ netdev_info(priv->dev, "Enabling Safety Features\n");
+ stmmac_safety_feat_config(priv, priv->ioaddr, priv->dma_cap.asp,
+ priv->plat->safety_feat_cfg);
+ } else {
+ netdev_info(priv->dev, "No Safety Features support found\n");
+ }
+}
+
+/* STM32MP25xx (dwmac v5.3) states "Do not enable time-based scheduling for
+ * channels on which the TSO feature is enabled." If we have a skb for a
+ * channel which has TBS enabled, fall back to software GSO.
+ */
+static bool stmmac_tso_channel_permitted(struct stmmac_priv *priv,
+ unsigned int chan)
+{
+ /* TSO and TBS cannot co-exist */
+ return !(priv->dma_conf.tx_queue[chan].tbs & STMMAC_TBS_AVAIL);
+}
+
+/**
+ * stmmac_hw_setup - setup mac in a usable state.
+ * @dev : pointer to the device structure.
+ * Description:
+ * this is the main function to setup the HW in a usable state because the
+ * dma engine is reset, the core registers are configured (e.g. AXI,
+ * Checksum features, timers). The DMA is ready to start receiving and
+ * transmitting.
+ * Return value:
+ * 0 on success and an appropriate (-)ve integer as defined in errno.h
+ * file on failure.
+ */
+static int stmmac_hw_setup(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 rx_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ bool sph_en;
+ u8 chan;
+ int ret;
+
+ /* Make sure RX clock is enabled */
+ if (priv->hw->phylink_pcs)
+ phylink_pcs_pre_init(priv->phylink, priv->hw->phylink_pcs);
+
+ /* Note that clk_rx_i must be running for reset to complete. This
+ * clock may also be required when setting the MAC address.
+ *
+ * Block the receive clock stop for LPI mode at the PHY in case
+ * the link is established with EEE mode active.
+ */
+ phylink_rx_clk_stop_block(priv->phylink);
+
+ /* DMA initialization and SW reset */
+ ret = stmmac_init_dma_engine(priv);
+ if (ret < 0) {
+ phylink_rx_clk_stop_unblock(priv->phylink);
+ netdev_err(priv->dev, "%s: DMA engine initialization failed\n",
+ __func__);
+ return ret;
+ }
+
+ /* Copy the MAC addr into the HW */
+ stmmac_set_umac_addr(priv, priv->hw, dev->dev_addr, 0);
+ phylink_rx_clk_stop_unblock(priv->phylink);
+
+ /* Initialize the MAC Core */
+ stmmac_core_init(priv, priv->hw, dev);
+
+ /* Initialize MTL*/
+ stmmac_mtl_configuration(priv);
+
+ /* Initialize Safety Features */
+ stmmac_safety_feat_configuration(priv);
+
+ ret = stmmac_rx_ipc(priv, priv->hw);
+ if (!ret) {
+ netdev_warn(priv->dev, "RX IPC Checksum Offload disabled\n");
+ priv->plat->rx_coe = STMMAC_RX_COE_NONE;
+ priv->hw->rx_csum = 0;
+ }
+
+ /* Enable the MAC Rx/Tx */
+ stmmac_mac_set(priv, priv->ioaddr, true);
+
+ /* Set the HW DMA mode and the COE */
+ stmmac_dma_operation_mode(priv);
+
+ stmmac_mmc_setup(priv);
+
+ if (priv->use_riwt) {
+ u32 queue;
+
+ for (queue = 0; queue < rx_cnt; queue++) {
+ if (!priv->rx_riwt[queue])
+ priv->rx_riwt[queue] = DEF_DMA_RIWT;
+
+ stmmac_rx_watchdog(priv, priv->ioaddr,
+ priv->rx_riwt[queue], queue);
+ }
+ }
+
+ /* set TX and RX rings length */
+ stmmac_set_rings_length(priv);
+
+ /* Enable TSO */
+ if (priv->dma_cap.tsoen && priv->plat->flags & STMMAC_FLAG_TSO_EN) {
+ for (chan = 0; chan < tx_cnt; chan++) {
+ if (!stmmac_tso_channel_permitted(priv, chan))
+ continue;
+
+ stmmac_enable_tso(priv, priv->ioaddr, 1, chan);
+ }
+ }
+
+ /* Enable Split Header */
+ sph_en = (priv->hw->rx_csum > 0) && priv->sph_active;
+ for (chan = 0; chan < rx_cnt; chan++)
+ stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan);
+
+
+ /* VLAN Tag Insertion */
+ if (priv->dma_cap.vlins)
+ stmmac_enable_vlan(priv, priv->hw, STMMAC_VLAN_INSERT);
+
+ /* TBS */
+ for (chan = 0; chan < tx_cnt; chan++) {
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
+ int enable = tx_q->tbs & STMMAC_TBS_AVAIL;
+
+ stmmac_enable_tbs(priv, priv->ioaddr, enable, chan);
+ }
+
+ /* Configure real RX and TX queues */
+ netif_set_real_num_rx_queues(dev, priv->plat->rx_queues_to_use);
+ netif_set_real_num_tx_queues(dev, priv->plat->tx_queues_to_use);
+
+ /* Start the ball rolling... */
+ stmmac_start_all_dma(priv);
+
+ phylink_rx_clk_stop_block(priv->phylink);
+ stmmac_set_hw_vlan_mode(priv, priv->hw);
+ phylink_rx_clk_stop_unblock(priv->phylink);
+
+ return 0;
+}
+
+static void stmmac_free_irq(struct net_device *dev,
+ enum request_irq_err irq_err, int irq_idx)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_msi *msi = priv->msi;
+ int j;
+
+ switch (irq_err) {
+ case REQ_IRQ_ERR_ALL:
+ irq_idx = priv->plat->tx_queues_to_use;
+ fallthrough;
+ case REQ_IRQ_ERR_TX:
+ for (j = irq_idx - 1; msi && j >= 0; j--) {
+ if (msi->tx_irq[j] > 0) {
+ irq_set_affinity_hint(msi->tx_irq[j], NULL);
+ free_irq(msi->tx_irq[j],
+ &priv->dma_conf.tx_queue[j]);
+ }
+ }
+ irq_idx = priv->plat->rx_queues_to_use;
+ fallthrough;
+ case REQ_IRQ_ERR_RX:
+ for (j = irq_idx - 1; msi && j >= 0; j--) {
+ if (msi->rx_irq[j] > 0) {
+ irq_set_affinity_hint(msi->rx_irq[j], NULL);
+ free_irq(msi->rx_irq[j],
+ &priv->dma_conf.rx_queue[j]);
+ }
+ }
+
+ if (msi && msi->sfty_ue_irq > 0 && msi->sfty_ue_irq != dev->irq)
+ free_irq(msi->sfty_ue_irq, dev);
+ fallthrough;
+ case REQ_IRQ_ERR_SFTY_UE:
+ if (msi && msi->sfty_ce_irq > 0 && msi->sfty_ce_irq != dev->irq)
+ free_irq(msi->sfty_ce_irq, dev);
+ fallthrough;
+ case REQ_IRQ_ERR_SFTY_CE:
+ if (priv->wol_irq > 0 && priv->wol_irq != dev->irq)
+ free_irq(priv->wol_irq, dev);
+ fallthrough;
+ case REQ_IRQ_ERR_SFTY:
+ if (priv->sfty_irq > 0 && priv->sfty_irq != dev->irq)
+ free_irq(priv->sfty_irq, dev);
+ fallthrough;
+ case REQ_IRQ_ERR_WOL:
+ free_irq(dev->irq, dev);
+ fallthrough;
+ case REQ_IRQ_ERR_MAC:
+ case REQ_IRQ_ERR_NO:
+ /* If MAC IRQ request error, no more IRQ to free */
+ break;
+ }
+}
+
+static int stmmac_msi_init(struct stmmac_priv *priv,
+ struct stmmac_resources *res)
+{
+ int i;
+
+ priv->msi = devm_kmalloc(priv->device, sizeof(*priv->msi), GFP_KERNEL);
+ if (!priv->msi)
+ return -ENOMEM;
+
+ priv->msi->sfty_ce_irq = res->sfty_ce_irq;
+ priv->msi->sfty_ue_irq = res->sfty_ue_irq;
+
+ for (i = 0; i < MTL_MAX_RX_QUEUES; i++)
+ priv->msi->rx_irq[i] = res->rx_irq[i];
+ for (i = 0; i < MTL_MAX_TX_QUEUES; i++)
+ priv->msi->tx_irq[i] = res->tx_irq[i];
+
+ return 0;
+}
+
+static int stmmac_request_irq_multi_msi(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_msi *msi = priv->msi;
+ enum request_irq_err irq_err;
+ int irq_idx = 0;
+ char *int_name;
+ int ret;
+ int i;
+
+ /* For common interrupt */
+ int_name = msi->int_name_mac;
+ sprintf(int_name, "%s:%s", dev->name, "mac");
+ ret = request_irq(dev->irq, stmmac_mac_interrupt,
+ 0, int_name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc mac MSI %d (error: %d)\n",
+ __func__, dev->irq, ret);
+ irq_err = REQ_IRQ_ERR_MAC;
+ goto irq_error;
+ }
+
+ /* Request the Wake IRQ in case of another line
+ * is used for WoL
+ */
+ if (priv->wol_irq > 0 && priv->wol_irq != dev->irq) {
+ int_name = msi->int_name_wol;
+ sprintf(int_name, "%s:%s", dev->name, "wol");
+ ret = request_irq(priv->wol_irq,
+ stmmac_mac_interrupt,
+ 0, int_name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc wol MSI %d (error: %d)\n",
+ __func__, priv->wol_irq, ret);
+ irq_err = REQ_IRQ_ERR_WOL;
+ goto irq_error;
+ }
+ }
+
+ /* Request the common Safety Feature Correctible/Uncorrectible
+ * Error line in case of another line is used
+ */
+ if (priv->sfty_irq > 0 && priv->sfty_irq != dev->irq) {
+ int_name = msi->int_name_sfty;
+ sprintf(int_name, "%s:%s", dev->name, "safety");
+ ret = request_irq(priv->sfty_irq, stmmac_safety_interrupt,
+ 0, int_name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc sfty MSI %d (error: %d)\n",
+ __func__, priv->sfty_irq, ret);
+ irq_err = REQ_IRQ_ERR_SFTY;
+ goto irq_error;
+ }
+ }
+
+ /* Request the Safety Feature Correctible Error line in
+ * case of another line is used
+ */
+ if (msi->sfty_ce_irq > 0 && msi->sfty_ce_irq != dev->irq) {
+ int_name = msi->int_name_sfty_ce;
+ sprintf(int_name, "%s:%s", dev->name, "safety-ce");
+ ret = request_irq(msi->sfty_ce_irq,
+ stmmac_safety_interrupt,
+ 0, int_name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc sfty ce MSI %d (error: %d)\n",
+ __func__, msi->sfty_ce_irq, ret);
+ irq_err = REQ_IRQ_ERR_SFTY_CE;
+ goto irq_error;
+ }
+ }
+
+ /* Request the Safety Feature Uncorrectible Error line in
+ * case of another line is used
+ */
+ if (msi->sfty_ue_irq > 0 && msi->sfty_ue_irq != dev->irq) {
+ int_name = msi->int_name_sfty_ue;
+ sprintf(int_name, "%s:%s", dev->name, "safety-ue");
+ ret = request_irq(msi->sfty_ue_irq,
+ stmmac_safety_interrupt,
+ 0, int_name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc sfty ue MSI %d (error: %d)\n",
+ __func__, msi->sfty_ue_irq, ret);
+ irq_err = REQ_IRQ_ERR_SFTY_UE;
+ goto irq_error;
+ }
+ }
+
+ /* Request Rx MSI irq */
+ for (i = 0; i < priv->plat->rx_queues_to_use; i++) {
+ if (i >= MTL_MAX_RX_QUEUES)
+ break;
+ if (msi->rx_irq[i] == 0)
+ continue;
+
+ int_name = msi->int_name_rx_irq[i];
+ sprintf(int_name, "%s:%s-%d", dev->name, "rx", i);
+ ret = request_irq(msi->rx_irq[i],
+ stmmac_msi_intr_rx,
+ 0, int_name, &priv->dma_conf.rx_queue[i]);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc rx-%d MSI %d (error: %d)\n",
+ __func__, i, msi->rx_irq[i], ret);
+ irq_err = REQ_IRQ_ERR_RX;
+ irq_idx = i;
+ goto irq_error;
+ }
+ irq_set_affinity_hint(msi->rx_irq[i],
+ cpumask_of(i % num_online_cpus()));
+ }
+
+ /* Request Tx MSI irq */
+ for (i = 0; i < priv->plat->tx_queues_to_use; i++) {
+ if (i >= MTL_MAX_TX_QUEUES)
+ break;
+ if (msi->tx_irq[i] == 0)
+ continue;
+
+ int_name = msi->int_name_tx_irq[i];
+ sprintf(int_name, "%s:%s-%d", dev->name, "tx", i);
+ ret = request_irq(msi->tx_irq[i],
+ stmmac_msi_intr_tx,
+ 0, int_name, &priv->dma_conf.tx_queue[i]);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: alloc tx-%d MSI %d (error: %d)\n",
+ __func__, i, msi->tx_irq[i], ret);
+ irq_err = REQ_IRQ_ERR_TX;
+ irq_idx = i;
+ goto irq_error;
+ }
+ irq_set_affinity_hint(msi->tx_irq[i],
+ cpumask_of(i % num_online_cpus()));
+ }
+
+ return 0;
+
+irq_error:
+ stmmac_free_irq(dev, irq_err, irq_idx);
+ return ret;
+}
+
+static int stmmac_request_irq_single(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ enum request_irq_err irq_err;
+ int ret;
+
+ ret = request_irq(dev->irq, stmmac_interrupt,
+ IRQF_SHARED, dev->name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: ERROR: allocating the IRQ %d (error: %d)\n",
+ __func__, dev->irq, ret);
+ irq_err = REQ_IRQ_ERR_MAC;
+ goto irq_error;
+ }
+
+ /* Request the Wake IRQ in case of another line
+ * is used for WoL
+ */
+ if (priv->wol_irq > 0 && priv->wol_irq != dev->irq) {
+ ret = request_irq(priv->wol_irq, stmmac_interrupt,
+ IRQF_SHARED, dev->name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: ERROR: allocating the WoL IRQ %d (%d)\n",
+ __func__, priv->wol_irq, ret);
+ irq_err = REQ_IRQ_ERR_WOL;
+ goto irq_error;
+ }
+ }
+
+ /* Request the common Safety Feature Correctible/Uncorrectible
+ * Error line in case of another line is used
+ */
+ if (priv->sfty_irq > 0 && priv->sfty_irq != dev->irq) {
+ ret = request_irq(priv->sfty_irq, stmmac_safety_interrupt,
+ IRQF_SHARED, dev->name, dev);
+ if (unlikely(ret < 0)) {
+ netdev_err(priv->dev,
+ "%s: ERROR: allocating the sfty IRQ %d (%d)\n",
+ __func__, priv->sfty_irq, ret);
+ irq_err = REQ_IRQ_ERR_SFTY;
+ goto irq_error;
+ }
+ }
+
+ return 0;
+
+irq_error:
+ stmmac_free_irq(dev, irq_err, 0);
+ return ret;
+}
+
+static int stmmac_request_irq(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int ret;
+
+ /* Request the IRQ lines */
+ if (priv->plat->flags & STMMAC_FLAG_MULTI_MSI_EN)
+ ret = stmmac_request_irq_multi_msi(dev);
+ else
+ ret = stmmac_request_irq_single(dev);
+
+ return ret;
+}
+
+/**
+ * stmmac_setup_dma_desc - Generate a dma_conf and allocate DMA queue
+ * @priv: driver private structure
+ * @mtu: MTU to setup the dma queue and buf with
+ * Description: Allocate and generate a dma_conf based on the provided MTU.
+ * Allocate the Tx/Rx DMA queue and init them.
+ * Return value:
+ * the dma_conf allocated struct on success and an appropriate ERR_PTR on failure.
+ */
+static struct stmmac_dma_conf *
+stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu)
+{
+ struct stmmac_dma_conf *dma_conf;
+ int bfsize, ret;
+ u8 chan;
+
+ dma_conf = kzalloc_obj(*dma_conf);
+ if (!dma_conf) {
+ netdev_err(priv->dev, "%s: DMA conf allocation failed\n",
+ __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* Returns 0 or BUF_SIZE_16KiB if mtu > 8KiB and dwmac4 or ring mode */
+ bfsize = stmmac_set_16kib_bfsize(priv, mtu);
+ if (bfsize < 0)
+ bfsize = 0;
+
+ if (bfsize < BUF_SIZE_16KiB)
+ bfsize = stmmac_set_bfsize(mtu);
+
+ dma_conf->dma_buf_sz = bfsize;
+ /* Chose the tx/rx size from the already defined one in the
+ * priv struct. (if defined)
+ */
+ dma_conf->dma_tx_size = priv->dma_conf.dma_tx_size;
+ dma_conf->dma_rx_size = priv->dma_conf.dma_rx_size;
+
+ if (!dma_conf->dma_tx_size)
+ dma_conf->dma_tx_size = DMA_DEFAULT_TX_SIZE;
+ if (!dma_conf->dma_rx_size)
+ dma_conf->dma_rx_size = DMA_DEFAULT_RX_SIZE;
+
+ /* Earlier check for TBS */
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) {
+ struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[chan];
+ int tbs_en = priv->plat->tx_queues_cfg[chan].tbs_en;
+
+ /* Setup per-TXQ tbs flag before TX descriptor alloc */
+ tx_q->tbs |= tbs_en ? STMMAC_TBS_AVAIL : 0;
+ }
+
+ ret = alloc_dma_desc_resources(priv, dma_conf);
+ if (ret < 0) {
+ netdev_err(priv->dev, "%s: DMA descriptors allocation failed\n",
+ __func__);
+ goto alloc_error;
+ }
+
+ ret = init_dma_desc_rings(priv->dev, dma_conf, GFP_KERNEL);
+ if (ret < 0) {
+ netdev_err(priv->dev, "%s: DMA descriptors initialization failed\n",
+ __func__);
+ goto init_error;
+ }
+
+ return dma_conf;
+
+init_error:
+ free_dma_desc_resources(priv, dma_conf);
+alloc_error:
+ kfree(dma_conf);
+ return ERR_PTR(ret);
+}
+
+/**
+ * __stmmac_open - open entry point of the driver
+ * @dev : pointer to the device structure.
+ * @dma_conf : structure to take the dma data
+ * Description:
+ * This function is the open entry point of the driver.
+ * Return value:
+ * 0 on success and an appropriate (-)ve integer as defined in errno.h
+ * file on failure.
+ */
+static int __stmmac_open(struct net_device *dev,
+ struct stmmac_dma_conf *dma_conf)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 chan;
+ int ret;
+
+ for (int i = 0; i < priv->plat->tx_queues_to_use; i++)
+ if (priv->dma_conf.tx_queue[i].tbs & STMMAC_TBS_EN)
+ dma_conf->tx_queue[i].tbs = priv->dma_conf.tx_queue[i].tbs;
+ memcpy(&priv->dma_conf, dma_conf, sizeof(*dma_conf));
+
+ stmmac_reset_queues_param(priv);
+
+ ret = stmmac_hw_setup(dev);
+ if (ret < 0) {
+ netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
+ goto init_error;
+ }
+
+ stmmac_setup_ptp(priv);
+
+ stmmac_init_coalesce(priv);
+
+ phylink_start(priv->phylink);
+
+ stmmac_vlan_restore(priv);
+
+ ret = stmmac_request_irq(dev);
+ if (ret)
+ goto irq_error;
+
+ stmmac_enable_all_queues(priv);
+ netif_tx_start_all_queues(priv->dev);
+ stmmac_enable_all_dma_irq(priv);
+
+ return 0;
+
+irq_error:
+ phylink_stop(priv->phylink);
+
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+
+ stmmac_release_ptp(priv);
+init_error:
+ return ret;
+}
+
+static int stmmac_open(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_dma_conf *dma_conf;
+ int ret;
+
+ /* Initialise the tx lpi timer, converting from msec to usec */
+ if (!priv->tx_lpi_timer)
+ priv->tx_lpi_timer = eee_timer * 1000;
+
+ dma_conf = stmmac_setup_dma_desc(priv, dev->mtu);
+ if (IS_ERR(dma_conf))
+ return PTR_ERR(dma_conf);
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ goto err_dma_resources;
+
+ ret = stmmac_init_phy(dev);
+ if (ret)
+ goto err_runtime_pm;
+
+ if (!(priv->plat->flags & STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP)) {
+ ret = stmmac_legacy_serdes_power_up(priv);
+ if (ret < 0)
+ goto err_disconnect_phy;
+ }
+
+ ret = __stmmac_open(dev, dma_conf);
+ if (ret)
+ goto err_serdes;
+
+ kfree(dma_conf);
+
+ /* We may have called phylink_speed_down before */
+ phylink_speed_up(priv->phylink);
+
+ return ret;
+
+err_serdes:
+ stmmac_legacy_serdes_power_down(priv);
+err_disconnect_phy:
+ phylink_disconnect_phy(priv->phylink);
+err_runtime_pm:
+ pm_runtime_put(priv->device);
+err_dma_resources:
+ free_dma_desc_resources(priv, dma_conf);
+ kfree(dma_conf);
+ return ret;
+}
+
+static void __stmmac_release(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 chan;
+
+ /* Stop and disconnect the PHY */
+ phylink_stop(priv->phylink);
+
+ stmmac_disable_all_queues(priv);
+
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+
+ netif_tx_disable(dev);
+
+ /* Free the IRQ lines */
+ stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+
+ /* Stop TX/RX DMA and clear the descriptors */
+ stmmac_stop_all_dma(priv);
+
+ /* Release and free the Rx/Tx resources */
+ free_dma_desc_resources(priv, &priv->dma_conf);
+
+ stmmac_release_ptp(priv);
+
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
+}
+
+/**
+ * stmmac_release - close entry point of the driver
+ * @dev : device pointer.
+ * Description:
+ * This is the stop entry point of the driver.
+ */
+static int stmmac_release(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ /* If the PHY or MAC has WoL enabled, then the PHY will not be
+ * suspended when phylink_stop() is called below. Set the PHY
+ * to its slowest speed to save power.
+ */
+ if (device_may_wakeup(priv->device))
+ phylink_speed_down(priv->phylink, false);
+
+ __stmmac_release(dev);
+
+ stmmac_legacy_serdes_power_down(priv);
+ phylink_disconnect_phy(priv->phylink);
+ pm_runtime_put(priv->device);
+
+ return 0;
+}
+
+static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb,
+ struct stmmac_tx_queue *tx_q)
+{
+ struct dma_desc *p;
+ u16 tag = 0x0;
+
+ if (!priv->dma_cap.vlins || !skb_vlan_tag_present(skb))
+ return false;
+
+ tag = skb_vlan_tag_get(skb);
+
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ p = &tx_q->dma_entx[tx_q->cur_tx].basic;
+ else
+ p = &tx_q->dma_tx[tx_q->cur_tx];
+
+ if (stmmac_set_desc_vlan_tag(priv, p, tag, 0x0, 0x0))
+ return false;
+
+ stmmac_set_tx_owner(priv, p);
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
+ return true;
+}
+
+/**
+ * stmmac_tso_allocator - close entry point of the driver
+ * @priv: driver private structure
+ * @des: buffer start address
+ * @total_len: total length to fill in descriptors
+ * @last_segment: condition for the last descriptor
+ * @queue: TX queue index
+ * Description:
+ * This function fills descriptor and request new descriptors according to
+ * buffer length to fill
+ */
+static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des,
+ int total_len, bool last_segment, u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct dma_desc *desc;
+ u32 buff_size;
+ int tmp_len;
+
+ tmp_len = total_len;
+
+ while (tmp_len > 0) {
+ dma_addr_t curr_addr;
+
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx,
+ priv->dma_conf.dma_tx_size);
+ WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]);
+
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ desc = &tx_q->dma_entx[tx_q->cur_tx].basic;
+ else
+ desc = &tx_q->dma_tx[tx_q->cur_tx];
+
+ curr_addr = des + (total_len - tmp_len);
+ stmmac_set_desc_addr(priv, desc, curr_addr);
+ buff_size = tmp_len >= TSO_MAX_BUFF_SIZE ?
+ TSO_MAX_BUFF_SIZE : tmp_len;
+
+ stmmac_prepare_tso_tx_desc(priv, desc, 0, buff_size,
+ 0, 1,
+ (last_segment) && (tmp_len <= TSO_MAX_BUFF_SIZE),
+ 0, 0);
+
+ tmp_len -= TSO_MAX_BUFF_SIZE;
+ }
+}
+
+static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+
+ /* The own bit must be the latest setting done when prepare the
+ * descriptor and then barrier is needed to make sure that
+ * all is coherent before granting the DMA engine.
+ */
+ wmb();
+
+ stmmac_set_queue_tx_tail_ptr(priv, tx_q, queue, tx_q->cur_tx);
+}
+
+static void stmmac_set_gso_types(struct stmmac_priv *priv, bool tso)
+{
+ if (!tso) {
+ priv->gso_enabled_types = 0;
+ } else {
+ /* Manage oversized TCP frames for GMAC4 device */
+ priv->gso_enabled_types = SKB_GSO_TCPV4 | SKB_GSO_TCPV6;
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4)
+ priv->gso_enabled_types |= SKB_GSO_UDP_L4;
+ }
+}
+
+static void stmmac_set_gso_features(struct net_device *ndev)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ const struct stmmac_dma_cfg *dma_cfg;
+ int txpbl;
+
+ if (priv->dma_cap.tsoen)
+ dev_info(priv->device, "TSO supported\n");
+
+ if (!(priv->plat->flags & STMMAC_FLAG_TSO_EN))
+ return;
+
+ if (!priv->dma_cap.tsoen) {
+ dev_warn(priv->device, "platform requests unsupported TSO\n");
+ return;
+ }
+
+ /* FIXME:
+ * STM32MP151 (v4.2 userver v4.0) states that TxPBL must be >= 4. It
+ * is not clear whether PBLx8 (which multiplies the PBL value by 8)
+ * influences this.
+ */
+ dma_cfg = priv->plat->dma_cfg;
+ txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
+ if (txpbl < 4) {
+ dev_warn(priv->device, "txpbl(%d) is too low for TSO\n", txpbl);
+ return;
+ }
+
+ ndev->hw_features |= NETIF_F_TSO | NETIF_F_TSO6;
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4)
+ ndev->hw_features |= NETIF_F_GSO_UDP_L4;
+
+ stmmac_set_gso_types(priv, true);
+
+ dev_info(priv->device, "TSO feature enabled\n");
+}
+
+static size_t stmmac_tso_header_size(struct sk_buff *skb)
+{
+ size_t size;
+
+ if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
+ size = skb_transport_offset(skb) + sizeof(struct udphdr);
+ else
+ size = skb_tcp_all_headers(skb);
+
+ return size;
+}
+
+/* STM32MP151 (dwmac v4.2) and STM32MP25xx (dwmac v5.3) states for TDES2 normal
+ * (read format) descriptor that the maximum header length supported for the
+ * TSO feature is 1023 bytes.
+ *
+ * While IPv4 is limited to MAC+VLAN+IPv4+ext+TCP+ext = 138 bytes, the IPv6
+ * extension headers aren't similarly limited.
+ *
+ * Fall back to software GSO for these skbs. Also check that the MSS is >=
+ * the recommended 64 bytes (documented in ETH_DMACxCR register description),
+ * and that a the header plus MSS is not larger than 16383 (documented in
+ * "Building the Descriptor and the packet for the TSO feature").
+ */
+static bool stmmac_tso_valid_packet(struct sk_buff *skb)
+{
+ size_t header_len = stmmac_tso_header_size(skb);
+ unsigned int gso_size = skb_shinfo(skb)->gso_size;
+
+ return header_len <= 1023 && gso_size >= 64 &&
+ header_len + gso_size < 16383;
+}
+
+/**
+ * stmmac_tso_xmit - Tx entry point of the driver for oversized frames (TSO)
+ * @skb : the socket buffer
+ * @dev : device pointer
+ * Description: this is the transmit function that is called on TSO frames
+ * (support available on GMAC4 and newer chips).
+ * Diagram below show the ring programming in case of TSO frames:
+ *
+ * First Descriptor
+ * --------
+ * | DES0 |---> buffer1 = L2/L3/L4 header
+ * | DES1 |---> can be used as buffer2 for TCP Payload if the DMA AXI address
+ * | | width is 32-bit, but we never use it.
+ * | | Also can be used as the most-significant 8-bits or 16-bits of
+ * | | buffer1 address pointer if the DMA AXI address width is 40-bit
+ * | | or 48-bit, and we always use it.
+ * | DES2 |---> buffer1 len
+ * | DES3 |---> must set TSE, TCP hdr len-> [22:19]. TCP payload len [17:0]
+ * --------
+ * --------
+ * | DES0 |---> buffer1 = TCP Payload (can continue on next descr...)
+ * | DES1 |---> same as the First Descriptor
+ * | DES2 |---> buffer1 len
+ * | DES3 |
+ * --------
+ * |
+ * ...
+ * |
+ * --------
+ * | DES0 |---> buffer1 = Split TCP Payload
+ * | DES1 |---> same as the First Descriptor
+ * | DES2 |---> buffer1 len
+ * | DES3 |
+ * --------
+ *
+ * mss is fixed when enable tso, so w/o programming the TDES3 ctx field.
+ */
+static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct dma_desc *desc, *first, *mss_desc = NULL;
+ struct stmmac_priv *priv = netdev_priv(dev);
+ unsigned int first_entry, tx_packets;
+ struct stmmac_txq_stats *txq_stats;
+ struct stmmac_tx_queue *tx_q;
+ bool set_ic, is_last_segment;
+ u32 pay_len, mss, queue;
+ int i, first_tx, nfrags;
+ u8 proto_hdr_len, hdr;
+ dma_addr_t des;
+
+ nfrags = skb_shinfo(skb)->nr_frags;
+ queue = skb_get_queue_mapping(skb);
+
+ tx_q = &priv->dma_conf.tx_queue[queue];
+ txq_stats = &priv->xstats.txq_stats[queue];
+ first_tx = tx_q->cur_tx;
+
+ /* Compute header lengths */
+ proto_hdr_len = stmmac_tso_header_size(skb);
+ if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
+ hdr = sizeof(struct udphdr);
+ else
+ hdr = tcp_hdrlen(skb);
+
+ /* Desc availability based on threshold should be enough safe */
+ if (unlikely(stmmac_tx_avail(priv, queue) <
+ (((skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1)))) {
+ if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) {
+ netif_tx_stop_queue(netdev_get_tx_queue(priv->dev,
+ queue));
+ /* This is a hard error, log it. */
+ netdev_err(priv->dev,
+ "%s: Tx Ring full when queue awake\n",
+ __func__);
+ }
+ return NETDEV_TX_BUSY;
+ }
+
+ pay_len = skb_headlen(skb) - proto_hdr_len; /* no frags */
+
+ mss = skb_shinfo(skb)->gso_size;
+
+ /* set new MSS value if needed */
+ if (mss != tx_q->mss) {
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ mss_desc = &tx_q->dma_entx[tx_q->cur_tx].basic;
+ else
+ mss_desc = &tx_q->dma_tx[tx_q->cur_tx];
+
+ stmmac_set_mss(priv, mss_desc, mss);
+ tx_q->mss = mss;
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx,
+ priv->dma_conf.dma_tx_size);
+ WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]);
+ }
+
+ if (netif_msg_tx_queued(priv)) {
+ pr_info("%s: hdrlen %d, hdr_len %d, pay_len %d, mss %d\n",
+ __func__, hdr, proto_hdr_len, pay_len, mss);
+ pr_info("\tskb->len %d, skb->data_len %d\n", skb->len,
+ skb->data_len);
+ }
+
+ first_entry = tx_q->cur_tx;
+ WARN_ON(tx_q->tx_skbuff[first_entry]);
+
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ desc = &tx_q->dma_entx[first_entry].basic;
+ else
+ desc = &tx_q->dma_tx[first_entry];
+ first = desc;
+
+ /* first descriptor: fill Headers on Buf1 */
+ des = dma_map_single(priv->device, skb->data, skb_headlen(skb),
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->device, des))
+ goto dma_map_err;
+
+ stmmac_set_desc_addr(priv, first, des);
+ stmmac_tso_allocator(priv, des + proto_hdr_len, pay_len,
+ (nfrags == 0), queue);
+
+ /* In case two or more DMA transmit descriptors are allocated for this
+ * non-paged SKB data, the DMA buffer address should be saved to
+ * tx_q->tx_skbuff_dma[].buf corresponding to the last descriptor,
+ * and leave the other tx_q->tx_skbuff_dma[].buf as NULL to guarantee
+ * that stmmac_tx_clean() does not unmap the entire DMA buffer too early
+ * since the tail areas of the DMA buffer can be accessed by DMA engine
+ * sooner or later.
+ * By saving the DMA buffer address to tx_q->tx_skbuff_dma[].buf
+ * corresponding to the last descriptor, stmmac_tx_clean() will unmap
+ * this DMA buffer right after the DMA engine completely finishes the
+ * full buffer transmission.
+ */
+ stmmac_set_tx_skb_dma_entry(tx_q, tx_q->cur_tx, des, skb_headlen(skb),
+ false);
+
+ /* Prepare fragments */
+ for (i = 0; i < nfrags; i++) {
+ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ des = skb_frag_dma_map(priv->device, frag, 0,
+ skb_frag_size(frag),
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->device, des))
+ goto dma_map_err;
+
+ stmmac_tso_allocator(priv, des, skb_frag_size(frag),
+ (i == nfrags - 1), queue);
+
+ stmmac_set_tx_skb_dma_entry(tx_q, tx_q->cur_tx, des,
+ skb_frag_size(frag), true);
+ }
+
+ stmmac_set_tx_dma_last_segment(tx_q, tx_q->cur_tx);
+
+ /* Only the last descriptor gets to point to the skb. */
+ tx_q->tx_skbuff[tx_q->cur_tx] = skb;
+
+ /* Manage tx mitigation */
+ tx_packets = CIRC_CNT(tx_q->cur_tx + 1, first_tx,
+ priv->dma_conf.dma_tx_size);
+ tx_q->tx_count_frames += tx_packets;
+
+ if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en)
+ set_ic = true;
+ else if (!priv->tx_coal_frames[queue])
+ set_ic = false;
+ else if (tx_packets > priv->tx_coal_frames[queue])
+ set_ic = true;
+ else if ((tx_q->tx_count_frames %
+ priv->tx_coal_frames[queue]) < tx_packets)
+ set_ic = true;
+ else
+ set_ic = false;
+
+ if (set_ic) {
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ desc = &tx_q->dma_entx[tx_q->cur_tx].basic;
+ else
+ desc = &tx_q->dma_tx[tx_q->cur_tx];
+
+ tx_q->tx_count_frames = 0;
+ stmmac_set_tx_ic(priv, desc);
+ }
+
+ /* We've used all descriptors we need for this skb, however,
+ * advance cur_tx so that it references a fresh descriptor.
+ * ndo_start_xmit will fill this descriptor the next time it's
+ * called and stmmac_tx_clean may clean up to this descriptor.
+ */
+ tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size);
+
+ if (unlikely(stmmac_tx_avail(priv, queue) <= (MAX_SKB_FRAGS + 1))) {
+ netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n",
+ __func__);
+ netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, queue));
+ }
+
+ u64_stats_update_begin(&txq_stats->q_syncp);
+ u64_stats_add(&txq_stats->q.tx_bytes, skb->len);
+ u64_stats_inc(&txq_stats->q.tx_tso_frames);
+ u64_stats_add(&txq_stats->q.tx_tso_nfrags, nfrags);
+ if (set_ic)
+ u64_stats_inc(&txq_stats->q.tx_set_ic_bit);
+ u64_stats_update_end(&txq_stats->q_syncp);
+
+ if (priv->sarc_type)
+ stmmac_set_desc_sarc(priv, first, priv->sarc_type);
+
+ if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) &&
+ priv->hwts_tx_en)) {
+ /* declare that device is doing timestamping */
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ stmmac_enable_tx_timestamp(priv, first);
+ }
+
+ /* If we only have one entry used, then the first entry is the last
+ * segment.
+ */
+ is_last_segment = CIRC_CNT(tx_q->cur_tx, first_entry,
+ priv->dma_conf.dma_tx_size) == 1;
+
+ /* Complete the first descriptor before granting the DMA */
+ stmmac_prepare_tso_tx_desc(priv, first, 1, proto_hdr_len, 0, 1,
+ is_last_segment, hdr / 4,
+ skb->len - proto_hdr_len);
+
+ /* If context desc is used to change MSS */
+ if (mss_desc) {
+ /* Make sure that first descriptor has been completely
+ * written, including its own bit. This is because MSS is
+ * actually before first descriptor, so we need to make
+ * sure that MSS's own bit is the last thing written.
+ */
+ dma_wmb();
+ stmmac_set_tx_owner(priv, mss_desc);
+ }
+
+ if (netif_msg_pktdata(priv)) {
+ pr_info("%s: curr=%d dirty=%d f=%d, e=%d, f_p=%p, nfrags %d\n",
+ __func__, tx_q->cur_tx, tx_q->dirty_tx, first_entry,
+ tx_q->cur_tx, first, nfrags);
+ pr_info(">>> frame to be transmitted: ");
+ print_pkt(skb->data, skb_headlen(skb));
+ }
+
+ netdev_tx_sent_queue(netdev_get_tx_queue(dev, queue), skb->len);
+ skb_tx_timestamp(skb);
+
+ stmmac_flush_tx_descriptors(priv, queue);
+ stmmac_tx_timer_arm(priv, queue);
+
+ return NETDEV_TX_OK;
+
+dma_map_err:
+ dev_err(priv->device, "Tx dma map failed\n");
+ dev_kfree_skb(skb);
+ priv->xstats.tx_dropped++;
+ return NETDEV_TX_OK;
+}
+
+/**
+ * stmmac_has_ip_ethertype() - Check if packet has IP ethertype
+ * @skb: socket buffer to check
+ *
+ * Check if a packet has an ethertype that will trigger the IP header checks
+ * and IP/TCP checksum engine of the stmmac core.
+ *
+ * Return: true if the ethertype can trigger the checksum engine, false
+ * otherwise
+ */
+static bool stmmac_has_ip_ethertype(struct sk_buff *skb)
+{
+ int depth = 0;
+ __be16 proto;
+
+ proto = __vlan_get_protocol(skb, eth_header_parse_protocol(skb),
+ &depth);
+
+ return (depth <= ETH_HLEN) &&
+ (proto == htons(ETH_P_IP) || proto == htons(ETH_P_IPV6));
+}
+
+/**
+ * stmmac_xmit - Tx entry point of the driver
+ * @skb : the socket buffer
+ * @dev : device pointer
+ * Description : this is the tx entry point of the driver.
+ * It programs the chain or the ring and supports oversized frames
+ * and SG feature.
+ */
+static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ bool enh_desc, has_vlan, set_ic, is_jumbo = false;
+ struct stmmac_priv *priv = netdev_priv(dev);
+ unsigned int nopaged_len = skb_headlen(skb);
+ u32 queue = skb_get_queue_mapping(skb);
+ int nfrags = skb_shinfo(skb)->nr_frags;
+ unsigned int first_entry, tx_packets;
+ struct stmmac_txq_stats *txq_stats;
+ struct dma_desc *desc, *first_desc;
+ struct stmmac_tx_queue *tx_q;
+ int i, csum_insertion = 0;
+ int entry, first_tx;
+ dma_addr_t dma_addr;
+ u32 sdu_len;
+
+ if (priv->tx_path_in_lpi_mode && priv->eee_sw_timer_en)
+ stmmac_stop_sw_lpi(priv);
+
+ if (skb_is_gso(skb) &&
+ skb_shinfo(skb)->gso_type & priv->gso_enabled_types)
+ return stmmac_tso_xmit(skb, dev);
+
+ if (priv->est && priv->est->enable &&
+ priv->est->max_sdu[queue]) {
+ sdu_len = skb->len;
+ /* Add VLAN tag length if VLAN tag insertion offload is requested */
+ if (priv->dma_cap.vlins && skb_vlan_tag_present(skb))
+ sdu_len += VLAN_HLEN;
+ if (sdu_len > priv->est->max_sdu[queue]) {
+ priv->xstats.max_sdu_txq_drop[queue]++;
+ goto max_sdu_err;
+ }
+ }
+
+ if (unlikely(stmmac_tx_avail(priv, queue) < nfrags + 1)) {
+ if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) {
+ netif_tx_stop_queue(netdev_get_tx_queue(priv->dev,
+ queue));
+ /* This is a hard error, log it. */
+ netdev_err(priv->dev,
+ "%s: Tx Ring full when queue awake\n",
+ __func__);
+ }
+ return NETDEV_TX_BUSY;
+ }
+
+ tx_q = &priv->dma_conf.tx_queue[queue];
+ first_tx = tx_q->cur_tx;
+
+ /* Check if VLAN can be inserted by HW */
+ has_vlan = stmmac_vlan_insert(priv, skb, tx_q);
+
+ entry = tx_q->cur_tx;
+ first_entry = entry;
+ WARN_ON(tx_q->tx_skbuff[first_entry]);
+
+ desc = stmmac_get_tx_desc(priv, tx_q, entry);
+ first_desc = desc;
+
+ if (has_vlan)
+ stmmac_set_desc_vlan(priv, first_desc, STMMAC_VLAN_INSERT);
+
+ enh_desc = priv->plat->enh_desc;
+ /* To program the descriptors according to the size of the frame */
+ if (enh_desc)
+ is_jumbo = stmmac_is_jumbo_frm(priv, skb->len, enh_desc);
+
+ csum_insertion = skb->ip_summed == CHECKSUM_PARTIAL;
+
+ if (unlikely(is_jumbo)) {
+ entry = stmmac_jumbo_frm(priv, tx_q, skb, csum_insertion);
+ if (unlikely(entry < 0) && (entry != -EINVAL))
+ goto dma_map_err;
+ } else {
+ bool last_segment = (nfrags == 0);
+
+ dma_addr = dma_map_single(priv->device, skb->data,
+ nopaged_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->device, dma_addr))
+ goto dma_map_err;
+
+ stmmac_set_tx_skb_dma_entry(tx_q, first_entry, dma_addr,
+ nopaged_len, false);
+
+ stmmac_set_desc_addr(priv, first_desc, dma_addr);
+
+ if (last_segment)
+ stmmac_set_tx_dma_last_segment(tx_q, first_entry);
+
+ if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) &&
+ priv->hwts_tx_en)) {
+ /* declare that device is doing timestamping */
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ stmmac_enable_tx_timestamp(priv, first_desc);
+ }
+
+ /* Prepare the first descriptor without setting the OWN bit */
+ stmmac_prepare_tx_desc(priv, first_desc, 1, nopaged_len,
+ csum_insertion, priv->descriptor_mode,
+ 0, last_segment, skb->len);
+ }
+
+ if (priv->sarc_type)
+ stmmac_set_desc_sarc(priv, first_desc, priv->sarc_type);
+
+ /* STMMAC_TBS_EN can only be set if STMMAC_TBS_AVAIL has already
+ * been set, which means the underlying type of the descriptors
+ * will be struct stmmac_edesc. Therefore, it is safe to convert
+ * the basic descriptor to the enhanced descriptor here.
+ */
+ if (tx_q->tbs & STMMAC_TBS_EN) {
+ struct timespec64 ts = ns_to_timespec64(skb->tstamp);
+
+ stmmac_set_desc_tbs(priv, dma_desc_to_edesc(first_desc),
+ ts.tv_sec, ts.tv_nsec);
+ }
+
+ for (i = 0; i < nfrags; i++) {
+ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ unsigned int frag_size = skb_frag_size(frag);
+ bool last_segment = (i == (nfrags - 1));
+
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ WARN_ON(tx_q->tx_skbuff[entry]);
+
+ desc = stmmac_get_tx_desc(priv, tx_q, entry);
+
+ dma_addr = skb_frag_dma_map(priv->device, frag, 0, frag_size,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->device, dma_addr))
+ goto dma_map_err; /* should reuse desc w/o issues */
+
+ stmmac_set_tx_skb_dma_entry(tx_q, entry, dma_addr, frag_size,
+ true);
+ stmmac_set_desc_addr(priv, desc, dma_addr);
+
+ /* Prepare the descriptor and set the own bit too */
+ stmmac_prepare_tx_desc(priv, desc, 0, frag_size, csum_insertion,
+ priv->descriptor_mode, 1, last_segment,
+ skb->len);
+ }
+
+ stmmac_set_tx_dma_last_segment(tx_q, entry);
+
+ /* Only the last descriptor gets to point to the skb. */
+ tx_q->tx_skbuff[entry] = skb;
+
+ /* According to the coalesce parameter the IC bit for the latest
+ * segment is reset and the timer re-started to clean the tx status.
+ * This approach takes care about the fragments: desc is the first
+ * element in case of no SG.
+ */
+ tx_packets = CIRC_CNT(entry + 1, first_tx, priv->dma_conf.dma_tx_size);
+ tx_q->tx_count_frames += tx_packets;
+
+ if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en)
+ set_ic = true;
+ else if (!priv->tx_coal_frames[queue])
+ set_ic = false;
+ else if (tx_packets > priv->tx_coal_frames[queue])
+ set_ic = true;
+ else if ((tx_q->tx_count_frames %
+ priv->tx_coal_frames[queue]) < tx_packets)
+ set_ic = true;
+ else
+ set_ic = false;
+
+ if (set_ic) {
+ desc = stmmac_get_tx_desc(priv, tx_q, entry);
+ tx_q->tx_count_frames = 0;
+ stmmac_set_tx_ic(priv, desc);
+ }
+
+ /* We've used all descriptors we need for this skb, however,
+ * advance cur_tx so that it references a fresh descriptor.
+ * ndo_start_xmit will fill this descriptor the next time it's
+ * called and stmmac_tx_clean may clean up to this descriptor.
+ */
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ tx_q->cur_tx = entry;
+
+ if (netif_msg_pktdata(priv)) {
+ netdev_dbg(priv->dev,
+ "%s: curr=%d dirty=%d f=%d, e=%d, first=%p, nfrags=%d",
+ __func__, tx_q->cur_tx, tx_q->dirty_tx, first_entry,
+ entry, first_desc, nfrags);
+
+ netdev_dbg(priv->dev, ">>> frame to be transmitted: ");
+ print_pkt(skb->data, skb->len);
+ }
+
+ if (unlikely(stmmac_tx_avail(priv, queue) <= (MAX_SKB_FRAGS + 1))) {
+ netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n",
+ __func__);
+ netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, queue));
+ }
+
+ txq_stats = &priv->xstats.txq_stats[queue];
+ u64_stats_update_begin(&txq_stats->q_syncp);
+ u64_stats_add(&txq_stats->q.tx_bytes, skb->len);
+ if (set_ic)
+ u64_stats_inc(&txq_stats->q.tx_set_ic_bit);
+ u64_stats_update_end(&txq_stats->q_syncp);
+
+ /* Set the OWN bit on the first descriptor now that all descriptors
+ * for this skb are populated.
+ */
+ stmmac_set_tx_owner(priv, first_desc);
+
+ netdev_tx_sent_queue(netdev_get_tx_queue(dev, queue), skb->len);
+
+ stmmac_enable_dma_transmission(priv, priv->ioaddr, queue);
+ skb_tx_timestamp(skb);
+ stmmac_flush_tx_descriptors(priv, queue);
+ stmmac_tx_timer_arm(priv, queue);
+
+ return NETDEV_TX_OK;
+
+dma_map_err:
+ netdev_err(priv->dev, "Tx DMA map failed\n");
+max_sdu_err:
+ dev_kfree_skb(skb);
+ priv->xstats.tx_dropped++;
+ return NETDEV_TX_OK;
+}
+
+static netdev_features_t stmmac_features_check(struct sk_buff *skb,
+ struct net_device *dev,
+ netdev_features_t features)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u16 queue = skb_get_queue_mapping(skb);
+
+ /* DWMAC IPs can be synthesized to support tx coe only for a few tx
+ * queues. In that case, checksum offloading for those queues that don't
+ * support tx coe needs to fallback to software checksum calculation.
+ *
+ * Packets that won't trigger the COE e.g. most DSA-tagged packets will
+ * also have to be checksummed in software.
+ *
+ * Note that disabling hardware checksumming also disables TSO. See
+ * harmonize_features() in net/core/dev.c
+ */
+ if (priv->plat->tx_queues_cfg[queue].coe_unsupported ||
+ !stmmac_has_ip_ethertype(skb))
+ features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM);
+
+ if (skb_is_gso(skb)) {
+ if (!stmmac_tso_channel_permitted(priv, queue) ||
+ !stmmac_tso_valid_packet(skb))
+ features &= ~NETIF_F_GSO_MASK;
+
+ /* If we are going to be using hardware TSO, always insert
+ * VLAN tag to SKB payload for TSO frames.
+ *
+ * Never insert VLAN tag by HW, since segments split by
+ * TSO engine will be un-tagged by mistake.
+ */
+ if (features & NETIF_F_GSO_MASK)
+ features &= ~(NETIF_F_HW_VLAN_STAG_TX |
+ NETIF_F_HW_VLAN_CTAG_TX);
+ }
+
+ return vlan_features_check(skb, features);
+}
+
+static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb)
+{
+ struct vlan_ethhdr *veth = skb_vlan_eth_hdr(skb);
+ __be16 vlan_proto = veth->h_vlan_proto;
+ u16 vlanid;
+
+ if ((vlan_proto == htons(ETH_P_8021Q) &&
+ dev->features & NETIF_F_HW_VLAN_CTAG_RX) ||
+ (vlan_proto == htons(ETH_P_8021AD) &&
+ dev->features & NETIF_F_HW_VLAN_STAG_RX)) {
+ /* pop the vlan tag */
+ vlanid = ntohs(veth->h_vlan_TCI);
+ memmove(skb->data + VLAN_HLEN, veth, ETH_ALEN * 2);
+ skb_pull(skb, VLAN_HLEN);
+ __vlan_hwaccel_put_tag(skb, vlan_proto, vlanid);
+ }
+}
+
+/**
+ * stmmac_rx_refill - refill used skb preallocated buffers
+ * @priv: driver private structure
+ * @queue: RX queue index
+ * Description : this is to reallocate the skb for the reception process
+ * that is based on zero-copy.
+ */
+static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ int dirty = stmmac_rx_dirty(priv, queue);
+ unsigned int entry = rx_q->dirty_rx;
+ gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN);
+
+ if (priv->dma_cap.host_dma_width <= 32)
+ gfp |= GFP_DMA32;
+
+ while (dirty-- > 0) {
+ struct stmmac_rx_buffer *buf = &rx_q->buf_pool[entry];
+ struct dma_desc *p;
+ bool use_rx_wd;
+
+ p = stmmac_get_rx_desc(priv, rx_q, entry);
+
+ if (!buf->page) {
+ buf->page = page_pool_alloc_pages(rx_q->page_pool, gfp);
+ if (!buf->page)
+ break;
+ }
+
+ if (priv->sph_active && !buf->sec_page) {
+ buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp);
+ if (!buf->sec_page)
+ break;
+
+ buf->sec_addr = page_pool_get_dma_addr(buf->sec_page);
+ }
+
+ buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset;
+
+ stmmac_set_desc_addr(priv, p, buf->addr);
+ if (priv->sph_active)
+ stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true);
+ else
+ stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false);
+ stmmac_refill_desc3(priv, rx_q, p);
+
+ rx_q->rx_count_frames++;
+ rx_q->rx_count_frames += priv->rx_coal_frames[queue];
+ if (rx_q->rx_count_frames > priv->rx_coal_frames[queue])
+ rx_q->rx_count_frames = 0;
+
+ use_rx_wd = !priv->rx_coal_frames[queue];
+ use_rx_wd |= rx_q->rx_count_frames > 0;
+ if (!priv->use_riwt)
+ use_rx_wd = false;
+
+ dma_wmb();
+ stmmac_set_rx_owner(priv, p, use_rx_wd);
+
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_rx_size);
+ }
+ rx_q->dirty_rx = entry;
+ stmmac_set_queue_rx_tail_ptr(priv, rx_q, queue, rx_q->dirty_rx);
+ /* Wake up Rx DMA from the suspend state if required */
+ stmmac_enable_dma_reception(priv, priv->ioaddr, queue);
+}
+
+static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv,
+ struct dma_desc *p,
+ int status, unsigned int len)
+{
+ unsigned int plen = 0, hlen = 0;
+ int coe = priv->hw->rx_csum;
+
+ /* Not first descriptor, buffer is always zero */
+ if (priv->sph_active && len)
+ return 0;
+
+ /* First descriptor, get split header length */
+ stmmac_get_rx_header_len(priv, p, &hlen);
+ if (priv->sph_active && hlen) {
+ priv->xstats.rx_split_hdr_pkt_n++;
+ return hlen;
+ }
+
+ /* First descriptor, not last descriptor and not split header */
+ if (status & rx_not_ls)
+ return priv->dma_conf.dma_buf_sz;
+
+ plen = stmmac_get_rx_frame_len(priv, p, coe);
+
+ /* First descriptor and last descriptor and not split header */
+ return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen);
+}
+
+static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv,
+ struct dma_desc *p,
+ int status, unsigned int len)
+{
+ int coe = priv->hw->rx_csum;
+ unsigned int plen = 0;
+
+ /* Not split header, buffer is not available */
+ if (!priv->sph_active)
+ return 0;
+
+ /* For GMAC4, when split header is enabled, in some rare cases, the
+ * hardware does not fill buf2 of the first descriptor with payload.
+ * Thus we cannot assume buf2 is always fully filled if it is not
+ * the last descriptor. Otherwise, the length of buf2 of the second
+ * descriptor will be calculated wrong and cause an oops.
+ *
+ * If this is the last descriptor, 'plen' is the length of the
+ * received packet that was transferred to system memory.
+ * Otherwise, it is the accumulated number of bytes that have been
+ * transferred for the current packet.
+ *
+ * Thus 'plen - len' always gives the correct length of buf2.
+ */
+
+ /* Not GMAC4 and not last descriptor */
+ if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls))
+ return priv->dma_conf.dma_buf_sz;
+
+ /* GMAC4 or last descriptor */
+ plen = stmmac_get_rx_frame_len(priv, p, coe);
+
+ return plen - len;
+}
+
+static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
+ struct xdp_frame *xdpf, bool dma_map)
+{
+ struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[queue];
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ bool csum = !priv->plat->tx_queues_cfg[queue].coe_unsupported;
+ unsigned int entry = tx_q->cur_tx;
+ enum stmmac_txbuf_type buf_type;
+ struct dma_desc *tx_desc;
+ dma_addr_t dma_addr;
+ bool set_ic;
+
+ if (stmmac_tx_avail(priv, queue) < STMMAC_TX_THRESH(priv))
+ return STMMAC_XDP_CONSUMED;
+
+ if (priv->est && priv->est->enable &&
+ priv->est->max_sdu[queue] &&
+ xdpf->len > priv->est->max_sdu[queue]) {
+ priv->xstats.max_sdu_txq_drop[queue]++;
+ return STMMAC_XDP_CONSUMED;
+ }
+
+ tx_desc = stmmac_get_tx_desc(priv, tx_q, entry);
+ if (dma_map) {
+ dma_addr = dma_map_single(priv->device, xdpf->data,
+ xdpf->len, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->device, dma_addr))
+ return STMMAC_XDP_CONSUMED;
+
+ buf_type = STMMAC_TXBUF_T_XDP_NDO;
+ } else {
+ struct page *page = virt_to_page(xdpf->data);
+
+ dma_addr = page_pool_get_dma_addr(page) + sizeof(*xdpf) +
+ xdpf->headroom;
+ dma_sync_single_for_device(priv->device, dma_addr,
+ xdpf->len, DMA_BIDIRECTIONAL);
+
+ buf_type = STMMAC_TXBUF_T_XDP_TX;
+ }
+
+ stmmac_set_tx_dma_entry(tx_q, entry, buf_type, dma_addr, xdpf->len,
+ false);
+ stmmac_set_tx_dma_last_segment(tx_q, entry);
+
+ tx_q->xdpf[entry] = xdpf;
+
+ stmmac_set_desc_addr(priv, tx_desc, dma_addr);
+
+ stmmac_prepare_tx_desc(priv, tx_desc, 1, xdpf->len,
+ csum, priv->descriptor_mode, true, true,
+ xdpf->len);
+
+ tx_q->tx_count_frames++;
+
+ if (tx_q->tx_count_frames % priv->tx_coal_frames[queue] == 0)
+ set_ic = true;
+ else
+ set_ic = false;
+
+ if (set_ic) {
+ tx_q->tx_count_frames = 0;
+ stmmac_set_tx_ic(priv, tx_desc);
+ u64_stats_update_begin(&txq_stats->q_syncp);
+ u64_stats_inc(&txq_stats->q.tx_set_ic_bit);
+ u64_stats_update_end(&txq_stats->q_syncp);
+ }
+
+ stmmac_enable_dma_transmission(priv, priv->ioaddr, queue);
+
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_tx_size);
+ tx_q->cur_tx = entry;
+
+ return STMMAC_XDP_TX;
+}
+
+static int stmmac_xdp_get_tx_queue(struct stmmac_priv *priv,
+ int cpu)
+{
+ int index = cpu;
+
+ if (unlikely(index < 0))
+ index = 0;
+
+ while (index >= priv->plat->tx_queues_to_use)
+ index -= priv->plat->tx_queues_to_use;
+
+ return index;
+}
+
+static int stmmac_xdp_xmit_back(struct stmmac_priv *priv,
+ struct xdp_buff *xdp)
+{
+ bool zc = !!(xdp->rxq->mem.type == MEM_TYPE_XSK_BUFF_POOL);
+ struct xdp_frame *xdpf = xdp_convert_buff_to_frame(xdp);
+ int cpu = smp_processor_id();
+ struct netdev_queue *nq;
+ int queue;
+ int res;
+
+ if (unlikely(!xdpf))
+ return STMMAC_XDP_CONSUMED;
+
+ queue = stmmac_xdp_get_tx_queue(priv, cpu);
+ nq = netdev_get_tx_queue(priv->dev, queue);
+
+ __netif_tx_lock(nq, cpu);
+ /* Avoids TX time-out as we are sharing with slow path */
+ txq_trans_cond_update(nq);
+
+ /* For zero copy XDP_TX action, dma_map is true */
+ res = stmmac_xdp_xmit_xdpf(priv, queue, xdpf, zc);
+ if (res == STMMAC_XDP_TX) {
+ stmmac_flush_tx_descriptors(priv, queue);
+ } else if (res == STMMAC_XDP_CONSUMED && zc) {
+ /* xdp has been freed by xdp_convert_buff_to_frame(),
+ * no need to call xsk_buff_free() again, so return
+ * STMMAC_XSK_CONSUMED.
+ */
+ res = STMMAC_XSK_CONSUMED;
+ xdp_return_frame(xdpf);
+ }
+
+ __netif_tx_unlock(nq);
+
+ return res;
+}
+
+static int __stmmac_xdp_run_prog(struct stmmac_priv *priv,
+ struct bpf_prog *prog,
+ struct xdp_buff *xdp)
+{
+ u32 act;
+ int res;
+
+ act = bpf_prog_run_xdp(prog, xdp);
+ switch (act) {
+ case XDP_PASS:
+ res = STMMAC_XDP_PASS;
+ break;
+ case XDP_TX:
+ res = stmmac_xdp_xmit_back(priv, xdp);
+ break;
+ case XDP_REDIRECT:
+ if (xdp_do_redirect(priv->dev, xdp, prog) < 0)
+ res = STMMAC_XDP_CONSUMED;
+ else
+ res = STMMAC_XDP_REDIRECT;
+ break;
+ default:
+ bpf_warn_invalid_xdp_action(priv->dev, prog, act);
+ fallthrough;
+ case XDP_ABORTED:
+ trace_xdp_exception(priv->dev, prog, act);
+ fallthrough;
+ case XDP_DROP:
+ res = STMMAC_XDP_CONSUMED;
+ break;
+ }
+
+ return res;
+}
+
+static struct sk_buff *stmmac_xdp_run_prog(struct stmmac_priv *priv,
+ struct xdp_buff *xdp)
+{
+ struct bpf_prog *prog;
+ int res;
+
+ prog = READ_ONCE(priv->xdp_prog);
+ if (!prog) {
+ res = STMMAC_XDP_PASS;
+ goto out;
+ }
+
+ res = __stmmac_xdp_run_prog(priv, prog, xdp);
+out:
+ return ERR_PTR(-res);
+}
+
+static void stmmac_finalize_xdp_rx(struct stmmac_priv *priv,
+ int xdp_status)
+{
+ int cpu = smp_processor_id();
+ int queue;
+
+ queue = stmmac_xdp_get_tx_queue(priv, cpu);
+
+ if (xdp_status & STMMAC_XDP_TX)
+ stmmac_tx_timer_arm(priv, queue);
+
+ if (xdp_status & STMMAC_XDP_REDIRECT)
+ xdp_do_flush();
+}
+
+static struct sk_buff *stmmac_construct_skb_zc(struct stmmac_channel *ch,
+ struct xdp_buff *xdp)
+{
+ unsigned int metasize = xdp->data - xdp->data_meta;
+ unsigned int datasize = xdp->data_end - xdp->data;
+ struct sk_buff *skb;
+
+ skb = napi_alloc_skb(&ch->rxtx_napi,
+ xdp->data_end - xdp->data_hard_start);
+ if (unlikely(!skb))
+ return NULL;
+
+ skb_reserve(skb, xdp->data - xdp->data_hard_start);
+ memcpy(__skb_put(skb, datasize), xdp->data, datasize);
+ if (metasize)
+ skb_metadata_set(skb, metasize);
+
+ return skb;
+}
+
+static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue,
+ struct dma_desc *p, struct dma_desc *np,
+ struct xdp_buff *xdp)
+{
+ struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue];
+ struct stmmac_channel *ch = &priv->channel[queue];
+ unsigned int len = xdp->data_end - xdp->data;
+ enum pkt_hash_types hash_type;
+ int coe = priv->hw->rx_csum;
+ struct sk_buff *skb;
+ u32 hash;
+
+ skb = stmmac_construct_skb_zc(ch, xdp);
+ if (!skb) {
+ priv->xstats.rx_dropped++;
+ return;
+ }
+
+ stmmac_get_rx_hwtstamp(priv, p, np, skb);
+ if (priv->hw->hw_vlan_en)
+ /* MAC level stripping. */
+ stmmac_rx_hw_vlan(priv, priv->hw, p, skb);
+ else
+ /* Driver level stripping. */
+ stmmac_rx_vlan(priv->dev, skb);
+ skb->protocol = eth_type_trans(skb, priv->dev);
+
+ if (unlikely(!coe) || !stmmac_has_ip_ethertype(skb))
+ skb_checksum_none_assert(skb);
+ else
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ if (!stmmac_get_rx_hash(priv, p, &hash, &hash_type))
+ skb_set_hash(skb, hash, hash_type);
+
+ skb_record_rx_queue(skb, queue);
+ napi_gro_receive(&ch->rxtx_napi, skb);
+
+ u64_stats_update_begin(&rxq_stats->napi_syncp);
+ u64_stats_inc(&rxq_stats->napi.rx_pkt_n);
+ u64_stats_add(&rxq_stats->napi.rx_bytes, len);
+ u64_stats_update_end(&rxq_stats->napi_syncp);
+}
+
+static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
+{
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ unsigned int entry = rx_q->dirty_rx;
+ struct dma_desc *rx_desc = NULL;
+ bool ret = true;
+
+ budget = min(budget, stmmac_rx_dirty(priv, queue));
+
+ while (budget-- > 0 && entry != rx_q->cur_rx) {
+ struct stmmac_rx_buffer *buf = &rx_q->buf_pool[entry];
+ dma_addr_t dma_addr;
+ bool use_rx_wd;
+
+ if (!buf->xdp) {
+ buf->xdp = xsk_buff_alloc(rx_q->xsk_pool);
+ if (!buf->xdp) {
+ ret = false;
+ break;
+ }
+ }
+
+ rx_desc = stmmac_get_rx_desc(priv, rx_q, entry);
+
+ dma_addr = xsk_buff_xdp_get_dma(buf->xdp);
+ stmmac_set_desc_addr(priv, rx_desc, dma_addr);
+ stmmac_set_desc_sec_addr(priv, rx_desc, 0, false);
+ stmmac_refill_desc3(priv, rx_q, rx_desc);
+
+ rx_q->rx_count_frames++;
+ rx_q->rx_count_frames += priv->rx_coal_frames[queue];
+ if (rx_q->rx_count_frames > priv->rx_coal_frames[queue])
+ rx_q->rx_count_frames = 0;
+
+ use_rx_wd = !priv->rx_coal_frames[queue];
+ use_rx_wd |= rx_q->rx_count_frames > 0;
+ if (!priv->use_riwt)
+ use_rx_wd = false;
+
+ dma_wmb();
+ stmmac_set_rx_owner(priv, rx_desc, use_rx_wd);
+
+ entry = STMMAC_NEXT_ENTRY(entry, priv->dma_conf.dma_rx_size);
+ }
+
+ if (rx_desc) {
+ rx_q->dirty_rx = entry;
+ stmmac_set_queue_rx_tail_ptr(priv, rx_q, queue, rx_q->dirty_rx);
+ }
+
+ return ret;
+}
+
+static struct stmmac_xdp_buff *xsk_buff_to_stmmac_ctx(struct xdp_buff *xdp)
+{
+ /* In XDP zero copy data path, xdp field in struct xdp_buff_xsk is used
+ * to represent incoming packet, whereas cb field in the same structure
+ * is used to store driver specific info. Thus, struct stmmac_xdp_buff
+ * is laid on top of xdp and cb fields of struct xdp_buff_xsk.
+ */
+ return (struct stmmac_xdp_buff *)xdp;
+}
+
+static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
+{
+ struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ unsigned int count = 0, error = 0, len = 0;
+ int dirty = stmmac_rx_dirty(priv, queue);
+ unsigned int next_entry = rx_q->cur_rx;
+ u32 rx_errors = 0, rx_dropped = 0;
+ unsigned int desc_size;
+ struct bpf_prog *prog;
+ bool failure = false;
+ int xdp_status = 0;
+ int status = 0;
+
+ if (netif_msg_rx_status(priv)) {
+ void *rx_head = stmmac_get_rx_desc(priv, rx_q, 0);
+
+ netdev_dbg(priv->dev, "%s: descriptor ring:\n", __func__);
+ desc_size = stmmac_get_rx_desc_size(priv);
+
+ stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
+ rx_q->dma_rx_phy, desc_size);
+ }
+ while (count < limit) {
+ struct stmmac_rx_buffer *buf;
+ struct stmmac_xdp_buff *ctx;
+ unsigned int buf1_len = 0;
+ struct dma_desc *np, *p;
+ int entry;
+ int res;
+
+ if (!count && rx_q->state_saved) {
+ error = rx_q->state.error;
+ len = rx_q->state.len;
+ } else {
+ rx_q->state_saved = false;
+ error = 0;
+ len = 0;
+ }
+
+read_again:
+ if (count >= limit)
+ break;
+
+ buf1_len = 0;
+ entry = next_entry;
+ buf = &rx_q->buf_pool[entry];
+
+ if (dirty >= STMMAC_RX_FILL_BATCH) {
+ failure = failure ||
+ !stmmac_rx_refill_zc(priv, queue, dirty);
+ dirty = 0;
+ }
+
+ p = stmmac_get_rx_desc(priv, rx_q, entry);
+
+ /* read the status of the incoming frame */
+ status = stmmac_rx_status(priv, &priv->xstats, p);
+ /* check if managed by the DMA otherwise go ahead */
+ if (unlikely(status & dma_own))
+ break;
+
+ /* Prefetch the next RX descriptor */
+ next_entry = STMMAC_NEXT_ENTRY(rx_q->cur_rx,
+ priv->dma_conf.dma_rx_size);
+ if (unlikely(next_entry == rx_q->dirty_rx))
+ break;
+
+ rx_q->cur_rx = next_entry;
+
+ np = stmmac_get_rx_desc(priv, rx_q, next_entry);
+
+ prefetch(np);
+
+ /* Ensure a valid XSK buffer before proceed */
+ if (!buf->xdp)
+ break;
+
+ if (priv->extend_desc)
+ stmmac_rx_extended_status(priv, &priv->xstats,
+ rx_q->dma_erx + entry);
+ if (unlikely(status == discard_frame)) {
+ xsk_buff_free(buf->xdp);
+ buf->xdp = NULL;
+ dirty++;
+ error = 1;
+ if (!priv->hwts_rx_en)
+ rx_errors++;
+ }
+
+ if (unlikely(error && (status & rx_not_ls)))
+ goto read_again;
+ if (unlikely(error)) {
+ count++;
+ continue;
+ }
+
+ /* XSK pool expects RX frame 1:1 mapped to XSK buffer */
+ if (likely(status & rx_not_ls)) {
+ xsk_buff_free(buf->xdp);
+ buf->xdp = NULL;
+ dirty++;
+ count++;
+ goto read_again;
+ }
+
+ ctx = xsk_buff_to_stmmac_ctx(buf->xdp);
+ ctx->priv = priv;
+ ctx->desc = p;
+ ctx->ndesc = np;
+
+ /* XDP ZC Frame only support primary buffers for now */
+ buf1_len = stmmac_rx_buf1_len(priv, p, status, len);
+ len += buf1_len;
+
+ /* ACS is disabled; strip manually. */
+ if (likely(!(status & rx_not_ls))) {
+ buf1_len -= ETH_FCS_LEN;
+ len -= ETH_FCS_LEN;
+ }
+
+ /* RX buffer is good and fit into a XSK pool buffer */
+ buf->xdp->data_end = buf->xdp->data + buf1_len;
+ xsk_buff_dma_sync_for_cpu(buf->xdp);
+
+ prog = READ_ONCE(priv->xdp_prog);
+ res = __stmmac_xdp_run_prog(priv, prog, buf->xdp);
+
+ switch (res) {
+ case STMMAC_XDP_PASS:
+ stmmac_dispatch_skb_zc(priv, queue, p, np, buf->xdp);
+ xsk_buff_free(buf->xdp);
+ break;
+ case STMMAC_XDP_CONSUMED:
+ xsk_buff_free(buf->xdp);
+ fallthrough;
+ case STMMAC_XSK_CONSUMED:
+ rx_dropped++;
+ break;
+ case STMMAC_XDP_TX:
+ case STMMAC_XDP_REDIRECT:
+ xdp_status |= res;
+ break;
+ }
+
+ buf->xdp = NULL;
+ dirty++;
+ count++;
+ }
+
+ if (status & rx_not_ls) {
+ rx_q->state_saved = true;
+ rx_q->state.error = error;
+ rx_q->state.len = len;
+ }
+
+ stmmac_finalize_xdp_rx(priv, xdp_status);
+
+ u64_stats_update_begin(&rxq_stats->napi_syncp);
+ u64_stats_add(&rxq_stats->napi.rx_pkt_n, count);
+ u64_stats_update_end(&rxq_stats->napi_syncp);
+
+ priv->xstats.rx_dropped += rx_dropped;
+ priv->xstats.rx_errors += rx_errors;
+
+ if (xsk_uses_need_wakeup(rx_q->xsk_pool)) {
+ if (failure || stmmac_rx_dirty(priv, queue) > 0)
+ xsk_set_rx_need_wakeup(rx_q->xsk_pool);
+ else
+ xsk_clear_rx_need_wakeup(rx_q->xsk_pool);
+
+ return (int)count;
+ }
+
+ return failure ? limit : (int)count;
+}
+
+/**
+ * stmmac_rx - manage the receive process
+ * @priv: driver private structure
+ * @limit: napi bugget
+ * @queue: RX queue index.
+ * Description : this the function called by the napi poll method.
+ * It gets all the frames inside the ring.
+ */
+static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
+{
+ u32 rx_errors = 0, rx_dropped = 0, rx_bytes = 0, rx_packets = 0;
+ struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue];
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_channel *ch = &priv->channel[queue];
+ unsigned int count = 0, error = 0, len = 0;
+ int status = 0, coe = priv->hw->rx_csum;
+ unsigned int next_entry = rx_q->cur_rx;
+ enum dma_data_direction dma_dir;
+ unsigned int desc_size;
+ struct sk_buff *skb = NULL;
+ struct stmmac_xdp_buff ctx;
+ int xdp_status = 0;
+ int bufsz;
+
+ dma_dir = page_pool_get_dma_dir(rx_q->page_pool);
+ bufsz = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE) * PAGE_SIZE;
+
+ if (netif_msg_rx_status(priv)) {
+ void *rx_head = stmmac_get_rx_desc(priv, rx_q, 0);
+
+ netdev_dbg(priv->dev, "%s: descriptor ring:\n", __func__);
+ desc_size = stmmac_get_rx_desc_size(priv);
+
+ stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
+ rx_q->dma_rx_phy, desc_size);
+ }
+ while (count < limit) {
+ unsigned int buf1_len = 0, buf2_len = 0;
+ enum pkt_hash_types hash_type;
+ struct stmmac_rx_buffer *buf;
+ struct dma_desc *np, *p;
+ int entry;
+ u32 hash;
+
+ if (!count && rx_q->state_saved) {
+ skb = rx_q->state.skb;
+ error = rx_q->state.error;
+ len = rx_q->state.len;
+ } else {
+ rx_q->state_saved = false;
+ skb = NULL;
+ error = 0;
+ len = 0;
+ }
+
+read_again:
+ if (count >= limit)
+ break;
+
+ buf1_len = 0;
+ buf2_len = 0;
+ entry = next_entry;
+ buf = &rx_q->buf_pool[entry];
+
+ p = stmmac_get_rx_desc(priv, rx_q, entry);
+
+ /* read the status of the incoming frame */
+ status = stmmac_rx_status(priv, &priv->xstats, p);
+ /* check if managed by the DMA otherwise go ahead */
+ if (unlikely(status & dma_own))
+ break;
+
+ next_entry = STMMAC_NEXT_ENTRY(rx_q->cur_rx,
+ priv->dma_conf.dma_rx_size);
+ if (unlikely(next_entry == rx_q->dirty_rx))
+ break;
+
+ rx_q->cur_rx = next_entry;
+
+ np = stmmac_get_rx_desc(priv, rx_q, next_entry);
+
+ prefetch(np);
+
+ if (priv->extend_desc)
+ stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry);
+ if (unlikely(status == discard_frame)) {
+ page_pool_put_page(rx_q->page_pool, buf->page, 0, true);
+ buf->page = NULL;
+ error = 1;
+ if (!priv->hwts_rx_en)
+ rx_errors++;
+ }
+
+ if (unlikely(error && (status & rx_not_ls)))
+ goto read_again;
+ if (unlikely(error)) {
+ dev_kfree_skb(skb);
+ skb = NULL;
+ count++;
+ continue;
+ }
+
+ /* Buffer is good. Go on. */
+
+ buf1_len = stmmac_rx_buf1_len(priv, p, status, len);
+ len += buf1_len;
+ buf2_len = stmmac_rx_buf2_len(priv, p, status, len);
+ len += buf2_len;
+
+ /* ACS is disabled; strip manually. */
+ if (likely(!(status & rx_not_ls))) {
+ if (buf2_len) {
+ buf2_len -= ETH_FCS_LEN;
+ len -= ETH_FCS_LEN;
+ } else if (buf1_len) {
+ buf1_len -= ETH_FCS_LEN;
+ len -= ETH_FCS_LEN;
+ }
+ }
+
+ if (!skb) {
+ unsigned int pre_len, sync_len;
+
+ dma_sync_single_for_cpu(priv->device, buf->addr,
+ buf1_len, dma_dir);
+ net_prefetch(page_address(buf->page) +
+ buf->page_offset);
+
+ xdp_init_buff(&ctx.xdp, bufsz, &rx_q->xdp_rxq);
+ xdp_prepare_buff(&ctx.xdp, page_address(buf->page),
+ buf->page_offset, buf1_len, true);
+
+ pre_len = ctx.xdp.data_end - ctx.xdp.data_hard_start -
+ buf->page_offset;
+
+ ctx.priv = priv;
+ ctx.desc = p;
+ ctx.ndesc = np;
+
+ skb = stmmac_xdp_run_prog(priv, &ctx.xdp);
+ /* Due xdp_adjust_tail: DMA sync for_device
+ * cover max len CPU touch
+ */
+ sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start -
+ buf->page_offset;
+ sync_len = max(sync_len, pre_len);
+
+ /* For Not XDP_PASS verdict */
+ if (IS_ERR(skb)) {
+ unsigned int xdp_res = -PTR_ERR(skb);
+
+ if (xdp_res & STMMAC_XDP_CONSUMED) {
+ page_pool_put_page(rx_q->page_pool,
+ virt_to_head_page(ctx.xdp.data),
+ sync_len, true);
+ buf->page = NULL;
+ rx_dropped++;
+
+ /* Clear skb as it was set as
+ * status by XDP program.
+ */
+ skb = NULL;
+
+ if (unlikely((status & rx_not_ls)))
+ goto read_again;
+
+ count++;
+ continue;
+ } else if (xdp_res & (STMMAC_XDP_TX |
+ STMMAC_XDP_REDIRECT)) {
+ xdp_status |= xdp_res;
+ buf->page = NULL;
+ skb = NULL;
+ count++;
+ continue;
+ }
+ }
+ }
+
+ if (!skb) {
+ unsigned int head_pad_len;
+
+ /* XDP program may expand or reduce tail */
+ buf1_len = ctx.xdp.data_end - ctx.xdp.data;
+
+ skb = napi_build_skb(page_address(buf->page),
+ rx_q->napi_skb_frag_size);
+ if (!skb) {
+ page_pool_recycle_direct(rx_q->page_pool,
+ buf->page);
+ rx_dropped++;
+ count++;
+ goto drain_data;
+ }
+
+ /* XDP program may adjust header */
+ head_pad_len = ctx.xdp.data - ctx.xdp.data_hard_start;
+ skb_reserve(skb, head_pad_len);
+ skb_put(skb, buf1_len);
+ skb_mark_for_recycle(skb);
+ buf->page = NULL;
+ } else if (buf1_len) {
+ dma_sync_single_for_cpu(priv->device, buf->addr,
+ buf1_len, dma_dir);
+ skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+ buf->page, buf->page_offset, buf1_len,
+ priv->dma_conf.dma_buf_sz);
+ buf->page = NULL;
+ }
+
+ if (buf2_len) {
+ dma_sync_single_for_cpu(priv->device, buf->sec_addr,
+ buf2_len, dma_dir);
+ skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+ buf->sec_page, 0, buf2_len,
+ priv->dma_conf.dma_buf_sz);
+ buf->sec_page = NULL;
+ }
+
+drain_data:
+ if (likely(status & rx_not_ls))
+ goto read_again;
+ if (!skb)
+ continue;
+
+ /* Got entire packet into SKB. Finish it. */
+
+ stmmac_get_rx_hwtstamp(priv, p, np, skb);
+
+ if (priv->hw->hw_vlan_en)
+ /* MAC level stripping. */
+ stmmac_rx_hw_vlan(priv, priv->hw, p, skb);
+ else
+ /* Driver level stripping. */
+ stmmac_rx_vlan(priv->dev, skb);
+
+ skb->protocol = eth_type_trans(skb, priv->dev);
+
+ if (unlikely(!coe) || !stmmac_has_ip_ethertype(skb) ||
+ (status & csum_none))
+ skb_checksum_none_assert(skb);
+ else
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ if (!stmmac_get_rx_hash(priv, p, &hash, &hash_type))
+ skb_set_hash(skb, hash, hash_type);
+
+ skb_record_rx_queue(skb, queue);
+ napi_gro_receive(&ch->rx_napi, skb);
+ skb = NULL;
+
+ rx_packets++;
+ rx_bytes += len;
+ count++;
+ }
+
+ if (status & rx_not_ls || skb) {
+ rx_q->state_saved = true;
+ rx_q->state.skb = skb;
+ rx_q->state.error = error;
+ rx_q->state.len = len;
+ }
+
+ stmmac_finalize_xdp_rx(priv, xdp_status);
+
+ stmmac_rx_refill(priv, queue);
+
+ u64_stats_update_begin(&rxq_stats->napi_syncp);
+ u64_stats_add(&rxq_stats->napi.rx_packets, rx_packets);
+ u64_stats_add(&rxq_stats->napi.rx_bytes, rx_bytes);
+ u64_stats_add(&rxq_stats->napi.rx_pkt_n, count);
+ u64_stats_update_end(&rxq_stats->napi_syncp);
+
+ priv->xstats.rx_dropped += rx_dropped;
+ priv->xstats.rx_errors += rx_errors;
+
+ return count;
+}
+
+static int stmmac_napi_poll_rx(struct napi_struct *napi, int budget)
+{
+ struct stmmac_channel *ch =
+ container_of(napi, struct stmmac_channel, rx_napi);
+ struct stmmac_priv *priv = ch->priv_data;
+ struct stmmac_rxq_stats *rxq_stats;
+ u32 chan = ch->index;
+ int work_done;
+
+ rxq_stats = &priv->xstats.rxq_stats[chan];
+ u64_stats_update_begin(&rxq_stats->napi_syncp);
+ u64_stats_inc(&rxq_stats->napi.poll);
+ u64_stats_update_end(&rxq_stats->napi_syncp);
+
+ work_done = stmmac_rx(priv, budget, chan);
+ if (work_done < budget && napi_complete_done(napi, work_done)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_enable_dma_irq(priv, priv->ioaddr, chan, 1, 0);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+
+ return work_done;
+}
+
+static int stmmac_napi_poll_tx(struct napi_struct *napi, int budget)
+{
+ struct stmmac_channel *ch =
+ container_of(napi, struct stmmac_channel, tx_napi);
+ struct stmmac_priv *priv = ch->priv_data;
+ struct stmmac_txq_stats *txq_stats;
+ bool pending_packets = false;
+ u32 chan = ch->index;
+ int work_done;
+
+ txq_stats = &priv->xstats.txq_stats[chan];
+ u64_stats_update_begin(&txq_stats->napi_syncp);
+ u64_stats_inc(&txq_stats->napi.poll);
+ u64_stats_update_end(&txq_stats->napi_syncp);
+
+ work_done = stmmac_tx_clean(priv, budget, chan, &pending_packets);
+ work_done = min(work_done, budget);
+
+ if (work_done < budget && napi_complete_done(napi, work_done)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_enable_dma_irq(priv, priv->ioaddr, chan, 0, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+
+ /* TX still have packet to handle, check if we need to arm tx timer */
+ if (pending_packets)
+ stmmac_tx_timer_arm(priv, chan);
+
+ return work_done;
+}
+
+static int stmmac_napi_poll_rxtx(struct napi_struct *napi, int budget)
+{
+ struct stmmac_channel *ch =
+ container_of(napi, struct stmmac_channel, rxtx_napi);
+ struct stmmac_priv *priv = ch->priv_data;
+ bool tx_pending_packets = false;
+ int rx_done, tx_done, rxtx_done;
+ struct stmmac_rxq_stats *rxq_stats;
+ struct stmmac_txq_stats *txq_stats;
+ u32 chan = ch->index;
+
+ rxq_stats = &priv->xstats.rxq_stats[chan];
+ u64_stats_update_begin(&rxq_stats->napi_syncp);
+ u64_stats_inc(&rxq_stats->napi.poll);
+ u64_stats_update_end(&rxq_stats->napi_syncp);
+
+ txq_stats = &priv->xstats.txq_stats[chan];
+ u64_stats_update_begin(&txq_stats->napi_syncp);
+ u64_stats_inc(&txq_stats->napi.poll);
+ u64_stats_update_end(&txq_stats->napi_syncp);
+
+ tx_done = stmmac_tx_clean(priv, budget, chan, &tx_pending_packets);
+ tx_done = min(tx_done, budget);
+
+ rx_done = stmmac_rx_zc(priv, budget, chan);
+
+ rxtx_done = max(tx_done, rx_done);
+
+ /* If either TX or RX work is not complete, return budget
+ * and keep pooling
+ */
+ if (rxtx_done >= budget)
+ return budget;
+
+ /* all work done, exit the polling mode */
+ if (napi_complete_done(napi, rxtx_done)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ /* Both RX and TX work done are complete,
+ * so enable both RX & TX IRQs.
+ */
+ stmmac_enable_dma_irq(priv, priv->ioaddr, chan, 1, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+
+ /* TX still have packet to handle, check if we need to arm tx timer */
+ if (tx_pending_packets)
+ stmmac_tx_timer_arm(priv, chan);
+
+ return min(rxtx_done, budget - 1);
+}
+
+/**
+ * stmmac_tx_timeout
+ * @dev : Pointer to net device structure
+ * @txqueue: the index of the hanging transmit queue
+ * Description: this function is called when a packet transmission fails to
+ * complete within a reasonable time. The driver will mark the error in the
+ * netdev structure and arrange for the device to be reset to a sane state
+ * in order to transmit a new packet.
+ */
+static void stmmac_tx_timeout(struct net_device *dev, unsigned int txqueue)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ stmmac_global_err(priv);
+}
+
+/**
+ * stmmac_set_rx_mode - entry point for multicast addressing
+ * @dev : pointer to the device structure
+ * Description:
+ * This function is a driver entry point which gets called by the kernel
+ * whenever multicast addresses must be enabled/disabled.
+ * Return value:
+ * void.
+ *
+ * FIXME: This may need RXC to be running, but it may be called with BH
+ * disabled, which means we can't call phylink_rx_clk_stop*().
+ */
+static void stmmac_set_rx_mode(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ stmmac_set_filter(priv, priv->hw, dev);
+}
+
+/**
+ * stmmac_change_mtu - entry point to change MTU size for the device.
+ * @dev : device pointer.
+ * @new_mtu : the new MTU size for the device.
+ * Description: the Maximum Transfer Unit (MTU) is used by the network layer
+ * to drive packet transmission. Ethernet has an MTU of 1500 octets
+ * (ETH_DATA_LEN). This value can be changed with ifconfig.
+ * Return value:
+ * 0 on success and an appropriate (-)ve integer as defined in errno.h
+ * file on failure.
+ */
+static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int txfifosz = priv->plat->tx_fifo_size;
+ struct stmmac_dma_conf *dma_conf;
+ const int mtu = new_mtu;
+ int ret;
+
+ if (txfifosz == 0)
+ txfifosz = priv->dma_cap.tx_fifo_size;
+
+ txfifosz /= priv->plat->tx_queues_to_use;
+
+ if (stmmac_xdp_is_enabled(priv) && new_mtu > ETH_DATA_LEN) {
+ netdev_dbg(priv->dev, "Jumbo frames not supported for XDP\n");
+ return -EINVAL;
+ }
+
+ new_mtu = STMMAC_ALIGN(new_mtu);
+
+ /* If condition true, FIFO is too small or MTU too large */
+ if ((txfifosz < new_mtu) || (new_mtu > BUF_SIZE_16KiB))
+ return -EINVAL;
+
+ if (netif_running(dev)) {
+ netdev_dbg(priv->dev, "restarting interface to change its MTU\n");
+ /* Try to allocate the new DMA conf with the new mtu */
+ dma_conf = stmmac_setup_dma_desc(priv, mtu);
+ if (IS_ERR(dma_conf)) {
+ netdev_err(priv->dev, "failed allocating new dma conf for new MTU %d\n",
+ mtu);
+ return PTR_ERR(dma_conf);
+ }
+
+ __stmmac_release(dev);
+
+ ret = __stmmac_open(dev, dma_conf);
+ if (ret) {
+ free_dma_desc_resources(priv, dma_conf);
+ kfree(dma_conf);
+ netdev_err(priv->dev, "failed reopening the interface after MTU change\n");
+ return ret;
+ }
+
+ kfree(dma_conf);
+
+ stmmac_set_rx_mode(dev);
+ }
+
+ WRITE_ONCE(dev->mtu, mtu);
+ netdev_update_features(dev);
+
+ return 0;
+}
+
+static netdev_features_t stmmac_fix_features(struct net_device *dev,
+ netdev_features_t features)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (priv->plat->rx_coe == STMMAC_RX_COE_NONE)
+ features &= ~NETIF_F_RXCSUM;
+
+ if (!priv->plat->tx_coe)
+ features &= ~NETIF_F_CSUM_MASK;
+
+ /* Some GMAC devices have a bugged Jumbo frame support that
+ * needs to have the Tx COE disabled for oversized frames
+ * (due to limited buffer sizes). In this case we disable
+ * the TX csum insertion in the TDES and not use SF.
+ */
+ if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN))
+ features &= ~NETIF_F_CSUM_MASK;
+
+ return features;
+}
+
+static int stmmac_set_features(struct net_device *netdev,
+ netdev_features_t features)
+{
+ struct stmmac_priv *priv = netdev_priv(netdev);
+
+ /* Keep the COE Type in case of csum is supporting */
+ if (features & NETIF_F_RXCSUM)
+ priv->hw->rx_csum = priv->plat->rx_coe;
+ else
+ priv->hw->rx_csum = 0;
+ /* No check needed because rx_coe has been set before and it will be
+ * fixed in case of issue.
+ */
+ stmmac_rx_ipc(priv, priv->hw);
+
+ if (priv->sph_capable) {
+ bool sph_en = (priv->hw->rx_csum > 0) && priv->sph_active;
+ u8 chan;
+
+ for (chan = 0; chan < priv->plat->rx_queues_to_use; chan++)
+ stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan);
+ }
+
+ stmmac_set_gso_types(priv, features & NETIF_F_TSO);
+
+ if (features & NETIF_F_HW_VLAN_CTAG_RX)
+ priv->hw->hw_vlan_en = true;
+ else
+ priv->hw->hw_vlan_en = false;
+
+ phylink_rx_clk_stop_block(priv->phylink);
+ stmmac_set_hw_vlan_mode(priv, priv->hw);
+ phylink_rx_clk_stop_unblock(priv->phylink);
+
+ return 0;
+}
+
+static void stmmac_common_interrupt(struct stmmac_priv *priv)
+{
+ u8 rx_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ u8 queues_count;
+ bool xmac;
+ u8 queue;
+
+ xmac = dwmac_is_xmac(priv->plat->core_type);
+ queues_count = (rx_cnt > tx_cnt) ? rx_cnt : tx_cnt;
+
+ if (priv->irq_wake)
+ pm_wakeup_event(priv->device, 0);
+
+ if (priv->dma_cap.estsel)
+ stmmac_est_irq_status(priv, priv, priv->dev,
+ &priv->xstats, tx_cnt);
+
+ if (stmmac_fpe_supported(priv))
+ stmmac_fpe_irq_status(priv);
+
+ /* To handle GMAC own interrupts */
+ if (priv->plat->core_type == DWMAC_CORE_GMAC || xmac) {
+ int status = stmmac_host_irq_status(priv, &priv->xstats);
+
+ if (unlikely(status)) {
+ /* For LPI we need to save the tx status */
+ if (status & CORE_IRQ_TX_PATH_IN_LPI_MODE)
+ priv->tx_path_in_lpi_mode = true;
+ if (status & CORE_IRQ_TX_PATH_EXIT_LPI_MODE)
+ priv->tx_path_in_lpi_mode = false;
+ }
+
+ for (queue = 0; queue < queues_count; queue++)
+ stmmac_host_mtl_irq_status(priv, priv->hw, queue);
+
+ stmmac_timestamp_interrupt(priv, priv);
+ }
+}
+
+/**
+ * stmmac_interrupt - main ISR
+ * @irq: interrupt number.
+ * @dev_id: to pass the net device pointer.
+ * Description: this is the main driver interrupt service routine.
+ * It can call:
+ * o DMA service routine (to manage incoming frame reception and transmission
+ * status)
+ * o Core interrupts to manage: remote wake-up, management counter, LPI
+ * interrupts.
+ */
+static irqreturn_t stmmac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *dev = (struct net_device *)dev_id;
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ /* Check if adapter is up */
+ if (test_bit(STMMAC_DOWN, &priv->state))
+ return IRQ_HANDLED;
+
+ /* Check ASP error if it isn't delivered via an individual IRQ */
+ if (priv->sfty_irq <= 0 && stmmac_safety_feat_interrupt(priv))
+ return IRQ_HANDLED;
+
+ /* To handle Common interrupts */
+ stmmac_common_interrupt(priv);
+
+ /* To handle DMA interrupts */
+ stmmac_dma_interrupt(priv);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t stmmac_mac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *dev = (struct net_device *)dev_id;
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ /* Check if adapter is up */
+ if (test_bit(STMMAC_DOWN, &priv->state))
+ return IRQ_HANDLED;
+
+ /* To handle Common interrupts */
+ stmmac_common_interrupt(priv);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id)
+{
+ struct net_device *dev = (struct net_device *)dev_id;
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ /* Check if adapter is up */
+ if (test_bit(STMMAC_DOWN, &priv->state))
+ return IRQ_HANDLED;
+
+ /* Check if a fatal error happened */
+ stmmac_safety_feat_interrupt(priv);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t stmmac_msi_intr_tx(int irq, void *data)
+{
+ struct stmmac_tx_queue *tx_q = (struct stmmac_tx_queue *)data;
+ struct stmmac_dma_conf *dma_conf;
+ int chan = tx_q->queue_index;
+ struct stmmac_priv *priv;
+ int status;
+
+ dma_conf = container_of(tx_q, struct stmmac_dma_conf, tx_queue[chan]);
+ priv = container_of(dma_conf, struct stmmac_priv, dma_conf);
+
+ /* Check if adapter is up */
+ if (test_bit(STMMAC_DOWN, &priv->state))
+ return IRQ_HANDLED;
+
+ status = stmmac_napi_check(priv, chan, DMA_DIR_TX);
+
+ if (unlikely(status & tx_hard_error_bump_tc)) {
+ /* Try to bump up the dma threshold on this failure */
+ stmmac_bump_dma_threshold(priv, chan);
+ } else if (unlikely(status == tx_hard_error)) {
+ stmmac_tx_err(priv, chan);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t stmmac_msi_intr_rx(int irq, void *data)
+{
+ struct stmmac_rx_queue *rx_q = (struct stmmac_rx_queue *)data;
+ struct stmmac_dma_conf *dma_conf;
+ int chan = rx_q->queue_index;
+ struct stmmac_priv *priv;
+
+ dma_conf = container_of(rx_q, struct stmmac_dma_conf, rx_queue[chan]);
+ priv = container_of(dma_conf, struct stmmac_priv, dma_conf);
+
+ /* Check if adapter is up */
+ if (test_bit(STMMAC_DOWN, &priv->state))
+ return IRQ_HANDLED;
+
+ stmmac_napi_check(priv, chan, DMA_DIR_RX);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * stmmac_ioctl - Entry point for the Ioctl
+ * @dev: Device pointer.
+ * @rq: An IOCTL specific structure, that can contain a pointer to
+ * a proprietary structure used to pass information to the driver.
+ * @cmd: IOCTL command
+ * Description:
+ * Currently it supports the phy_mii_ioctl(...) and HW time stamping.
+ */
+static int stmmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ struct stmmac_priv *priv = netdev_priv (dev);
+ int ret = -EOPNOTSUPP;
+
+ if (!netif_running(dev))
+ return -EINVAL;
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ case SIOCGMIIREG:
+ case SIOCSMIIREG:
+ ret = phylink_mii_ioctl(priv->phylink, rq, cmd);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int stmmac_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
+ void *cb_priv)
+{
+ struct stmmac_priv *priv = cb_priv;
+ int ret = -EOPNOTSUPP;
+
+ if (!tc_cls_can_offload_and_chain0(priv->dev, type_data))
+ return ret;
+
+ __stmmac_disable_all_queues(priv);
+
+ switch (type) {
+ case TC_SETUP_CLSU32:
+ ret = stmmac_tc_setup_cls_u32(priv, priv, type_data);
+ break;
+ case TC_SETUP_CLSFLOWER:
+ ret = stmmac_tc_setup_cls(priv, priv, type_data);
+ break;
+ default:
+ break;
+ }
+
+ stmmac_enable_all_queues(priv);
+ return ret;
+}
+
+static LIST_HEAD(stmmac_block_cb_list);
+
+static int stmmac_setup_tc(struct net_device *ndev, enum tc_setup_type type,
+ void *type_data)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ switch (type) {
+ case TC_QUERY_CAPS:
+ return stmmac_tc_query_caps(priv, priv, type_data);
+ case TC_SETUP_QDISC_MQPRIO:
+ return stmmac_tc_setup_mqprio(priv, priv, type_data);
+ case TC_SETUP_BLOCK:
+ return flow_block_cb_setup_simple(type_data,
+ &stmmac_block_cb_list,
+ stmmac_setup_tc_block_cb,
+ priv, priv, true);
+ case TC_SETUP_QDISC_CBS:
+ return stmmac_tc_setup_cbs(priv, priv, type_data);
+ case TC_SETUP_QDISC_TAPRIO:
+ return stmmac_tc_setup_taprio(priv, priv, type_data);
+ case TC_SETUP_QDISC_ETF:
+ return stmmac_tc_setup_etf(priv, priv, type_data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static u16 stmmac_select_queue(struct net_device *dev, struct sk_buff *skb,
+ struct net_device *sb_dev)
+{
+ int gso = skb_shinfo(skb)->gso_type;
+
+ if (gso & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6 | SKB_GSO_UDP_L4)) {
+ /*
+ * There is no way to determine the number of TSO/USO
+ * capable Queues. Let's use always the Queue 0
+ * because if TSO/USO is supported then at least this
+ * one will be capable.
+ */
+ return 0;
+ }
+
+ return netdev_pick_tx(dev, skb, NULL) % dev->real_num_tx_queues;
+}
+
+static int stmmac_set_mac_address(struct net_device *ndev, void *addr)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int ret = 0;
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+
+ ret = eth_mac_addr(ndev, addr);
+ if (ret)
+ goto set_mac_error;
+
+ phylink_rx_clk_stop_block(priv->phylink);
+ stmmac_set_umac_addr(priv, priv->hw, ndev->dev_addr, 0);
+ phylink_rx_clk_stop_unblock(priv->phylink);
+
+set_mac_error:
+ pm_runtime_put(priv->device);
+
+ return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *stmmac_fs_dir;
+
+static void sysfs_display_ring(void *head, int size, int extend_desc,
+ struct seq_file *seq, dma_addr_t dma_phy_addr)
+{
+ struct dma_extended_desc *ep = (struct dma_extended_desc *)head;
+ struct dma_desc *p = (struct dma_desc *)head;
+ unsigned int desc_size;
+ dma_addr_t dma_addr;
+ int i;
+
+ desc_size = extend_desc ? sizeof(*ep) : sizeof(*p);
+ for (i = 0; i < size; i++) {
+ dma_addr = dma_phy_addr + i * desc_size;
+ seq_printf(seq, "%d [%pad]: 0x%x 0x%x 0x%x 0x%x\n",
+ i, &dma_addr,
+ le32_to_cpu(p->des0), le32_to_cpu(p->des1),
+ le32_to_cpu(p->des2), le32_to_cpu(p->des3));
+ if (extend_desc)
+ p = &(++ep)->basic;
+ else
+ p++;
+ }
+}
+
+static int stmmac_rings_status_show(struct seq_file *seq, void *v)
+{
+ struct net_device *dev = seq->private;
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 rx_count = priv->plat->rx_queues_to_use;
+ u8 tx_count = priv->plat->tx_queues_to_use;
+ u8 queue;
+
+ if ((dev->flags & IFF_UP) == 0)
+ return 0;
+
+ for (queue = 0; queue < rx_count; queue++) {
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+
+ seq_printf(seq, "RX Queue %d:\n", queue);
+
+ if (priv->extend_desc) {
+ seq_printf(seq, "Extended descriptor ring:\n");
+ sysfs_display_ring((void *)rx_q->dma_erx,
+ priv->dma_conf.dma_rx_size, 1, seq, rx_q->dma_rx_phy);
+ } else {
+ seq_printf(seq, "Descriptor ring:\n");
+ sysfs_display_ring((void *)rx_q->dma_rx,
+ priv->dma_conf.dma_rx_size, 0, seq, rx_q->dma_rx_phy);
+ }
+ }
+
+ for (queue = 0; queue < tx_count; queue++) {
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+
+ seq_printf(seq, "TX Queue %d:\n", queue);
+
+ if (priv->extend_desc) {
+ seq_printf(seq, "Extended descriptor ring:\n");
+ sysfs_display_ring((void *)tx_q->dma_etx,
+ priv->dma_conf.dma_tx_size, 1, seq, tx_q->dma_tx_phy);
+ } else if (!(tx_q->tbs & STMMAC_TBS_AVAIL)) {
+ seq_printf(seq, "Descriptor ring:\n");
+ sysfs_display_ring((void *)tx_q->dma_tx,
+ priv->dma_conf.dma_tx_size, 0, seq, tx_q->dma_tx_phy);
+ }
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(stmmac_rings_status);
+
+static int stmmac_dma_cap_show(struct seq_file *seq, void *v)
+{
+ static const char * const dwxgmac_timestamp_source[] = {
+ "None",
+ "Internal",
+ "External",
+ "Both",
+ };
+ static const char * const dwxgmac_safety_feature_desc[] = {
+ "No",
+ "All Safety Features with ECC and Parity",
+ "All Safety Features without ECC or Parity",
+ "All Safety Features with Parity Only",
+ "ECC Only",
+ "UNDEFINED",
+ "UNDEFINED",
+ "UNDEFINED",
+ };
+ struct net_device *dev = seq->private;
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (!priv->hw_cap_support) {
+ seq_printf(seq, "DMA HW features not supported\n");
+ return 0;
+ }
+
+ seq_printf(seq, "==============================\n");
+ seq_printf(seq, "\tDMA HW features\n");
+ seq_printf(seq, "==============================\n");
+
+ seq_printf(seq, "\t10/100 Mbps: %s\n",
+ (priv->dma_cap.mbps_10_100) ? "Y" : "N");
+ seq_printf(seq, "\t1000 Mbps: %s\n",
+ (priv->dma_cap.mbps_1000) ? "Y" : "N");
+ seq_printf(seq, "\tHalf duplex: %s\n",
+ (priv->dma_cap.half_duplex) ? "Y" : "N");
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC) {
+ seq_printf(seq,
+ "\tNumber of Additional MAC address registers: %d\n",
+ priv->dma_cap.multi_addr);
+ } else {
+ seq_printf(seq, "\tHash Filter: %s\n",
+ (priv->dma_cap.hash_filter) ? "Y" : "N");
+ seq_printf(seq, "\tMultiple MAC address registers: %s\n",
+ (priv->dma_cap.multi_addr) ? "Y" : "N");
+ }
+ seq_printf(seq, "\tPCS (TBI/SGMII/RTBI PHY interfaces): %s\n",
+ (priv->dma_cap.pcs) ? "Y" : "N");
+ seq_printf(seq, "\tSMA (MDIO) Interface: %s\n",
+ (priv->dma_cap.sma_mdio) ? "Y" : "N");
+ seq_printf(seq, "\tPMT Remote wake up: %s\n",
+ (priv->dma_cap.pmt_remote_wake_up) ? "Y" : "N");
+ seq_printf(seq, "\tPMT Magic Frame: %s\n",
+ (priv->dma_cap.pmt_magic_frame) ? "Y" : "N");
+ seq_printf(seq, "\tRMON module: %s\n",
+ (priv->dma_cap.rmon) ? "Y" : "N");
+ seq_printf(seq, "\tIEEE 1588-2002 Time Stamp: %s\n",
+ (priv->dma_cap.time_stamp) ? "Y" : "N");
+ seq_printf(seq, "\tIEEE 1588-2008 Advanced Time Stamp: %s\n",
+ (priv->dma_cap.atime_stamp) ? "Y" : "N");
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC)
+ seq_printf(seq, "\tTimestamp System Time Source: %s\n",
+ dwxgmac_timestamp_source[priv->dma_cap.tssrc]);
+ seq_printf(seq, "\t802.3az - Energy-Efficient Ethernet (EEE): %s\n",
+ (priv->dma_cap.eee) ? "Y" : "N");
+ seq_printf(seq, "\tAV features: %s\n", (priv->dma_cap.av) ? "Y" : "N");
+ seq_printf(seq, "\tChecksum Offload in TX: %s\n",
+ (priv->dma_cap.tx_coe) ? "Y" : "N");
+ if (priv->synopsys_id >= DWMAC_CORE_4_00 ||
+ priv->plat->core_type == DWMAC_CORE_XGMAC) {
+ seq_printf(seq, "\tIP Checksum Offload in RX: %s\n",
+ (priv->dma_cap.rx_coe) ? "Y" : "N");
+ } else {
+ seq_printf(seq, "\tIP Checksum Offload (type1) in RX: %s\n",
+ (priv->dma_cap.rx_coe_type1) ? "Y" : "N");
+ seq_printf(seq, "\tIP Checksum Offload (type2) in RX: %s\n",
+ (priv->dma_cap.rx_coe_type2) ? "Y" : "N");
+ seq_printf(seq, "\tRXFIFO > 2048bytes: %s\n",
+ (priv->dma_cap.rxfifo_over_2048) ? "Y" : "N");
+ }
+ seq_printf(seq, "\tNumber of Additional RX channel: %d\n",
+ priv->dma_cap.number_rx_channel);
+ seq_printf(seq, "\tNumber of Additional TX channel: %d\n",
+ priv->dma_cap.number_tx_channel);
+ seq_printf(seq, "\tNumber of Additional RX queues: %u\n",
+ priv->dma_cap.number_rx_queues);
+ seq_printf(seq, "\tNumber of Additional TX queues: %u\n",
+ priv->dma_cap.number_tx_queues);
+ seq_printf(seq, "\tEnhanced descriptors: %s\n",
+ (priv->dma_cap.enh_desc) ? "Y" : "N");
+ seq_printf(seq, "\tTX Fifo Size: %d\n", priv->dma_cap.tx_fifo_size);
+ seq_printf(seq, "\tRX Fifo Size: %d\n", priv->dma_cap.rx_fifo_size);
+ seq_printf(seq, "\tHash Table Size: %lu\n", priv->dma_cap.hash_tb_sz ?
+ (BIT(priv->dma_cap.hash_tb_sz) << 5) : 0);
+ seq_printf(seq, "\tTSO: %s\n", priv->dma_cap.tsoen ? "Y" : "N");
+ seq_printf(seq, "\tNumber of PPS Outputs: %d\n",
+ priv->dma_cap.pps_out_num);
+ seq_printf(seq, "\tSafety Features: %s\n",
+ dwxgmac_safety_feature_desc[priv->dma_cap.asp]);
+ seq_printf(seq, "\tFlexible RX Parser: %s\n",
+ priv->dma_cap.frpsel ? "Y" : "N");
+ seq_printf(seq, "\tEnhanced Addressing: %d\n",
+ priv->dma_cap.host_dma_width);
+ seq_printf(seq, "\tReceive Side Scaling: %s\n",
+ priv->dma_cap.rssen ? "Y" : "N");
+ seq_printf(seq, "\tVLAN Hash Filtering: %s\n",
+ priv->dma_cap.vlhash ? "Y" : "N");
+ seq_printf(seq, "\tSplit Header: %s\n",
+ priv->dma_cap.sphen ? "Y" : "N");
+ seq_printf(seq, "\tVLAN TX Insertion: %s\n",
+ priv->dma_cap.vlins ? "Y" : "N");
+ seq_printf(seq, "\tDouble VLAN: %s\n",
+ priv->dma_cap.dvlan ? "Y" : "N");
+ seq_printf(seq, "\tNumber of L3/L4 Filters: %d\n",
+ priv->dma_cap.l3l4fnum);
+ seq_printf(seq, "\tARP Offloading: %s\n",
+ priv->dma_cap.arpoffsel ? "Y" : "N");
+ seq_printf(seq, "\tEnhancements to Scheduled Traffic (EST): %s\n",
+ priv->dma_cap.estsel ? "Y" : "N");
+ seq_printf(seq, "\tFrame Preemption (FPE): %s\n",
+ priv->dma_cap.fpesel ? "Y" : "N");
+ seq_printf(seq, "\tTime-Based Scheduling (TBS): %s\n",
+ priv->dma_cap.tbssel ? "Y" : "N");
+ seq_printf(seq, "\tNumber of DMA Channels Enabled for TBS: %d\n",
+ priv->dma_cap.tbs_ch_num);
+ seq_printf(seq, "\tPer-Stream Filtering: %s\n",
+ priv->dma_cap.sgfsel ? "Y" : "N");
+ seq_printf(seq, "\tTX Timestamp FIFO Depth: %lu\n",
+ BIT(priv->dma_cap.ttsfd) >> 1);
+ seq_printf(seq, "\tNumber of Traffic Classes: %d\n",
+ priv->dma_cap.numtc);
+ seq_printf(seq, "\tDCB Feature: %s\n",
+ priv->dma_cap.dcben ? "Y" : "N");
+ seq_printf(seq, "\tIEEE 1588 High Word Register: %s\n",
+ priv->dma_cap.advthword ? "Y" : "N");
+ seq_printf(seq, "\tPTP Offload: %s\n",
+ priv->dma_cap.ptoen ? "Y" : "N");
+ seq_printf(seq, "\tOne-Step Timestamping: %s\n",
+ priv->dma_cap.osten ? "Y" : "N");
+ seq_printf(seq, "\tPriority-Based Flow Control: %s\n",
+ priv->dma_cap.pfcen ? "Y" : "N");
+ seq_printf(seq, "\tNumber of Flexible RX Parser Instructions: %lu\n",
+ BIT(priv->dma_cap.frpes) << 6);
+ seq_printf(seq, "\tNumber of Flexible RX Parser Parsable Bytes: %lu\n",
+ BIT(priv->dma_cap.frpbs) << 6);
+ seq_printf(seq, "\tParallel Instruction Processor Engines: %d\n",
+ priv->dma_cap.frppipe_num);
+ seq_printf(seq, "\tNumber of Extended VLAN Tag Filters: %lu\n",
+ priv->dma_cap.nrvf_num ?
+ (BIT(priv->dma_cap.nrvf_num) << 1) : 0);
+ seq_printf(seq, "\tWidth of the Time Interval Field in GCL: %d\n",
+ priv->dma_cap.estwid ? 4 * priv->dma_cap.estwid + 12 : 0);
+ seq_printf(seq, "\tDepth of GCL: %lu\n",
+ priv->dma_cap.estdep ? (BIT(priv->dma_cap.estdep) << 5) : 0);
+ seq_printf(seq, "\tQueue/Channel-Based VLAN Tag Insertion on TX: %s\n",
+ priv->dma_cap.cbtisel ? "Y" : "N");
+ seq_printf(seq, "\tNumber of Auxiliary Snapshot Inputs: %d\n",
+ priv->dma_cap.aux_snapshot_n);
+ seq_printf(seq, "\tOne-Step Timestamping for PTP over UDP/IP: %s\n",
+ priv->dma_cap.pou_ost_en ? "Y" : "N");
+ seq_printf(seq, "\tEnhanced DMA: %s\n",
+ priv->dma_cap.edma ? "Y" : "N");
+ seq_printf(seq, "\tDifferent Descriptor Cache: %s\n",
+ priv->dma_cap.ediffc ? "Y" : "N");
+ seq_printf(seq, "\tVxLAN/NVGRE: %s\n",
+ priv->dma_cap.vxn ? "Y" : "N");
+ seq_printf(seq, "\tDebug Memory Interface: %s\n",
+ priv->dma_cap.dbgmem ? "Y" : "N");
+ seq_printf(seq, "\tNumber of Policing Counters: %lu\n",
+ priv->dma_cap.pcsel ? BIT(priv->dma_cap.pcsel + 3) : 0);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(stmmac_dma_cap);
+
+/* Use network device events to rename debugfs file entries.
+ */
+static int stmmac_device_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ if (dev->netdev_ops != &stmmac_netdev_ops)
+ goto done;
+
+ switch (event) {
+ case NETDEV_CHANGENAME:
+ debugfs_change_name(priv->dbgfs_dir, "%s", dev->name);
+ break;
+ }
+done:
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block stmmac_notifier = {
+ .notifier_call = stmmac_device_event,
+};
+
+static void stmmac_init_fs(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ rtnl_lock();
+
+ /* Create per netdev entries */
+ priv->dbgfs_dir = debugfs_create_dir(dev->name, stmmac_fs_dir);
+
+ /* Entry to report DMA RX/TX rings */
+ debugfs_create_file("descriptors_status", 0444, priv->dbgfs_dir, dev,
+ &stmmac_rings_status_fops);
+
+ /* Entry to report the DMA HW features */
+ debugfs_create_file("dma_cap", 0444, priv->dbgfs_dir, dev,
+ &stmmac_dma_cap_fops);
+
+ rtnl_unlock();
+}
+
+static void stmmac_exit_fs(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ debugfs_remove_recursive(priv->dbgfs_dir);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static u32 stmmac_vid_crc32_le(__le16 vid_le)
+{
+ unsigned char *data = (unsigned char *)&vid_le;
+ unsigned char data_byte = 0;
+ u32 crc = ~0x0;
+ u32 temp = 0;
+ int i, bits;
+
+ bits = get_bitmask_order(VLAN_VID_MASK);
+ for (i = 0; i < bits; i++) {
+ if ((i % 8) == 0)
+ data_byte = data[i / 8];
+
+ temp = ((crc & 1) ^ data_byte) & 1;
+ crc >>= 1;
+ data_byte >>= 1;
+
+ if (temp)
+ crc ^= 0xedb88320;
+ }
+
+ return crc;
+}
+
+static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double)
+{
+ u32 crc, hash = 0;
+ u16 pmatch = 0;
+ int count = 0;
+ u16 vid = 0;
+
+ for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) {
+ __le16 vid_le = cpu_to_le16(vid);
+ crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28;
+ hash |= (1 << crc);
+ count++;
+ }
+
+ if (!priv->dma_cap.vlhash) {
+ if (count > 2) /* VID = 0 always passes filter */
+ return -EOPNOTSUPP;
+
+ pmatch = vid;
+ hash = 0;
+ }
+
+ if (!netif_running(priv->dev))
+ return 0;
+
+ return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double);
+}
+
+/* FIXME: This may need RXC to be running, but it may be called with BH
+ * disabled, which means we can't call phylink_rx_clk_stop*().
+ */
+static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ unsigned int num_double_vlans;
+ bool is_double = false;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+
+ if (be16_to_cpu(proto) == ETH_P_8021AD)
+ is_double = true;
+
+ set_bit(vid, priv->active_vlans);
+ num_double_vlans = priv->num_double_vlans + is_double;
+ ret = stmmac_vlan_update(priv, num_double_vlans);
+ if (ret) {
+ clear_bit(vid, priv->active_vlans);
+ goto err_pm_put;
+ }
+
+ if (priv->hw->num_vlan) {
+ ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid);
+ if (ret) {
+ clear_bit(vid, priv->active_vlans);
+ stmmac_vlan_update(priv, priv->num_double_vlans);
+ goto err_pm_put;
+ }
+ }
+
+ priv->num_double_vlans = num_double_vlans;
+
+err_pm_put:
+ pm_runtime_put(priv->device);
+
+ return ret;
+}
+
+/* FIXME: This may need RXC to be running, but it may be called with BH
+ * disabled, which means we can't call phylink_rx_clk_stop*().
+ */
+static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ unsigned int num_double_vlans;
+ bool is_double = false;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+
+ if (be16_to_cpu(proto) == ETH_P_8021AD)
+ is_double = true;
+
+ clear_bit(vid, priv->active_vlans);
+ num_double_vlans = priv->num_double_vlans - is_double;
+ ret = stmmac_vlan_update(priv, num_double_vlans);
+ if (ret) {
+ set_bit(vid, priv->active_vlans);
+ goto del_vlan_error;
+ }
+
+ if (priv->hw->num_vlan) {
+ ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid);
+ if (ret) {
+ set_bit(vid, priv->active_vlans);
+ stmmac_vlan_update(priv, priv->num_double_vlans);
+ goto del_vlan_error;
+ }
+ }
+
+ priv->num_double_vlans = num_double_vlans;
+
+del_vlan_error:
+ pm_runtime_put(priv->device);
+
+ return ret;
+}
+
+static void stmmac_vlan_restore(struct stmmac_priv *priv)
+{
+ if (!(priv->dev->features & NETIF_F_VLAN_FEATURES))
+ return;
+
+ if (priv->hw->num_vlan)
+ stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw);
+
+ stmmac_vlan_update(priv, priv->num_double_vlans);
+}
+
+static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+
+ switch (bpf->command) {
+ case XDP_SETUP_PROG:
+ return stmmac_xdp_set_prog(priv, bpf->prog, bpf->extack);
+ case XDP_SETUP_XSK_POOL:
+ return stmmac_xdp_setup_pool(priv, bpf->xsk.pool,
+ bpf->xsk.queue_id);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int stmmac_xdp_xmit(struct net_device *dev, int num_frames,
+ struct xdp_frame **frames, u32 flags)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int cpu = smp_processor_id();
+ struct netdev_queue *nq;
+ int i, nxmit = 0;
+ int queue;
+
+ if (unlikely(test_bit(STMMAC_DOWN, &priv->state)))
+ return -ENETDOWN;
+
+ if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
+ return -EINVAL;
+
+ queue = stmmac_xdp_get_tx_queue(priv, cpu);
+ nq = netdev_get_tx_queue(priv->dev, queue);
+
+ __netif_tx_lock(nq, cpu);
+ /* Avoids TX time-out as we are sharing with slow path */
+ txq_trans_cond_update(nq);
+
+ for (i = 0; i < num_frames; i++) {
+ int res;
+
+ res = stmmac_xdp_xmit_xdpf(priv, queue, frames[i], true);
+ if (res == STMMAC_XDP_CONSUMED)
+ break;
+
+ nxmit++;
+ }
+
+ if (flags & XDP_XMIT_FLUSH) {
+ stmmac_flush_tx_descriptors(priv, queue);
+ stmmac_tx_timer_arm(priv, queue);
+ }
+
+ __netif_tx_unlock(nq);
+
+ return nxmit;
+}
+
+void stmmac_disable_rx_queue(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_channel *ch = &priv->channel[queue];
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, queue, 1, 0);
+ spin_unlock_irqrestore(&ch->lock, flags);
+
+ stmmac_stop_rx_dma(priv, queue);
+ __free_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
+}
+
+void stmmac_enable_rx_queue(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+ struct stmmac_channel *ch = &priv->channel[queue];
+ unsigned long flags;
+ int ret;
+
+ ret = __alloc_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
+ if (ret) {
+ netdev_err(priv->dev, "Failed to alloc RX desc.\n");
+ return;
+ }
+
+ ret = __init_dma_rx_desc_rings(priv, &priv->dma_conf, queue, GFP_KERNEL);
+ if (ret) {
+ __free_dma_rx_desc_resources(priv, &priv->dma_conf, queue);
+ netdev_err(priv->dev, "Failed to init RX desc.\n");
+ return;
+ }
+
+ stmmac_reset_rx_queue(priv, queue);
+ stmmac_clear_rx_descriptors(priv, &priv->dma_conf, queue);
+
+ stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ rx_q->dma_rx_phy, queue);
+
+ stmmac_set_queue_rx_tail_ptr(priv, rx_q, queue, rx_q->buf_alloc_num);
+
+ stmmac_set_queue_rx_buf_size(priv, rx_q, queue);
+
+ stmmac_start_rx_dma(priv, queue);
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_enable_dma_irq(priv, priv->ioaddr, queue, 1, 0);
+ spin_unlock_irqrestore(&ch->lock, flags);
+}
+
+void stmmac_disable_tx_queue(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_channel *ch = &priv->channel[queue];
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, queue, 0, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+
+ stmmac_stop_tx_dma(priv, queue);
+ __free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
+}
+
+void stmmac_enable_tx_queue(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+ struct stmmac_channel *ch = &priv->channel[queue];
+ unsigned long flags;
+ int ret;
+
+ ret = __alloc_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
+ if (ret) {
+ netdev_err(priv->dev, "Failed to alloc TX desc.\n");
+ return;
+ }
+
+ ret = __init_dma_tx_desc_rings(priv, &priv->dma_conf, queue);
+ if (ret) {
+ __free_dma_tx_desc_resources(priv, &priv->dma_conf, queue);
+ netdev_err(priv->dev, "Failed to init TX desc.\n");
+ return;
+ }
+
+ stmmac_reset_tx_queue(priv, queue);
+ stmmac_clear_tx_descriptors(priv, &priv->dma_conf, queue);
+
+ stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ tx_q->dma_tx_phy, queue);
+
+ if (tx_q->tbs & STMMAC_TBS_AVAIL)
+ stmmac_enable_tbs(priv, priv->ioaddr, 1, queue);
+
+ stmmac_set_queue_tx_tail_ptr(priv, tx_q, queue, 0);
+
+ stmmac_start_tx_dma(priv, queue);
+
+ spin_lock_irqsave(&ch->lock, flags);
+ stmmac_enable_dma_irq(priv, priv->ioaddr, queue, 0, 1);
+ spin_unlock_irqrestore(&ch->lock, flags);
+}
+
+void stmmac_xdp_release(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 chan;
+
+ /* Ensure tx function is not running */
+ netif_tx_disable(dev);
+
+ /* Disable NAPI process */
+ stmmac_disable_all_queues(priv);
+
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+
+ /* Free the IRQ lines */
+ stmmac_free_irq(dev, REQ_IRQ_ERR_ALL, 0);
+
+ /* Stop TX/RX DMA channels */
+ stmmac_stop_all_dma(priv);
+
+ /* Release and free the Rx/Tx resources */
+ free_dma_desc_resources(priv, &priv->dma_conf);
+
+ /* Disable the MAC Rx/Tx */
+ stmmac_mac_set(priv, priv->ioaddr, false);
+
+ /* set trans_start so we don't get spurious
+ * watchdogs during reset
+ */
+ netif_trans_update(dev);
+ netif_carrier_off(dev);
+}
+
+int stmmac_xdp_open(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 rx_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ u8 dma_csr_ch = max(rx_cnt, tx_cnt);
+ struct stmmac_rx_queue *rx_q;
+ struct stmmac_tx_queue *tx_q;
+ bool sph_en;
+ u8 chan;
+ int ret;
+
+ ret = alloc_dma_desc_resources(priv, &priv->dma_conf);
+ if (ret < 0) {
+ netdev_err(dev, "%s: DMA descriptors allocation failed\n",
+ __func__);
+ goto dma_desc_error;
+ }
+
+ ret = init_dma_desc_rings(dev, &priv->dma_conf, GFP_KERNEL);
+ if (ret < 0) {
+ netdev_err(dev, "%s: DMA descriptors initialization failed\n",
+ __func__);
+ goto init_error;
+ }
+
+ stmmac_reset_queues_param(priv);
+
+ /* DMA CSR Channel configuration */
+ for (chan = 0; chan < dma_csr_ch; chan++) {
+ stmmac_init_chan(priv, priv->ioaddr, priv->plat->dma_cfg, chan);
+ stmmac_disable_dma_irq(priv, priv->ioaddr, chan, 1, 1);
+ }
+
+ /* Adjust Split header */
+ sph_en = (priv->hw->rx_csum > 0) && priv->sph_active;
+
+ /* DMA RX Channel Configuration */
+ for (chan = 0; chan < rx_cnt; chan++) {
+ rx_q = &priv->dma_conf.rx_queue[chan];
+
+ stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ rx_q->dma_rx_phy, chan);
+
+ stmmac_set_queue_rx_tail_ptr(priv, rx_q, chan,
+ rx_q->buf_alloc_num);
+
+ stmmac_set_queue_rx_buf_size(priv, rx_q, chan);
+
+ stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan);
+ }
+
+ /* DMA TX Channel Configuration */
+ for (chan = 0; chan < tx_cnt; chan++) {
+ tx_q = &priv->dma_conf.tx_queue[chan];
+
+ stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
+ tx_q->dma_tx_phy, chan);
+
+ stmmac_set_queue_tx_tail_ptr(priv, tx_q, chan, 0);
+
+ hrtimer_setup(&tx_q->txtimer, stmmac_tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ }
+
+ /* Enable the MAC Rx/Tx */
+ stmmac_mac_set(priv, priv->ioaddr, true);
+
+ /* Start Rx & Tx DMA Channels */
+ stmmac_start_all_dma(priv);
+
+ ret = stmmac_request_irq(dev);
+ if (ret)
+ goto irq_error;
+
+ /* Enable NAPI process*/
+ stmmac_enable_all_queues(priv);
+ netif_carrier_on(dev);
+ netif_tx_start_all_queues(dev);
+ stmmac_enable_all_dma_irq(priv);
+
+ return 0;
+
+irq_error:
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+
+init_error:
+ free_dma_desc_resources(priv, &priv->dma_conf);
+dma_desc_error:
+ return ret;
+}
+
+int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ struct stmmac_rx_queue *rx_q;
+ struct stmmac_tx_queue *tx_q;
+ struct stmmac_channel *ch;
+
+ if (test_bit(STMMAC_DOWN, &priv->state) ||
+ !netif_carrier_ok(priv->dev))
+ return -ENETDOWN;
+
+ if (!stmmac_xdp_is_enabled(priv))
+ return -EINVAL;
+
+ if (queue >= priv->plat->rx_queues_to_use ||
+ queue >= priv->plat->tx_queues_to_use)
+ return -EINVAL;
+
+ rx_q = &priv->dma_conf.rx_queue[queue];
+ tx_q = &priv->dma_conf.tx_queue[queue];
+ ch = &priv->channel[queue];
+
+ if (!rx_q->xsk_pool && !tx_q->xsk_pool)
+ return -EINVAL;
+
+ if (!napi_if_scheduled_mark_missed(&ch->rxtx_napi)) {
+ /* EQoS does not have per-DMA channel SW interrupt,
+ * so we schedule RX Napi straight-away.
+ */
+ if (likely(napi_schedule_prep(&ch->rxtx_napi)))
+ __napi_schedule(&ch->rxtx_napi);
+ }
+
+ return 0;
+}
+
+static void stmmac_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ u8 rx_cnt = priv->plat->rx_queues_to_use;
+ unsigned int start;
+ u8 q;
+
+ for (q = 0; q < tx_cnt; q++) {
+ struct stmmac_txq_stats *txq_stats = &priv->xstats.txq_stats[q];
+ u64 tx_packets;
+ u64 tx_bytes;
+
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->q_syncp);
+ tx_bytes = u64_stats_read(&txq_stats->q.tx_bytes);
+ } while (u64_stats_fetch_retry(&txq_stats->q_syncp, start));
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->napi_syncp);
+ tx_packets = u64_stats_read(&txq_stats->napi.tx_packets);
+ } while (u64_stats_fetch_retry(&txq_stats->napi_syncp, start));
+
+ stats->tx_packets += tx_packets;
+ stats->tx_bytes += tx_bytes;
+ }
+
+ for (q = 0; q < rx_cnt; q++) {
+ struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[q];
+ u64 rx_packets;
+ u64 rx_bytes;
+
+ do {
+ start = u64_stats_fetch_begin(&rxq_stats->napi_syncp);
+ rx_packets = u64_stats_read(&rxq_stats->napi.rx_packets);
+ rx_bytes = u64_stats_read(&rxq_stats->napi.rx_bytes);
+ } while (u64_stats_fetch_retry(&rxq_stats->napi_syncp, start));
+
+ stats->rx_packets += rx_packets;
+ stats->rx_bytes += rx_bytes;
+ }
+
+ stats->rx_dropped = priv->xstats.rx_dropped;
+ stats->rx_errors = priv->xstats.rx_errors;
+ stats->tx_dropped = priv->xstats.tx_dropped;
+ stats->tx_errors = priv->xstats.tx_errors;
+ stats->tx_carrier_errors = priv->xstats.tx_losscarrier + priv->xstats.tx_carrier;
+ stats->collisions = priv->xstats.tx_collision + priv->xstats.rx_collision;
+ stats->rx_length_errors = priv->xstats.rx_length;
+ stats->rx_crc_errors = priv->xstats.rx_crc_errors;
+ stats->rx_over_errors = priv->xstats.rx_overflow_cntr;
+ stats->rx_missed_errors = priv->xstats.rx_missed_cntr;
+}
+
+static const struct net_device_ops stmmac_netdev_ops = {
+ .ndo_open = stmmac_open,
+ .ndo_start_xmit = stmmac_xmit,
+ .ndo_features_check = stmmac_features_check,
+ .ndo_stop = stmmac_release,
+ .ndo_change_mtu = stmmac_change_mtu,
+ .ndo_fix_features = stmmac_fix_features,
+ .ndo_set_features = stmmac_set_features,
+ .ndo_set_rx_mode = stmmac_set_rx_mode,
+ .ndo_tx_timeout = stmmac_tx_timeout,
+ .ndo_eth_ioctl = stmmac_ioctl,
+ .ndo_get_stats64 = stmmac_get_stats64,
+ .ndo_setup_tc = stmmac_setup_tc,
+ .ndo_select_queue = stmmac_select_queue,
+ .ndo_set_mac_address = stmmac_set_mac_address,
+ .ndo_vlan_rx_add_vid = stmmac_vlan_rx_add_vid,
+ .ndo_vlan_rx_kill_vid = stmmac_vlan_rx_kill_vid,
+ .ndo_bpf = stmmac_bpf,
+ .ndo_xdp_xmit = stmmac_xdp_xmit,
+ .ndo_xsk_wakeup = stmmac_xsk_wakeup,
+ .ndo_hwtstamp_get = stmmac_hwtstamp_get,
+ .ndo_hwtstamp_set = stmmac_hwtstamp_set,
+};
+
+static void stmmac_reset_subtask(struct stmmac_priv *priv)
+{
+ if (!test_and_clear_bit(STMMAC_RESET_REQUESTED, &priv->state))
+ return;
+ if (test_bit(STMMAC_DOWN, &priv->state))
+ return;
+
+ netdev_err(priv->dev, "Reset adapter.\n");
+
+ rtnl_lock();
+ netif_trans_update(priv->dev);
+ while (test_and_set_bit(STMMAC_RESETING, &priv->state))
+ usleep_range(1000, 2000);
+
+ set_bit(STMMAC_DOWN, &priv->state);
+ dev_close(priv->dev);
+ dev_open(priv->dev, NULL);
+ clear_bit(STMMAC_DOWN, &priv->state);
+ clear_bit(STMMAC_RESETING, &priv->state);
+ rtnl_unlock();
+}
+
+static void stmmac_service_task(struct work_struct *work)
+{
+ struct stmmac_priv *priv = container_of(work, struct stmmac_priv,
+ service_task);
+
+ stmmac_reset_subtask(priv);
+ clear_bit(STMMAC_SERVICE_SCHED, &priv->state);
+}
+
+static void stmmac_print_actphyif(struct stmmac_priv *priv)
+{
+ const char **phyif_table;
+ const char *actphyif_str;
+ size_t phyif_table_size;
+
+ switch (priv->plat->core_type) {
+ case DWMAC_CORE_MAC100:
+ return;
+
+ case DWMAC_CORE_GMAC:
+ case DWMAC_CORE_GMAC4:
+ phyif_table = stmmac_dwmac_actphyif;
+ phyif_table_size = ARRAY_SIZE(stmmac_dwmac_actphyif);
+ break;
+
+ case DWMAC_CORE_XGMAC:
+ phyif_table = stmmac_dwxgmac_phyif;
+ phyif_table_size = ARRAY_SIZE(stmmac_dwxgmac_phyif);
+ break;
+ }
+
+ if (priv->dma_cap.actphyif < phyif_table_size)
+ actphyif_str = phyif_table[priv->dma_cap.actphyif];
+ else
+ actphyif_str = NULL;
+
+ if (!actphyif_str)
+ actphyif_str = "unknown";
+
+ dev_info(priv->device, "Active PHY interface: %s (%u)\n",
+ actphyif_str, priv->dma_cap.actphyif);
+}
+
+/**
+ * stmmac_hw_init - Init the MAC device
+ * @priv: driver private structure
+ * Description: this function is to configure the MAC device according to
+ * some platform parameters or the HW capability register. It prepares the
+ * driver to use either ring or chain modes and to setup either enhanced or
+ * normal descriptors.
+ */
+static int stmmac_hw_init(struct stmmac_priv *priv)
+{
+ int ret;
+
+ /* dwmac-sun8i only work in chain mode */
+ if (priv->plat->flags & STMMAC_FLAG_HAS_SUN8I)
+ chain_mode = 1;
+ priv->chain_mode = !!chain_mode;
+
+ /* Initialize HW Interface */
+ ret = stmmac_hwif_init(priv);
+ if (ret)
+ return ret;
+
+ /* Get the HW capability (new GMAC newer than 3.50a) */
+ priv->hw_cap_support = stmmac_get_hw_features(priv);
+ if (priv->hw_cap_support) {
+ dev_info(priv->device, "DMA HW capability register supported\n");
+
+ /* We can override some gmac/dma configuration fields: e.g.
+ * enh_desc, tx_coe (e.g. that are passed through the
+ * platform) with the values from the HW capability
+ * register (if supported).
+ */
+ priv->plat->enh_desc = priv->dma_cap.enh_desc;
+ priv->plat->pmt = priv->dma_cap.pmt_remote_wake_up &&
+ !(priv->plat->flags & STMMAC_FLAG_USE_PHY_WOL);
+ if (priv->dma_cap.hash_tb_sz) {
+ priv->hw->multicast_filter_bins =
+ (BIT(priv->dma_cap.hash_tb_sz) << 5);
+ priv->hw->mcast_bits_log2 =
+ ilog2(priv->hw->multicast_filter_bins);
+ }
+
+ /* TXCOE doesn't work in thresh DMA mode */
+ if (priv->plat->force_thresh_dma_mode)
+ priv->plat->tx_coe = false;
+ else
+ priv->plat->tx_coe = priv->dma_cap.tx_coe;
+
+ /* In case of GMAC4 rx_coe is from HW cap register. */
+ priv->plat->rx_coe = priv->dma_cap.rx_coe;
+
+ if (priv->dma_cap.rx_coe_type2)
+ priv->plat->rx_coe = STMMAC_RX_COE_TYPE2;
+ else if (priv->dma_cap.rx_coe_type1)
+ priv->plat->rx_coe = STMMAC_RX_COE_TYPE1;
+
+ stmmac_print_actphyif(priv);
+ } else {
+ dev_info(priv->device, "No HW DMA feature register supported\n");
+ }
+
+ if (priv->plat->rx_coe) {
+ priv->hw->rx_csum = priv->plat->rx_coe;
+ dev_info(priv->device, "RX Checksum Offload Engine supported\n");
+ if (priv->synopsys_id < DWMAC_CORE_4_00)
+ dev_info(priv->device, "COE Type %d\n", priv->hw->rx_csum);
+ }
+ if (priv->plat->tx_coe)
+ dev_info(priv->device, "TX Checksum insertion supported\n");
+
+ if (priv->plat->pmt) {
+ dev_info(priv->device, "Wake-Up On Lan supported\n");
+ device_set_wakeup_capable(priv->device, 1);
+ devm_pm_set_wake_irq(priv->device, priv->wol_irq);
+ }
+
+ if (priv->dma_cap.number_rx_queues &&
+ priv->plat->rx_queues_to_use > priv->dma_cap.number_rx_queues) {
+ dev_warn(priv->device,
+ "Number of Rx queues (%u) exceeds dma capability\n",
+ priv->plat->rx_queues_to_use);
+ priv->plat->rx_queues_to_use = priv->dma_cap.number_rx_queues;
+ }
+ if (priv->dma_cap.number_tx_queues &&
+ priv->plat->tx_queues_to_use > priv->dma_cap.number_tx_queues) {
+ dev_warn(priv->device,
+ "Number of Tx queues (%u) exceeds dma capability\n",
+ priv->plat->tx_queues_to_use);
+ priv->plat->tx_queues_to_use = priv->dma_cap.number_tx_queues;
+ }
+
+ if (priv->dma_cap.rx_fifo_size &&
+ priv->plat->rx_fifo_size > priv->dma_cap.rx_fifo_size) {
+ dev_warn(priv->device,
+ "Rx FIFO size (%u) exceeds dma capability\n",
+ priv->plat->rx_fifo_size);
+ priv->plat->rx_fifo_size = priv->dma_cap.rx_fifo_size;
+ }
+ if (priv->dma_cap.tx_fifo_size &&
+ priv->plat->tx_fifo_size > priv->dma_cap.tx_fifo_size) {
+ dev_warn(priv->device,
+ "Tx FIFO size (%u) exceeds dma capability\n",
+ priv->plat->tx_fifo_size);
+ priv->plat->tx_fifo_size = priv->dma_cap.tx_fifo_size;
+ }
+
+ priv->hw->vlan_fail_q_en =
+ (priv->plat->flags & STMMAC_FLAG_VLAN_FAIL_Q_EN);
+ priv->hw->vlan_fail_q = priv->plat->vlan_fail_q;
+
+ /* Run HW quirks, if any */
+ if (priv->hwif_quirks) {
+ ret = priv->hwif_quirks(priv);
+ if (ret)
+ return ret;
+ }
+
+ /* Set alternate descriptor size (which tells the hardware that
+ * descriptors are 8 32-bit words) when using extended descriptors
+ * with ring mode. Only applicable for pre-v4.0 cores. Platform glue
+ * is not expected to change this.
+ */
+ priv->plat->dma_cfg->atds = priv->extend_desc &&
+ priv->descriptor_mode == STMMAC_RING_MODE;
+
+ /* Rx Watchdog is available in the COREs newer than the 3.40.
+ * In some case, for example on bugged HW this feature
+ * has to be disable and this can be done by passing the
+ * riwt_off field from the platform.
+ */
+ if ((priv->synopsys_id >= DWMAC_CORE_3_50 ||
+ priv->plat->core_type == DWMAC_CORE_XGMAC) &&
+ !priv->plat->riwt_off) {
+ priv->use_riwt = 1;
+ dev_info(priv->device,
+ "Enable RX Mitigation via HW Watchdog Timer\n");
+ }
+
+ /* Unimplemented PCS init (as indicated by stmmac_do_callback()
+ * perversely returning -EINVAL) is non-fatal.
+ */
+ ret = stmmac_mac_pcs_init(priv);
+ if (ret != -EINVAL)
+ return ret;
+
+ return 0;
+}
+
+static void stmmac_napi_add(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 queue, maxq;
+
+ maxq = max(priv->plat->rx_queues_to_use, priv->plat->tx_queues_to_use);
+
+ for (queue = 0; queue < maxq; queue++) {
+ struct stmmac_channel *ch = &priv->channel[queue];
+
+ ch->priv_data = priv;
+ ch->index = queue;
+ spin_lock_init(&ch->lock);
+
+ if (queue < priv->plat->rx_queues_to_use) {
+ netif_napi_add(dev, &ch->rx_napi, stmmac_napi_poll_rx);
+ }
+ if (queue < priv->plat->tx_queues_to_use) {
+ netif_napi_add_tx(dev, &ch->tx_napi,
+ stmmac_napi_poll_tx);
+ }
+ if (queue < priv->plat->rx_queues_to_use &&
+ queue < priv->plat->tx_queues_to_use) {
+ netif_napi_add(dev, &ch->rxtx_napi,
+ stmmac_napi_poll_rxtx);
+ }
+ }
+}
+
+static void stmmac_napi_del(struct net_device *dev)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ u8 queue, maxq;
+
+ maxq = max(priv->plat->rx_queues_to_use, priv->plat->tx_queues_to_use);
+
+ for (queue = 0; queue < maxq; queue++) {
+ struct stmmac_channel *ch = &priv->channel[queue];
+
+ if (queue < priv->plat->rx_queues_to_use)
+ netif_napi_del(&ch->rx_napi);
+ if (queue < priv->plat->tx_queues_to_use)
+ netif_napi_del(&ch->tx_napi);
+ if (queue < priv->plat->rx_queues_to_use &&
+ queue < priv->plat->tx_queues_to_use) {
+ netif_napi_del(&ch->rxtx_napi);
+ }
+ }
+}
+
+int stmmac_reinit_queues(struct net_device *dev, u8 rx_cnt, u8 tx_cnt)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int ret = 0, i;
+
+ if (netif_running(dev))
+ stmmac_release(dev);
+
+ stmmac_napi_del(dev);
+
+ priv->plat->rx_queues_to_use = rx_cnt;
+ priv->plat->tx_queues_to_use = tx_cnt;
+ if (!netif_is_rxfh_configured(dev))
+ for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++)
+ priv->rss.table[i] = ethtool_rxfh_indir_default(i,
+ rx_cnt);
+
+ stmmac_napi_add(dev);
+
+ if (netif_running(dev))
+ ret = stmmac_open(dev);
+
+ return ret;
+}
+
+int stmmac_reinit_ringparam(struct net_device *dev, u32 rx_size, u32 tx_size)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int ret = 0;
+
+ if (netif_running(dev))
+ stmmac_release(dev);
+
+ priv->dma_conf.dma_rx_size = rx_size;
+ priv->dma_conf.dma_tx_size = tx_size;
+
+ if (netif_running(dev))
+ ret = stmmac_open(dev);
+
+ return ret;
+}
+
+static int stmmac_xdp_rx_timestamp(const struct xdp_md *_ctx, u64 *timestamp)
+{
+ const struct stmmac_xdp_buff *ctx = (void *)_ctx;
+ struct dma_desc *desc_contains_ts = ctx->desc;
+ struct stmmac_priv *priv = ctx->priv;
+ struct dma_desc *ndesc = ctx->ndesc;
+ struct dma_desc *desc = ctx->desc;
+ u64 ns = 0;
+
+ if (!priv->hwts_rx_en)
+ return -ENODATA;
+
+ /* For GMAC4, the valid timestamp is from CTX next desc. */
+ if (dwmac_is_xmac(priv->plat->core_type))
+ desc_contains_ts = ndesc;
+
+ /* Check if timestamp is available */
+ if (stmmac_get_rx_timestamp_status(priv, desc, ndesc, priv->adv_ts)) {
+ stmmac_get_timestamp(priv, desc_contains_ts, priv->adv_ts, &ns);
+ ns -= priv->plat->cdc_error_adj;
+ *timestamp = ns_to_ktime(ns);
+ return 0;
+ }
+
+ return -ENODATA;
+}
+
+static const struct xdp_metadata_ops stmmac_xdp_metadata_ops = {
+ .xmo_rx_timestamp = stmmac_xdp_rx_timestamp,
+};
+
+static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id,
+ struct devlink_param_gset_ctx *ctx,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_devlink_priv *dl_priv = devlink_priv(dl);
+ struct stmmac_priv *priv = dl_priv->stmmac_priv;
+
+ priv->tsfupdt_coarse = ctx->val.vbool;
+
+ if (priv->tsfupdt_coarse)
+ priv->systime_flags &= ~PTP_TCR_TSCFUPDT;
+ else
+ priv->systime_flags |= PTP_TCR_TSCFUPDT;
+
+ /* In Coarse mode, we can use a smaller subsecond increment, let's
+ * reconfigure the systime, subsecond increment and addend.
+ */
+ stmmac_update_subsecond_increment(priv);
+
+ return 0;
+}
+
+static int stmmac_dl_ts_coarse_get(struct devlink *dl, u32 id,
+ struct devlink_param_gset_ctx *ctx,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_devlink_priv *dl_priv = devlink_priv(dl);
+ struct stmmac_priv *priv = dl_priv->stmmac_priv;
+
+ ctx->val.vbool = priv->tsfupdt_coarse;
+
+ return 0;
+}
+
+static const struct devlink_param stmmac_devlink_params[] = {
+ DEVLINK_PARAM_DRIVER(STMMAC_DEVLINK_PARAM_ID_TS_COARSE, "phc_coarse_adj",
+ DEVLINK_PARAM_TYPE_BOOL,
+ BIT(DEVLINK_PARAM_CMODE_RUNTIME),
+ stmmac_dl_ts_coarse_get,
+ stmmac_dl_ts_coarse_set, NULL),
+};
+
+/* None of the generic devlink parameters are implemented */
+static const struct devlink_ops stmmac_devlink_ops = {};
+
+static int stmmac_register_devlink(struct stmmac_priv *priv)
+{
+ struct stmmac_devlink_priv *dl_priv;
+ int ret;
+
+ /* For now, what is exposed over devlink is only relevant when
+ * timestamping is available and we have a valid ptp clock rate
+ */
+ if (!(priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp) ||
+ !priv->plat->clk_ptp_rate)
+ return 0;
+
+ priv->devlink = devlink_alloc(&stmmac_devlink_ops, sizeof(*dl_priv),
+ priv->device);
+ if (!priv->devlink)
+ return -ENOMEM;
+
+ dl_priv = devlink_priv(priv->devlink);
+ dl_priv->stmmac_priv = priv;
+
+ ret = devlink_params_register(priv->devlink, stmmac_devlink_params,
+ ARRAY_SIZE(stmmac_devlink_params));
+ if (ret)
+ goto dl_free;
+
+ devlink_register(priv->devlink);
+ return 0;
+
+dl_free:
+ devlink_free(priv->devlink);
+
+ return ret;
+}
+
+static void stmmac_unregister_devlink(struct stmmac_priv *priv)
+{
+ if (!priv->devlink)
+ return;
+
+ devlink_unregister(priv->devlink);
+ devlink_params_unregister(priv->devlink, stmmac_devlink_params,
+ ARRAY_SIZE(stmmac_devlink_params));
+ devlink_free(priv->devlink);
+}
+
+struct plat_stmmacenet_data *stmmac_plat_dat_alloc(struct device *dev)
+{
+ struct plat_stmmacenet_data *plat_dat;
+ int i;
+
+ plat_dat = devm_kzalloc(dev, sizeof(*plat_dat), GFP_KERNEL);
+ if (!plat_dat)
+ return NULL;
+
+ plat_dat->dma_cfg = &plat_dat->__dma_cfg;
+
+ /* Set the defaults:
+ * - phy autodetection
+ * - determine GMII_Address CR field from CSR clock
+ * - allow MTU up to JUMBO_LEN
+ * - hash table size
+ * - one unicast filter entry
+ */
+ plat_dat->phy_addr = -1;
+ plat_dat->clk_csr = -1;
+ plat_dat->maxmtu = JUMBO_LEN;
+ plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
+ plat_dat->unicast_filter_entries = 1;
+
+ /* Set the mtl defaults */
+ plat_dat->tx_queues_to_use = 1;
+ plat_dat->rx_queues_to_use = 1;
+
+ /* Setup the default RX queue channel map */
+ for (i = 0; i < ARRAY_SIZE(plat_dat->rx_queues_cfg); i++)
+ plat_dat->rx_queues_cfg[i].chan = i;
+
+ return plat_dat;
+}
+EXPORT_SYMBOL_GPL(stmmac_plat_dat_alloc);
+
+static int __stmmac_dvr_probe(struct device *device,
+ struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *res)
+{
+ struct net_device *ndev = NULL;
+ struct stmmac_priv *priv;
+ int i, ret = 0;
+ u8 rxq;
+
+ if (!plat_dat->dma_cfg || !plat_dat->dma_cfg->pbl) {
+ dev_err(device, "invalid DMA configuration\n");
+ return -EINVAL;
+ }
+
+ ndev = devm_alloc_etherdev_mqs(device, sizeof(struct stmmac_priv),
+ MTL_MAX_TX_QUEUES, MTL_MAX_RX_QUEUES);
+ if (!ndev)
+ return -ENOMEM;
+
+ SET_NETDEV_DEV(ndev, device);
+
+ priv = netdev_priv(ndev);
+ priv->device = device;
+ priv->dev = ndev;
+
+ for (i = 0; i < MTL_MAX_RX_QUEUES; i++)
+ u64_stats_init(&priv->xstats.rxq_stats[i].napi_syncp);
+ for (i = 0; i < MTL_MAX_TX_QUEUES; i++) {
+ u64_stats_init(&priv->xstats.txq_stats[i].q_syncp);
+ u64_stats_init(&priv->xstats.txq_stats[i].napi_syncp);
+ }
+
+ priv->xstats.pcpu_stats =
+ devm_netdev_alloc_pcpu_stats(device, struct stmmac_pcpu_stats);
+ if (!priv->xstats.pcpu_stats)
+ return -ENOMEM;
+
+ stmmac_set_ethtool_ops(ndev);
+ priv->pause_time = pause;
+ priv->plat = plat_dat;
+ priv->ioaddr = res->addr;
+ priv->dev->base_addr = (unsigned long)res->addr;
+ priv->plat->dma_cfg->multi_msi_en =
+ (priv->plat->flags & STMMAC_FLAG_MULTI_MSI_EN);
+
+ priv->dev->irq = res->irq;
+ priv->wol_irq = res->wol_irq;
+ priv->sfty_irq = res->sfty_irq;
+
+ if (priv->plat->flags & STMMAC_FLAG_MULTI_MSI_EN) {
+ ret = stmmac_msi_init(priv, res);
+ if (ret)
+ return ret;
+ }
+
+ if (!is_zero_ether_addr(res->mac))
+ eth_hw_addr_set(priv->dev, res->mac);
+
+ dev_set_drvdata(device, priv->dev);
+
+ /* Verify driver arguments */
+ stmmac_verify_args();
+
+ priv->af_xdp_zc_qps = bitmap_zalloc(MTL_MAX_TX_QUEUES, GFP_KERNEL);
+ if (!priv->af_xdp_zc_qps)
+ return -ENOMEM;
+
+ /* Allocate workqueue */
+ priv->wq = create_singlethread_workqueue("stmmac_wq");
+ if (!priv->wq) {
+ dev_err(priv->device, "failed to create workqueue\n");
+ ret = -ENOMEM;
+ goto error_wq_init;
+ }
+
+ INIT_WORK(&priv->service_task, stmmac_service_task);
+
+ timer_setup(&priv->eee_ctrl_timer, stmmac_eee_ctrl_timer, 0);
+
+ /* Override with kernel parameters if supplied XXX CRS XXX
+ * this needs to have multiple instances
+ */
+ if ((phyaddr >= 0) && (phyaddr <= 31))
+ priv->plat->phy_addr = phyaddr;
+
+ if (priv->plat->stmmac_rst) {
+ ret = reset_control_assert(priv->plat->stmmac_rst);
+ reset_control_deassert(priv->plat->stmmac_rst);
+ /* Some reset controllers have only reset callback instead of
+ * assert + deassert callbacks pair.
+ */
+ if (ret == -ENOTSUPP)
+ reset_control_reset(priv->plat->stmmac_rst);
+ }
+
+ ret = reset_control_deassert(priv->plat->stmmac_ahb_rst);
+ if (ret == -ENOTSUPP)
+ dev_err(priv->device, "unable to bring out of ahb reset: %pe\n",
+ ERR_PTR(ret));
+
+ /* Wait a bit for the reset to take effect */
+ udelay(10);
+
+ /* Init MAC and get the capabilities */
+ ret = stmmac_hw_init(priv);
+ if (ret)
+ goto error_hw_init;
+
+ /* Only DWMAC core version 5.20 onwards supports HW descriptor prefetch.
+ */
+ if (priv->synopsys_id < DWMAC_CORE_5_20)
+ priv->plat->dma_cfg->dche = false;
+
+ stmmac_check_ether_addr(priv);
+
+ ndev->netdev_ops = &stmmac_netdev_ops;
+
+ ndev->xdp_metadata_ops = &stmmac_xdp_metadata_ops;
+ ndev->xsk_tx_metadata_ops = &stmmac_xsk_tx_metadata_ops;
+
+ ndev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
+ NETIF_F_RXCSUM;
+ ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
+ NETDEV_XDP_ACT_XSK_ZEROCOPY;
+
+ ret = stmmac_tc_init(priv, priv);
+ if (!ret) {
+ ndev->hw_features |= NETIF_F_HW_TC;
+ }
+
+ stmmac_set_gso_features(ndev);
+
+ if (priv->dma_cap.sphen &&
+ !(priv->plat->flags & STMMAC_FLAG_SPH_DISABLE)) {
+ ndev->hw_features |= NETIF_F_GRO;
+ priv->sph_capable = true;
+ priv->sph_active = priv->sph_capable;
+ dev_info(priv->device, "SPH feature enabled\n");
+ }
+
+ /* Ideally our host DMA address width is the same as for the
+ * device. However, it may differ and then we have to use our
+ * host DMA width for allocation and the device DMA width for
+ * register handling.
+ */
+ if (priv->plat->host_dma_width)
+ priv->dma_cap.host_dma_width = priv->plat->host_dma_width;
+ else
+ priv->dma_cap.host_dma_width = priv->dma_cap.addr64;
+
+ if (priv->dma_cap.host_dma_width) {
+ ret = dma_set_mask_and_coherent(device,
+ DMA_BIT_MASK(priv->dma_cap.host_dma_width));
+ if (!ret) {
+ dev_info(priv->device, "Using %d/%d bits DMA host/device width\n",
+ priv->dma_cap.host_dma_width, priv->dma_cap.addr64);
+
+ /*
+ * If more than 32 bits can be addressed, make sure to
+ * enable enhanced addressing mode.
+ */
+ if (IS_ENABLED(CONFIG_ARCH_DMA_ADDR_T_64BIT))
+ priv->plat->dma_cfg->eame = true;
+ } else {
+ ret = dma_set_mask_and_coherent(device, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(priv->device, "Failed to set DMA Mask\n");
+ goto error_hw_init;
+ }
+
+ priv->dma_cap.host_dma_width = 32;
+ }
+ }
+
+ ndev->features |= ndev->hw_features | NETIF_F_HIGHDMA;
+ ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
+#ifdef STMMAC_VLAN_TAG_USED
+ /* Both mac100 and gmac support receive VLAN tag detection */
+ ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX;
+ if (dwmac_is_xmac(priv->plat->core_type)) {
+ ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX;
+ priv->hw->hw_vlan_en = true;
+ }
+ if (priv->dma_cap.vlhash) {
+ ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+ ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER;
+ }
+ if (priv->dma_cap.vlins)
+ ndev->features |= NETIF_F_HW_VLAN_CTAG_TX;
+#endif
+ priv->msg_enable = netif_msg_init(debug, default_msg_level);
+
+ priv->xstats.threshold = tc;
+
+ /* Initialize RSS */
+ rxq = priv->plat->rx_queues_to_use;
+ netdev_rss_key_fill(priv->rss.key, sizeof(priv->rss.key));
+ for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++)
+ priv->rss.table[i] = ethtool_rxfh_indir_default(i, rxq);
+
+ if (priv->dma_cap.rssen && priv->plat->rss_en)
+ ndev->features |= NETIF_F_RXHASH;
+
+ ndev->vlan_features |= ndev->features;
+
+ /* MTU range: 46 - hw-specific max */
+ ndev->min_mtu = ETH_ZLEN - ETH_HLEN;
+
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC)
+ ndev->max_mtu = XGMAC_JUMBO_LEN;
+ else if (priv->plat->enh_desc || priv->synopsys_id >= DWMAC_CORE_4_00)
+ ndev->max_mtu = JUMBO_LEN;
+ else
+ ndev->max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);
+
+ /* Warn if the platform's maxmtu is smaller than the minimum MTU,
+ * otherwise clamp the maximum MTU above to the platform's maxmtu.
+ */
+ if (priv->plat->maxmtu < ndev->min_mtu)
+ dev_warn(priv->device,
+ "%s: warning: maxmtu having invalid value (%d)\n",
+ __func__, priv->plat->maxmtu);
+ else if (priv->plat->maxmtu < ndev->max_mtu)
+ ndev->max_mtu = priv->plat->maxmtu;
+
+ ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
+
+ /* Setup channels NAPI */
+ stmmac_napi_add(ndev);
+
+ mutex_init(&priv->lock);
+
+ stmmac_fpe_init(priv);
+
+ stmmac_check_pcs_mode(priv);
+
+ pm_runtime_get_noresume(device);
+ pm_runtime_set_active(device);
+ if (!pm_runtime_enabled(device))
+ pm_runtime_enable(device);
+
+ ret = stmmac_mdio_register(ndev);
+ if (ret < 0) {
+ dev_err_probe(priv->device, ret,
+ "MDIO bus (id: %d) registration failed\n",
+ priv->plat->bus_id);
+ goto error_mdio_register;
+ }
+
+ ret = stmmac_pcs_setup(ndev);
+ if (ret)
+ goto error_pcs_setup;
+
+ ret = stmmac_phylink_setup(priv);
+ if (ret) {
+ netdev_err(ndev, "failed to setup phy (%d)\n", ret);
+ goto error_phy_setup;
+ }
+
+ ret = stmmac_register_devlink(priv);
+ if (ret)
+ goto error_devlink_setup;
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ dev_err(priv->device, "%s: ERROR %i registering the device\n",
+ __func__, ret);
+ goto error_netdev_register;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ stmmac_init_fs(ndev);
+#endif
+
+ if (priv->plat->dump_debug_regs)
+ priv->plat->dump_debug_regs(priv->plat->bsp_priv);
+
+ /* Let pm_runtime_put() disable the clocks.
+ * If CONFIG_PM is not enabled, the clocks will stay powered.
+ */
+ pm_runtime_put(device);
+
+ return ret;
+
+error_netdev_register:
+ stmmac_unregister_devlink(priv);
+error_devlink_setup:
+ phylink_destroy(priv->phylink);
+error_phy_setup:
+ stmmac_pcs_clean(ndev);
+error_pcs_setup:
+ stmmac_mdio_unregister(ndev);
+error_mdio_register:
+ stmmac_napi_del(ndev);
+error_hw_init:
+ destroy_workqueue(priv->wq);
+error_wq_init:
+ bitmap_free(priv->af_xdp_zc_qps);
+
+ return ret;
+}
+
+/**
+ * stmmac_dvr_probe
+ * @dev: device pointer
+ * @plat_dat: platform data pointer
+ * @res: stmmac resource pointer
+ * Description: this is the main probe function used to
+ * call the alloc_etherdev, allocate the priv structure.
+ * Return:
+ * returns 0 on success, otherwise errno.
+ */
+int stmmac_dvr_probe(struct device *dev, struct plat_stmmacenet_data *plat_dat,
+ struct stmmac_resources *res)
+{
+ int ret;
+
+ if (plat_dat->init) {
+ ret = plat_dat->init(dev, plat_dat->bsp_priv);
+ if (ret)
+ return ret;
+ }
+
+ ret = __stmmac_dvr_probe(dev, plat_dat, res);
+ if (ret && plat_dat->exit)
+ plat_dat->exit(dev, plat_dat->bsp_priv);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stmmac_dvr_probe);
+
+/**
+ * stmmac_dvr_remove
+ * @dev: device pointer
+ * Description: this function resets the TX/RX processes, disables the MAC RX/TX
+ * changes the link status, releases the DMA descriptor rings.
+ */
+void stmmac_dvr_remove(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ netdev_info(priv->dev, "%s: removing driver", __func__);
+
+ pm_runtime_get_sync(dev);
+
+ unregister_netdev(ndev);
+
+#ifdef CONFIG_DEBUG_FS
+ stmmac_exit_fs(ndev);
+#endif
+ stmmac_unregister_devlink(priv);
+
+ phylink_destroy(priv->phylink);
+ if (priv->plat->stmmac_rst)
+ reset_control_assert(priv->plat->stmmac_rst);
+ reset_control_assert(priv->plat->stmmac_ahb_rst);
+
+ stmmac_pcs_clean(ndev);
+ stmmac_mdio_unregister(ndev);
+
+ destroy_workqueue(priv->wq);
+ mutex_destroy(&priv->lock);
+ bitmap_free(priv->af_xdp_zc_qps);
+
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+
+ if (priv->plat->exit)
+ priv->plat->exit(dev, priv->plat->bsp_priv);
+}
+EXPORT_SYMBOL_GPL(stmmac_dvr_remove);
+
+/**
+ * stmmac_suspend - suspend callback
+ * @dev: device pointer
+ * Description: this is the function to suspend the device and it is called
+ * by the platform driver to stop the network queue, release the resources,
+ * program the PMT register (for WoL), clean and release driver resources.
+ */
+int stmmac_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ u8 chan;
+
+ if (!ndev || !netif_running(ndev))
+ goto suspend_bsp;
+
+ mutex_lock(&priv->lock);
+
+ netif_device_detach(ndev);
+
+ stmmac_disable_all_queues(priv);
+
+ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++)
+ hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer);
+
+ if (priv->eee_sw_timer_en) {
+ priv->tx_path_in_lpi_mode = false;
+ timer_delete_sync(&priv->eee_ctrl_timer);
+ }
+
+ /* Stop TX/RX DMA */
+ stmmac_stop_all_dma(priv);
+
+ stmmac_legacy_serdes_power_down(priv);
+
+ /* Enable Power down mode by programming the PMT regs */
+ if (priv->wolopts) {
+ stmmac_pmt(priv, priv->hw, priv->wolopts);
+ priv->irq_wake = 1;
+ } else {
+ stmmac_mac_set(priv, priv->ioaddr, false);
+ pinctrl_pm_select_sleep_state(priv->device);
+ }
+
+ mutex_unlock(&priv->lock);
+
+ rtnl_lock();
+ phylink_suspend(priv->phylink, !!priv->wolopts);
+ rtnl_unlock();
+
+ if (stmmac_fpe_supported(priv))
+ ethtool_mmsv_stop(&priv->fpe_cfg.mmsv);
+
+suspend_bsp:
+ if (priv->plat->suspend)
+ return priv->plat->suspend(dev, priv->plat->bsp_priv);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(stmmac_suspend);
+
+static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+
+ rx_q->cur_rx = 0;
+ rx_q->dirty_rx = 0;
+}
+
+static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue)
+{
+ struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue];
+
+ tx_q->cur_tx = 0;
+ tx_q->dirty_tx = 0;
+ tx_q->mss = 0;
+
+ netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, queue));
+}
+
+/**
+ * stmmac_reset_queues_param - reset queue parameters
+ * @priv: device pointer
+ */
+static void stmmac_reset_queues_param(struct stmmac_priv *priv)
+{
+ u8 rx_cnt = priv->plat->rx_queues_to_use;
+ u8 tx_cnt = priv->plat->tx_queues_to_use;
+ u8 queue;
+
+ for (queue = 0; queue < rx_cnt; queue++)
+ stmmac_reset_rx_queue(priv, queue);
+
+ for (queue = 0; queue < tx_cnt; queue++)
+ stmmac_reset_tx_queue(priv, queue);
+}
+
+/**
+ * stmmac_resume - resume callback
+ * @dev: device pointer
+ * Description: when resume this function is invoked to setup the DMA and CORE
+ * in a usable state.
+ */
+int stmmac_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ if (priv->plat->resume) {
+ ret = priv->plat->resume(dev, priv->plat->bsp_priv);
+ if (ret)
+ return ret;
+ }
+
+ if (!netif_running(ndev))
+ return 0;
+
+ /* Power Down bit, into the PM register, is cleared
+ * automatically as soon as a magic packet or a Wake-up frame
+ * is received. Anyway, it's better to manually clear
+ * this bit because it can generate problems while resuming
+ * from another devices (e.g. serial console).
+ */
+ if (priv->wolopts) {
+ mutex_lock(&priv->lock);
+ stmmac_pmt(priv, priv->hw, 0);
+ mutex_unlock(&priv->lock);
+ priv->irq_wake = 0;
+ } else {
+ pinctrl_pm_select_default_state(priv->device);
+ /* reset the phy so that it's ready */
+ if (priv->mii)
+ stmmac_mdio_reset(priv->mii);
+ }
+
+ if (!(priv->plat->flags & STMMAC_FLAG_SERDES_UP_AFTER_PHY_LINKUP)) {
+ ret = stmmac_legacy_serdes_power_up(priv);
+ if (ret < 0)
+ return ret;
+ }
+
+ rtnl_lock();
+
+ /* Prepare the PHY to resume, ensuring that its clocks which are
+ * necessary for the MAC DMA reset to complete are running
+ */
+ phylink_prepare_resume(priv->phylink);
+
+ mutex_lock(&priv->lock);
+
+ stmmac_reset_queues_param(priv);
+
+ stmmac_free_tx_skbufs(priv);
+ stmmac_clear_descriptors(priv, &priv->dma_conf);
+
+ ret = stmmac_hw_setup(ndev);
+ if (ret < 0) {
+ netdev_err(priv->dev, "%s: Hw setup failed\n", __func__);
+ stmmac_legacy_serdes_power_down(priv);
+ mutex_unlock(&priv->lock);
+ rtnl_unlock();
+ return ret;
+ }
+
+ stmmac_init_timestamping(priv);
+
+ stmmac_init_coalesce(priv);
+ phylink_rx_clk_stop_block(priv->phylink);
+ stmmac_set_rx_mode(ndev);
+ phylink_rx_clk_stop_unblock(priv->phylink);
+
+ stmmac_vlan_restore(priv);
+
+ stmmac_enable_all_queues(priv);
+ stmmac_enable_all_dma_irq(priv);
+
+ mutex_unlock(&priv->lock);
+
+ /* phylink_resume() must be called after the hardware has been
+ * initialised because it may bring the link up immediately in a
+ * workqueue thread, which will race with initialisation.
+ */
+ phylink_resume(priv->phylink);
+ rtnl_unlock();
+
+ netif_device_attach(ndev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(stmmac_resume);
+
+/* This is not the same as EXPORT_GPL_SIMPLE_DEV_PM_OPS() when CONFIG_PM=n */
+DEFINE_SIMPLE_DEV_PM_OPS(stmmac_simple_pm_ops, stmmac_suspend, stmmac_resume);
+EXPORT_SYMBOL_GPL(stmmac_simple_pm_ops);
+
+#ifndef MODULE
+static int __init stmmac_cmdline_opt(char *str)
+{
+ char *opt;
+
+ if (!str || !*str)
+ return 1;
+ while ((opt = strsep(&str, ",")) != NULL) {
+ if (!strncmp(opt, "debug:", 6)) {
+ if (kstrtoint(opt + 6, 0, &debug))
+ goto err;
+ } else if (!strncmp(opt, "phyaddr:", 8)) {
+ if (kstrtoint(opt + 8, 0, &phyaddr))
+ goto err;
+ } else if (!strncmp(opt, "tc:", 3)) {
+ if (kstrtoint(opt + 3, 0, &tc))
+ goto err;
+ } else if (!strncmp(opt, "watchdog:", 9)) {
+ if (kstrtoint(opt + 9, 0, &watchdog))
+ goto err;
+ } else if (!strncmp(opt, "flow_ctrl:", 10)) {
+ if (kstrtoint(opt + 10, 0, &flow_ctrl))
+ goto err;
+ } else if (!strncmp(opt, "pause:", 6)) {
+ if (kstrtoint(opt + 6, 0, &pause))
+ goto err;
+ } else if (!strncmp(opt, "eee_timer:", 10)) {
+ if (kstrtoint(opt + 10, 0, &eee_timer))
+ goto err;
+ } else if (!strncmp(opt, "chain_mode:", 11)) {
+ if (kstrtoint(opt + 11, 0, &chain_mode))
+ goto err;
+ }
+ }
+ return 1;
+
+err:
+ pr_err("%s: ERROR broken module parameter conversion", __func__);
+ return 1;
+}
+
+__setup("stmmaceth=", stmmac_cmdline_opt);
+#endif /* MODULE */
+
+static int __init stmmac_init(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ /* Create debugfs main directory if it doesn't exist yet */
+ if (!stmmac_fs_dir)
+ stmmac_fs_dir = debugfs_create_dir(STMMAC_RESOURCE_NAME, NULL);
+ register_netdevice_notifier(&stmmac_notifier);
+#endif
+
+ return 0;
+}
+
+static void __exit stmmac_exit(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ unregister_netdevice_notifier(&stmmac_notifier);
+ debugfs_remove_recursive(stmmac_fs_dir);
+#endif
+}
+
+module_init(stmmac_init)
+module_exit(stmmac_exit)
+
+MODULE_DESCRIPTION("STMMAC 10/100/1000 Ethernet device driver");
+MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
new file mode 100644
index 000000000..afe98ff5b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
@@ -0,0 +1,751 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ STMMAC Ethernet Driver -- MDIO bus implementation
+ Provides Bus interface for MII registers
+
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Carl Shaw <carl.shaw@st.com>
+ Maintainer: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/gpio/consumer.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mii.h>
+#include <linux/of_mdio.h>
+#include <linux/pm_runtime.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include "dwxgmac2.h"
+#include "stmmac.h"
+
+#define MII_ADDR_GBUSY BIT(0)
+#define MII_ADDR_GWRITE BIT(1)
+#define MII_DATA_GD_MASK GENMASK(15, 0)
+
+/* GMAC4 defines */
+#define MII_GMAC4_GOC_SHIFT 2
+#define MII_GMAC4_REG_ADDR_SHIFT 16
+#define MII_GMAC4_WRITE (1 << MII_GMAC4_GOC_SHIFT)
+#define MII_GMAC4_READ (3 << MII_GMAC4_GOC_SHIFT)
+#define MII_GMAC4_C45E BIT(1)
+
+/* XGMAC defines */
+#define MII_XGMAC_SADDR BIT(18)
+#define MII_XGMAC_CMD_SHIFT 16
+#define MII_XGMAC_WRITE (1 << MII_XGMAC_CMD_SHIFT)
+#define MII_XGMAC_READ (3 << MII_XGMAC_CMD_SHIFT)
+#define MII_XGMAC_BUSY BIT(22)
+#define MII_XGMAC_MAX_C22ADDR 3
+#define MII_XGMAC_C22P_MASK GENMASK(MII_XGMAC_MAX_C22ADDR, 0)
+#define MII_XGMAC_PA_SHIFT 16
+#define MII_XGMAC_DA_SHIFT 21
+
+static int stmmac_mdio_wait(void __iomem *reg, u32 mask)
+{
+ u32 v;
+
+ if (readl_poll_timeout(reg, v, !(v & mask), 100, 10000))
+ return -EBUSY;
+
+ return 0;
+}
+
+static void stmmac_xgmac2_c45_format(struct stmmac_priv *priv, int phyaddr,
+ int devad, int phyreg, u32 *hw_addr)
+{
+ u32 tmp;
+
+ /* Set port as Clause 45 */
+ tmp = readl(priv->ioaddr + XGMAC_MDIO_C22P);
+ tmp &= ~BIT(phyaddr);
+ writel(tmp, priv->ioaddr + XGMAC_MDIO_C22P);
+
+ *hw_addr = (phyaddr << MII_XGMAC_PA_SHIFT) | (phyreg & 0xffff);
+ *hw_addr |= devad << MII_XGMAC_DA_SHIFT;
+}
+
+static void stmmac_xgmac2_c22_format(struct stmmac_priv *priv, int phyaddr,
+ int phyreg, u32 *hw_addr)
+{
+ u32 tmp = 0;
+
+ if (priv->synopsys_id < DWXGMAC_CORE_2_20) {
+ /* Until ver 2.20 XGMAC does not support C22 addr >= 4. Those
+ * bits above bit 3 of XGMAC_MDIO_C22P register are reserved.
+ */
+ tmp = readl(priv->ioaddr + XGMAC_MDIO_C22P);
+ tmp &= ~MII_XGMAC_C22P_MASK;
+ }
+ /* Set port as Clause 22 */
+ tmp |= BIT(phyaddr);
+ writel(tmp, priv->ioaddr + XGMAC_MDIO_C22P);
+
+ *hw_addr = (phyaddr << MII_XGMAC_PA_SHIFT) | (phyreg & 0x1f);
+}
+
+static int stmmac_xgmac2_mdio_read(struct stmmac_priv *priv, u32 addr,
+ u32 value)
+{
+ unsigned int mii_address = priv->hw->mii.addr;
+ unsigned int mii_data = priv->hw->mii.data;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+
+ /* Wait until any existing MII operation is complete */
+ ret = stmmac_mdio_wait(priv->ioaddr + mii_data, MII_XGMAC_BUSY);
+ if (ret)
+ goto err_disable_clks;
+
+ value |= priv->gmii_address_bus_config | MII_XGMAC_READ;
+
+ /* Wait until any existing MII operation is complete */
+ ret = stmmac_mdio_wait(priv->ioaddr + mii_data, MII_XGMAC_BUSY);
+ if (ret)
+ goto err_disable_clks;
+
+ /* Set the MII address register to read */
+ writel(addr, priv->ioaddr + mii_address);
+ writel(value, priv->ioaddr + mii_data);
+
+ /* Wait until any existing MII operation is complete */
+ ret = stmmac_mdio_wait(priv->ioaddr + mii_data, MII_XGMAC_BUSY);
+ if (ret)
+ goto err_disable_clks;
+
+ /* Read the data from the MII data register */
+ ret = (int)readl(priv->ioaddr + mii_data) & GENMASK(15, 0);
+
+err_disable_clks:
+ pm_runtime_put(priv->device);
+
+ return ret;
+}
+
+static int stmmac_xgmac2_mdio_read_c22(struct mii_bus *bus, int phyaddr,
+ int phyreg)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 addr;
+
+ /* Until ver 2.20 XGMAC does not support C22 addr >= 4 */
+ if (priv->synopsys_id < DWXGMAC_CORE_2_20 &&
+ phyaddr > MII_XGMAC_MAX_C22ADDR)
+ return -ENODEV;
+
+ stmmac_xgmac2_c22_format(priv, phyaddr, phyreg, &addr);
+
+ return stmmac_xgmac2_mdio_read(priv, addr, MII_XGMAC_BUSY);
+}
+
+static int stmmac_xgmac2_mdio_read_c45(struct mii_bus *bus, int phyaddr,
+ int devad, int phyreg)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 addr;
+
+ stmmac_xgmac2_c45_format(priv, phyaddr, devad, phyreg, &addr);
+
+ return stmmac_xgmac2_mdio_read(priv, addr, MII_XGMAC_BUSY);
+}
+
+static int stmmac_xgmac2_mdio_write(struct stmmac_priv *priv, u32 addr,
+ u32 value, u16 phydata)
+{
+ unsigned int mii_address = priv->hw->mii.addr;
+ unsigned int mii_data = priv->hw->mii.data;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+
+ /* Wait until any existing MII operation is complete */
+ ret = stmmac_mdio_wait(priv->ioaddr + mii_data, MII_XGMAC_BUSY);
+ if (ret)
+ goto err_disable_clks;
+
+ value |= priv->gmii_address_bus_config | phydata | MII_XGMAC_WRITE;
+
+ /* Wait until any existing MII operation is complete */
+ ret = stmmac_mdio_wait(priv->ioaddr + mii_data, MII_XGMAC_BUSY);
+ if (ret)
+ goto err_disable_clks;
+
+ /* Set the MII address register to write */
+ writel(addr, priv->ioaddr + mii_address);
+ writel(value, priv->ioaddr + mii_data);
+
+ /* Wait until any existing MII operation is complete */
+ ret = stmmac_mdio_wait(priv->ioaddr + mii_data, MII_XGMAC_BUSY);
+
+err_disable_clks:
+ pm_runtime_put(priv->device);
+
+ return ret;
+}
+
+static int stmmac_xgmac2_mdio_write_c22(struct mii_bus *bus, int phyaddr,
+ int phyreg, u16 phydata)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 addr;
+
+ /* Until ver 2.20 XGMAC does not support C22 addr >= 4 */
+ if (priv->synopsys_id < DWXGMAC_CORE_2_20 &&
+ phyaddr > MII_XGMAC_MAX_C22ADDR)
+ return -ENODEV;
+
+ stmmac_xgmac2_c22_format(priv, phyaddr, phyreg, &addr);
+
+ return stmmac_xgmac2_mdio_write(priv, addr,
+ MII_XGMAC_BUSY | MII_XGMAC_SADDR, phydata);
+}
+
+static int stmmac_xgmac2_mdio_write_c45(struct mii_bus *bus, int phyaddr,
+ int devad, int phyreg, u16 phydata)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 addr;
+
+ stmmac_xgmac2_c45_format(priv, phyaddr, devad, phyreg, &addr);
+
+ return stmmac_xgmac2_mdio_write(priv, addr, MII_XGMAC_BUSY,
+ phydata);
+}
+
+/**
+ * stmmac_mdio_format_addr() - format the address register
+ * @priv: struct stmmac_priv pointer
+ * @pa: 5-bit MDIO package address
+ * @gr: 5-bit MDIO register address (C22) or MDIO device address (C45)
+ *
+ * Return: formatted address register
+ */
+static u32 stmmac_mdio_format_addr(struct stmmac_priv *priv,
+ unsigned int pa, unsigned int gr)
+{
+ const struct mii_regs *mii_regs = &priv->hw->mii;
+
+ return field_prep(mii_regs->addr_mask, pa) |
+ field_prep(mii_regs->reg_mask, gr) |
+ priv->gmii_address_bus_config |
+ MII_ADDR_GBUSY;
+}
+
+static int stmmac_mdio_access(struct stmmac_priv *priv, unsigned int pa,
+ unsigned int gr, u32 cmd, u32 data, bool read)
+{
+ void __iomem *mii_address = priv->ioaddr + priv->hw->mii.addr;
+ void __iomem *mii_data = priv->ioaddr + priv->hw->mii.data;
+ u32 addr;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(priv->device);
+ if (ret < 0)
+ return ret;
+
+ ret = stmmac_mdio_wait(mii_address, MII_ADDR_GBUSY);
+ if (ret)
+ goto out;
+
+ addr = stmmac_mdio_format_addr(priv, pa, gr) | cmd;
+
+ writel(data, mii_data);
+ writel(addr, mii_address);
+
+ ret = stmmac_mdio_wait(mii_address, MII_ADDR_GBUSY);
+ if (ret)
+ goto out;
+
+ /* Read the data from the MII data register if in read mode */
+ ret = read ? readl(mii_data) & MII_DATA_GD_MASK : 0;
+
+out:
+ pm_runtime_put(priv->device);
+
+ return ret;
+}
+
+static int stmmac_mdio_read(struct stmmac_priv *priv, unsigned int pa,
+ unsigned int gr, u32 cmd, int data)
+{
+ return stmmac_mdio_access(priv, pa, gr, cmd, data, true);
+}
+
+static int stmmac_mdio_write(struct stmmac_priv *priv, unsigned int pa,
+ unsigned int gr, u32 cmd, int data)
+{
+ return stmmac_mdio_access(priv, pa, gr, cmd, data, false);
+}
+
+/**
+ * stmmac_mdio_read_c22
+ * @bus: points to the mii_bus structure
+ * @phyaddr: MII addr
+ * @phyreg: MII reg
+ * Description: it reads data from the MII register from within the phy device.
+ * For the 7111 GMAC, we must set the bit 0 in the MII address register while
+ * accessing the PHY registers.
+ * Fortunately, it seems this has no drawback for the 7109 MAC.
+ */
+static int stmmac_mdio_read_c22(struct mii_bus *bus, int phyaddr, int phyreg)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 cmd;
+
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4)
+ cmd = MII_GMAC4_READ;
+ else
+ cmd = 0;
+
+ return stmmac_mdio_read(priv, phyaddr, phyreg, cmd, 0);
+}
+
+/**
+ * stmmac_mdio_read_c45
+ * @bus: points to the mii_bus structure
+ * @phyaddr: MII addr
+ * @devad: device address to read
+ * @phyreg: MII reg
+ * Description: it reads data from the MII register from within the phy device.
+ * For the 7111 GMAC, we must set the bit 0 in the MII address register while
+ * accessing the PHY registers.
+ * Fortunately, it seems this has no drawback for the 7109 MAC.
+ */
+static int stmmac_mdio_read_c45(struct mii_bus *bus, int phyaddr, int devad,
+ int phyreg)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ int data = phyreg << MII_GMAC4_REG_ADDR_SHIFT;
+ u32 cmd = MII_GMAC4_READ | MII_GMAC4_C45E;
+
+ return stmmac_mdio_read(priv, phyaddr, devad, cmd, data);
+}
+
+/**
+ * stmmac_mdio_write_c22
+ * @bus: points to the mii_bus structure
+ * @phyaddr: MII addr
+ * @phyreg: MII reg
+ * @phydata: phy data
+ * Description: it writes the data into the MII register from within the device.
+ */
+static int stmmac_mdio_write_c22(struct mii_bus *bus, int phyaddr, int phyreg,
+ u16 phydata)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 cmd;
+
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4)
+ cmd = MII_GMAC4_WRITE;
+ else
+ cmd = MII_ADDR_GWRITE;
+
+ return stmmac_mdio_write(priv, phyaddr, phyreg, cmd, phydata);
+}
+
+/**
+ * stmmac_mdio_write_c45
+ * @bus: points to the mii_bus structure
+ * @phyaddr: MII addr
+ * @phyreg: MII reg
+ * @devad: device address to read
+ * @phydata: phy data
+ * Description: it writes the data into the MII register from within the device.
+ */
+static int stmmac_mdio_write_c45(struct mii_bus *bus, int phyaddr,
+ int devad, int phyreg, u16 phydata)
+{
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ u32 cmd = MII_GMAC4_WRITE | MII_GMAC4_C45E;
+ int data = phydata;
+
+ data |= phyreg << MII_GMAC4_REG_ADDR_SHIFT;
+
+ return stmmac_mdio_write(priv, phyaddr, devad, cmd, data);
+}
+
+/**
+ * stmmac_mdio_reset
+ * @bus: points to the mii_bus structure
+ * Description: reset the MII bus
+ */
+int stmmac_mdio_reset(struct mii_bus *bus)
+{
+#if IS_ENABLED(CONFIG_STMMAC_PLATFORM)
+ struct stmmac_priv *priv = netdev_priv(bus->priv);
+ unsigned int mii_address = priv->hw->mii.addr;
+
+#ifdef CONFIG_OF
+ if (priv->device->of_node) {
+ struct gpio_desc *reset_gpio;
+ u32 delays[3] = { 0, 0, 0 };
+
+ reset_gpio = devm_gpiod_get_optional(priv->device,
+ "snps,reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+
+ device_property_read_u32_array(priv->device,
+ "snps,reset-delays-us",
+ delays, ARRAY_SIZE(delays));
+
+ if (delays[0])
+ msleep(DIV_ROUND_UP(delays[0], 1000));
+
+ gpiod_set_value_cansleep(reset_gpio, 1);
+ if (delays[1])
+ msleep(DIV_ROUND_UP(delays[1], 1000));
+
+ gpiod_set_value_cansleep(reset_gpio, 0);
+ if (delays[2])
+ msleep(DIV_ROUND_UP(delays[2], 1000));
+ }
+#endif
+
+ /* This is a workaround for problems with the STE101P PHY.
+ * It doesn't complete its reset until at least one clock cycle
+ * on MDC, so perform a dummy mdio read. To be updated for GMAC4
+ * if needed.
+ */
+ if (priv->plat->core_type != DWMAC_CORE_GMAC4)
+ writel(0, priv->ioaddr + mii_address);
+#endif
+ return 0;
+}
+
+int stmmac_pcs_setup(struct net_device *ndev)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct fwnode_handle *devnode, *pcsnode;
+ struct dw_xpcs *xpcs = NULL;
+ int addr, ret;
+
+ devnode = dev_fwnode(priv->device);
+
+ if (priv->plat->pcs_init) {
+ ret = priv->plat->pcs_init(priv);
+ } else if (fwnode_property_present(devnode, "pcs-handle")) {
+ pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
+ xpcs = xpcs_create_fwnode(pcsnode);
+ fwnode_handle_put(pcsnode);
+ ret = PTR_ERR_OR_ZERO(xpcs);
+ } else if (priv->plat->mdio_bus_data &&
+ priv->plat->mdio_bus_data->pcs_mask) {
+ addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
+ xpcs = xpcs_create_mdiodev(priv->mii, addr);
+ ret = PTR_ERR_OR_ZERO(xpcs);
+ } else {
+ return 0;
+ }
+
+ if (ret)
+ return dev_err_probe(priv->device, ret, "No xPCS found\n");
+
+ if (xpcs)
+ xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
+
+ priv->hw->xpcs = xpcs;
+
+ return 0;
+}
+
+void stmmac_pcs_clean(struct net_device *ndev)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ if (priv->plat->pcs_exit)
+ priv->plat->pcs_exit(priv);
+
+ if (!priv->hw->xpcs)
+ return;
+
+ xpcs_destroy(priv->hw->xpcs);
+ priv->hw->xpcs = NULL;
+}
+
+struct stmmac_clk_rate {
+ unsigned long rate;
+ u8 cr;
+};
+
+/* The standard clk_csr_i to GMII_Address CR field mapping. The rate provided
+ * in this table is the exclusive maximum frequency for the divisor. The
+ * comments for each entry give the divisor and the resulting range of MDC
+ * clock frequencies.
+ */
+static const struct stmmac_clk_rate stmmac_std_csr_to_mdc[] = {
+ { CSR_F_800M, ~0 },
+ { CSR_F_500M, STMMAC_CSR_500_800M },
+ { CSR_F_300M, STMMAC_CSR_300_500M },
+ { CSR_F_250M, STMMAC_CSR_250_300M },
+ { CSR_F_150M, STMMAC_CSR_150_250M },
+ { CSR_F_100M, STMMAC_CSR_100_150M },
+ { CSR_F_60M, STMMAC_CSR_60_100M },
+ { CSR_F_35M, STMMAC_CSR_35_60M },
+ { CSR_F_20M, STMMAC_CSR_20_35M },
+ { 0, ~0 },
+};
+
+/* The sun8i clk_csr_i to GMII_Address CR field mapping uses rate as the
+ * exclusive minimum frequency for the divisor. Note that the last entry
+ * is valid and also acts as the sentinel.
+ */
+static const struct stmmac_clk_rate stmmac_sun8i_csr_to_mdc[] = {
+ { 160000000, 3 },
+ { 80000000, 2 },
+ { 40000000, 1 },
+ { 0, 0 },
+};
+
+/* The xgmac clk_csr_i to GMII_Address CR field mapping similarly uses rate
+ * as the exclusive minimum frequency for the divisor, and again the last
+ * entry is valid and also the sentinel.
+ */
+static const struct stmmac_clk_rate stmmac_xgmac_csr_to_mdc[] = {
+ { 400000000, 5 },
+ { 350000000, 4 },
+ { 300000000, 3 },
+ { 250000000, 2 },
+ { 150000000, 1 },
+ { 0, 0 },
+};
+
+/**
+ * stmmac_clk_csr_set - dynamically set the MDC clock
+ * @priv: driver private structure
+ * Description: this is to dynamically set the MDC clock according to the csr
+ * clock input.
+ * Return: MII register CR field value
+ * Note:
+ * If a specific clk_csr value is passed from the platform
+ * this means that the CSR Clock Range selection cannot be
+ * changed at run-time and it is fixed (as reported in the driver
+ * documentation). Vice versa the driver will try to set the MDC
+ * clock dynamically according to the actual clock input.
+ */
+static u32 stmmac_clk_csr_set(struct stmmac_priv *priv)
+{
+ const struct stmmac_clk_rate *rates;
+ unsigned long clk_rate;
+ u32 value = ~0;
+ int i;
+
+ clk_rate = clk_get_rate(priv->plat->stmmac_clk);
+
+ /* Platform provided default clk_csr would be assumed valid
+ * for all other cases except for the below mentioned ones.
+ * For values higher than the IEEE 802.3 specified frequency
+ * we can not estimate the proper divider as it is not known
+ * the frequency of clk_csr_i. So we do not change the default
+ * divider.
+ */
+ rates = stmmac_std_csr_to_mdc;
+ if (priv->plat->flags & STMMAC_FLAG_HAS_SUN8I)
+ rates = stmmac_sun8i_csr_to_mdc;
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC)
+ rates = stmmac_xgmac_csr_to_mdc;
+
+ for (i = 0; rates[i].rate; i++)
+ if (clk_rate > rates[i].rate)
+ break;
+ if (rates[i].cr != (u8)~0)
+ value = rates[i].cr;
+
+ return value;
+}
+
+static void stmmac_mdio_bus_config(struct stmmac_priv *priv)
+{
+ u32 value;
+
+ /* If a specific clk_csr value is passed from the platform, this means
+ * that the CSR Clock Range value should not be computed from the CSR
+ * clock.
+ */
+ if (priv->plat->clk_csr >= 0)
+ value = priv->plat->clk_csr;
+ else
+ value = stmmac_clk_csr_set(priv);
+
+ value <<= __ffs(priv->hw->mii.clk_csr_mask);
+
+ if (value & ~priv->hw->mii.clk_csr_mask)
+ dev_warn(priv->device,
+ "clk_csr value out of range (0x%08x exceeds mask 0x%08x), truncating\n",
+ value, priv->hw->mii.clk_csr_mask);
+
+ priv->gmii_address_bus_config = value & priv->hw->mii.clk_csr_mask;
+}
+
+/**
+ * stmmac_mdio_register
+ * @ndev: net device structure
+ * Description: it registers the MII bus
+ */
+int stmmac_mdio_register(struct net_device *ndev)
+{
+ int err = 0;
+ struct mii_bus *new_bus;
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct stmmac_mdio_bus_data *mdio_bus_data = priv->plat->mdio_bus_data;
+ struct device_node *mdio_node = priv->plat->mdio_node;
+ struct device *dev = ndev->dev.parent;
+ struct fwnode_handle *fixed_node;
+ int max_addr = PHY_MAX_ADDR - 1;
+ struct fwnode_handle *fwnode;
+ struct phy_device *phydev;
+
+ if (!mdio_bus_data)
+ return 0;
+
+ stmmac_mdio_bus_config(priv);
+
+ new_bus = mdiobus_alloc();
+ if (!new_bus)
+ return -ENOMEM;
+
+ if (mdio_bus_data->irqs)
+ memcpy(new_bus->irq, mdio_bus_data->irqs, sizeof(new_bus->irq));
+
+ new_bus->name = "stmmac";
+
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC) {
+ new_bus->read = &stmmac_xgmac2_mdio_read_c22;
+ new_bus->write = &stmmac_xgmac2_mdio_write_c22;
+ new_bus->read_c45 = &stmmac_xgmac2_mdio_read_c45;
+ new_bus->write_c45 = &stmmac_xgmac2_mdio_write_c45;
+
+ if (priv->synopsys_id < DWXGMAC_CORE_2_20) {
+ /* Right now only C22 phys are supported */
+ max_addr = MII_XGMAC_MAX_C22ADDR;
+
+ /* Check if DT specified an unsupported phy addr */
+ if (priv->plat->phy_addr > MII_XGMAC_MAX_C22ADDR)
+ dev_err(dev, "Unsupported phy_addr (max=%d)\n",
+ MII_XGMAC_MAX_C22ADDR);
+ }
+ } else {
+ new_bus->read = &stmmac_mdio_read_c22;
+ new_bus->write = &stmmac_mdio_write_c22;
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4) {
+ new_bus->read_c45 = &stmmac_mdio_read_c45;
+ new_bus->write_c45 = &stmmac_mdio_write_c45;
+ }
+ }
+
+ if (mdio_bus_data->needs_reset)
+ new_bus->reset = &stmmac_mdio_reset;
+
+ snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x",
+ new_bus->name, priv->plat->bus_id);
+ new_bus->priv = ndev;
+ new_bus->phy_mask = mdio_bus_data->phy_mask | mdio_bus_data->pcs_mask;
+ new_bus->parent = priv->device;
+
+ err = of_mdiobus_register(new_bus, mdio_node);
+ if (err == -ENODEV) {
+ err = 0;
+ dev_info(dev, "MDIO bus is disabled\n");
+ goto bus_register_fail;
+ } else if (err) {
+ dev_err_probe(dev, err, "Cannot register the MDIO bus\n");
+ goto bus_register_fail;
+ }
+
+ /* Looks like we need a dummy read for XGMAC only and C45 PHYs */
+ if (priv->plat->core_type == DWMAC_CORE_XGMAC)
+ stmmac_xgmac2_mdio_read_c45(new_bus, 0, 0, 0);
+
+ /* If fixed-link is set, skip PHY scanning */
+ fwnode = dev_fwnode(priv->device);
+ if (fwnode) {
+ fixed_node = fwnode_get_named_child_node(fwnode, "fixed-link");
+ if (fixed_node) {
+ fwnode_handle_put(fixed_node);
+ goto bus_register_done;
+ }
+ }
+
+ if (priv->plat->phy_node || mdio_node)
+ goto bus_register_done;
+
+ phydev = phy_find_first(new_bus);
+ if (!phydev || phydev->mdio.addr > max_addr) {
+ dev_warn(dev, "No PHY found\n");
+ err = -ENODEV;
+ goto no_phy_found;
+ }
+
+ /*
+ * If an IRQ was provided to be assigned after
+ * the bus probe, do it here.
+ */
+ if (!mdio_bus_data->irqs && mdio_bus_data->probed_phy_irq > 0) {
+ new_bus->irq[phydev->mdio.addr] = mdio_bus_data->probed_phy_irq;
+ phydev->irq = mdio_bus_data->probed_phy_irq;
+ }
+
+ /*
+ * If we're going to bind the MAC to this PHY bus, and no PHY number
+ * was provided to the MAC, use the one probed here.
+ */
+ if (priv->plat->phy_addr == -1)
+ priv->plat->phy_addr = phydev->mdio.addr;
+
+ phy_attached_info(phydev);
+
+bus_register_done:
+ priv->mii = new_bus;
+
+ return 0;
+
+no_phy_found:
+ mdiobus_unregister(new_bus);
+bus_register_fail:
+ mdiobus_free(new_bus);
+ return err;
+}
+
+/**
+ * stmmac_mdio_unregister
+ * @ndev: net device structure
+ * Description: it unregisters the MII bus
+ */
+int stmmac_mdio_unregister(struct net_device *ndev)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ if (!priv->mii)
+ return 0;
+
+ mdiobus_unregister(priv->mii);
+ priv->mii->priv = NULL;
+ mdiobus_free(priv->mii);
+ priv->mii = NULL;
+
+ return 0;
+}
+
+void stmmac_mdio_lock(struct stmmac_priv *priv)
+{
+ if (priv->mii)
+ mutex_lock(&priv->mii->mdio_lock);
+}
+EXPORT_SYMBOL_GPL(stmmac_mdio_lock);
+
+void stmmac_mdio_unlock(struct stmmac_priv *priv)
+{
+ if (priv->mii)
+ mutex_unlock(&priv->mii->mdio_lock);
+}
+EXPORT_SYMBOL_GPL(stmmac_mdio_unlock);
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c
new file mode 100644
index 000000000..d584fd2da
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This contains the functions to handle the pci driver.
+
+ Copyright (C) 2011-2012 Vayavya Labs Pvt Ltd
+
+
+ Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/clk-provider.h>
+#include <linux/pci.h>
+#include <linux/dmi.h>
+
+#include "stmmac.h"
+#include "stmmac_libpci.h"
+
+struct stmmac_pci_info {
+ int (*setup)(struct pci_dev *pdev, struct plat_stmmacenet_data *plat);
+};
+
+static void common_default_data(struct plat_stmmacenet_data *plat)
+{
+ /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */
+ plat->clk_csr = STMMAC_CSR_20_35M;
+ plat->core_type = DWMAC_CORE_GMAC;
+ plat->force_sf_dma_mode = true;
+
+ plat->mdio_bus_data->needs_reset = true;
+}
+
+static int stmmac_default_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ /* Set common default data first */
+ common_default_data(plat);
+
+ plat->bus_id = 1;
+ plat->phy_addr = 0;
+ plat->phy_interface = PHY_INTERFACE_MODE_GMII;
+
+ plat->dma_cfg->pbl = 32;
+ plat->dma_cfg->pblx8 = true;
+ /* TODO: AXI */
+
+ return 0;
+}
+
+static const struct stmmac_pci_info stmmac_pci_info = {
+ .setup = stmmac_default_data,
+};
+
+static int snps_gmac5_default_data(struct pci_dev *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ int i;
+
+ plat->clk_csr = STMMAC_CSR_250_300M;
+ plat->core_type = DWMAC_CORE_GMAC4;
+ plat->force_sf_dma_mode = true;
+ plat->flags |= STMMAC_FLAG_TSO_EN;
+ plat->pmt = true;
+
+ /* Set default number of RX and TX queues to use */
+ plat->tx_queues_to_use = 4;
+ plat->rx_queues_to_use = 4;
+
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR;
+ for (i = 0; i < plat->tx_queues_to_use; i++) {
+ plat->tx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+ plat->tx_queues_cfg[i].weight = 25;
+ if (i > 0)
+ plat->tx_queues_cfg[i].tbs_en = 1;
+ }
+
+ plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
+ for (i = 0; i < plat->rx_queues_to_use; i++)
+ plat->rx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+ plat->bus_id = 1;
+ plat->phy_interface = PHY_INTERFACE_MODE_GMII;
+
+ plat->dma_cfg->pbl = 32;
+ plat->dma_cfg->pblx8 = true;
+
+ /* Axi Configuration */
+ plat->axi = devm_kzalloc(&pdev->dev, sizeof(*plat->axi), GFP_KERNEL);
+ if (!plat->axi)
+ return -ENOMEM;
+
+ plat->axi->axi_wr_osr_lmt = 31;
+ plat->axi->axi_rd_osr_lmt = 31;
+
+ plat->axi->axi_fb = false;
+ plat->axi->axi_blen_regval = DMA_AXI_BLEN4 | DMA_AXI_BLEN8 |
+ DMA_AXI_BLEN16 | DMA_AXI_BLEN32;
+
+ return 0;
+}
+
+static const struct stmmac_pci_info snps_gmac5_pci_info = {
+ .setup = snps_gmac5_default_data,
+};
+
+/**
+ * stmmac_pci_probe
+ *
+ * @pdev: pci device pointer
+ * @id: pointer to table of device id/id's.
+ *
+ * Description: This probing function gets called for all PCI devices which
+ * match the ID table and are not "owned" by other driver yet. This function
+ * gets passed a "struct pci_dev *" for each device whose entry in the ID table
+ * matches the device. The probe functions returns zero when the driver choose
+ * to take "ownership" of the device or an error code(-ve no) otherwise.
+ */
+static int stmmac_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct stmmac_pci_info *info = (struct stmmac_pci_info *)id->driver_data;
+ struct plat_stmmacenet_data *plat;
+ struct stmmac_resources res = {};
+ int ret;
+ int i;
+
+ plat = stmmac_plat_dat_alloc(&pdev->dev);
+ if (!plat)
+ return -ENOMEM;
+
+ plat->mdio_bus_data = devm_kzalloc(&pdev->dev,
+ sizeof(*plat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat->mdio_bus_data)
+ return -ENOMEM;
+
+ plat->safety_feat_cfg = devm_kzalloc(&pdev->dev,
+ sizeof(*plat->safety_feat_cfg),
+ GFP_KERNEL);
+ if (!plat->safety_feat_cfg)
+ return -ENOMEM;
+
+ /* Enable pci device */
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: ERROR: failed to enable device\n",
+ __func__);
+ return ret;
+ }
+
+ /* Get the base address of device */
+ for (i = 0; i < PCI_STD_NUM_BARS; i++) {
+ if (pci_resource_len(pdev, i) == 0)
+ continue;
+ res.addr = pcim_iomap_region(pdev, i, STMMAC_RESOURCE_NAME);
+ if (IS_ERR(res.addr))
+ return PTR_ERR(res.addr);
+ break;
+ }
+
+ pci_set_master(pdev);
+
+ ret = info->setup(pdev, plat);
+ if (ret)
+ return ret;
+
+ res.wol_irq = pdev->irq;
+ res.irq = pdev->irq;
+
+ plat->safety_feat_cfg->tsoee = 1;
+ plat->safety_feat_cfg->mrxpee = 1;
+ plat->safety_feat_cfg->mestee = 1;
+ plat->safety_feat_cfg->mrxee = 1;
+ plat->safety_feat_cfg->mtxee = 1;
+ plat->safety_feat_cfg->epsi = 1;
+ plat->safety_feat_cfg->edpp = 1;
+ plat->safety_feat_cfg->prtyen = 1;
+ plat->safety_feat_cfg->tmouten = 1;
+
+ plat->suspend = stmmac_pci_plat_suspend;
+ plat->resume = stmmac_pci_plat_resume;
+
+ return stmmac_dvr_probe(&pdev->dev, plat, &res);
+}
+
+/**
+ * stmmac_pci_remove
+ *
+ * @pdev: platform device pointer
+ * Description: this function calls the main to free the net resources.
+ */
+static void stmmac_pci_remove(struct pci_dev *pdev)
+{
+ stmmac_dvr_remove(&pdev->dev);
+}
+
+/* synthetic ID, no official vendor */
+#define PCI_VENDOR_ID_STMMAC 0x0700
+
+#define PCI_DEVICE_ID_STMMAC_STMMAC 0x1108
+#define PCI_DEVICE_ID_SYNOPSYS_GMAC5_ID 0x7102
+
+static const struct pci_device_id stmmac_id_table[] = {
+ { PCI_DEVICE_DATA(STMMAC, STMMAC, &stmmac_pci_info) },
+ { PCI_DEVICE_DATA(STMICRO, MAC, &stmmac_pci_info) },
+ { PCI_DEVICE_DATA(SYNOPSYS, GMAC5_ID, &snps_gmac5_pci_info) },
+ {}
+};
+
+MODULE_DEVICE_TABLE(pci, stmmac_id_table);
+
+static struct pci_driver stmmac_pci_driver = {
+ .name = STMMAC_RESOURCE_NAME,
+ .id_table = stmmac_id_table,
+ .probe = stmmac_pci_probe,
+ .remove = stmmac_pci_remove,
+ .driver = {
+ .pm = &stmmac_simple_pm_ops,
+ },
+};
+
+module_pci_driver(stmmac_pci_driver);
+
+MODULE_DESCRIPTION("STMMAC 10/100/1000 Ethernet PCI driver");
+MODULE_AUTHOR("Rayagond Kokatanur <rayagond.kokatanur@vayavyalabs.com>");
+MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c
new file mode 100644
index 000000000..df37af5ab
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include "stmmac.h"
+#include "stmmac_pcs.h"
+
+/*
+ * GMAC_AN_STATUS is equivalent to MII_BMSR
+ * GMAC_ANE_ADV is equivalent to 802.3z MII_ADVERTISE
+ * GMAC_ANE_LPA is equivalent to 802.3z MII_LPA
+ * GMAC_ANE_EXP is equivalent to MII_EXPANSION
+ * GMAC_TBI is equivalent to MII_ESTATUS
+ *
+ * ADV, LPA and EXP are only available for the TBI and RTBI modes.
+ */
+#define GMAC_AN_STATUS 0x04 /* AN status */
+#define GMAC_ANE_ADV 0x08 /* ANE Advertisement */
+#define GMAC_ANE_LPA 0x0c /* ANE link partener ability */
+#define GMAC_TBI 0x14 /* TBI extend status */
+
+/*
+ * RGSMII status bitfield definitions.
+ */
+#define GMAC_RGSMII_LNKMOD BIT(0)
+#define GMAC_RGSMII_SPEED_MASK GENMASK(2, 1)
+#define GMAC_RGSMII_SPEED_125 2
+#define GMAC_RGSMII_SPEED_25 1
+#define GMAC_RGSMII_SPEED_2_5 0
+#define GMAC_RGSMII_LNKSTS BIT(3)
+
+static unsigned int dwmac_integrated_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+ unsigned int ib_caps;
+
+ if (phy_interface_mode_is_8023z(interface)) {
+ ib_caps = LINK_INBAND_DISABLE;
+
+ /* If the PCS supports TBI/RTBI, then BASE-X negotiation is
+ * supported.
+ */
+ if (spcs->support_tbi_rtbi)
+ ib_caps |= LINK_INBAND_ENABLE;
+
+ return ib_caps;
+ }
+
+ return 0;
+}
+
+static int dwmac_integrated_pcs_enable(struct phylink_pcs *pcs)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+
+ stmmac_mac_irq_modify(spcs->priv, 0, spcs->int_mask);
+
+ return 0;
+}
+
+static void dwmac_integrated_pcs_disable(struct phylink_pcs *pcs)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+
+ stmmac_mac_irq_modify(spcs->priv, spcs->int_mask, 0);
+}
+
+static void dwmac_integrated_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+ u32 status, lpa, rgsmii;
+
+ status = readl(spcs->base + GMAC_AN_STATUS);
+
+ if (phy_interface_mode_is_8023z(state->interface)) {
+ /* For BASE-X modes, the PCS block supports the advertisement
+ * and link partner advertisement registers using standard
+ * 802.3 format. The status register also has the link status
+ * and AN complete bits in the same bit location. This will
+ * only be used when AN is enabled.
+ */
+ lpa = readl(spcs->base + GMAC_ANE_LPA);
+
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, status, lpa);
+ } else {
+ rgsmii = field_get(spcs->rgsmii_status_mask,
+ readl(spcs->rgsmii));
+
+ state->link = status & BMSR_LSTATUS &&
+ rgsmii & GMAC_RGSMII_LNKSTS;
+
+ if (state->link && neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+ state->duplex = rgsmii & GMAC_RGSMII_LNKMOD ?
+ DUPLEX_FULL : DUPLEX_HALF;
+ switch (FIELD_GET(GMAC_RGSMII_SPEED_MASK, rgsmii)) {
+ case GMAC_RGSMII_SPEED_2_5:
+ state->speed = SPEED_10;
+ break;
+
+ case GMAC_RGSMII_SPEED_25:
+ state->speed = SPEED_100;
+ break;
+
+ case GMAC_RGSMII_SPEED_125:
+ state->speed = SPEED_1000;
+ break;
+
+ default:
+ state->link = false;
+ break;
+ }
+ }
+ }
+}
+
+static int dwmac_integrated_pcs_config_aneg(struct stmmac_pcs *spcs,
+ phy_interface_t interface,
+ const unsigned long *advertising)
+{
+ bool changed = false;
+ u32 adv;
+
+ adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
+ if (readl(spcs->base + GMAC_ANE_ADV) != adv)
+ changed = true;
+ writel(adv, spcs->base + GMAC_ANE_ADV);
+
+ return changed;
+}
+
+static int dwmac_integrated_pcs_config(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+ bool changed = false, ane = true;
+
+ /* Only configure the advertisement and allow AN in BASE-X mode if
+ * the core supports TBI/RTBI. AN will be filtered out by via phylink
+ * and the .pcs_inband_caps() method above.
+ */
+ if (phy_interface_mode_is_8023z(interface) &&
+ spcs->support_tbi_rtbi) {
+ ane = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED;
+
+ changed = dwmac_integrated_pcs_config_aneg(spcs, interface,
+ advertising);
+ }
+
+ dwmac_ctrl_ane(spcs->base, 0, ane,
+ spcs->priv->hw->reverse_sgmii_enable);
+
+ return changed;
+}
+
+static void dwmac_integrated_pcs_an_restart(struct phylink_pcs *pcs)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+ void __iomem *an_control = spcs->base + GMAC_AN_CTRL(0);
+ u32 ctrl;
+
+ /* We can only do AN restart if using TBI/RTBI mode */
+ if (spcs->support_tbi_rtbi) {
+ ctrl = readl(an_control) | GMAC_AN_CTRL_RAN;
+ writel(ctrl, an_control);
+ }
+}
+
+static const struct phylink_pcs_ops dwmac_integrated_pcs_ops = {
+ .pcs_inband_caps = dwmac_integrated_pcs_inband_caps,
+ .pcs_enable = dwmac_integrated_pcs_enable,
+ .pcs_disable = dwmac_integrated_pcs_disable,
+ .pcs_get_state = dwmac_integrated_pcs_get_state,
+ .pcs_config = dwmac_integrated_pcs_config,
+ .pcs_an_restart = dwmac_integrated_pcs_an_restart,
+};
+
+void stmmac_integrated_pcs_irq(struct stmmac_priv *priv, u32 status,
+ struct stmmac_extra_stats *x)
+{
+ struct stmmac_pcs *spcs = priv->integrated_pcs;
+ u32 val = readl(spcs->base + GMAC_AN_STATUS);
+
+ if (status & PCS_ANE_IRQ) {
+ x->irq_pcs_ane_n++;
+ if (val & BMSR_ANEGCOMPLETE)
+ dev_info(priv->device,
+ "PCS ANE process completed\n");
+ }
+
+ if (status & PCS_LINK_IRQ) {
+ x->irq_pcs_link_n++;
+ dev_info(priv->device, "PCS Link %s\n",
+ val & BMSR_LSTATUS ? "Up" : "Down");
+
+ phylink_pcs_change(&spcs->pcs, val & BMSR_LSTATUS);
+ }
+}
+
+int stmmac_integrated_pcs_get_phy_intf_sel(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs);
+
+ if (interface == PHY_INTERFACE_MODE_SGMII)
+ return PHY_INTF_SEL_SGMII;
+
+ if (phy_interface_mode_is_8023z(interface)) {
+ if (spcs->support_tbi_rtbi)
+ return PHY_INTF_SEL_TBI;
+ else
+ return PHY_INTF_SEL_SGMII;
+ }
+
+ return -EINVAL;
+}
+
+int stmmac_integrated_pcs_init(struct stmmac_priv *priv,
+ const struct stmmac_pcs_info *pcs_info)
+{
+ struct stmmac_pcs *spcs;
+
+ spcs = devm_kzalloc(priv->device, sizeof(*spcs), GFP_KERNEL);
+ if (!spcs)
+ return -ENOMEM;
+
+ spcs->priv = priv;
+ spcs->base = priv->ioaddr + pcs_info->pcs_offset;
+ spcs->rgsmii = priv->ioaddr + pcs_info->rgsmii_offset;
+ spcs->rgsmii_status_mask = pcs_info->rgsmii_status_mask;
+ spcs->int_mask = pcs_info->int_mask;
+ spcs->pcs.ops = &dwmac_integrated_pcs_ops;
+
+ /* If the PCS supports extended status, then it supports BASE-X AN
+ * with a TBI interface to the SerDes. Otherwise, we can support
+ * BASE-X without AN using SGMII, which is required for qcom-ethqos.
+ */
+ if (readl(spcs->base + GMAC_AN_STATUS) & BMSR_ESTATEN)
+ spcs->support_tbi_rtbi = true;
+
+ __set_bit(PHY_INTERFACE_MODE_SGMII, spcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, spcs->pcs.supported_interfaces);
+
+ /* Only allow 2500BASE-X if the SerDes has support. */
+ if (priv->plat->flags & STMMAC_FLAG_SERDES_SUPPORTS_2500M)
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ spcs->pcs.supported_interfaces);
+
+ priv->integrated_pcs = spcs;
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h
new file mode 100644
index 000000000..b2b12d34b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * stmmac_pcs.h: Physical Coding Sublayer Header File
+ *
+ * Copyright (C) 2016 STMicroelectronics (R&D) Limited
+ * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ */
+
+#ifndef __STMMAC_PCS_H__
+#define __STMMAC_PCS_H__
+
+#include <linux/phylink.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include "common.h"
+
+/* PCS registers (AN/TBI/SGMII/RGMII) offsets */
+#define GMAC_AN_CTRL(x) (x) /* AN control */
+
+/* AN Configuration defines */
+#define GMAC_AN_CTRL_RAN BIT_U32(9) /* Restart Auto-Negotiation */
+#define GMAC_AN_CTRL_ANE BIT_U32(12) /* Auto-Negotiation Enable */
+#define GMAC_AN_CTRL_ELE BIT_U32(14) /* External Loopback Enable */
+#define GMAC_AN_CTRL_ECD BIT_U32(16) /* Enable Comma Detect */
+#define GMAC_AN_CTRL_LR BIT_U32(17) /* Lock to Reference */
+#define GMAC_AN_CTRL_SGMRAL BIT_U32(18) /* SGMII RAL Control */
+
+struct stmmac_priv;
+
+struct stmmac_pcs_info {
+ unsigned int pcs_offset;
+ unsigned int rgsmii_offset;
+ u32 rgsmii_status_mask;
+ u32 int_mask;
+};
+
+struct stmmac_pcs {
+ struct stmmac_priv *priv;
+ void __iomem *base;
+ void __iomem *rgsmii;
+ u32 rgsmii_status_mask;
+ u32 int_mask;
+ struct phylink_pcs pcs;
+ bool support_tbi_rtbi;
+};
+
+static inline struct stmmac_pcs *
+phylink_pcs_to_stmmac_pcs(struct phylink_pcs *pcs)
+{
+ return container_of(pcs, struct stmmac_pcs, pcs);
+}
+
+void stmmac_integrated_pcs_irq(struct stmmac_priv *priv, u32 status,
+ struct stmmac_extra_stats *x);
+int stmmac_integrated_pcs_get_phy_intf_sel(struct phylink_pcs *pcs,
+ phy_interface_t interface);
+int stmmac_integrated_pcs_init(struct stmmac_priv *priv,
+ const struct stmmac_pcs_info *pcs_info);
+
+/**
+ * dwmac_ctrl_ane - To program the AN Control Register.
+ * @ioaddr: IO registers pointer
+ * @reg: Base address of the AN Control Register.
+ * @ane: to enable the auto-negotiation
+ * @srgmi_ral: to manage MAC-2-MAC SGMII connections.
+ * Description: this is the main function to configure the AN control register
+ * and init the ANE, select loopback (usually for debugging purpose) and
+ * configure SGMII RAL.
+ */
+static inline void dwmac_ctrl_ane(void __iomem *ioaddr, u32 reg, bool ane,
+ bool srgmi_ral)
+{
+ u32 value = readl(ioaddr + GMAC_AN_CTRL(reg));
+
+ /* Enable and restart the Auto-Negotiation */
+ if (ane)
+ value |= GMAC_AN_CTRL_ANE | GMAC_AN_CTRL_RAN;
+ else
+ value &= ~GMAC_AN_CTRL_ANE;
+
+ /* In case of MAC-2-MAC connection, block is configured to operate
+ * according to MAC conf register.
+ */
+ if (srgmi_ral)
+ value |= GMAC_AN_CTRL_SGMRAL;
+
+ writel(value, ioaddr + GMAC_AN_CTRL(reg));
+}
+#endif /* __STMMAC_PCS_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
new file mode 100644
index 000000000..dc5f951a3
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
@@ -0,0 +1,1010 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ This contains the functions to handle the platform driver.
+
+ Copyright (C) 2007-2011 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+
+#ifdef CONFIG_OF
+
+/**
+ * dwmac1000_validate_mcast_bins - validates the number of Multicast filter bins
+ * @dev: struct device of the platform device
+ * @mcast_bins: Multicast filtering bins
+ * Description:
+ * this function validates the number of Multicast filtering bins specified
+ * by the configuration through the device tree. The Synopsys GMAC supports
+ * 64 bins, 128 bins, or 256 bins. "bins" refer to the division of CRC
+ * number space. 64 bins correspond to 6 bits of the CRC, 128 corresponds
+ * to 7 bits, and 256 refers to 8 bits of the CRC. Any other setting is
+ * invalid and will cause the filtering algorithm to use Multicast
+ * promiscuous mode.
+ */
+static int dwmac1000_validate_mcast_bins(struct device *dev, int mcast_bins)
+{
+ int x = mcast_bins;
+
+ switch (x) {
+ case HASH_TABLE_SIZE:
+ case 128:
+ case 256:
+ break;
+ default:
+ x = 0;
+ dev_info(dev, "Hash table entries set to unexpected value %d\n",
+ mcast_bins);
+ break;
+ }
+ return x;
+}
+
+/**
+ * dwmac1000_validate_ucast_entries - validate the Unicast address entries
+ * @dev: struct device of the platform device
+ * @ucast_entries: number of Unicast address entries
+ * Description:
+ * This function validates the number of Unicast address entries supported
+ * by a particular Synopsys 10/100/1000 controller. The Synopsys controller
+ * supports 1..32, 64, or 128 Unicast filter entries for its Unicast filter
+ * logic. This function validates a valid, supported configuration is
+ * selected, and defaults to 1 Unicast address if an unsupported
+ * configuration is selected.
+ */
+static int dwmac1000_validate_ucast_entries(struct device *dev,
+ int ucast_entries)
+{
+ int x = ucast_entries;
+
+ switch (x) {
+ case 1 ... 32:
+ case 64:
+ case 128:
+ break;
+ default:
+ x = 1;
+ dev_info(dev, "Unicast table entries set to unexpected value %d\n",
+ ucast_entries);
+ break;
+ }
+ return x;
+}
+
+/**
+ * stmmac_axi_setup - parse DT parameters for programming the AXI register
+ * @pdev: platform device
+ * Description:
+ * if required, from device-tree the AXI internal register can be tuned
+ * by using platform parameters.
+ */
+static struct stmmac_axi *stmmac_axi_setup(struct platform_device *pdev)
+{
+ struct device_node *np;
+ struct stmmac_axi *axi;
+ u32 axi_blen[AXI_BLEN];
+
+ np = of_parse_phandle(pdev->dev.of_node, "snps,axi-config", 0);
+ if (!np)
+ return NULL;
+
+ axi = devm_kzalloc(&pdev->dev, sizeof(*axi), GFP_KERNEL);
+ if (!axi) {
+ of_node_put(np);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ axi->axi_lpi_en = of_property_read_bool(np, "snps,lpi_en");
+ axi->axi_xit_frm = of_property_read_bool(np, "snps,xit_frm");
+ axi->axi_fb = of_property_read_bool(np, "snps,fb");
+
+ if (of_property_read_u32(np, "snps,wr_osr_lmt", &axi->axi_wr_osr_lmt))
+ axi->axi_wr_osr_lmt = 1;
+ if (of_property_read_u32(np, "snps,rd_osr_lmt", &axi->axi_rd_osr_lmt))
+ axi->axi_rd_osr_lmt = 1;
+ of_property_read_u32_array(np, "snps,blen", axi_blen, AXI_BLEN);
+ stmmac_axi_blen_to_mask(&axi->axi_blen_regval, axi_blen, AXI_BLEN);
+ of_node_put(np);
+
+ return axi;
+}
+
+/**
+ * stmmac_mtl_setup - parse DT parameters for multiple queues configuration
+ * @pdev: platform device
+ * @plat: enet data
+ */
+static int stmmac_mtl_setup(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat)
+{
+ struct device_node *q_node;
+ struct device_node *rx_node;
+ struct device_node *tx_node;
+ u8 queue = 0;
+ int ret = 0;
+ u32 value;
+
+ /* First Queue must always be in DCB mode. As MTL_QUEUE_DCB = 1 we need
+ * to always set this, otherwise Queue will be classified as AVB
+ * (because MTL_QUEUE_AVB = 0).
+ */
+ plat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
+ plat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
+
+ rx_node = of_parse_phandle(pdev->dev.of_node, "snps,mtl-rx-config", 0);
+ if (!rx_node)
+ return ret;
+
+ tx_node = of_parse_phandle(pdev->dev.of_node, "snps,mtl-tx-config", 0);
+ if (!tx_node) {
+ of_node_put(rx_node);
+ return ret;
+ }
+
+ /* Processing RX queues common config */
+ if (!of_property_read_u32(rx_node, "snps,rx-queues-to-use", &value)) {
+ if (value > MTL_MAX_RX_QUEUES)
+ value = MTL_MAX_RX_QUEUES;
+ plat->rx_queues_to_use = value;
+ }
+
+ if (of_property_read_bool(rx_node, "snps,rx-sched-sp"))
+ plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
+ else if (of_property_read_bool(rx_node, "snps,rx-sched-wsp"))
+ plat->rx_sched_algorithm = MTL_RX_ALGORITHM_WSP;
+ else
+ plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
+
+ /* Processing individual RX queue config */
+ for_each_child_of_node(rx_node, q_node) {
+ if (queue >= plat->rx_queues_to_use)
+ break;
+
+ if (of_property_read_bool(q_node, "snps,dcb-algorithm"))
+ plat->rx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
+ else if (of_property_read_bool(q_node, "snps,avb-algorithm"))
+ plat->rx_queues_cfg[queue].mode_to_use = MTL_QUEUE_AVB;
+ else
+ plat->rx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
+
+ of_property_read_u32(q_node, "snps,map-to-dma-channel",
+ &plat->rx_queues_cfg[queue].chan);
+ /* TODO: Dynamic mapping to be included in the future */
+
+ if (!of_property_read_u32(q_node, "snps,priority",
+ &plat->rx_queues_cfg[queue].prio))
+ plat->rx_queues_cfg[queue].use_prio = true;
+
+ /* RX queue specific packet type routing */
+ if (of_property_read_bool(q_node, "snps,route-avcp"))
+ plat->rx_queues_cfg[queue].pkt_route = PACKET_AVCPQ;
+ else if (of_property_read_bool(q_node, "snps,route-ptp"))
+ plat->rx_queues_cfg[queue].pkt_route = PACKET_PTPQ;
+ else if (of_property_read_bool(q_node, "snps,route-dcbcp"))
+ plat->rx_queues_cfg[queue].pkt_route = PACKET_DCBCPQ;
+ else if (of_property_read_bool(q_node, "snps,route-up"))
+ plat->rx_queues_cfg[queue].pkt_route = PACKET_UPQ;
+ else if (of_property_read_bool(q_node, "snps,route-multi-broad"))
+ plat->rx_queues_cfg[queue].pkt_route = PACKET_MCBCQ;
+
+ queue++;
+ }
+ if (queue != plat->rx_queues_to_use) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "Not all RX queues were configured\n");
+ goto out;
+ }
+
+ /* Processing TX queues common config */
+ if (!of_property_read_u32(tx_node, "snps,tx-queues-to-use", &value)) {
+ if (value > MTL_MAX_TX_QUEUES)
+ value = MTL_MAX_TX_QUEUES;
+ plat->tx_queues_to_use = value;
+ }
+
+ if (of_property_read_bool(tx_node, "snps,tx-sched-wrr"))
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR;
+ else if (of_property_read_bool(tx_node, "snps,tx-sched-wfq"))
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WFQ;
+ else if (of_property_read_bool(tx_node, "snps,tx-sched-dwrr"))
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_DWRR;
+ else
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_SP;
+
+ queue = 0;
+
+ /* Processing individual TX queue config */
+ for_each_child_of_node(tx_node, q_node) {
+ if (queue >= plat->tx_queues_to_use)
+ break;
+
+ if (of_property_read_u32(q_node, "snps,weight",
+ &plat->tx_queues_cfg[queue].weight))
+ plat->tx_queues_cfg[queue].weight = 0x10 + queue;
+
+ if (of_property_read_bool(q_node, "snps,dcb-algorithm")) {
+ plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
+ } else if (of_property_read_bool(q_node,
+ "snps,avb-algorithm")) {
+ plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_AVB;
+
+ /* Credit Base Shaper parameters used by AVB */
+ if (of_property_read_u32(q_node, "snps,send_slope",
+ &plat->tx_queues_cfg[queue].send_slope))
+ plat->tx_queues_cfg[queue].send_slope = 0x0;
+ if (of_property_read_u32(q_node, "snps,idle_slope",
+ &plat->tx_queues_cfg[queue].idle_slope))
+ plat->tx_queues_cfg[queue].idle_slope = 0x0;
+ if (of_property_read_u32(q_node, "snps,high_credit",
+ &plat->tx_queues_cfg[queue].high_credit))
+ plat->tx_queues_cfg[queue].high_credit = 0x0;
+ if (of_property_read_u32(q_node, "snps,low_credit",
+ &plat->tx_queues_cfg[queue].low_credit))
+ plat->tx_queues_cfg[queue].low_credit = 0x0;
+ } else {
+ plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
+ }
+
+ if (!of_property_read_u32(q_node, "snps,priority",
+ &plat->tx_queues_cfg[queue].prio))
+ plat->tx_queues_cfg[queue].use_prio = true;
+
+ plat->tx_queues_cfg[queue].coe_unsupported =
+ of_property_read_bool(q_node, "snps,coe-unsupported");
+
+ queue++;
+ }
+ if (queue != plat->tx_queues_to_use) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "Not all TX queues were configured\n");
+ goto out;
+ }
+
+out:
+ of_node_put(rx_node);
+ of_node_put(tx_node);
+ of_node_put(q_node);
+
+ return ret;
+}
+
+/**
+ * stmmac_of_get_mdio() - Gets the MDIO bus from the devicetree.
+ * @np: devicetree node
+ *
+ * The MDIO bus will be searched for in the following ways:
+ * 1. The compatible is "snps,dwc-qos-ethernet-4.10" && a "mdio" named
+ * child node exists
+ * 2. A child node with the "snps,dwmac-mdio" compatible is present
+ *
+ * Return: The MDIO node if present otherwise NULL
+ */
+static struct device_node *stmmac_of_get_mdio(struct device_node *np)
+{
+ static const struct of_device_id need_mdio_ids[] = {
+ { .compatible = "snps,dwc-qos-ethernet-4.10" },
+ {},
+ };
+ struct device_node *mdio_node = NULL;
+
+ if (of_match_node(need_mdio_ids, np)) {
+ mdio_node = of_get_child_by_name(np, "mdio");
+ } else {
+ /**
+ * If snps,dwmac-mdio is passed from DT, always register
+ * the MDIO
+ */
+ for_each_child_of_node(np, mdio_node) {
+ if (of_device_is_compatible(mdio_node,
+ "snps,dwmac-mdio"))
+ break;
+ }
+ }
+
+ return mdio_node;
+}
+
+/**
+ * stmmac_mdio_setup() - Populate platform related MDIO structures.
+ * @plat: driver data platform structure
+ * @np: devicetree node
+ * @dev: device pointer
+ *
+ * This searches for MDIO information from the devicetree.
+ * If an MDIO node is found, it's assigned to plat->mdio_node and
+ * plat->mdio_bus_data is allocated.
+ * If no connection can be determined, just plat->mdio_bus_data is allocated
+ * to indicate a bus should be created and scanned for a phy.
+ * If it's determined there's no MDIO bus needed, both are left NULL.
+ *
+ * This expects that plat->phy_node has already been searched for.
+ *
+ * Return: 0 on success, errno otherwise.
+ */
+static int stmmac_mdio_setup(struct plat_stmmacenet_data *plat,
+ struct device_node *np, struct device *dev)
+{
+ bool legacy_mdio;
+
+ plat->mdio_node = stmmac_of_get_mdio(np);
+ if (plat->mdio_node)
+ dev_dbg(dev, "Found MDIO subnode\n");
+
+ /* Legacy devicetrees allowed for no MDIO bus description and expect
+ * the bus to be scanned for devices. If there's no phy or fixed-link
+ * described assume this is the case since there must be something
+ * connected to the MAC.
+ */
+ legacy_mdio = !of_phy_is_fixed_link(np) && !plat->phy_node;
+ if (legacy_mdio)
+ dev_info(dev, "Deprecated MDIO bus assumption used\n");
+
+ if (plat->mdio_node || legacy_mdio) {
+ plat->mdio_bus_data = devm_kzalloc(dev,
+ sizeof(*plat->mdio_bus_data),
+ GFP_KERNEL);
+ if (!plat->mdio_bus_data)
+ return -ENOMEM;
+
+ plat->mdio_bus_data->needs_reset = true;
+ }
+
+ return 0;
+}
+
+/**
+ * stmmac_of_get_mac_mode - retrieves the interface of the MAC
+ * @np: - device-tree node
+ * Description:
+ * Similar to `of_get_phy_mode()`, this function will retrieve (from
+ * the device-tree) the interface mode on the MAC side. This assumes
+ * that there is mode converter in-between the MAC & PHY
+ * (e.g. GMII-to-RGMII).
+ */
+static int stmmac_of_get_mac_mode(struct device_node *np)
+{
+ const char *pm;
+ int err, i;
+
+ err = of_property_read_string(np, "mac-mode", &pm);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) {
+ if (!strcasecmp(pm, phy_modes(i)))
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+/* Compatible string array for all gmac4 devices */
+static const char * const stmmac_gmac4_compats[] = {
+ "snps,dwmac-4.00",
+ "snps,dwmac-4.10a",
+ "snps,dwmac-4.20a",
+ "snps,dwmac-5.00a",
+ "snps,dwmac-5.10a",
+ "snps,dwmac-5.20",
+ "snps,dwmac-5.30a",
+ "snps,dwmac-5.40a",
+ NULL
+};
+
+/**
+ * stmmac_probe_config_dt - parse device-tree driver parameters
+ * @pdev: platform_device structure
+ * @mac: MAC address to use
+ * Description:
+ * this function is to read the driver parameters from device-tree and
+ * set some private fields that will be used by the main at runtime.
+ */
+static struct plat_stmmacenet_data *
+stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct plat_stmmacenet_data *plat;
+ struct stmmac_dma_cfg *dma_cfg;
+ static int bus_id = -ENODEV;
+ int phy_mode;
+ void *ret;
+ int rc;
+
+ plat = stmmac_plat_dat_alloc(&pdev->dev);
+ if (!plat)
+ return ERR_PTR(-ENOMEM);
+
+ rc = of_get_mac_address(np, mac);
+ if (rc) {
+ if (rc == -EPROBE_DEFER)
+ return ERR_PTR(rc);
+
+ eth_zero_addr(mac);
+ }
+
+ phy_mode = device_get_phy_mode(&pdev->dev);
+ if (phy_mode < 0)
+ return ERR_PTR(phy_mode);
+
+ plat->phy_interface = phy_mode;
+
+ rc = stmmac_of_get_mac_mode(np);
+ if (rc >= 0 && rc != phy_mode)
+ dev_warn(&pdev->dev,
+ "\"mac-mode\" property used for %s but differs to \"phy-mode\" of %s, and will be ignored. Please report.\n",
+ phy_modes(rc), phy_modes(phy_mode));
+
+ /* Some wrapper drivers still rely on phy_node. Let's save it while
+ * they are not converted to phylink. */
+ plat->phy_node = of_parse_phandle(np, "phy-handle", 0);
+
+ /* Get max speed of operation from device tree */
+ of_property_read_u32(np, "max-speed", &plat->max_speed);
+
+ plat->bus_id = of_alias_get_id(np, "ethernet");
+ if (plat->bus_id < 0) {
+ if (bus_id < 0)
+ bus_id = of_alias_get_highest_id("ethernet");
+ /* No ethernet alias found, init at -1 so first bus_id is 0 */
+ if (bus_id < 0)
+ bus_id = -1;
+ plat->bus_id = ++bus_id;
+ }
+
+ if (of_property_read_u32(np, "snps,clk-csr", &plat->clk_csr))
+ of_property_read_u32(np, "clk_csr", &plat->clk_csr);
+
+ /* "snps,phy-addr" is not a standard property. Mark it as deprecated
+ * and warn of its use. Remove this when phy node support is added.
+ */
+ if (of_property_read_u32(np, "snps,phy-addr", &plat->phy_addr) == 0)
+ dev_warn(&pdev->dev, "snps,phy-addr property is deprecated\n");
+
+ rc = stmmac_mdio_setup(plat, np, &pdev->dev);
+ if (rc) {
+ ret = ERR_PTR(rc);
+ goto error_put_phy;
+ }
+
+ of_property_read_u32(np, "tx-fifo-depth", &plat->tx_fifo_size);
+
+ of_property_read_u32(np, "rx-fifo-depth", &plat->rx_fifo_size);
+
+ plat->force_sf_dma_mode =
+ of_property_read_bool(np, "snps,force_sf_dma_mode");
+
+ if (of_property_read_bool(np, "snps,en-tx-lpi-clockgating")) {
+ dev_warn(&pdev->dev,
+ "OF property snps,en-tx-lpi-clockgating is deprecated, please convert driver to use STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP\n");
+ plat->flags |= STMMAC_FLAG_EN_TX_LPI_CLOCKGATING;
+ }
+
+ /*
+ * Currently only the properties needed on SPEAr600
+ * are provided. All other properties should be added
+ * once needed on other platforms.
+ */
+ if (of_device_is_compatible(np, "st,spear600-gmac") ||
+ of_device_is_compatible(np, "snps,dwmac-3.50a") ||
+ of_device_is_compatible(np, "snps,dwmac-3.70a") ||
+ of_device_is_compatible(np, "snps,dwmac-3.72a") ||
+ of_device_is_compatible(np, "snps,dwmac")) {
+ /* Note that the max-frame-size parameter as defined in the
+ * ePAPR v1.1 spec is defined as max-frame-size, it's
+ * actually used as the IEEE definition of MAC Client
+ * data, or MTU. The ePAPR specification is confusing as
+ * the definition is max-frame-size, but usage examples
+ * are clearly MTUs
+ */
+ of_property_read_u32(np, "max-frame-size", &plat->maxmtu);
+ of_property_read_u32(np, "snps,multicast-filter-bins",
+ &plat->multicast_filter_bins);
+ of_property_read_u32(np, "snps,perfect-filter-entries",
+ &plat->unicast_filter_entries);
+ plat->unicast_filter_entries = dwmac1000_validate_ucast_entries(
+ &pdev->dev, plat->unicast_filter_entries);
+ plat->multicast_filter_bins = dwmac1000_validate_mcast_bins(
+ &pdev->dev, plat->multicast_filter_bins);
+ plat->core_type = DWMAC_CORE_GMAC;
+ plat->pmt = true;
+ }
+
+ if (of_device_is_compatible(np, "snps,dwmac-3.40a")) {
+ plat->core_type = DWMAC_CORE_GMAC;
+ plat->enh_desc = true;
+ plat->tx_coe = true;
+ plat->bugged_jumbo = true;
+ plat->pmt = true;
+ }
+
+ if (of_device_compatible_match(np, stmmac_gmac4_compats)) {
+ plat->core_type = DWMAC_CORE_GMAC4;
+ plat->pmt = true;
+ if (of_property_read_bool(np, "snps,tso"))
+ plat->flags |= STMMAC_FLAG_TSO_EN;
+ }
+
+ if (of_device_is_compatible(np, "snps,dwmac-3.610") ||
+ of_device_is_compatible(np, "snps,dwmac-3.710")) {
+ plat->enh_desc = true;
+ plat->bugged_jumbo = true;
+ plat->force_sf_dma_mode = true;
+ }
+
+ if (of_device_is_compatible(np, "snps,dwxgmac")) {
+ plat->core_type = DWMAC_CORE_XGMAC;
+ plat->pmt = true;
+ if (of_property_read_bool(np, "snps,tso"))
+ plat->flags |= STMMAC_FLAG_TSO_EN;
+ of_property_read_u32(np, "snps,multicast-filter-bins",
+ &plat->multicast_filter_bins);
+ }
+
+ dma_cfg = plat->dma_cfg;
+
+ of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl);
+ if (!dma_cfg->pbl)
+ dma_cfg->pbl = DEFAULT_DMA_PBL;
+ of_property_read_u32(np, "snps,txpbl", &dma_cfg->txpbl);
+ of_property_read_u32(np, "snps,rxpbl", &dma_cfg->rxpbl);
+ dma_cfg->pblx8 = !of_property_read_bool(np, "snps,no-pbl-x8");
+
+ dma_cfg->aal = of_property_read_bool(np, "snps,aal");
+ dma_cfg->fixed_burst = of_property_read_bool(np, "snps,fixed-burst");
+ dma_cfg->mixed_burst = of_property_read_bool(np, "snps,mixed-burst");
+
+ plat->force_thresh_dma_mode = of_property_read_bool(np, "snps,force_thresh_dma_mode");
+ if (plat->force_thresh_dma_mode && plat->force_sf_dma_mode) {
+ plat->force_sf_dma_mode = 0;
+ dev_warn(&pdev->dev,
+ "force_sf_dma_mode is ignored if force_thresh_dma_mode is set.\n");
+ }
+
+ of_property_read_u32(np, "snps,ps-speed", &plat->mac_port_sel_speed);
+
+ plat->axi = stmmac_axi_setup(pdev);
+
+ rc = stmmac_mtl_setup(pdev, plat);
+ if (rc) {
+ ret = ERR_PTR(rc);
+ goto error_put_mdio;
+ }
+
+ /* clock setup */
+ if (!of_device_is_compatible(np, "snps,dwc-qos-ethernet-4.10")) {
+ plat->stmmac_clk = devm_clk_get(&pdev->dev,
+ STMMAC_RESOURCE_NAME);
+ if (IS_ERR(plat->stmmac_clk)) {
+ dev_warn(&pdev->dev, "Cannot get CSR clock\n");
+ plat->stmmac_clk = NULL;
+ }
+ clk_prepare_enable(plat->stmmac_clk);
+ }
+
+ plat->pclk = devm_clk_get_optional(&pdev->dev, "pclk");
+ if (IS_ERR(plat->pclk)) {
+ ret = plat->pclk;
+ goto error_pclk_get;
+ }
+ clk_prepare_enable(plat->pclk);
+
+ /* Fall-back to main clock in case of no PTP ref is passed */
+ plat->clk_ptp_ref = devm_clk_get(&pdev->dev, "ptp_ref");
+ if (IS_ERR(plat->clk_ptp_ref)) {
+ plat->clk_ptp_rate = clk_get_rate(plat->stmmac_clk);
+ plat->clk_ptp_ref = NULL;
+ dev_info(&pdev->dev, "PTP uses main clock\n");
+ } else {
+ plat->clk_ptp_rate = clk_get_rate(plat->clk_ptp_ref);
+ dev_dbg(&pdev->dev, "PTP rate %lu\n", plat->clk_ptp_rate);
+ }
+
+ plat->stmmac_rst = devm_reset_control_get_optional(&pdev->dev,
+ STMMAC_RESOURCE_NAME);
+ if (IS_ERR(plat->stmmac_rst)) {
+ ret = plat->stmmac_rst;
+ goto error_hw_init;
+ }
+
+ plat->stmmac_ahb_rst = devm_reset_control_get_optional_shared(
+ &pdev->dev, "ahb");
+ if (IS_ERR(plat->stmmac_ahb_rst)) {
+ ret = plat->stmmac_ahb_rst;
+ goto error_hw_init;
+ }
+
+ return plat;
+
+error_hw_init:
+ clk_disable_unprepare(plat->pclk);
+error_pclk_get:
+ clk_disable_unprepare(plat->stmmac_clk);
+error_put_mdio:
+ of_node_put(plat->mdio_node);
+error_put_phy:
+ of_node_put(plat->phy_node);
+
+ return ret;
+}
+
+static void devm_stmmac_remove_config_dt(void *data)
+{
+ struct plat_stmmacenet_data *plat = data;
+
+ clk_disable_unprepare(plat->stmmac_clk);
+ clk_disable_unprepare(plat->pclk);
+ of_node_put(plat->mdio_node);
+ of_node_put(plat->phy_node);
+}
+
+/**
+ * devm_stmmac_probe_config_dt
+ * @pdev: platform_device structure
+ * @mac: MAC address to use
+ * Description: Devres variant of stmmac_probe_config_dt().
+ */
+struct plat_stmmacenet_data *
+devm_stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac)
+{
+ struct plat_stmmacenet_data *plat;
+ int ret;
+
+ plat = stmmac_probe_config_dt(pdev, mac);
+ if (IS_ERR(plat))
+ return plat;
+
+ ret = devm_add_action_or_reset(&pdev->dev,
+ devm_stmmac_remove_config_dt, plat);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return plat;
+}
+#else
+struct plat_stmmacenet_data *
+devm_stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac)
+{
+ return ERR_PTR(-EINVAL);
+}
+#endif /* CONFIG_OF */
+EXPORT_SYMBOL_GPL(devm_stmmac_probe_config_dt);
+
+struct clk *stmmac_pltfr_find_clk(struct plat_stmmacenet_data *plat_dat,
+ const char *name)
+{
+ for (int i = 0; i < plat_dat->num_clks; i++)
+ if (strcmp(plat_dat->clks[i].id, name) == 0)
+ return plat_dat->clks[i].clk;
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(stmmac_pltfr_find_clk);
+
+/**
+ * stmmac_pltfr_get_irq_array - Read per-channel IRQs from platform device
+ * @pdev: platform device
+ * @fmt: IRQ name format string (e.g., "tx-queue-%d")
+ * @irqs: array to store IRQ numbers
+ * @num: maximum number of IRQs to read
+ *
+ * Return: 0 on success, -EPROBE_DEFER if IRQ is deferred, -EINVAL on error.
+ * Missing IRQs are set to 0 and iteration stops at first missing IRQ.
+ */
+static int stmmac_pltfr_get_irq_array(struct platform_device *pdev,
+ const char *fmt, int *irqs, size_t num)
+{
+ char name[16];
+ int i;
+
+ for (i = 0; i < num; i++) {
+ if (snprintf(name, sizeof(name), fmt, i) >= sizeof(name))
+ return -EINVAL;
+
+ irqs[i] = platform_get_irq_byname_optional(pdev, name);
+ if (irqs[i] == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (irqs[i] <= 0) {
+ dev_dbg(&pdev->dev, "IRQ %s not found\n", name);
+
+ /* Stop silently on first unset irq */
+ irqs[i] = 0;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int stmmac_get_platform_resources(struct platform_device *pdev,
+ struct stmmac_resources *stmmac_res)
+{
+ int ret;
+
+ memset(stmmac_res, 0, sizeof(*stmmac_res));
+
+ /* Get IRQ information early to have an ability to ask for deferred
+ * probe if needed before we went too far with resource allocation.
+ */
+ stmmac_res->irq = platform_get_irq_byname(pdev, "macirq");
+ if (stmmac_res->irq < 0)
+ return stmmac_res->irq;
+
+ /* On some platforms e.g. SPEAr the wake up irq differs from the mac irq
+ * The external wake up irq can be passed through the platform code
+ * named as "eth_wake_irq"
+ *
+ * In case the wake up interrupt is not passed from the platform
+ * so the driver will continue to use the mac irq (ndev->irq)
+ */
+ stmmac_res->wol_irq =
+ platform_get_irq_byname_optional(pdev, "eth_wake_irq");
+ if (stmmac_res->wol_irq < 0) {
+ if (stmmac_res->wol_irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_info(&pdev->dev, "IRQ eth_wake_irq not found\n");
+ stmmac_res->wol_irq = stmmac_res->irq;
+ }
+
+ stmmac_res->sfty_irq =
+ platform_get_irq_byname_optional(pdev, "sfty");
+ if (stmmac_res->sfty_irq < 0) {
+ if (stmmac_res->sfty_irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_info(&pdev->dev, "IRQ sfty not found\n");
+ }
+
+ stmmac_res->addr = devm_platform_ioremap_resource(pdev, 0);
+
+ if (IS_ERR(stmmac_res->addr))
+ return PTR_ERR(stmmac_res->addr);
+
+ /* TX channels irq */
+ ret = stmmac_pltfr_get_irq_array(pdev, "tx-queue-%d",
+ stmmac_res->tx_irq,
+ MTL_MAX_TX_QUEUES);
+ if (ret)
+ return ret;
+
+ /* RX channels irq */
+ ret = stmmac_pltfr_get_irq_array(pdev, "rx-queue-%d",
+ stmmac_res->rx_irq,
+ MTL_MAX_RX_QUEUES);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(stmmac_get_platform_resources);
+
+/**
+ * stmmac_pltfr_init
+ * @dev: pointer to the device structure
+ * @plat: driver data platform structure
+ * Description: Call the platform's init callback (if any) and propagate
+ * the return value.
+ */
+static int stmmac_pltfr_init(struct device *dev,
+ struct plat_stmmacenet_data *plat)
+{
+ int ret = 0;
+
+ if (plat->init)
+ ret = plat->init(dev, plat->bsp_priv);
+
+ return ret;
+}
+
+/**
+ * stmmac_pltfr_exit
+ * @dev: pointer to the device structure
+ * @plat: driver data platform structure
+ * Description: Call the platform's exit callback (if any).
+ */
+static void stmmac_pltfr_exit(struct device *dev,
+ struct plat_stmmacenet_data *plat)
+{
+ if (plat->exit)
+ plat->exit(dev, plat->bsp_priv);
+}
+
+static int stmmac_plat_suspend(struct device *dev, void *bsp_priv)
+{
+ struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev));
+
+ stmmac_pltfr_exit(dev, priv->plat);
+
+ return 0;
+}
+
+static int stmmac_plat_resume(struct device *dev, void *bsp_priv)
+{
+ struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev));
+
+ return stmmac_pltfr_init(dev, priv->plat);
+}
+
+/**
+ * stmmac_pltfr_probe
+ * @pdev: platform device pointer
+ * @plat: driver data platform structure
+ * @res: stmmac resources structure
+ * Description: This calls the platform's init() callback and probes the
+ * stmmac driver.
+ */
+int stmmac_pltfr_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ if (!plat->suspend && plat->exit)
+ plat->suspend = stmmac_plat_suspend;
+ if (!plat->resume && plat->init)
+ plat->resume = stmmac_plat_resume;
+
+ return stmmac_dvr_probe(&pdev->dev, plat, res);
+}
+EXPORT_SYMBOL_GPL(stmmac_pltfr_probe);
+
+static void devm_stmmac_pltfr_remove(void *data)
+{
+ struct platform_device *pdev = data;
+
+ stmmac_pltfr_remove(pdev);
+}
+
+/**
+ * devm_stmmac_pltfr_probe
+ * @pdev: pointer to the platform device
+ * @plat: driver data platform structure
+ * @res: stmmac resources
+ * Description: Devres variant of stmmac_pltfr_probe(). Allows users to skip
+ * calling stmmac_pltfr_remove() on driver detach.
+ */
+int devm_stmmac_pltfr_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res)
+{
+ int ret;
+
+ ret = stmmac_pltfr_probe(pdev, plat, res);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(&pdev->dev, devm_stmmac_pltfr_remove,
+ pdev);
+}
+EXPORT_SYMBOL_GPL(devm_stmmac_pltfr_probe);
+
+/**
+ * stmmac_pltfr_remove
+ * @pdev: pointer to the platform device
+ * Description: This undoes the effects of stmmac_pltfr_probe() by removing the
+ * driver and calling the platform's exit() callback.
+ */
+void stmmac_pltfr_remove(struct platform_device *pdev)
+{
+ stmmac_dvr_remove(&pdev->dev);
+}
+EXPORT_SYMBOL_GPL(stmmac_pltfr_remove);
+
+static int stmmac_bus_clks_config(struct stmmac_priv *priv, bool enabled)
+{
+ struct plat_stmmacenet_data *plat_dat = priv->plat;
+ int ret;
+
+ if (enabled) {
+ ret = clk_prepare_enable(plat_dat->stmmac_clk);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(plat_dat->pclk);
+ if (ret) {
+ clk_disable_unprepare(plat_dat->stmmac_clk);
+ return ret;
+ }
+ if (plat_dat->clks_config) {
+ ret = plat_dat->clks_config(plat_dat->bsp_priv, enabled);
+ if (ret) {
+ clk_disable_unprepare(plat_dat->stmmac_clk);
+ clk_disable_unprepare(plat_dat->pclk);
+ return ret;
+ }
+ }
+ } else {
+ clk_disable_unprepare(plat_dat->stmmac_clk);
+ clk_disable_unprepare(plat_dat->pclk);
+ if (plat_dat->clks_config)
+ plat_dat->clks_config(plat_dat->bsp_priv, enabled);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused stmmac_runtime_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ stmmac_bus_clks_config(priv, false);
+
+ return 0;
+}
+
+static int __maybe_unused stmmac_runtime_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ return stmmac_bus_clks_config(priv, true);
+}
+
+static int __maybe_unused stmmac_pltfr_noirq_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ if (!netif_running(ndev))
+ return 0;
+
+ if (!priv->wolopts) {
+ /* Disable clock in case of PWM is off */
+ clk_disable_unprepare(priv->plat->clk_ptp_ref);
+
+ ret = pm_runtime_force_suspend(dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused stmmac_pltfr_noirq_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ if (!netif_running(ndev))
+ return 0;
+
+ if (!priv->wolopts) {
+ /* enable the clk previously disabled */
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
+ if (ret < 0) {
+ netdev_warn(priv->dev,
+ "failed to enable PTP reference clock: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+const struct dev_pm_ops stmmac_pltfr_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(stmmac_suspend, stmmac_resume)
+ SET_RUNTIME_PM_OPS(stmmac_runtime_suspend, stmmac_runtime_resume, NULL)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(stmmac_pltfr_noirq_suspend, stmmac_pltfr_noirq_resume)
+};
+EXPORT_SYMBOL_GPL(stmmac_pltfr_pm_ops);
+
+MODULE_DESCRIPTION("STMMAC 10/100/1000 Ethernet platform support");
+MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h
new file mode 100644
index 000000000..6e6561e29
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*******************************************************************************
+ Copyright (C) 2007-2009 STMicroelectronics Ltd
+
+
+ Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#ifndef __STMMAC_PLATFORM_H__
+#define __STMMAC_PLATFORM_H__
+
+#include "stmmac.h"
+
+struct plat_stmmacenet_data *
+devm_stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac);
+
+struct clk *stmmac_pltfr_find_clk(struct plat_stmmacenet_data *plat_dat,
+ const char *name);
+
+int stmmac_get_platform_resources(struct platform_device *pdev,
+ struct stmmac_resources *stmmac_res);
+
+int stmmac_pltfr_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res);
+int devm_stmmac_pltfr_probe(struct platform_device *pdev,
+ struct plat_stmmacenet_data *plat,
+ struct stmmac_resources *res);
+void stmmac_pltfr_remove(struct platform_device *pdev);
+extern const struct dev_pm_ops stmmac_pltfr_pm_ops;
+
+static inline void *get_stmmac_bsp_priv(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ return priv->plat->bsp_priv;
+}
+
+#endif /* __STMMAC_PLATFORM_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
new file mode 100644
index 000000000..960249960
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*******************************************************************************
+ PTP 1588 clock using the STMMAC.
+
+ Copyright (C) 2013 Vayavya Labs Pvt Ltd
+
+
+ Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
+*******************************************************************************/
+#include "stmmac.h"
+#include "stmmac_ptp.h"
+
+#define PTP_SAFE_TIME_OFFSET_NS 500000
+
+/**
+ * stmmac_adjust_freq
+ *
+ * @ptp: pointer to ptp_clock_info structure
+ * @scaled_ppm: desired period change in scaled parts per million
+ *
+ * Description: this function will adjust the frequency of hardware clock.
+ *
+ * Scaled parts per million is ppm with a 16-bit binary fractional field.
+ */
+static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ unsigned long flags;
+ u32 addend;
+
+ addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm);
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_config_addend(priv, priv->ptpaddr, addend);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ return 0;
+}
+
+/**
+ * stmmac_adjust_time
+ *
+ * @ptp: pointer to ptp_clock_info structure
+ * @delta: desired change in nanoseconds
+ *
+ * Description: this function will shift/adjust the hardware clock time.
+ */
+static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ unsigned long flags;
+ u32 sec, nsec;
+ u32 quotient, reminder;
+ int neg_adj = 0;
+ bool xmac, est_rst = false;
+ int ret;
+
+ xmac = dwmac_is_xmac(priv->plat->core_type);
+
+ if (delta < 0) {
+ neg_adj = 1;
+ delta = -delta;
+ }
+
+ quotient = div_u64_rem(delta, 1000000000ULL, &reminder);
+ sec = quotient;
+ nsec = reminder;
+
+ /* If EST is enabled, disabled it before adjust ptp time. */
+ if (priv->est && priv->est->enable) {
+ est_rst = true;
+ mutex_lock(&priv->est_lock);
+ priv->est->enable = false;
+ stmmac_est_configure(priv, priv, priv->est,
+ priv->plat->clk_ptp_rate);
+ mutex_unlock(&priv->est_lock);
+ }
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_adjust_systime(priv, priv->ptpaddr, sec, nsec, neg_adj, xmac);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ /* Calculate new basetime and re-configured EST after PTP time adjust. */
+ if (est_rst) {
+ struct timespec64 current_time, time;
+ ktime_t current_time_ns, basetime;
+ u64 cycle_time;
+
+ mutex_lock(&priv->est_lock);
+ priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, &current_time);
+ current_time_ns = timespec64_to_ktime(current_time);
+ time.tv_nsec = priv->est->btr_reserve[0];
+ time.tv_sec = priv->est->btr_reserve[1];
+ basetime = timespec64_to_ktime(time);
+ cycle_time = (u64)priv->est->ctr[1] * NSEC_PER_SEC +
+ priv->est->ctr[0];
+ time = stmmac_calc_tas_basetime(basetime,
+ current_time_ns,
+ cycle_time);
+
+ priv->est->btr[0] = (u32)time.tv_nsec;
+ priv->est->btr[1] = (u32)time.tv_sec;
+ priv->est->enable = true;
+ ret = stmmac_est_configure(priv, priv, priv->est,
+ priv->plat->clk_ptp_rate);
+ mutex_unlock(&priv->est_lock);
+ if (ret)
+ netdev_err(priv->dev, "failed to configure EST\n");
+ }
+
+ return 0;
+}
+
+/**
+ * stmmac_get_time
+ *
+ * @ptp: pointer to ptp_clock_info structure
+ * @ts: pointer to hold time/result
+ *
+ * Description: this function will read the current time from the
+ * hardware clock and store it in @ts.
+ */
+static int stmmac_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ unsigned long flags;
+ u64 ns = 0;
+
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_get_systime(priv, priv->ptpaddr, &ns);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ *ts = ns_to_timespec64(ns);
+
+ return 0;
+}
+
+/**
+ * stmmac_set_time
+ *
+ * @ptp: pointer to ptp_clock_info structure
+ * @ts: time value to set
+ *
+ * Description: this function will set the current time on the
+ * hardware clock.
+ */
+static int stmmac_set_time(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ unsigned long flags;
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ return 0;
+}
+
+static int stmmac_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+ void __iomem *ptpaddr = priv->ptpaddr;
+ struct stmmac_pps_cfg *cfg;
+ int ret = -EOPNOTSUPP;
+ unsigned long flags;
+ u32 acr_value;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_PEROUT: {
+ struct timespec64 curr_time;
+ u64 target_ns = 0;
+ u64 ns = 0;
+
+ /* Reject requests with unsupported flags */
+ if (rq->perout.flags)
+ return -EOPNOTSUPP;
+
+ cfg = &priv->pps[rq->perout.index];
+
+ cfg->start.tv_sec = rq->perout.start.sec;
+ cfg->start.tv_nsec = rq->perout.start.nsec;
+
+ /* A time set in the past won't trigger the start of the flexible PPS generation for
+ * the GMAC5. For some reason it does for the GMAC4 but setting a time in the past
+ * should be addressed anyway. Therefore, any value set it the past is considered as
+ * an offset compared to the current MAC system time.
+ * Be aware that an offset too low may not trigger flexible PPS generation
+ * if time spent in this configuration makes the targeted time already outdated.
+ * To address this, add a safe time offset.
+ */
+ if (!cfg->start.tv_sec && cfg->start.tv_nsec < PTP_SAFE_TIME_OFFSET_NS)
+ cfg->start.tv_nsec += PTP_SAFE_TIME_OFFSET_NS;
+
+ target_ns = cfg->start.tv_nsec + ((u64)cfg->start.tv_sec * NSEC_PER_SEC);
+
+ stmmac_get_systime(priv, priv->ptpaddr, &ns);
+ if (ns > TIME64_MAX - PTP_SAFE_TIME_OFFSET_NS)
+ return -EINVAL;
+
+ curr_time = ns_to_timespec64(ns);
+ if (target_ns < ns + PTP_SAFE_TIME_OFFSET_NS) {
+ cfg->start = timespec64_add_safe(cfg->start, curr_time);
+ if (cfg->start.tv_sec == TIME64_MAX)
+ return -EINVAL;
+ }
+
+ cfg->period.tv_sec = rq->perout.period.sec;
+ cfg->period.tv_nsec = rq->perout.period.nsec;
+
+ write_lock_irqsave(&priv->ptp_lock, flags);
+ ret = stmmac_flex_pps_config(priv, priv->ioaddr,
+ rq->perout.index, cfg, on,
+ priv->sub_second_inc,
+ priv->systime_flags);
+ write_unlock_irqrestore(&priv->ptp_lock, flags);
+ break;
+ }
+ case PTP_CLK_REQ_EXTTS: {
+ u8 channel;
+
+ mutex_lock(&priv->aux_ts_lock);
+ acr_value = readl(ptpaddr + PTP_ACR);
+ channel = ilog2(FIELD_GET(PTP_ACR_MASK, acr_value));
+ acr_value &= ~PTP_ACR_MASK;
+
+ if (on) {
+ if (FIELD_GET(PTP_ACR_MASK, acr_value)) {
+ netdev_err(priv->dev,
+ "Cannot enable auxiliary snapshot %d as auxiliary snapshot %d is already enabled",
+ rq->extts.index, channel);
+ mutex_unlock(&priv->aux_ts_lock);
+ return -EBUSY;
+ }
+
+ priv->plat->flags |= STMMAC_FLAG_EXT_SNAPSHOT_EN;
+
+ /* Enable External snapshot trigger */
+ acr_value |= PTP_ACR_ATSEN(rq->extts.index);
+ acr_value |= PTP_ACR_ATSFC;
+ } else {
+ priv->plat->flags &= ~STMMAC_FLAG_EXT_SNAPSHOT_EN;
+ }
+ netdev_dbg(priv->dev, "Auxiliary Snapshot %d %s.\n",
+ rq->extts.index, on ? "enabled" : "disabled");
+ writel(acr_value, ptpaddr + PTP_ACR);
+ mutex_unlock(&priv->aux_ts_lock);
+ /* wait for auxts fifo clear to finish */
+ ret = readl_poll_timeout(ptpaddr + PTP_ACR, acr_value,
+ !(acr_value & PTP_ACR_ATSFC),
+ 10, 10000);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * stmmac_get_syncdevicetime
+ * @device: current device time
+ * @system: system counter value read synchronously with device time
+ * @ctx: context provided by timekeeping code
+ * Description: Read device and system clock simultaneously and return the
+ * corrected clock values in ns.
+ **/
+static int stmmac_get_syncdevicetime(ktime_t *device,
+ struct system_counterval_t *system,
+ void *ctx)
+{
+ struct stmmac_priv *priv = (struct stmmac_priv *)ctx;
+
+ return priv->plat->crosststamp(device, system, ctx);
+}
+
+static int stmmac_getcrosststamp(struct ptp_clock_info *ptp,
+ struct system_device_crosststamp *xtstamp)
+{
+ struct stmmac_priv *priv =
+ container_of(ptp, struct stmmac_priv, ptp_clock_ops);
+
+ return get_device_system_crosststamp(stmmac_get_syncdevicetime,
+ priv, NULL, xtstamp);
+}
+
+/* structure describing a PTP hardware clock */
+const struct ptp_clock_info stmmac_ptp_clock_ops = {
+ .owner = THIS_MODULE,
+ .name = "stmmac ptp",
+ .max_adj = 62500000,
+ .n_alarm = 0,
+ .n_ext_ts = 0, /* will be overwritten in stmmac_ptp_register */
+ .n_per_out = 0, /* will be overwritten in stmmac_ptp_register */
+ .n_pins = 0,
+ .pps = 0,
+ .adjfine = stmmac_adjust_freq,
+ .adjtime = stmmac_adjust_time,
+ .gettime64 = stmmac_get_time,
+ .settime64 = stmmac_set_time,
+ .enable = stmmac_enable,
+};
+
+/* structure describing a PTP hardware clock */
+const struct ptp_clock_info dwmac1000_ptp_clock_ops = {
+ .owner = THIS_MODULE,
+ .name = "stmmac ptp",
+ .max_adj = 62500000,
+ .n_alarm = 0,
+ .n_ext_ts = 1,
+ .n_per_out = 0,
+ .n_pins = 0,
+ .pps = 0,
+ .adjfine = stmmac_adjust_freq,
+ .adjtime = stmmac_adjust_time,
+ .gettime64 = stmmac_get_time,
+ .settime64 = stmmac_set_time,
+ .enable = dwmac1000_ptp_enable,
+};
+
+/**
+ * stmmac_ptp_register
+ * @priv: driver private structure
+ * Description: this function will register the ptp clock driver
+ * to kernel. It also does some house keeping work.
+ */
+void stmmac_ptp_register(struct stmmac_priv *priv)
+{
+ unsigned int pps_out_num = priv->dma_cap.pps_out_num;
+ unsigned int n_ext_ts;
+
+ if (pps_out_num > STMMAC_PPS_MAX) {
+ dev_warn(priv->device,
+ "pps outputs (%u) exceeds driver maximum, limiting to %u\n",
+ pps_out_num, STMMAC_PPS_MAX);
+ pps_out_num = STMMAC_PPS_MAX;
+ }
+
+ /* Calculate the clock domain crossing (CDC) error if necessary */
+ priv->plat->cdc_error_adj = 0;
+ if (priv->plat->core_type == DWMAC_CORE_GMAC4)
+ priv->plat->cdc_error_adj = (2 * NSEC_PER_SEC) / priv->plat->clk_ptp_rate;
+
+ /* Update the ptp clock parameters based on feature discovery, when
+ * available
+ */
+ if (pps_out_num)
+ priv->ptp_clock_ops.n_per_out = pps_out_num;
+
+ n_ext_ts = priv->dma_cap.aux_snapshot_n;
+ if (n_ext_ts)
+ priv->ptp_clock_ops.n_ext_ts = n_ext_ts;
+
+ if (priv->plat->ptp_max_adj)
+ priv->ptp_clock_ops.max_adj = priv->plat->ptp_max_adj;
+
+ if (priv->plat->crosststamp)
+ priv->ptp_clock_ops.getcrosststamp = stmmac_getcrosststamp;
+
+ rwlock_init(&priv->ptp_lock);
+ mutex_init(&priv->aux_ts_lock);
+
+ priv->ptp_clock = ptp_clock_register(&priv->ptp_clock_ops,
+ priv->device);
+ if (IS_ERR(priv->ptp_clock)) {
+ netdev_err(priv->dev, "ptp_clock_register failed\n");
+ priv->ptp_clock = NULL;
+ }
+
+ if (priv->ptp_clock)
+ netdev_info(priv->dev, "registered PTP clock\n");
+ else
+ mutex_destroy(&priv->aux_ts_lock);
+}
+
+/**
+ * stmmac_ptp_unregister
+ * @priv: driver private structure
+ * Description: this function will remove/unregister the ptp clock driver
+ * from the kernel.
+ */
+void stmmac_ptp_unregister(struct stmmac_priv *priv)
+{
+ if (priv->ptp_clock) {
+ ptp_clock_unregister(priv->ptp_clock);
+ priv->ptp_clock = NULL;
+ pr_debug("Removed PTP HW clock successfully on %s\n",
+ priv->dev->name);
+
+ mutex_destroy(&priv->aux_ts_lock);
+ }
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h
new file mode 100644
index 000000000..3fe0e3a80
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/******************************************************************************
+ PTP Header file
+
+ Copyright (C) 2013 Vayavya Labs Pvt Ltd
+
+
+ Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
+******************************************************************************/
+
+#ifndef __STMMAC_PTP_H__
+#define __STMMAC_PTP_H__
+
+#define PTP_XGMAC_OFFSET 0xd00
+#define PTP_GMAC4_OFFSET 0xb00
+#define PTP_GMAC3_X_OFFSET 0x700
+
+/* IEEE 1588 PTP register offsets */
+#define PTP_TCR 0x00 /* Timestamp Control Reg */
+#define PTP_SSIR 0x04 /* Sub-Second Increment Reg */
+#define PTP_STSR 0x08 /* System Time – Seconds Regr */
+#define PTP_STNSR 0x0c /* System Time – Nanoseconds Reg */
+#define PTP_STSUR 0x10 /* System Time – Seconds Update Reg */
+#define PTP_STNSUR 0x14 /* System Time – Nanoseconds Update Reg */
+#define PTP_TAR 0x18 /* Timestamp Addend Reg */
+#define PTP_ACR 0x40 /* Auxiliary Control Reg */
+#define PTP_ATNR 0x48 /* Auxiliary Timestamp - Nanoseconds Reg */
+#define PTP_ATSR 0x4c /* Auxiliary Timestamp - Seconds Reg */
+#define PTP_TS_INGR_CORR_NS 0x58 /* Ingress timestamp correction nanoseconds */
+#define PTP_TS_EGR_CORR_NS 0x5C /* Egress timestamp correction nanoseconds*/
+#define PTP_TS_INGR_CORR_SNS 0x60 /* Ingress timestamp correction subnanoseconds */
+#define PTP_TS_EGR_CORR_SNS 0x64 /* Egress timestamp correction subnanoseconds */
+#define PTP_TS_INGR_LAT 0x68 /* MAC internal Ingress Latency */
+#define PTP_TS_EGR_LAT 0x6c /* MAC internal Egress Latency */
+
+#define PTP_STNSUR_ADDSUB_SHIFT 31
+#define PTP_DIGITAL_ROLLOVER_MODE 0x3B9ACA00 /* 10e9-1 ns */
+#define PTP_BINARY_ROLLOVER_MODE 0x80000000 /* ~0.466 ns */
+
+/* PTP Timestamp control register defines */
+#define PTP_TCR_TSENA BIT(0) /* Timestamp Enable */
+#define PTP_TCR_TSCFUPDT BIT(1) /* Timestamp Fine/Coarse Update */
+#define PTP_TCR_TSINIT BIT(2) /* Timestamp Initialize */
+#define PTP_TCR_TSUPDT BIT(3) /* Timestamp Update */
+#define PTP_TCR_TSTRIG BIT(4) /* Timestamp Interrupt Trigger Enable */
+#define PTP_TCR_TSADDREG BIT(5) /* Addend Reg Update */
+#define PTP_TCR_TSENALL BIT(8) /* Enable Timestamp for All Frames */
+#define PTP_TCR_TSCTRLSSR BIT(9) /* Digital or Binary Rollover Control */
+/* Enable PTP packet Processing for Version 2 Format */
+#define PTP_TCR_TSVER2ENA BIT(10)
+/* Enable Processing of PTP over Ethernet Frames */
+#define PTP_TCR_TSIPENA BIT(11)
+/* Enable Processing of PTP Frames Sent over IPv6-UDP */
+#define PTP_TCR_TSIPV6ENA BIT(12)
+/* Enable Processing of PTP Frames Sent over IPv4-UDP */
+#define PTP_TCR_TSIPV4ENA BIT(13)
+/* Enable Timestamp Snapshot for Event Messages */
+#define PTP_TCR_TSEVNTENA BIT(14)
+/* Enable Snapshot for Messages Relevant to Master */
+#define PTP_TCR_TSMSTRENA BIT(15)
+/* Select PTP packets for Taking Snapshots
+ * On gmac4 specifically:
+ * Enable SYNC, Pdelay_Req, Pdelay_Resp when TSEVNTENA is enabled.
+ * or
+ * Enable SYNC, Follow_Up, Delay_Req, Delay_Resp, Pdelay_Req, Pdelay_Resp,
+ * Pdelay_Resp_Follow_Up if TSEVNTENA is disabled
+ */
+#define PTP_TCR_SNAPTYPSEL_1 BIT(16)
+/* Enable MAC address for PTP Frame Filtering */
+#define PTP_TCR_TSENMACADDR BIT(18)
+
+/* SSIR defines */
+#define PTP_SSIR_SSINC_MAX 0xff
+#define GMAC4_PTP_SSIR_SSINC_SHIFT 16
+
+/* Auxiliary Control defines */
+#define PTP_ACR_ATSFC BIT(0) /* Auxiliary Snapshot FIFO Clear */
+#define PTP_ACR_ATSEN0 BIT(4) /* Auxiliary Snapshot 0 Enable */
+#define PTP_ACR_ATSEN1 BIT(5) /* Auxiliary Snapshot 1 Enable */
+#define PTP_ACR_ATSEN2 BIT(6) /* Auxiliary Snapshot 2 Enable */
+#define PTP_ACR_ATSEN3 BIT(7) /* Auxiliary Snapshot 3 Enable */
+#define PTP_ACR_ATSEN(index) (PTP_ACR_ATSEN0 << (index))
+#define PTP_ACR_MASK GENMASK(7, 4) /* Aux Snapshot Mask */
+#define PMC_ART_VALUE0 0x01 /* PMC_ART[15:0] timer value */
+#define PMC_ART_VALUE1 0x02 /* PMC_ART[31:16] timer value */
+#define PMC_ART_VALUE2 0x03 /* PMC_ART[47:32] timer value */
+#define PMC_ART_VALUE3 0x04 /* PMC_ART[63:48] timer value */
+#define GMAC4_ART_TIME_SHIFT 16 /* ART TIME 16-bits shift */
+
+enum aux_snapshot {
+ AUX_SNAPSHOT0 = 0x10,
+ AUX_SNAPSHOT1 = 0x20,
+ AUX_SNAPSHOT2 = 0x40,
+ AUX_SNAPSHOT3 = 0x80,
+};
+
+struct ptp_clock_info;
+struct ptp_clock_request;
+struct stmmac_priv;
+
+int dwmac1000_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on);
+
+void dwmac1000_get_ptptime(void __iomem *ptpaddr, u64 *ptp_time);
+void dwmac1000_timestamp_interrupt(struct stmmac_priv *priv);
+
+extern const struct ptp_clock_info stmmac_ptp_clock_ops;
+extern const struct ptp_clock_info dwmac1000_ptp_clock_ops;
+
+#endif /* __STMMAC_PTP_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
new file mode 100644
index 000000000..a0c758865
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c
@@ -0,0 +1,2049 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
+ * stmmac Selftests Support
+ *
+ * Author: Jose Abreu <joabreu@synopsys.com>
+ */
+
+#include <linux/bitrev.h>
+#include <linux/completion.h>
+#include <linux/crc32.h>
+#include <linux/ethtool.h>
+#include <linux/ip.h>
+#include <linux/phy.h>
+#include <linux/udp.h>
+#include <net/pkt_cls.h>
+#include <net/pkt_sched.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/tc_act/tc_gact.h>
+#include "stmmac.h"
+
+struct stmmachdr {
+ __be32 version;
+ __be64 magic;
+ u8 id;
+} __packed;
+
+#define STMMAC_TEST_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \
+ sizeof(struct stmmachdr))
+#define STMMAC_TEST_PKT_MAGIC 0xdeadcafecafedeadULL
+#define STMMAC_LB_TIMEOUT msecs_to_jiffies(200)
+
+struct stmmac_packet_attrs {
+ int vlan;
+ int vlan_id_in;
+ int vlan_id_out;
+ unsigned char *src;
+ const unsigned char *dst;
+ u32 ip_src;
+ u32 ip_dst;
+ int tcp;
+ int sport;
+ int dport;
+ u32 exp_hash;
+ int dont_wait;
+ int timeout;
+ int size;
+ int max_size;
+ int remove_sa;
+ u8 id;
+ int sarc;
+ u16 queue_mapping;
+ u64 timestamp;
+};
+
+static u8 stmmac_test_next_id;
+
+static struct sk_buff *stmmac_test_get_udp_skb(struct stmmac_priv *priv,
+ struct stmmac_packet_attrs *attr)
+{
+ struct sk_buff *skb = NULL;
+ struct udphdr *uhdr = NULL;
+ struct tcphdr *thdr = NULL;
+ struct stmmachdr *shdr;
+ struct ethhdr *ehdr;
+ struct iphdr *ihdr;
+ int iplen, size;
+
+ size = attr->size + STMMAC_TEST_PKT_SIZE;
+ if (attr->vlan) {
+ size += 4;
+ if (attr->vlan > 1)
+ size += 4;
+ }
+
+ if (attr->tcp)
+ size += sizeof(struct tcphdr);
+ else
+ size += sizeof(struct udphdr);
+
+ if (attr->max_size && (attr->max_size > size))
+ size = attr->max_size;
+
+ skb = netdev_alloc_skb(priv->dev, size);
+ if (!skb)
+ return NULL;
+
+ prefetchw(skb->data);
+
+ if (attr->vlan > 1)
+ ehdr = skb_push(skb, ETH_HLEN + 8);
+ else if (attr->vlan)
+ ehdr = skb_push(skb, ETH_HLEN + 4);
+ else if (attr->remove_sa)
+ ehdr = skb_push(skb, ETH_HLEN - 6);
+ else
+ ehdr = skb_push(skb, ETH_HLEN);
+ skb_reset_mac_header(skb);
+
+ skb_set_network_header(skb, skb->len);
+ ihdr = skb_put(skb, sizeof(*ihdr));
+
+ skb_set_transport_header(skb, skb->len);
+ if (attr->tcp)
+ thdr = skb_put(skb, sizeof(*thdr));
+ else
+ uhdr = skb_put(skb, sizeof(*uhdr));
+
+ if (!attr->remove_sa)
+ eth_zero_addr(ehdr->h_source);
+ eth_zero_addr(ehdr->h_dest);
+ if (attr->src && !attr->remove_sa)
+ ether_addr_copy(ehdr->h_source, attr->src);
+ if (attr->dst)
+ ether_addr_copy(ehdr->h_dest, attr->dst);
+
+ if (!attr->remove_sa) {
+ ehdr->h_proto = htons(ETH_P_IP);
+ } else {
+ __be16 *ptr = (__be16 *)ehdr;
+
+ /* HACK */
+ ptr[3] = htons(ETH_P_IP);
+ }
+
+ if (attr->vlan) {
+ __be16 *tag, *proto;
+
+ if (!attr->remove_sa) {
+ tag = (void *)ehdr + ETH_HLEN;
+ proto = (void *)ehdr + (2 * ETH_ALEN);
+ } else {
+ tag = (void *)ehdr + ETH_HLEN - 6;
+ proto = (void *)ehdr + ETH_ALEN;
+ }
+
+ proto[0] = htons(ETH_P_8021Q);
+ tag[0] = htons(attr->vlan_id_out);
+ tag[1] = htons(ETH_P_IP);
+ if (attr->vlan > 1) {
+ proto[0] = htons(ETH_P_8021AD);
+ tag[1] = htons(ETH_P_8021Q);
+ tag[2] = htons(attr->vlan_id_in);
+ tag[3] = htons(ETH_P_IP);
+ }
+ }
+
+ if (attr->tcp) {
+ thdr->source = htons(attr->sport);
+ thdr->dest = htons(attr->dport);
+ thdr->doff = sizeof(struct tcphdr) / 4;
+ thdr->check = 0;
+ } else {
+ uhdr->source = htons(attr->sport);
+ uhdr->dest = htons(attr->dport);
+ uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size);
+ if (attr->max_size)
+ uhdr->len = htons(attr->max_size -
+ (sizeof(*ihdr) + sizeof(*ehdr)));
+ 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);
+
+ if (attr->max_size)
+ iplen = attr->max_size - sizeof(*ehdr);
+
+ 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 = skb_put(skb, sizeof(*shdr));
+ shdr->version = 0;
+ shdr->magic = cpu_to_be64(STMMAC_TEST_PKT_MAGIC);
+ attr->id = stmmac_test_next_id;
+ shdr->id = stmmac_test_next_id++;
+
+ if (attr->size)
+ skb_put(skb, attr->size);
+ if (attr->max_size && (attr->max_size > skb->len))
+ skb_put(skb, attr->max_size - skb->len);
+
+ skb->csum = 0;
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ if (attr->tcp) {
+ 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 {
+ udp4_hwcsum(skb, ihdr->saddr, ihdr->daddr);
+ }
+
+ skb->protocol = htons(ETH_P_IP);
+ skb->pkt_type = PACKET_HOST;
+ skb->dev = priv->dev;
+
+ if (attr->timestamp)
+ skb->tstamp = ns_to_ktime(attr->timestamp);
+
+ return skb;
+}
+
+static struct sk_buff *stmmac_test_get_arp_skb(struct stmmac_priv *priv,
+ struct stmmac_packet_attrs *attr)
+{
+ __be32 ip_src = htonl(attr->ip_src);
+ __be32 ip_dst = htonl(attr->ip_dst);
+ struct sk_buff *skb = NULL;
+
+ skb = arp_create(ARPOP_REQUEST, ETH_P_ARP, ip_dst, priv->dev, ip_src,
+ NULL, attr->src, attr->dst);
+ if (!skb)
+ return NULL;
+
+ skb->pkt_type = PACKET_HOST;
+ skb->dev = priv->dev;
+
+ return skb;
+}
+
+struct stmmac_test_priv {
+ struct stmmac_packet_attrs *packet;
+ struct packet_type pt;
+ struct completion comp;
+ int double_vlan;
+ int vlan_id;
+ int ok;
+};
+
+static int stmmac_test_loopback_validate(struct sk_buff *skb,
+ struct net_device *ndev,
+ struct packet_type *pt,
+ struct net_device *orig_ndev)
+{
+ struct stmmac_test_priv *tpriv = pt->af_packet_priv;
+ const unsigned char *dst = tpriv->packet->dst;
+ unsigned char *src = tpriv->packet->src;
+ struct stmmachdr *shdr;
+ struct ethhdr *ehdr;
+ struct udphdr *uhdr;
+ struct tcphdr *thdr;
+ struct iphdr *ihdr;
+
+ skb = skb_unshare(skb, GFP_ATOMIC);
+ if (!skb)
+ goto out;
+
+ if (skb_linearize(skb))
+ goto out;
+ if (skb_headlen(skb) < (STMMAC_TEST_PKT_SIZE - ETH_HLEN))
+ goto out;
+
+ ehdr = (struct ethhdr *)skb_mac_header(skb);
+ if (dst) {
+ if (!ether_addr_equal_unaligned(ehdr->h_dest, dst))
+ goto out;
+ }
+ if (tpriv->packet->sarc) {
+ if (!ether_addr_equal_unaligned(ehdr->h_source, ehdr->h_dest))
+ goto out;
+ } else if (src) {
+ if (!ether_addr_equal_unaligned(ehdr->h_source, src))
+ goto out;
+ }
+
+ ihdr = ip_hdr(skb);
+ if (tpriv->double_vlan)
+ ihdr = (struct iphdr *)(skb_network_header(skb) + 4);
+
+ if (tpriv->packet->tcp) {
+ if (ihdr->protocol != IPPROTO_TCP)
+ goto out;
+
+ thdr = (struct tcphdr *)((u8 *)ihdr + 4 * ihdr->ihl);
+ if (thdr->dest != htons(tpriv->packet->dport))
+ goto out;
+
+ shdr = (struct stmmachdr *)((u8 *)thdr + sizeof(*thdr));
+ } else {
+ if (ihdr->protocol != IPPROTO_UDP)
+ goto out;
+
+ uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl);
+ if (uhdr->dest != htons(tpriv->packet->dport))
+ goto out;
+
+ shdr = (struct stmmachdr *)((u8 *)uhdr + sizeof(*uhdr));
+ }
+
+ if (shdr->magic != cpu_to_be64(STMMAC_TEST_PKT_MAGIC))
+ goto out;
+ if (tpriv->packet->exp_hash && !skb->hash)
+ goto out;
+ if (tpriv->packet->id != shdr->id)
+ goto out;
+
+ tpriv->ok = true;
+ complete(&tpriv->comp);
+out:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int __stmmac_test_loopback(struct stmmac_priv *priv,
+ struct stmmac_packet_attrs *attr)
+{
+ struct stmmac_test_priv *tpriv;
+ struct sk_buff *skb = NULL;
+ int ret = 0;
+
+ tpriv = kzalloc_obj(*tpriv);
+ if (!tpriv)
+ return -ENOMEM;
+
+ tpriv->ok = false;
+ init_completion(&tpriv->comp);
+
+ tpriv->pt.type = htons(ETH_P_IP);
+ tpriv->pt.func = stmmac_test_loopback_validate;
+ tpriv->pt.dev = priv->dev;
+ tpriv->pt.af_packet_priv = tpriv;
+ tpriv->packet = attr;
+
+ if (!attr->dont_wait)
+ dev_add_pack(&tpriv->pt);
+
+ skb = stmmac_test_get_udp_skb(priv, attr);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ ret = dev_direct_xmit(skb, attr->queue_mapping);
+ if (ret)
+ goto cleanup;
+
+ if (attr->dont_wait)
+ goto cleanup;
+
+ if (!attr->timeout)
+ attr->timeout = STMMAC_LB_TIMEOUT;
+
+ wait_for_completion_timeout(&tpriv->comp, attr->timeout);
+ ret = tpriv->ok ? 0 : -ETIMEDOUT;
+
+cleanup:
+ if (!attr->dont_wait)
+ dev_remove_pack(&tpriv->pt);
+ kfree(tpriv);
+ return ret;
+}
+
+static int stmmac_test_mac_loopback(struct stmmac_priv *priv)
+{
+ struct stmmac_packet_attrs attr = { };
+
+ attr.dst = priv->dev->dev_addr;
+ return __stmmac_test_loopback(priv, &attr);
+}
+
+static int stmmac_test_phy_loopback(struct stmmac_priv *priv)
+{
+ struct stmmac_packet_attrs attr = { };
+ int ret;
+
+ if (!priv->dev->phydev)
+ return -EOPNOTSUPP;
+
+ ret = phy_loopback(priv->dev->phydev, true, 0);
+ if (ret)
+ return ret;
+
+ attr.dst = priv->dev->dev_addr;
+ ret = __stmmac_test_loopback(priv, &attr);
+
+ phy_loopback(priv->dev->phydev, false, 0);
+ return ret;
+}
+
+static int stmmac_test_mmc(struct stmmac_priv *priv)
+{
+ struct stmmac_counters initial, final;
+ int ret;
+
+ memset(&initial, 0, sizeof(initial));
+ memset(&final, 0, sizeof(final));
+
+ if (!priv->dma_cap.rmon)
+ return -EOPNOTSUPP;
+
+ /* Save previous results into internal struct */
+ stmmac_mmc_read(priv, priv->mmcaddr, &priv->mmc);
+
+ ret = stmmac_test_mac_loopback(priv);
+ if (ret)
+ return ret;
+
+ /* These will be loopback results so no need to save them */
+ stmmac_mmc_read(priv, priv->mmcaddr, &final);
+
+ /*
+ * The number of MMC counters available depends on HW configuration
+ * so we just use this one to validate the feature. I hope there is
+ * not a version without this counter.
+ */
+ if (final.mmc_tx_framecount_g <= initial.mmc_tx_framecount_g)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int stmmac_test_eee(struct stmmac_priv *priv)
+{
+ struct stmmac_extra_stats *initial, *final;
+ int retries = 10;
+ int ret;
+
+ if (!priv->dma_cap.eee || !priv->eee_active)
+ return -EOPNOTSUPP;
+
+ initial = kzalloc_obj(*initial);
+ if (!initial)
+ return -ENOMEM;
+
+ final = kzalloc_obj(*final);
+ if (!final) {
+ ret = -ENOMEM;
+ goto out_free_initial;
+ }
+
+ memcpy(initial, &priv->xstats, sizeof(*initial));
+
+ ret = stmmac_test_mac_loopback(priv);
+ if (ret)
+ goto out_free_final;
+
+ /* We have no traffic in the line so, sooner or later it will go LPI */
+ while (--retries) {
+ memcpy(final, &priv->xstats, sizeof(*final));
+
+ if (final->irq_tx_path_in_lpi_mode_n >
+ initial->irq_tx_path_in_lpi_mode_n)
+ break;
+ msleep(100);
+ }
+
+ if (!retries) {
+ ret = -ETIMEDOUT;
+ goto out_free_final;
+ }
+
+ if (final->irq_tx_path_in_lpi_mode_n <=
+ initial->irq_tx_path_in_lpi_mode_n) {
+ ret = -EINVAL;
+ goto out_free_final;
+ }
+
+ if (final->irq_tx_path_exit_lpi_mode_n <=
+ initial->irq_tx_path_exit_lpi_mode_n) {
+ ret = -EINVAL;
+ goto out_free_final;
+ }
+
+out_free_final:
+ kfree(final);
+out_free_initial:
+ kfree(initial);
+ return ret;
+}
+
+static int stmmac_filter_check(struct stmmac_priv *priv)
+{
+ if (!(priv->dev->flags & IFF_PROMISC))
+ return 0;
+
+ netdev_warn(priv->dev, "Test can't be run in promiscuous mode!\n");
+ return -EOPNOTSUPP;
+}
+
+static bool stmmac_hash_check(struct stmmac_priv *priv, unsigned char *addr)
+{
+ int mc_offset = 32 - priv->hw->mcast_bits_log2;
+ struct netdev_hw_addr *ha;
+ u32 hash, hash_nr;
+
+ /* First compute the hash for desired addr */
+ hash = bitrev32(~crc32_le(~0, addr, 6)) >> mc_offset;
+ hash_nr = hash >> 5;
+ hash = 1 << (hash & 0x1f);
+
+ /* Now, check if it collides with any existing one */
+ netdev_for_each_mc_addr(ha, priv->dev) {
+ u32 nr = bitrev32(~crc32_le(~0, ha->addr, ETH_ALEN)) >> mc_offset;
+ if (((nr >> 5) == hash_nr) && ((1 << (nr & 0x1f)) == hash))
+ return false;
+ }
+
+ /* No collisions, address is good to go */
+ return true;
+}
+
+static bool stmmac_perfect_check(struct stmmac_priv *priv, unsigned char *addr)
+{
+ struct netdev_hw_addr *ha;
+
+ /* Check if it collides with any existing one */
+ netdev_for_each_uc_addr(ha, priv->dev) {
+ if (!memcmp(ha->addr, addr, ETH_ALEN))
+ return false;
+ }
+
+ /* No collisions, address is good to go */
+ return true;
+}
+
+static int stmmac_test_hfilt(struct stmmac_priv *priv)
+{
+ unsigned char gd_addr[ETH_ALEN] = {0xf1, 0xee, 0xdd, 0xcc, 0xbb, 0xaa};
+ unsigned char bd_addr[ETH_ALEN] = {0xf1, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct stmmac_packet_attrs attr = { };
+ int ret, tries = 256;
+
+ ret = stmmac_filter_check(priv);
+ if (ret)
+ return ret;
+
+ if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins)
+ return -EOPNOTSUPP;
+
+ while (--tries) {
+ /* We only need to check the bd_addr for collisions */
+ bd_addr[ETH_ALEN - 1] = tries;
+ if (stmmac_hash_check(priv, bd_addr))
+ break;
+ }
+
+ if (!tries)
+ return -EOPNOTSUPP;
+
+ ret = dev_mc_add(priv->dev, gd_addr);
+ if (ret)
+ return ret;
+
+ attr.dst = gd_addr;
+
+ /* Shall receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup;
+
+ attr.dst = bd_addr;
+
+ /* Shall NOT receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL;
+
+cleanup:
+ dev_mc_del(priv->dev, gd_addr);
+ return ret;
+}
+
+static int stmmac_test_pfilt(struct stmmac_priv *priv)
+{
+ unsigned char gd_addr[ETH_ALEN] = {0xf0, 0x01, 0x44, 0x55, 0x66, 0x77};
+ unsigned char bd_addr[ETH_ALEN] = {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct stmmac_packet_attrs attr = { };
+ int ret, tries = 256;
+
+ if (stmmac_filter_check(priv))
+ return -EOPNOTSUPP;
+ if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries)
+ return -EOPNOTSUPP;
+
+ while (--tries) {
+ /* We only need to check the bd_addr for collisions */
+ bd_addr[ETH_ALEN - 1] = tries;
+ if (stmmac_perfect_check(priv, bd_addr))
+ break;
+ }
+
+ if (!tries)
+ return -EOPNOTSUPP;
+
+ ret = dev_uc_add(priv->dev, gd_addr);
+ if (ret)
+ return ret;
+
+ attr.dst = gd_addr;
+
+ /* Shall receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup;
+
+ attr.dst = bd_addr;
+
+ /* Shall NOT receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL;
+
+cleanup:
+ dev_uc_del(priv->dev, gd_addr);
+ return ret;
+}
+
+static int stmmac_test_mcfilt(struct stmmac_priv *priv)
+{
+ unsigned char uc_addr[ETH_ALEN] = {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff};
+ unsigned char mc_addr[ETH_ALEN] = {0xf1, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct stmmac_packet_attrs attr = { };
+ int ret, tries = 256;
+
+ if (stmmac_filter_check(priv))
+ return -EOPNOTSUPP;
+ if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries)
+ return -EOPNOTSUPP;
+ if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins)
+ return -EOPNOTSUPP;
+
+ while (--tries) {
+ /* We only need to check the mc_addr for collisions */
+ mc_addr[ETH_ALEN - 1] = tries;
+ if (stmmac_hash_check(priv, mc_addr))
+ break;
+ }
+
+ if (!tries)
+ return -EOPNOTSUPP;
+
+ ret = dev_uc_add(priv->dev, uc_addr);
+ if (ret)
+ return ret;
+
+ attr.dst = uc_addr;
+
+ /* Shall receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup;
+
+ attr.dst = mc_addr;
+
+ /* Shall NOT receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL;
+
+cleanup:
+ dev_uc_del(priv->dev, uc_addr);
+ return ret;
+}
+
+static int stmmac_test_ucfilt(struct stmmac_priv *priv)
+{
+ unsigned char uc_addr[ETH_ALEN] = {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff};
+ unsigned char mc_addr[ETH_ALEN] = {0xf1, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct stmmac_packet_attrs attr = { };
+ int ret, tries = 256;
+
+ if (stmmac_filter_check(priv))
+ return -EOPNOTSUPP;
+ if (netdev_uc_count(priv->dev) >= priv->hw->unicast_filter_entries)
+ return -EOPNOTSUPP;
+ if (netdev_mc_count(priv->dev) >= priv->hw->multicast_filter_bins)
+ return -EOPNOTSUPP;
+
+ while (--tries) {
+ /* We only need to check the uc_addr for collisions */
+ uc_addr[ETH_ALEN - 1] = tries;
+ if (stmmac_perfect_check(priv, uc_addr))
+ break;
+ }
+
+ if (!tries)
+ return -EOPNOTSUPP;
+
+ ret = dev_mc_add(priv->dev, mc_addr);
+ if (ret)
+ return ret;
+
+ attr.dst = mc_addr;
+
+ /* Shall receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup;
+
+ attr.dst = uc_addr;
+
+ /* Shall NOT receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL;
+
+cleanup:
+ dev_mc_del(priv->dev, mc_addr);
+ return ret;
+}
+
+static int stmmac_test_flowctrl_validate(struct sk_buff *skb,
+ struct net_device *ndev,
+ struct packet_type *pt,
+ struct net_device *orig_ndev)
+{
+ struct stmmac_test_priv *tpriv = pt->af_packet_priv;
+ struct ethhdr *ehdr;
+
+ ehdr = (struct ethhdr *)skb_mac_header(skb);
+ if (!ether_addr_equal_unaligned(ehdr->h_source, orig_ndev->dev_addr))
+ goto out;
+ if (ehdr->h_proto != htons(ETH_P_PAUSE))
+ goto out;
+
+ tpriv->ok = true;
+ complete(&tpriv->comp);
+out:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int stmmac_test_flowctrl(struct stmmac_priv *priv)
+{
+ unsigned char paddr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x01};
+ struct phy_device *phydev = priv->dev->phydev;
+ u32 rx_cnt = priv->plat->rx_queues_to_use;
+ struct stmmac_test_priv *tpriv;
+ unsigned int pkt_count;
+ int i, ret = 0;
+
+ if (!phydev || (!phydev->pause && !phydev->asym_pause))
+ return -EOPNOTSUPP;
+
+ tpriv = kzalloc_obj(*tpriv);
+ if (!tpriv)
+ return -ENOMEM;
+
+ tpriv->ok = false;
+ init_completion(&tpriv->comp);
+ tpriv->pt.type = htons(ETH_P_PAUSE);
+ tpriv->pt.func = stmmac_test_flowctrl_validate;
+ tpriv->pt.dev = priv->dev;
+ tpriv->pt.af_packet_priv = tpriv;
+ dev_add_pack(&tpriv->pt);
+
+ /* Compute minimum number of packets to make FIFO full */
+ pkt_count = priv->plat->rx_fifo_size;
+ if (!pkt_count)
+ pkt_count = priv->dma_cap.rx_fifo_size;
+ pkt_count /= 1400;
+ pkt_count *= 2;
+
+ for (i = 0; i < rx_cnt; i++)
+ stmmac_stop_rx(priv, priv->ioaddr, i);
+
+ ret = dev_set_promiscuity(priv->dev, 1);
+ if (ret)
+ goto cleanup;
+
+ ret = dev_mc_add(priv->dev, paddr);
+ if (ret)
+ goto cleanup;
+
+ for (i = 0; i < pkt_count; i++) {
+ struct stmmac_packet_attrs attr = { };
+
+ attr.dst = priv->dev->dev_addr;
+ attr.dont_wait = true;
+ attr.size = 1400;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup;
+ if (tpriv->ok)
+ break;
+ }
+
+ /* Wait for some time in case RX Watchdog is enabled */
+ msleep(200);
+
+ for (i = 0; i < rx_cnt; i++) {
+ struct stmmac_channel *ch = &priv->channel[i];
+ u32 tail;
+
+ tail = priv->dma_conf.rx_queue[i].dma_rx_phy +
+ (priv->dma_conf.dma_rx_size * sizeof(struct dma_desc));
+
+ stmmac_set_rx_tail_ptr(priv, priv->ioaddr, tail, i);
+ stmmac_start_rx(priv, priv->ioaddr, i);
+
+ local_bh_disable();
+ napi_schedule(&ch->rx_napi);
+ local_bh_enable();
+ }
+
+ wait_for_completion_timeout(&tpriv->comp, STMMAC_LB_TIMEOUT);
+ ret = tpriv->ok ? 0 : -ETIMEDOUT;
+
+cleanup:
+ dev_mc_del(priv->dev, paddr);
+ dev_set_promiscuity(priv->dev, -1);
+ dev_remove_pack(&tpriv->pt);
+ kfree(tpriv);
+ return ret;
+}
+
+static int stmmac_test_rss(struct stmmac_priv *priv)
+{
+ struct stmmac_packet_attrs attr = { };
+
+ if (!priv->dma_cap.rssen || !priv->rss.enable)
+ return -EOPNOTSUPP;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.exp_hash = true;
+ attr.sport = 0x321;
+ attr.dport = 0x123;
+
+ return __stmmac_test_loopback(priv, &attr);
+}
+
+static int stmmac_test_vlan_validate(struct sk_buff *skb,
+ struct net_device *ndev,
+ struct packet_type *pt,
+ struct net_device *orig_ndev)
+{
+ struct stmmac_test_priv *tpriv = pt->af_packet_priv;
+ struct stmmachdr *shdr;
+ struct ethhdr *ehdr;
+ struct udphdr *uhdr;
+ struct iphdr *ihdr;
+ u16 proto;
+
+ proto = tpriv->double_vlan ? ETH_P_8021AD : ETH_P_8021Q;
+
+ skb = skb_unshare(skb, GFP_ATOMIC);
+ if (!skb)
+ goto out;
+
+ if (skb_linearize(skb))
+ goto out;
+ if (skb_headlen(skb) < (STMMAC_TEST_PKT_SIZE - ETH_HLEN))
+ goto out;
+ if (tpriv->vlan_id) {
+ if (skb->vlan_proto != htons(proto))
+ goto out;
+ if (skb->vlan_tci != tpriv->vlan_id) {
+ /* Means filter did not work. */
+ tpriv->ok = false;
+ complete(&tpriv->comp);
+ goto out;
+ }
+ }
+
+ ehdr = (struct ethhdr *)skb_mac_header(skb);
+ if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst))
+ goto out;
+
+ ihdr = ip_hdr(skb);
+ if (tpriv->double_vlan)
+ ihdr = (struct iphdr *)(skb_network_header(skb) + 4);
+ if (ihdr->protocol != IPPROTO_UDP)
+ goto out;
+
+ uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl);
+ if (uhdr->dest != htons(tpriv->packet->dport))
+ goto out;
+
+ shdr = (struct stmmachdr *)((u8 *)uhdr + sizeof(*uhdr));
+ if (shdr->magic != cpu_to_be64(STMMAC_TEST_PKT_MAGIC))
+ goto out;
+
+ tpriv->ok = true;
+ complete(&tpriv->comp);
+
+out:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int __stmmac_test_vlanfilt(struct stmmac_priv *priv)
+{
+ struct stmmac_packet_attrs attr = { };
+ struct stmmac_test_priv *tpriv;
+ struct sk_buff *skb = NULL;
+ int ret = 0, i;
+
+ tpriv = kzalloc_obj(*tpriv);
+ if (!tpriv)
+ return -ENOMEM;
+
+ tpriv->ok = false;
+ init_completion(&tpriv->comp);
+
+ tpriv->pt.type = htons(ETH_P_IP);
+ tpriv->pt.func = stmmac_test_vlan_validate;
+ tpriv->pt.dev = priv->dev;
+ tpriv->pt.af_packet_priv = tpriv;
+ tpriv->packet = &attr;
+
+ /*
+ * As we use HASH filtering, false positives may appear. This is a
+ * specially chosen ID so that adjacent IDs (+4) have different
+ * HASH values.
+ */
+ tpriv->vlan_id = 0x123;
+ dev_add_pack(&tpriv->pt);
+
+ ret = vlan_vid_add(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id);
+ if (ret)
+ goto cleanup;
+
+ for (i = 0; i < 4; i++) {
+ attr.vlan = 1;
+ attr.vlan_id_out = tpriv->vlan_id + i;
+ attr.dst = priv->dev->dev_addr;
+ attr.sport = 9;
+ attr.dport = 9;
+
+ skb = stmmac_test_get_udp_skb(priv, &attr);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto vlan_del;
+ }
+
+ ret = dev_direct_xmit(skb, 0);
+ if (ret)
+ goto vlan_del;
+
+ wait_for_completion_timeout(&tpriv->comp, STMMAC_LB_TIMEOUT);
+ ret = tpriv->ok ? 0 : -ETIMEDOUT;
+ if (ret && !i) {
+ goto vlan_del;
+ } else if (!ret && i) {
+ ret = -EINVAL;
+ goto vlan_del;
+ } else {
+ ret = 0;
+ }
+
+ tpriv->ok = false;
+ }
+
+vlan_del:
+ vlan_vid_del(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id);
+cleanup:
+ dev_remove_pack(&tpriv->pt);
+ kfree(tpriv);
+ return ret;
+}
+
+static int stmmac_test_vlanfilt(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.vlhash)
+ return -EOPNOTSUPP;
+
+ return __stmmac_test_vlanfilt(priv);
+}
+
+static int stmmac_test_vlanfilt_perfect(struct stmmac_priv *priv)
+{
+ int ret, prev_cap = priv->dma_cap.vlhash;
+
+ if (!(priv->dev->features & NETIF_F_HW_VLAN_CTAG_FILTER))
+ return -EOPNOTSUPP;
+
+ priv->dma_cap.vlhash = 0;
+ ret = __stmmac_test_vlanfilt(priv);
+ priv->dma_cap.vlhash = prev_cap;
+
+ return ret;
+}
+
+static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv)
+{
+ struct stmmac_packet_attrs attr = { };
+ struct stmmac_test_priv *tpriv;
+ struct sk_buff *skb = NULL;
+ int ret = 0, i;
+
+ tpriv = kzalloc_obj(*tpriv);
+ if (!tpriv)
+ return -ENOMEM;
+
+ tpriv->ok = false;
+ tpriv->double_vlan = true;
+ init_completion(&tpriv->comp);
+
+ tpriv->pt.type = htons(ETH_P_8021Q);
+ tpriv->pt.func = stmmac_test_vlan_validate;
+ tpriv->pt.dev = priv->dev;
+ tpriv->pt.af_packet_priv = tpriv;
+ tpriv->packet = &attr;
+
+ /*
+ * As we use HASH filtering, false positives may appear. This is a
+ * specially chosen ID so that adjacent IDs (+4) have different
+ * HASH values.
+ */
+ tpriv->vlan_id = 0x123;
+ dev_add_pack(&tpriv->pt);
+
+ ret = vlan_vid_add(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id);
+ if (ret)
+ goto cleanup;
+
+ for (i = 0; i < 4; i++) {
+ attr.vlan = 2;
+ attr.vlan_id_out = tpriv->vlan_id + i;
+ attr.dst = priv->dev->dev_addr;
+ attr.sport = 9;
+ attr.dport = 9;
+
+ skb = stmmac_test_get_udp_skb(priv, &attr);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto vlan_del;
+ }
+
+ ret = dev_direct_xmit(skb, 0);
+ if (ret)
+ goto vlan_del;
+
+ wait_for_completion_timeout(&tpriv->comp, STMMAC_LB_TIMEOUT);
+ ret = tpriv->ok ? 0 : -ETIMEDOUT;
+ if (ret && !i) {
+ goto vlan_del;
+ } else if (!ret && i) {
+ ret = -EINVAL;
+ goto vlan_del;
+ } else {
+ ret = 0;
+ }
+
+ tpriv->ok = false;
+ }
+
+vlan_del:
+ vlan_vid_del(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id);
+cleanup:
+ dev_remove_pack(&tpriv->pt);
+ kfree(tpriv);
+ return ret;
+}
+
+static int stmmac_test_dvlanfilt(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.vlhash)
+ return -EOPNOTSUPP;
+
+ return __stmmac_test_dvlanfilt(priv);
+}
+
+static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv)
+{
+ int ret, prev_cap = priv->dma_cap.vlhash;
+
+ if (!(priv->dev->features & NETIF_F_HW_VLAN_STAG_FILTER))
+ return -EOPNOTSUPP;
+
+ priv->dma_cap.vlhash = 0;
+ ret = __stmmac_test_dvlanfilt(priv);
+ priv->dma_cap.vlhash = prev_cap;
+
+ return ret;
+}
+
+#ifdef CONFIG_NET_CLS_ACT
+static int stmmac_test_rxp(struct stmmac_priv *priv)
+{
+ unsigned char addr[ETH_ALEN] = {0xde, 0xad, 0xbe, 0xef, 0x00, 0x00};
+ struct tc_cls_u32_offload cls_u32 = { };
+ struct stmmac_packet_attrs attr = { };
+ struct tc_action **actions;
+ struct tc_u32_sel *sel;
+ struct tcf_gact *gact;
+ struct tcf_exts *exts;
+ int ret, i, nk = 1;
+
+ if (!tc_can_offload(priv->dev))
+ return -EOPNOTSUPP;
+ if (!priv->dma_cap.frpsel)
+ return -EOPNOTSUPP;
+
+ sel = kzalloc_flex(*sel, keys, nk);
+ if (!sel)
+ return -ENOMEM;
+
+ exts = kzalloc_obj(*exts);
+ if (!exts) {
+ ret = -ENOMEM;
+ goto cleanup_sel;
+ }
+
+ actions = kzalloc_objs(*actions, nk);
+ if (!actions) {
+ ret = -ENOMEM;
+ goto cleanup_exts;
+ }
+
+ gact = kzalloc_objs(*gact, nk);
+ if (!gact) {
+ ret = -ENOMEM;
+ goto cleanup_actions;
+ }
+
+ cls_u32.command = TC_CLSU32_NEW_KNODE;
+ cls_u32.common.chain_index = 0;
+ cls_u32.common.protocol = htons(ETH_P_ALL);
+ cls_u32.knode.exts = exts;
+ cls_u32.knode.sel = sel;
+ cls_u32.knode.handle = 0x123;
+
+ exts->nr_actions = nk;
+ exts->actions = actions;
+ for (i = 0; i < nk; i++) {
+ actions[i] = (struct tc_action *)&gact[i];
+ gact->tcf_action = TC_ACT_SHOT;
+ }
+
+ sel->nkeys = nk;
+ sel->offshift = 0;
+ sel->keys[0].off = 6;
+ sel->keys[0].val = htonl(0xdeadbeef);
+ sel->keys[0].mask = ~0x0;
+
+ ret = stmmac_tc_setup_cls_u32(priv, priv, &cls_u32);
+ if (ret)
+ goto cleanup_act;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.src = addr;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL; /* Shall NOT receive packet */
+
+ cls_u32.command = TC_CLSU32_DELETE_KNODE;
+ stmmac_tc_setup_cls_u32(priv, priv, &cls_u32);
+
+cleanup_act:
+ kfree(gact);
+cleanup_actions:
+ kfree(actions);
+cleanup_exts:
+ kfree(exts);
+cleanup_sel:
+ kfree(sel);
+ return ret;
+}
+#else
+static int stmmac_test_rxp(struct stmmac_priv *priv)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+static int stmmac_test_desc_sai(struct stmmac_priv *priv)
+{
+ unsigned char src[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ struct stmmac_packet_attrs attr = { };
+ int ret;
+
+ if (!priv->dma_cap.vlins)
+ return -EOPNOTSUPP;
+
+ attr.remove_sa = true;
+ attr.sarc = true;
+ attr.src = src;
+ attr.dst = priv->dev->dev_addr;
+
+ priv->sarc_type = 0x1;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+
+ priv->sarc_type = 0x0;
+ return ret;
+}
+
+static int stmmac_test_desc_sar(struct stmmac_priv *priv)
+{
+ unsigned char src[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ struct stmmac_packet_attrs attr = { };
+ int ret;
+
+ if (!priv->dma_cap.vlins)
+ return -EOPNOTSUPP;
+
+ attr.sarc = true;
+ attr.src = src;
+ attr.dst = priv->dev->dev_addr;
+
+ priv->sarc_type = 0x2;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+
+ priv->sarc_type = 0x0;
+ return ret;
+}
+
+static int stmmac_test_reg_sai(struct stmmac_priv *priv)
+{
+ unsigned char src[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ struct stmmac_packet_attrs attr = { };
+ int ret;
+
+ if (!priv->dma_cap.vlins)
+ return -EOPNOTSUPP;
+
+ attr.remove_sa = true;
+ attr.sarc = true;
+ attr.src = src;
+ attr.dst = priv->dev->dev_addr;
+
+ if (stmmac_sarc_configure(priv, priv->ioaddr, 0x2))
+ return -EOPNOTSUPP;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+
+ stmmac_sarc_configure(priv, priv->ioaddr, 0x0);
+ return ret;
+}
+
+static int stmmac_test_reg_sar(struct stmmac_priv *priv)
+{
+ unsigned char src[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ struct stmmac_packet_attrs attr = { };
+ int ret;
+
+ if (!priv->dma_cap.vlins)
+ return -EOPNOTSUPP;
+
+ attr.sarc = true;
+ attr.src = src;
+ attr.dst = priv->dev->dev_addr;
+
+ if (stmmac_sarc_configure(priv, priv->ioaddr, 0x3))
+ return -EOPNOTSUPP;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+
+ stmmac_sarc_configure(priv, priv->ioaddr, 0x0);
+ return ret;
+}
+
+static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan)
+{
+ struct stmmac_packet_attrs attr = { };
+ struct stmmac_test_priv *tpriv;
+ struct sk_buff *skb = NULL;
+ int ret = 0;
+ u16 proto;
+
+ if (!priv->dma_cap.vlins)
+ return -EOPNOTSUPP;
+
+ tpriv = kzalloc_obj(*tpriv);
+ if (!tpriv)
+ return -ENOMEM;
+
+ proto = svlan ? ETH_P_8021AD : ETH_P_8021Q;
+
+ tpriv->ok = false;
+ tpriv->double_vlan = svlan;
+ init_completion(&tpriv->comp);
+
+ tpriv->pt.type = svlan ? htons(ETH_P_8021Q) : htons(ETH_P_IP);
+ tpriv->pt.func = stmmac_test_vlan_validate;
+ tpriv->pt.dev = priv->dev;
+ tpriv->pt.af_packet_priv = tpriv;
+ tpriv->packet = &attr;
+ tpriv->vlan_id = 0x123;
+ dev_add_pack(&tpriv->pt);
+
+ ret = vlan_vid_add(priv->dev, htons(proto), tpriv->vlan_id);
+ if (ret)
+ goto cleanup;
+
+ attr.dst = priv->dev->dev_addr;
+
+ skb = stmmac_test_get_udp_skb(priv, &attr);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto vlan_del;
+ }
+
+ __vlan_hwaccel_put_tag(skb, htons(proto), tpriv->vlan_id);
+ skb->protocol = htons(proto);
+
+ ret = dev_direct_xmit(skb, 0);
+ if (ret)
+ goto vlan_del;
+
+ wait_for_completion_timeout(&tpriv->comp, STMMAC_LB_TIMEOUT);
+ ret = tpriv->ok ? 0 : -ETIMEDOUT;
+
+vlan_del:
+ vlan_vid_del(priv->dev, htons(proto), tpriv->vlan_id);
+cleanup:
+ dev_remove_pack(&tpriv->pt);
+ kfree(tpriv);
+ return ret;
+}
+
+static int stmmac_test_vlanoff(struct stmmac_priv *priv)
+{
+ return stmmac_test_vlanoff_common(priv, false);
+}
+
+static int stmmac_test_svlanoff(struct stmmac_priv *priv)
+{
+ if (!priv->dma_cap.dvlan)
+ return -EOPNOTSUPP;
+ return stmmac_test_vlanoff_common(priv, true);
+}
+
+#ifdef CONFIG_NET_CLS_ACT
+static int __stmmac_test_l3filt(struct stmmac_priv *priv, u32 dst, u32 src,
+ u32 dst_mask, u32 src_mask)
+{
+ struct flow_dissector_key_ipv4_addrs key, mask;
+ unsigned long dummy_cookie = 0xdeadbeef;
+ struct stmmac_packet_attrs attr = { };
+ struct flow_dissector *dissector;
+ struct flow_cls_offload *cls;
+ int ret, old_enable = 0;
+ struct flow_rule *rule;
+
+ if (!tc_can_offload(priv->dev))
+ return -EOPNOTSUPP;
+ if (!priv->dma_cap.l3l4fnum)
+ return -EOPNOTSUPP;
+ if (priv->rss.enable) {
+ old_enable = priv->rss.enable;
+ priv->rss.enable = false;
+ stmmac_rss_configure(priv, priv->hw, NULL,
+ priv->plat->rx_queues_to_use);
+ }
+
+ dissector = kzalloc_obj(*dissector);
+ if (!dissector) {
+ ret = -ENOMEM;
+ goto cleanup_rss;
+ }
+
+ dissector->used_keys |= (1ULL << FLOW_DISSECTOR_KEY_IPV4_ADDRS);
+ dissector->offset[FLOW_DISSECTOR_KEY_IPV4_ADDRS] = 0;
+
+ cls = kzalloc_obj(*cls);
+ if (!cls) {
+ ret = -ENOMEM;
+ goto cleanup_dissector;
+ }
+
+ cls->common.chain_index = 0;
+ cls->command = FLOW_CLS_REPLACE;
+ cls->cookie = dummy_cookie;
+
+ rule = kzalloc_flex(*rule, action.entries, 1);
+ if (!rule) {
+ ret = -ENOMEM;
+ goto cleanup_cls;
+ }
+
+ rule->match.dissector = dissector;
+ rule->match.key = (void *)&key;
+ rule->match.mask = (void *)&mask;
+
+ key.src = htonl(src);
+ key.dst = htonl(dst);
+ mask.src = src_mask;
+ mask.dst = dst_mask;
+
+ cls->rule = rule;
+
+ rule->action.entries[0].id = FLOW_ACTION_DROP;
+ rule->action.entries[0].hw_stats = FLOW_ACTION_HW_STATS_ANY;
+ rule->action.num_entries = 1;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.ip_dst = dst;
+ attr.ip_src = src;
+
+ /* Shall receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup_rule;
+
+ ret = stmmac_tc_setup_cls(priv, priv, cls);
+ if (ret)
+ goto cleanup_rule;
+
+ /* Shall NOT receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL;
+
+ cls->command = FLOW_CLS_DESTROY;
+ stmmac_tc_setup_cls(priv, priv, cls);
+cleanup_rule:
+ kfree(rule);
+cleanup_cls:
+ kfree(cls);
+cleanup_dissector:
+ kfree(dissector);
+cleanup_rss:
+ if (old_enable) {
+ priv->rss.enable = old_enable;
+ stmmac_rss_configure(priv, priv->hw, &priv->rss,
+ priv->plat->rx_queues_to_use);
+ }
+
+ return ret;
+}
+#else
+static int __stmmac_test_l3filt(struct stmmac_priv *priv, u32 dst, u32 src,
+ u32 dst_mask, u32 src_mask)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+static int stmmac_test_l3filt_da(struct stmmac_priv *priv)
+{
+ u32 addr = 0x10203040;
+
+ return __stmmac_test_l3filt(priv, addr, 0, ~0, 0);
+}
+
+static int stmmac_test_l3filt_sa(struct stmmac_priv *priv)
+{
+ u32 addr = 0x10203040;
+
+ return __stmmac_test_l3filt(priv, 0, addr, 0, ~0);
+}
+
+#ifdef CONFIG_NET_CLS_ACT
+static int __stmmac_test_l4filt(struct stmmac_priv *priv, u32 dst, u32 src,
+ u32 dst_mask, u32 src_mask, bool udp)
+{
+ struct {
+ struct flow_dissector_key_basic bkey;
+ struct flow_dissector_key_ports key;
+ } __aligned(BITS_PER_LONG / 8) keys;
+ struct {
+ struct flow_dissector_key_basic bmask;
+ struct flow_dissector_key_ports mask;
+ } __aligned(BITS_PER_LONG / 8) masks;
+ unsigned long dummy_cookie = 0xdeadbeef;
+ struct stmmac_packet_attrs attr = { };
+ struct flow_dissector *dissector;
+ struct flow_cls_offload *cls;
+ int ret, old_enable = 0;
+ struct flow_rule *rule;
+
+ if (!tc_can_offload(priv->dev))
+ return -EOPNOTSUPP;
+ if (!priv->dma_cap.l3l4fnum)
+ return -EOPNOTSUPP;
+ if (priv->rss.enable) {
+ old_enable = priv->rss.enable;
+ priv->rss.enable = false;
+ stmmac_rss_configure(priv, priv->hw, NULL,
+ priv->plat->rx_queues_to_use);
+ }
+
+ dissector = kzalloc_obj(*dissector);
+ if (!dissector) {
+ ret = -ENOMEM;
+ goto cleanup_rss;
+ }
+
+ dissector->used_keys |= (1ULL << FLOW_DISSECTOR_KEY_BASIC);
+ dissector->used_keys |= (1ULL << FLOW_DISSECTOR_KEY_PORTS);
+ dissector->offset[FLOW_DISSECTOR_KEY_BASIC] = 0;
+ dissector->offset[FLOW_DISSECTOR_KEY_PORTS] = offsetof(typeof(keys), key);
+
+ cls = kzalloc_obj(*cls);
+ if (!cls) {
+ ret = -ENOMEM;
+ goto cleanup_dissector;
+ }
+
+ cls->common.chain_index = 0;
+ cls->command = FLOW_CLS_REPLACE;
+ cls->cookie = dummy_cookie;
+
+ rule = kzalloc_flex(*rule, action.entries, 1);
+ if (!rule) {
+ ret = -ENOMEM;
+ goto cleanup_cls;
+ }
+
+ rule->match.dissector = dissector;
+ rule->match.key = (void *)&keys;
+ rule->match.mask = (void *)&masks;
+
+ keys.bkey.ip_proto = udp ? IPPROTO_UDP : IPPROTO_TCP;
+ keys.key.src = htons(src);
+ keys.key.dst = htons(dst);
+ masks.mask.src = src_mask;
+ masks.mask.dst = dst_mask;
+
+ cls->rule = rule;
+
+ rule->action.entries[0].id = FLOW_ACTION_DROP;
+ rule->action.entries[0].hw_stats = FLOW_ACTION_HW_STATS_ANY;
+ rule->action.num_entries = 1;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.tcp = !udp;
+ attr.sport = src;
+ attr.dport = dst;
+ attr.ip_dst = 0;
+
+ /* Shall receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto cleanup_rule;
+
+ ret = stmmac_tc_setup_cls(priv, priv, cls);
+ if (ret)
+ goto cleanup_rule;
+
+ /* Shall NOT receive packet */
+ ret = __stmmac_test_loopback(priv, &attr);
+ ret = ret ? 0 : -EINVAL;
+
+ cls->command = FLOW_CLS_DESTROY;
+ stmmac_tc_setup_cls(priv, priv, cls);
+cleanup_rule:
+ kfree(rule);
+cleanup_cls:
+ kfree(cls);
+cleanup_dissector:
+ kfree(dissector);
+cleanup_rss:
+ if (old_enable) {
+ priv->rss.enable = old_enable;
+ stmmac_rss_configure(priv, priv->hw, &priv->rss,
+ priv->plat->rx_queues_to_use);
+ }
+
+ return ret;
+}
+#else
+static int __stmmac_test_l4filt(struct stmmac_priv *priv, u32 dst, u32 src,
+ u32 dst_mask, u32 src_mask, bool udp)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+static int stmmac_test_l4filt_da_tcp(struct stmmac_priv *priv)
+{
+ u16 dummy_port = 0x123;
+
+ return __stmmac_test_l4filt(priv, dummy_port, 0, ~0, 0, false);
+}
+
+static int stmmac_test_l4filt_sa_tcp(struct stmmac_priv *priv)
+{
+ u16 dummy_port = 0x123;
+
+ return __stmmac_test_l4filt(priv, 0, dummy_port, 0, ~0, false);
+}
+
+static int stmmac_test_l4filt_da_udp(struct stmmac_priv *priv)
+{
+ u16 dummy_port = 0x123;
+
+ return __stmmac_test_l4filt(priv, dummy_port, 0, ~0, 0, true);
+}
+
+static int stmmac_test_l4filt_sa_udp(struct stmmac_priv *priv)
+{
+ u16 dummy_port = 0x123;
+
+ return __stmmac_test_l4filt(priv, 0, dummy_port, 0, ~0, true);
+}
+
+static int stmmac_test_arp_validate(struct sk_buff *skb,
+ struct net_device *ndev,
+ struct packet_type *pt,
+ struct net_device *orig_ndev)
+{
+ struct stmmac_test_priv *tpriv = pt->af_packet_priv;
+ struct ethhdr *ehdr;
+ struct arphdr *ahdr;
+
+ ehdr = (struct ethhdr *)skb_mac_header(skb);
+ if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->src))
+ goto out;
+
+ ahdr = arp_hdr(skb);
+ if (ahdr->ar_op != htons(ARPOP_REPLY))
+ goto out;
+
+ tpriv->ok = true;
+ complete(&tpriv->comp);
+out:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int stmmac_test_arpoffload(struct stmmac_priv *priv)
+{
+ unsigned char src[ETH_ALEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+ unsigned char dst[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct stmmac_packet_attrs attr = { };
+ struct stmmac_test_priv *tpriv;
+ struct sk_buff *skb = NULL;
+ u32 ip_addr = 0xdeadcafe;
+ u32 ip_src = 0xdeadbeef;
+ int ret;
+
+ if (!priv->dma_cap.arpoffsel)
+ return -EOPNOTSUPP;
+
+ tpriv = kzalloc_obj(*tpriv);
+ if (!tpriv)
+ return -ENOMEM;
+
+ tpriv->ok = false;
+ init_completion(&tpriv->comp);
+
+ tpriv->pt.type = htons(ETH_P_ARP);
+ tpriv->pt.func = stmmac_test_arp_validate;
+ tpriv->pt.dev = priv->dev;
+ tpriv->pt.af_packet_priv = tpriv;
+ tpriv->packet = &attr;
+ dev_add_pack(&tpriv->pt);
+
+ attr.src = src;
+ attr.ip_src = ip_src;
+ attr.dst = dst;
+ attr.ip_dst = ip_addr;
+
+ skb = stmmac_test_get_arp_skb(priv, &attr);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ ret = stmmac_set_arp_offload(priv, priv->hw, true, ip_addr);
+ if (ret) {
+ kfree_skb(skb);
+ goto cleanup;
+ }
+
+ ret = dev_set_promiscuity(priv->dev, 1);
+ if (ret) {
+ kfree_skb(skb);
+ goto cleanup;
+ }
+
+ ret = dev_direct_xmit(skb, 0);
+ if (ret)
+ goto cleanup_promisc;
+
+ wait_for_completion_timeout(&tpriv->comp, STMMAC_LB_TIMEOUT);
+ ret = tpriv->ok ? 0 : -ETIMEDOUT;
+
+cleanup_promisc:
+ dev_set_promiscuity(priv->dev, -1);
+cleanup:
+ stmmac_set_arp_offload(priv, priv->hw, false, 0x0);
+ dev_remove_pack(&tpriv->pt);
+ kfree(tpriv);
+ return ret;
+}
+
+static int __stmmac_test_jumbo(struct stmmac_priv *priv, u16 queue)
+{
+ struct stmmac_packet_attrs attr = { };
+ int size = priv->dma_conf.dma_buf_sz;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.max_size = size - ETH_FCS_LEN;
+ attr.queue_mapping = queue;
+
+ return __stmmac_test_loopback(priv, &attr);
+}
+
+static int stmmac_test_jumbo(struct stmmac_priv *priv)
+{
+ return __stmmac_test_jumbo(priv, 0);
+}
+
+static int stmmac_test_mjumbo(struct stmmac_priv *priv)
+{
+ u32 chan, tx_cnt = priv->plat->tx_queues_to_use;
+ int ret;
+
+ if (tx_cnt <= 1)
+ return -EOPNOTSUPP;
+
+ for (chan = 0; chan < tx_cnt; chan++) {
+ ret = __stmmac_test_jumbo(priv, chan);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmmac_test_sph(struct stmmac_priv *priv)
+{
+ unsigned long cnt_end, cnt_start = priv->xstats.rx_split_hdr_pkt_n;
+ struct stmmac_packet_attrs attr = { };
+ int ret;
+
+ if (!priv->sph_active)
+ return -EOPNOTSUPP;
+
+ /* Check for UDP first */
+ attr.dst = priv->dev->dev_addr;
+ attr.tcp = false;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ return ret;
+
+ cnt_end = priv->xstats.rx_split_hdr_pkt_n;
+ if (cnt_end <= cnt_start)
+ return -EINVAL;
+
+ /* Check for TCP now */
+ cnt_start = cnt_end;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.tcp = true;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ return ret;
+
+ cnt_end = priv->xstats.rx_split_hdr_pkt_n;
+ if (cnt_end <= cnt_start)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int stmmac_test_tbs(struct stmmac_priv *priv)
+{
+#define STMMAC_TBS_LT_OFFSET (500 * 1000 * 1000) /* 500 ms*/
+ struct stmmac_packet_attrs attr = { };
+ struct tc_etf_qopt_offload qopt;
+ u64 start_time, curr_time = 0;
+ unsigned long flags;
+ int ret, i;
+
+ if (!priv->hwts_tx_en)
+ return -EOPNOTSUPP;
+
+ /* Find first TBS enabled Queue, if any */
+ for (i = 0; i < priv->plat->tx_queues_to_use; i++)
+ if (priv->dma_conf.tx_queue[i].tbs & STMMAC_TBS_AVAIL)
+ break;
+
+ if (i >= priv->plat->tx_queues_to_use)
+ return -EOPNOTSUPP;
+
+ qopt.enable = true;
+ qopt.queue = i;
+
+ ret = stmmac_tc_setup_etf(priv, priv, &qopt);
+ if (ret)
+ return ret;
+
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_get_systime(priv, priv->ptpaddr, &curr_time);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ if (!curr_time) {
+ ret = -EOPNOTSUPP;
+ goto fail_disable;
+ }
+
+ start_time = curr_time;
+ curr_time += STMMAC_TBS_LT_OFFSET;
+
+ attr.dst = priv->dev->dev_addr;
+ attr.timestamp = curr_time;
+ attr.timeout = nsecs_to_jiffies(2 * STMMAC_TBS_LT_OFFSET);
+ attr.queue_mapping = i;
+
+ ret = __stmmac_test_loopback(priv, &attr);
+ if (ret)
+ goto fail_disable;
+
+ /* Check if expected time has elapsed */
+ read_lock_irqsave(&priv->ptp_lock, flags);
+ stmmac_get_systime(priv, priv->ptpaddr, &curr_time);
+ read_unlock_irqrestore(&priv->ptp_lock, flags);
+
+ if ((curr_time - start_time) < STMMAC_TBS_LT_OFFSET)
+ ret = -EINVAL;
+
+fail_disable:
+ qopt.enable = false;
+ stmmac_tc_setup_etf(priv, priv, &qopt);
+ return ret;
+}
+
+#define STMMAC_LOOPBACK_NONE 0
+#define STMMAC_LOOPBACK_MAC 1
+#define STMMAC_LOOPBACK_PHY 2
+
+static const struct stmmac_test {
+ char name[ETH_GSTRING_LEN];
+ int lb;
+ int (*fn)(struct stmmac_priv *priv);
+} stmmac_selftests[] = {
+ {
+ .name = "MAC Loopback ",
+ .lb = STMMAC_LOOPBACK_MAC,
+ .fn = stmmac_test_mac_loopback,
+ }, {
+ .name = "PHY Loopback ",
+ .lb = STMMAC_LOOPBACK_NONE, /* Test will handle it */
+ .fn = stmmac_test_phy_loopback,
+ }, {
+ .name = "MMC Counters ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_mmc,
+ }, {
+ .name = "EEE ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_eee,
+ }, {
+ .name = "Hash Filter MC ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_hfilt,
+ }, {
+ .name = "Perfect Filter UC ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_pfilt,
+ }, {
+ .name = "MC Filter ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_mcfilt,
+ }, {
+ .name = "UC Filter ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_ucfilt,
+ }, {
+ .name = "Flow Control ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_flowctrl,
+ }, {
+ .name = "RSS ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_rss,
+ }, {
+ .name = "VLAN Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_vlanfilt,
+ }, {
+ .name = "VLAN Filtering (perf) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_vlanfilt_perfect,
+ }, {
+ .name = "Double VLAN Filter ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_dvlanfilt,
+ }, {
+ .name = "Double VLAN Filter (perf) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_dvlanfilt_perfect,
+ }, {
+ .name = "Flexible RX Parser ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_rxp,
+ }, {
+ .name = "SA Insertion (desc) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_desc_sai,
+ }, {
+ .name = "SA Replacement (desc) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_desc_sar,
+ }, {
+ .name = "SA Insertion (reg) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_reg_sai,
+ }, {
+ .name = "SA Replacement (reg) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_reg_sar,
+ }, {
+ .name = "VLAN TX Insertion ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_vlanoff,
+ }, {
+ .name = "SVLAN TX Insertion ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_svlanoff,
+ }, {
+ .name = "L3 DA Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_l3filt_da,
+ }, {
+ .name = "L3 SA Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_l3filt_sa,
+ }, {
+ .name = "L4 DA TCP Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_l4filt_da_tcp,
+ }, {
+ .name = "L4 SA TCP Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_l4filt_sa_tcp,
+ }, {
+ .name = "L4 DA UDP Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_l4filt_da_udp,
+ }, {
+ .name = "L4 SA UDP Filtering ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_l4filt_sa_udp,
+ }, {
+ .name = "ARP Offload ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_arpoffload,
+ }, {
+ .name = "Jumbo Frame ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_jumbo,
+ }, {
+ .name = "Multichannel Jumbo ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_mjumbo,
+ }, {
+ .name = "Split Header ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_sph,
+ }, {
+ .name = "TBS (ETF Scheduler) ",
+ .lb = STMMAC_LOOPBACK_PHY,
+ .fn = stmmac_test_tbs,
+ },
+};
+
+void stmmac_selftest_run(struct net_device *dev,
+ struct ethtool_test *etest, u64 *buf)
+{
+ struct stmmac_priv *priv = netdev_priv(dev);
+ int count = stmmac_selftest_get_count(priv);
+ int i, ret;
+
+ memset(buf, 0, sizeof(*buf) * count);
+ stmmac_test_next_id = 0;
+
+ if (etest->flags != ETH_TEST_FL_OFFLINE) {
+ netdev_err(priv->dev, "Only offline tests are supported\n");
+ etest->flags |= ETH_TEST_FL_FAILED;
+ return;
+ } else if (!netif_carrier_ok(dev)) {
+ netdev_err(priv->dev, "You need valid Link to execute tests\n");
+ etest->flags |= ETH_TEST_FL_FAILED;
+ return;
+ }
+
+ /* Wait for queues drain */
+ msleep(200);
+
+ for (i = 0; i < count; i++) {
+ ret = 0;
+
+ switch (stmmac_selftests[i].lb) {
+ case STMMAC_LOOPBACK_PHY:
+ ret = -EOPNOTSUPP;
+ if (dev->phydev)
+ ret = phy_loopback(dev->phydev, true, 0);
+ if (!ret)
+ break;
+ fallthrough;
+ case STMMAC_LOOPBACK_MAC:
+ ret = stmmac_set_mac_loopback(priv, priv->ioaddr, true);
+ break;
+ case STMMAC_LOOPBACK_NONE:
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ /*
+ * First tests will always be MAC / PHY loopback. If any of
+ * them is not supported we abort earlier.
+ */
+ if (ret) {
+ netdev_err(priv->dev, "Loopback is not supported\n");
+ etest->flags |= ETH_TEST_FL_FAILED;
+ break;
+ }
+
+ ret = stmmac_selftests[i].fn(priv);
+ if (ret && (ret != -EOPNOTSUPP))
+ etest->flags |= ETH_TEST_FL_FAILED;
+ buf[i] = ret;
+
+ switch (stmmac_selftests[i].lb) {
+ case STMMAC_LOOPBACK_PHY:
+ ret = -EOPNOTSUPP;
+ if (dev->phydev)
+ ret = phy_loopback(dev->phydev, false, 0);
+ if (!ret)
+ break;
+ fallthrough;
+ case STMMAC_LOOPBACK_MAC:
+ stmmac_set_mac_loopback(priv, priv->ioaddr, false);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void stmmac_selftest_get_strings(struct stmmac_priv *priv, u8 *data)
+{
+ u8 *p = data;
+ int i;
+
+ for (i = 0; i < stmmac_selftest_get_count(priv); i++) {
+ snprintf(p, ETH_GSTRING_LEN, "%2d. %s", i + 1,
+ stmmac_selftests[i].name);
+ p += ETH_GSTRING_LEN;
+ }
+}
+
+int stmmac_selftest_get_count(struct stmmac_priv *priv)
+{
+ return ARRAY_SIZE(stmmac_selftests);
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
new file mode 100644
index 000000000..d78652718
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
@@ -0,0 +1,1289 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * stmmac TC Handling (HW only)
+ */
+
+#include <net/pkt_cls.h>
+#include <net/tc_act/tc_gact.h>
+#include "common.h"
+#include "dwmac4.h"
+#include "dwmac5.h"
+#include "stmmac.h"
+
+static void tc_fill_all_pass_entry(struct stmmac_tc_entry *entry)
+{
+ memset(entry, 0, sizeof(*entry));
+ entry->in_use = true;
+ entry->is_last = true;
+ entry->is_frag = false;
+ entry->prio = ~0x0;
+ entry->handle = 0;
+ entry->val.match_data = 0x0;
+ entry->val.match_en = 0x0;
+ entry->val.af = 1;
+ entry->val.dma_ch_no = 0x0;
+}
+
+static struct stmmac_tc_entry *tc_find_entry(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls,
+ bool free)
+{
+ struct stmmac_tc_entry *entry, *first = NULL, *dup = NULL;
+ u32 loc = cls->knode.handle;
+ int i;
+
+ for (i = 0; i < priv->tc_entries_max; i++) {
+ entry = &priv->tc_entries[i];
+ if (!entry->in_use && !first && free)
+ first = entry;
+ if ((entry->handle == loc) && !free && !entry->is_frag)
+ dup = entry;
+ }
+
+ if (dup)
+ return dup;
+ if (first) {
+ first->handle = loc;
+ first->in_use = true;
+
+ /* Reset HW values */
+ memset(&first->val, 0, sizeof(first->val));
+ }
+
+ return first;
+}
+
+static int tc_fill_actions(struct stmmac_tc_entry *entry,
+ struct stmmac_tc_entry *frag,
+ struct tc_cls_u32_offload *cls)
+{
+ struct stmmac_tc_entry *action_entry = entry;
+ const struct tc_action *act;
+ struct tcf_exts *exts;
+ int i;
+
+ exts = cls->knode.exts;
+ if (!tcf_exts_has_actions(exts))
+ return -EINVAL;
+ if (frag)
+ action_entry = frag;
+
+ tcf_exts_for_each_action(i, act, exts) {
+ /* Accept */
+ if (is_tcf_gact_ok(act)) {
+ action_entry->val.af = 1;
+ break;
+ }
+ /* Drop */
+ if (is_tcf_gact_shot(act)) {
+ action_entry->val.rf = 1;
+ break;
+ }
+
+ /* Unsupported */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tc_fill_entry(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls)
+{
+ struct stmmac_tc_entry *entry, *frag = NULL;
+ struct tc_u32_sel *sel = cls->knode.sel;
+ u32 off, data, mask, real_off, rem;
+ u32 prio = cls->common.prio << 16;
+ int ret;
+
+ /* Only 1 match per entry */
+ if (sel->nkeys <= 0 || sel->nkeys > 1)
+ return -EINVAL;
+
+ off = sel->keys[0].off << sel->offshift;
+ data = sel->keys[0].val;
+ mask = sel->keys[0].mask;
+
+ switch (ntohs(cls->common.protocol)) {
+ case ETH_P_ALL:
+ break;
+ case ETH_P_IP:
+ off += ETH_HLEN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (off > priv->tc_off_max)
+ return -EINVAL;
+
+ real_off = off / 4;
+ rem = off % 4;
+
+ entry = tc_find_entry(priv, cls, true);
+ if (!entry)
+ return -EINVAL;
+
+ if (rem) {
+ frag = tc_find_entry(priv, cls, true);
+ if (!frag) {
+ ret = -EINVAL;
+ goto err_unuse;
+ }
+
+ entry->frag_ptr = frag;
+ entry->val.match_en = (mask << (rem * 8)) &
+ GENMASK(31, rem * 8);
+ entry->val.match_data = (data << (rem * 8)) &
+ GENMASK(31, rem * 8);
+ entry->val.frame_offset = real_off;
+ entry->prio = prio;
+
+ frag->val.match_en = (mask >> (rem * 8)) &
+ GENMASK(rem * 8 - 1, 0);
+ frag->val.match_data = (data >> (rem * 8)) &
+ GENMASK(rem * 8 - 1, 0);
+ frag->val.frame_offset = real_off + 1;
+ frag->prio = prio;
+ frag->is_frag = true;
+ } else {
+ entry->frag_ptr = NULL;
+ entry->val.match_en = mask;
+ entry->val.match_data = data;
+ entry->val.frame_offset = real_off;
+ entry->prio = prio;
+ }
+
+ ret = tc_fill_actions(entry, frag, cls);
+ if (ret)
+ goto err_unuse;
+
+ return 0;
+
+err_unuse:
+ if (frag)
+ frag->in_use = false;
+ entry->in_use = false;
+ return ret;
+}
+
+static void tc_unfill_entry(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls)
+{
+ struct stmmac_tc_entry *entry;
+
+ entry = tc_find_entry(priv, cls, false);
+ if (!entry)
+ return;
+
+ entry->in_use = false;
+ if (entry->frag_ptr) {
+ entry = entry->frag_ptr;
+ entry->is_frag = false;
+ entry->in_use = false;
+ }
+}
+
+static int tc_config_knode(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls)
+{
+ int ret;
+
+ ret = tc_fill_entry(priv, cls);
+ if (ret)
+ return ret;
+
+ ret = stmmac_rxp_config(priv, priv->hw->pcsr, priv->tc_entries,
+ priv->tc_entries_max);
+ if (ret)
+ goto err_unfill;
+
+ return 0;
+
+err_unfill:
+ tc_unfill_entry(priv, cls);
+ return ret;
+}
+
+static int tc_delete_knode(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls)
+{
+ /* Set entry and fragments as not used */
+ tc_unfill_entry(priv, cls);
+
+ return stmmac_rxp_config(priv, priv->hw->pcsr, priv->tc_entries,
+ priv->tc_entries_max);
+}
+
+static int tc_setup_cls_u32(struct stmmac_priv *priv,
+ struct tc_cls_u32_offload *cls)
+{
+ switch (cls->command) {
+ case TC_CLSU32_REPLACE_KNODE:
+ tc_unfill_entry(priv, cls);
+ fallthrough;
+ case TC_CLSU32_NEW_KNODE:
+ return tc_config_knode(priv, cls);
+ case TC_CLSU32_DELETE_KNODE:
+ return tc_delete_knode(priv, cls);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int tc_rfs_init(struct stmmac_priv *priv)
+{
+ int i;
+
+ priv->rfs_entries_max[STMMAC_RFS_T_VLAN] = 8;
+ priv->rfs_entries_max[STMMAC_RFS_T_LLDP] = 1;
+ priv->rfs_entries_max[STMMAC_RFS_T_1588] = 1;
+
+ for (i = 0; i < STMMAC_RFS_T_MAX; i++)
+ priv->rfs_entries_total += priv->rfs_entries_max[i];
+
+ priv->rfs_entries = devm_kcalloc(priv->device,
+ priv->rfs_entries_total,
+ sizeof(*priv->rfs_entries),
+ GFP_KERNEL);
+ if (!priv->rfs_entries)
+ return -ENOMEM;
+
+ dev_info(priv->device, "Enabled RFS Flow TC (entries=%d)\n",
+ priv->rfs_entries_total);
+
+ return 0;
+}
+
+static int tc_init(struct stmmac_priv *priv)
+{
+ struct dma_features *dma_cap = &priv->dma_cap;
+ unsigned int count;
+ int ret, i;
+
+ priv->flow_entries_max = dma_cap->l3l4fnum;
+ if (priv->flow_entries_max) {
+ priv->flow_entries = devm_kcalloc(priv->device,
+ priv->flow_entries_max,
+ sizeof(*priv->flow_entries),
+ GFP_KERNEL);
+ if (!priv->flow_entries)
+ return -ENOMEM;
+
+ for (i = 0; i < priv->flow_entries_max; i++)
+ priv->flow_entries[i].idx = i;
+
+ dev_info(priv->device, "Enabled L3L4 Flow TC (entries=%d)\n",
+ priv->flow_entries_max);
+ }
+
+ ret = tc_rfs_init(priv);
+ if (ret)
+ return -ENOMEM;
+
+ /* Fail silently as we can still use remaining features, e.g. CBS */
+ if (!dma_cap->frpsel)
+ return 0;
+
+ switch (dma_cap->frpbs) {
+ case 0x0:
+ priv->tc_off_max = 64;
+ break;
+ case 0x1:
+ priv->tc_off_max = 128;
+ break;
+ case 0x2:
+ priv->tc_off_max = 256;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (dma_cap->frpes) {
+ case 0x0:
+ count = 64;
+ break;
+ case 0x1:
+ count = 128;
+ break;
+ case 0x2:
+ count = 256;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Reserve one last filter which lets all pass */
+ priv->tc_entries_max = count;
+ priv->tc_entries = devm_kcalloc(priv->device,
+ count, sizeof(*priv->tc_entries), GFP_KERNEL);
+ if (!priv->tc_entries)
+ return -ENOMEM;
+
+ tc_fill_all_pass_entry(&priv->tc_entries[count - 1]);
+
+ dev_info(priv->device, "Enabling HW TC (entries=%d, max_off=%d)\n",
+ priv->tc_entries_max, priv->tc_off_max);
+
+ return 0;
+}
+
+static int tc_setup_cbs(struct stmmac_priv *priv,
+ struct tc_cbs_qopt_offload *qopt)
+{
+ u32 tx_queues_count = priv->plat->tx_queues_to_use;
+ s64 port_transmit_rate_kbps;
+ u32 queue = qopt->queue;
+ u32 mode_to_use;
+ u64 value;
+ u32 ptr;
+ int ret;
+
+ /* Queue 0 is not AVB capable */
+ if (queue <= 0 || queue >= tx_queues_count)
+ return -EINVAL;
+ if (!priv->dma_cap.av)
+ return -EOPNOTSUPP;
+
+ port_transmit_rate_kbps = qopt->idleslope - qopt->sendslope;
+
+ if (qopt->enable) {
+ /* Port Transmit Rate and Speed Divider */
+ switch (div_s64(port_transmit_rate_kbps, 1000)) {
+ case SPEED_10000:
+ case SPEED_5000:
+ ptr = 32;
+ break;
+ case SPEED_2500:
+ case SPEED_1000:
+ ptr = 8;
+ break;
+ case SPEED_100:
+ ptr = 4;
+ break;
+ default:
+ netdev_err(priv->dev,
+ "Invalid portTransmitRate %lld (idleSlope - sendSlope)\n",
+ port_transmit_rate_kbps);
+ return -EINVAL;
+ }
+ } else {
+ ptr = 0;
+ }
+
+ mode_to_use = priv->plat->tx_queues_cfg[queue].mode_to_use;
+ if (mode_to_use == MTL_QUEUE_DCB && qopt->enable) {
+ ret = stmmac_dma_qmode(priv, priv->ioaddr, queue, MTL_QUEUE_AVB);
+ if (ret)
+ return ret;
+
+ priv->plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_AVB;
+ } else if (!qopt->enable) {
+ ret = stmmac_dma_qmode(priv, priv->ioaddr, queue,
+ MTL_QUEUE_DCB);
+ if (ret)
+ return ret;
+
+ priv->plat->tx_queues_cfg[queue].mode_to_use = MTL_QUEUE_DCB;
+ return 0;
+ }
+
+ /* Final adjustments for HW */
+ value = div_s64(qopt->idleslope * 1024ll * ptr, port_transmit_rate_kbps);
+ priv->plat->tx_queues_cfg[queue].idle_slope = value & GENMASK(31, 0);
+
+ value = div_s64(-qopt->sendslope * 1024ll * ptr, port_transmit_rate_kbps);
+ priv->plat->tx_queues_cfg[queue].send_slope = value & GENMASK(31, 0);
+
+ value = qopt->hicredit * 1024ll * 8;
+ priv->plat->tx_queues_cfg[queue].high_credit = value & GENMASK(31, 0);
+
+ value = qopt->locredit * 1024ll * 8;
+ priv->plat->tx_queues_cfg[queue].low_credit = value & GENMASK(31, 0);
+
+ ret = stmmac_config_cbs(priv, priv->hw,
+ priv->plat->tx_queues_cfg[queue].send_slope,
+ priv->plat->tx_queues_cfg[queue].idle_slope,
+ priv->plat->tx_queues_cfg[queue].high_credit,
+ priv->plat->tx_queues_cfg[queue].low_credit,
+ queue);
+ if (ret)
+ return ret;
+
+ dev_info(priv->device, "CBS queue %d: send %d, idle %d, hi %d, lo %d\n",
+ queue, qopt->sendslope, qopt->idleslope,
+ qopt->hicredit, qopt->locredit);
+ return 0;
+}
+
+static int tc_parse_flow_actions(struct stmmac_priv *priv,
+ struct flow_action *action,
+ struct stmmac_flow_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ struct flow_action_entry *act;
+ int i;
+
+ if (!flow_action_has_entries(action))
+ return -EINVAL;
+
+ if (!flow_action_basic_hw_stats_check(action, extack))
+ return -EOPNOTSUPP;
+
+ flow_action_for_each(i, act, action) {
+ switch (act->id) {
+ case FLOW_ACTION_DROP:
+ entry->action |= STMMAC_FLOW_ACTION_DROP;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ /* Nothing to do, maybe inverse filter ? */
+ return 0;
+}
+
+#define ETHER_TYPE_FULL_MASK cpu_to_be16(~0)
+
+static int tc_add_basic_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls,
+ struct stmmac_flow_entry *entry)
+{
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct flow_dissector *dissector = rule->match.dissector;
+ struct flow_match_basic match;
+
+ /* Nothing to do here */
+ if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_BASIC))
+ return -EINVAL;
+
+ flow_rule_match_basic(rule, &match);
+
+ entry->ip_proto = match.key->ip_proto;
+ return 0;
+}
+
+static int tc_add_ip4_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls,
+ struct stmmac_flow_entry *entry)
+{
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct flow_dissector *dissector = rule->match.dissector;
+ bool inv = entry->action & STMMAC_FLOW_ACTION_DROP;
+ struct flow_match_ipv4_addrs match;
+ u32 hw_match;
+ int ret;
+
+ /* Nothing to do here */
+ if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_IPV4_ADDRS))
+ return -EINVAL;
+
+ flow_rule_match_ipv4_addrs(rule, &match);
+ hw_match = ntohl(match.key->src) & ntohl(match.mask->src);
+ if (hw_match) {
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, true,
+ false, true, inv, hw_match);
+ if (ret)
+ return ret;
+ }
+
+ hw_match = ntohl(match.key->dst) & ntohl(match.mask->dst);
+ if (hw_match) {
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, true,
+ false, false, inv, hw_match);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tc_add_ports_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls,
+ struct stmmac_flow_entry *entry)
+{
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct flow_dissector *dissector = rule->match.dissector;
+ bool inv = entry->action & STMMAC_FLOW_ACTION_DROP;
+ struct flow_match_ports match;
+ u32 hw_match;
+ bool is_udp;
+ int ret;
+
+ /* Nothing to do here */
+ if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_PORTS))
+ return -EINVAL;
+
+ switch (entry->ip_proto) {
+ case IPPROTO_TCP:
+ is_udp = false;
+ break;
+ case IPPROTO_UDP:
+ is_udp = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ flow_rule_match_ports(rule, &match);
+
+ hw_match = ntohs(match.key->src) & ntohs(match.mask->src);
+ if (hw_match) {
+ ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx, true,
+ is_udp, true, inv, hw_match);
+ if (ret)
+ return ret;
+ }
+
+ hw_match = ntohs(match.key->dst) & ntohs(match.mask->dst);
+ if (hw_match) {
+ ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx, true,
+ is_udp, false, inv, hw_match);
+ if (ret)
+ return ret;
+ }
+
+ entry->is_l4 = true;
+ return 0;
+}
+
+static struct stmmac_flow_entry *tc_find_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls,
+ bool get_free)
+{
+ int i;
+
+ for (i = 0; i < priv->flow_entries_max; i++) {
+ struct stmmac_flow_entry *entry = &priv->flow_entries[i];
+
+ if (entry->cookie == cls->cookie)
+ return entry;
+ if (get_free && (entry->in_use == false))
+ return entry;
+ }
+
+ return NULL;
+}
+
+static struct {
+ int (*fn)(struct stmmac_priv *priv, struct flow_cls_offload *cls,
+ struct stmmac_flow_entry *entry);
+} tc_flow_parsers[] = {
+ { .fn = tc_add_basic_flow },
+ { .fn = tc_add_ip4_flow },
+ { .fn = tc_add_ports_flow },
+};
+
+static int tc_add_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ struct stmmac_flow_entry *entry = tc_find_flow(priv, cls, false);
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ int i, ret;
+
+ if (!entry) {
+ entry = tc_find_flow(priv, cls, true);
+ if (!entry)
+ return -ENOENT;
+ }
+
+ ret = tc_parse_flow_actions(priv, &rule->action, entry,
+ cls->common.extack);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(tc_flow_parsers); i++) {
+ ret = tc_flow_parsers[i].fn(priv, cls, entry);
+ if (!ret)
+ entry->in_use = true;
+ }
+
+ if (!entry->in_use)
+ return -EINVAL;
+
+ entry->cookie = cls->cookie;
+ return 0;
+}
+
+static int tc_del_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ struct stmmac_flow_entry *entry = tc_find_flow(priv, cls, false);
+ int ret;
+
+ if (!entry || !entry->in_use)
+ return -ENOENT;
+
+ if (entry->is_l4) {
+ ret = stmmac_config_l4_filter(priv, priv->hw, entry->idx, false,
+ false, false, false, 0);
+ } else {
+ ret = stmmac_config_l3_filter(priv, priv->hw, entry->idx, false,
+ false, false, false, 0);
+ }
+
+ entry->in_use = false;
+ entry->cookie = 0;
+ entry->is_l4 = false;
+ return ret;
+}
+
+static struct stmmac_rfs_entry *tc_find_rfs(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls,
+ bool get_free)
+{
+ int i;
+
+ for (i = 0; i < priv->rfs_entries_total; i++) {
+ struct stmmac_rfs_entry *entry = &priv->rfs_entries[i];
+
+ if (entry->cookie == cls->cookie)
+ return entry;
+ if (get_free && entry->in_use == false)
+ return entry;
+ }
+
+ return NULL;
+}
+
+#define VLAN_PRIO_FULL_MASK (0x07)
+
+static int tc_add_vlan_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct flow_dissector *dissector = rule->match.dissector;
+ int tc = tc_classid_to_hwtc(priv->dev, cls->classid);
+ struct flow_match_vlan match;
+
+ if (!entry) {
+ entry = tc_find_rfs(priv, cls, true);
+ if (!entry)
+ return -ENOENT;
+ }
+
+ if (priv->rfs_entries_cnt[STMMAC_RFS_T_VLAN] >=
+ priv->rfs_entries_max[STMMAC_RFS_T_VLAN])
+ return -ENOENT;
+
+ /* Nothing to do here */
+ if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_VLAN))
+ return -EINVAL;
+
+ if (tc < 0) {
+ netdev_err(priv->dev, "Invalid traffic class\n");
+ return -EINVAL;
+ }
+
+ flow_rule_match_vlan(rule, &match);
+
+ if (match.mask->vlan_priority) {
+ u32 prio;
+
+ if (match.mask->vlan_priority != VLAN_PRIO_FULL_MASK) {
+ netdev_err(priv->dev, "Only full mask is supported for VLAN priority");
+ return -EINVAL;
+ }
+
+ prio = BIT(match.key->vlan_priority);
+ stmmac_rx_queue_prio(priv, priv->hw, prio, tc);
+
+ entry->in_use = true;
+ entry->cookie = cls->cookie;
+ entry->tc = tc;
+ entry->type = STMMAC_RFS_T_VLAN;
+ priv->rfs_entries_cnt[STMMAC_RFS_T_VLAN]++;
+ }
+
+ return 0;
+}
+
+static int tc_del_vlan_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);
+
+ if (!entry || !entry->in_use || entry->type != STMMAC_RFS_T_VLAN)
+ return -ENOENT;
+
+ stmmac_rx_queue_prio(priv, priv->hw, 0, entry->tc);
+
+ entry->in_use = false;
+ entry->cookie = 0;
+ entry->tc = 0;
+ entry->type = 0;
+
+ priv->rfs_entries_cnt[STMMAC_RFS_T_VLAN]--;
+
+ return 0;
+}
+
+static int tc_add_ethtype_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct flow_dissector *dissector = rule->match.dissector;
+ int tc = tc_classid_to_hwtc(priv->dev, cls->classid);
+ struct flow_match_basic match;
+
+ if (!entry) {
+ entry = tc_find_rfs(priv, cls, true);
+ if (!entry)
+ return -ENOENT;
+ }
+
+ /* Nothing to do here */
+ if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_BASIC))
+ return -EINVAL;
+
+ if (tc < 0) {
+ netdev_err(priv->dev, "Invalid traffic class\n");
+ return -EINVAL;
+ }
+
+ flow_rule_match_basic(rule, &match);
+
+ if (match.mask->n_proto) {
+ u16 etype = ntohs(match.key->n_proto);
+
+ if (match.mask->n_proto != ETHER_TYPE_FULL_MASK) {
+ netdev_err(priv->dev, "Only full mask is supported for EthType filter");
+ return -EINVAL;
+ }
+ switch (etype) {
+ case ETH_P_LLDP:
+ if (priv->rfs_entries_cnt[STMMAC_RFS_T_LLDP] >=
+ priv->rfs_entries_max[STMMAC_RFS_T_LLDP])
+ return -ENOENT;
+
+ entry->type = STMMAC_RFS_T_LLDP;
+ priv->rfs_entries_cnt[STMMAC_RFS_T_LLDP]++;
+
+ stmmac_rx_queue_routing(priv, priv->hw,
+ PACKET_DCBCPQ, tc);
+ break;
+ case ETH_P_1588:
+ if (priv->rfs_entries_cnt[STMMAC_RFS_T_1588] >=
+ priv->rfs_entries_max[STMMAC_RFS_T_1588])
+ return -ENOENT;
+
+ entry->type = STMMAC_RFS_T_1588;
+ priv->rfs_entries_cnt[STMMAC_RFS_T_1588]++;
+
+ stmmac_rx_queue_routing(priv, priv->hw,
+ PACKET_PTPQ, tc);
+ break;
+ default:
+ netdev_err(priv->dev, "EthType(0x%x) is not supported", etype);
+ return -EINVAL;
+ }
+
+ entry->in_use = true;
+ entry->cookie = cls->cookie;
+ entry->tc = tc;
+ entry->etype = etype;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int tc_del_ethtype_flow(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);
+
+ if (!entry || !entry->in_use ||
+ entry->type < STMMAC_RFS_T_LLDP ||
+ entry->type > STMMAC_RFS_T_1588)
+ return -ENOENT;
+
+ switch (entry->etype) {
+ case ETH_P_LLDP:
+ stmmac_rx_queue_routing(priv, priv->hw,
+ PACKET_DCBCPQ, 0);
+ priv->rfs_entries_cnt[STMMAC_RFS_T_LLDP]--;
+ break;
+ case ETH_P_1588:
+ stmmac_rx_queue_routing(priv, priv->hw,
+ PACKET_PTPQ, 0);
+ priv->rfs_entries_cnt[STMMAC_RFS_T_1588]--;
+ break;
+ default:
+ netdev_err(priv->dev, "EthType(0x%x) is not supported",
+ entry->etype);
+ return -EINVAL;
+ }
+
+ entry->in_use = false;
+ entry->cookie = 0;
+ entry->tc = 0;
+ entry->etype = 0;
+ entry->type = 0;
+
+ return 0;
+}
+
+static int tc_add_flow_cls(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ int ret;
+
+ ret = tc_add_flow(priv, cls);
+ if (!ret)
+ return ret;
+
+ ret = tc_add_ethtype_flow(priv, cls);
+ if (!ret)
+ return ret;
+
+ return tc_add_vlan_flow(priv, cls);
+}
+
+static int tc_del_flow_cls(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ int ret;
+
+ ret = tc_del_flow(priv, cls);
+ if (!ret)
+ return ret;
+
+ ret = tc_del_ethtype_flow(priv, cls);
+ if (!ret)
+ return ret;
+
+ return tc_del_vlan_flow(priv, cls);
+}
+
+static int tc_setup_cls(struct stmmac_priv *priv,
+ struct flow_cls_offload *cls)
+{
+ int ret = 0;
+
+ /* When RSS is enabled, the filtering will be bypassed */
+ if (priv->rss.enable)
+ return -EBUSY;
+
+ switch (cls->command) {
+ case FLOW_CLS_REPLACE:
+ ret = tc_add_flow_cls(priv, cls);
+ break;
+ case FLOW_CLS_DESTROY:
+ ret = tc_del_flow_cls(priv, cls);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+struct timespec64 stmmac_calc_tas_basetime(ktime_t old_base_time,
+ ktime_t current_time,
+ u64 cycle_time)
+{
+ struct timespec64 time;
+
+ if (ktime_after(old_base_time, current_time)) {
+ time = ktime_to_timespec64(old_base_time);
+ } else {
+ s64 n;
+ ktime_t base_time;
+
+ n = div64_s64(ktime_sub_ns(current_time, old_base_time),
+ cycle_time);
+ base_time = ktime_add_ns(old_base_time,
+ (n + 1) * cycle_time);
+
+ time = ktime_to_timespec64(base_time);
+ }
+
+ return time;
+}
+
+static void tc_taprio_map_maxsdu_txq(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ u32 num_tc = qopt->mqprio.qopt.num_tc;
+ u32 offset, count, i, j;
+
+ /* QueueMaxSDU received from the driver corresponds to the Linux traffic
+ * class. Map queueMaxSDU per Linux traffic class to DWMAC Tx queues.
+ */
+ for (i = 0; i < num_tc; i++) {
+ if (!qopt->max_sdu[i])
+ continue;
+
+ offset = qopt->mqprio.qopt.offset[i];
+ count = qopt->mqprio.qopt.count[i];
+
+ for (j = offset; j < offset + count; j++)
+ priv->est->max_sdu[j] = qopt->max_sdu[i] + ETH_HLEN - ETH_TLEN;
+ }
+}
+
+static int tc_taprio_configure(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ u32 size, wid = priv->dma_cap.estwid, dep = priv->dma_cap.estdep;
+ struct netlink_ext_ack *extack = qopt->mqprio.extack;
+ struct timespec64 time, current_time, qopt_time;
+ ktime_t current_time_ns;
+ int i, ret = 0;
+ u64 ctr;
+
+ if (qopt->base_time < 0)
+ return -ERANGE;
+
+ if (!priv->dma_cap.estsel)
+ return -EOPNOTSUPP;
+
+ switch (wid) {
+ case 0x1:
+ wid = 16;
+ break;
+ case 0x2:
+ wid = 20;
+ break;
+ case 0x3:
+ wid = 24;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ switch (dep) {
+ case 0x1:
+ dep = 64;
+ break;
+ case 0x2:
+ dep = 128;
+ break;
+ case 0x3:
+ dep = 256;
+ break;
+ case 0x4:
+ dep = 512;
+ break;
+ case 0x5:
+ dep = 1024;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (qopt->cmd == TAPRIO_CMD_DESTROY)
+ goto disable;
+
+ if (qopt->num_entries > dep)
+ return -EINVAL;
+ if (!qopt->cycle_time)
+ return -ERANGE;
+ if (qopt->cycle_time_extension >= BIT(wid + 7))
+ return -ERANGE;
+
+ if (!priv->est) {
+ priv->est = devm_kzalloc(priv->device, sizeof(*priv->est),
+ GFP_KERNEL);
+ if (!priv->est)
+ return -ENOMEM;
+
+ mutex_init(&priv->est_lock);
+ } else {
+ mutex_lock(&priv->est_lock);
+ memset(priv->est, 0, sizeof(*priv->est));
+ mutex_unlock(&priv->est_lock);
+ }
+
+ size = qopt->num_entries;
+
+ mutex_lock(&priv->est_lock);
+ priv->est->gcl_size = size;
+ priv->est->enable = qopt->cmd == TAPRIO_CMD_REPLACE;
+ mutex_unlock(&priv->est_lock);
+
+ for (i = 0; i < size; i++) {
+ s64 delta_ns = qopt->entries[i].interval;
+ u32 gates = qopt->entries[i].gate_mask;
+
+ if (delta_ns > GENMASK(wid - 1, 0))
+ return -ERANGE;
+ if (gates > GENMASK(31 - wid, 0))
+ return -ERANGE;
+
+ switch (qopt->entries[i].command) {
+ case TC_TAPRIO_CMD_SET_GATES:
+ break;
+ case TC_TAPRIO_CMD_SET_AND_HOLD:
+ gates |= BIT(0);
+ break;
+ case TC_TAPRIO_CMD_SET_AND_RELEASE:
+ gates &= ~BIT(0);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ priv->est->gcl[i] = delta_ns | (gates << wid);
+ }
+
+ mutex_lock(&priv->est_lock);
+ /* Adjust for real system time */
+ priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, &current_time);
+ current_time_ns = timespec64_to_ktime(current_time);
+ time = stmmac_calc_tas_basetime(qopt->base_time, current_time_ns,
+ qopt->cycle_time);
+
+ priv->est->btr[0] = (u32)time.tv_nsec;
+ priv->est->btr[1] = (u32)time.tv_sec;
+
+ qopt_time = ktime_to_timespec64(qopt->base_time);
+ priv->est->btr_reserve[0] = (u32)qopt_time.tv_nsec;
+ priv->est->btr_reserve[1] = (u32)qopt_time.tv_sec;
+
+ ctr = qopt->cycle_time;
+ priv->est->ctr[0] = do_div(ctr, NSEC_PER_SEC);
+ priv->est->ctr[1] = (u32)ctr;
+
+ priv->est->ter = qopt->cycle_time_extension;
+
+ tc_taprio_map_maxsdu_txq(priv, qopt);
+
+ ret = stmmac_est_configure(priv, priv, priv->est,
+ priv->plat->clk_ptp_rate);
+ mutex_unlock(&priv->est_lock);
+ if (ret) {
+ netdev_err(priv->dev, "failed to configure EST\n");
+ goto disable;
+ }
+
+ ret = stmmac_fpe_map_preemption_class(priv, priv->dev, extack,
+ qopt->mqprio.preemptible_tcs);
+ if (ret)
+ goto disable;
+
+ return 0;
+
+disable:
+ if (priv->est) {
+ mutex_lock(&priv->est_lock);
+ priv->est->enable = false;
+ stmmac_est_configure(priv, priv, priv->est,
+ priv->plat->clk_ptp_rate);
+ /* Reset taprio status */
+ for (i = 0; i < priv->plat->tx_queues_to_use; i++) {
+ priv->xstats.max_sdu_txq_drop[i] = 0;
+ priv->xstats.mtl_est_txq_hlbf[i] = 0;
+ priv->xstats.mtl_est_txq_hlbs[i] = 0;
+ }
+ mutex_unlock(&priv->est_lock);
+ }
+
+ stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0);
+
+ return ret;
+}
+
+static void tc_taprio_stats(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ u64 window_drops = 0;
+ int i = 0;
+
+ for (i = 0; i < priv->plat->tx_queues_to_use; i++)
+ window_drops += priv->xstats.max_sdu_txq_drop[i] +
+ priv->xstats.mtl_est_txq_hlbf[i] +
+ priv->xstats.mtl_est_txq_hlbs[i];
+ qopt->stats.window_drops = window_drops;
+
+ /* Transmission overrun doesn't happen for stmmac, hence always 0 */
+ qopt->stats.tx_overruns = 0;
+}
+
+static void tc_taprio_queue_stats(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ struct tc_taprio_qopt_queue_stats *q_stats = &qopt->queue_stats;
+ int queue = qopt->queue_stats.queue;
+
+ q_stats->stats.window_drops = priv->xstats.max_sdu_txq_drop[queue] +
+ priv->xstats.mtl_est_txq_hlbf[queue] +
+ priv->xstats.mtl_est_txq_hlbs[queue];
+
+ /* Transmission overrun doesn't happen for stmmac, hence always 0 */
+ q_stats->stats.tx_overruns = 0;
+}
+
+static int tc_setup_taprio(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ int err = 0;
+
+ switch (qopt->cmd) {
+ case TAPRIO_CMD_REPLACE:
+ case TAPRIO_CMD_DESTROY:
+ err = tc_taprio_configure(priv, qopt);
+ break;
+ case TAPRIO_CMD_STATS:
+ tc_taprio_stats(priv, qopt);
+ break;
+ case TAPRIO_CMD_QUEUE_STATS:
+ tc_taprio_queue_stats(priv, qopt);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ }
+
+ return err;
+}
+
+static int tc_setup_taprio_without_fpe(struct stmmac_priv *priv,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ if (!qopt->mqprio.preemptible_tcs)
+ return tc_setup_taprio(priv, qopt);
+
+ NL_SET_ERR_MSG_MOD(qopt->mqprio.extack,
+ "taprio with FPE is not implemented for this MAC");
+
+ return -EOPNOTSUPP;
+}
+
+static int tc_setup_etf(struct stmmac_priv *priv,
+ struct tc_etf_qopt_offload *qopt)
+{
+ if (!priv->dma_cap.tbssel)
+ return -EOPNOTSUPP;
+ if (qopt->queue >= priv->plat->tx_queues_to_use)
+ return -EINVAL;
+ if (!(priv->dma_conf.tx_queue[qopt->queue].tbs & STMMAC_TBS_AVAIL))
+ return -EINVAL;
+
+ if (qopt->enable)
+ priv->dma_conf.tx_queue[qopt->queue].tbs |= STMMAC_TBS_EN;
+ else
+ priv->dma_conf.tx_queue[qopt->queue].tbs &= ~STMMAC_TBS_EN;
+
+ netdev_info(priv->dev, "%s ETF for Queue %d\n",
+ qopt->enable ? "enabled" : "disabled", qopt->queue);
+ return 0;
+}
+
+static int tc_query_caps(struct stmmac_priv *priv,
+ struct tc_query_caps_base *base)
+{
+ switch (base->type) {
+ case TC_SETUP_QDISC_MQPRIO: {
+ struct tc_mqprio_caps *caps = base->caps;
+
+ caps->validate_queue_counts = true;
+
+ return 0;
+ }
+ case TC_SETUP_QDISC_TAPRIO: {
+ struct tc_taprio_caps *caps = base->caps;
+
+ if (!priv->dma_cap.estsel)
+ return -EOPNOTSUPP;
+
+ caps->gate_mask_per_txq = true;
+ caps->supports_queue_max_sdu = true;
+
+ return 0;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void stmmac_reset_tc_mqprio(struct net_device *ndev,
+ struct netlink_ext_ack *extack)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+
+ netdev_reset_tc(ndev);
+ netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use);
+ stmmac_fpe_map_preemption_class(priv, ndev, extack, 0);
+}
+
+static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv,
+ struct tc_mqprio_qopt_offload *mqprio)
+{
+ struct netlink_ext_ack *extack = mqprio->extack;
+ struct tc_mqprio_qopt *qopt = &mqprio->qopt;
+ u32 offset, count, num_stack_tx_queues = 0;
+ struct net_device *ndev = priv->dev;
+ u32 num_tc = qopt->num_tc;
+ int err;
+
+ if (!num_tc) {
+ stmmac_reset_tc_mqprio(ndev, extack);
+ return 0;
+ }
+
+ err = netdev_set_num_tc(ndev, num_tc);
+ if (err)
+ return err;
+
+ for (u32 tc = 0; tc < num_tc; tc++) {
+ offset = qopt->offset[tc];
+ count = qopt->count[tc];
+ num_stack_tx_queues += count;
+
+ err = netdev_set_tc_queue(ndev, tc, count, offset);
+ if (err)
+ goto err_reset_tc;
+ }
+
+ err = netif_set_real_num_tx_queues(ndev, num_stack_tx_queues);
+ if (err)
+ goto err_reset_tc;
+
+ err = stmmac_fpe_map_preemption_class(priv, ndev, extack,
+ mqprio->preemptible_tcs);
+ if (err)
+ goto err_reset_tc;
+
+ return 0;
+
+err_reset_tc:
+ stmmac_reset_tc_mqprio(ndev, extack);
+
+ return err;
+}
+
+static int tc_setup_mqprio_unimplemented(struct stmmac_priv *priv,
+ struct tc_mqprio_qopt_offload *mqprio)
+{
+ NL_SET_ERR_MSG_MOD(mqprio->extack,
+ "mqprio HW offload is not implemented for this MAC");
+ return -EOPNOTSUPP;
+}
+
+const struct stmmac_tc_ops dwmac4_tc_ops = {
+ .init = tc_init,
+ .setup_cls_u32 = tc_setup_cls_u32,
+ .setup_cbs = tc_setup_cbs,
+ .setup_cls = tc_setup_cls,
+ .setup_taprio = tc_setup_taprio_without_fpe,
+ .setup_etf = tc_setup_etf,
+ .query_caps = tc_query_caps,
+ .setup_mqprio = tc_setup_mqprio_unimplemented,
+};
+
+const struct stmmac_tc_ops dwmac510_tc_ops = {
+ .init = tc_init,
+ .setup_cls_u32 = tc_setup_cls_u32,
+ .setup_cbs = tc_setup_cbs,
+ .setup_cls = tc_setup_cls,
+ .setup_taprio = tc_setup_taprio,
+ .setup_etf = tc_setup_etf,
+ .query_caps = tc_query_caps,
+ .setup_mqprio = tc_setup_dwmac510_mqprio,
+};
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c
new file mode 100644
index 000000000..e24efe3bf
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025, Altera Corporation
+ * stmmac VLAN (802.1Q) handling
+ */
+
+#include "stmmac.h"
+#include "stmmac_vlan.h"
+
+static void vlan_write_single(struct net_device *dev, u16 vid)
+{
+ void __iomem *ioaddr = (void __iomem *)dev->base_addr;
+ u32 val;
+
+ val = readl(ioaddr + VLAN_TAG);
+ val &= ~VLAN_TAG_VID;
+ val |= VLAN_TAG_ETV | vid;
+
+ writel(val, ioaddr + VLAN_TAG);
+}
+
+static int vlan_write_filter(struct net_device *dev,
+ struct mac_device_info *hw,
+ u8 index, u32 data)
+{
+ void __iomem *ioaddr = (void __iomem *)dev->base_addr;
+ int ret;
+ u32 val;
+
+ if (index >= hw->num_vlan)
+ return -EINVAL;
+
+ writel(data, ioaddr + VLAN_TAG_DATA);
+
+ val = readl(ioaddr + VLAN_TAG);
+ val &= ~(VLAN_TAG_CTRL_OFS_MASK |
+ VLAN_TAG_CTRL_CT |
+ VLAN_TAG_CTRL_OB);
+ val |= (index << VLAN_TAG_CTRL_OFS_SHIFT) | VLAN_TAG_CTRL_OB;
+
+ writel(val, ioaddr + VLAN_TAG);
+
+ ret = readl_poll_timeout(ioaddr + VLAN_TAG, val,
+ !(val & VLAN_TAG_CTRL_OB),
+ 1000, 500000);
+ if (ret) {
+ netdev_err(dev, "Timeout accessing MAC_VLAN_Tag_Filter\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int vlan_add_hw_rx_fltr(struct net_device *dev,
+ struct mac_device_info *hw,
+ __be16 proto, u16 vid)
+{
+ int index = -1;
+ u32 val = 0;
+ int i, ret;
+
+ if (vid > 4095)
+ return -EINVAL;
+
+ /* Single Rx VLAN Filter */
+ if (hw->num_vlan == 1) {
+ /* For single VLAN filter, VID 0 means VLAN promiscuous */
+ if (vid == 0) {
+ netdev_warn(dev, "Adding VLAN ID 0 is not supported\n");
+ return -EPERM;
+ }
+
+ if (hw->vlan_filter[0] & VLAN_TAG_VID) {
+ netdev_err(dev, "Only single VLAN ID supported\n");
+ return -EPERM;
+ }
+
+ hw->vlan_filter[0] = vid;
+
+ if (netif_running(dev))
+ vlan_write_single(dev, vid);
+
+ return 0;
+ }
+
+ /* Extended Rx VLAN Filter Enable */
+ val |= VLAN_TAG_DATA_ETV | VLAN_TAG_DATA_VEN | vid;
+
+ for (i = 0; i < hw->num_vlan; i++) {
+ if (hw->vlan_filter[i] == val)
+ return 0;
+ else if (!(hw->vlan_filter[i] & VLAN_TAG_DATA_VEN))
+ index = i;
+ }
+
+ if (index == -1) {
+ netdev_err(dev, "MAC_VLAN_Tag_Filter full (size: %0u)\n",
+ hw->num_vlan);
+ return -EPERM;
+ }
+
+ if (netif_running(dev)) {
+ ret = vlan_write_filter(dev, hw, index, val);
+ if (ret)
+ return ret;
+ }
+
+ hw->vlan_filter[index] = val;
+
+ return 0;
+}
+
+static int vlan_del_hw_rx_fltr(struct net_device *dev,
+ struct mac_device_info *hw,
+ __be16 proto, u16 vid)
+{
+ int i, ret = 0;
+
+ /* Single Rx VLAN Filter */
+ if (hw->num_vlan == 1) {
+ if ((hw->vlan_filter[0] & VLAN_TAG_VID) == vid) {
+ hw->vlan_filter[0] = 0;
+
+ if (netif_running(dev))
+ vlan_write_single(dev, 0);
+ }
+ return 0;
+ }
+
+ /* Extended Rx VLAN Filter Enable */
+ for (i = 0; i < hw->num_vlan; i++) {
+ if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) &&
+ ((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid)) {
+
+ if (netif_running(dev)) {
+ ret = vlan_write_filter(dev, hw, i, 0);
+ if (ret)
+ return ret;
+ }
+
+ hw->vlan_filter[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void vlan_restore_hw_rx_fltr(struct net_device *dev,
+ struct mac_device_info *hw)
+{
+ int i;
+
+ /* Single Rx VLAN Filter */
+ if (hw->num_vlan == 1) {
+ vlan_write_single(dev, hw->vlan_filter[0]);
+ return;
+ }
+
+ /* Extended Rx VLAN Filter Enable */
+ for (i = 0; i < hw->num_vlan; i++)
+ vlan_write_filter(dev, hw, i, hw->vlan_filter[i]);
+}
+
+static void vlan_update_hash(struct mac_device_info *hw, u32 hash,
+ u16 perfect_match, bool is_double)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ writel(hash, ioaddr + VLAN_HASH_TABLE);
+
+ value = readl(ioaddr + VLAN_TAG);
+
+ if (hash) {
+ value |= VLAN_VTHM | VLAN_ETV;
+ if (is_double) {
+ value |= VLAN_EDVLP;
+ value |= VLAN_ESVL;
+ value |= VLAN_DOVLTC;
+ } else {
+ value &= ~VLAN_EDVLP;
+ value &= ~VLAN_ESVL;
+ value &= ~VLAN_DOVLTC;
+ }
+
+ writel(value, ioaddr + VLAN_TAG);
+ } else if (perfect_match) {
+ u32 value = VLAN_ETV;
+
+ if (is_double) {
+ value |= VLAN_EDVLP;
+ value |= VLAN_ESVL;
+ value |= VLAN_DOVLTC;
+ } else {
+ value &= ~VLAN_EDVLP;
+ value &= ~VLAN_ESVL;
+ value &= ~VLAN_DOVLTC;
+ }
+
+ writel(value | perfect_match, ioaddr + VLAN_TAG);
+ } else {
+ value &= ~(VLAN_VTHM | VLAN_ETV);
+ value &= ~(VLAN_EDVLP | VLAN_ESVL);
+ value &= ~VLAN_DOVLTC;
+ value &= ~VLAN_VID;
+
+ writel(value, ioaddr + VLAN_TAG);
+ }
+}
+
+static void vlan_enable(struct mac_device_info *hw, u32 type)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value;
+
+ value = readl(ioaddr + VLAN_INCL);
+ value |= VLAN_VLTI;
+ value &= ~VLAN_CSVL; /* Only use CVLAN */
+ value &= ~VLAN_VLC;
+ value |= (type << VLAN_VLC_SHIFT) & VLAN_VLC;
+ writel(value, ioaddr + VLAN_INCL);
+}
+
+static void vlan_rx_hw(struct mac_device_info *hw,
+ struct dma_desc *rx_desc, struct sk_buff *skb)
+{
+ if (hw->desc->get_rx_vlan_valid(rx_desc)) {
+ u16 vid = hw->desc->get_rx_vlan_tci(rx_desc);
+
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
+ }
+}
+
+static void vlan_set_hw_mode(struct mac_device_info *hw)
+{
+ void __iomem *ioaddr = hw->pcsr;
+ u32 value = readl(ioaddr + VLAN_TAG);
+
+ value &= ~VLAN_TAG_CTRL_EVLS_MASK;
+
+ if (hw->hw_vlan_en)
+ /* Always strip VLAN on Receive */
+ value |= VLAN_TAG_STRIP_ALL;
+ else
+ /* Do not strip VLAN on Receive */
+ value |= VLAN_TAG_STRIP_NONE;
+
+ /* Enable outer VLAN Tag in Rx DMA descriptor */
+ value |= VLAN_TAG_CTRL_EVLRXS;
+ writel(value, ioaddr + VLAN_TAG);
+}
+
+static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash,
+ u16 perfect_match, bool is_double)
+{
+ void __iomem *ioaddr = hw->pcsr;
+
+ writel(hash, ioaddr + VLAN_HASH_TABLE);
+
+ if (hash) {
+ u32 value = readl(ioaddr + XGMAC_PACKET_FILTER);
+
+ value |= XGMAC_FILTER_VTFE;
+
+ writel(value, ioaddr + XGMAC_PACKET_FILTER);
+
+ value = readl(ioaddr + VLAN_TAG);
+
+ value |= VLAN_VTHM | VLAN_ETV;
+ if (is_double) {
+ value |= VLAN_EDVLP;
+ value |= VLAN_ESVL;
+ value |= VLAN_DOVLTC;
+ } else {
+ value &= ~VLAN_EDVLP;
+ value &= ~VLAN_ESVL;
+ value &= ~VLAN_DOVLTC;
+ }
+
+ value &= ~VLAN_VID;
+ writel(value, ioaddr + VLAN_TAG);
+ } else if (perfect_match) {
+ u32 value = readl(ioaddr + XGMAC_PACKET_FILTER);
+
+ value |= XGMAC_FILTER_VTFE;
+
+ writel(value, ioaddr + XGMAC_PACKET_FILTER);
+
+ value = readl(ioaddr + VLAN_TAG);
+
+ value &= ~VLAN_VTHM;
+ value |= VLAN_ETV;
+ if (is_double) {
+ value |= VLAN_EDVLP;
+ value |= VLAN_ESVL;
+ value |= VLAN_DOVLTC;
+ } else {
+ value &= ~VLAN_EDVLP;
+ value &= ~VLAN_ESVL;
+ value &= ~VLAN_DOVLTC;
+ }
+
+ value &= ~VLAN_VID;
+ writel(value | perfect_match, ioaddr + VLAN_TAG);
+ } else {
+ u32 value = readl(ioaddr + XGMAC_PACKET_FILTER);
+
+ value &= ~XGMAC_FILTER_VTFE;
+
+ writel(value, ioaddr + XGMAC_PACKET_FILTER);
+
+ value = readl(ioaddr + VLAN_TAG);
+
+ value &= ~(VLAN_VTHM | VLAN_ETV);
+ value &= ~(VLAN_EDVLP | VLAN_ESVL);
+ value &= ~VLAN_DOVLTC;
+ value &= ~VLAN_VID;
+
+ writel(value, ioaddr + VLAN_TAG);
+ }
+}
+
+const struct stmmac_vlan_ops dwmac_vlan_ops = {
+ .update_vlan_hash = vlan_update_hash,
+ .enable_vlan = vlan_enable,
+ .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr,
+ .del_hw_vlan_rx_fltr = vlan_del_hw_rx_fltr,
+ .restore_hw_vlan_rx_fltr = vlan_restore_hw_rx_fltr,
+ .rx_hw_vlan = vlan_rx_hw,
+ .set_hw_vlan_mode = vlan_set_hw_mode,
+};
+
+const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = {
+ .update_vlan_hash = dwxgmac2_update_vlan_hash,
+ .enable_vlan = vlan_enable,
+};
+
+const struct stmmac_vlan_ops dwxgmac210_vlan_ops = {
+ .update_vlan_hash = dwxgmac2_update_vlan_hash,
+ .enable_vlan = vlan_enable,
+ .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr,
+ .del_hw_vlan_rx_fltr = vlan_del_hw_rx_fltr,
+ .restore_hw_vlan_rx_fltr = vlan_restore_hw_rx_fltr,
+ .rx_hw_vlan = vlan_rx_hw,
+ .set_hw_vlan_mode = vlan_set_hw_mode,
+};
+
+u32 stmmac_get_num_vlan(void __iomem *ioaddr)
+{
+ u32 val, num_vlan;
+
+ val = readl(ioaddr + HW_FEATURE3);
+ switch (val & VLAN_HW_FEAT_NRVF) {
+ case 0:
+ num_vlan = 1;
+ break;
+ case 1:
+ num_vlan = 4;
+ break;
+ case 2:
+ num_vlan = 8;
+ break;
+ case 3:
+ num_vlan = 16;
+ break;
+ case 4:
+ num_vlan = 24;
+ break;
+ case 5:
+ num_vlan = 32;
+ break;
+ default:
+ num_vlan = 1;
+ }
+
+ return num_vlan;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h
new file mode 100644
index 000000000..c24f89a90
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025, Altera Corporation
+ * stmmac VLAN(802.1Q) handling
+ */
+
+#ifndef __STMMAC_VLAN_H__
+#define __STMMAC_VLAN_H__
+
+#include <linux/bitfield.h>
+#include "dwxgmac2.h"
+
+#define VLAN_TAG 0x00000050
+#define VLAN_TAG_DATA 0x00000054
+#define VLAN_HASH_TABLE 0x00000058
+#define VLAN_INCL 0x00000060
+
+/* MAC VLAN */
+#define VLAN_EDVLP BIT(26)
+#define VLAN_VTHM BIT(25)
+#define VLAN_DOVLTC BIT(20)
+#define VLAN_ESVL BIT(18)
+#define VLAN_ETV BIT(16)
+#define VLAN_VID GENMASK(15, 0)
+#define VLAN_VLTI BIT(20)
+#define VLAN_CSVL BIT(19)
+#define VLAN_VLC GENMASK(17, 16)
+#define VLAN_VLC_SHIFT 16
+#define VLAN_VLHT GENMASK(15, 0)
+
+/* MAC VLAN Tag */
+#define VLAN_TAG_VID GENMASK(15, 0)
+#define VLAN_TAG_ETV BIT(16)
+
+/* MAC VLAN Tag Control */
+#define VLAN_TAG_CTRL_OB BIT(0)
+#define VLAN_TAG_CTRL_CT BIT(1)
+#define VLAN_TAG_CTRL_OFS_MASK GENMASK(6, 2)
+#define VLAN_TAG_CTRL_OFS_SHIFT 2
+#define VLAN_TAG_CTRL_EVLS_MASK GENMASK(22, 21)
+#define VLAN_TAG_CTRL_EVLS_SHIFT 21
+#define VLAN_TAG_CTRL_EVLRXS BIT(24)
+
+#define VLAN_TAG_STRIP_NONE FIELD_PREP(VLAN_TAG_CTRL_EVLS_MASK, 0x0)
+#define VLAN_TAG_STRIP_PASS FIELD_PREP(VLAN_TAG_CTRL_EVLS_MASK, 0x1)
+#define VLAN_TAG_STRIP_FAIL FIELD_PREP(VLAN_TAG_CTRL_EVLS_MASK, 0x2)
+#define VLAN_TAG_STRIP_ALL FIELD_PREP(VLAN_TAG_CTRL_EVLS_MASK, 0x3)
+
+/* MAC VLAN Tag Data/Filter */
+#define VLAN_TAG_DATA_VID GENMASK(15, 0)
+#define VLAN_TAG_DATA_VEN BIT(16)
+#define VLAN_TAG_DATA_ETV BIT(17)
+
+/* MAC VLAN HW FEAT */
+#define HW_FEATURE3 0x00000128
+#define VLAN_HW_FEAT_NRVF GENMASK(2, 0)
+
+extern const struct stmmac_vlan_ops dwmac_vlan_ops;
+extern const struct stmmac_vlan_ops dwxgmac210_vlan_ops;
+extern const struct stmmac_vlan_ops dwxlgmac2_vlan_ops;
+
+u32 stmmac_get_num_vlan(void __iomem *ioaddr);
+
+#endif /* __STMMAC_VLAN_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
new file mode 100644
index 000000000..d7e4db722
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2021, Intel Corporation. */
+
+#include <net/xdp_sock_drv.h>
+
+#include "stmmac.h"
+#include "stmmac_xdp.h"
+
+static int stmmac_xdp_enable_pool(struct stmmac_priv *priv,
+ struct xsk_buff_pool *pool, u16 queue)
+{
+ struct stmmac_channel *ch = &priv->channel[queue];
+ bool need_update;
+ u32 frame_size;
+ int err;
+
+ if (queue >= priv->plat->rx_queues_to_use ||
+ queue >= priv->plat->tx_queues_to_use)
+ return -EINVAL;
+
+ frame_size = xsk_pool_get_rx_frame_size(pool);
+ /* XDP ZC does not span multiple frame, make sure XSK pool buffer
+ * size can at least store Q-in-Q frame.
+ */
+ if (frame_size < ETH_FRAME_LEN + VLAN_HLEN * 2)
+ return -EOPNOTSUPP;
+
+ err = xsk_pool_dma_map(pool, priv->device, STMMAC_RX_DMA_ATTR);
+ if (err) {
+ netdev_err(priv->dev, "Failed to map xsk pool\n");
+ return err;
+ }
+
+ need_update = netif_running(priv->dev) && stmmac_xdp_is_enabled(priv);
+
+ if (need_update) {
+ napi_disable(&ch->rx_napi);
+ napi_disable(&ch->tx_napi);
+ stmmac_disable_rx_queue(priv, queue);
+ stmmac_disable_tx_queue(priv, queue);
+ }
+
+ set_bit(queue, priv->af_xdp_zc_qps);
+
+ if (need_update) {
+ stmmac_enable_rx_queue(priv, queue);
+ stmmac_enable_tx_queue(priv, queue);
+ napi_enable(&ch->rxtx_napi);
+
+ err = stmmac_xsk_wakeup(priv->dev, queue, XDP_WAKEUP_RX);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int stmmac_xdp_disable_pool(struct stmmac_priv *priv, u16 queue)
+{
+ struct stmmac_channel *ch = &priv->channel[queue];
+ struct xsk_buff_pool *pool;
+ bool need_update;
+
+ if (queue >= priv->plat->rx_queues_to_use ||
+ queue >= priv->plat->tx_queues_to_use)
+ return -EINVAL;
+
+ pool = xsk_get_pool_from_qid(priv->dev, queue);
+ if (!pool)
+ return -EINVAL;
+
+ need_update = netif_running(priv->dev) && stmmac_xdp_is_enabled(priv);
+
+ if (need_update) {
+ napi_disable(&ch->rxtx_napi);
+ stmmac_disable_rx_queue(priv, queue);
+ stmmac_disable_tx_queue(priv, queue);
+ synchronize_rcu();
+ }
+
+ xsk_pool_dma_unmap(pool, STMMAC_RX_DMA_ATTR);
+
+ clear_bit(queue, priv->af_xdp_zc_qps);
+
+ if (need_update) {
+ stmmac_enable_rx_queue(priv, queue);
+ stmmac_enable_tx_queue(priv, queue);
+ napi_enable(&ch->rx_napi);
+ napi_enable(&ch->tx_napi);
+ }
+
+ return 0;
+}
+
+int stmmac_xdp_setup_pool(struct stmmac_priv *priv, struct xsk_buff_pool *pool,
+ u16 queue)
+{
+ return pool ? stmmac_xdp_enable_pool(priv, pool, queue) :
+ stmmac_xdp_disable_pool(priv, queue);
+}
+
+int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *dev = priv->dev;
+ struct bpf_prog *old_prog;
+ bool need_update;
+ bool if_running;
+
+ if_running = netif_running(dev);
+
+ if (prog && dev->mtu > ETH_DATA_LEN) {
+ /* For now, the driver doesn't support XDP functionality with
+ * jumbo frames so we return error.
+ */
+ NL_SET_ERR_MSG_MOD(extack, "Jumbo frames not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (!prog)
+ xdp_features_clear_redirect_target(dev);
+
+ need_update = !!priv->xdp_prog != !!prog;
+ if (if_running && need_update)
+ stmmac_xdp_release(dev);
+
+ old_prog = xchg(&priv->xdp_prog, prog);
+ if (old_prog)
+ bpf_prog_put(old_prog);
+
+ /* Disable RX SPH for XDP operation */
+ priv->sph_active = priv->sph_capable && !stmmac_xdp_is_enabled(priv);
+
+ if (if_running && need_update)
+ stmmac_xdp_open(dev);
+
+ if (prog)
+ xdp_features_set_redirect_target(dev, false);
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.h
new file mode 100644
index 000000000..77ce8cfbe
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2021, Intel Corporation. */
+
+#ifndef _STMMAC_XDP_H_
+#define _STMMAC_XDP_H_
+
+#define STMMAC_RX_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING)
+
+int stmmac_xdp_setup_pool(struct stmmac_priv *priv, struct xsk_buff_pool *pool,
+ u16 queue);
+int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog,
+ struct netlink_ext_ack *extack);
+
+#endif /* _STMMAC_XDP_H_ */