--- /dev/null
+CONFIG_64BIT=y
+CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y
+CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE=y
+CONFIG_ARCH_DEFAULT_KEXEC_IMAGE_VERIFY_SIG=y
+CONFIG_ARCH_DMA_ADDR_T_64BIT=y
+CONFIG_ARCH_FORCE_MAX_ORDER=10
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y
+CONFIG_ARCH_MMAP_RND_BITS=18
+CONFIG_ARCH_MMAP_RND_BITS_MAX=24
+CONFIG_ARCH_MMAP_RND_BITS_MIN=18
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=11
+CONFIG_ARCH_PKEY_BITS=3
+CONFIG_ARCH_PROC_KCORE_TEXT=y
+CONFIG_ARCH_QCOM=y
+CONFIG_ARCH_SPARSEMEM_ENABLE=y
+CONFIG_ARCH_STACKWALK=y
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_ARCH_WANTS_EXECMEM_LATE=y
+CONFIG_ARCH_WANTS_NO_INSTR=y
+CONFIG_ARCH_WANTS_THP_SWAP=y
+CONFIG_ARM64=y
+CONFIG_ARM64_4K_PAGES=y
+CONFIG_ARM64_ERRATUM_1165522=y
+CONFIG_ARM64_ERRATUM_1286807=y
+CONFIG_ARM64_ERRATUM_2051678=y
+CONFIG_ARM64_ERRATUM_2054223=y
+CONFIG_ARM64_ERRATUM_2067961=y
+CONFIG_ARM64_ERRATUM_2077057=y
+CONFIG_ARM64_ERRATUM_2658417=y
+CONFIG_ARM64_LD_HAS_FIX_ERRATUM_843419=y
+CONFIG_ARM64_PA_BITS=48
+CONFIG_ARM64_PA_BITS_48=y
+CONFIG_ARM64_PLATFORM_DEVICES=y
+CONFIG_ARM64_PTR_AUTH=y
+CONFIG_ARM64_PTR_AUTH_KERNEL=y
+CONFIG_ARM64_SVE=y
+CONFIG_ARM64_TAGGED_ADDR_ABI=y
+CONFIG_ARM64_VA_BITS=39
+CONFIG_ARM64_VA_BITS_39=y
+# CONFIG_ARM64_VA_BITS_52 is not set
+CONFIG_ARM64_WORKAROUND_REPEAT_TLBI=y
+CONFIG_ARM64_WORKAROUND_SPECULATIVE_AT=y
+CONFIG_ARM64_WORKAROUND_TSB_FLUSH_FAILURE=y
+CONFIG_ARM_AMBA=y
+CONFIG_ARM_ARCH_TIMER=y
+CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y
+CONFIG_ARM_GIC=y
+CONFIG_ARM_GIC_V2M=y
+CONFIG_ARM_GIC_V3=y
+CONFIG_ARM_GIC_V3_ITS=y
+# CONFIG_ARM_MHU_V2 is not set
+# CONFIG_ARM_MHU_V3 is not set
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_ARM_PSCI_FW=y
+# CONFIG_ARM_QCOM_CPUFREQ_HW is not set
+CONFIG_ARM_QCOM_CPUFREQ_NVMEM=y
+CONFIG_AT803X_PHY=y
+CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y
+CONFIG_AUXILIARY_BUS=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_BLK_MQ_PCI=y
+CONFIG_BLK_MQ_VIRTIO=y
+CONFIG_BLK_PM=y
+CONFIG_BUILTIN_RETURN_ADDRESS_STRIPS_PAC=y
+CONFIG_CC_HAVE_SHADOW_CALL_STACK=y
+CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y
+# CONFIG_CLK_QCM2290_GPUCC is not set
+# CONFIG_CLK_X1E80100_CAMCC is not set
+# CONFIG_CLK_X1E80100_DISPCC is not set
+# CONFIG_CLK_X1E80100_GCC is not set
+# CONFIG_CLK_X1E80100_GPUCC is not set
+# CONFIG_CLK_X1E80100_TCSRCC is not set
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+CONFIG_COMMON_CLK_QCOM=y
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+# CONFIG_COMPAT_32BIT_TIME is not set
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_COREDUMP=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_CPUFREQ_DT_PLATDEV=y
+CONFIG_CPU_FREQ=y
+# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set
+CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
+CONFIG_CPU_FREQ_GOV_ATTR_SET=y
+# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
+# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_THERMAL=y
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_IDLE_GOV_MENU=y
+CONFIG_CPU_IDLE_MULTIPLE_DRIVERS=y
+CONFIG_CPU_LITTLE_ENDIAN=y
+CONFIG_CPU_MITIGATIONS=y
+CONFIG_CPU_PM=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_THERMAL=y
+CONFIG_CRC16=y
+CONFIG_CRC8=y
+CONFIG_CRYPTO_AUTHENC=y
+CONFIG_CRYPTO_CBC=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_DEV_QCE=y
+CONFIG_CRYPTO_DEV_QCE_AEAD=y
+# CONFIG_CRYPTO_DEV_QCE_ENABLE_AEAD is not set
+CONFIG_CRYPTO_DEV_QCE_ENABLE_ALL=y
+# CONFIG_CRYPTO_DEV_QCE_ENABLE_SHA is not set
+# CONFIG_CRYPTO_DEV_QCE_ENABLE_SKCIPHER is not set
+CONFIG_CRYPTO_DEV_QCE_SHA=y
+CONFIG_CRYPTO_DEV_QCE_SKCIPHER=y
+CONFIG_CRYPTO_DEV_QCE_SW_MAX_LEN=512
+CONFIG_CRYPTO_DEV_QCOM_RNG=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_HASH_INFO=y
+CONFIG_CRYPTO_HW=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_DES=y
+CONFIG_CRYPTO_LIB_GF128MUL=y
+CONFIG_CRYPTO_LIB_SHA1=y
+CONFIG_CRYPTO_LIB_SHA256=y
+CONFIG_CRYPTO_LIB_UTILS=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_RNG=y
+CONFIG_CRYPTO_RNG2=y
+CONFIG_CRYPTO_SHA1=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_XTS=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_DCACHE_WORD_ACCESS=y
+CONFIG_DEBUG_BUGVERBOSE=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEV_COREDUMP=y
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC=y
+CONFIG_DMA_DIRECT_REMAP=y
+CONFIG_DMA_ENGINE=y
+CONFIG_DMA_NEED_SYNC=y
+CONFIG_DMA_OF=y
+CONFIG_DMA_VIRTUAL_CHANNELS=y
+CONFIG_DTC=y
+CONFIG_DT_IDLE_STATES=y
+CONFIG_EDAC_SUPPORT=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_FIXED_PHY=y
+CONFIG_FIX_EARLYCON_MEM=y
+CONFIG_FRAME_POINTER=y
+CONFIG_FS_IOMAP=y
+CONFIG_FUNCTION_ALIGNMENT=4
+CONFIG_FUNCTION_ALIGNMENT_4B=y
+CONFIG_FWNODE_MDIO=y
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GCC_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_GENERIC_ARCH_TOPOLOGY=y
+CONFIG_GENERIC_BUG=y
+CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_CPU_DEVICES=y
+CONFIG_GENERIC_CPU_VULNERABILITIES=y
+CONFIG_GENERIC_CSUM=y
+CONFIG_GENERIC_EARLY_IOREMAP=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IOREMAP=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_IRQ_SHOW_LEVEL=y
+CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED=y
+CONFIG_GENERIC_MSI_IRQ=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_PHY=y
+CONFIG_GENERIC_PINCONF=y
+CONFIG_GENERIC_PINCTRL_GROUPS=y
+CONFIG_GENERIC_PINMUX_FUNCTIONS=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_STRNCPY_FROM_USER=y
+CONFIG_GENERIC_STRNLEN_USER=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GLOB=y
+CONFIG_GPIOLIB_IRQCHIP=y
+CONFIG_GPIO_CDEV=y
+CONFIG_HARDIRQS_SW_RESEND=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HWMON=y
+CONFIG_HWSPINLOCK=y
+CONFIG_HWSPINLOCK_QCOM=y
+CONFIG_HW_RANDOM=y
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_HELPER_AUTO=y
+# CONFIG_I2C_QCOM_CCI is not set
+CONFIG_I2C_QUP=y
+# CONFIG_IDPF is not set
+CONFIG_IIO=y
+CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_INTERCONNECT=y
+CONFIG_INTERCONNECT_CLK=y
+# CONFIG_INTERCONNECT_QCOM is not set
+CONFIG_IPQ_APSS_6018=y
+CONFIG_IPQ_APSS_PLL=y
+# CONFIG_IPQ_CMN_PLL is not set
+# CONFIG_IPQ_GCC_4019 is not set
+# CONFIG_IPQ_GCC_5018 is not set
+# CONFIG_IPQ_GCC_5332 is not set
+# CONFIG_IPQ_GCC_6018 is not set
+# CONFIG_IPQ_GCC_8074 is not set
+# CONFIG_IPQ_GCC_9574 is not set
+# CONFIG_IPQ_NSSCC_QCA8K is not set
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_MSI_LIB=y
+CONFIG_IRQ_WORK=y
+# CONFIG_KPSS_XCC is not set
+CONFIG_LEDS_TLC591XX=y
+CONFIG_LIBFDT=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_LOCK_SPIN_ON_OWNER=y
+CONFIG_LRU_GEN_WALKS_MMU=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MAILBOX=y
+# CONFIG_MAILBOX_TEST is not set
+CONFIG_MDIO_BUS=y
+CONFIG_MDIO_DEVICE=y
+CONFIG_MDIO_DEVRES=y
+CONFIG_MDIO_IPQ4019=y
+# CONFIG_MFD_QCOM_RPM is not set
+CONFIG_MFD_SYSCON=y
+CONFIG_MIGRATION=y
+# CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY is not set
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_CQHCI=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_IO_ACCESSORS=y
+CONFIG_MMC_SDHCI_MSM=y
+# CONFIG_MMC_SDHCI_PCI is not set
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMU_LAZY_TLB_REFCOUNT=y
+CONFIG_MODULES_USE_ELF_RELA=y
+# CONFIG_MSM_GCC_8916 is not set
+# CONFIG_MSM_GCC_8917 is not set
+# CONFIG_MSM_GCC_8939 is not set
+# CONFIG_MSM_GCC_8976 is not set
+# CONFIG_MSM_GCC_8994 is not set
+# CONFIG_MSM_GCC_8996 is not set
+# CONFIG_MSM_GCC_8998 is not set
+# CONFIG_MSM_GPUCC_8998 is not set
+# CONFIG_MSM_MMCC_8996 is not set
+# CONFIG_MSM_MMCC_8998 is not set
+CONFIG_MTD_NAND_CORE=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_ECC_SW_HAMMING=y
+CONFIG_MTD_NAND_QCOM=y
+CONFIG_MTD_QCOMSMEM_PARTS=y
+CONFIG_MTD_RAW_NAND=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=20
+CONFIG_MTD_UBI_BLOCK=y
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_MUTEX_SPIN_ON_OWNER=y
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEED_SG_DMA_LENGTH=y
+CONFIG_NET_EGRESS=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_INGRESS=y
+CONFIG_NET_SELFTESTS=y
+CONFIG_NET_XGRESS=y
+CONFIG_NLS=y
+CONFIG_NO_HZ_COMMON=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NR_CPUS=4
+# CONFIG_NSM is not set
+CONFIG_NVMEM=y
+CONFIG_NVMEM_LAYOUTS=y
+CONFIG_NVMEM_LAYOUT_U_BOOT_ENV=y
+CONFIG_NVMEM_QCOM_QFPROM=y
+# CONFIG_NVMEM_QCOM_SEC_QFPROM is not set
+CONFIG_NVMEM_SYSFS=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_OF_MDIO=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+CONFIG_PARTITION_PERCPU=y
+CONFIG_PCI=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEASPM=y
+CONFIG_PCIEASPM_DEFAULT=y
+# CONFIG_PCIEASPM_PERFORMANCE is not set
+# CONFIG_PCIEASPM_POWERSAVE is not set
+# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIE_DW=y
+CONFIG_PCIE_DW_HOST=y
+CONFIG_PCIE_PME=y
+CONFIG_PCIE_QCOM=y
+CONFIG_PCIE_QCOM_COMMON=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DOMAINS_GENERIC=y
+CONFIG_PCI_MSI=y
+# CONFIG_PCS_QCOM_IPQ9574 is not set
+CONFIG_PER_VMA_LOCK=y
+CONFIG_PGTABLE_LEVELS=3
+CONFIG_PHYLIB=y
+CONFIG_PHYLIB_LEDS=y
+CONFIG_PHYS_ADDR_T_64BIT=y
+# CONFIG_PHY_QCOM_APQ8064_SATA is not set
+# CONFIG_PHY_QCOM_EDP is not set
+# CONFIG_PHY_QCOM_EUSB2_REPEATER is not set
+# CONFIG_PHY_QCOM_IPQ4019_USB is not set
+# CONFIG_PHY_QCOM_IPQ806X_SATA is not set
+# CONFIG_PHY_QCOM_IPQ806X_USB is not set
+# CONFIG_PHY_QCOM_M31_USB is not set
+# CONFIG_PHY_QCOM_PCIE2 is not set
+CONFIG_PHY_QCOM_QMP=y
+CONFIG_PHY_QCOM_QMP_COMBO=y
+CONFIG_PHY_QCOM_QMP_PCIE=y
+CONFIG_PHY_QCOM_QMP_PCIE_8996=y
+CONFIG_PHY_QCOM_QMP_UFS=y
+CONFIG_PHY_QCOM_QMP_USB=y
+# CONFIG_PHY_QCOM_QMP_USB_LEGACY is not set
+CONFIG_PHY_QCOM_QUSB2=y
+# CONFIG_PHY_QCOM_SGMII_ETH is not set
+# CONFIG_PHY_QCOM_SNPS_EUSB2 is not set
+# CONFIG_PHY_QCOM_USB_HS_28NM is not set
+# CONFIG_PHY_QCOM_USB_SNPS_FEMTO_V2 is not set
+# CONFIG_PHY_QCOM_USB_SS is not set
+CONFIG_PINCTRL=y
+# CONFIG_PINCTRL_IPQ5018 is not set
+# CONFIG_PINCTRL_IPQ5332 is not set
+# CONFIG_PINCTRL_IPQ6018 is not set
+# CONFIG_PINCTRL_IPQ8074 is not set
+# CONFIG_PINCTRL_IPQ9574 is not set
+CONFIG_PINCTRL_MSM=y
+# CONFIG_PINCTRL_MSM8916 is not set
+# CONFIG_PINCTRL_MSM8976 is not set
+# CONFIG_PINCTRL_MSM8994 is not set
+# CONFIG_PINCTRL_MSM8996 is not set
+# CONFIG_PINCTRL_MSM8998 is not set
+# CONFIG_PINCTRL_QCM2290 is not set
+# CONFIG_PINCTRL_QCOM_SSBI_PMIC is not set
+# CONFIG_PINCTRL_QCS404 is not set
+# CONFIG_PINCTRL_QDU1000 is not set
+# CONFIG_PINCTRL_SA8775P is not set
+# CONFIG_PINCTRL_SC7180 is not set
+# CONFIG_PINCTRL_SC8280XP is not set
+# CONFIG_PINCTRL_SDM660 is not set
+# CONFIG_PINCTRL_SDM670 is not set
+# CONFIG_PINCTRL_SDM845 is not set
+# CONFIG_PINCTRL_SDX75 is not set
+# CONFIG_PINCTRL_SM4450 is not set
+# CONFIG_PINCTRL_SM6350 is not set
+# CONFIG_PINCTRL_SM6375 is not set
+# CONFIG_PINCTRL_SM7150 is not set
+# CONFIG_PINCTRL_SM8150 is not set
+# CONFIG_PINCTRL_SM8250 is not set
+# CONFIG_PINCTRL_SM8450 is not set
+# CONFIG_PINCTRL_SM8550 is not set
+# CONFIG_PINCTRL_SM8650 is not set
+# CONFIG_PINCTRL_X1E80100 is not set
+CONFIG_PM=y
+CONFIG_PM_CLK=y
+CONFIG_PM_OPP=y
+CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y
+CONFIG_POWER_RESET=y
+# CONFIG_POWER_RESET_MSM is not set
+CONFIG_POWER_SUPPLY=y
+CONFIG_PRINTK_TIME=y
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QCA807X_PHY=y
+CONFIG_QCA808X_PHY=y
+# CONFIG_QCM_DISPCC_2290 is not set
+# CONFIG_QCM_GCC_2290 is not set
+# CONFIG_QCOM_A53PLL is not set
+# CONFIG_QCOM_AOSS_QMP is not set
+CONFIG_QCOM_APCS_IPC=y
+# CONFIG_QCOM_APR is not set
+CONFIG_QCOM_BAM_DMA=y
+# CONFIG_QCOM_CLK_APCC_MSM8996 is not set
+# CONFIG_QCOM_CLK_APCS_MSM8916 is not set
+# CONFIG_QCOM_COMMAND_DB is not set
+# CONFIG_QCOM_CPR is not set
+# CONFIG_QCOM_CPUCP_MBOX is not set
+# CONFIG_QCOM_EBI2 is not set
+# CONFIG_QCOM_FASTRPC is not set
+# CONFIG_QCOM_GENI_SE is not set
+# CONFIG_QCOM_GSBI is not set
+# CONFIG_QCOM_HFPLL is not set
+# CONFIG_QCOM_ICC_BWMON is not set
+# CONFIG_QCOM_IPA is not set
+# CONFIG_QCOM_IPCC is not set
+# CONFIG_QCOM_LLCC is not set
+CONFIG_QCOM_MDT_LOADER=y
+# CONFIG_QCOM_MPM is not set
+CONFIG_QCOM_NET_PHYLIB=y
+# CONFIG_QCOM_OCMEM is not set
+# CONFIG_QCOM_PDC is not set
+CONFIG_QCOM_PIL_INFO=y
+# CONFIG_QCOM_PPE is not set
+# CONFIG_QCOM_Q6V5_ADSP is not set
+CONFIG_QCOM_Q6V5_COMMON=y
+# CONFIG_QCOM_Q6V5_MSS is not set
+# CONFIG_QCOM_Q6V5_PAS is not set
+CONFIG_QCOM_Q6V5_WCSS=y
+# CONFIG_QCOM_QSEECOM is not set
+# CONFIG_QCOM_RAMP_CTRL is not set
+# CONFIG_QCOM_RMTFS_MEM is not set
+# CONFIG_QCOM_RPMH is not set
+# CONFIG_QCOM_RPM_MASTER_STATS is not set
+CONFIG_QCOM_RPROC_COMMON=y
+CONFIG_QCOM_SCM=y
+# CONFIG_QCOM_SMD_RPM is not set
+CONFIG_QCOM_SMEM=y
+CONFIG_QCOM_SMEM_STATE=y
+CONFIG_QCOM_SMP2P=y
+# CONFIG_QCOM_SMSM is not set
+CONFIG_QCOM_SOCINFO=y
+# CONFIG_QCOM_SPM is not set
+# CONFIG_QCOM_STATS is not set
+# CONFIG_QCOM_SYSMON is not set
+CONFIG_QCOM_TSENS=y
+CONFIG_QCOM_TZMEM=y
+CONFIG_QCOM_TZMEM_MODE_GENERIC=y
+# CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE is not set
+# CONFIG_QCOM_WCNSS_CTRL is not set
+# CONFIG_QCOM_WCNSS_PIL is not set
+CONFIG_QCOM_WDT=y
+# CONFIG_QCS_GCC_404 is not set
+# CONFIG_QCS_Q6SSTOP_404 is not set
+# CONFIG_QCS_TURING_404 is not set
+# CONFIG_QDU_ECPRICC_1000 is not set
+# CONFIG_QDU_GCC_1000 is not set
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RAS=y
+CONFIG_RATIONAL=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_I2C=y
+CONFIG_REGMAP_MMIO=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+# CONFIG_REGULATOR_VQMMC_IPQ4019 is not set
+CONFIG_RELOCATABLE=y
+CONFIG_REMOTEPROC=y
+CONFIG_REMOTEPROC_CDEV=y
+CONFIG_RESET_CONTROLLER=y
+# CONFIG_RESET_QCOM_AOSS is not set
+# CONFIG_RESET_QCOM_PDC is not set
+CONFIG_RFS_ACCEL=y
+CONFIG_RODATA_FULL_DEFAULT_ENABLED=y
+CONFIG_RPMSG=y
+CONFIG_RPMSG_CHAR=y
+# CONFIG_RPMSG_CTRL is not set
+# CONFIG_RPMSG_NS is not set
+CONFIG_RPMSG_QCOM_GLINK=y
+CONFIG_RPMSG_QCOM_GLINK_RPM=y
+CONFIG_RPMSG_QCOM_GLINK_SMEM=y
+CONFIG_RPMSG_QCOM_SMD=y
+# CONFIG_RPMSG_TTY is not set
+CONFIG_RPS=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_I2C_AND_SPI=y
+CONFIG_RWSEM_SPIN_ON_OWNER=y
+# CONFIG_SA_GCC_8775P is not set
+# CONFIG_SA_GPUCC_8775P is not set
+# CONFIG_SCHED_CORE is not set
+CONFIG_SCHED_HW_PRESSURE=y
+CONFIG_SCHED_MC=y
+CONFIG_SCHED_SMT=y
+CONFIG_SCSI=y
+CONFIG_SCSI_COMMON=y
+# CONFIG_SCSI_LOWLEVEL is not set
+# CONFIG_SCSI_PROC_FS is not set
+# CONFIG_SC_CAMCC_7280 is not set
+# CONFIG_SC_CAMCC_8280XP is not set
+# CONFIG_SC_DISPCC_7180 is not set
+# CONFIG_SC_DISPCC_8280XP is not set
+# CONFIG_SC_GCC_7180 is not set
+# CONFIG_SC_GCC_8280XP is not set
+# CONFIG_SC_GPUCC_7180 is not set
+# CONFIG_SC_LPASSCC_7280 is not set
+# CONFIG_SC_LPASSCC_8280XP is not set
+# CONFIG_SC_LPASS_CORECC_7180 is not set
+# CONFIG_SC_LPASS_CORECC_7280 is not set
+# CONFIG_SC_VIDEOCC_7180 is not set
+# CONFIG_SDM_CAMCC_845 is not set
+# CONFIG_SDM_DISPCC_845 is not set
+# CONFIG_SDM_GCC_660 is not set
+# CONFIG_SDM_GCC_845 is not set
+# CONFIG_SDM_GPUCC_845 is not set
+# CONFIG_SDM_LPASSCC_845 is not set
+# CONFIG_SDM_VIDEOCC_845 is not set
+# CONFIG_SDX_GCC_75 is not set
+CONFIG_SERIAL_8250_FSL=y
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SERIAL_MSM=y
+CONFIG_SERIAL_MSM_CONSOLE=y
+CONFIG_SGL_ALLOC=y
+CONFIG_SG_POOL=y
+CONFIG_SMP=y
+# CONFIG_SM_CAMCC_4450 is not set
+# CONFIG_SM_CAMCC_6350 is not set
+# CONFIG_SM_CAMCC_7150 is not set
+# CONFIG_SM_CAMCC_8150 is not set
+# CONFIG_SM_CAMCC_8450 is not set
+# CONFIG_SM_CAMCC_8550 is not set
+# CONFIG_SM_CAMCC_8650 is not set
+# CONFIG_SM_GCC_4450 is not set
+# CONFIG_SM_GCC_7150 is not set
+# CONFIG_SM_GCC_8150 is not set
+# CONFIG_SM_GCC_8250 is not set
+# CONFIG_SM_GCC_8450 is not set
+# CONFIG_SM_GCC_8550 is not set
+# CONFIG_SM_GCC_8650 is not set
+# CONFIG_SM_GPUCC_4450 is not set
+# CONFIG_SM_GPUCC_6115 is not set
+# CONFIG_SM_GPUCC_6125 is not set
+# CONFIG_SM_GPUCC_6350 is not set
+# CONFIG_SM_GPUCC_6375 is not set
+# CONFIG_SM_GPUCC_8150 is not set
+# CONFIG_SM_GPUCC_8250 is not set
+# CONFIG_SM_GPUCC_8350 is not set
+# CONFIG_SM_GPUCC_8450 is not set
+# CONFIG_SM_GPUCC_8550 is not set
+# CONFIG_SM_GPUCC_8650 is not set
+# CONFIG_SM_TCSRCC_8550 is not set
+# CONFIG_SM_TCSRCC_8650 is not set
+# CONFIG_SM_VIDEOCC_7150 is not set
+# CONFIG_SM_VIDEOCC_8150 is not set
+# CONFIG_SM_VIDEOCC_8250 is not set
+# CONFIG_SM_VIDEOCC_8350 is not set
+# CONFIG_SM_VIDEOCC_8450 is not set
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+CONFIG_SOC_BUS=y
+CONFIG_SOFTIRQ_ON_OWN_STACK=y
+CONFIG_SPARSEMEM=y
+CONFIG_SPARSEMEM_EXTREME=y
+CONFIG_SPARSEMEM_VMEMMAP=y
+CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
+CONFIG_SPARSE_IRQ=y
+CONFIG_SPI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+# CONFIG_SPI_QPIC_SNAND is not set
+CONFIG_SPI_QUP=y
+CONFIG_SPLIT_PMD_PTLOCKS=y
+CONFIG_SPLIT_PTE_PTLOCKS=y
+CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU=y
+CONFIG_SWIOTLB=y
+CONFIG_SWPHY=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+# CONFIG_TEST_FPU is not set
+CONFIG_THERMAL=y
+CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
+CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0
+CONFIG_THERMAL_GOV_STEP_WISE=y
+CONFIG_THERMAL_HWMON=y
+CONFIG_THERMAL_OF=y
+CONFIG_THREAD_INFO_IN_TASK=y
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
+CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UBIFS_FS=y
+CONFIG_UBIFS_FS_ADVANCED_COMPR=y
+# CONFIG_UCLAMP_TASK is not set
+CONFIG_UNMAP_KERNEL_AT_EL0=y
+CONFIG_USB=y
+CONFIG_USB_COMMON=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USER_STACKTRACE_SUPPORT=y
+CONFIG_VDSO_GETRANDOM=y
+CONFIG_VIRTIO=y
+CONFIG_VIRTIO_ANCHOR=y
+# CONFIG_VIRTIO_BLK is not set
+# CONFIG_VIRTIO_DEBUG is not set
+# CONFIG_VIRTIO_NET is not set
+CONFIG_VMAP_STACK=y
+CONFIG_WANT_DEV_COREDUMP=y
+CONFIG_WATCHDOG_CORE=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_XPS=y
+CONFIG_XXHASH=y
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZONE_DMA32=y
+CONFIG_ZSTD_COMMON=y
+CONFIG_ZSTD_COMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
--- /dev/null
+From 52ebd52aa1906961142a2aba55d47a53b956847c Mon Sep 17 00:00:00 2001
+From: Devi Priya <quic_devipriy@quicinc.com>
+Date: Thu, 13 Mar 2025 16:33:58 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq9574: Add nsscc node
+
+Add a node for the nss clock controller found on ipq9574 based devices.
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Signed-off-by: Devi Priya <quic_devipriy@quicinc.com>
+Signed-off-by: Manikanta Mylavarapu <quic_mmanikan@quicinc.com>
+Link: https://lore.kernel.org/r/20250313110359.242491-6-quic_mmanikan@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 29 +++++++++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -1195,6 +1195,35 @@
+ status = "disabled";
+ };
+
++ nsscc: clock-controller@39b00000 {
++ compatible = "qcom,ipq9574-nsscc";
++ reg = <0x39b00000 0x80000>;
++ clocks = <&xo_board_clk>,
++ <&cmn_pll NSS_1200MHZ_CLK>,
++ <&cmn_pll PPE_353MHZ_CLK>,
++ <&gcc GPLL0_OUT_AUX>,
++ <0>,
++ <0>,
++ <0>,
++ <0>,
++ <0>,
++ <0>,
++ <&gcc GCC_NSSCC_CLK>;
++ clock-names = "xo",
++ "nss_1200",
++ "ppe_353",
++ "gpll0_out",
++ "uniphy0_rx",
++ "uniphy0_tx",
++ "uniphy1_rx",
++ "uniphy1_tx",
++ "uniphy2_rx",
++ "uniphy2_tx",
++ "bus";
++ #clock-cells = <1>;
++ #reset-cells = <1>;
++ #interconnect-cells = <1>;
++ };
+ };
+
+ thermal-zones {
--- /dev/null
+From 2f2f5ae4d52ea882ba58f6b2fa6373a3d3db2bce Mon Sep 17 00:00:00 2001
+From: Manikanta Mylavarapu <quic_mmanikan@quicinc.com>
+Date: Thu, 13 Mar 2025 12:44:22 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq9574: fix the msi interrupt numbers of
+ pcie3
+
+The MSI interrupt numbers of the PCIe3 controller are incorrect. Due
+to this, the functional bring up of the QDSP6 processor on the PCIe
+endpoint has failed. Correct the MSI interrupt numbers to properly
+bring up the QDSP6 processor on the PCIe endpoint.
+
+Fixes: d80c7fbfa908 ("arm64: dts: qcom: ipq9574: Add PCIe PHYs and controller nodes")
+Signed-off-by: Manikanta Mylavarapu <quic_mmanikan@quicinc.com>
+Link: https://lore.kernel.org/r/20250313071422.510-1-quic_mmanikan@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -974,14 +974,14 @@
+ ranges = <0x01000000 0x0 0x00000000 0x18200000 0x0 0x100000>,
+ <0x02000000 0x0 0x18300000 0x18300000 0x0 0x7d00000>;
+
+- interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 137 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 141 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 142 IRQ_TYPE_LEVEL_HIGH>,
+- <GIC_SPI 143 IRQ_TYPE_LEVEL_HIGH>;
++ interrupts = <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 225 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 312 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 415 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 494 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 495 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "msi0",
+ "msi1",
+ "msi2",
--- /dev/null
+From 583299efa34c4a484b211f84c63aee78b6c2b469 Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Thu, 6 Mar 2025 17:03:55 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq9574: Add SPI nand support
+
+Add SPI NAND support for ipq9574 SoC.
+
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Link: https://lore.kernel.org/r/20250306113357.126602-2-quic_mdalam@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -675,6 +675,33 @@
+ status = "disabled";
+ };
+
++ qpic_bam: dma-controller@7984000 {
++ compatible = "qcom,bam-v1.7.4", "qcom,bam-v1.7.0";
++ reg = <0x07984000 0x1c000>;
++ interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_QPIC_AHB_CLK>;
++ clock-names = "bam_clk";
++ #dma-cells = <1>;
++ qcom,ee = <0>;
++ status = "disabled";
++ };
++
++ qpic_nand: spi@79b0000 {
++ compatible = "qcom,ipq9574-snand";
++ reg = <0x079b0000 0x10000>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ clocks = <&gcc GCC_QPIC_CLK>,
++ <&gcc GCC_QPIC_AHB_CLK>,
++ <&gcc GCC_QPIC_IO_MACRO_CLK>;
++ clock-names = "core", "aon", "iom";
++ dmas = <&qpic_bam 0>,
++ <&qpic_bam 1>,
++ <&qpic_bam 2>;
++ dma-names = "tx", "rx", "cmd";
++ status = "disabled";
++ };
++
+ usb_0_qusbphy: phy@7b000 {
+ compatible = "qcom,ipq9574-qusb2-phy";
+ reg = <0x0007b000 0x180>;
--- /dev/null
+From a7c88bc81632974c0708308493aefb1f871b65fa Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Thu, 6 Mar 2025 17:03:56 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq9574: Enable SPI NAND for ipq9574
+
+Enable SPI NAND support for ipq9574 SoC.
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Link: https://lore.kernel.org/r/20250306113357.126602-3-quic_mdalam@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ .../boot/dts/qcom/ipq9574-rdp-common.dtsi | 44 +++++++++++++++++++
+ 1 file changed, 44 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp-common.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp-common.dtsi
+@@ -146,6 +146,50 @@
+ drive-strength = <8>;
+ bias-pull-up;
+ };
++
++ qpic_snand_default_state: qpic-snand-default-state {
++ clock-pins {
++ pins = "gpio5";
++ function = "qspi_clk";
++ drive-strength = <8>;
++ bias-disable;
++ };
++
++ cs-pins {
++ pins = "gpio4";
++ function = "qspi_cs";
++ drive-strength = <8>;
++ bias-disable;
++ };
++
++ data-pins {
++ pins = "gpio0", "gpio1", "gpio2", "gpio3";
++ function = "qspi_data";
++ drive-strength = <8>;
++ bias-disable;
++ };
++ };
++};
++
++&qpic_bam {
++ status = "okay";
++};
++
++&qpic_nand {
++ pinctrl-0 = <&qpic_snand_default_state>;
++ pinctrl-names = "default";
++
++ status = "okay";
++
++ flash@0 {
++ compatible = "spi-nand";
++ reg = <0>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ nand-ecc-engine = <&qpic_nand>;
++ nand-ecc-strength = <4>;
++ nand-ecc-step-size = <512>;
++ };
+ };
+
+ &usb_0_dwc3 {
--- /dev/null
+From 0156e327aa854be5eb9cbec9d020be1026b5b446 Mon Sep 17 00:00:00 2001
+From: Md Sadre Alam <quic_mdalam@quicinc.com>
+Date: Thu, 6 Mar 2025 17:03:57 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq9574: Remove eMMC node
+
+Remove eMMC node for rdp433, since rdp433
+default boot mode is norplusnand
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Signed-off-by: Md Sadre Alam <quic_mdalam@quicinc.com>
+Link: https://lore.kernel.org/r/20250306113357.126602-4-quic_mdalam@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts | 12 ------------
+ 1 file changed, 12 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
+@@ -55,18 +55,6 @@
+ status = "okay";
+ };
+
+-&sdhc_1 {
+- pinctrl-0 = <&sdc_default_state>;
+- pinctrl-names = "default";
+- mmc-ddr-1_8v;
+- mmc-hs200-1_8v;
+- mmc-hs400-1_8v;
+- mmc-hs400-enhanced-strobe;
+- max-frequency = <384000000>;
+- bus-width = <8>;
+- status = "okay";
+-};
+-
+ &tlmm {
+
+ pcie1_default: pcie1-default-state {
--- /dev/null
+From 657833a74f532262d415fa2ca354b69f4a97353c Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Thu, 23 Nov 2023 15:41:20 +0800
+Subject: [PATCH] arm64: dts: qcom: Add IPQ9574 MDIO device node
+
+The MDIO bus master block is used to accessing the MDIO slave
+device (such as PHY device), the dedicated MDIO PINs needs to
+be configured.
+
+Change-Id: Ia64083529e693256dbd8f8af4071c02afdded8f9
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -295,6 +295,8 @@
+ mdio: mdio@90000 {
+ compatible = "qcom,ipq9574-mdio", "qcom,ipq4019-mdio";
+ reg = <0x00090000 0x64>;
++ pinctrl-0 = <&mdio_pins>;
++ pinctrl-names = "default";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&gcc GCC_MDIO_AHB_CLK>;
+@@ -414,6 +416,22 @@
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
++ mdio_pins: mdio-pins {
++ mdc-state {
++ pins = "gpio38";
++ function = "mdc";
++ drive-strength = <8>;
++ bias-disable;
++ };
++
++ mdio-state {
++ pins = "gpio39";
++ function = "mdio";
++ drive-strength = <8>;
++ bias-pull-up;
++ };
++ };
++
+ uart2_pins: uart2-state {
+ pins = "gpio34", "gpio35";
+ function = "blsp2_uart";
--- /dev/null
+From 91467ca0db1654644b2168f882f223d47dcfb9c1 Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+Date: Sat, 30 Mar 2024 20:03:30 -0500
+Subject: [PATCH] arm64: dts: qcom: ipq9574: Use 'usb-phy' for node names
+
+The devicetree spec allows node names of "usb-phy". So be more
+specific for the USB PHYs, and name the nodes "usb-phy" instead of
+just "phy".
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -720,7 +720,7 @@
+ status = "disabled";
+ };
+
+- usb_0_qusbphy: phy@7b000 {
++ usb_0_qusbphy: usb-phy@7b000 {
+ compatible = "qcom,ipq9574-qusb2-phy";
+ reg = <0x0007b000 0x180>;
+ #phy-cells = <0>;
+@@ -734,7 +734,7 @@
+ status = "disabled";
+ };
+
+- usb_0_qmpphy: phy@7d000 {
++ usb_0_qmpphy: usb-phy@7d000 {
+ compatible = "qcom,ipq9574-qmp-usb3-phy";
+ reg = <0x0007d000 0xa00>;
+ #phy-cells = <0>;
--- /dev/null
+From be44d0251a2540f3b8d7205e0bc6659704366711 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 30 Jan 2025 00:39:30 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq9574: add QPIC SPI NAND default
+ partition nodes
+
+Add QPIC SPI NAND default partition nodes for RDP reference board.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ .../boot/dts/qcom/ipq9574-rdp-common.dtsi | 28 +++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp-common.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp-common.dtsi
+@@ -189,6 +189,34 @@
+ nand-ecc-engine = <&qpic_nand>;
+ nand-ecc-strength = <4>;
+ nand-ecc-step-size = <512>;
++
++ partitions {
++ compatible = "fixed-partitions";
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ partition@0 {
++ label = "0:training";
++ reg = <0x0 0x80000>;
++ read-only;
++ };
++
++ partition@80000 {
++ label = "0:license";
++ reg = <0x80000 0x40000>;
++ read-only;
++ };
++
++ partition@c0000 {
++ label = "rootfs";
++ reg = <0xc0000 0x3c00000>;
++ };
++
++ partition@3cc0000 {
++ label = "rootfs_1";
++ reg = <0x3cc0000 0x3c00000>;
++ };
++ };
+ };
+ };
+
--- /dev/null
+From 47c7ae9715d76054d98e8407dbb8ca1cf42fd587 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 9 Dec 2024 17:50:31 +0100
+Subject: [PATCH] arm64: dts: qcom: add partition table for ipq9574 rdp common
+
+Add partition table for ipq9574 SoC common to every RDB board.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ .../boot/dts/qcom/ipq9574-rdp-common.dtsi | 146 +++++++++++++++++-
+ 1 file changed, 145 insertions(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp-common.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp-common.dtsi
+@@ -74,11 +74,158 @@
+ status = "okay";
+
+ flash@0 {
+- compatible = "micron,n25q128a11", "jedec,spi-nor";
++ compatible = "jedec,spi-nor";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-max-frequency = <50000000>;
++
++ partitions {
++ compatible = "fixed-partitions";
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ partition@0 {
++ label = "0:sbl1";
++ reg = <0x0 0xc0000>;
++ read-only;
++ };
++
++ partition@c0000 {
++ label = "0:mibib";
++ reg = <0xc0000 0x10000>;
++ read-only;
++ };
++
++ partition@d0000 {
++ label = "0:bootconfig";
++ reg = <0xd0000 0x20000>;
++ read-only;
++ };
++
++ partition@f0000 {
++ label = "0:bootconfig1";
++ reg = <0xf0000 0x20000>;
++ read-only;
++ };
++
++ partition@110000 {
++ label = "0:qsee";
++ reg = <0x110000 0x180000>;
++ read-only;
++ };
++
++ partition@290000 {
++ label = "0:qsee_1";
++ reg = <0x290000 0x180000>;
++ read-only;
++ };
++
++ partition@410000 {
++ label = "0:devcfg";
++ reg = <0x410000 0x10000>;
++ read-only;
++ };
++
++ partition@420000 {
++ label = "0:devcfg_1";
++ reg = <0x420000 0x10000>;
++ read-only;
++ };
++
++ partition@430000 {
++ label = "0:apdp";
++ reg = <0x430000 0x10000>;
++ read-only;
++ };
++
++ partition@440000 {
++ label = "0:apdp_1";
++ reg = <0x440000 0x10000>;
++ read-only;
++ };
++
++ partition@450000 {
++ label = "0:tme";
++ reg = <0x450000 0x40000>;
++ read-only;
++ };
++
++ partition@490000 {
++ label = "0:tme_1";
++ reg = <0x490000 0x40000>;
++ read-only;
++ };
++
++ partition@4d0000 {
++ label = "0:rpm";
++ reg = <0x4d0000 0x20000>;
++ read-only;
++ };
++
++ partition@4f0000 {
++ label = "0:rpm_1";
++ reg = <0x4f0000 0x20000>;
++ read-only;
++ };
++
++ partition@510000 {
++ label = "0:cdt";
++ reg = <0x510000 0x10000>;
++ read-only;
++ };
++
++ partition@520000 {
++ label = "0:cdt_1";
++ reg = <0x520000 0x10000>;
++ read-only;
++ };
++
++ partition@530000 {
++ label = "0:appsblenv";
++ reg = <0x530000 0x10000>;
++
++ nvmem-layout {
++ compatible = "u-boot,env";
++
++ macaddr_lan: ethaddr {
++ #nvmem-cell-cells = <1>;
++ };
++ };
++ };
++
++ partition@540000 {
++ label = "0:appsbl";
++ reg = <0x540000 0xa0000>;
++ read-only;
++ };
++
++ partition@5e0000 {
++ label = "0:appsbl_1";
++ reg = <0x5e0000 0xa0000>;
++ read-only;
++ };
++
++ partition@680000 {
++ label = "0:art";
++ reg = <0x680000 0x100000>;
++ read-only;
++ };
++
++ partition@780000 {
++ label = "0:ethphyfw";
++ reg = <0x780000 0x80000>;
++ read-only;
++
++ nvmem-layout {
++ compatible = "fixed-layout";
++
++ aqr_fw: aqr-fw@0 {
++ reg = <0x0 0x5fc02>;
++ };
++ };
++ };
++ };
+ };
+ };
+
--- /dev/null
+From 7b1c4e22532ded6b20ee41936fa38b5ca1e61ff9 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Mon, 29 Jan 2024 17:57:20 +0800
+Subject: [PATCH] dt-bindings: net: Document Qualcomm QCA8084 PHY package
+
+QCA8084 is quad PHY chip, which integrates 4 PHYs, 2 PCS
+interfaces (PCS0 and PCS1) and clock controller, which can
+also be integrated to the switch chip named as QCA8386.
+
+1. MDIO address of 4 PHYs, 2 PCS and 1 XPCS (PCS1 includes
+ PCS and XPCS, PCS0 includes PCS) can be configured.
+2. The package mode of PHY is optionally configured for the
+ interface mode of two PCSes working correctly.
+3. The package level clock and reset need to be initialized.
+4. The clock and reset per PHY device need to be initialized
+ so that the PHY register can be accessed.
+
+Change-Id: Idb2338d2673152cbd3c57e95968faa59e9d4a80f
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Update to match the patches that will be upstream.
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ .../devicetree/bindings/net/qcom,qca8084.yaml | 488 ++++++++++++++++++
+ include/dt-bindings/net/qcom,qca808x.h | 14 +
+ 2 files changed, 502 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/net/qcom,qca8084.yaml
+ create mode 100644 include/dt-bindings/net/qcom,qca808x.h
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/qcom,qca8084.yaml
+@@ -0,0 +1,488 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/qcom,qca8084.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Qualcomm QCA8084 Ethernet Quad PHY
++
++maintainers:
++ - Luo Jie <quic_luoj@quicinc.com>
++
++description:
++ Qualcomm QCA8084 is PHY package of four-port Ethernet transceiver,
++ the Ethernet port supports link speed 10/100/1000/2500 Mbps.
++ There are two PCSes (PCS0 and PCS1) integrated in the PHY
++ package, PCS1 includes XPCS and PCS to support the interface
++ mode 10G-QXGMII and SGMII, PCS0 includes a PCS to support the
++ interface mode SGMII only. There is also a clock controller
++ integrated in the PHY package. This four-port Ethernet
++ transceiver can also be integrated to the switch chip named
++ as QCA8386. The PHY package mode needs to be configured as the
++ correct value to apply the interface mode of two PCSes as
++ mentioned below.
++
++ QCA8084 expects an input reference clock 50 MHZ as the clock
++ source of the integrated clock controller, the integrated
++ clock controller supplies the clocks and resets to the
++ integrated PHY, PCS and PHY package.
++
++ - |
++ +--| |--+-------------------+--| |--+
++ | PCS1 |<------------+---->| PCS0 |
++ +-------+ | +-------+
++ | | |
++ Ref 50M clk +--------+ | |
++ ------------>| | clk & rst | |
++ GPIO Reset |QCA8K-CC+------------+ |
++ ------------>| | | |
++ +--------+ | |
++ | V |
++ +--------+--------+--------+--------+
++ | PHY0 | PHY1 | PHY2 | PHY3 |
++ +--------+--------+--------+--------+
++
++properties:
++ compatible:
++ const: qcom,qca8084-package
++
++ clocks:
++ description:
++ PHY package level initial common clocks, which are needed to
++ be enabled after GPIO reset on the PHY package, these clocks
++ are supplied from the PHY integrated clock controller (QCA8K-CC).
++ items:
++ - description: APB bridge clock
++ - description: AHB clock
++ - description: Security control clock
++ - description: TLMM clock
++ - description: TLMM AHB clock
++ - description: CNOC AHB clock
++ - description: MDIO AHB clock
++
++ clock-names:
++ items:
++ - const: apb_bridge
++ - const: ahb
++ - const: sec_ctrl_ahb
++ - const: tlmm
++ - const: tlmm_ahb
++ - const: cnoc_ahb
++ - const: mdio_ahb
++
++ resets:
++ description:
++ PHY package level initial common reset, which are needed to
++ be deasserted after GPIO reset on the PHY package, this reset
++ is provided by the PHY integrated clock controller to do PHY
++ DSP reset.
++ maxItems: 1
++
++ qcom,package-mode:
++ description: |
++ The package mode of PHY supports to be configured as 3 modes
++ to apply the combinations of interface mode of two PCSes
++ correctly. This value should use one of the values defined in
++ dt-bindings/net/qcom,qca808x.h. The package mode 10G-QXGMII of
++ Quad PHY is used by default.
++
++ package mode PCS1 PCS0
++ phy mode (0) 10G-QXGMII for not used
++ PHY0-PHY3
++
++ switch mode (1) SGMII for SGMII for
++ switch MAC0 switch MAC5 (optional)
++
++ switch bypass MAC5 (2) SGMII for SGMII for
++ switch MAC0 PHY3
++ $ref: /schemas/types.yaml#/definitions/uint32
++ enum: [0, 1, 2]
++ default: 0
++
++ qcom,phy-addr-fixup:
++ description:
++ MDIO address for PHY0-PHY3, PCS0 and PCS1 including PCS and XPCS,
++ which can be optionally customized by programming the security
++ control register of PHY package. The hardware default MDIO address
++ of PHY0-PHY3, PCS0 and PCS1 including PCS and XPCS is 0-6.
++ $ref: /schemas/types.yaml#/definitions/uint32-array
++ minItems: 7
++ maxItems: 7
++
++patternProperties:
++ ^ethernet-phy@[a-f0-9]+$:
++ unevaluatedProperties: false
++ $ref: ethernet-phy.yaml#
++
++ properties:
++ compatible:
++ const: ethernet-phy-id004d.d180
++
++ qcom,xpcs-channel:
++ description:
++ When PCS1 works on the interface mode 10G-QXGMII, the integrated
++ XPCS including 4 channels is used to connected with the Quad PHYs,
++ each PHY needs to be specified the XPCS channel ID to deliver the
++ PHY link status to the XPCS.
++ $ref: /schemas/types.yaml#/definitions/uint32
++ enum: [0, 1, 2, 3]
++
++ required:
++ - compatible
++ - reg
++ - clocks
++ - resets
++
++ ^pcs-phy@[a-f0-9]+$:
++ type: object
++ additionalProperties: false
++ description:
++ PCS device has the independent MDIO address, which controls
++ the interface mode used and provides the clocks such as
++ 312.5 MHZ as RX and TX root clocks to the integrated clock
++ controller.
++ properties:
++ compatible:
++ const: qcom,qca8k-pcs-phy
++
++ reg:
++ items:
++ - description: PCS MDIO address.
++
++ clocks:
++ items:
++ - description: PCS clock.
++ - description: PCS RX root clock.
++ - description: PCS TX root clock.
++
++ clock-names:
++ items:
++ - const: pcs
++ - const: pcs_rx_root
++ - const: pcs_tx_root
++
++ resets:
++ items:
++ - description: PCS reset.
++
++ required:
++ - compatible
++ - reg
++ - clocks
++ - resets
++
++ ^xpcs-phy@[a-f0-9]+$:
++ type: object
++ additionalProperties: false
++ description:
++ XPCS device has the independent MDIO address, which includes 4
++ channels to connect with Quad PHYs.
++ properties:
++ compatible:
++ const: qcom,qca8k-xpcs-phy
++
++ reg:
++ items:
++ - description: XPCS MDIO address.
++
++ resets:
++ items:
++ - description: XPCS reset.
++
++ '#address-cells':
++ const: 1
++
++ '#size-cells':
++ const: 0
++
++ required:
++ - compatible
++ - reg
++ - resets
++ - '#address-cells'
++ - '#size-cells'
++
++ patternProperties:
++ "^channel@[0-3]+$":
++ type: object
++ additionalProperties: false
++ description:
++ XPCS is used to support 10G-QXGMII mode, which inlcudes 4 channels
++ to be connected with Quad PHYs, each channels has the dedicated
++ clocks and resets from the integrated clock controller of QCA8084.
++
++ properties:
++ reg:
++ items:
++ - description: XPCS channel ID
++
++ clocks:
++ items:
++ - description: XPCS XGMII RX clock
++ - description: XPCS XGMII TX clock
++ - description: XPCS RX clock
++ - description: XPCS TX clock
++ - description: Port RX clock
++ - description: Port TX clock
++ - description: RX source clock
++ - description: TX source clock
++
++ clock-names:
++ items:
++ - const: xgmii_rx
++ - const: xgmii_tx
++ - const: xpcs_rx
++ - const: xpcs_tx
++ - const: port_rx
++ - const: port_tx
++ - const: rx_src
++ - const: tx_src
++
++ resets:
++ items:
++ - description: XPCS XGMII RX reset
++ - description: XPCS XGMII TX reset
++ - description: XPCS RX reset
++ - description: XPCS TX reset
++ - description: Port RX reset
++ - description: Port TX reset
++
++ reset-names:
++ items:
++ - const: xgmii_rx
++ - const: xgmii_tx
++ - const: xpcs_rx
++ - const: xpcs_tx
++ - const: port_rx
++ - const: port_tx
++
++ required:
++ - reg
++ - clocks
++ - clock-names
++ - resets
++ - reset-names
++
++required:
++ - compatible
++ - clocks
++ - clock-names
++ - resets
++
++allOf:
++ - $ref: ethernet-phy-package.yaml#
++
++unevaluatedProperties: false
++
++examples:
++ - |
++ #include <dt-bindings/clock/qcom,qca8k-nsscc.h>
++ #include <dt-bindings/net/qcom,qca808x.h>
++ #include <dt-bindings/reset/qcom,qca8k-nsscc.h>
++
++ mdio {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ ethernet-phy-package@1 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "qcom,qca8084-package";
++ reg = <1>;
++ clocks = <&qca8k_nsscc NSS_CC_APB_BRIDGE_CLK>,
++ <&qca8k_nsscc NSS_CC_AHB_CLK>,
++ <&qca8k_nsscc NSS_CC_SEC_CTRL_AHB_CLK>,
++ <&qca8k_nsscc NSS_CC_TLMM_CLK>,
++ <&qca8k_nsscc NSS_CC_TLMM_AHB_CLK>,
++ <&qca8k_nsscc NSS_CC_CNOC_AHB_CLK>,
++ <&qca8k_nsscc NSS_CC_MDIO_AHB_CLK>;
++ clock-names = "apb_bridge",
++ "ahb",
++ "sec_ctrl_ahb",
++ "tlmm",
++ "tlmm_ahb",
++ "cnoc_ahb",
++ "mdio_ahb";
++ resets = <&qca8k_nsscc NSS_CC_GEPHY_FULL_ARES>;
++ qcom,package-mode = <QCA808X_PCS1_10G_QXGMII_PCS0_UNUNSED>;
++ qcom,phy-addr-fixup = <1 2 3 4 5 6 7>;
++
++ ethernet-phy@1 {
++ compatible = "ethernet-phy-id004d.d180";
++ reg = <1>;
++ clocks = <&qca8k_nsscc NSS_CC_GEPHY0_SYS_CLK>;
++ resets = <&qca8k_nsscc NSS_CC_GEPHY0_SYS_ARES>;
++ qcom,xpcs-channel = <0>;
++ };
++
++ ethernet-phy@2 {
++ compatible = "ethernet-phy-id004d.d180";
++ reg = <2>;
++ clocks = <&qca8k_nsscc NSS_CC_GEPHY1_SYS_CLK>;
++ resets = <&qca8k_nsscc NSS_CC_GEPHY1_SYS_ARES>;
++ qcom,xpcs-channel = <1>;
++ };
++
++ ethernet-phy@3 {
++ compatible = "ethernet-phy-id004d.d180";
++ reg = <3>;
++ clocks = <&qca8k_nsscc NSS_CC_GEPHY2_SYS_CLK>;
++ resets = <&qca8k_nsscc NSS_CC_GEPHY2_SYS_ARES>;
++ qcom,xpcs-channel = <2>;
++ };
++
++ ethernet-phy@4 {
++ compatible = "ethernet-phy-id004d.d180";
++ reg = <4>;
++ clocks = <&qca8k_nsscc NSS_CC_GEPHY3_SYS_CLK>;
++ resets = <&qca8k_nsscc NSS_CC_GEPHY3_SYS_ARES>;
++ qcom,xpcs-channel = <3>;
++ };
++
++ pcs-phy@6 {
++ compatible = "qcom,qca8k-pcs-phy";
++ reg = <6>;
++ clocks = <&qca8k_nsscc NSS_CC_SRDS1_SYS_CLK>,
++ <&qca8k_uniphy1_tx312p5m>,
++ <&qca8k_uniphy1_rx312p5m>;
++ clock-names = "pcs", "pcs_rx_root", "pcs_tx_root";
++ resets = <&qca8k_nsscc NSS_CC_SRDS1_SYS_ARES>;
++ };
++
++ xpcs-phy@7 {
++ compatible = "qcom,qca8k-xpcs-phy";
++ reg = <7>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ resets = <&qca8k_nsscc NSS_CC_XPCS_ARES>;
++
++ channel@0 {
++ reg = <0>;
++ clocks = <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_XGMII_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_XGMII_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC1_GEPHY0_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC1_GEPHY0_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC1_RX_CLK_SRC>,
++ <&qca8k_nsscc NSS_CC_MAC1_TX_CLK_SRC>;
++ clock-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx",
++ "rx_src",
++ "tx_src";
++ resets = <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_XGMII_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_XGMII_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC1_SRDS1_CH0_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC1_GEPHY0_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC1_GEPHY0_TX_ARES>;
++ reset-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx";
++ };
++
++ channel@1 {
++ reg = <1>;
++ clocks = <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_XGMII_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_XGMII_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC2_GEPHY1_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC2_GEPHY1_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC2_RX_CLK_SRC>,
++ <&qca8k_nsscc NSS_CC_MAC2_TX_CLK_SRC>;
++ clock-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx",
++ "rx_src",
++ "tx_src";
++ resets = <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_XGMII_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_XGMII_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC2_SRDS1_CH1_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC2_GEPHY1_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC2_GEPHY1_TX_ARES>;
++ reset-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx";
++ };
++
++ channel@2 {
++ reg = <2>;
++ clocks = <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_XGMII_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_XGMII_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC3_GEPHY2_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC3_GEPHY2_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC3_RX_CLK_SRC>,
++ <&qca8k_nsscc NSS_CC_MAC3_TX_CLK_SRC>;
++ clock-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx",
++ "rx_src",
++ "tx_src";
++ resets = <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_XGMII_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_XGMII_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC3_SRDS1_CH2_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC3_GEPHY2_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC3_GEPHY2_TX_ARES>;
++ reset-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx";
++ };
++
++ channel@3 {
++ reg = <3>;
++ clocks = <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_XGMII_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_XGMII_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC4_GEPHY3_RX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC4_GEPHY3_TX_CLK>,
++ <&qca8k_nsscc NSS_CC_MAC4_RX_CLK_SRC>,
++ <&qca8k_nsscc NSS_CC_MAC4_TX_CLK_SRC>;
++ clock-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx",
++ "rx_src",
++ "tx_src";
++ resets = <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_XGMII_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_XGMII_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_TX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC4_SRDS1_CH3_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC4_GEPHY3_RX_ARES>,
++ <&qca8k_nsscc NSS_CC_MAC4_GEPHY3_TX_ARES>;
++ reset-names = "xgmii_rx",
++ "xgmii_tx",
++ "xpcs_rx",
++ "xpcs_tx",
++ "port_rx",
++ "port_tx";
++ };
++ };
++ };
++ };
+--- /dev/null
++++ b/include/dt-bindings/net/qcom,qca808x.h
+@@ -0,0 +1,14 @@
++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
++/*
++ * Device Tree constants for the Qualcomm QCA808X PHYs
++ */
++
++#ifndef _DT_BINDINGS_QCOM_QCA808X_H
++#define _DT_BINDINGS_QCOM_QCA808X_H
++
++/* PHY package modes of QCA8084 to apply the interface modes of two PCSes. */
++#define QCA808X_PCS1_10G_QXGMII_PCS0_UNUNSED 0
++#define QCA808X_PCS1_SGMII_MAC_PCS0_SGMII_MAC 1
++#define QCA808X_PCS1_SGMII_MAC_PCS0_SGMII_PHY 2
++
++#endif
--- /dev/null
+From 60c44842f9611be237ab3f68afe8ebf2d9595fb2 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Thu, 6 Apr 2023 18:09:07 +0800
+Subject: [PATCH] net: phy: qca808x: Add QCA8084 ethernet phy support
+
+Add QCA8084 Quad-PHY support, which is a four-port PHY with
+maximum link capability of 2.5 Gbps. The features of each port
+are almost same as QCA8081. The slave seed and fast retrain
+configs are not needed for QCA8084. It includes two PCSes.
+
+PCS0 of QCA8084 supports the interface modes:
+PHY_INTERFACE_MODE_2500BASEX and PHY_INTERFACE_MODE_SGMII.
+
+PCS1 of QCA8084 supports the interface modes:
+PHY_INTERFACE_MODE_10G_QXGMII, PHY_INTERFACE_MODE_2500BASEX and
+PHY_INTERFACE_MODE_SGMII.
+
+The additional CDT configurations needed for QCA8084 compared
+with QCA8081.
+
+Change-Id: I12555fa70662682474ab4432204405b5e752fef6
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Update to match the patches that will be upstream.
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/phy/qcom/qca808x.c | 65 ++++++++++++++++++++++++++++++++--
+ 1 file changed, 63 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -86,9 +86,16 @@
+ #define QCA8081_PHY_FIFO_RSTN BIT(11)
+
+ #define QCA8081_PHY_ID 0x004dd101
++#define QCA8084_PHY_ID 0x004dd180
++
++#define QCA8084_MMD3_CDT_PULSE_CTRL 0x8075
++#define QCA8084_CDT_PULSE_THRESH_VAL 0xa060
++
++#define QCA8084_MMD3_CDT_NEAR_CTRL 0x807f
++#define QCA8084_CDT_NEAR_BYPASS BIT(15)
+
+ MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
+-MODULE_AUTHOR("Matus Ujhelyi");
++MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+
+ struct qca808x_priv {
+@@ -153,13 +160,18 @@ static bool qca808x_is_prefer_master(str
+
+ static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+ {
+- return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
++ return phydev_id_compare(phydev, QCA8081_PHY_ID) &&
++ linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
++ phydev->supported);
+ }
+
+ static bool qca808x_is_1g_only(struct phy_device *phydev)
+ {
+ int ret;
+
++ if (!phydev_id_compare(phydev, QCA8081_PHY_ID))
++ return false;
++
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+ if (ret < 0)
+ return true;
+@@ -273,6 +285,23 @@ static int qca808x_read_status(struct ph
+ return ret;
+
+ if (phydev->link) {
++ /* There are two PCSes available for QCA8084, which support
++ * the following interface modes.
++ *
++ * 1. PHY_INTERFACE_MODE_10G_QXGMII utilizes PCS1 for all
++ * available 4 ports, which is for all link speeds.
++ *
++ * 2. PHY_INTERFACE_MODE_2500BASEX utilizes PCS0 for the
++ * fourth port, which is only for the link speed 2500M same
++ * as QCA8081.
++ *
++ * 3. PHY_INTERFACE_MODE_SGMII utilizes PCS0 for the fourth
++ * port, which is for the link speed 10M, 100M and 1000M same
++ * as QCA8081.
++ */
++ if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII)
++ return 0;
++
+ if (phydev->speed == SPEED_2500)
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ else
+@@ -352,6 +381,18 @@ static int qca808x_cable_test_start(stru
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
+
++ if (phydev_id_compare(phydev, QCA8084_PHY_ID)) {
++ /* Adjust the positive and negative pulse thereshold of CDT. */
++ phy_write_mmd(phydev, MDIO_MMD_PCS,
++ QCA8084_MMD3_CDT_PULSE_CTRL,
++ QCA8084_CDT_PULSE_THRESH_VAL);
++
++ /* Disable the near bypass of CDT. */
++ phy_modify_mmd(phydev, MDIO_MMD_PCS,
++ QCA8084_MMD3_CDT_NEAR_CTRL,
++ QCA8084_CDT_NEAR_BYPASS, 0);
++ }
++
+ return 0;
+ }
+
+@@ -651,12 +692,32 @@ static struct phy_driver qca808x_driver[
+ .led_hw_control_set = qca808x_led_hw_control_set,
+ .led_hw_control_get = qca808x_led_hw_control_get,
+ .led_polarity_set = qca808x_led_polarity_set,
++}, {
++ /* Qualcomm QCA8084 */
++ PHY_ID_MATCH_MODEL(QCA8084_PHY_ID),
++ .name = "Qualcomm QCA8084",
++ .flags = PHY_POLL_CABLE_TEST,
++ .config_intr = at803x_config_intr,
++ .handle_interrupt = at803x_handle_interrupt,
++ .get_tunable = at803x_get_tunable,
++ .set_tunable = at803x_set_tunable,
++ .set_wol = at803x_set_wol,
++ .get_wol = at803x_get_wol,
++ .get_features = qca808x_get_features,
++ .config_aneg = qca808x_config_aneg,
++ .suspend = genphy_suspend,
++ .resume = genphy_resume,
++ .read_status = qca808x_read_status,
++ .soft_reset = qca808x_soft_reset,
++ .cable_test_start = qca808x_cable_test_start,
++ .cable_test_get_status = qca808x_cable_test_get_status,
+ }, };
+
+ module_phy_driver(qca808x_driver);
+
+ static const struct mdio_device_id __maybe_unused qca808x_tbl[] = {
+ { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
++ { PHY_ID_MATCH_MODEL(QCA8084_PHY_ID) },
+ { }
+ };
+
--- /dev/null
+From c052b9a4ab869cc54976402b3f9dbdef5bdb9f27 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Wed, 8 Nov 2023 16:18:02 +0800
+Subject: [PATCH] net: phy: qca808x: Add config_init function for QCA8084
+
+1. The ADC of QCA8084 PHY must be configured as edge inverted
+and falling whenever it is initialized or reset. In addition,
+the default MSE (Mean square error) threshold value is adjusted,
+which comes into play during link partner detection to detect
+the valid link signal.
+
+2. Add the possible interface modes.
+ When QCA8084 works on the interface mode SGMII or 2500BASE-X, the
+ interface mode can be switched according to the PHY link speed.
+
+ When QCA8084 works on the 10G-QXGMII mode, which will be the only
+ possible interface mode.
+
+Change-Id: I832c0d0b069e95cc411a8a7b680a5f60e1d6041a
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/phy/qcom/qca808x.c | 38 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 38 insertions(+)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -94,6 +94,15 @@
+ #define QCA8084_MMD3_CDT_NEAR_CTRL 0x807f
+ #define QCA8084_CDT_NEAR_BYPASS BIT(15)
+
++/* QCA8084 ADC clock edge */
++#define QCA8084_ADC_CLK_SEL 0x8b80
++#define QCA8084_ADC_CLK_SEL_ACLK GENMASK(7, 4)
++#define QCA8084_ADC_CLK_SEL_ACLK_FALL 0xf
++#define QCA8084_ADC_CLK_SEL_ACLK_RISE 0x0
++
++#define QCA8084_MSE_THRESHOLD 0x800a
++#define QCA8084_MSE_THRESHOLD_2P5G_VAL 0x51c6
++
+ MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+@@ -663,6 +672,34 @@ static int qca808x_led_polarity_set(stru
+ active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
+ }
+
++static int qca8084_config_init(struct phy_device *phydev)
++{
++ int ret;
++
++ if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII)
++ __set_bit(PHY_INTERFACE_MODE_10G_QXGMII,
++ phydev->possible_interfaces);
++ else
++ qca808x_fill_possible_interfaces(phydev);
++
++ /* Configure the ADC to convert the signal using falling edge
++ * instead of the default rising edge.
++ */
++ ret = at803x_debug_reg_mask(phydev, QCA8084_ADC_CLK_SEL,
++ QCA8084_ADC_CLK_SEL_ACLK,
++ FIELD_PREP(QCA8084_ADC_CLK_SEL_ACLK,
++ QCA8084_ADC_CLK_SEL_ACLK_FALL));
++ if (ret < 0)
++ return ret;
++
++ /* Adjust MSE threshold value to avoid link issue with
++ * some link partner.
++ */
++ return phy_write_mmd(phydev, MDIO_MMD_PMAPMD,
++ QCA8084_MSE_THRESHOLD,
++ QCA8084_MSE_THRESHOLD_2P5G_VAL);
++}
++
+ static struct phy_driver qca808x_driver[] = {
+ {
+ /* Qualcomm QCA8081 */
+@@ -711,6 +748,7 @@ static struct phy_driver qca808x_driver[
+ .soft_reset = qca808x_soft_reset,
+ .cable_test_start = qca808x_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
++ .config_init = qca8084_config_init,
+ }, };
+
+ module_phy_driver(qca808x_driver);
--- /dev/null
+From aec49c172cd9c739c1d97ff2d42b9718bb20b609 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Wed, 8 Nov 2023 18:01:14 +0800
+Subject: [PATCH] net: phy: qca808x: Add link_change_notify function for
+ QCA8084
+
+When the link is changed, QCA8084 needs to do the fifo reset and
+adjust the IPG level for the 10G-QXGMII link on the speed 1000M.
+
+Change-Id: I21de802c78496fb95f1c5119fe3894c9fdebbd65
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/phy/qcom/qca808x.c | 52 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 52 insertions(+)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -103,6 +103,14 @@
+ #define QCA8084_MSE_THRESHOLD 0x800a
+ #define QCA8084_MSE_THRESHOLD_2P5G_VAL 0x51c6
+
++/* QCA8084 FIFO reset control */
++#define QCA8084_FIFO_CONTROL 0x19
++#define QCA8084_FIFO_MAC_2_PHY BIT(1)
++#define QCA8084_FIFO_PHY_2_MAC BIT(0)
++
++#define QCA8084_MMD7_IPG_OP 0x901d
++#define QCA8084_IPG_10_TO_11_EN BIT(0)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+@@ -700,6 +708,49 @@ static int qca8084_config_init(struct ph
+ QCA8084_MSE_THRESHOLD_2P5G_VAL);
+ }
+
++static void qca8084_link_change_notify(struct phy_device *phydev)
++{
++ int ret;
++
++ /* Assert the FIFO between PHY and MAC. */
++ ret = phy_modify(phydev, QCA8084_FIFO_CONTROL,
++ QCA8084_FIFO_MAC_2_PHY | QCA8084_FIFO_PHY_2_MAC,
++ 0);
++ if (ret) {
++ phydev_err(phydev, "Asserting PHY FIFO failed\n");
++ return;
++ }
++
++ /* If the PHY is in 10G_QXGMII mode, the FIFO needs to be kept in
++ * reset state when link is down, otherwise the FIFO needs to be
++ * de-asserted after waiting 50 ms to make the assert completed.
++ */
++ if (phydev->interface != PHY_INTERFACE_MODE_10G_QXGMII ||
++ phydev->link) {
++ msleep(50);
++
++ /* Deassert the FIFO between PHY and MAC. */
++ ret = phy_modify(phydev, QCA8084_FIFO_CONTROL,
++ QCA8084_FIFO_MAC_2_PHY |
++ QCA8084_FIFO_PHY_2_MAC,
++ QCA8084_FIFO_MAC_2_PHY |
++ QCA8084_FIFO_PHY_2_MAC);
++ if (ret) {
++ phydev_err(phydev, "De-asserting PHY FIFO failed\n");
++ return;
++ }
++ }
++
++ /* Enable IPG level 10 to 11 tuning for link speed 1000M in the
++ * 10G_QXGMII mode.
++ */
++ if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII)
++ phy_modify_mmd(phydev, MDIO_MMD_AN, QCA8084_MMD7_IPG_OP,
++ QCA8084_IPG_10_TO_11_EN,
++ phydev->speed == SPEED_1000 ?
++ QCA8084_IPG_10_TO_11_EN : 0);
++}
++
+ static struct phy_driver qca808x_driver[] = {
+ {
+ /* Qualcomm QCA8081 */
+@@ -749,6 +800,7 @@ static struct phy_driver qca808x_driver[
+ .cable_test_start = qca808x_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
+ .config_init = qca8084_config_init,
++ .link_change_notify = qca8084_link_change_notify,
+ }, };
+
+ module_phy_driver(qca808x_driver);
--- /dev/null
+From cea8043def0c0867370c2efd5a1cd73bf4d3e5ba Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Wed, 29 Nov 2023 15:21:22 +0800
+Subject: [PATCH] net: phy: qca808x: Add register access support routines for
+ QCA8084
+
+QCA8084 integrates clock controller and security control modules
+besides of the PHY and PCS. The 32bit registers in these modules
+are accessed using special MDIO sequences to read or write these
+registers.
+
+The MDIO address of PHY and PCS are configured by writing to the
+security control register. The package mode for QCA8084 is also
+configured in a similar manner.
+
+Change-Id: I9317307ef9bbc738a6adcbc3ea1be8e6528d711e
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/phy/qcom/qca808x.c | 88 ++++++++++++++++++++++++++++++++++
+ 1 file changed, 88 insertions(+)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -111,6 +111,22 @@
+ #define QCA8084_MMD7_IPG_OP 0x901d
+ #define QCA8084_IPG_10_TO_11_EN BIT(0)
+
++/* QCA8084 includes secure control module, which supports customizing the
++ * MDIO address of PHY device and PCS device and configuring package mode
++ * for the interface mode of PCS. The register of secure control is accessed
++ * by MDIO bus with the special MDIO sequences, where the 32 bits register
++ * address is split into 3 MDIO operations with 16 bits address.
++ */
++#define QCA8084_HIGH_ADDR_PREFIX 0x18
++#define QCA8084_LOW_ADDR_PREFIX 0x10
++
++/* Bottom two bits of REG must be zero */
++#define QCA8084_MII_REG_MASK GENMASK(4, 0)
++#define QCA8084_MII_PHY_ADDR_MASK GENMASK(7, 5)
++#define QCA8084_MII_PAGE_MASK GENMASK(23, 8)
++#define QCA8084_MII_SW_ADDR_MASK GENMASK(31, 24)
++#define QCA8084_MII_REG_DATA_UPPER_16_BITS BIT(1)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+@@ -119,6 +135,78 @@ struct qca808x_priv {
+ int led_polarity_mode;
+ };
+
++static int __qca8084_set_page(struct mii_bus *bus, u16 sw_addr, u16 page)
++{
++ return __mdiobus_write(bus, QCA8084_HIGH_ADDR_PREFIX | (sw_addr >> 5),
++ sw_addr & 0x1f, page);
++}
++
++static int __qca8084_mii_read(struct mii_bus *bus, u16 addr, u16 reg, u32 *val)
++{
++ int ret, data;
++
++ ret = __mdiobus_read(bus, addr, reg);
++ if (ret < 0)
++ return ret;
++
++ data = ret;
++ ret = __mdiobus_read(bus, addr,
++ reg | QCA8084_MII_REG_DATA_UPPER_16_BITS);
++ if (ret < 0)
++ return ret;
++
++ *val = data | ret << 16;
++
++ return 0;
++}
++
++static int __qca8084_mii_write(struct mii_bus *bus, u16 addr, u16 reg, u32 val)
++{
++ int ret;
++
++ ret = __mdiobus_write(bus, addr, reg, lower_16_bits(val));
++ if (!ret)
++ ret = __mdiobus_write(bus, addr,
++ reg | QCA8084_MII_REG_DATA_UPPER_16_BITS,
++ upper_16_bits(val));
++
++ return ret;
++}
++
++static int qca8084_mii_modify(struct phy_device *phydev, u32 regaddr,
++ u32 clear, u32 set)
++{
++ u16 reg, addr, page, sw_addr;
++ struct mii_bus *bus;
++ u32 val;
++ int ret;
++
++ bus = phydev->mdio.bus;
++ mutex_lock(&bus->mdio_lock);
++
++ reg = FIELD_GET(QCA8084_MII_REG_MASK, regaddr);
++ addr = FIELD_GET(QCA8084_MII_PHY_ADDR_MASK, regaddr);
++ page = FIELD_GET(QCA8084_MII_PAGE_MASK, regaddr);
++ sw_addr = FIELD_GET(QCA8084_MII_SW_ADDR_MASK, regaddr);
++
++ ret = __qca8084_set_page(bus, sw_addr, page);
++ if (ret < 0)
++ goto qca8084_mii_modify_exit;
++
++ ret = __qca8084_mii_read(bus, QCA8084_LOW_ADDR_PREFIX | addr,
++ reg, &val);
++ if (ret < 0)
++ goto qca8084_mii_modify_exit;
++
++ val &= ~clear;
++ val |= set;
++ ret = __qca8084_mii_write(bus, QCA8084_LOW_ADDR_PREFIX | addr,
++ reg, val);
++qca8084_mii_modify_exit:
++ mutex_unlock(&bus->mdio_lock);
++ return ret;
++};
++
+ static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+ {
+ int ret;
--- /dev/null
+From a7fe2c13f3188bf01b60fb15063d028c76dd2f1a Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Mon, 29 Jan 2024 10:51:38 +0800
+Subject: [PATCH] net: phy: qca808x: Add QCA8084 probe function
+
+Add the PHY package probe function. The MDIO slave address of
+PHY, PCS and XPCS can be optionally customized by configuring
+the PHY package level register.
+
+In addition, enable system clock of PHY and de-assert PHY in
+the probe function so that the register of PHY device can be
+accessed, and the features of PHY can be acquired.
+
+Change-Id: I2251b9c5c398a21a4ef547a727189a934ad3a44c
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: include <linux/reset.h>
+ include "phylib.h" for phy_package_*() declarations
+ select PHY_PACKAGE in Kconfig
+ use phy_package_get_node() instead of phylib->shared->np
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+
+freckup c89414adf2ec7c
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/phy/qcom/Kconfig | 1 +
+ drivers/net/phy/qcom/qca808x.c | 92 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 93 insertions(+)
+
+--- a/drivers/net/phy/qcom/Kconfig
++++ b/drivers/net/phy/qcom/Kconfig
+@@ -18,6 +18,7 @@ config QCA83XX_PHY
+ config QCA808X_PHY
+ tristate "Qualcomm QCA808x PHYs"
+ select QCOM_NET_PHYLIB
++ select PHY_PACKAGE
+ help
+ Currently supports the QCA8081 model
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -2,7 +2,11 @@
+
+ #include <linux/phy.h>
+ #include <linux/module.h>
++#include <linux/of.h>
++#include <linux/reset.h>
++#include <linux/clk.h>
+
++#include "../phylib.h"
+ #include "qcom.h"
+
+ /* ADC threshold */
+@@ -127,6 +131,21 @@
+ #define QCA8084_MII_SW_ADDR_MASK GENMASK(31, 24)
+ #define QCA8084_MII_REG_DATA_UPPER_16_BITS BIT(1)
+
++/* QCA8084 integrates 4 PHYs, PCS0 and PCS1(includes PCS and XPCS). */
++#define QCA8084_MDIO_DEVICE_NUM 7
++
++#define QCA8084_PCS_CFG 0xc90f014
++#define QCA8084_PCS_ADDR0_MASK GENMASK(4, 0)
++#define QCA8084_PCS_ADDR1_MASK GENMASK(9, 5)
++#define QCA8084_PCS_ADDR2_MASK GENMASK(14, 10)
++
++#define QCA8084_EPHY_CFG 0xc90f018
++#define QCA8084_EPHY_ADDR0_MASK GENMASK(4, 0)
++#define QCA8084_EPHY_ADDR1_MASK GENMASK(9, 5)
++#define QCA8084_EPHY_ADDR2_MASK GENMASK(14, 10)
++#define QCA8084_EPHY_ADDR3_MASK GENMASK(19, 15)
++#define QCA8084_EPHY_LDO_EN GENMASK(21, 20)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+@@ -839,6 +858,78 @@ static void qca8084_link_change_notify(s
+ QCA8084_IPG_10_TO_11_EN : 0);
+ }
+
++static int qca8084_phy_package_probe_once(struct phy_device *phydev)
++{
++ int addr[QCA8084_MDIO_DEVICE_NUM] = {0, 1, 2, 3, 4, 5, 6};
++ struct device_node *np = phy_package_get_node(phydev);
++ int ret, clear, set;
++
++ /* Program the MDIO address of PHY and PCS optionally, the MDIO
++ * address 0-6 is used for PHY and PCS MDIO devices by default.
++ */
++ ret = of_property_read_u32_array(np, "qcom,phy-addr-fixup",
++ addr, ARRAY_SIZE(addr));
++ if (ret && ret != -EINVAL)
++ return ret;
++
++ /* Configure the MDIO addresses for the four PHY devices. */
++ clear = QCA8084_EPHY_ADDR0_MASK | QCA8084_EPHY_ADDR1_MASK |
++ QCA8084_EPHY_ADDR2_MASK | QCA8084_EPHY_ADDR3_MASK;
++ set = FIELD_PREP(QCA8084_EPHY_ADDR0_MASK, addr[0]);
++ set |= FIELD_PREP(QCA8084_EPHY_ADDR1_MASK, addr[1]);
++ set |= FIELD_PREP(QCA8084_EPHY_ADDR2_MASK, addr[2]);
++ set |= FIELD_PREP(QCA8084_EPHY_ADDR3_MASK, addr[3]);
++
++ ret = qca8084_mii_modify(phydev, QCA8084_EPHY_CFG, clear, set);
++ if (ret)
++ return ret;
++
++ /* Configure the MDIO addresses for PCS0 and PCS1 including
++ * PCS and XPCS.
++ */
++ clear = QCA8084_PCS_ADDR0_MASK | QCA8084_PCS_ADDR1_MASK |
++ QCA8084_PCS_ADDR2_MASK;
++ set = FIELD_PREP(QCA8084_PCS_ADDR0_MASK, addr[4]);
++ set |= FIELD_PREP(QCA8084_PCS_ADDR1_MASK, addr[5]);
++ set |= FIELD_PREP(QCA8084_PCS_ADDR2_MASK, addr[6]);
++
++ return qca8084_mii_modify(phydev, QCA8084_PCS_CFG, clear, set);
++}
++
++static int qca8084_probe(struct phy_device *phydev)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct reset_control *rstc;
++ struct clk *clk;
++ int ret;
++
++ ret = devm_of_phy_package_join(dev, phydev, 0);
++ if (ret)
++ return ret;
++
++ if (phy_package_probe_once(phydev)) {
++ ret = qca8084_phy_package_probe_once(phydev);
++ if (ret)
++ return ret;
++ }
++
++ /* Enable clock of PHY device, so that the PHY register
++ * can be accessed to get PHY features.
++ */
++ clk = devm_clk_get_enabled(dev, NULL);
++ if (IS_ERR(clk))
++ return dev_err_probe(dev, PTR_ERR(clk),
++ "Enable PHY clock failed\n");
++
++ /* De-assert PHY reset after the clock of PHY enabled. */
++ rstc = devm_reset_control_get_exclusive(dev, NULL);
++ if (IS_ERR(rstc))
++ return dev_err_probe(dev, PTR_ERR(rstc),
++ "Get PHY reset failed\n");
++
++ return reset_control_deassert(rstc);
++}
++
+ static struct phy_driver qca808x_driver[] = {
+ {
+ /* Qualcomm QCA8081 */
+@@ -889,6 +980,7 @@ static struct phy_driver qca808x_driver[
+ .cable_test_get_status = qca808x_cable_test_get_status,
+ .config_init = qca8084_config_init,
+ .link_change_notify = qca8084_link_change_notify,
++ .probe = qca8084_probe,
+ }, };
+
+ module_phy_driver(qca808x_driver);
--- /dev/null
+From 57379fe257895b374d35ce6578ecd62ce1cc1a4d Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Tue, 9 Apr 2024 16:30:55 +0800
+Subject: [PATCH] net: phy: qca808x: Add package clocks and resets for QCA8084
+
+Parse the PHY package clocks from the PHY package DTS node.
+These package level clocks will be enabled in the PHY package
+init function.
+
+Deassert PHY package reset, which is necessary for accessing
+the PHY registers.
+
+Change-Id: I254d0aa0a1155d3618c6f1fc7d7a5b6ecadccbaa
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Use accessors for struct phy_package_shared
+ Update to match the patches that will be upstream.
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/phy/qcom/qca808x.c | 74 ++++++++++++++++++++++++++++++++--
+ 1 file changed, 71 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -150,10 +150,39 @@ MODULE_DESCRIPTION("Qualcomm Atheros QCA
+ MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+
++enum {
++ APB_BRIDGE_CLK,
++ AHB_CLK,
++ SEC_CTRL_AHB_CLK,
++ TLMM_CLK,
++ TLMM_AHB_CLK,
++ CNOC_AHB_CLK,
++ MDIO_AHB_CLK,
++ MDIO_MASTER_AHB_CLK,
++ SWITCH_CORE_CLK,
++ PACKAGE_CLK_MAX
++};
++
+ struct qca808x_priv {
+ int led_polarity_mode;
+ };
+
++struct qca808x_shared_priv {
++ struct clk *clk[PACKAGE_CLK_MAX];
++};
++
++static const char *const qca8084_package_clk_name[PACKAGE_CLK_MAX] = {
++ [APB_BRIDGE_CLK] = "apb_bridge",
++ [AHB_CLK] = "ahb",
++ [SEC_CTRL_AHB_CLK] = "sec_ctrl_ahb",
++ [TLMM_CLK] = "tlmm",
++ [TLMM_AHB_CLK] = "tlmm_ahb",
++ [CNOC_AHB_CLK] = "cnoc_ahb",
++ [MDIO_AHB_CLK] = "mdio_ahb",
++ [MDIO_MASTER_AHB_CLK] = "mdio_master_ahb",
++ [SWITCH_CORE_CLK] = "switch_core",
++};
++
+ static int __qca8084_set_page(struct mii_bus *bus, u16 sw_addr, u16 page)
+ {
+ return __mdiobus_write(bus, QCA8084_HIGH_ADDR_PREFIX | (sw_addr >> 5),
+@@ -858,11 +887,24 @@ static void qca8084_link_change_notify(s
+ QCA8084_IPG_10_TO_11_EN : 0);
+ }
+
++/* QCA8084 is a four-port PHY, which integrates the clock controller,
++ * 4 PHY devices and 2 PCS interfaces (PCS0 and PCS1). PCS1 includes
++ * XPCS and PCS to support 10G-QXGMII and SGMII. PCS0 includes one PCS
++ * to support SGMII.
++ *
++ * The clocks and resets are sourced from the integrated clock controller
++ * of the PHY package. This integrated clock controller is driven by a
++ * QCA8K clock provider that supplies the clocks and resets to the four
++ * PHYs, PCS and PHY package.
++ */
+ static int qca8084_phy_package_probe_once(struct phy_device *phydev)
+ {
+ int addr[QCA8084_MDIO_DEVICE_NUM] = {0, 1, 2, 3, 4, 5, 6};
+ struct device_node *np = phy_package_get_node(phydev);
+- int ret, clear, set;
++ struct qca808x_shared_priv *shared_priv;
++ struct reset_control *rstc;
++ int i, ret, clear, set;
++ struct clk *clk;
+
+ /* Program the MDIO address of PHY and PCS optionally, the MDIO
+ * address 0-6 is used for PHY and PCS MDIO devices by default.
+@@ -893,17 +935,43 @@ static int qca8084_phy_package_probe_onc
+ set |= FIELD_PREP(QCA8084_PCS_ADDR1_MASK, addr[5]);
+ set |= FIELD_PREP(QCA8084_PCS_ADDR2_MASK, addr[6]);
+
+- return qca8084_mii_modify(phydev, QCA8084_PCS_CFG, clear, set);
++ ret = qca8084_mii_modify(phydev, QCA8084_PCS_CFG, clear, set);
++ if (ret)
++ return ret;
++
++ shared_priv = phy_package_get_priv(phydev);
++ for (i = 0; i < ARRAY_SIZE(qca8084_package_clk_name); i++) {
++ clk = of_clk_get_by_name(np, qca8084_package_clk_name[i]);
++ if (IS_ERR(clk)) {
++ if (PTR_ERR(clk) == -EINVAL)
++ clk = NULL;
++ else
++ return dev_err_probe(&phydev->mdio.dev, PTR_ERR(clk),
++ "package clock %s not ready\n",
++ qca8084_package_clk_name[i]);
++ }
++
++ shared_priv->clk[i] = clk;
++ }
++
++ rstc = of_reset_control_get_exclusive(np, NULL);
++ if (IS_ERR(rstc))
++ return dev_err_probe(&phydev->mdio.dev, PTR_ERR(rstc),
++ "package reset not ready\n");
++
++ /* Deassert PHY package. */
++ return reset_control_deassert(rstc);
+ }
+
+ static int qca8084_probe(struct phy_device *phydev)
+ {
++ struct qca808x_shared_priv *shared_priv;
+ struct device *dev = &phydev->mdio.dev;
+ struct reset_control *rstc;
+ struct clk *clk;
+ int ret;
+
+- ret = devm_of_phy_package_join(dev, phydev, 0);
++ ret = devm_of_phy_package_join(dev, phydev, sizeof(*shared_priv));
+ if (ret)
+ return ret;
+
--- /dev/null
+From d39dc53424bcc778f1e468015490577e7bf0c7b6 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Thu, 25 Jan 2024 17:13:24 +0800
+Subject: [PATCH] net: phy: qca808x: Add QCA8084 package init function
+
+The package mode of PHY is configured for the interface mode of two
+PCSes working correctly.
+
+The PHY package level clocks are enabled and their rates configured.
+
+Change-Id: I63d4b22d2a70ee713cc6a6818b0f3c7aa098a5f5
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Use phy_package_get_*() accessors
+ Update to match the patches that will be upstream.
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/phy/qcom/qca808x.c | 118 +++++++++++++++++++++++++++++++++
+ 1 file changed, 118 insertions(+)
+
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -1,5 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0+
+
++#include <dt-bindings/net/qcom,qca808x.h>
+ #include <linux/phy.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+@@ -146,6 +147,12 @@
+ #define QCA8084_EPHY_ADDR3_MASK GENMASK(19, 15)
+ #define QCA8084_EPHY_LDO_EN GENMASK(21, 20)
+
++#define QCA8084_WORK_MODE_CFG 0xc90f030
++#define QCA8084_WORK_MODE_MASK GENMASK(5, 0)
++#define QCA8084_WORK_MODE_QXGMII (BIT(5) | GENMASK(3, 0))
++#define QCA8084_WORK_MODE_SWITCH BIT(4)
++#define QCA8084_WORK_MODE_SWITCH_PORT4_SGMII BIT(5)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
+ MODULE_LICENSE("GPL");
+@@ -168,6 +175,7 @@ struct qca808x_priv {
+ };
+
+ struct qca808x_shared_priv {
++ int package_mode;
+ struct clk *clk[PACKAGE_CLK_MAX];
+ };
+
+@@ -816,10 +824,111 @@ static int qca808x_led_polarity_set(stru
+ active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
+ }
+
++static int qca8084_package_clock_init(struct qca808x_shared_priv *shared_priv)
++{
++ int ret;
++
++ /* Configure clock rate 312.5MHZ for the PHY package
++ * APB bridge clock tree.
++ */
++ ret = clk_set_rate(shared_priv->clk[APB_BRIDGE_CLK], 312500000);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[SWITCH_CORE_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[APB_BRIDGE_CLK]);
++ if (ret)
++ return ret;
++
++ /* Configure clock rate 104.17MHZ for the PHY package
++ * AHB clock tree.
++ */
++ ret = clk_set_rate(shared_priv->clk[AHB_CLK], 104170000);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[AHB_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[SEC_CTRL_AHB_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[TLMM_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[TLMM_AHB_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[CNOC_AHB_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_prepare_enable(shared_priv->clk[MDIO_MASTER_AHB_CLK]);
++ if (ret)
++ return ret;
++
++ return clk_prepare_enable(shared_priv->clk[MDIO_AHB_CLK]);
++}
++
++static int qca8084_phy_package_config_init_once(struct phy_device *phydev)
++{
++ struct qca808x_shared_priv *shared_priv;
++ int ret, mode;
++
++ shared_priv = phy_package_get_priv(phydev);
++ switch (shared_priv->package_mode) {
++ case QCA808X_PCS1_10G_QXGMII_PCS0_UNUNSED:
++ mode = QCA8084_WORK_MODE_QXGMII;
++ break;
++ case QCA808X_PCS1_SGMII_MAC_PCS0_SGMII_MAC:
++ mode = QCA8084_WORK_MODE_SWITCH;
++ break;
++ case QCA808X_PCS1_SGMII_MAC_PCS0_SGMII_PHY:
++ mode = QCA8084_WORK_MODE_SWITCH_PORT4_SGMII;
++ break;
++ default:
++ phydev_err(phydev, "Invalid qcom,package-mode %d\n",
++ shared_priv->package_mode);
++ return -EINVAL;
++ }
++
++ ret = qca8084_mii_modify(phydev, QCA8084_WORK_MODE_CFG,
++ QCA8084_WORK_MODE_MASK,
++ FIELD_PREP(QCA8084_WORK_MODE_MASK, mode));
++ if (ret)
++ return ret;
++
++ /* Enable efuse loading into analog circuit */
++ ret = qca8084_mii_modify(phydev, QCA8084_EPHY_CFG,
++ QCA8084_EPHY_LDO_EN, 0);
++ if (ret)
++ return ret;
++
++ usleep_range(10000, 11000);
++
++ /* Initialize the PHY package clock and reset, which is the
++ * necessary config sequence after GPIO reset on the PHY package.
++ */
++ return qca8084_package_clock_init(shared_priv);
++}
++
+ static int qca8084_config_init(struct phy_device *phydev)
+ {
+ int ret;
+
++ if (phy_package_init_once(phydev)) {
++ ret = qca8084_phy_package_config_init_once(phydev);
++ if (ret)
++ return ret;
++ }
++
+ if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII)
+ __set_bit(PHY_INTERFACE_MODE_10G_QXGMII,
+ phydev->possible_interfaces);
+@@ -954,6 +1063,15 @@ static int qca8084_phy_package_probe_onc
+ shared_priv->clk[i] = clk;
+ }
+
++ /* The package mode 10G-QXGMII of PCS1 is used for Quad PHY and
++ * PCS0 is unused by default.
++ */
++ shared_priv->package_mode = QCA808X_PCS1_10G_QXGMII_PCS0_UNUNSED;
++ ret = of_property_read_u32(np, "qcom,package-mode",
++ &shared_priv->package_mode);
++ if (ret && ret != -EINVAL)
++ return ret;
++
+ rstc = of_reset_control_get_exclusive(np, NULL);
+ if (IS_ERR(rstc))
+ return dev_err_probe(&phydev->mdio.dev, PTR_ERR(rstc),
--- /dev/null
+From 5f650721c4b232a14a1a3e25b686f2234faee961 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:12 +0800
+Subject: [PATCH] dt-bindings: net: pcs: Add Ethernet PCS for Qualcomm IPQ9574
+ SoC
+
+The 'UNIPHY' PCS block in the IPQ9574 SoC includes PCS and SerDes
+functions. It supports different interface modes to enable Ethernet
+MAC connections to different types of external PHYs/switch. It includes
+PCS functions for 1Gbps and 2.5Gbps interface modes and XPCS functions
+for 10Gbps interface modes. There are three UNIPHY (PCS) instances
+in IPQ9574 SoC which provide PCS/XPCS functions to the six Ethernet
+ports.
+
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ .../bindings/net/pcs/qcom,ipq9574-pcs.yaml | 190 ++++++++++++++++++
+ include/dt-bindings/net/qcom,ipq9574-pcs.h | 15 ++
+ 2 files changed, 205 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
+ create mode 100644 include/dt-bindings/net/qcom,ipq9574-pcs.h
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
+@@ -0,0 +1,190 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/pcs/qcom,ipq9574-pcs.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Ethernet PCS for Qualcomm IPQ9574 SoC
++
++maintainers:
++ - Lei Wei <quic_leiwei@quicinc.com>
++
++description:
++ The UNIPHY hardware blocks in the Qualcomm IPQ SoC include PCS and SerDes
++ functions. They enable connectivity between the Ethernet MAC inside the
++ PPE (packet processing engine) and external Ethernet PHY/switch. There are
++ three UNIPHY instances in IPQ9574 SoC which provide PCS functions to the
++ six Ethernet ports.
++
++ For SGMII (1Gbps PHY) or 2500BASE-X (2.5Gbps PHY) interface modes, the PCS
++ function is enabled by using the PCS block inside UNIPHY. For USXGMII (10Gbps
++ PHY), the XPCS block in UNIPHY is used.
++
++ The SerDes provides 125M (1Gbps mode) or 312.5M (2.5Gbps and 10Gbps modes)
++ RX and TX clocks to the NSSCC (Networking Sub System Clock Controller). The
++ NSSCC divides these clocks and generates the MII RX and TX clocks to each
++ of the MII interfaces between the PCS and MAC, as per the link speeds and
++ interface modes.
++
++ Different IPQ SoC may support different number of UNIPHYs (PCSes) since the
++ number of ports and their capabilities can be different between these SoCs
++
++ Below diagram depicts the UNIPHY (PCS) connections for an IPQ9574 SoC based
++ board. In this example, the PCS0 has four GMIIs/XGMIIs, which can connect
++ with four MACs to support QSGMII (4 x 1Gbps) or 10G_QXGMII (4 x 2.5Gbps)
++ interface modes.
++
++ - +-------+ +---------+ +-------------------------+
++ +---------+CMN PLL| | GCC | | NSSCC (Divider) |
++ | +----+--+ +----+----+ +--+-------+--------------+
++ | | | ^ |
++ | 31.25M | SYS/AHB|clk RX/TX|clk +------------+
++ | ref clk| | | | |
++ | | v | MII RX|TX clk MAC| RX/TX clk
++ |25/50M +--+---------+----------+-------+---+ +-+---------+
++ |ref clk | | +----------------+ | | | | PPE |
++ v | | | UNIPHY0 V | | V |
++ +-------+ | v | +-----------+ (X)GMII| | |
++ | | | +---+---+ | |--------|------|-- MAC0 |
++ | | | | | | | (X)GMII| | |
++ | Quad | | |SerDes | | PCS/XPCS |--------|------|-- MAC1 |
++ | +<----+ | | | | (X)GMII| | |
++ |(X)GPHY| | | | | |--------|------|-- MAC2 |
++ | | | | | | | (X)GMII| | |
++ | | | +-------+ | |--------|------|-- MAC3 |
++ +-------+ | | | | | |
++ | +-----------+ | | |
++ +-----------------------------------+ | |
++ +--+---------+----------+-------+---+ | |
++ +-------+ | UNIPHY1 | | |
++ | | | +-----------+ | | |
++ |(X)GPHY| | +-------+ | | (X)GMII| | |
++ | +<----+ |SerDes | | PCS/XPCS |--------|------|- MAC4 |
++ | | | | | | | | | |
++ +-------+ | +-------+ | | | | |
++ | +-----------+ | | |
++ +-----------------------------------+ | |
++ +--+---------+----------+-------+---+ | |
++ +-------+ | UNIPHY2 | | |
++ | | | +-----------+ | | |
++ |(X)GPHY| | +-------+ | | (X)GMII| | |
++ | +<----+ |SerDes | | PCS/XPCS |--------|------|- MAC5 |
++ | | | | | | | | | |
++ +-------+ | +-------+ | | | | |
++ | +-----------+ | | |
++ +-----------------------------------+ +-----------+
++
++properties:
++ compatible:
++ enum:
++ - qcom,ipq9574-pcs
++
++ reg:
++ maxItems: 1
++
++ '#address-cells':
++ const: 1
++
++ '#size-cells':
++ const: 0
++
++ clocks:
++ items:
++ - description: System clock
++ - description: AHB clock needed for register interface access
++
++ clock-names:
++ items:
++ - const: sys
++ - const: ahb
++
++ '#clock-cells':
++ const: 1
++ description: See include/dt-bindings/net/qcom,ipq9574-pcs.h for constants
++
++patternProperties:
++ '^pcs-mii@[0-4]$':
++ type: object
++ description: PCS MII interface.
++
++ properties:
++ reg:
++ minimum: 0
++ maximum: 4
++ description: MII index
++
++ clocks:
++ items:
++ - description: PCS MII RX clock
++ - description: PCS MII TX clock
++
++ clock-names:
++ items:
++ - const: rx
++ - const: tx
++
++ required:
++ - reg
++ - clocks
++ - clock-names
++
++ additionalProperties: false
++
++required:
++ - compatible
++ - reg
++ - '#address-cells'
++ - '#size-cells'
++ - clocks
++ - clock-names
++ - '#clock-cells'
++
++additionalProperties: false
++
++examples:
++ - |
++ #include <dt-bindings/clock/qcom,ipq9574-gcc.h>
++
++ ethernet-pcs@7a00000 {
++ compatible = "qcom,ipq9574-pcs";
++ reg = <0x7a00000 0x10000>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ clocks = <&gcc GCC_UNIPHY0_SYS_CLK>,
++ <&gcc GCC_UNIPHY0_AHB_CLK>;
++ clock-names = "sys",
++ "ahb";
++ #clock-cells = <1>;
++
++ pcs-mii@0 {
++ reg = <0>;
++ clocks = <&nsscc 116>,
++ <&nsscc 117>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs-mii@1 {
++ reg = <1>;
++ clocks = <&nsscc 118>,
++ <&nsscc 119>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs-mii@2 {
++ reg = <2>;
++ clocks = <&nsscc 120>,
++ <&nsscc 121>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs-mii@3 {
++ reg = <3>;
++ clocks = <&nsscc 122>,
++ <&nsscc 123>;
++ clock-names = "rx",
++ "tx";
++ };
++ };
+--- /dev/null
++++ b/include/dt-bindings/net/qcom,ipq9574-pcs.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ *
++ * Device Tree constants for the Qualcomm IPQ9574 PCS
++ */
++
++#ifndef _DT_BINDINGS_PCS_QCOM_IPQ9574_H
++#define _DT_BINDINGS_PCS_QCOM_IPQ9574_H
++
++/* The RX and TX clocks which are provided from the SerDes to NSSCC. */
++#define PCS_RX_CLK 0
++#define PCS_TX_CLK 1
++
++#endif /* _DT_BINDINGS_PCS_QCOM_IPQ9574_H */
--- /dev/null
+From e404519d9f3e5e7d661cb105d3766d87e37e4ef5 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:13 +0800
+Subject: [PATCH] net: pcs: Add PCS driver for Qualcomm IPQ9574 SoC
+
+The 'UNIPHY' PCS hardware block in Qualcomm's IPQ SoC supports
+different interface modes to enable Ethernet MAC connections
+for different types of external PHYs/switch. Each UNIPHY block
+includes a SerDes and PCS/XPCS blocks, and can operate in either
+PCS or XPCS modes. It supports 1Gbps and 2.5Gbps interface modes
+(Ex: SGMII) using the PCS, and 10Gbps interface modes (Ex: USXGMII)
+using the XPCS. There are three UNIPHY (PCS) instances in IPQ9574
+SoC which support the six Ethernet ports in the SoC.
+
+This patch adds support for the platform driver, probe and clock
+registrations for the PCS driver. The platform driver creates an
+'ipq_pcs' instance for each of the UNIPHY used on the given board.
+
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ drivers/net/pcs/Kconfig | 9 ++
+ drivers/net/pcs/Makefile | 1 +
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 245 +++++++++++++++++++++++++++++
+ 3 files changed, 255 insertions(+)
+ create mode 100644 drivers/net/pcs/pcs-qcom-ipq9574.c
+
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -36,6 +36,15 @@ config PCS_MTK_USXGMII
+ 1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
+ differential pairs via an embedded LynxI PHY.
+
++config PCS_QCOM_IPQ9574
++ tristate "Qualcomm IPQ9574 PCS"
++ depends on OF && (ARCH_QCOM || COMPILE_TEST)
++ depends on HAS_IOMEM && COMMON_CLK
++ help
++ This module provides driver for UNIPHY PCS available on Qualcomm
++ IPQ9574 SoC. The UNIPHY PCS supports both PCS and XPCS functions
++ to support different interface modes for MAC to PHY connections.
++
+ config PCS_RZN1_MIIC
+ tristate "Renesas RZ/N1 MII converter"
+ depends on OF && (ARCH_RZN1 || COMPILE_TEST)
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -7,5 +7,6 @@ pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.
+ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
+ obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
++obj-$(CONFIG_PCS_QCOM_IPQ9574) += pcs-qcom-ipq9574.o
+ obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
+ obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
+--- /dev/null
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -0,0 +1,245 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#include <linux/clk.h>
++#include <linux/clk-provider.h>
++#include <linux/device.h>
++#include <linux/phy.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#include <dt-bindings/net/qcom,ipq9574-pcs.h>
++
++#define XPCS_INDIRECT_ADDR 0x8000
++#define XPCS_INDIRECT_AHB_ADDR 0x83fc
++#define XPCS_INDIRECT_ADDR_H GENMASK(20, 8)
++#define XPCS_INDIRECT_ADDR_L GENMASK(7, 0)
++#define XPCS_INDIRECT_DATA_ADDR(reg) (FIELD_PREP(GENMASK(15, 10), 0x20) | \
++ FIELD_PREP(GENMASK(9, 2), \
++ FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
++
++/* PCS private data */
++struct ipq_pcs {
++ struct device *dev;
++ void __iomem *base;
++ struct regmap *regmap;
++ phy_interface_t interface;
++
++ /* RX clock supplied to NSSCC */
++ struct clk_hw rx_hw;
++ /* TX clock supplied to NSSCC */
++ struct clk_hw tx_hw;
++};
++
++static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
++{
++ switch (qpcs->interface) {
++ case PHY_INTERFACE_MODE_USXGMII:
++ return 312500000;
++ default:
++ return 125000000;
++ }
++}
++
++/* Return clock rate for the RX clock supplied to NSSCC
++ * as per the interface mode.
++ */
++static unsigned long ipq_pcs_rx_clk_recalc_rate(struct clk_hw *hw,
++ unsigned long parent_rate)
++{
++ struct ipq_pcs *qpcs = container_of(hw, struct ipq_pcs, rx_hw);
++
++ return ipq_pcs_clk_rate_get(qpcs);
++}
++
++/* Return clock rate for the TX clock supplied to NSSCC
++ * as per the interface mode.
++ */
++static unsigned long ipq_pcs_tx_clk_recalc_rate(struct clk_hw *hw,
++ unsigned long parent_rate)
++{
++ struct ipq_pcs *qpcs = container_of(hw, struct ipq_pcs, tx_hw);
++
++ return ipq_pcs_clk_rate_get(qpcs);
++}
++
++static int ipq_pcs_clk_determine_rate(struct clk_hw *hw,
++ struct clk_rate_request *req)
++{
++ switch (req->rate) {
++ case 125000000:
++ case 312500000:
++ return 0;
++ default:
++ return -EINVAL;
++ }
++}
++
++/* Clock ops for the RX clock supplied to NSSCC */
++static const struct clk_ops ipq_pcs_rx_clk_ops = {
++ .determine_rate = ipq_pcs_clk_determine_rate,
++ .recalc_rate = ipq_pcs_rx_clk_recalc_rate,
++};
++
++/* Clock ops for the TX clock supplied to NSSCC */
++static const struct clk_ops ipq_pcs_tx_clk_ops = {
++ .determine_rate = ipq_pcs_clk_determine_rate,
++ .recalc_rate = ipq_pcs_tx_clk_recalc_rate,
++};
++
++static struct clk_hw *ipq_pcs_clk_hw_get(struct of_phandle_args *clkspec,
++ void *data)
++{
++ struct ipq_pcs *qpcs = data;
++
++ switch (clkspec->args[0]) {
++ case PCS_RX_CLK:
++ return &qpcs->rx_hw;
++ case PCS_TX_CLK:
++ return &qpcs->tx_hw;
++ }
++
++ return ERR_PTR(-EINVAL);
++}
++
++/* Register the RX and TX clock which are output from SerDes to
++ * the NSSCC. The NSSCC driver assigns the RX and TX clock as
++ * parent, divides them to generate the MII RX and TX clock to
++ * each MII interface of the PCS as per the link speeds and
++ * interface modes.
++ */
++static int ipq_pcs_clk_register(struct ipq_pcs *qpcs)
++{
++ struct clk_init_data init = { };
++ int ret;
++
++ init.ops = &ipq_pcs_rx_clk_ops;
++ init.name = devm_kasprintf(qpcs->dev, GFP_KERNEL, "%s::rx_clk",
++ dev_name(qpcs->dev));
++ if (!init.name)
++ return -ENOMEM;
++
++ qpcs->rx_hw.init = &init;
++ ret = devm_clk_hw_register(qpcs->dev, &qpcs->rx_hw);
++ if (ret)
++ return ret;
++
++ init.ops = &ipq_pcs_tx_clk_ops;
++ init.name = devm_kasprintf(qpcs->dev, GFP_KERNEL, "%s::tx_clk",
++ dev_name(qpcs->dev));
++ if (!init.name)
++ return -ENOMEM;
++
++ qpcs->tx_hw.init = &init;
++ ret = devm_clk_hw_register(qpcs->dev, &qpcs->tx_hw);
++ if (ret)
++ return ret;
++
++ return devm_of_clk_add_hw_provider(qpcs->dev, ipq_pcs_clk_hw_get, qpcs);
++}
++
++static int ipq_pcs_regmap_read(void *context, unsigned int reg,
++ unsigned int *val)
++{
++ struct ipq_pcs *qpcs = context;
++
++ /* PCS uses direct AHB access while XPCS uses indirect AHB access */
++ if (reg >= XPCS_INDIRECT_ADDR) {
++ writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
++ qpcs->base + XPCS_INDIRECT_AHB_ADDR);
++ *val = readl(qpcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
++ } else {
++ *val = readl(qpcs->base + reg);
++ }
++
++ return 0;
++}
++
++static int ipq_pcs_regmap_write(void *context, unsigned int reg,
++ unsigned int val)
++{
++ struct ipq_pcs *qpcs = context;
++
++ /* PCS uses direct AHB access while XPCS uses indirect AHB access */
++ if (reg >= XPCS_INDIRECT_ADDR) {
++ writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
++ qpcs->base + XPCS_INDIRECT_AHB_ADDR);
++ writel(val, qpcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
++ } else {
++ writel(val, qpcs->base + reg);
++ }
++
++ return 0;
++}
++
++static const struct regmap_config ipq_pcs_regmap_cfg = {
++ .reg_bits = 32,
++ .val_bits = 32,
++ .reg_read = ipq_pcs_regmap_read,
++ .reg_write = ipq_pcs_regmap_write,
++ .fast_io = true,
++};
++
++static int ipq9574_pcs_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct ipq_pcs *qpcs;
++ struct clk *clk;
++ int ret;
++
++ qpcs = devm_kzalloc(dev, sizeof(*qpcs), GFP_KERNEL);
++ if (!qpcs)
++ return -ENOMEM;
++
++ qpcs->dev = dev;
++
++ qpcs->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(qpcs->base))
++ return dev_err_probe(dev, PTR_ERR(qpcs->base),
++ "Failed to ioremap resource\n");
++
++ qpcs->regmap = devm_regmap_init(dev, NULL, qpcs, &ipq_pcs_regmap_cfg);
++ if (IS_ERR(qpcs->regmap))
++ return dev_err_probe(dev, PTR_ERR(qpcs->regmap),
++ "Failed to allocate register map\n");
++
++ clk = devm_clk_get_enabled(dev, "sys");
++ if (IS_ERR(clk))
++ return dev_err_probe(dev, PTR_ERR(clk),
++ "Failed to enable SYS clock\n");
++
++ clk = devm_clk_get_enabled(dev, "ahb");
++ if (IS_ERR(clk))
++ return dev_err_probe(dev, PTR_ERR(clk),
++ "Failed to enable AHB clock\n");
++
++ ret = ipq_pcs_clk_register(qpcs);
++ if (ret)
++ return ret;
++
++ platform_set_drvdata(pdev, qpcs);
++
++ return 0;
++}
++
++static const struct of_device_id ipq9574_pcs_of_mtable[] = {
++ { .compatible = "qcom,ipq9574-pcs" },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, ipq9574_pcs_of_mtable);
++
++static struct platform_driver ipq9574_pcs_driver = {
++ .driver = {
++ .name = "ipq9574_pcs",
++ .suppress_bind_attrs = true,
++ .of_match_table = ipq9574_pcs_of_mtable,
++ },
++ .probe = ipq9574_pcs_probe,
++};
++module_platform_driver(ipq9574_pcs_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Qualcomm IPQ9574 PCS driver");
++MODULE_AUTHOR("Lei Wei <quic_leiwei@quicinc.com>");
--- /dev/null
+From 10b609ddbf4d369c80098efa39451ef3973759b5 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:14 +0800
+Subject: [PATCH] net: pcs: qcom-ipq9574: Add PCS instantiation and phylink
+ operations
+
+This patch adds the following PCS functionality for the PCS driver
+for IPQ9574 SoC:
+
+a.) Parses PCS MII DT nodes and instantiate each MII PCS instance.
+b.) Exports PCS instance get and put APIs. The network driver calls
+the PCS get API to get and associate the PCS instance with the port
+MAC.
+c.) PCS phylink operations for SGMII/QSGMII interface modes.
+
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Alex G: remove phylink_pcs .neg_mode boolean
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 468 +++++++++++++++++++++++++++
+ include/linux/pcs/pcs-qcom-ipq9574.h | 15 +
+ 2 files changed, 483 insertions(+)
+ create mode 100644 include/linux/pcs/pcs-qcom-ipq9574.h
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -6,12 +6,46 @@
+ #include <linux/clk.h>
+ #include <linux/clk-provider.h>
+ #include <linux/device.h>
++#include <linux/of.h>
++#include <linux/of_platform.h>
++#include <linux/pcs/pcs-qcom-ipq9574.h>
+ #include <linux/phy.h>
++#include <linux/phylink.h>
+ #include <linux/platform_device.h>
+ #include <linux/regmap.h>
+
+ #include <dt-bindings/net/qcom,ipq9574-pcs.h>
+
++/* Maximum number of MIIs per PCS instance. There are 5 MIIs for PSGMII. */
++#define PCS_MAX_MII_NRS 5
++
++#define PCS_CALIBRATION 0x1e0
++#define PCS_CALIBRATION_DONE BIT(7)
++
++#define PCS_MODE_CTRL 0x46c
++#define PCS_MODE_SEL_MASK GENMASK(12, 8)
++#define PCS_MODE_SGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
++#define PCS_MODE_QSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++
++#define PCS_MII_CTRL(x) (0x480 + 0x18 * (x))
++#define PCS_MII_ADPT_RESET BIT(11)
++#define PCS_MII_FORCE_MODE BIT(3)
++#define PCS_MII_SPEED_MASK GENMASK(2, 1)
++#define PCS_MII_SPEED_1000 FIELD_PREP(PCS_MII_SPEED_MASK, 0x2)
++#define PCS_MII_SPEED_100 FIELD_PREP(PCS_MII_SPEED_MASK, 0x1)
++#define PCS_MII_SPEED_10 FIELD_PREP(PCS_MII_SPEED_MASK, 0x0)
++
++#define PCS_MII_STS(x) (0x488 + 0x18 * (x))
++#define PCS_MII_LINK_STS BIT(7)
++#define PCS_MII_STS_DUPLEX_FULL BIT(6)
++#define PCS_MII_STS_SPEED_MASK GENMASK(5, 4)
++#define PCS_MII_STS_SPEED_10 0
++#define PCS_MII_STS_SPEED_100 1
++#define PCS_MII_STS_SPEED_1000 2
++
++#define PCS_PLL_RESET 0x780
++#define PCS_ANA_SW_RESET BIT(6)
++
+ #define XPCS_INDIRECT_ADDR 0x8000
+ #define XPCS_INDIRECT_AHB_ADDR 0x83fc
+ #define XPCS_INDIRECT_ADDR_H GENMASK(20, 8)
+@@ -20,6 +54,18 @@
+ FIELD_PREP(GENMASK(9, 2), \
+ FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
+
++/* Per PCS MII private data */
++struct ipq_pcs_mii {
++ struct ipq_pcs *qpcs;
++ struct phylink_pcs pcs;
++ int index;
++
++ /* RX clock from NSSCC to PCS MII */
++ struct clk *rx_clk;
++ /* TX clock from NSSCC to PCS MII */
++ struct clk *tx_clk;
++};
++
+ /* PCS private data */
+ struct ipq_pcs {
+ struct device *dev;
+@@ -31,8 +77,358 @@ struct ipq_pcs {
+ struct clk_hw rx_hw;
+ /* TX clock supplied to NSSCC */
+ struct clk_hw tx_hw;
++
++ struct ipq_pcs_mii *qpcs_mii[PCS_MAX_MII_NRS];
+ };
+
++#define phylink_pcs_to_qpcs_mii(_pcs) \
++ container_of(_pcs, struct ipq_pcs_mii, pcs)
++
++static void ipq_pcs_get_state_sgmii(struct ipq_pcs *qpcs,
++ int index,
++ struct phylink_link_state *state)
++{
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(qpcs->regmap, PCS_MII_STS(index), &val);
++ if (ret) {
++ state->link = 0;
++ return;
++ }
++
++ state->link = !!(val & PCS_MII_LINK_STS);
++
++ if (!state->link)
++ return;
++
++ switch (FIELD_GET(PCS_MII_STS_SPEED_MASK, val)) {
++ case PCS_MII_STS_SPEED_1000:
++ state->speed = SPEED_1000;
++ break;
++ case PCS_MII_STS_SPEED_100:
++ state->speed = SPEED_100;
++ break;
++ case PCS_MII_STS_SPEED_10:
++ state->speed = SPEED_10;
++ break;
++ default:
++ state->link = false;
++ return;
++ }
++
++ if (val & PCS_MII_STS_DUPLEX_FULL)
++ state->duplex = DUPLEX_FULL;
++ else
++ state->duplex = DUPLEX_HALF;
++}
++
++static int ipq_pcs_config_mode(struct ipq_pcs *qpcs,
++ phy_interface_t interface)
++{
++ unsigned int val;
++ int ret;
++
++ /* Configure PCS interface mode */
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ val = PCS_MODE_SGMII;
++ break;
++ case PHY_INTERFACE_MODE_QSGMII:
++ val = PCS_MODE_QSGMII;
++ break;
++ default:
++ return -EOPNOTSUPP;
++ }
++
++ ret = regmap_update_bits(qpcs->regmap, PCS_MODE_CTRL,
++ PCS_MODE_SEL_MASK, val);
++ if (ret)
++ return ret;
++
++ /* PCS PLL reset */
++ ret = regmap_clear_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
++ if (ret)
++ return ret;
++
++ fsleep(1000);
++ ret = regmap_set_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
++ if (ret)
++ return ret;
++
++ /* Wait for calibration completion */
++ ret = regmap_read_poll_timeout(qpcs->regmap, PCS_CALIBRATION,
++ val, val & PCS_CALIBRATION_DONE,
++ 1000, 100000);
++ if (ret) {
++ dev_err(qpcs->dev, "PCS calibration timed-out\n");
++ return ret;
++ }
++
++ qpcs->interface = interface;
++
++ return 0;
++}
++
++static int ipq_pcs_config_sgmii(struct ipq_pcs *qpcs,
++ int index,
++ unsigned int neg_mode,
++ phy_interface_t interface)
++{
++ int ret;
++
++ /* Configure the PCS mode if required */
++ if (qpcs->interface != interface) {
++ ret = ipq_pcs_config_mode(qpcs, interface);
++ if (ret)
++ return ret;
++ }
++
++ /* Nothing to do here as in-band autoneg mode is enabled
++ * by default for each PCS MII port.
++ */
++ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
++ return 0;
++
++ /* Set force speed mode */
++ return regmap_set_bits(qpcs->regmap,
++ PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
++}
++
++static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
++ int index,
++ unsigned int neg_mode,
++ int speed)
++{
++ unsigned int val;
++ int ret;
++
++ /* PCS speed need not be configured if in-band autoneg is enabled */
++ if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
++ /* PCS speed set for force mode */
++ switch (speed) {
++ case SPEED_1000:
++ val = PCS_MII_SPEED_1000;
++ break;
++ case SPEED_100:
++ val = PCS_MII_SPEED_100;
++ break;
++ case SPEED_10:
++ val = PCS_MII_SPEED_10;
++ break;
++ default:
++ dev_err(qpcs->dev, "Invalid SGMII speed %d\n", speed);
++ return -EINVAL;
++ }
++
++ ret = regmap_update_bits(qpcs->regmap, PCS_MII_CTRL(index),
++ PCS_MII_SPEED_MASK, val);
++ if (ret)
++ return ret;
++ }
++
++ /* PCS adapter reset */
++ ret = regmap_clear_bits(qpcs->regmap,
++ PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
++ if (ret)
++ return ret;
++
++ return regmap_set_bits(qpcs->regmap,
++ PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
++}
++
++static int ipq_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
++ const struct phylink_link_state *state)
++{
++ switch (state->interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return 0;
++ default:
++ return -EINVAL;
++ }
++}
++
++static unsigned int ipq_pcs_inband_caps(struct phylink_pcs *pcs,
++ phy_interface_t interface)
++{
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++ default:
++ return 0;
++ }
++}
++
++static int ipq_pcs_enable(struct phylink_pcs *pcs)
++{
++ struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++ struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++ int index = qpcs_mii->index;
++ int ret;
++
++ ret = clk_prepare_enable(qpcs_mii->rx_clk);
++ if (ret) {
++ dev_err(qpcs->dev, "Failed to enable MII %d RX clock\n", index);
++ return ret;
++ }
++
++ ret = clk_prepare_enable(qpcs_mii->tx_clk);
++ if (ret) {
++ /* This is a fatal event since phylink does not support unwinding
++ * the state back for this error. So, we only report the error
++ * and do not disable the clocks.
++ */
++ dev_err(qpcs->dev, "Failed to enable MII %d TX clock\n", index);
++ return ret;
++ }
++
++ return 0;
++}
++
++static void ipq_pcs_disable(struct phylink_pcs *pcs)
++{
++ struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++
++ clk_disable_unprepare(qpcs_mii->rx_clk);
++ clk_disable_unprepare(qpcs_mii->tx_clk);
++}
++
++static void ipq_pcs_get_state(struct phylink_pcs *pcs,
++ struct phylink_link_state *state)
++{
++ struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++ struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++ int index = qpcs_mii->index;
++
++ switch (state->interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ ipq_pcs_get_state_sgmii(qpcs, index, state);
++ break;
++ default:
++ break;
++ }
++
++ dev_dbg_ratelimited(qpcs->dev,
++ "mode=%s/%s/%s link=%u\n",
++ phy_modes(state->interface),
++ phy_speed_to_str(state->speed),
++ phy_duplex_to_str(state->duplex),
++ state->link);
++}
++
++static int ipq_pcs_config(struct phylink_pcs *pcs,
++ unsigned int neg_mode,
++ phy_interface_t interface,
++ const unsigned long *advertising,
++ bool permit)
++{
++ struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++ struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++ int index = qpcs_mii->index;
++
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
++ default:
++ return -EOPNOTSUPP;
++ };
++}
++
++static void ipq_pcs_link_up(struct phylink_pcs *pcs,
++ unsigned int neg_mode,
++ phy_interface_t interface,
++ int speed, int duplex)
++{
++ struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++ struct ipq_pcs *qpcs = qpcs_mii->qpcs;
++ int index = qpcs_mii->index;
++ int ret;
++
++ switch (interface) {
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_QSGMII:
++ ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
++ neg_mode, speed);
++ break;
++ default:
++ return;
++ }
++
++ if (ret)
++ dev_err(qpcs->dev, "PCS link up fail for interface %s\n",
++ phy_modes(interface));
++}
++
++static const struct phylink_pcs_ops ipq_pcs_phylink_ops = {
++ .pcs_validate = ipq_pcs_validate,
++ .pcs_inband_caps = ipq_pcs_inband_caps,
++ .pcs_enable = ipq_pcs_enable,
++ .pcs_disable = ipq_pcs_disable,
++ .pcs_get_state = ipq_pcs_get_state,
++ .pcs_config = ipq_pcs_config,
++ .pcs_link_up = ipq_pcs_link_up,
++};
++
++/* Parse the PCS MII DT nodes which are child nodes of the PCS node,
++ * and instantiate each MII PCS instance.
++ */
++static int ipq_pcs_create_miis(struct ipq_pcs *qpcs)
++{
++ struct device *dev = qpcs->dev;
++ struct ipq_pcs_mii *qpcs_mii;
++ struct device_node *mii_np;
++ u32 index;
++ int ret;
++
++ for_each_available_child_of_node(dev->of_node, mii_np) {
++ ret = of_property_read_u32(mii_np, "reg", &index);
++ if (ret) {
++ dev_err(dev, "Failed to read MII index\n");
++ of_node_put(mii_np);
++ return ret;
++ }
++
++ if (index >= PCS_MAX_MII_NRS) {
++ dev_err(dev, "Invalid MII index\n");
++ of_node_put(mii_np);
++ return -EINVAL;
++ }
++
++ qpcs_mii = devm_kzalloc(dev, sizeof(*qpcs_mii), GFP_KERNEL);
++ if (!qpcs_mii) {
++ of_node_put(mii_np);
++ return -ENOMEM;
++ }
++
++ qpcs_mii->qpcs = qpcs;
++ qpcs_mii->index = index;
++ qpcs_mii->pcs.ops = &ipq_pcs_phylink_ops;
++ qpcs_mii->pcs.poll = true;
++
++ qpcs_mii->rx_clk = devm_get_clk_from_child(dev, mii_np, "rx");
++ if (IS_ERR(qpcs_mii->rx_clk)) {
++ of_node_put(mii_np);
++ return dev_err_probe(dev, PTR_ERR(qpcs_mii->rx_clk),
++ "Failed to get MII %d RX clock\n", index);
++ }
++
++ qpcs_mii->tx_clk = devm_get_clk_from_child(dev, mii_np, "tx");
++ if (IS_ERR(qpcs_mii->tx_clk)) {
++ of_node_put(mii_np);
++ return dev_err_probe(dev, PTR_ERR(qpcs_mii->tx_clk),
++ "Failed to get MII %d TX clock\n", index);
++ }
++
++ qpcs->qpcs_mii[index] = qpcs_mii;
++ }
++
++ return 0;
++}
++
+ static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
+ {
+ switch (qpcs->interface) {
+@@ -219,6 +615,10 @@ static int ipq9574_pcs_probe(struct plat
+ if (ret)
+ return ret;
+
++ ret = ipq_pcs_create_miis(qpcs);
++ if (ret)
++ return ret;
++
+ platform_set_drvdata(pdev, qpcs);
+
+ return 0;
+@@ -230,6 +630,74 @@ static const struct of_device_id ipq9574
+ };
+ MODULE_DEVICE_TABLE(of, ipq9574_pcs_of_mtable);
+
++/**
++ * ipq_pcs_get() - Get the IPQ PCS MII instance
++ * @np: Device tree node to the PCS MII
++ *
++ * Description: Get the phylink PCS instance for the given PCS MII node @np.
++ * This instance is associated with the specific MII of the PCS and the
++ * corresponding Ethernet netdevice.
++ *
++ * Return: A pointer to the phylink PCS instance or an error-pointer value.
++ */
++struct phylink_pcs *ipq_pcs_get(struct device_node *np)
++{
++ struct platform_device *pdev;
++ struct ipq_pcs_mii *qpcs_mii;
++ struct ipq_pcs *qpcs;
++ u32 index;
++
++ if (of_property_read_u32(np, "reg", &index))
++ return ERR_PTR(-EINVAL);
++
++ if (index >= PCS_MAX_MII_NRS)
++ return ERR_PTR(-EINVAL);
++
++ if (!of_match_node(ipq9574_pcs_of_mtable, np->parent))
++ return ERR_PTR(-EINVAL);
++
++ /* Get the parent device */
++ pdev = of_find_device_by_node(np->parent);
++ if (!pdev)
++ return ERR_PTR(-ENODEV);
++
++ qpcs = platform_get_drvdata(pdev);
++ if (!qpcs) {
++ put_device(&pdev->dev);
++
++ /* If probe is not yet completed, return DEFER to
++ * the dependent driver.
++ */
++ return ERR_PTR(-EPROBE_DEFER);
++ }
++
++ qpcs_mii = qpcs->qpcs_mii[index];
++ if (!qpcs_mii) {
++ put_device(&pdev->dev);
++ return ERR_PTR(-ENOENT);
++ }
++
++ return &qpcs_mii->pcs;
++}
++EXPORT_SYMBOL(ipq_pcs_get);
++
++/**
++ * ipq_pcs_put() - Release the IPQ PCS MII instance
++ * @pcs: PCS instance
++ *
++ * Description: Release a phylink PCS instance.
++ */
++void ipq_pcs_put(struct phylink_pcs *pcs)
++{
++ struct ipq_pcs_mii *qpcs_mii = phylink_pcs_to_qpcs_mii(pcs);
++
++ /* Put reference taken by of_find_device_by_node() in
++ * ipq_pcs_get().
++ */
++ put_device(qpcs_mii->qpcs->dev);
++}
++EXPORT_SYMBOL(ipq_pcs_put);
++
+ static struct platform_driver ipq9574_pcs_driver = {
+ .driver = {
+ .name = "ipq9574_pcs",
+--- /dev/null
++++ b/include/linux/pcs/pcs-qcom-ipq9574.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __LINUX_PCS_QCOM_IPQ9574_H
++#define __LINUX_PCS_QCOM_IPQ9574_H
++
++struct device_node;
++struct phylink_pcs;
++
++struct phylink_pcs *ipq_pcs_get(struct device_node *np);
++void ipq_pcs_put(struct phylink_pcs *pcs);
++
++#endif /* __LINUX_PCS_QCOM_IPQ9574_H */
--- /dev/null
+From 4923ca63214a4e6bbee1b3f8f6b9b79f0fd3a3be Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:15 +0800
+Subject: [PATCH] net: pcs: qcom-ipq9574: Add USXGMII interface mode support
+
+USXGMII mode is enabled by PCS when 10Gbps PHYs are connected, such as
+Aquantia 10Gbps PHY.
+
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 170 +++++++++++++++++++++++++++++
+ 1 file changed, 170 insertions(+)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -26,6 +26,7 @@
+ #define PCS_MODE_SEL_MASK GENMASK(12, 8)
+ #define PCS_MODE_SGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
+ #define PCS_MODE_QSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++#define PCS_MODE_XPCS FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
+
+ #define PCS_MII_CTRL(x) (0x480 + 0x18 * (x))
+ #define PCS_MII_ADPT_RESET BIT(11)
+@@ -54,6 +55,34 @@
+ FIELD_PREP(GENMASK(9, 2), \
+ FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
+
++#define XPCS_DIG_CTRL 0x38000
++#define XPCS_USXG_ADPT_RESET BIT(10)
++#define XPCS_USXG_EN BIT(9)
++
++#define XPCS_MII_CTRL 0x1f0000
++#define XPCS_MII_AN_EN BIT(12)
++#define XPCS_DUPLEX_FULL BIT(8)
++#define XPCS_SPEED_MASK (BIT(13) | BIT(6) | BIT(5))
++#define XPCS_SPEED_10000 (BIT(13) | BIT(6))
++#define XPCS_SPEED_5000 (BIT(13) | BIT(5))
++#define XPCS_SPEED_2500 BIT(5)
++#define XPCS_SPEED_1000 BIT(6)
++#define XPCS_SPEED_100 BIT(13)
++#define XPCS_SPEED_10 0
++
++#define XPCS_MII_AN_CTRL 0x1f8001
++#define XPCS_MII_AN_8BIT BIT(8)
++
++#define XPCS_MII_AN_INTR_STS 0x1f8002
++#define XPCS_USXG_AN_LINK_STS BIT(14)
++#define XPCS_USXG_AN_SPEED_MASK GENMASK(12, 10)
++#define XPCS_USXG_AN_SPEED_10 0
++#define XPCS_USXG_AN_SPEED_100 1
++#define XPCS_USXG_AN_SPEED_1000 2
++#define XPCS_USXG_AN_SPEED_2500 4
++#define XPCS_USXG_AN_SPEED_5000 5
++#define XPCS_USXG_AN_SPEED_10000 3
++
+ /* Per PCS MII private data */
+ struct ipq_pcs_mii {
+ struct ipq_pcs *qpcs;
+@@ -123,9 +152,54 @@ static void ipq_pcs_get_state_sgmii(stru
+ state->duplex = DUPLEX_HALF;
+ }
+
++static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
++ struct phylink_link_state *state)
++{
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(qpcs->regmap, XPCS_MII_AN_INTR_STS, &val);
++ if (ret) {
++ state->link = 0;
++ return;
++ }
++
++ state->link = !!(val & XPCS_USXG_AN_LINK_STS);
++
++ if (!state->link)
++ return;
++
++ switch (FIELD_GET(XPCS_USXG_AN_SPEED_MASK, val)) {
++ case XPCS_USXG_AN_SPEED_10000:
++ state->speed = SPEED_10000;
++ break;
++ case XPCS_USXG_AN_SPEED_5000:
++ state->speed = SPEED_5000;
++ break;
++ case XPCS_USXG_AN_SPEED_2500:
++ state->speed = SPEED_2500;
++ break;
++ case XPCS_USXG_AN_SPEED_1000:
++ state->speed = SPEED_1000;
++ break;
++ case XPCS_USXG_AN_SPEED_100:
++ state->speed = SPEED_100;
++ break;
++ case XPCS_USXG_AN_SPEED_10:
++ state->speed = SPEED_10;
++ break;
++ default:
++ state->link = false;
++ return;
++ }
++
++ state->duplex = DUPLEX_FULL;
++}
++
+ static int ipq_pcs_config_mode(struct ipq_pcs *qpcs,
+ phy_interface_t interface)
+ {
++ unsigned long rate = 125000000;
+ unsigned int val;
+ int ret;
+
+@@ -137,6 +211,10 @@ static int ipq_pcs_config_mode(struct ip
+ case PHY_INTERFACE_MODE_QSGMII:
+ val = PCS_MODE_QSGMII;
+ break;
++ case PHY_INTERFACE_MODE_USXGMII:
++ val = PCS_MODE_XPCS;
++ rate = 312500000;
++ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+@@ -167,6 +245,21 @@ static int ipq_pcs_config_mode(struct ip
+
+ qpcs->interface = interface;
+
++ /* Configure the RX and TX clock to NSSCC as 125M or 312.5M based
++ * on current interface mode.
++ */
++ ret = clk_set_rate(qpcs->rx_hw.clk, rate);
++ if (ret) {
++ dev_err(qpcs->dev, "Failed to set RX clock rate\n");
++ return ret;
++ }
++
++ ret = clk_set_rate(qpcs->tx_hw.clk, rate);
++ if (ret) {
++ dev_err(qpcs->dev, "Failed to set TX clock rate\n");
++ return ret;
++ }
++
+ return 0;
+ }
+
+@@ -195,6 +288,29 @@ static int ipq_pcs_config_sgmii(struct i
+ PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
+ }
+
++static int ipq_pcs_config_usxgmii(struct ipq_pcs *qpcs)
++{
++ int ret;
++
++ /* Configure the XPCS for USXGMII mode if required */
++ if (qpcs->interface == PHY_INTERFACE_MODE_USXGMII)
++ return 0;
++
++ ret = ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_USXGMII);
++ if (ret)
++ return ret;
++
++ ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_USXG_EN);
++ if (ret)
++ return ret;
++
++ ret = regmap_set_bits(qpcs->regmap, XPCS_MII_AN_CTRL, XPCS_MII_AN_8BIT);
++ if (ret)
++ return ret;
++
++ return regmap_set_bits(qpcs->regmap, XPCS_MII_CTRL, XPCS_MII_AN_EN);
++}
++
+ static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
+ int index,
+ unsigned int neg_mode,
+@@ -237,6 +353,46 @@ static int ipq_pcs_link_up_config_sgmii(
+ PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
+ }
+
++static int ipq_pcs_link_up_config_usxgmii(struct ipq_pcs *qpcs, int speed)
++{
++ unsigned int val;
++ int ret;
++
++ switch (speed) {
++ case SPEED_10000:
++ val = XPCS_SPEED_10000;
++ break;
++ case SPEED_5000:
++ val = XPCS_SPEED_5000;
++ break;
++ case SPEED_2500:
++ val = XPCS_SPEED_2500;
++ break;
++ case SPEED_1000:
++ val = XPCS_SPEED_1000;
++ break;
++ case SPEED_100:
++ val = XPCS_SPEED_100;
++ break;
++ case SPEED_10:
++ val = XPCS_SPEED_10;
++ break;
++ default:
++ dev_err(qpcs->dev, "Invalid USXGMII speed %d\n", speed);
++ return -EINVAL;
++ }
++
++ /* Configure XPCS speed */
++ ret = regmap_update_bits(qpcs->regmap, XPCS_MII_CTRL,
++ XPCS_SPEED_MASK, val | XPCS_DUPLEX_FULL);
++ if (ret)
++ return ret;
++
++ /* XPCS adapter reset */
++ return regmap_set_bits(qpcs->regmap,
++ XPCS_DIG_CTRL, XPCS_USXG_ADPT_RESET);
++}
++
+ static int ipq_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+ const struct phylink_link_state *state)
+ {
+@@ -244,6 +400,11 @@ static int ipq_pcs_validate(struct phyli
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ return 0;
++ case PHY_INTERFACE_MODE_USXGMII:
++ /* USXGMII only supports full duplex mode */
++ phylink_clear(supported, 100baseT_Half);
++ phylink_clear(supported, 10baseT_Half);
++ return 0;
+ default:
+ return -EINVAL;
+ }
+@@ -255,6 +416,7 @@ static unsigned int ipq_pcs_inband_caps(
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_USXGMII:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+ default:
+ return 0;
+@@ -307,6 +469,9 @@ static void ipq_pcs_get_state(struct phy
+ case PHY_INTERFACE_MODE_QSGMII:
+ ipq_pcs_get_state_sgmii(qpcs, index, state);
+ break;
++ case PHY_INTERFACE_MODE_USXGMII:
++ ipq_pcs_get_state_usxgmii(qpcs, state);
++ break;
+ default:
+ break;
+ }
+@@ -333,6 +498,8 @@ static int ipq_pcs_config(struct phylink
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
++ case PHY_INTERFACE_MODE_USXGMII:
++ return ipq_pcs_config_usxgmii(qpcs);
+ default:
+ return -EOPNOTSUPP;
+ };
+@@ -354,6 +521,9 @@ static void ipq_pcs_link_up(struct phyli
+ ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
+ neg_mode, speed);
+ break;
++ case PHY_INTERFACE_MODE_USXGMII:
++ ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
++ break;
+ default:
+ return;
+ }
--- /dev/null
+From 34d10a4eb8fea32bb79e3012dc9d8bd2dffb0df3 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 7 Feb 2025 23:53:16 +0800
+Subject: [PATCH] MAINTAINERS: Add maintainer for Qualcomm IPQ9574 PCS driver
+
+Add maintainer for the Ethernet PCS driver supported for Qualcomm
+IPQ9574 SoC.
+
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ MAINTAINERS | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -19116,6 +19116,15 @@ S: Maintained
+ F: Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
+ F: drivers/regulator/vqmmc-ipq4019-regulator.c
+
++QUALCOMM IPQ9574 Ethernet PCS DRIVER
++M: Lei Wei <quic_leiwei@quicinc.com>
++L: netdev@vger.kernel.org
++S: Supported
++F: Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
++F: drivers/net/pcs/pcs-qcom-ipq9574.c
++F: include/dt-bindings/net/qcom,ipq9574-pcs.h
++F: include/linux/pcs/pcs-qcom-ipq9574.h
++
+ QUALCOMM NAND CONTROLLER DRIVER
+ M: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+ L: linux-mtd@lists.infradead.org
--- /dev/null
+From d6f184181b076cbb54f152994f5bc73ce524a67e Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+Date: Sun, 11 May 2025 18:21:00 -0500
+Subject: [PATCH] arm64: dts: qcom: ipq9574: add PCS uniphy nodes
+
+IPQ9574 has three uniphy blocks. IPQ9554 lacks uniphy1. They take
+their system and AHB clocks from NSSCC, and also feed NSSCC with
+the clocks that are intended for the PHYs. This is not a cirular
+dependency. Add nodes for these uniphy blocks, and the clocks they
+feed back to the NSSCC node.
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 100 ++++++++++++++++++++++++--
+ 1 file changed, 94 insertions(+), 6 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -9,6 +9,7 @@
+ #include <dt-bindings/clock/qcom,apss-ipq.h>
+ #include <dt-bindings/clock/qcom,ipq-cmn-pll.h>
+ #include <dt-bindings/clock/qcom,ipq9574-gcc.h>
++#include <dt-bindings/clock/qcom,ipq9574-nsscc.h>
+ #include <dt-bindings/interconnect/qcom,ipq9574.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/reset/qcom,ipq9574-gcc.h>
+@@ -1247,12 +1248,12 @@
+ <&cmn_pll NSS_1200MHZ_CLK>,
+ <&cmn_pll PPE_353MHZ_CLK>,
+ <&gcc GPLL0_OUT_AUX>,
+- <0>,
+- <0>,
+- <0>,
+- <0>,
+- <0>,
+- <0>,
++ <&pcs0 0>,
++ <&pcs0 1>,
++ <&pcs1 0>,
++ <&pcs1 1>,
++ <&pcs2 0>,
++ <&pcs2 1>,
+ <&gcc GCC_NSSCC_CLK>;
+ clock-names = "xo",
+ "nss_1200",
+@@ -1269,6 +1270,93 @@
+ #reset-cells = <1>;
+ #interconnect-cells = <1>;
+ };
++
++ pcs0: ethernet-pcs@7a00000 {
++ compatible = "qcom,ipq9574-pcs";
++ reg = <0x7a00000 0x10000>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ clocks = <&gcc GCC_UNIPHY0_SYS_CLK>,
++ <&gcc GCC_UNIPHY0_AHB_CLK>;
++ clock-names = "sys",
++ "ahb";
++ resets = <&gcc GCC_UNIPHY0_XPCS_RESET>;
++ #clock-cells = <1>;
++
++ pcs0_ch0: pcs-mii@0 {
++ reg = <0>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT1_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT1_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs0_ch1: pcs-mii@1 {
++ reg = <1>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT2_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT2_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs0_ch2: pcs-mii@2 {
++ reg = <2>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT3_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT3_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++
++ pcs0_ch3: pcs-mii@3 {
++ reg = <3>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT4_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT4_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++ };
++
++ pcs1: ethernet-pcs@7a10000 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "qcom,ipq9574-pcs";
++ reg = <0x7a10000 0x10000>;
++ clocks = <&gcc GCC_UNIPHY1_SYS_CLK>,
++ <&gcc GCC_UNIPHY1_AHB_CLK>;
++ clock-names = "sys",
++ "ahb";
++ resets = <&gcc GCC_UNIPHY1_XPCS_RESET>;
++ #clock-cells = <1>;
++
++ pcs1_ch0: pcs-mii@0 {
++ reg = <0>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT5_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT5_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++ };
++
++ pcs2: ethernet-pcs@7a20000 {
++ compatible = "qcom,ipq9574-pcs";
++ reg = <0x7a20000 0x10000>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ clocks = <&gcc GCC_UNIPHY2_SYS_CLK>,
++ <&gcc GCC_UNIPHY2_AHB_CLK>;
++ clock-names = "sys",
++ "ahb";
++ resets = <&gcc GCC_UNIPHY2_XPCS_RESET>;
++ #clock-cells = <1>;
++
++ pcs2_ch0: pcs-mii@0 {
++ reg = <0>;
++ clocks = <&nsscc NSS_CC_UNIPHY_PORT6_RX_CLK>,
++ <&nsscc NSS_CC_UNIPHY_PORT6_TX_CLK>;
++ clock-names = "rx",
++ "tx";
++ };
++ };
+ };
+
+ thermal-zones {
--- /dev/null
+From 48dc6d2fe28865a5c3d271aeb966b984a8085e7c Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:35 +0800
+Subject: [PATCH] dt-bindings: net: Add PPE for Qualcomm IPQ9574 SoC
+
+The PPE (packet process engine) hardware block is available in Qualcomm
+IPQ chipsets that support PPE architecture, such as IPQ9574. The PPE in
+the IPQ9574 SoC includes six ethernet ports (6 GMAC and 6 XGMAC), which
+are used to connect with external PHY devices by PCS. It includes an L2
+switch function for bridging packets among the 6 ethernet ports and the
+CPU port. The CPU port enables packet transfer between the ethernet
+ports and the ARM cores in the SoC, using the ethernet DMA.
+
+The PPE also includes packet processing offload capabilities for various
+networking functions such as route and bridge flows, VLANs, different
+tunnel protocols and VPN.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../bindings/net/qcom,ipq9574-ppe.yaml | 406 ++++++++++++++++++
+ 1 file changed, 406 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml
+@@ -0,0 +1,406 @@
++# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/net/qcom,ipq9574-ppe.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Qualcomm IPQ packet process engine (PPE)
++
++maintainers:
++ - Luo Jie <quic_luoj@quicinc.com>
++ - Lei Wei <quic_leiwei@quicinc.com>
++ - Suruchi Agarwal <quic_suruchia@quicinc.com>
++ - Pavithra R <quic_pavir@quicinc.com>>
++
++description:
++ The Ethernet functionality in the PPE (Packet Process Engine) is comprised
++ of three components, the switch core, port wrapper and Ethernet DMA.
++
++ The Switch core in the IPQ9574 PPE has maximum of 6 front panel ports and
++ two FIFO interfaces. One of the two FIFO interfaces is used for Ethernet
++ port to host CPU communication using Ethernet DMA. The other is used
++ communicating to the EIP engine which is used for IPsec offload. On the
++ IPQ9574, the PPE includes 6 GMAC/XGMACs that can be connected with external
++ Ethernet PHY. Switch core also includes BM (Buffer Management), QM (Queue
++ Management) and SCH (Scheduler) modules for supporting the packet processing.
++
++ The port wrapper provides connections from the 6 GMAC/XGMACS to UNIPHY (PCS)
++ supporting various modes such as SGMII/QSGMII/PSGMII/USXGMII/10G-BASER. There
++ are 3 UNIPHY (PCS) instances supported on the IPQ9574.
++
++ Ethernet DMA is used to transmit and receive packets between the six Ethernet
++ ports and ARM host CPU.
++
++ The follow diagram shows the PPE hardware block along with its connectivity
++ to the external hardware blocks such clock hardware blocks (CMNPLL, GCC,
++ NSS clock controller) and ethernet PCS/PHY blocks. For depicting the PHY
++ connectivity, one 4x1 Gbps PHY (QCA8075) and two 10 GBps PHYs are used as an
++ example.
++ - |
++ +---------+
++ | 48 MHZ |
++ +----+----+
++ |(clock)
++ v
++ +----+----+
++ +------| CMN PLL |
++ | +----+----+
++ | |(clock)
++ | v
++ | +----+----+ +----+----+ (clock) +----+----+
++ | +---| NSSCC | | GCC |--------->| MDIO |
++ | | +----+----+ +----+----+ +----+----+
++ | | |(clock & reset) |(clock)
++ | | v v
++ | | +-----------------------------+----------+----------+---------+
++ | | | +-----+ |EDMA FIFO | | EIP FIFO|
++ | | | | SCH | +----------+ +---------+
++ | | | +-----+ | | |
++ | | | +------+ +------+ +-------------------+ |
++ | | | | BM | | QM | IPQ9574-PPE | L2/L3 Process | |
++ | | | +------+ +------+ +-------------------+ |
++ | | | | |
++ | | | +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ |
++ | | | | MAC0 | | MAC1 | | MAC2 | | MAC3 | | XGMAC4| |XGMAC5 | |
++ | | | +---+---+ +---+---+ +---+---+ +---+---+ +---+---+ +---+---+ |
++ | | | | | | | | | |
++ | | +-----+---------+---------+---------+---------+---------+-----+
++ | | | | | | | |
++ | | +---+---------+---------+---------+---+ +---+---+ +---+---+
++ +--+---->| PCS0 | | PCS1 | | PCS2 |
++ |(clock) +---+---------+---------+---------+---+ +---+---+ +---+---+
++ | | | | | | |
++ | +---+---------+---------+---------+---+ +---+---+ +---+---+
++ +------->| QCA8075 PHY | | PHY4 | | PHY5 |
++ (clock) +-------------------------------------+ +-------+ +-------+
++
++properties:
++ compatible:
++ enum:
++ - qcom,ipq9574-ppe
++
++ reg:
++ maxItems: 1
++
++ clocks:
++ items:
++ - description: PPE core clock from NSS clock controller
++ - description: PPE APB (Advanced Peripheral Bus) clock from NSS clock controller
++ - description: PPE ingress process engine clock from NSS clock controller
++ - description: PPE BM, QM and scheduler clock from NSS clock controller
++
++ clock-names:
++ items:
++ - const: ppe
++ - const: apb
++ - const: ipe
++ - const: btq
++
++ resets:
++ maxItems: 1
++ description: PPE reset, which is necessary before configuring PPE hardware
++
++ interconnects:
++ items:
++ - description: Clock path leading to PPE switch core function
++ - description: Clock path leading to PPE register access
++ - description: Clock path leading to QoS generation
++ - description: Clock path leading to timeout reference
++ - description: Clock path leading to NSS NOC from memory NOC
++ - description: Clock path leading to memory NOC from NSS NOC
++ - description: Clock path leading to enhanced memory NOC from NSS NOC
++
++ interconnect-names:
++ items:
++ - const: ppe
++ - const: ppe_cfg
++ - const: qos_gen
++ - const: timeout_ref
++ - const: nssnoc_memnoc
++ - const: memnoc_nssnoc
++ - const: memnoc_nssnoc_1
++
++ ethernet-dma:
++ type: object
++ additionalProperties: false
++ description:
++ EDMA (Ethernet DMA) is used to transmit packets between PPE and ARM
++ host CPU. There are 32 TX descriptor rings, 32 TX completion rings,
++ 24 RX descriptor rings and 8 RX fill rings supported.
++
++ properties:
++ clocks:
++ items:
++ - description: EDMA system clock from NSS Clock Controller
++ - description: EDMA APB (Advanced Peripheral Bus) clock from
++ NSS Clock Controller
++
++ clock-names:
++ items:
++ - const: sys
++ - const: apb
++
++ resets:
++ maxItems: 1
++ description: EDMA reset from NSS clock controller
++
++ interrupts:
++ minItems: 29
++ maxItems: 57
++
++ interrupt-names:
++ minItems: 29
++ maxItems: 57
++ items:
++ pattern: '^(txcmpl_([0-9]|[1-2][0-9]|3[0-1])|rxdesc_([0-9]|1[0-9]|2[0-3])|misc)$'
++ description:
++ Interrupts "txcmpl_[0-31]" are the Ethernet DMA Tx completion ring interrupts.
++ Interrupts "rxdesc_[0-23]" are the Ethernet DMA Rx Descriptor ring interrupts.
++ Interrupt "misc" is the Ethernet DMA miscellaneous error interrupt.
++
++ required:
++ - clocks
++ - clock-names
++ - resets
++ - interrupts
++ - interrupt-names
++
++required:
++ - compatible
++ - reg
++ - clocks
++ - clock-names
++ - resets
++ - interconnects
++ - interconnect-names
++ - ethernet-dma
++
++allOf:
++ - $ref: ethernet-switch.yaml
++
++unevaluatedProperties: false
++
++examples:
++ - |
++ #include <dt-bindings/clock/qcom,ipq9574-gcc.h>
++ #include <dt-bindings/interconnect/qcom,ipq9574.h>
++ #include <dt-bindings/interrupt-controller/arm-gic.h>
++
++ ethernet-switch@3a000000 {
++ compatible = "qcom,ipq9574-ppe";
++ reg = <0x3a000000 0xbef800>;
++ clocks = <&nsscc 80>,
++ <&nsscc 79>,
++ <&nsscc 81>,
++ <&nsscc 78>;
++ clock-names = "ppe",
++ "apb",
++ "ipe",
++ "btq";
++ resets = <&nsscc 108>;
++ interconnects = <&nsscc MASTER_NSSNOC_PPE &nsscc SLAVE_NSSNOC_PPE>,
++ <&nsscc MASTER_NSSNOC_PPE_CFG &nsscc SLAVE_NSSNOC_PPE_CFG>,
++ <&gcc MASTER_NSSNOC_QOSGEN_REF &gcc SLAVE_NSSNOC_QOSGEN_REF>,
++ <&gcc MASTER_NSSNOC_TIMEOUT_REF &gcc SLAVE_NSSNOC_TIMEOUT_REF>,
++ <&gcc MASTER_MEM_NOC_NSSNOC &gcc SLAVE_MEM_NOC_NSSNOC>,
++ <&gcc MASTER_NSSNOC_MEMNOC &gcc SLAVE_NSSNOC_MEMNOC>,
++ <&gcc MASTER_NSSNOC_MEM_NOC_1 &gcc SLAVE_NSSNOC_MEM_NOC_1>;
++ interconnect-names = "ppe",
++ "ppe_cfg",
++ "qos_gen",
++ "timeout_ref",
++ "nssnoc_memnoc",
++ "memnoc_nssnoc",
++ "memnoc_nssnoc_1";
++
++ ethernet-dma {
++ clocks = <&nsscc 77>,
++ <&nsscc 76>;
++ clock-names = "sys",
++ "apb";
++ resets = <&nsscc 0>;
++ interrupts = <GIC_SPI 371 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 372 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 373 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 374 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 375 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 376 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 377 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 378 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 379 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 380 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 381 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 382 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 383 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 384 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 505 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 504 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 503 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 502 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 501 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 500 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 352 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 353 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 354 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 499 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "txcmpl_8",
++ "txcmpl_9",
++ "txcmpl_10",
++ "txcmpl_11",
++ "txcmpl_12",
++ "txcmpl_13",
++ "txcmpl_14",
++ "txcmpl_15",
++ "txcmpl_16",
++ "txcmpl_17",
++ "txcmpl_18",
++ "txcmpl_19",
++ "txcmpl_20",
++ "txcmpl_21",
++ "txcmpl_22",
++ "txcmpl_23",
++ "txcmpl_24",
++ "txcmpl_25",
++ "txcmpl_26",
++ "txcmpl_27",
++ "txcmpl_28",
++ "txcmpl_29",
++ "txcmpl_30",
++ "txcmpl_31",
++ "rxdesc_20",
++ "rxdesc_21",
++ "rxdesc_22",
++ "rxdesc_23",
++ "misc";
++ };
++
++ ethernet-ports {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ port@1 {
++ reg = <1>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy0>;
++ pcs-handle = <&pcs0_mii0>;
++ clocks = <&nsscc 33>,
++ <&nsscc 34>,
++ <&nsscc 37>;
++ clock-names = "mac",
++ "rx",
++ "tx";
++ resets = <&nsscc 29>,
++ <&nsscc 96>,
++ <&nsscc 97>;
++ reset-names = "mac",
++ "rx",
++ "tx";
++ };
++
++ port@2 {
++ reg = <2>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy1>;
++ pcs-handle = <&pcs0_mii1>;
++ clocks = <&nsscc 40>,
++ <&nsscc 41>,
++ <&nsscc 44>;
++ clock-names = "mac",
++ "rx",
++ "tx";
++ resets = <&nsscc 30>,
++ <&nsscc 98>,
++ <&nsscc 99>;
++ reset-names = "mac",
++ "rx",
++ "tx";
++ };
++
++ port@3 {
++ reg = <3>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy2>;
++ pcs-handle = <&pcs0_mii2>;
++ clocks = <&nsscc 47>,
++ <&nsscc 48>,
++ <&nsscc 51>;
++ clock-names = "mac",
++ "rx",
++ "tx";
++ resets = <&nsscc 31>,
++ <&nsscc 100>,
++ <&nsscc 101>;
++ reset-names = "mac",
++ "rx",
++ "tx";
++ };
++
++ port@4 {
++ reg = <4>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy3>;
++ pcs-handle = <&pcs0_mii3>;
++ clocks = <&nsscc 54>,
++ <&nsscc 55>,
++ <&nsscc 58>;
++ clock-names = "mac",
++ "rx",
++ "tx";
++ resets = <&nsscc 32>,
++ <&nsscc 102>,
++ <&nsscc 103>;
++ reset-names = "mac",
++ "rx",
++ "tx";
++ };
++
++ port@5 {
++ reg = <5>;
++ phy-mode = "usxgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy4>;
++ pcs-handle = <&pcs1_mii0>;
++ clocks = <&nsscc 61>,
++ <&nsscc 62>,
++ <&nsscc 65>;
++ clock-names = "mac",
++ "rx",
++ "tx";
++ resets = <&nsscc 33>,
++ <&nsscc 104>,
++ <&nsscc 105>;
++ reset-names = "mac",
++ "rx",
++ "tx";
++ };
++
++ port@6 {
++ reg = <6>;
++ phy-mode = "usxgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy5>;
++ pcs-handle = <&pcs2_mii0>;
++ clocks = <&nsscc 68>,
++ <&nsscc 69>,
++ <&nsscc 72>;
++ clock-names = "mac",
++ "rx",
++ "tx";
++ resets = <&nsscc 34>,
++ <&nsscc 106>,
++ <&nsscc 107>;
++ reset-names = "mac",
++ "rx",
++ "tx";
++ };
++ };
++ };
--- /dev/null
+From 9973b6610830146af1a12fe02d2d6440eb80b0f9 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:36 +0800
+Subject: [PATCH] docs: networking: Add PPE driver documentation for Qualcomm
+ IPQ9574 SoC
+
+Add description and high-level diagram for PPE, driver overview and
+module enable/debug information.
+
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../device_drivers/ethernet/index.rst | 1 +
+ .../ethernet/qualcomm/ppe/ppe.rst | 197 ++++++++++++++++++
+ 2 files changed, 198 insertions(+)
+ create mode 100644 Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst
+
+--- a/Documentation/networking/device_drivers/ethernet/index.rst
++++ b/Documentation/networking/device_drivers/ethernet/index.rst
+@@ -49,6 +49,7 @@ Contents:
+ neterion/s2io
+ netronome/nfp
+ pensando/ionic
++ qualcomm/ppe/ppe
+ smsc/smc9
+ stmicro/stmmac
+ ti/cpsw
+--- /dev/null
++++ b/Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst
+@@ -0,0 +1,197 @@
++.. SPDX-License-Identifier: GPL-2.0
++
++===============================================
++PPE Ethernet Driver for Qualcomm IPQ SoC Family
++===============================================
++
++Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++
++Author: Lei Wei <quic_leiwei@quicinc.com>
++
++
++Contents
++========
++
++- `PPE Overview`_
++- `PPE Driver Overview`_
++- `PPE Driver Supported SoCs`_
++- `Enabling the Driver`_
++- `Debugging`_
++
++
++PPE Overview
++============
++
++IPQ (Qualcomm Internet Processor) SoC (System-on-Chip) series is Qualcomm's series of
++networking SoC for Wi-Fi access points. The PPE (Packet Process Engine) is the Ethernet
++packet process engine in the IPQ SoC.
++
++Below is a simplified hardware diagram of IPQ9574 SoC which includes the PPE engine and
++other blocks which are in the SoC but outside the PPE engine. These blocks work together
++to enable the Ethernet for the IPQ SoC::
++
++ +------+ +------+ +------+ +------+ +------+ +------+ start +-------+
++ |netdev| |netdev| |netdev| |netdev| |netdev| |netdev|<------|PHYLINK|
++ +------+ +------+ +------+ +------+ +------+ +------+ stop +-+-+-+-+
++ | | | ^
++ +-------+ +-------------------------+--------+----------------------+ | | |
++ | GCC | | | EDMA | | | | |
++ +---+---+ | PPE +---+----+ | | | |
++ | clk | | | | | |
++ +------>| +-----------------------+------+-----+---------------+ | | | |
++ | | Switch Core |Port0 | |Port7(EIP FIFO)| | | | |
++ | | +---+--+ +------+--------+ | | | |
++ | | | | | | | | |
++ +-------+ | | +------+---------------+----+ | | | | |
++ |CMN PLL| | | +---+ +---+ +----+ | +--------+ | | | | | |
++ +---+---+ | | |BM | |QM | |SCH | | | L2/L3 | ....... | | | | | |
++ | | | | +---+ +---+ +----+ | +--------+ | | | | | |
++ | | | | +------+--------------------+ | | | | |
++ | | | | | | | | | |
++ | v | | +-----+-+-----+-+-----+-+-+---+--+-----+-+-----+ | | | | |
++ | +------+ | | |Port1| |Port2| |Port3| |Port4| |Port5| |Port6| | | | | |
++ | |NSSCC | | | +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ | | mac| | |
++ | +-+-+--+ | | |MAC0 | |MAC1 | |MAC2 | |MAC3 | |MAC4 | |MAC5 | | |<---+ | |
++ | ^ | |clk | | +-----+-+-----+-+-----+-+-----+--+-----+-+-----+ | | ops | |
++ | | | +---->| +----|------|-------|-------|---------|--------|-----+ | | |
++ | | | +---------------------------------------------------------+ | |
++ | | | | | | | | | | |
++ | | | MII clk | QSGMII USXGMII USXGMII | |
++ | | +------------->| | | | | | | |
++ | | +-------------------------+ +---------+ +---------+ | |
++ | |125/312.5M clk| (PCS0) | | (PCS1) | | (PCS2) | pcs ops | |
++ | +--------------+ UNIPHY0 | | UNIPHY1 | | UNIPHY2 |<--------+ |
++ +--------------->| | | | | | |
++ | 31.25M ref clk +-------------------------+ +---------+ +---------+ |
++ | | | | | | | |
++ | +-----------------------------------------------------+ |
++ |25/50M ref clk| +-------------------------+ +------+ +------+ | link |
++ +------------->| | QUAD PHY | | PHY4 | | PHY5 | |---------+
++ | +-------------------------+ +------+ +------+ | change
++ | |
++ | MDIO bus |
++ +-----------------------------------------------------+
++
++The CMN (Common) PLL, NSSCC (Networking Sub System Clock Controller) and GCC (Global
++Clock Controller) blocks are in the SoC and act as clock providers.
++
++The UNIPHY block is in the SoC and provides the PCS (Physical Coding Sublayer) and
++XPCS (10-Gigabit Physical Coding Sublayer) functions to support different interface
++modes between the PPE MAC and the external PHY.
++
++This documentation focuses on the descriptions of PPE engine and the PPE driver.
++
++The Ethernet functionality in the PPE (Packet Process Engine) is comprised of three
++components: the switch core, port wrapper and Ethernet DMA.
++
++The Switch core in the IPQ9574 PPE has maximum of 6 front panel ports and two FIFO
++interfaces. One of the two FIFO interfaces is used for Ethernet port to host CPU
++communication using Ethernet DMA. The other is used communicating to the EIP engine
++which is used for IPsec offload. On the IPQ9574, the PPE includes 6 GMAC/XGMACs that
++can be connected with external Ethernet PHY. Switch core also includes BM (Buffer
++Management), QM (Queue Management) and SCH (Scheduler) modules for supporting the
++packet processing.
++
++The port wrapper provides connections from the 6 GMAC/XGMACS to UNIPHY (PCS) supporting
++various modes such as SGMII/QSGMII/PSGMII/USXGMII/10G-BASER. There are 3 UNIPHY (PCS)
++instances supported on the IPQ9574.
++
++Ethernet DMA is used to transmit and receive packets between the Ethernet subsystem
++and ARM host CPU.
++
++The following lists the main blocks in the PPE engine which will be driven by this
++PPE driver:
++
++- BM
++ BM is the hardware buffer manager for the PPE switch ports.
++- QM
++ Queue Manager for managing the egress hardware queues of the PPE switch ports.
++- SCH
++ The scheduler which manages the hardware traffic scheduling for the PPE switch ports.
++- L2
++ The L2 block performs the packet bridging in the switch core. The bridge domain is
++ represented by the VSI (Virtual Switch Instance) domain in PPE. FDB learning can be
++ enabled based on the VSI domain and bridge forwarding occurs within the VSI domain.
++- MAC
++ The PPE in the IPQ9574 supports up to six MACs (MAC0 to MAC5) which are corresponding
++ to six switch ports (port1 to port6). The MAC block is connected with external PHY
++ through the UNIPHY PCS block. Each MAC block includes the GMAC and XGMAC blocks and
++ the switch port can select to use GMAC or XMAC through a MUX selection according to
++ the external PHY's capability.
++- EDMA (Ethernet DMA)
++ The Ethernet DMA is used to transmit and receive Ethernet packets between the PPE
++ ports and the ARM cores.
++
++The received packet on a PPE MAC port can be forwarded to another PPE MAC port. It can
++be also forwarded to internal switch port0 so that the packet can be delivered to the
++ARM cores using the Ethernet DMA (EDMA) engine. The Ethernet DMA driver will deliver the
++packet to the corresponding 'netdevice' interface.
++
++The software instantiations of the PPE MAC (netdevice), PCS and external PHYs interact
++with the Linux PHYLINK framework to manage the connectivity between the PPE ports and
++the connected PHYs, and the port link states. This is also illustrated in above diagram.
++
++
++PPE Driver Overview
++===================
++PPE driver is Ethernet driver for the Qualcomm IPQ SoC. It is a single platform driver
++which includes the PPE part and Ethernet DMA part. The PPE part initializes and drives the
++various blocks in PPE switch core such as BM/QM/L2 blocks and the PPE MACs. The EDMA part
++drives the Ethernet DMA for packet transfer between PPE ports and ARM cores, and enables
++the netdevice driver for the PPE ports.
++
++The PPE driver files in drivers/net/ethernet/qualcomm/ppe/ are listed as below:
++
++- Makefile
++- ppe.c
++- ppe.h
++- ppe_config.c
++- ppe_config.h
++- ppe_debugfs.c
++- ppe_debugfs.h
++- ppe_regs.h
++
++The ppe.c file contains the main PPE platform driver and undertakes the initialization of
++PPE switch core blocks such as QM, BM and L2. The configuration APIs for these hardware
++blocks are provided in the ppe_config.c file.
++
++The ppe.h defines the PPE device data structure which will be used by PPE driver functions.
++
++The ppe_debugfs.c enables the PPE statistics counters such as PPE port Rx and Tx counters,
++CPU code counters and queue counters.
++
++
++PPE Driver Supported SoCs
++=========================
++
++The PPE driver supports the following IPQ SoC:
++
++- IPQ9574
++
++
++Enabling the Driver
++===================
++
++The driver is located in the menu structure at:
++
++ -> Device Drivers
++ -> Network device support (NETDEVICES [=y])
++ -> Ethernet driver support
++ -> Qualcomm devices
++ -> Qualcomm Technologies, Inc. PPE Ethernet support
++
++If this driver is built as a module, we can use below commands to install and remove it:
++
++- insmod qcom-ppe.ko
++- rmmod qcom-ppe.ko
++
++The PPE driver functionally depends on the CMN PLL and NSSCC clock controller drivers.
++Please make sure the dependent modules are installed before installing the PPE driver
++module.
++
++
++Debugging
++=========
++
++The PPE hardware counters are available in the debugfs and can be checked by the command
++``cat /sys/kernel/debug/ppe/packet_counters``.
--- /dev/null
+From d1158f0282304c89217894aa346fc45364b95542 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:37 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE driver for IPQ9574 SoC
+
+The PPE (Packet Process Engine) hardware block is available
+on Qualcomm IPQ SoC that support PPE architecture, such as
+IPQ9574.
+
+The PPE in IPQ9574 includes six integrated ethernet MAC
+(for 6 PPE ports), buffer management, queue management and
+scheduler functions. The MACs can connect with the external
+PHY or switch devices using the UNIPHY PCS block available
+in the SoC.
+
+The PPE also includes various packet processing offload
+capabilities such as L3 routing and L2 bridging, VLAN and
+tunnel processing offload. It also includes Ethernet DMA
+function for transferring packets between ARM cores and
+PPE ethernet ports.
+
+This patch adds the base source files and Makefiles for
+the PPE driver such as platform driver registration,
+clock initialization, and PPE reset routines.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 15 ++
+ drivers/net/ethernet/qualcomm/Makefile | 1 +
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 7 +
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 218 +++++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe.h | 36 ++++
+ 5 files changed, 277 insertions(+)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/Makefile
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe.h
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -61,6 +61,21 @@ config QCOM_EMAC
+ low power, Receive-Side Scaling (RSS), and IEEE 1588-2008
+ Precision Clock Synchronization Protocol.
+
++config QCOM_PPE
++ tristate "Qualcomm Technologies, Inc. PPE Ethernet support"
++ depends on HAS_IOMEM && OF
++ depends on COMMON_CLK
++ select REGMAP_MMIO
++ help
++ This driver supports the Qualcomm Technologies, Inc. packet
++ process engine (PPE) available with IPQ SoC. The PPE includes
++ the ethernet MACs, Ethernet DMA (EDMA) and switch core that
++ supports L3 flow offload, L2 switch function, RSS and tunnel
++ offload.
++
++ To compile this driver as a module, choose M here. The module
++ will be called qcom-ppe.
++
+ source "drivers/net/ethernet/qualcomm/rmnet/Kconfig"
+
+ endif # NET_VENDOR_QUALCOMM
+--- a/drivers/net/ethernet/qualcomm/Makefile
++++ b/drivers/net/ethernet/qualcomm/Makefile
+@@ -11,4 +11,5 @@ qcauart-objs := qca_uart.o
+
+ obj-y += emac/
+
++obj-$(CONFIG_QCOM_PPE) += ppe/
+ obj-$(CONFIG_RMNET) += rmnet/
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -0,0 +1,7 @@
++# SPDX-License-Identifier: GPL-2.0-only
++#
++# Makefile for the device driver of PPE (Packet Process Engine) in IPQ SoC
++#
++
++obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
++qcom-ppe-objs := ppe.o
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -0,0 +1,218 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE platform device probe, DTSI parser and PPE clock initializations. */
++
++#include <linux/clk.h>
++#include <linux/interconnect.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++#include <linux/reset.h>
++
++#include "ppe.h"
++
++#define PPE_PORT_MAX 8
++#define PPE_CLK_RATE 353000000
++
++/* ICC clocks for enabling PPE device. The avg_bw and peak_bw with value 0
++ * will be updated by the clock rate of PPE.
++ */
++static const struct icc_bulk_data ppe_icc_data[] = {
++ {
++ .name = "ppe",
++ .avg_bw = 0,
++ .peak_bw = 0,
++ },
++ {
++ .name = "ppe_cfg",
++ .avg_bw = 0,
++ .peak_bw = 0,
++ },
++ {
++ .name = "qos_gen",
++ .avg_bw = 6000,
++ .peak_bw = 6000,
++ },
++ {
++ .name = "timeout_ref",
++ .avg_bw = 6000,
++ .peak_bw = 6000,
++ },
++ {
++ .name = "nssnoc_memnoc",
++ .avg_bw = 533333,
++ .peak_bw = 533333,
++ },
++ {
++ .name = "memnoc_nssnoc",
++ .avg_bw = 533333,
++ .peak_bw = 533333,
++ },
++ {
++ .name = "memnoc_nssnoc_1",
++ .avg_bw = 533333,
++ .peak_bw = 533333,
++ },
++};
++
++static const struct regmap_range ppe_readable_ranges[] = {
++ regmap_reg_range(0x0, 0x1ff), /* Global */
++ regmap_reg_range(0x400, 0x5ff), /* LPI CSR */
++ regmap_reg_range(0x1000, 0x11ff), /* GMAC0 */
++ regmap_reg_range(0x1200, 0x13ff), /* GMAC1 */
++ regmap_reg_range(0x1400, 0x15ff), /* GMAC2 */
++ regmap_reg_range(0x1600, 0x17ff), /* GMAC3 */
++ regmap_reg_range(0x1800, 0x19ff), /* GMAC4 */
++ regmap_reg_range(0x1a00, 0x1bff), /* GMAC5 */
++ regmap_reg_range(0xb000, 0xefff), /* PRX CSR */
++ regmap_reg_range(0xf000, 0x1efff), /* IPE */
++ regmap_reg_range(0x20000, 0x5ffff), /* PTX CSR */
++ regmap_reg_range(0x60000, 0x9ffff), /* IPE L2 CSR */
++ regmap_reg_range(0xb0000, 0xeffff), /* IPO CSR */
++ regmap_reg_range(0x100000, 0x17ffff), /* IPE PC */
++ regmap_reg_range(0x180000, 0x1bffff), /* PRE IPO CSR */
++ regmap_reg_range(0x1d0000, 0x1dffff), /* Tunnel parser */
++ regmap_reg_range(0x1e0000, 0x1effff), /* Ingress parse */
++ regmap_reg_range(0x200000, 0x2fffff), /* IPE L3 */
++ regmap_reg_range(0x300000, 0x3fffff), /* IPE tunnel */
++ regmap_reg_range(0x400000, 0x4fffff), /* Scheduler */
++ regmap_reg_range(0x500000, 0x503fff), /* XGMAC0 */
++ regmap_reg_range(0x504000, 0x507fff), /* XGMAC1 */
++ regmap_reg_range(0x508000, 0x50bfff), /* XGMAC2 */
++ regmap_reg_range(0x50c000, 0x50ffff), /* XGMAC3 */
++ regmap_reg_range(0x510000, 0x513fff), /* XGMAC4 */
++ regmap_reg_range(0x514000, 0x517fff), /* XGMAC5 */
++ regmap_reg_range(0x600000, 0x6fffff), /* BM */
++ regmap_reg_range(0x800000, 0x9fffff), /* QM */
++ regmap_reg_range(0xb00000, 0xbef800), /* EDMA */
++};
++
++static const struct regmap_access_table ppe_reg_table = {
++ .yes_ranges = ppe_readable_ranges,
++ .n_yes_ranges = ARRAY_SIZE(ppe_readable_ranges),
++};
++
++static const struct regmap_config regmap_config_ipq9574 = {
++ .reg_bits = 32,
++ .reg_stride = 4,
++ .val_bits = 32,
++ .rd_table = &ppe_reg_table,
++ .wr_table = &ppe_reg_table,
++ .max_register = 0xbef800,
++ .fast_io = true,
++};
++
++static int ppe_clock_init_and_reset(struct ppe_device *ppe_dev)
++{
++ unsigned long ppe_rate = ppe_dev->clk_rate;
++ struct device *dev = ppe_dev->dev;
++ struct reset_control *rstc;
++ struct clk_bulk_data *clks;
++ struct clk *clk;
++ int ret, i;
++
++ for (i = 0; i < ppe_dev->num_icc_paths; i++) {
++ ppe_dev->icc_paths[i].name = ppe_icc_data[i].name;
++ ppe_dev->icc_paths[i].avg_bw = ppe_icc_data[i].avg_bw ? :
++ Bps_to_icc(ppe_rate);
++ ppe_dev->icc_paths[i].peak_bw = ppe_icc_data[i].peak_bw ? :
++ Bps_to_icc(ppe_rate);
++ }
++
++ ret = devm_of_icc_bulk_get(dev, ppe_dev->num_icc_paths,
++ ppe_dev->icc_paths);
++ if (ret)
++ return ret;
++
++ ret = icc_bulk_set_bw(ppe_dev->num_icc_paths, ppe_dev->icc_paths);
++ if (ret)
++ return ret;
++
++ /* The PPE clocks have a common parent clock. Setting the clock
++ * rate of "ppe" ensures the clock rate of all PPE clocks is
++ * configured to the same rate.
++ */
++ clk = devm_clk_get(dev, "ppe");
++ if (IS_ERR(clk))
++ return PTR_ERR(clk);
++
++ ret = clk_set_rate(clk, ppe_rate);
++ if (ret)
++ return ret;
++
++ ret = devm_clk_bulk_get_all_enable(dev, &clks);
++ if (ret < 0)
++ return ret;
++
++ /* Reset the PPE. */
++ rstc = devm_reset_control_get_exclusive(dev, NULL);
++ if (IS_ERR(rstc))
++ return PTR_ERR(rstc);
++
++ ret = reset_control_assert(rstc);
++ if (ret)
++ return ret;
++
++ /* The delay 10 ms of assert is necessary for resetting PPE. */
++ usleep_range(10000, 11000);
++
++ return reset_control_deassert(rstc);
++}
++
++static int qcom_ppe_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct ppe_device *ppe_dev;
++ void __iomem *base;
++ int ret, num_icc;
++
++ num_icc = ARRAY_SIZE(ppe_icc_data);
++ ppe_dev = devm_kzalloc(dev, struct_size(ppe_dev, icc_paths, num_icc),
++ GFP_KERNEL);
++ if (!ppe_dev)
++ return -ENOMEM;
++
++ base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(base))
++ return dev_err_probe(dev, PTR_ERR(base), "PPE ioremap failed\n");
++
++ ppe_dev->regmap = devm_regmap_init_mmio(dev, base, ®map_config_ipq9574);
++ if (IS_ERR(ppe_dev->regmap))
++ return dev_err_probe(dev, PTR_ERR(ppe_dev->regmap),
++ "PPE initialize regmap failed\n");
++ ppe_dev->dev = dev;
++ ppe_dev->clk_rate = PPE_CLK_RATE;
++ ppe_dev->num_ports = PPE_PORT_MAX;
++ ppe_dev->num_icc_paths = num_icc;
++
++ ret = ppe_clock_init_and_reset(ppe_dev);
++ if (ret)
++ return dev_err_probe(dev, ret, "PPE clock config failed\n");
++
++ platform_set_drvdata(pdev, ppe_dev);
++
++ return 0;
++}
++
++static const struct of_device_id qcom_ppe_of_match[] = {
++ { .compatible = "qcom,ipq9574-ppe" },
++ {}
++};
++MODULE_DEVICE_TABLE(of, qcom_ppe_of_match);
++
++static struct platform_driver qcom_ppe_driver = {
++ .driver = {
++ .name = "qcom_ppe",
++ .of_match_table = qcom_ppe_of_match,
++ },
++ .probe = qcom_ppe_probe,
++};
++module_platform_driver(qcom_ppe_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Qualcomm Technologies, Inc. IPQ PPE driver");
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.h
+@@ -0,0 +1,36 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ *
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __PPE_H__
++#define __PPE_H__
++
++#include <linux/compiler.h>
++#include <linux/interconnect.h>
++
++struct device;
++struct regmap;
++
++/**
++ * struct ppe_device - PPE device private data.
++ * @dev: PPE device structure.
++ * @regmap: PPE register map.
++ * @clk_rate: PPE clock rate.
++ * @num_ports: Number of PPE ports.
++ * @num_icc_paths: Number of interconnect paths.
++ * @icc_paths: Interconnect path array.
++ *
++ * PPE device is the instance of PPE hardware, which is used to
++ * configure PPE packet process modules such as BM (buffer management),
++ * QM (queue management), and scheduler.
++ */
++struct ppe_device {
++ struct device *dev;
++ struct regmap *regmap;
++ unsigned long clk_rate;
++ unsigned int num_ports;
++ unsigned int num_icc_paths;
++ struct icc_bulk_data icc_paths[] __counted_by(num_icc_paths);
++};
++#endif
--- /dev/null
+From 6e639ab45348ee7a697db8b481fa6f8555280f58 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:38 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE buffer management for
+ IPQ9574
+
+The BM (Buffer Management) config controls the pause frame generated
+on the PPE port. There are maximum 15 BM ports and 4 groups supported,
+all BM ports are assigned to group 0 by default. The number of hardware
+buffers configured for the port influence the threshold of the flow
+control for that port.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 5 +
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 195 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 12 ++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 59 ++++++
+ 5 files changed, 272 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -4,4 +4,4 @@
+ #
+
+ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+-qcom-ppe-objs := ppe.o
++qcom-ppe-objs := ppe.o ppe_config.o
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -15,6 +15,7 @@
+ #include <linux/reset.h>
+
+ #include "ppe.h"
++#include "ppe_config.h"
+
+ #define PPE_PORT_MAX 8
+ #define PPE_CLK_RATE 353000000
+@@ -194,6 +195,10 @@ static int qcom_ppe_probe(struct platfor
+ if (ret)
+ return dev_err_probe(dev, ret, "PPE clock config failed\n");
+
++ ret = ppe_hw_config(ppe_dev);
++ if (ret)
++ return dev_err_probe(dev, ret, "PPE HW config failed\n");
++
+ platform_set_drvdata(pdev, ppe_dev);
+
+ return 0;
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -0,0 +1,195 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE HW initialization configs such as BM(buffer management),
++ * QM(queue management) and scheduler configs.
++ */
++
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/device.h>
++#include <linux/regmap.h>
++
++#include "ppe.h"
++#include "ppe_config.h"
++#include "ppe_regs.h"
++
++/**
++ * struct ppe_bm_port_config - PPE BM port configuration.
++ * @port_id_start: The fist BM port ID to configure.
++ * @port_id_end: The last BM port ID to configure.
++ * @pre_alloc: BM port dedicated buffer number.
++ * @in_fly_buf: Buffer number for receiving the packet after pause frame sent.
++ * @ceil: Ceil to generate the back pressure.
++ * @weight: Weight value.
++ * @resume_offset: Resume offset from the threshold value.
++ * @resume_ceil: Ceil to resume from the back pressure state.
++ * @dynamic: Dynamic threshold used or not.
++ *
++ * The is for configuring the threshold that impacts the port
++ * flow control.
++ */
++struct ppe_bm_port_config {
++ unsigned int port_id_start;
++ unsigned int port_id_end;
++ unsigned int pre_alloc;
++ unsigned int in_fly_buf;
++ unsigned int ceil;
++ unsigned int weight;
++ unsigned int resume_offset;
++ unsigned int resume_ceil;
++ bool dynamic;
++};
++
++/* Assign the share buffer number 1550 to group 0 by default. */
++static const int ipq9574_ppe_bm_group_config = 1550;
++
++/* The buffer configurations per PPE port. There are 15 BM ports and
++ * 4 BM groups supported by PPE. BM port (0-7) is for EDMA port 0,
++ * BM port (8-13) is for PPE physical port 1-6 and BM port 14 is for
++ * EIP port.
++ */
++static const struct ppe_bm_port_config ipq9574_ppe_bm_port_config[] = {
++ {
++ /* Buffer configuration for the BM port ID 0 of EDMA. */
++ .port_id_start = 0,
++ .port_id_end = 0,
++ .pre_alloc = 0,
++ .in_fly_buf = 100,
++ .ceil = 1146,
++ .weight = 7,
++ .resume_offset = 8,
++ .resume_ceil = 0,
++ .dynamic = true,
++ },
++ {
++ /* Buffer configuration for the BM port ID 1-7 of EDMA. */
++ .port_id_start = 1,
++ .port_id_end = 7,
++ .pre_alloc = 0,
++ .in_fly_buf = 100,
++ .ceil = 250,
++ .weight = 4,
++ .resume_offset = 36,
++ .resume_ceil = 0,
++ .dynamic = true,
++ },
++ {
++ /* Buffer configuration for the BM port ID 8-13 of PPE ports. */
++ .port_id_start = 8,
++ .port_id_end = 13,
++ .pre_alloc = 0,
++ .in_fly_buf = 128,
++ .ceil = 250,
++ .weight = 4,
++ .resume_offset = 36,
++ .resume_ceil = 0,
++ .dynamic = true,
++ },
++ {
++ /* Buffer configuration for the BM port ID 14 of EIP. */
++ .port_id_start = 14,
++ .port_id_end = 14,
++ .pre_alloc = 0,
++ .in_fly_buf = 40,
++ .ceil = 250,
++ .weight = 4,
++ .resume_offset = 36,
++ .resume_ceil = 0,
++ .dynamic = true,
++ },
++};
++
++static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
++ const struct ppe_bm_port_config port_cfg)
++{
++ u32 reg, val, bm_fc_val[2];
++ int ret;
++
++ reg = PPE_BM_PORT_FC_CFG_TBL_ADDR + PPE_BM_PORT_FC_CFG_TBL_INC * bm_port_id;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ bm_fc_val, ARRAY_SIZE(bm_fc_val));
++ if (ret)
++ return ret;
++
++ /* Configure BM flow control related threshold. */
++ PPE_BM_PORT_FC_SET_WEIGHT(bm_fc_val, port_cfg.weight);
++ PPE_BM_PORT_FC_SET_RESUME_OFFSET(bm_fc_val, port_cfg.resume_offset);
++ PPE_BM_PORT_FC_SET_RESUME_THRESHOLD(bm_fc_val, port_cfg.resume_ceil);
++ PPE_BM_PORT_FC_SET_DYNAMIC(bm_fc_val, port_cfg.dynamic);
++ PPE_BM_PORT_FC_SET_REACT_LIMIT(bm_fc_val, port_cfg.in_fly_buf);
++ PPE_BM_PORT_FC_SET_PRE_ALLOC(bm_fc_val, port_cfg.pre_alloc);
++
++ /* Configure low/high bits of the ceiling for the BM port. */
++ val = FIELD_GET(GENMASK(2, 0), port_cfg.ceil);
++ PPE_BM_PORT_FC_SET_CEILING_LOW(bm_fc_val, val);
++ val = FIELD_GET(GENMASK(10, 3), port_cfg.ceil);
++ PPE_BM_PORT_FC_SET_CEILING_HIGH(bm_fc_val, val);
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ bm_fc_val, ARRAY_SIZE(bm_fc_val));
++ if (ret)
++ return ret;
++
++ /* Assign the default group ID 0 to the BM port. */
++ val = FIELD_PREP(PPE_BM_PORT_GROUP_ID_SHARED_GROUP_ID, 0);
++ reg = PPE_BM_PORT_GROUP_ID_ADDR + PPE_BM_PORT_GROUP_ID_INC * bm_port_id;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_BM_PORT_GROUP_ID_SHARED_GROUP_ID,
++ val);
++ if (ret)
++ return ret;
++
++ /* Enable BM port flow control. */
++ reg = PPE_BM_PORT_FC_MODE_ADDR + PPE_BM_PORT_FC_MODE_INC * bm_port_id;
++
++ return regmap_set_bits(ppe_dev->regmap, reg, PPE_BM_PORT_FC_MODE_EN);
++}
++
++/* Configure the buffer threshold for the port flow control function. */
++static int ppe_config_bm(struct ppe_device *ppe_dev)
++{
++ const struct ppe_bm_port_config *port_cfg;
++ unsigned int i, bm_port_id, port_cfg_cnt;
++ u32 reg, val;
++ int ret;
++
++ /* Configure the allocated buffer number only for group 0.
++ * The buffer number of group 1-3 is already cleared to 0
++ * after PPE reset during the probe of PPE driver.
++ */
++ reg = PPE_BM_SHARED_GROUP_CFG_ADDR;
++ val = FIELD_PREP(PPE_BM_SHARED_GROUP_CFG_SHARED_LIMIT,
++ ipq9574_ppe_bm_group_config);
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_BM_SHARED_GROUP_CFG_SHARED_LIMIT,
++ val);
++ if (ret)
++ goto bm_config_fail;
++
++ /* Configure buffer thresholds for the BM ports. */
++ port_cfg = ipq9574_ppe_bm_port_config;
++ port_cfg_cnt = ARRAY_SIZE(ipq9574_ppe_bm_port_config);
++ for (i = 0; i < port_cfg_cnt; i++) {
++ for (bm_port_id = port_cfg[i].port_id_start;
++ bm_port_id <= port_cfg[i].port_id_end; bm_port_id++) {
++ ret = ppe_config_bm_threshold(ppe_dev, bm_port_id,
++ port_cfg[i]);
++ if (ret)
++ goto bm_config_fail;
++ }
++ }
++
++ return 0;
++
++bm_config_fail:
++ dev_err(ppe_dev->dev, "PPE BM config error %d\n", ret);
++ return ret;
++}
++
++int ppe_hw_config(struct ppe_device *ppe_dev)
++{
++ return ppe_config_bm(ppe_dev);
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -0,0 +1,12 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ *
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __PPE_CONFIG_H__
++#define __PPE_CONFIG_H__
++
++#include "ppe.h"
++
++int ppe_hw_config(struct ppe_device *ppe_dev);
++#endif
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -0,0 +1,59 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ *
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE hardware register and table declarations. */
++#ifndef __PPE_REGS_H__
++#define __PPE_REGS_H__
++
++#include <linux/bitfield.h>
++
++/* There are 15 BM ports and 4 BM groups supported by PPE.
++ * BM port (0-7) is for EDMA port 0, BM port (8-13) is for
++ * PPE physical port 1-6 and BM port 14 is for EIP port.
++ */
++#define PPE_BM_PORT_FC_MODE_ADDR 0x600100
++#define PPE_BM_PORT_FC_MODE_ENTRIES 15
++#define PPE_BM_PORT_FC_MODE_INC 0x4
++#define PPE_BM_PORT_FC_MODE_EN BIT(0)
++
++#define PPE_BM_PORT_GROUP_ID_ADDR 0x600180
++#define PPE_BM_PORT_GROUP_ID_ENTRIES 15
++#define PPE_BM_PORT_GROUP_ID_INC 0x4
++#define PPE_BM_PORT_GROUP_ID_SHARED_GROUP_ID GENMASK(1, 0)
++
++#define PPE_BM_SHARED_GROUP_CFG_ADDR 0x600290
++#define PPE_BM_SHARED_GROUP_CFG_ENTRIES 4
++#define PPE_BM_SHARED_GROUP_CFG_INC 0x4
++#define PPE_BM_SHARED_GROUP_CFG_SHARED_LIMIT GENMASK(10, 0)
++
++#define PPE_BM_PORT_FC_CFG_TBL_ADDR 0x601000
++#define PPE_BM_PORT_FC_CFG_TBL_ENTRIES 15
++#define PPE_BM_PORT_FC_CFG_TBL_INC 0x10
++#define PPE_BM_PORT_FC_W0_REACT_LIMIT GENMASK(8, 0)
++#define PPE_BM_PORT_FC_W0_RESUME_THRESHOLD GENMASK(17, 9)
++#define PPE_BM_PORT_FC_W0_RESUME_OFFSET GENMASK(28, 18)
++#define PPE_BM_PORT_FC_W0_CEILING_LOW GENMASK(31, 29)
++#define PPE_BM_PORT_FC_W1_CEILING_HIGH GENMASK(7, 0)
++#define PPE_BM_PORT_FC_W1_WEIGHT GENMASK(10, 8)
++#define PPE_BM_PORT_FC_W1_DYNAMIC BIT(11)
++#define PPE_BM_PORT_FC_W1_PRE_ALLOC GENMASK(22, 12)
++
++#define PPE_BM_PORT_FC_SET_REACT_LIMIT(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_BM_PORT_FC_W0_REACT_LIMIT)
++#define PPE_BM_PORT_FC_SET_RESUME_THRESHOLD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_BM_PORT_FC_W0_RESUME_THRESHOLD)
++#define PPE_BM_PORT_FC_SET_RESUME_OFFSET(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_BM_PORT_FC_W0_RESUME_OFFSET)
++#define PPE_BM_PORT_FC_SET_CEILING_LOW(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_BM_PORT_FC_W0_CEILING_LOW)
++#define PPE_BM_PORT_FC_SET_CEILING_HIGH(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_CEILING_HIGH)
++#define PPE_BM_PORT_FC_SET_WEIGHT(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_WEIGHT)
++#define PPE_BM_PORT_FC_SET_DYNAMIC(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_DYNAMIC)
++#define PPE_BM_PORT_FC_SET_PRE_ALLOC(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_PRE_ALLOC)
++#endif
--- /dev/null
+From 9be6c3590ef3c241e6a3cfd05291304a1f973bcf Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:39 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE queue management for
+ IPQ9574
+
+QM (queue management) configurations decide the length of PPE
+queues and the queue depth for these queues which are used to
+drop packets in events of congestion.
+
+There are two types of PPE queues - unicast queues (0-255) and
+multicast queues (256-299). These queue types are used to forward
+different types of traffic, and are configured with different
+lengths.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 177 +++++++++++++++++-
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 85 +++++++++
+ 2 files changed, 261 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -43,6 +43,29 @@ struct ppe_bm_port_config {
+ bool dynamic;
+ };
+
++/**
++ * struct ppe_qm_queue_config - PPE queue config.
++ * @queue_start: PPE start of queue ID.
++ * @queue_end: PPE end of queue ID.
++ * @prealloc_buf: Queue dedicated buffer number.
++ * @ceil: Ceil to start drop packet from queue.
++ * @weight: Weight value.
++ * @resume_offset: Resume offset from the threshold.
++ * @dynamic: Threshold value is decided dynamically or statically.
++ *
++ * Queue configuration decides the threshold to drop packet from PPE
++ * hardware queue.
++ */
++struct ppe_qm_queue_config {
++ unsigned int queue_start;
++ unsigned int queue_end;
++ unsigned int prealloc_buf;
++ unsigned int ceil;
++ unsigned int weight;
++ unsigned int resume_offset;
++ bool dynamic;
++};
++
+ /* Assign the share buffer number 1550 to group 0 by default. */
+ static const int ipq9574_ppe_bm_group_config = 1550;
+
+@@ -102,6 +125,33 @@ static const struct ppe_bm_port_config i
+ },
+ };
+
++/* Default QM group settings for IPQ9754. */
++static const int ipq9574_ppe_qm_group_config = 2000;
++
++/* Default QM settings for unicast and multicast queues for IPQ9754. */
++static const struct ppe_qm_queue_config ipq9574_ppe_qm_queue_config[] = {
++ {
++ /* QM settings for unicast queues 0 to 255. */
++ .queue_start = 0,
++ .queue_end = 255,
++ .prealloc_buf = 0,
++ .ceil = 1200,
++ .weight = 7,
++ .resume_offset = 36,
++ .dynamic = true,
++ },
++ {
++ /* QM settings for multicast queues 256 to 299. */
++ .queue_start = 256,
++ .queue_end = 299,
++ .prealloc_buf = 0,
++ .ceil = 250,
++ .weight = 0,
++ .resume_offset = 36,
++ .dynamic = false,
++ },
++};
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -189,7 +239,132 @@ bm_config_fail:
+ return ret;
+ }
+
++/* Configure PPE hardware queue depth, which is decided by the threshold
++ * of queue.
++ */
++static int ppe_config_qm(struct ppe_device *ppe_dev)
++{
++ const struct ppe_qm_queue_config *queue_cfg;
++ int ret, i, queue_id, queue_cfg_count;
++ u32 reg, multicast_queue_cfg[5];
++ u32 unicast_queue_cfg[4];
++ u32 group_cfg[3];
++
++ /* Assign the buffer number to the group 0 by default. */
++ reg = PPE_AC_GRP_CFG_TBL_ADDR;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ group_cfg, ARRAY_SIZE(group_cfg));
++ if (ret)
++ goto qm_config_fail;
++
++ PPE_AC_GRP_SET_BUF_LIMIT(group_cfg, ipq9574_ppe_qm_group_config);
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ group_cfg, ARRAY_SIZE(group_cfg));
++ if (ret)
++ goto qm_config_fail;
++
++ queue_cfg = ipq9574_ppe_qm_queue_config;
++ queue_cfg_count = ARRAY_SIZE(ipq9574_ppe_qm_queue_config);
++ for (i = 0; i < queue_cfg_count; i++) {
++ queue_id = queue_cfg[i].queue_start;
++
++ /* Configure threshold for dropping packets separately for
++ * unicast and multicast PPE queues.
++ */
++ while (queue_id <= queue_cfg[i].queue_end) {
++ if (queue_id < PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES) {
++ reg = PPE_AC_UNICAST_QUEUE_CFG_TBL_ADDR +
++ PPE_AC_UNICAST_QUEUE_CFG_TBL_INC * queue_id;
++
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ unicast_queue_cfg,
++ ARRAY_SIZE(unicast_queue_cfg));
++ if (ret)
++ goto qm_config_fail;
++
++ PPE_AC_UNICAST_QUEUE_SET_EN(unicast_queue_cfg, true);
++ PPE_AC_UNICAST_QUEUE_SET_GRP_ID(unicast_queue_cfg, 0);
++ PPE_AC_UNICAST_QUEUE_SET_PRE_LIMIT(unicast_queue_cfg,
++ queue_cfg[i].prealloc_buf);
++ PPE_AC_UNICAST_QUEUE_SET_DYNAMIC(unicast_queue_cfg,
++ queue_cfg[i].dynamic);
++ PPE_AC_UNICAST_QUEUE_SET_WEIGHT(unicast_queue_cfg,
++ queue_cfg[i].weight);
++ PPE_AC_UNICAST_QUEUE_SET_THRESHOLD(unicast_queue_cfg,
++ queue_cfg[i].ceil);
++ PPE_AC_UNICAST_QUEUE_SET_GRN_RESUME(unicast_queue_cfg,
++ queue_cfg[i].resume_offset);
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ unicast_queue_cfg,
++ ARRAY_SIZE(unicast_queue_cfg));
++ if (ret)
++ goto qm_config_fail;
++ } else {
++ reg = PPE_AC_MULTICAST_QUEUE_CFG_TBL_ADDR +
++ PPE_AC_MULTICAST_QUEUE_CFG_TBL_INC * queue_id;
++
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ multicast_queue_cfg,
++ ARRAY_SIZE(multicast_queue_cfg));
++ if (ret)
++ goto qm_config_fail;
++
++ PPE_AC_MULTICAST_QUEUE_SET_EN(multicast_queue_cfg, true);
++ PPE_AC_MULTICAST_QUEUE_SET_GRN_GRP_ID(multicast_queue_cfg, 0);
++ PPE_AC_MULTICAST_QUEUE_SET_GRN_PRE_LIMIT(multicast_queue_cfg,
++ queue_cfg[i].prealloc_buf);
++ PPE_AC_MULTICAST_QUEUE_SET_GRN_THRESHOLD(multicast_queue_cfg,
++ queue_cfg[i].ceil);
++ PPE_AC_MULTICAST_QUEUE_SET_GRN_RESUME(multicast_queue_cfg,
++ queue_cfg[i].resume_offset);
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ multicast_queue_cfg,
++ ARRAY_SIZE(multicast_queue_cfg));
++ if (ret)
++ goto qm_config_fail;
++ }
++
++ /* Enable enqueue. */
++ reg = PPE_ENQ_OPR_TBL_ADDR + PPE_ENQ_OPR_TBL_INC * queue_id;
++ ret = regmap_clear_bits(ppe_dev->regmap, reg,
++ PPE_ENQ_OPR_TBL_ENQ_DISABLE);
++ if (ret)
++ goto qm_config_fail;
++
++ /* Enable dequeue. */
++ reg = PPE_DEQ_OPR_TBL_ADDR + PPE_DEQ_OPR_TBL_INC * queue_id;
++ ret = regmap_clear_bits(ppe_dev->regmap, reg,
++ PPE_DEQ_OPR_TBL_DEQ_DISABLE);
++ if (ret)
++ goto qm_config_fail;
++
++ queue_id++;
++ }
++ }
++
++ /* Enable queue counter for all PPE hardware queues. */
++ ret = regmap_set_bits(ppe_dev->regmap, PPE_EG_BRIDGE_CONFIG_ADDR,
++ PPE_EG_BRIDGE_CONFIG_QUEUE_CNT_EN);
++ if (ret)
++ goto qm_config_fail;
++
++ return 0;
++
++qm_config_fail:
++ dev_err(ppe_dev->dev, "PPE QM config error %d\n", ret);
++ return ret;
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+- return ppe_config_bm(ppe_dev);
++ int ret;
++
++ ret = ppe_config_bm(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_config_qm(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -9,6 +9,16 @@
+
+ #include <linux/bitfield.h>
+
++/* PPE queue counters enable/disable control. */
++#define PPE_EG_BRIDGE_CONFIG_ADDR 0x20044
++#define PPE_EG_BRIDGE_CONFIG_QUEUE_CNT_EN BIT(2)
++
++/* Table addresses for per-queue dequeue setting. */
++#define PPE_DEQ_OPR_TBL_ADDR 0x430000
++#define PPE_DEQ_OPR_TBL_ENTRIES 300
++#define PPE_DEQ_OPR_TBL_INC 0x10
++#define PPE_DEQ_OPR_TBL_DEQ_DISABLE BIT(0)
++
+ /* There are 15 BM ports and 4 BM groups supported by PPE.
+ * BM port (0-7) is for EDMA port 0, BM port (8-13) is for
+ * PPE physical port 1-6 and BM port 14 is for EIP port.
+@@ -56,4 +66,79 @@
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_DYNAMIC)
+ #define PPE_BM_PORT_FC_SET_PRE_ALLOC(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_PRE_ALLOC)
++
++/* PPE unicast queue (0-255) configurations. */
++#define PPE_AC_UNICAST_QUEUE_CFG_TBL_ADDR 0x848000
++#define PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES 256
++#define PPE_AC_UNICAST_QUEUE_CFG_TBL_INC 0x10
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_EN BIT(0)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_WRED_EN BIT(1)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_FC_EN BIT(2)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_CLR_AWARE BIT(3)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_GRP_ID GENMASK(5, 4)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_PRE_LIMIT GENMASK(16, 6)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_DYNAMIC BIT(17)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_WEIGHT GENMASK(20, 18)
++#define PPE_AC_UNICAST_QUEUE_CFG_W0_THRESHOLD GENMASK(31, 21)
++#define PPE_AC_UNICAST_QUEUE_CFG_W3_GRN_RESUME GENMASK(23, 13)
++
++#define PPE_AC_UNICAST_QUEUE_SET_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_UNICAST_QUEUE_CFG_W0_EN)
++#define PPE_AC_UNICAST_QUEUE_SET_GRP_ID(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_UNICAST_QUEUE_CFG_W0_GRP_ID)
++#define PPE_AC_UNICAST_QUEUE_SET_PRE_LIMIT(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_UNICAST_QUEUE_CFG_W0_PRE_LIMIT)
++#define PPE_AC_UNICAST_QUEUE_SET_DYNAMIC(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_UNICAST_QUEUE_CFG_W0_DYNAMIC)
++#define PPE_AC_UNICAST_QUEUE_SET_WEIGHT(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_UNICAST_QUEUE_CFG_W0_WEIGHT)
++#define PPE_AC_UNICAST_QUEUE_SET_THRESHOLD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_UNICAST_QUEUE_CFG_W0_THRESHOLD)
++#define PPE_AC_UNICAST_QUEUE_SET_GRN_RESUME(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x3, value, PPE_AC_UNICAST_QUEUE_CFG_W3_GRN_RESUME)
++
++/* PPE multicast queue (256-299) configurations. */
++#define PPE_AC_MULTICAST_QUEUE_CFG_TBL_ADDR 0x84a000
++#define PPE_AC_MULTICAST_QUEUE_CFG_TBL_ENTRIES 44
++#define PPE_AC_MULTICAST_QUEUE_CFG_TBL_INC 0x10
++#define PPE_AC_MULTICAST_QUEUE_CFG_W0_EN BIT(0)
++#define PPE_AC_MULTICAST_QUEUE_CFG_W0_FC_EN BIT(1)
++#define PPE_AC_MULTICAST_QUEUE_CFG_W0_CLR_AWARE BIT(2)
++#define PPE_AC_MULTICAST_QUEUE_CFG_W0_GRP_ID GENMASK(4, 3)
++#define PPE_AC_MULTICAST_QUEUE_CFG_W0_PRE_LIMIT GENMASK(15, 5)
++#define PPE_AC_MULTICAST_QUEUE_CFG_W0_THRESHOLD GENMASK(26, 16)
++#define PPE_AC_MULTICAST_QUEUE_CFG_W2_RESUME GENMASK(17, 7)
++
++#define PPE_AC_MULTICAST_QUEUE_SET_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_MULTICAST_QUEUE_CFG_W0_EN)
++#define PPE_AC_MULTICAST_QUEUE_SET_GRN_GRP_ID(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_MULTICAST_QUEUE_CFG_W0_GRP_ID)
++#define PPE_AC_MULTICAST_QUEUE_SET_GRN_PRE_LIMIT(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_MULTICAST_QUEUE_CFG_W0_PRE_LIMIT)
++#define PPE_AC_MULTICAST_QUEUE_SET_GRN_THRESHOLD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_AC_MULTICAST_QUEUE_CFG_W0_THRESHOLD)
++#define PPE_AC_MULTICAST_QUEUE_SET_GRN_RESUME(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x2, value, PPE_AC_MULTICAST_QUEUE_CFG_W2_RESUME)
++
++/* PPE admission control group (0-3) configurations */
++#define PPE_AC_GRP_CFG_TBL_ADDR 0x84c000
++#define PPE_AC_GRP_CFG_TBL_ENTRIES 0x4
++#define PPE_AC_GRP_CFG_TBL_INC 0x10
++#define PPE_AC_GRP_W0_AC_EN BIT(0)
++#define PPE_AC_GRP_W0_AC_FC_EN BIT(1)
++#define PPE_AC_GRP_W0_CLR_AWARE BIT(2)
++#define PPE_AC_GRP_W0_THRESHOLD_LOW GENMASK(31, 25)
++#define PPE_AC_GRP_W1_THRESHOLD_HIGH GENMASK(3, 0)
++#define PPE_AC_GRP_W1_BUF_LIMIT GENMASK(14, 4)
++#define PPE_AC_GRP_W2_RESUME_GRN GENMASK(15, 5)
++#define PPE_AC_GRP_W2_PRE_ALLOC GENMASK(26, 16)
++
++#define PPE_AC_GRP_SET_BUF_LIMIT(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_AC_GRP_W1_BUF_LIMIT)
++
++/* Table addresses for per-queue enqueue setting. */
++#define PPE_ENQ_OPR_TBL_ADDR 0x85c000
++#define PPE_ENQ_OPR_TBL_ENTRIES 300
++#define PPE_ENQ_OPR_TBL_INC 0x10
++#define PPE_ENQ_OPR_TBL_ENQ_DISABLE BIT(0)
+ #endif
--- /dev/null
+From 333edaf474cd707b0a04c57f255b56bc3c015789 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:40 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize the PPE scheduler
+ settings
+
+The PPE scheduler settings determine the priority of scheduling the
+packet across the different hardware queues per PPE port.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 788 +++++++++++++++++-
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 37 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 97 +++
+ 3 files changed, 921 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -16,6 +16,8 @@
+ #include "ppe_config.h"
+ #include "ppe_regs.h"
+
++#define PPE_QUEUE_SCH_PRI_NUM 8
++
+ /**
+ * struct ppe_bm_port_config - PPE BM port configuration.
+ * @port_id_start: The fist BM port ID to configure.
+@@ -66,6 +68,66 @@ struct ppe_qm_queue_config {
+ bool dynamic;
+ };
+
++/**
++ * struct ppe_scheduler_bm_config - PPE arbitration for buffer config.
++ * @valid: Arbitration entry valid or not.
++ * @is_egress: Arbitration entry for egress or not.
++ * @port: Port ID to use arbitration entry.
++ * @second_valid: Second port valid or not.
++ * @second_port: Second port to use.
++ *
++ * Configure the scheduler settings for accessing and releasing the PPE buffers.
++ */
++struct ppe_scheduler_bm_config {
++ bool valid;
++ bool is_egress;
++ unsigned int port;
++ bool second_valid;
++ unsigned int second_port;
++};
++
++/**
++ * struct ppe_scheduler_qm_config - PPE arbitration for scheduler config.
++ * @ensch_port_bmp: Port bit map for enqueue scheduler.
++ * @ensch_port: Port ID to enqueue scheduler.
++ * @desch_port: Port ID to dequeue scheduler.
++ * @desch_second_valid: Dequeue for the second port valid or not.
++ * @desch_second_port: Second port ID to dequeue scheduler.
++ *
++ * Configure the scheduler settings for enqueuing and dequeuing packets on
++ * the PPE port.
++ */
++struct ppe_scheduler_qm_config {
++ unsigned int ensch_port_bmp;
++ unsigned int ensch_port;
++ unsigned int desch_port;
++ bool desch_second_valid;
++ unsigned int desch_second_port;
++};
++
++/**
++ * struct ppe_scheduler_port_config - PPE port scheduler config.
++ * @port: Port ID to be scheduled.
++ * @flow_level: Scheduler flow level or not.
++ * @node_id: Node ID, for level 0, queue ID is used.
++ * @loop_num: Loop number of scheduler config.
++ * @pri_max: Max priority configured.
++ * @flow_id: Strict priority ID.
++ * @drr_node_id: Node ID for scheduler.
++ *
++ * PPE port scheduler configuration which decides the priority in the
++ * packet scheduler for the egress port.
++ */
++struct ppe_scheduler_port_config {
++ unsigned int port;
++ bool flow_level;
++ unsigned int node_id;
++ unsigned int loop_num;
++ unsigned int pri_max;
++ unsigned int flow_id;
++ unsigned int drr_node_id;
++};
++
+ /* Assign the share buffer number 1550 to group 0 by default. */
+ static const int ipq9574_ppe_bm_group_config = 1550;
+
+@@ -152,6 +214,599 @@ static const struct ppe_qm_queue_config
+ },
+ };
+
++/* Scheduler configuration for the assigning and releasing buffers for the
++ * packet passing through PPE, which is different per SoC.
++ */
++static const struct ppe_scheduler_bm_config ipq9574_ppe_sch_bm_config[] = {
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 7, 0, 0},
++ {1, 1, 7, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 2, 0, 0},
++ {1, 1, 2, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 3, 0, 0},
++ {1, 1, 3, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 7, 0, 0},
++ {1, 1, 7, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 4, 0, 0},
++ {1, 1, 4, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 2, 0, 0},
++ {1, 1, 2, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 7, 0, 0},
++ {1, 1, 7, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 3, 0, 0},
++ {1, 1, 3, 0, 0},
++ {1, 0, 1, 0, 0},
++ {1, 1, 1, 0, 0},
++ {1, 0, 0, 0, 0},
++ {1, 1, 0, 0, 0},
++ {1, 0, 5, 0, 0},
++ {1, 1, 5, 0, 0},
++ {1, 0, 6, 0, 0},
++ {1, 1, 6, 0, 0},
++ {1, 0, 4, 0, 0},
++ {1, 1, 4, 0, 0},
++ {1, 0, 7, 0, 0},
++ {1, 1, 7, 0, 0},
++};
++
++/* Scheduler configuration for dispatching packet on PPE queues, which
++ * is different per SoC.
++ */
++static const struct ppe_scheduler_qm_config ipq9574_ppe_sch_qm_config[] = {
++ {0x98, 6, 0, 1, 1},
++ {0x94, 5, 6, 1, 3},
++ {0x86, 0, 5, 1, 4},
++ {0x8C, 1, 6, 1, 0},
++ {0x1C, 7, 5, 1, 1},
++ {0x98, 2, 6, 1, 0},
++ {0x1C, 5, 7, 1, 1},
++ {0x34, 3, 6, 1, 0},
++ {0x8C, 4, 5, 1, 1},
++ {0x98, 2, 6, 1, 0},
++ {0x8C, 5, 4, 1, 1},
++ {0xA8, 0, 6, 1, 2},
++ {0x98, 5, 1, 1, 0},
++ {0x98, 6, 5, 1, 2},
++ {0x89, 1, 6, 1, 4},
++ {0xA4, 3, 0, 1, 1},
++ {0x8C, 5, 6, 1, 4},
++ {0xA8, 0, 2, 1, 1},
++ {0x98, 6, 5, 1, 0},
++ {0xC4, 4, 3, 1, 1},
++ {0x94, 6, 5, 1, 0},
++ {0x1C, 7, 6, 1, 1},
++ {0x98, 2, 5, 1, 0},
++ {0x1C, 6, 7, 1, 1},
++ {0x1C, 5, 6, 1, 0},
++ {0x94, 3, 5, 1, 1},
++ {0x8C, 4, 6, 1, 0},
++ {0x94, 1, 5, 1, 3},
++ {0x94, 6, 1, 1, 0},
++ {0xD0, 3, 5, 1, 2},
++ {0x98, 6, 0, 1, 1},
++ {0x94, 5, 6, 1, 3},
++ {0x94, 1, 5, 1, 0},
++ {0x98, 2, 6, 1, 1},
++ {0x8C, 4, 5, 1, 0},
++ {0x1C, 7, 6, 1, 1},
++ {0x8C, 0, 5, 1, 4},
++ {0x89, 1, 6, 1, 2},
++ {0x98, 5, 0, 1, 1},
++ {0x94, 6, 5, 1, 3},
++ {0x92, 0, 6, 1, 2},
++ {0x98, 1, 5, 1, 0},
++ {0x98, 6, 2, 1, 1},
++ {0xD0, 0, 5, 1, 3},
++ {0x94, 6, 0, 1, 1},
++ {0x8C, 5, 6, 1, 4},
++ {0x8C, 1, 5, 1, 0},
++ {0x1C, 6, 7, 1, 1},
++ {0x1C, 5, 6, 1, 0},
++ {0xB0, 2, 3, 1, 1},
++ {0xC4, 4, 5, 1, 0},
++ {0x8C, 6, 4, 1, 1},
++ {0xA4, 3, 6, 1, 0},
++ {0x1C, 5, 7, 1, 1},
++ {0x4C, 0, 5, 1, 4},
++ {0x8C, 6, 0, 1, 1},
++ {0x34, 7, 6, 1, 3},
++ {0x94, 5, 0, 1, 1},
++ {0x98, 6, 5, 1, 2},
++};
++
++static const struct ppe_scheduler_port_config ppe_port_sch_config[] = {
++ {
++ .port = 0,
++ .flow_level = true,
++ .node_id = 0,
++ .loop_num = 1,
++ .pri_max = 1,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 0,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 8,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 16,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 24,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 32,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 40,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 48,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 56,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 256,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 0,
++ .flow_level = false,
++ .node_id = 264,
++ .loop_num = 8,
++ .pri_max = 8,
++ .flow_id = 0,
++ .drr_node_id = 0,
++ },
++ {
++ .port = 1,
++ .flow_level = true,
++ .node_id = 36,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 1,
++ .drr_node_id = 8,
++ },
++ {
++ .port = 1,
++ .flow_level = false,
++ .node_id = 144,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 36,
++ .drr_node_id = 48,
++ },
++ {
++ .port = 1,
++ .flow_level = false,
++ .node_id = 272,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 36,
++ .drr_node_id = 48,
++ },
++ {
++ .port = 2,
++ .flow_level = true,
++ .node_id = 40,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 2,
++ .drr_node_id = 12,
++ },
++ {
++ .port = 2,
++ .flow_level = false,
++ .node_id = 160,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 40,
++ .drr_node_id = 64,
++ },
++ {
++ .port = 2,
++ .flow_level = false,
++ .node_id = 276,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 40,
++ .drr_node_id = 64,
++ },
++ {
++ .port = 3,
++ .flow_level = true,
++ .node_id = 44,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 3,
++ .drr_node_id = 16,
++ },
++ {
++ .port = 3,
++ .flow_level = false,
++ .node_id = 176,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 44,
++ .drr_node_id = 80,
++ },
++ {
++ .port = 3,
++ .flow_level = false,
++ .node_id = 280,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 44,
++ .drr_node_id = 80,
++ },
++ {
++ .port = 4,
++ .flow_level = true,
++ .node_id = 48,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 4,
++ .drr_node_id = 20,
++ },
++ {
++ .port = 4,
++ .flow_level = false,
++ .node_id = 192,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 48,
++ .drr_node_id = 96,
++ },
++ {
++ .port = 4,
++ .flow_level = false,
++ .node_id = 284,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 48,
++ .drr_node_id = 96,
++ },
++ {
++ .port = 5,
++ .flow_level = true,
++ .node_id = 52,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 5,
++ .drr_node_id = 24,
++ },
++ {
++ .port = 5,
++ .flow_level = false,
++ .node_id = 208,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 52,
++ .drr_node_id = 112,
++ },
++ {
++ .port = 5,
++ .flow_level = false,
++ .node_id = 288,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 52,
++ .drr_node_id = 112,
++ },
++ {
++ .port = 6,
++ .flow_level = true,
++ .node_id = 56,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 6,
++ .drr_node_id = 28,
++ },
++ {
++ .port = 6,
++ .flow_level = false,
++ .node_id = 224,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 56,
++ .drr_node_id = 128,
++ },
++ {
++ .port = 6,
++ .flow_level = false,
++ .node_id = 292,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 56,
++ .drr_node_id = 128,
++ },
++ {
++ .port = 7,
++ .flow_level = true,
++ .node_id = 60,
++ .loop_num = 2,
++ .pri_max = 0,
++ .flow_id = 7,
++ .drr_node_id = 32,
++ },
++ {
++ .port = 7,
++ .flow_level = false,
++ .node_id = 240,
++ .loop_num = 16,
++ .pri_max = 8,
++ .flow_id = 60,
++ .drr_node_id = 144,
++ },
++ {
++ .port = 7,
++ .flow_level = false,
++ .node_id = 296,
++ .loop_num = 4,
++ .pri_max = 4,
++ .flow_id = 60,
++ .drr_node_id = 144,
++ },
++};
++
++/* Set the PPE queue level scheduler configuration. */
++static int ppe_scheduler_l0_queue_map_set(struct ppe_device *ppe_dev,
++ int node_id, int port,
++ struct ppe_scheduler_cfg scheduler_cfg)
++{
++ u32 val, reg;
++ int ret;
++
++ reg = PPE_L0_FLOW_MAP_TBL_ADDR + node_id * PPE_L0_FLOW_MAP_TBL_INC;
++ val = FIELD_PREP(PPE_L0_FLOW_MAP_TBL_FLOW_ID, scheduler_cfg.flow_id);
++ val |= FIELD_PREP(PPE_L0_FLOW_MAP_TBL_C_PRI, scheduler_cfg.pri);
++ val |= FIELD_PREP(PPE_L0_FLOW_MAP_TBL_E_PRI, scheduler_cfg.pri);
++ val |= FIELD_PREP(PPE_L0_FLOW_MAP_TBL_C_NODE_WT, scheduler_cfg.drr_node_wt);
++ val |= FIELD_PREP(PPE_L0_FLOW_MAP_TBL_E_NODE_WT, scheduler_cfg.drr_node_wt);
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ reg = PPE_L0_C_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg.flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg.pri) *
++ PPE_L0_C_FLOW_CFG_TBL_INC;
++ val = FIELD_PREP(PPE_L0_C_FLOW_CFG_TBL_NODE_ID, scheduler_cfg.drr_node_id);
++ val |= FIELD_PREP(PPE_L0_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT, scheduler_cfg.unit_is_packet);
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ reg = PPE_L0_E_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg.flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg.pri) *
++ PPE_L0_E_FLOW_CFG_TBL_INC;
++ val = FIELD_PREP(PPE_L0_E_FLOW_CFG_TBL_NODE_ID, scheduler_cfg.drr_node_id);
++ val |= FIELD_PREP(PPE_L0_E_FLOW_CFG_TBL_NODE_CREDIT_UNIT, scheduler_cfg.unit_is_packet);
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ reg = PPE_L0_FLOW_PORT_MAP_TBL_ADDR + node_id * PPE_L0_FLOW_PORT_MAP_TBL_INC;
++ val = FIELD_PREP(PPE_L0_FLOW_PORT_MAP_TBL_PORT_NUM, port);
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ reg = PPE_L0_COMP_CFG_TBL_ADDR + node_id * PPE_L0_COMP_CFG_TBL_INC;
++ val = FIELD_PREP(PPE_L0_COMP_CFG_TBL_NODE_METER_LEN, scheduler_cfg.frame_mode);
++
++ return regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_L0_COMP_CFG_TBL_NODE_METER_LEN,
++ val);
++}
++
++/* Set the PPE flow level scheduler configuration. */
++static int ppe_scheduler_l1_queue_map_set(struct ppe_device *ppe_dev,
++ int node_id, int port,
++ struct ppe_scheduler_cfg scheduler_cfg)
++{
++ u32 val, reg;
++ int ret;
++
++ val = FIELD_PREP(PPE_L1_FLOW_MAP_TBL_FLOW_ID, scheduler_cfg.flow_id);
++ val |= FIELD_PREP(PPE_L1_FLOW_MAP_TBL_C_PRI, scheduler_cfg.pri);
++ val |= FIELD_PREP(PPE_L1_FLOW_MAP_TBL_E_PRI, scheduler_cfg.pri);
++ val |= FIELD_PREP(PPE_L1_FLOW_MAP_TBL_C_NODE_WT, scheduler_cfg.drr_node_wt);
++ val |= FIELD_PREP(PPE_L1_FLOW_MAP_TBL_E_NODE_WT, scheduler_cfg.drr_node_wt);
++ reg = PPE_L1_FLOW_MAP_TBL_ADDR + node_id * PPE_L1_FLOW_MAP_TBL_INC;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(PPE_L1_C_FLOW_CFG_TBL_NODE_ID, scheduler_cfg.drr_node_id);
++ val |= FIELD_PREP(PPE_L1_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT, scheduler_cfg.unit_is_packet);
++ reg = PPE_L1_C_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg.flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg.pri) *
++ PPE_L1_C_FLOW_CFG_TBL_INC;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(PPE_L1_E_FLOW_CFG_TBL_NODE_ID, scheduler_cfg.drr_node_id);
++ val |= FIELD_PREP(PPE_L1_E_FLOW_CFG_TBL_NODE_CREDIT_UNIT, scheduler_cfg.unit_is_packet);
++ reg = PPE_L1_E_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg.flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg.pri) *
++ PPE_L1_E_FLOW_CFG_TBL_INC;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(PPE_L1_FLOW_PORT_MAP_TBL_PORT_NUM, port);
++ reg = PPE_L1_FLOW_PORT_MAP_TBL_ADDR + node_id * PPE_L1_FLOW_PORT_MAP_TBL_INC;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ reg = PPE_L1_COMP_CFG_TBL_ADDR + node_id * PPE_L1_COMP_CFG_TBL_INC;
++ val = FIELD_PREP(PPE_L1_COMP_CFG_TBL_NODE_METER_LEN, scheduler_cfg.frame_mode);
++
++ return regmap_update_bits(ppe_dev->regmap, reg, PPE_L1_COMP_CFG_TBL_NODE_METER_LEN, val);
++}
++
++/**
++ * ppe_queue_scheduler_set - Configure scheduler for PPE hardware queue
++ * @ppe_dev: PPE device
++ * @node_id: PPE queue ID or flow ID
++ * @flow_level: Flow level scheduler or queue level scheduler
++ * @port: PPE port ID set scheduler configuration
++ * @scheduler_cfg: PPE scheduler configuration
++ *
++ * PPE scheduler configuration supports queue level and flow level on
++ * the PPE egress port.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
++ int node_id, bool flow_level, int port,
++ struct ppe_scheduler_cfg scheduler_cfg)
++{
++ if (flow_level)
++ return ppe_scheduler_l1_queue_map_set(ppe_dev, node_id,
++ port, scheduler_cfg);
++
++ return ppe_scheduler_l0_queue_map_set(ppe_dev, node_id,
++ port, scheduler_cfg);
++}
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -358,6 +1013,133 @@ qm_config_fail:
+ return ret;
+ }
+
++static int ppe_node_scheduler_config(struct ppe_device *ppe_dev,
++ const struct ppe_scheduler_port_config config)
++{
++ struct ppe_scheduler_cfg sch_cfg;
++ int ret, i;
++
++ for (i = 0; i < config.loop_num; i++) {
++ if (!config.pri_max) {
++ /* Round robin scheduler without priority. */
++ sch_cfg.flow_id = config.flow_id;
++ sch_cfg.pri = 0;
++ sch_cfg.drr_node_id = config.drr_node_id;
++ } else {
++ sch_cfg.flow_id = config.flow_id + (i / config.pri_max);
++ sch_cfg.pri = i % config.pri_max;
++ sch_cfg.drr_node_id = config.drr_node_id + i;
++ }
++
++ /* Scheduler weight, must be more than 0. */
++ sch_cfg.drr_node_wt = 1;
++ /* Byte based to be scheduled. */
++ sch_cfg.unit_is_packet = false;
++ /* Frame + CRC calculated. */
++ sch_cfg.frame_mode = PPE_SCH_WITH_FRAME_CRC;
++
++ ret = ppe_queue_scheduler_set(ppe_dev, config.node_id + i,
++ config.flow_level,
++ config.port,
++ sch_cfg);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++/* Initialize scheduler settings for PPE buffer utilization and dispatching
++ * packet on PPE queue.
++ */
++static int ppe_config_scheduler(struct ppe_device *ppe_dev)
++{
++ const struct ppe_scheduler_port_config *port_cfg;
++ const struct ppe_scheduler_qm_config *qm_cfg;
++ const struct ppe_scheduler_bm_config *bm_cfg;
++ int ret, i, count;
++ u32 val, reg;
++
++ count = ARRAY_SIZE(ipq9574_ppe_sch_bm_config);
++ bm_cfg = ipq9574_ppe_sch_bm_config;
++
++ /* Configure the depth of BM scheduler entries. */
++ val = FIELD_PREP(PPE_BM_SCH_CTRL_SCH_DEPTH, count);
++ val |= FIELD_PREP(PPE_BM_SCH_CTRL_SCH_OFFSET, 0);
++ val |= FIELD_PREP(PPE_BM_SCH_CTRL_SCH_EN, 1);
++
++ ret = regmap_write(ppe_dev->regmap, PPE_BM_SCH_CTRL_ADDR, val);
++ if (ret)
++ goto sch_config_fail;
++
++ /* Configure each BM scheduler entry with the valid ingress port and
++ * egress port, the second port takes effect when the specified port
++ * is in the inactive state.
++ */
++ for (i = 0; i < count; i++) {
++ val = FIELD_PREP(PPE_BM_SCH_CFG_TBL_VALID, bm_cfg[i].valid);
++ val |= FIELD_PREP(PPE_BM_SCH_CFG_TBL_DIR, bm_cfg[i].is_egress);
++ val |= FIELD_PREP(PPE_BM_SCH_CFG_TBL_PORT_NUM, bm_cfg[i].port);
++ val |= FIELD_PREP(PPE_BM_SCH_CFG_TBL_SECOND_PORT_VALID, bm_cfg[i].second_valid);
++ val |= FIELD_PREP(PPE_BM_SCH_CFG_TBL_SECOND_PORT, bm_cfg[i].second_port);
++
++ reg = PPE_BM_SCH_CFG_TBL_ADDR + i * PPE_BM_SCH_CFG_TBL_INC;
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ goto sch_config_fail;
++ }
++
++ count = ARRAY_SIZE(ipq9574_ppe_sch_qm_config);
++ qm_cfg = ipq9574_ppe_sch_qm_config;
++
++ /* Configure the depth of QM scheduler entries. */
++ val = FIELD_PREP(PPE_PSCH_SCH_DEPTH_CFG_SCH_DEPTH, count);
++ ret = regmap_write(ppe_dev->regmap, PPE_PSCH_SCH_DEPTH_CFG_ADDR, val);
++ if (ret)
++ goto sch_config_fail;
++
++ /* Configure each QM scheduler entry with enqueue port and dequeue
++ * port, the second port takes effect when the specified dequeue
++ * port is in the inactive port.
++ */
++ for (i = 0; i < count; i++) {
++ val = FIELD_PREP(PPE_PSCH_SCH_CFG_TBL_ENS_PORT_BITMAP,
++ qm_cfg[i].ensch_port_bmp);
++ val |= FIELD_PREP(PPE_PSCH_SCH_CFG_TBL_ENS_PORT,
++ qm_cfg[i].ensch_port);
++ val |= FIELD_PREP(PPE_PSCH_SCH_CFG_TBL_DES_PORT,
++ qm_cfg[i].desch_port);
++ val |= FIELD_PREP(PPE_PSCH_SCH_CFG_TBL_DES_SECOND_PORT_EN,
++ qm_cfg[i].desch_second_valid);
++ val |= FIELD_PREP(PPE_PSCH_SCH_CFG_TBL_DES_SECOND_PORT,
++ qm_cfg[i].desch_second_port);
++
++ reg = PPE_PSCH_SCH_CFG_TBL_ADDR + i * PPE_PSCH_SCH_CFG_TBL_INC;
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ goto sch_config_fail;
++ }
++
++ count = ARRAY_SIZE(ppe_port_sch_config);
++ port_cfg = ppe_port_sch_config;
++
++ /* Configure scheduler per PPE queue or flow. */
++ for (i = 0; i < count; i++) {
++ if (port_cfg[i].port >= ppe_dev->num_ports)
++ break;
++
++ ret = ppe_node_scheduler_config(ppe_dev, port_cfg[i]);
++ if (ret)
++ goto sch_config_fail;
++ }
++
++ return 0;
++
++sch_config_fail:
++ dev_err(ppe_dev->dev, "PPE scheduler arbitration config error %d\n", ret);
++ return ret;
++};
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -366,5 +1148,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_config_qm(ppe_dev);
++ ret = ppe_config_qm(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_config_scheduler(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -8,5 +8,42 @@
+
+ #include "ppe.h"
+
++/**
++ * enum ppe_scheduler_frame_mode - PPE scheduler frame mode.
++ * @PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC: The scheduled frame includes IPG,
++ * preamble, Ethernet packet and CRC.
++ * @PPE_SCH_WITH_FRAME_CRC: The scheduled frame includes Ethernet frame and CRC
++ * excluding IPG and preamble.
++ * @PPE_SCH_WITH_L3_PAYLOAD: The scheduled frame includes layer 3 packet data.
++ */
++enum ppe_scheduler_frame_mode {
++ PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC = 0,
++ PPE_SCH_WITH_FRAME_CRC = 1,
++ PPE_SCH_WITH_L3_PAYLOAD = 2,
++};
++
++/**
++ * struct ppe_scheduler_cfg - PPE scheduler configuration.
++ * @flow_id: PPE flow ID.
++ * @pri: Scheduler priority.
++ * @drr_node_id: Node ID for scheduled traffic.
++ * @drr_node_wt: Weight for scheduled traffic.
++ * @unit_is_packet: Packet based or byte based unit for scheduled traffic.
++ * @frame_mode: Packet mode to be scheduled.
++ *
++ * PPE scheduler supports commit rate and exceed rate configurations.
++ */
++struct ppe_scheduler_cfg {
++ int flow_id;
++ int pri;
++ int drr_node_id;
++ int drr_node_wt;
++ bool unit_is_packet;
++ enum ppe_scheduler_frame_mode frame_mode;
++};
++
+ int ppe_hw_config(struct ppe_device *ppe_dev);
++int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
++ int node_id, bool flow_level, int port,
++ struct ppe_scheduler_cfg scheduler_cfg);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -9,16 +9,113 @@
+
+ #include <linux/bitfield.h>
+
++/* PPE scheduler configurations for buffer manager block. */
++#define PPE_BM_SCH_CTRL_ADDR 0xb000
++#define PPE_BM_SCH_CTRL_INC 4
++#define PPE_BM_SCH_CTRL_SCH_DEPTH GENMASK(7, 0)
++#define PPE_BM_SCH_CTRL_SCH_OFFSET GENMASK(14, 8)
++#define PPE_BM_SCH_CTRL_SCH_EN BIT(31)
++
++#define PPE_BM_SCH_CFG_TBL_ADDR 0xc000
++#define PPE_BM_SCH_CFG_TBL_ENTRIES 128
++#define PPE_BM_SCH_CFG_TBL_INC 0x10
++#define PPE_BM_SCH_CFG_TBL_PORT_NUM GENMASK(3, 0)
++#define PPE_BM_SCH_CFG_TBL_DIR BIT(4)
++#define PPE_BM_SCH_CFG_TBL_VALID BIT(5)
++#define PPE_BM_SCH_CFG_TBL_SECOND_PORT_VALID BIT(6)
++#define PPE_BM_SCH_CFG_TBL_SECOND_PORT GENMASK(11, 8)
++
+ /* PPE queue counters enable/disable control. */
+ #define PPE_EG_BRIDGE_CONFIG_ADDR 0x20044
+ #define PPE_EG_BRIDGE_CONFIG_QUEUE_CNT_EN BIT(2)
+
++/* Port scheduler global config. */
++#define PPE_PSCH_SCH_DEPTH_CFG_ADDR 0x400000
++#define PPE_PSCH_SCH_DEPTH_CFG_INC 4
++#define PPE_PSCH_SCH_DEPTH_CFG_SCH_DEPTH GENMASK(7, 0)
++
++/* PPE queue level scheduler configurations. */
++#define PPE_L0_FLOW_MAP_TBL_ADDR 0x402000
++#define PPE_L0_FLOW_MAP_TBL_ENTRIES 300
++#define PPE_L0_FLOW_MAP_TBL_INC 0x10
++#define PPE_L0_FLOW_MAP_TBL_FLOW_ID GENMASK(5, 0)
++#define PPE_L0_FLOW_MAP_TBL_C_PRI GENMASK(8, 6)
++#define PPE_L0_FLOW_MAP_TBL_E_PRI GENMASK(11, 9)
++#define PPE_L0_FLOW_MAP_TBL_C_NODE_WT GENMASK(21, 12)
++#define PPE_L0_FLOW_MAP_TBL_E_NODE_WT GENMASK(31, 22)
++
++#define PPE_L0_C_FLOW_CFG_TBL_ADDR 0x404000
++#define PPE_L0_C_FLOW_CFG_TBL_ENTRIES 512
++#define PPE_L0_C_FLOW_CFG_TBL_INC 0x10
++#define PPE_L0_C_FLOW_CFG_TBL_NODE_ID GENMASK(7, 0)
++#define PPE_L0_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(8)
++
++#define PPE_L0_E_FLOW_CFG_TBL_ADDR 0x406000
++#define PPE_L0_E_FLOW_CFG_TBL_ENTRIES 512
++#define PPE_L0_E_FLOW_CFG_TBL_INC 0x10
++#define PPE_L0_E_FLOW_CFG_TBL_NODE_ID GENMASK(7, 0)
++#define PPE_L0_E_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(8)
++
++#define PPE_L0_FLOW_PORT_MAP_TBL_ADDR 0x408000
++#define PPE_L0_FLOW_PORT_MAP_TBL_ENTRIES 300
++#define PPE_L0_FLOW_PORT_MAP_TBL_INC 0x10
++#define PPE_L0_FLOW_PORT_MAP_TBL_PORT_NUM GENMASK(3, 0)
++
++#define PPE_L0_COMP_CFG_TBL_ADDR 0x428000
++#define PPE_L0_COMP_CFG_TBL_ENTRIES 300
++#define PPE_L0_COMP_CFG_TBL_INC 0x10
++#define PPE_L0_COMP_CFG_TBL_SHAPER_METER_LEN GENMASK(1, 0)
++#define PPE_L0_COMP_CFG_TBL_NODE_METER_LEN GENMASK(3, 2)
++
+ /* Table addresses for per-queue dequeue setting. */
+ #define PPE_DEQ_OPR_TBL_ADDR 0x430000
+ #define PPE_DEQ_OPR_TBL_ENTRIES 300
+ #define PPE_DEQ_OPR_TBL_INC 0x10
+ #define PPE_DEQ_OPR_TBL_DEQ_DISABLE BIT(0)
+
++/* PPE flow level scheduler configurations. */
++#define PPE_L1_FLOW_MAP_TBL_ADDR 0x440000
++#define PPE_L1_FLOW_MAP_TBL_ENTRIES 64
++#define PPE_L1_FLOW_MAP_TBL_INC 0x10
++#define PPE_L1_FLOW_MAP_TBL_FLOW_ID GENMASK(3, 0)
++#define PPE_L1_FLOW_MAP_TBL_C_PRI GENMASK(6, 4)
++#define PPE_L1_FLOW_MAP_TBL_E_PRI GENMASK(9, 7)
++#define PPE_L1_FLOW_MAP_TBL_C_NODE_WT GENMASK(19, 10)
++#define PPE_L1_FLOW_MAP_TBL_E_NODE_WT GENMASK(29, 20)
++
++#define PPE_L1_C_FLOW_CFG_TBL_ADDR 0x442000
++#define PPE_L1_C_FLOW_CFG_TBL_ENTRIES 64
++#define PPE_L1_C_FLOW_CFG_TBL_INC 0x10
++#define PPE_L1_C_FLOW_CFG_TBL_NODE_ID GENMASK(5, 0)
++#define PPE_L1_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(6)
++
++#define PPE_L1_E_FLOW_CFG_TBL_ADDR 0x444000
++#define PPE_L1_E_FLOW_CFG_TBL_ENTRIES 64
++#define PPE_L1_E_FLOW_CFG_TBL_INC 0x10
++#define PPE_L1_E_FLOW_CFG_TBL_NODE_ID GENMASK(5, 0)
++#define PPE_L1_E_FLOW_CFG_TBL_NODE_CREDIT_UNIT BIT(6)
++
++#define PPE_L1_FLOW_PORT_MAP_TBL_ADDR 0x446000
++#define PPE_L1_FLOW_PORT_MAP_TBL_ENTRIES 64
++#define PPE_L1_FLOW_PORT_MAP_TBL_INC 0x10
++#define PPE_L1_FLOW_PORT_MAP_TBL_PORT_NUM GENMASK(3, 0)
++
++#define PPE_L1_COMP_CFG_TBL_ADDR 0x46a000
++#define PPE_L1_COMP_CFG_TBL_ENTRIES 64
++#define PPE_L1_COMP_CFG_TBL_INC 0x10
++#define PPE_L1_COMP_CFG_TBL_SHAPER_METER_LEN GENMASK(1, 0)
++#define PPE_L1_COMP_CFG_TBL_NODE_METER_LEN GENMASK(3, 2)
++
++/* PPE port scheduler configurations for egress. */
++#define PPE_PSCH_SCH_CFG_TBL_ADDR 0x47a000
++#define PPE_PSCH_SCH_CFG_TBL_ENTRIES 128
++#define PPE_PSCH_SCH_CFG_TBL_INC 0x10
++#define PPE_PSCH_SCH_CFG_TBL_DES_PORT GENMASK(3, 0)
++#define PPE_PSCH_SCH_CFG_TBL_ENS_PORT GENMASK(7, 4)
++#define PPE_PSCH_SCH_CFG_TBL_ENS_PORT_BITMAP GENMASK(15, 8)
++#define PPE_PSCH_SCH_CFG_TBL_DES_SECOND_PORT_EN BIT(16)
++#define PPE_PSCH_SCH_CFG_TBL_DES_SECOND_PORT GENMASK(20, 17)
++
+ /* There are 15 BM ports and 4 BM groups supported by PPE.
+ * BM port (0-7) is for EDMA port 0, BM port (8-13) is for
+ * PPE physical port 1-6 and BM port 14 is for EIP port.
--- /dev/null
+From 63874f7c2e46f192e43e6214d66236372e36396c Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:41 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE queue settings
+
+Configure unicast and multicast hardware queues for the PPE
+ports to enable packet forwarding between the ports.
+
+Each PPE port is assigned with a range of queues. The queue ID
+selection for a packet is decided by the queue base and queue
+offset that is configured based on the internal priority and
+the RSS hash value of the packet.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 356 +++++++++++++++++-
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 63 ++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 21 ++
+ 3 files changed, 439 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -128,6 +128,34 @@ struct ppe_scheduler_port_config {
+ unsigned int drr_node_id;
+ };
+
++/**
++ * struct ppe_port_schedule_resource - PPE port scheduler resource.
++ * @ucastq_start: Unicast queue start ID.
++ * @ucastq_end: Unicast queue end ID.
++ * @mcastq_start: Multicast queue start ID.
++ * @mcastq_end: Multicast queue end ID.
++ * @flow_id_start: Flow start ID.
++ * @flow_id_end: Flow end ID.
++ * @l0node_start: Scheduler node start ID for queue level.
++ * @l0node_end: Scheduler node end ID for queue level.
++ * @l1node_start: Scheduler node start ID for flow level.
++ * @l1node_end: Scheduler node end ID for flow level.
++ *
++ * PPE scheduler resource allocated among the PPE ports.
++ */
++struct ppe_port_schedule_resource {
++ unsigned int ucastq_start;
++ unsigned int ucastq_end;
++ unsigned int mcastq_start;
++ unsigned int mcastq_end;
++ unsigned int flow_id_start;
++ unsigned int flow_id_end;
++ unsigned int l0node_start;
++ unsigned int l0node_end;
++ unsigned int l1node_start;
++ unsigned int l1node_end;
++};
++
+ /* Assign the share buffer number 1550 to group 0 by default. */
+ static const int ipq9574_ppe_bm_group_config = 1550;
+
+@@ -676,6 +704,111 @@ static const struct ppe_scheduler_port_c
+ },
+ };
+
++/* The scheduler resource is applied to each PPE port, The resource
++ * includes the unicast & multicast queues, flow nodes and DRR nodes.
++ */
++static const struct ppe_port_schedule_resource ppe_scheduler_res[] = {
++ { .ucastq_start = 0,
++ .ucastq_end = 63,
++ .mcastq_start = 256,
++ .mcastq_end = 271,
++ .flow_id_start = 0,
++ .flow_id_end = 0,
++ .l0node_start = 0,
++ .l0node_end = 7,
++ .l1node_start = 0,
++ .l1node_end = 0,
++ },
++ { .ucastq_start = 144,
++ .ucastq_end = 159,
++ .mcastq_start = 272,
++ .mcastq_end = 275,
++ .flow_id_start = 36,
++ .flow_id_end = 39,
++ .l0node_start = 48,
++ .l0node_end = 63,
++ .l1node_start = 8,
++ .l1node_end = 11,
++ },
++ { .ucastq_start = 160,
++ .ucastq_end = 175,
++ .mcastq_start = 276,
++ .mcastq_end = 279,
++ .flow_id_start = 40,
++ .flow_id_end = 43,
++ .l0node_start = 64,
++ .l0node_end = 79,
++ .l1node_start = 12,
++ .l1node_end = 15,
++ },
++ { .ucastq_start = 176,
++ .ucastq_end = 191,
++ .mcastq_start = 280,
++ .mcastq_end = 283,
++ .flow_id_start = 44,
++ .flow_id_end = 47,
++ .l0node_start = 80,
++ .l0node_end = 95,
++ .l1node_start = 16,
++ .l1node_end = 19,
++ },
++ { .ucastq_start = 192,
++ .ucastq_end = 207,
++ .mcastq_start = 284,
++ .mcastq_end = 287,
++ .flow_id_start = 48,
++ .flow_id_end = 51,
++ .l0node_start = 96,
++ .l0node_end = 111,
++ .l1node_start = 20,
++ .l1node_end = 23,
++ },
++ { .ucastq_start = 208,
++ .ucastq_end = 223,
++ .mcastq_start = 288,
++ .mcastq_end = 291,
++ .flow_id_start = 52,
++ .flow_id_end = 55,
++ .l0node_start = 112,
++ .l0node_end = 127,
++ .l1node_start = 24,
++ .l1node_end = 27,
++ },
++ { .ucastq_start = 224,
++ .ucastq_end = 239,
++ .mcastq_start = 292,
++ .mcastq_end = 295,
++ .flow_id_start = 56,
++ .flow_id_end = 59,
++ .l0node_start = 128,
++ .l0node_end = 143,
++ .l1node_start = 28,
++ .l1node_end = 31,
++ },
++ { .ucastq_start = 240,
++ .ucastq_end = 255,
++ .mcastq_start = 296,
++ .mcastq_end = 299,
++ .flow_id_start = 60,
++ .flow_id_end = 63,
++ .l0node_start = 144,
++ .l0node_end = 159,
++ .l1node_start = 32,
++ .l1node_end = 35,
++ },
++ { .ucastq_start = 64,
++ .ucastq_end = 143,
++ .mcastq_start = 0,
++ .mcastq_end = 0,
++ .flow_id_start = 1,
++ .flow_id_end = 35,
++ .l0node_start = 8,
++ .l0node_end = 47,
++ .l1node_start = 1,
++ .l1node_end = 7,
++ },
++};
++
+ /* Set the PPE queue level scheduler configuration. */
+ static int ppe_scheduler_l0_queue_map_set(struct ppe_device *ppe_dev,
+ int node_id, int port,
+@@ -807,6 +940,149 @@ int ppe_queue_scheduler_set(struct ppe_d
+ port, scheduler_cfg);
+ }
+
++/**
++ * ppe_queue_ucast_base_set - Set PPE unicast queue base ID and profile ID
++ * @ppe_dev: PPE device
++ * @queue_dst: PPE queue destination configuration
++ * @queue_base: PPE queue base ID
++ * @profile_id: Profile ID
++ *
++ * The PPE unicast queue base ID and profile ID are configured based on the
++ * destination port information that can be service code or CPU code or the
++ * destination port.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_queue_ucast_base_set(struct ppe_device *ppe_dev,
++ struct ppe_queue_ucast_dest queue_dst,
++ int queue_base, int profile_id)
++{
++ int index, profile_size;
++ u32 val, reg;
++
++ profile_size = queue_dst.src_profile << 8;
++ if (queue_dst.service_code_en)
++ index = PPE_QUEUE_BASE_SERVICE_CODE + profile_size +
++ queue_dst.service_code;
++ else if (queue_dst.cpu_code_en)
++ index = PPE_QUEUE_BASE_CPU_CODE + profile_size +
++ queue_dst.cpu_code;
++ else
++ index = profile_size + queue_dst.dest_port;
++
++ val = FIELD_PREP(PPE_UCAST_QUEUE_MAP_TBL_PROFILE_ID, profile_id);
++ val |= FIELD_PREP(PPE_UCAST_QUEUE_MAP_TBL_QUEUE_ID, queue_base);
++ reg = PPE_UCAST_QUEUE_MAP_TBL_ADDR + index * PPE_UCAST_QUEUE_MAP_TBL_INC;
++
++ return regmap_write(ppe_dev->regmap, reg, val);
++}
++
++/**
++ * ppe_queue_ucast_offset_pri_set - Set PPE unicast queue offset based on priority
++ * @ppe_dev: PPE device
++ * @profile_id: Profile ID
++ * @priority: PPE internal priority to be used to set queue offset
++ * @queue_offset: Queue offset used for calculating the destination queue ID
++ *
++ * The PPE unicast queue offset is configured based on the PPE
++ * internal priority.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_queue_ucast_offset_pri_set(struct ppe_device *ppe_dev,
++ int profile_id,
++ int priority,
++ int queue_offset)
++{
++ u32 val, reg;
++ int index;
++
++ index = (profile_id << 4) + priority;
++ val = FIELD_PREP(PPE_UCAST_PRIORITY_MAP_TBL_CLASS, queue_offset);
++ reg = PPE_UCAST_PRIORITY_MAP_TBL_ADDR + index * PPE_UCAST_PRIORITY_MAP_TBL_INC;
++
++ return regmap_write(ppe_dev->regmap, reg, val);
++}
++
++/**
++ * ppe_queue_ucast_offset_hash_set - Set PPE unicast queue offset based on hash
++ * @ppe_dev: PPE device
++ * @profile_id: Profile ID
++ * @rss_hash: Packet hash value to be used to set queue offset
++ * @queue_offset: Queue offset used for calculating the destination queue ID
++ *
++ * The PPE unicast queue offset is configured based on the RSS hash value.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_queue_ucast_offset_hash_set(struct ppe_device *ppe_dev,
++ int profile_id,
++ int rss_hash,
++ int queue_offset)
++{
++ u32 val, reg;
++ int index;
++
++ index = (profile_id << 8) + rss_hash;
++ val = FIELD_PREP(PPE_UCAST_HASH_MAP_TBL_HASH, queue_offset);
++ reg = PPE_UCAST_HASH_MAP_TBL_ADDR + index * PPE_UCAST_HASH_MAP_TBL_INC;
++
++ return regmap_write(ppe_dev->regmap, reg, val);
++}
++
++/**
++ * ppe_port_resource_get - Get PPE resource per port
++ * @ppe_dev: PPE device
++ * @port: PPE port
++ * @type: Resource type
++ * @res_start: Resource start ID returned
++ * @res_end: Resource end ID returned
++ *
++ * PPE resource is assigned per PPE port, which is acquired for QoS scheduler.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_port_resource_get(struct ppe_device *ppe_dev, int port,
++ enum ppe_resource_type type,
++ int *res_start, int *res_end)
++{
++ struct ppe_port_schedule_resource res;
++
++ /* The reserved resource with the maximum port ID of PPE is
++ * also allowed to be acquired.
++ */
++ if (port > ppe_dev->num_ports)
++ return -EINVAL;
++
++ res = ppe_scheduler_res[port];
++ switch (type) {
++ case PPE_RES_UCAST:
++ *res_start = res.ucastq_start;
++ *res_end = res.ucastq_end;
++ break;
++ case PPE_RES_MCAST:
++ *res_start = res.mcastq_start;
++ *res_end = res.mcastq_end;
++ break;
++ case PPE_RES_FLOW_ID:
++ *res_start = res.flow_id_start;
++ *res_end = res.flow_id_end;
++ break;
++ case PPE_RES_L0_NODE:
++ *res_start = res.l0node_start;
++ *res_end = res.l0node_end;
++ break;
++ case PPE_RES_L1_NODE:
++ *res_start = res.l1node_start;
++ *res_end = res.l1node_end;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -1140,6 +1416,80 @@ sch_config_fail:
+ return ret;
+ };
+
++/* Configure PPE queue destination of each PPE port. */
++static int ppe_queue_dest_init(struct ppe_device *ppe_dev)
++{
++ int ret, port_id, index, q_base, q_offset, res_start, res_end, pri_max;
++ struct ppe_queue_ucast_dest queue_dst;
++
++ for (port_id = 0; port_id < ppe_dev->num_ports; port_id++) {
++ memset(&queue_dst, 0, sizeof(queue_dst));
++
++ ret = ppe_port_resource_get(ppe_dev, port_id, PPE_RES_UCAST,
++ &res_start, &res_end);
++ if (ret)
++ return ret;
++
++ q_base = res_start;
++ queue_dst.dest_port = port_id;
++
++ /* Configure queue base ID and profile ID that is same as
++ * physical port ID.
++ */
++ ret = ppe_queue_ucast_base_set(ppe_dev, queue_dst,
++ q_base, port_id);
++ if (ret)
++ return ret;
++
++ /* Queue priority range supported by each PPE port */
++ ret = ppe_port_resource_get(ppe_dev, port_id, PPE_RES_L0_NODE,
++ &res_start, &res_end);
++ if (ret)
++ return ret;
++
++ pri_max = res_end - res_start;
++
++ /* Redirect ARP reply packet with the max priority on CPU port,
++ * which keeps the ARP reply directed to CPU (CPU code is 101)
++ * with highest priority queue of EDMA.
++ */
++ if (port_id == 0) {
++ memset(&queue_dst, 0, sizeof(queue_dst));
++
++ queue_dst.cpu_code_en = true;
++ queue_dst.cpu_code = 101;
++ ret = ppe_queue_ucast_base_set(ppe_dev, queue_dst,
++ q_base + pri_max,
++ 0);
++ if (ret)
++ return ret;
++ }
++
++ /* Initialize the queue offset of internal priority. */
++ for (index = 0; index < PPE_QUEUE_INTER_PRI_NUM; index++) {
++ q_offset = index > pri_max ? pri_max : index;
++
++ ret = ppe_queue_ucast_offset_pri_set(ppe_dev, port_id,
++ index, q_offset);
++ if (ret)
++ return ret;
++ }
++
++ /* Initialize the queue offset of RSS hash as 0 to avoid the
++ * random hardware value that will lead to the unexpected
++ * destination queue generated.
++ */
++ for (index = 0; index < PPE_QUEUE_HASH_NUM; index++) {
++ ret = ppe_queue_ucast_offset_hash_set(ppe_dev, port_id,
++ index, 0);
++ if (ret)
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -1152,5 +1502,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_config_scheduler(ppe_dev);
++ ret = ppe_config_scheduler(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_queue_dest_init(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -8,6 +8,16 @@
+
+ #include "ppe.h"
+
++/* There are different table index ranges for configuring queue base ID of
++ * the destination port, CPU code and service code.
++ */
++#define PPE_QUEUE_BASE_DEST_PORT 0
++#define PPE_QUEUE_BASE_CPU_CODE 1024
++#define PPE_QUEUE_BASE_SERVICE_CODE 2048
++
++#define PPE_QUEUE_INTER_PRI_NUM 16
++#define PPE_QUEUE_HASH_NUM 256
++
+ /**
+ * enum ppe_scheduler_frame_mode - PPE scheduler frame mode.
+ * @PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC: The scheduled frame includes IPG,
+@@ -42,8 +52,61 @@ struct ppe_scheduler_cfg {
+ enum ppe_scheduler_frame_mode frame_mode;
+ };
+
++/**
++ * enum ppe_resource_type - PPE resource type.
++ * @PPE_RES_UCAST: Unicast queue resource.
++ * @PPE_RES_MCAST: Multicast queue resource.
++ * @PPE_RES_L0_NODE: Level 0 for queue based node resource.
++ * @PPE_RES_L1_NODE: Level 1 for flow based node resource.
++ * @PPE_RES_FLOW_ID: Flow based node resource.
++ */
++enum ppe_resource_type {
++ PPE_RES_UCAST,
++ PPE_RES_MCAST,
++ PPE_RES_L0_NODE,
++ PPE_RES_L1_NODE,
++ PPE_RES_FLOW_ID,
++};
++
++/**
++ * struct ppe_queue_ucast_dest - PPE unicast queue destination.
++ * @src_profile: Source profile.
++ * @service_code_en: Enable service code to map the queue base ID.
++ * @service_code: Service code.
++ * @cpu_code_en: Enable CPU code to map the queue base ID.
++ * @cpu_code: CPU code.
++ * @dest_port: destination port.
++ *
++ * PPE egress queue ID is decided by the service code if enabled, otherwise
++ * by the CPU code if enabled, or by destination port if both service code
++ * and CPU code are disabled.
++ */
++struct ppe_queue_ucast_dest {
++ int src_profile;
++ bool service_code_en;
++ int service_code;
++ bool cpu_code_en;
++ int cpu_code;
++ int dest_port;
++};
++
+ int ppe_hw_config(struct ppe_device *ppe_dev);
+ int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
+ int node_id, bool flow_level, int port,
+ struct ppe_scheduler_cfg scheduler_cfg);
++int ppe_queue_ucast_base_set(struct ppe_device *ppe_dev,
++ struct ppe_queue_ucast_dest queue_dst,
++ int queue_base,
++ int profile_id);
++int ppe_queue_ucast_offset_pri_set(struct ppe_device *ppe_dev,
++ int profile_id,
++ int priority,
++ int queue_offset);
++int ppe_queue_ucast_offset_hash_set(struct ppe_device *ppe_dev,
++ int profile_id,
++ int rss_hash,
++ int queue_offset);
++int ppe_port_resource_get(struct ppe_device *ppe_dev, int port,
++ enum ppe_resource_type type,
++ int *res_start, int *res_end);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -164,6 +164,27 @@
+ #define PPE_BM_PORT_FC_SET_PRE_ALLOC(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_BM_PORT_FC_W1_PRE_ALLOC)
+
++/* The queue base configurations based on destination port,
++ * service code or CPU code.
++ */
++#define PPE_UCAST_QUEUE_MAP_TBL_ADDR 0x810000
++#define PPE_UCAST_QUEUE_MAP_TBL_ENTRIES 3072
++#define PPE_UCAST_QUEUE_MAP_TBL_INC 0x10
++#define PPE_UCAST_QUEUE_MAP_TBL_PROFILE_ID GENMASK(3, 0)
++#define PPE_UCAST_QUEUE_MAP_TBL_QUEUE_ID GENMASK(11, 4)
++
++/* The queue offset configurations based on RSS hash value. */
++#define PPE_UCAST_HASH_MAP_TBL_ADDR 0x830000
++#define PPE_UCAST_HASH_MAP_TBL_ENTRIES 4096
++#define PPE_UCAST_HASH_MAP_TBL_INC 0x10
++#define PPE_UCAST_HASH_MAP_TBL_HASH GENMASK(7, 0)
++
++/* The queue offset configurations based on PPE internal priority. */
++#define PPE_UCAST_PRIORITY_MAP_TBL_ADDR 0x842000
++#define PPE_UCAST_PRIORITY_MAP_TBL_ENTRIES 256
++#define PPE_UCAST_PRIORITY_MAP_TBL_INC 0x10
++#define PPE_UCAST_PRIORITY_MAP_TBL_CLASS GENMASK(3, 0)
++
+ /* PPE unicast queue (0-255) configurations. */
+ #define PPE_AC_UNICAST_QUEUE_CFG_TBL_ADDR 0x848000
+ #define PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES 256
--- /dev/null
+From 4147ce0d95816bded5c5e6cb276b1aa9f2620045 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:42 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE service code settings
+
+PPE service code is a special code (0-255) that is defined by PPE for
+PPE's packet processing stages, as per the network functions required
+for the packet.
+
+For packet being sent out by ARM cores on Ethernet ports, The service
+code 1 is used as the default service code. This service code is used
+to bypass most of packet processing stages of the PPE before the packet
+transmitted out PPE port, since the software network stack has already
+processed the packet.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 95 +++++++++++-
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 145 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 53 +++++++
+ 3 files changed, 292 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -8,6 +8,7 @@
+ */
+
+ #include <linux/bitfield.h>
++#include <linux/bitmap.h>
+ #include <linux/bits.h>
+ #include <linux/device.h>
+ #include <linux/regmap.h>
+@@ -1083,6 +1084,75 @@ int ppe_port_resource_get(struct ppe_dev
+ return 0;
+ }
+
++/**
++ * ppe_sc_config_set - Set PPE service code configuration
++ * @ppe_dev: PPE device
++ * @sc: Service ID, 0-255 supported by PPE
++ * @cfg: Service code configuration
++ *
++ * PPE service code is used by the PPE during its packet processing stages,
++ * to perform or bypass certain selected packet operations on the packet.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_sc_config_set(struct ppe_device *ppe_dev, int sc, struct ppe_sc_cfg cfg)
++{
++ u32 val, reg, servcode_val[2] = {};
++ unsigned long bitmap_value;
++ int ret;
++
++ val = FIELD_PREP(PPE_IN_L2_SERVICE_TBL_DST_PORT_ID_VALID, cfg.dest_port_valid);
++ val |= FIELD_PREP(PPE_IN_L2_SERVICE_TBL_DST_PORT_ID, cfg.dest_port);
++ val |= FIELD_PREP(PPE_IN_L2_SERVICE_TBL_DST_DIRECTION, cfg.is_src);
++
++ bitmap_value = bitmap_read(cfg.bitmaps.egress, 0, PPE_SC_BYPASS_EGRESS_SIZE);
++ val |= FIELD_PREP(PPE_IN_L2_SERVICE_TBL_DST_BYPASS_BITMAP, bitmap_value);
++ val |= FIELD_PREP(PPE_IN_L2_SERVICE_TBL_RX_CNT_EN,
++ test_bit(PPE_SC_BYPASS_COUNTER_RX, cfg.bitmaps.counter));
++ val |= FIELD_PREP(PPE_IN_L2_SERVICE_TBL_TX_CNT_EN,
++ test_bit(PPE_SC_BYPASS_COUNTER_TX, cfg.bitmaps.counter));
++ reg = PPE_IN_L2_SERVICE_TBL_ADDR + PPE_IN_L2_SERVICE_TBL_INC * sc;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++
++ bitmap_value = bitmap_read(cfg.bitmaps.ingress, 0, PPE_SC_BYPASS_INGRESS_SIZE);
++ PPE_SERVICE_SET_BYPASS_BITMAP(servcode_val, bitmap_value);
++ PPE_SERVICE_SET_RX_CNT_EN(servcode_val,
++ test_bit(PPE_SC_BYPASS_COUNTER_RX_VLAN, cfg.bitmaps.counter));
++ reg = PPE_SERVICE_TBL_ADDR + PPE_SERVICE_TBL_INC * sc;
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ servcode_val, ARRAY_SIZE(servcode_val));
++ if (ret)
++ return ret;
++
++ reg = PPE_EG_SERVICE_TBL_ADDR + PPE_EG_SERVICE_TBL_INC * sc;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ servcode_val, ARRAY_SIZE(servcode_val));
++ if (ret)
++ return ret;
++
++ PPE_EG_SERVICE_SET_NEXT_SERVCODE(servcode_val, cfg.next_service_code);
++ PPE_EG_SERVICE_SET_UPDATE_ACTION(servcode_val, cfg.eip_field_update_bitmap);
++ PPE_EG_SERVICE_SET_HW_SERVICE(servcode_val, cfg.eip_hw_service);
++ PPE_EG_SERVICE_SET_OFFSET_SEL(servcode_val, cfg.eip_offset_sel);
++ PPE_EG_SERVICE_SET_TX_CNT_EN(servcode_val,
++ test_bit(PPE_SC_BYPASS_COUNTER_TX_VLAN, cfg.bitmaps.counter));
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ servcode_val, ARRAY_SIZE(servcode_val));
++ if (ret)
++ return ret;
++
++ bitmap_value = bitmap_read(cfg.bitmaps.tunnel, 0, PPE_SC_BYPASS_TUNNEL_SIZE);
++ val = FIELD_PREP(PPE_TL_SERVICE_TBL_BYPASS_BITMAP, bitmap_value);
++ reg = PPE_TL_SERVICE_TBL_ADDR + PPE_TL_SERVICE_TBL_INC * sc;
++
++ return regmap_write(ppe_dev->regmap, reg, val);
++}
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -1490,6 +1560,25 @@ static int ppe_queue_dest_init(struct pp
+ return 0;
+ }
+
++/* Initialize the service code 1 used by CPU port. */
++static int ppe_servcode_init(struct ppe_device *ppe_dev)
++{
++ struct ppe_sc_cfg sc_cfg = {};
++
++ bitmap_zero(sc_cfg.bitmaps.counter, PPE_SC_BYPASS_COUNTER_SIZE);
++ bitmap_zero(sc_cfg.bitmaps.tunnel, PPE_SC_BYPASS_TUNNEL_SIZE);
++
++ bitmap_fill(sc_cfg.bitmaps.ingress, PPE_SC_BYPASS_INGRESS_SIZE);
++ clear_bit(PPE_SC_BYPASS_INGRESS_FAKE_MAC_HEADER, sc_cfg.bitmaps.ingress);
++ clear_bit(PPE_SC_BYPASS_INGRESS_SERVICE_CODE, sc_cfg.bitmaps.ingress);
++ clear_bit(PPE_SC_BYPASS_INGRESS_FAKE_L2_PROTO, sc_cfg.bitmaps.ingress);
++
++ bitmap_fill(sc_cfg.bitmaps.egress, PPE_SC_BYPASS_EGRESS_SIZE);
++ clear_bit(PPE_SC_BYPASS_EGRESS_ACL_POST_ROUTING_CHECK, sc_cfg.bitmaps.egress);
++
++ return ppe_sc_config_set(ppe_dev, PPE_EDMA_SC_BYPASS_ID, sc_cfg);
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -1506,5 +1595,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_queue_dest_init(ppe_dev);
++ ret = ppe_queue_dest_init(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_servcode_init(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -6,6 +6,8 @@
+ #ifndef __PPE_CONFIG_H__
+ #define __PPE_CONFIG_H__
+
++#include <linux/types.h>
++
+ #include "ppe.h"
+
+ /* There are different table index ranges for configuring queue base ID of
+@@ -18,6 +20,9 @@
+ #define PPE_QUEUE_INTER_PRI_NUM 16
+ #define PPE_QUEUE_HASH_NUM 256
+
++/* The service code is used by EDMA port to transmit packet to PPE. */
++#define PPE_EDMA_SC_BYPASS_ID 1
++
+ /**
+ * enum ppe_scheduler_frame_mode - PPE scheduler frame mode.
+ * @PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC: The scheduled frame includes IPG,
+@@ -90,6 +95,144 @@ struct ppe_queue_ucast_dest {
+ int dest_port;
+ };
+
++/* Hardware bitmaps for bypassing features of the ingress packet. */
++enum ppe_sc_ingress_type {
++ PPE_SC_BYPASS_INGRESS_VLAN_TAG_FMT_CHECK = 0,
++ PPE_SC_BYPASS_INGRESS_VLAN_MEMBER_CHECK = 1,
++ PPE_SC_BYPASS_INGRESS_VLAN_TRANSLATE = 2,
++ PPE_SC_BYPASS_INGRESS_MY_MAC_CHECK = 3,
++ PPE_SC_BYPASS_INGRESS_DIP_LOOKUP = 4,
++ PPE_SC_BYPASS_INGRESS_FLOW_LOOKUP = 5,
++ PPE_SC_BYPASS_INGRESS_FLOW_ACTION = 6,
++ PPE_SC_BYPASS_INGRESS_ACL = 7,
++ PPE_SC_BYPASS_INGRESS_FAKE_MAC_HEADER = 8,
++ PPE_SC_BYPASS_INGRESS_SERVICE_CODE = 9,
++ PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L2 = 10,
++ PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L3_IPV4 = 11,
++ PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L3_IPV6 = 12,
++ PPE_SC_BYPASS_INGRESS_WRONG_PKT_FMT_L4 = 13,
++ PPE_SC_BYPASS_INGRESS_FLOW_SERVICE_CODE = 14,
++ PPE_SC_BYPASS_INGRESS_ACL_SERVICE_CODE = 15,
++ PPE_SC_BYPASS_INGRESS_FAKE_L2_PROTO = 16,
++ PPE_SC_BYPASS_INGRESS_PPPOE_TERMINATION = 17,
++ PPE_SC_BYPASS_INGRESS_DEFAULT_VLAN = 18,
++ PPE_SC_BYPASS_INGRESS_DEFAULT_PCP = 19,
++ PPE_SC_BYPASS_INGRESS_VSI_ASSIGN = 20,
++ /* Values 21-23 are not specified by hardware. */
++ PPE_SC_BYPASS_INGRESS_VLAN_ASSIGN_FAIL = 24,
++ PPE_SC_BYPASS_INGRESS_SOURCE_GUARD = 25,
++ PPE_SC_BYPASS_INGRESS_MRU_MTU_CHECK = 26,
++ PPE_SC_BYPASS_INGRESS_FLOW_SRC_CHECK = 27,
++ PPE_SC_BYPASS_INGRESS_FLOW_QOS = 28,
++ /* This must be last as it determines the size of the BITMAP. */
++ PPE_SC_BYPASS_INGRESS_SIZE,
++};
++
++/* Hardware bitmaps for bypassing features of the egress packet. */
++enum ppe_sc_egress_type {
++ PPE_SC_BYPASS_EGRESS_VLAN_MEMBER_CHECK = 0,
++ PPE_SC_BYPASS_EGRESS_VLAN_TRANSLATE = 1,
++ PPE_SC_BYPASS_EGRESS_VLAN_TAG_FMT_CTRL = 2,
++ PPE_SC_BYPASS_EGRESS_FDB_LEARN = 3,
++ PPE_SC_BYPASS_EGRESS_FDB_REFRESH = 4,
++ PPE_SC_BYPASS_EGRESS_L2_SOURCE_SECURITY = 5,
++ PPE_SC_BYPASS_EGRESS_MANAGEMENT_FWD = 6,
++ PPE_SC_BYPASS_EGRESS_BRIDGING_FWD = 7,
++ PPE_SC_BYPASS_EGRESS_IN_STP_FLTR = 8,
++ PPE_SC_BYPASS_EGRESS_EG_STP_FLTR = 9,
++ PPE_SC_BYPASS_EGRESS_SOURCE_FLTR = 10,
++ PPE_SC_BYPASS_EGRESS_POLICER = 11,
++ PPE_SC_BYPASS_EGRESS_L2_PKT_EDIT = 12,
++ PPE_SC_BYPASS_EGRESS_L3_PKT_EDIT = 13,
++ PPE_SC_BYPASS_EGRESS_ACL_POST_ROUTING_CHECK = 14,
++ PPE_SC_BYPASS_EGRESS_PORT_ISOLATION = 15,
++ PPE_SC_BYPASS_EGRESS_PRE_ACL_QOS = 16,
++ PPE_SC_BYPASS_EGRESS_POST_ACL_QOS = 17,
++ PPE_SC_BYPASS_EGRESS_DSCP_QOS = 18,
++ PPE_SC_BYPASS_EGRESS_PCP_QOS = 19,
++ PPE_SC_BYPASS_EGRESS_PREHEADER_QOS = 20,
++ PPE_SC_BYPASS_EGRESS_FAKE_MAC_DROP = 21,
++ PPE_SC_BYPASS_EGRESS_TUNL_CONTEXT = 22,
++ PPE_SC_BYPASS_EGRESS_FLOW_POLICER = 23,
++ /* This must be last as it determines the size of the BITMAP. */
++ PPE_SC_BYPASS_EGRESS_SIZE,
++};
++
++/* Hardware bitmaps for bypassing counter of packet. */
++enum ppe_sc_counter_type {
++ PPE_SC_BYPASS_COUNTER_RX_VLAN = 0,
++ PPE_SC_BYPASS_COUNTER_RX = 1,
++ PPE_SC_BYPASS_COUNTER_TX_VLAN = 2,
++ PPE_SC_BYPASS_COUNTER_TX = 3,
++ /* This must be last as it determines the size of the BITMAP. */
++ PPE_SC_BYPASS_COUNTER_SIZE,
++};
++
++/* Hardware bitmaps for bypassing features of tunnel packet. */
++enum ppe_sc_tunnel_type {
++ PPE_SC_BYPASS_TUNNEL_SERVICE_CODE = 0,
++ PPE_SC_BYPASS_TUNNEL_TUNNEL_HANDLE = 1,
++ PPE_SC_BYPASS_TUNNEL_L3_IF_CHECK = 2,
++ PPE_SC_BYPASS_TUNNEL_VLAN_CHECK = 3,
++ PPE_SC_BYPASS_TUNNEL_DMAC_CHECK = 4,
++ PPE_SC_BYPASS_TUNNEL_UDP_CSUM_0_CHECK = 5,
++ PPE_SC_BYPASS_TUNNEL_TBL_DE_ACCE_CHECK = 6,
++ PPE_SC_BYPASS_TUNNEL_PPPOE_MC_TERM_CHECK = 7,
++ PPE_SC_BYPASS_TUNNEL_TTL_EXCEED_CHECK = 8,
++ PPE_SC_BYPASS_TUNNEL_MAP_SRC_CHECK = 9,
++ PPE_SC_BYPASS_TUNNEL_MAP_DST_CHECK = 10,
++ PPE_SC_BYPASS_TUNNEL_LPM_DST_LOOKUP = 11,
++ PPE_SC_BYPASS_TUNNEL_LPM_LOOKUP = 12,
++ PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L2 = 13,
++ PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L3_IPV4 = 14,
++ PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L3_IPV6 = 15,
++ PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_L4 = 16,
++ PPE_SC_BYPASS_TUNNEL_WRONG_PKT_FMT_TUNNEL = 17,
++ /* Values 18-19 are not specified by hardware. */
++ PPE_SC_BYPASS_TUNNEL_PRE_IPO = 20,
++ /* This must be last as it determines the size of the BITMAP. */
++ PPE_SC_BYPASS_TUNNEL_SIZE,
++};
++
++/**
++ * struct ppe_sc_bypass - PPE service bypass bitmaps
++ * @ingress: Bitmap of features that can be bypassed on the ingress packet.
++ * @egress: Bitmap of features that can be bypassed on the egress packet.
++ * @counter: Bitmap of features that can be bypassed on the counter type.
++ * @tunnel: Bitmap of features that can be bypassed on the tunnel packet.
++ */
++struct ppe_sc_bypass {
++ DECLARE_BITMAP(ingress, PPE_SC_BYPASS_INGRESS_SIZE);
++ DECLARE_BITMAP(egress, PPE_SC_BYPASS_EGRESS_SIZE);
++ DECLARE_BITMAP(counter, PPE_SC_BYPASS_COUNTER_SIZE);
++ DECLARE_BITMAP(tunnel, PPE_SC_BYPASS_TUNNEL_SIZE);
++};
++
++/**
++ * struct ppe_sc_cfg - PPE service code configuration.
++ * @dest_port_valid: Generate destination port or not.
++ * @dest_port: Destination port ID.
++ * @bitmaps: Bitmap of bypass features.
++ * @is_src: Destination port acts as source port, packet sent to CPU.
++ * @next_service_code: New service code generated.
++ * @eip_field_update_bitmap: Fields updated as actions taken for EIP.
++ * @eip_hw_service: Selected hardware functions for EIP.
++ * @eip_offset_sel: Packet offset selection, using packet's layer 4 offset
++ * or using packet's layer 3 offset for EIP.
++ *
++ * Service code is generated during the packet passing through PPE.
++ */
++struct ppe_sc_cfg {
++ bool dest_port_valid;
++ int dest_port;
++ struct ppe_sc_bypass bitmaps;
++ bool is_src;
++ int next_service_code;
++ int eip_field_update_bitmap;
++ int eip_hw_service;
++ int eip_offset_sel;
++};
++
+ int ppe_hw_config(struct ppe_device *ppe_dev);
+ int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
+ int node_id, bool flow_level, int port,
+@@ -109,4 +252,6 @@ int ppe_queue_ucast_offset_hash_set(stru
+ int ppe_port_resource_get(struct ppe_device *ppe_dev, int port,
+ enum ppe_resource_type type,
+ int *res_start, int *res_end);
++int ppe_sc_config_set(struct ppe_device *ppe_dev, int sc,
++ struct ppe_sc_cfg cfg);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -25,10 +25,63 @@
+ #define PPE_BM_SCH_CFG_TBL_SECOND_PORT_VALID BIT(6)
+ #define PPE_BM_SCH_CFG_TBL_SECOND_PORT GENMASK(11, 8)
+
++/* PPE service code configuration for the ingress direction functions,
++ * including bypass configuration for relevant PPE switch core functions
++ * such as flow entry lookup bypass.
++ */
++#define PPE_SERVICE_TBL_ADDR 0x15000
++#define PPE_SERVICE_TBL_ENTRIES 256
++#define PPE_SERVICE_TBL_INC 0x10
++#define PPE_SERVICE_W0_BYPASS_BITMAP GENMASK(31, 0)
++#define PPE_SERVICE_W1_RX_CNT_EN BIT(0)
++
++#define PPE_SERVICE_SET_BYPASS_BITMAP(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_SERVICE_W0_BYPASS_BITMAP)
++#define PPE_SERVICE_SET_RX_CNT_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_SERVICE_W1_RX_CNT_EN)
++
+ /* PPE queue counters enable/disable control. */
+ #define PPE_EG_BRIDGE_CONFIG_ADDR 0x20044
+ #define PPE_EG_BRIDGE_CONFIG_QUEUE_CNT_EN BIT(2)
+
++/* PPE service code configuration on the egress direction. */
++#define PPE_EG_SERVICE_TBL_ADDR 0x43000
++#define PPE_EG_SERVICE_TBL_ENTRIES 256
++#define PPE_EG_SERVICE_TBL_INC 0x10
++#define PPE_EG_SERVICE_W0_UPDATE_ACTION GENMASK(31, 0)
++#define PPE_EG_SERVICE_W1_NEXT_SERVCODE GENMASK(7, 0)
++#define PPE_EG_SERVICE_W1_HW_SERVICE GENMASK(13, 8)
++#define PPE_EG_SERVICE_W1_OFFSET_SEL BIT(14)
++#define PPE_EG_SERVICE_W1_TX_CNT_EN BIT(15)
++
++#define PPE_EG_SERVICE_SET_UPDATE_ACTION(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_EG_SERVICE_W0_UPDATE_ACTION)
++#define PPE_EG_SERVICE_SET_NEXT_SERVCODE(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_EG_SERVICE_W1_NEXT_SERVCODE)
++#define PPE_EG_SERVICE_SET_HW_SERVICE(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_EG_SERVICE_W1_HW_SERVICE)
++#define PPE_EG_SERVICE_SET_OFFSET_SEL(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_EG_SERVICE_W1_OFFSET_SEL)
++#define PPE_EG_SERVICE_SET_TX_CNT_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_EG_SERVICE_W1_TX_CNT_EN)
++
++/* PPE service code configuration for destination port and counter. */
++#define PPE_IN_L2_SERVICE_TBL_ADDR 0x66000
++#define PPE_IN_L2_SERVICE_TBL_ENTRIES 256
++#define PPE_IN_L2_SERVICE_TBL_INC 0x10
++#define PPE_IN_L2_SERVICE_TBL_DST_PORT_ID_VALID BIT(0)
++#define PPE_IN_L2_SERVICE_TBL_DST_PORT_ID GENMASK(4, 1)
++#define PPE_IN_L2_SERVICE_TBL_DST_DIRECTION BIT(5)
++#define PPE_IN_L2_SERVICE_TBL_DST_BYPASS_BITMAP GENMASK(29, 6)
++#define PPE_IN_L2_SERVICE_TBL_RX_CNT_EN BIT(30)
++#define PPE_IN_L2_SERVICE_TBL_TX_CNT_EN BIT(31)
++
++/* PPE service code configuration for the tunnel packet. */
++#define PPE_TL_SERVICE_TBL_ADDR 0x306000
++#define PPE_TL_SERVICE_TBL_ENTRIES 256
++#define PPE_TL_SERVICE_TBL_INC 4
++#define PPE_TL_SERVICE_TBL_BYPASS_BITMAP GENMASK(31, 0)
++
+ /* Port scheduler global config. */
+ #define PPE_PSCH_SCH_DEPTH_CFG_ADDR 0x400000
+ #define PPE_PSCH_SCH_DEPTH_CFG_INC 4
--- /dev/null
+From 63af46200da794acda25cf8083bde0c1576b0859 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:43 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE port control settings
+
+1. Enable port specific counters in PPE.
+2. Configure the default action as drop when the packet size
+ is more than the configured MTU of physical port.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 86 ++++++++++++++++++-
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 15 ++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 47 ++++++++++
+ 3 files changed, 147 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -1153,6 +1153,44 @@ int ppe_sc_config_set(struct ppe_device
+ return regmap_write(ppe_dev->regmap, reg, val);
+ }
+
++/**
++ * ppe_counter_enable_set - Set PPE port counter enabled
++ * @ppe_dev: PPE device
++ * @port: PPE port ID
++ *
++ * Enable PPE counters on the given port for the unicast packet, multicast
++ * packet and VLAN packet received and transmitted by PPE.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_counter_enable_set(struct ppe_device *ppe_dev, int port)
++{
++ u32 reg, mru_mtu_val[3];
++ int ret;
++
++ reg = PPE_MRU_MTU_CTRL_TBL_ADDR + PPE_MRU_MTU_CTRL_TBL_INC * port;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++ if (ret)
++ return ret;
++
++ PPE_MRU_MTU_CTRL_SET_RX_CNT_EN(mru_mtu_val, true);
++ PPE_MRU_MTU_CTRL_SET_TX_CNT_EN(mru_mtu_val, true);
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++ if (ret)
++ return ret;
++
++ reg = PPE_MC_MTU_CTRL_TBL_ADDR + PPE_MC_MTU_CTRL_TBL_INC * port;
++ ret = regmap_set_bits(ppe_dev->regmap, reg, PPE_MC_MTU_CTRL_TBL_TX_CNT_EN);
++ if (ret)
++ return ret;
++
++ reg = PPE_PORT_EG_VLAN_TBL_ADDR + PPE_PORT_EG_VLAN_TBL_INC * port;
++
++ return regmap_set_bits(ppe_dev->regmap, reg, PPE_PORT_EG_VLAN_TBL_TX_COUNTING_EN);
++}
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -1579,6 +1617,48 @@ static int ppe_servcode_init(struct ppe_
+ return ppe_sc_config_set(ppe_dev, PPE_EDMA_SC_BYPASS_ID, sc_cfg);
+ }
+
++/* Initialize PPE port configurations. */
++static int ppe_port_config_init(struct ppe_device *ppe_dev)
++{
++ u32 reg, val, mru_mtu_val[3];
++ int i, ret;
++
++ /* MTU and MRU settings are not required for CPU port 0. */
++ for (i = 1; i < ppe_dev->num_ports; i++) {
++ /* Enable Ethernet port counter */
++ ret = ppe_counter_enable_set(ppe_dev, i);
++ if (ret)
++ return ret;
++
++ reg = PPE_MRU_MTU_CTRL_TBL_ADDR + PPE_MRU_MTU_CTRL_TBL_INC * i;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++ if (ret)
++ return ret;
++
++ /* Drop the packet when the packet size is more than
++ * the MTU or MRU of the physical interface.
++ */
++ PPE_MRU_MTU_CTRL_SET_MRU_CMD(mru_mtu_val, PPE_ACTION_DROP);
++ PPE_MRU_MTU_CTRL_SET_MTU_CMD(mru_mtu_val, PPE_ACTION_DROP);
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++ if (ret)
++ return ret;
++
++ reg = PPE_MC_MTU_CTRL_TBL_ADDR + PPE_MC_MTU_CTRL_TBL_INC * i;
++ val = FIELD_PREP(PPE_MC_MTU_CTRL_TBL_MTU_CMD, PPE_ACTION_DROP);
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_MC_MTU_CTRL_TBL_MTU_CMD,
++ val);
++ if (ret)
++ return ret;
++ }
++
++ /* Enable CPU port counters. */
++ return ppe_counter_enable_set(ppe_dev, 0);
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -1599,5 +1679,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_servcode_init(ppe_dev);
++ ret = ppe_servcode_init(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_port_config_init(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -233,6 +233,20 @@ struct ppe_sc_cfg {
+ int eip_offset_sel;
+ };
+
++/**
++ * enum ppe_action_type - PPE action of the received packet.
++ * @PPE_ACTION_FORWARD: Packet forwarded per L2/L3 process.
++ * @PPE_ACTION_DROP: Packet dropped by PPE.
++ * @PPE_ACTION_COPY_TO_CPU: Packet copied to CPU port per multicast queue.
++ * @PPE_ACTION_REDIRECT_TO_CPU: Packet redirected to CPU port per unicast queue.
++ */
++enum ppe_action_type {
++ PPE_ACTION_FORWARD = 0,
++ PPE_ACTION_DROP = 1,
++ PPE_ACTION_COPY_TO_CPU = 2,
++ PPE_ACTION_REDIRECT_TO_CPU = 3,
++};
++
+ int ppe_hw_config(struct ppe_device *ppe_dev);
+ int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
+ int node_id, bool flow_level, int port,
+@@ -254,4 +268,5 @@ int ppe_port_resource_get(struct ppe_dev
+ int *res_start, int *res_end);
+ int ppe_sc_config_set(struct ppe_device *ppe_dev, int sc,
+ struct ppe_sc_cfg cfg);
++int ppe_counter_enable_set(struct ppe_device *ppe_dev, int port);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -40,6 +40,18 @@
+ #define PPE_SERVICE_SET_RX_CNT_EN(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_SERVICE_W1_RX_CNT_EN)
+
++/* PPE port egress VLAN configurations. */
++#define PPE_PORT_EG_VLAN_TBL_ADDR 0x20020
++#define PPE_PORT_EG_VLAN_TBL_ENTRIES 8
++#define PPE_PORT_EG_VLAN_TBL_INC 4
++#define PPE_PORT_EG_VLAN_TBL_VLAN_TYPE BIT(0)
++#define PPE_PORT_EG_VLAN_TBL_CTAG_MODE GENMASK(2, 1)
++#define PPE_PORT_EG_VLAN_TBL_STAG_MODE GENMASK(4, 3)
++#define PPE_PORT_EG_VLAN_TBL_VSI_TAG_MODE_EN BIT(5)
++#define PPE_PORT_EG_VLAN_TBL_PCP_PROP_CMD BIT(6)
++#define PPE_PORT_EG_VLAN_TBL_DEI_PROP_CMD BIT(7)
++#define PPE_PORT_EG_VLAN_TBL_TX_COUNTING_EN BIT(8)
++
+ /* PPE queue counters enable/disable control. */
+ #define PPE_EG_BRIDGE_CONFIG_ADDR 0x20044
+ #define PPE_EG_BRIDGE_CONFIG_QUEUE_CNT_EN BIT(2)
+@@ -65,6 +77,41 @@
+ #define PPE_EG_SERVICE_SET_TX_CNT_EN(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_EG_SERVICE_W1_TX_CNT_EN)
+
++/* PPE port control configurations for the traffic to the multicast queues. */
++#define PPE_MC_MTU_CTRL_TBL_ADDR 0x60a00
++#define PPE_MC_MTU_CTRL_TBL_ENTRIES 8
++#define PPE_MC_MTU_CTRL_TBL_INC 4
++#define PPE_MC_MTU_CTRL_TBL_MTU GENMASK(13, 0)
++#define PPE_MC_MTU_CTRL_TBL_MTU_CMD GENMASK(15, 14)
++#define PPE_MC_MTU_CTRL_TBL_TX_CNT_EN BIT(16)
++
++/* PPE port control configurations for the traffic to the unicast queues. */
++#define PPE_MRU_MTU_CTRL_TBL_ADDR 0x65000
++#define PPE_MRU_MTU_CTRL_TBL_ENTRIES 256
++#define PPE_MRU_MTU_CTRL_TBL_INC 0x10
++#define PPE_MRU_MTU_CTRL_W0_MRU GENMASK(13, 0)
++#define PPE_MRU_MTU_CTRL_W0_MRU_CMD GENMASK(15, 14)
++#define PPE_MRU_MTU_CTRL_W0_MTU GENMASK(29, 16)
++#define PPE_MRU_MTU_CTRL_W0_MTU_CMD GENMASK(31, 30)
++#define PPE_MRU_MTU_CTRL_W1_RX_CNT_EN BIT(0)
++#define PPE_MRU_MTU_CTRL_W1_TX_CNT_EN BIT(1)
++#define PPE_MRU_MTU_CTRL_W1_SRC_PROFILE GENMASK(3, 2)
++#define PPE_MRU_MTU_CTRL_W1_INNER_PREC_LOW BIT(31)
++#define PPE_MRU_MTU_CTRL_W2_INNER_PREC_HIGH GENMASK(1, 0)
++
++#define PPE_MRU_MTU_CTRL_SET_MRU(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_MRU_MTU_CTRL_W0_MRU)
++#define PPE_MRU_MTU_CTRL_SET_MRU_CMD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_MRU_MTU_CTRL_W0_MRU_CMD)
++#define PPE_MRU_MTU_CTRL_SET_MTU(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_MRU_MTU_CTRL_W0_MTU)
++#define PPE_MRU_MTU_CTRL_SET_MTU_CMD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_MRU_MTU_CTRL_W0_MTU_CMD)
++#define PPE_MRU_MTU_CTRL_SET_RX_CNT_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_MRU_MTU_CTRL_W1_RX_CNT_EN)
++#define PPE_MRU_MTU_CTRL_SET_TX_CNT_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_MRU_MTU_CTRL_W1_TX_CNT_EN)
++
+ /* PPE service code configuration for destination port and counter. */
+ #define PPE_IN_L2_SERVICE_TBL_ADDR 0x66000
+ #define PPE_IN_L2_SERVICE_TBL_ENTRIES 256
--- /dev/null
+From 796be78fffeebe77237a6464da7ebe9807d670f0 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:44 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE RSS hash settings
+
+PPE RSS hash is generated during PPE receive, based on the packet
+content (3 tuples or 5 tuples) and as per the configured RSS seed.
+The hash is then used to select the queue to transmit the packet
+to the ARM CPU.
+
+This patch initializes the RSS hash settings that are used to
+generate the hash for the packet during PPE packet receive.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 194 +++++++++++++++++-
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 39 ++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 40 ++++
+ 3 files changed, 272 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -1191,6 +1191,143 @@ int ppe_counter_enable_set(struct ppe_de
+ return regmap_set_bits(ppe_dev->regmap, reg, PPE_PORT_EG_VLAN_TBL_TX_COUNTING_EN);
+ }
+
++static int ppe_rss_hash_ipv4_config(struct ppe_device *ppe_dev, int index,
++ struct ppe_rss_hash_cfg cfg)
++{
++ u32 reg, val;
++
++ switch (index) {
++ case 0:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_IPV4_VAL, cfg.hash_sip_mix[0]);
++ break;
++ case 1:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_IPV4_VAL, cfg.hash_dip_mix[0]);
++ break;
++ case 2:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_IPV4_VAL, cfg.hash_protocol_mix);
++ break;
++ case 3:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_IPV4_VAL, cfg.hash_dport_mix);
++ break;
++ case 4:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_IPV4_VAL, cfg.hash_sport_mix);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ reg = PPE_RSS_HASH_MIX_IPV4_ADDR + index * PPE_RSS_HASH_MIX_IPV4_INC;
++
++ return regmap_write(ppe_dev->regmap, reg, val);
++}
++
++static int ppe_rss_hash_ipv6_config(struct ppe_device *ppe_dev, int index,
++ struct ppe_rss_hash_cfg cfg)
++{
++ u32 reg, val;
++
++ switch (index) {
++ case 0 ... 3:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_VAL, cfg.hash_sip_mix[index]);
++ break;
++ case 4 ... 7:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_VAL, cfg.hash_dip_mix[index - 4]);
++ break;
++ case 8:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_VAL, cfg.hash_protocol_mix);
++ break;
++ case 9:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_VAL, cfg.hash_dport_mix);
++ break;
++ case 10:
++ val = FIELD_PREP(PPE_RSS_HASH_MIX_VAL, cfg.hash_sport_mix);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ reg = PPE_RSS_HASH_MIX_ADDR + index * PPE_RSS_HASH_MIX_INC;
++
++ return regmap_write(ppe_dev->regmap, reg, val);
++}
++
++/**
++ * ppe_rss_hash_config_set - Configure the PPE hash settings for the packet received.
++ * @ppe_dev: PPE device.
++ * @mode: Configure RSS hash for the packet type IPv4 and IPv6.
++ * @cfg: RSS hash configuration.
++ *
++ * PPE RSS hash settings are configured for the packet type IPv4 and IPv6.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode,
++ struct ppe_rss_hash_cfg cfg)
++{
++ u32 val, reg;
++ int i, ret;
++
++ if (mode & PPE_RSS_HASH_MODE_IPV4) {
++ val = FIELD_PREP(PPE_RSS_HASH_MASK_IPV4_HASH_MASK, cfg.hash_mask);
++ val |= FIELD_PREP(PPE_RSS_HASH_MASK_IPV4_FRAGMENT, cfg.hash_fragment_mode);
++ ret = regmap_write(ppe_dev->regmap, PPE_RSS_HASH_MASK_IPV4_ADDR, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(PPE_RSS_HASH_SEED_IPV4_VAL, cfg.hash_seed);
++ ret = regmap_write(ppe_dev->regmap, PPE_RSS_HASH_SEED_IPV4_ADDR, val);
++ if (ret)
++ return ret;
++
++ for (i = 0; i < PPE_RSS_HASH_MIX_IPV4_ENTRIES; i++) {
++ ret = ppe_rss_hash_ipv4_config(ppe_dev, i, cfg);
++ if (ret)
++ return ret;
++ }
++
++ for (i = 0; i < PPE_RSS_HASH_FIN_IPV4_ENTRIES; i++) {
++ val = FIELD_PREP(PPE_RSS_HASH_FIN_IPV4_INNER, cfg.hash_fin_inner[i]);
++ val |= FIELD_PREP(PPE_RSS_HASH_FIN_IPV4_OUTER, cfg.hash_fin_outer[i]);
++ reg = PPE_RSS_HASH_FIN_IPV4_ADDR + i * PPE_RSS_HASH_FIN_IPV4_INC;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++ }
++ }
++
++ if (mode & PPE_RSS_HASH_MODE_IPV6) {
++ val = FIELD_PREP(PPE_RSS_HASH_MASK_HASH_MASK, cfg.hash_mask);
++ val |= FIELD_PREP(PPE_RSS_HASH_MASK_FRAGMENT, cfg.hash_fragment_mode);
++ ret = regmap_write(ppe_dev->regmap, PPE_RSS_HASH_MASK_ADDR, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(PPE_RSS_HASH_SEED_VAL, cfg.hash_seed);
++ ret = regmap_write(ppe_dev->regmap, PPE_RSS_HASH_SEED_ADDR, val);
++ if (ret)
++ return ret;
++
++ for (i = 0; i < PPE_RSS_HASH_MIX_ENTRIES; i++) {
++ ret = ppe_rss_hash_ipv6_config(ppe_dev, i, cfg);
++ if (ret)
++ return ret;
++ }
++
++ for (i = 0; i < PPE_RSS_HASH_FIN_ENTRIES; i++) {
++ val = FIELD_PREP(PPE_RSS_HASH_FIN_INNER, cfg.hash_fin_inner[i]);
++ val |= FIELD_PREP(PPE_RSS_HASH_FIN_OUTER, cfg.hash_fin_outer[i]);
++ reg = PPE_RSS_HASH_FIN_ADDR + i * PPE_RSS_HASH_FIN_INC;
++
++ ret = regmap_write(ppe_dev->regmap, reg, val);
++ if (ret)
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -1659,6 +1796,57 @@ static int ppe_port_config_init(struct p
+ return ppe_counter_enable_set(ppe_dev, 0);
+ }
+
++/* Initialize the PPE RSS configuration for IPv4 and IPv6 packet receive.
++ * RSS settings are to calculate the random RSS hash value generated during
++ * packet receive. This hash is then used to generate the queue offset used
++ * to determine the queue used to transmit the packet.
++ */
++static int ppe_rss_hash_init(struct ppe_device *ppe_dev)
++{
++ u16 fins[PPE_RSS_HASH_TUPLES] = { 0x205, 0x264, 0x227, 0x245, 0x201 };
++ u8 ips[PPE_RSS_HASH_IP_LENGTH] = { 0x13, 0xb, 0x13, 0xb };
++ struct ppe_rss_hash_cfg hash_cfg;
++ int i, ret;
++
++ hash_cfg.hash_seed = get_random_u32();
++ hash_cfg.hash_mask = 0xfff;
++
++ /* Use 5 tuple as RSS hash key for the first fragment of TCP, UDP
++ * and UDP-Lite packets.
++ */
++ hash_cfg.hash_fragment_mode = false;
++
++ /* The final common seed configs used to calculate the RSS has value,
++ * which is available for both IPv4 and IPv6 packet.
++ */
++ for (i = 0; i < ARRAY_SIZE(fins); i++) {
++ hash_cfg.hash_fin_inner[i] = fins[i] & 0x1f;
++ hash_cfg.hash_fin_outer[i] = fins[i] >> 5;
++ }
++
++ /* RSS seeds for IP protocol, L4 destination & source port and
++ * destination & source IP used to calculate the RSS hash value.
++ */
++ hash_cfg.hash_protocol_mix = 0x13;
++ hash_cfg.hash_dport_mix = 0xb;
++ hash_cfg.hash_sport_mix = 0x13;
++ hash_cfg.hash_dip_mix[0] = 0xb;
++ hash_cfg.hash_sip_mix[0] = 0x13;
++
++ /* Configure RSS seed configs for IPv4 packet. */
++ ret = ppe_rss_hash_config_set(ppe_dev, PPE_RSS_HASH_MODE_IPV4, hash_cfg);
++ if (ret)
++ return ret;
++
++ for (i = 0; i < ARRAY_SIZE(ips); i++) {
++ hash_cfg.hash_sip_mix[i] = ips[i];
++ hash_cfg.hash_dip_mix[i] = ips[i];
++ }
++
++ /* Configure RSS seed configs for IPv6 packet. */
++ return ppe_rss_hash_config_set(ppe_dev, PPE_RSS_HASH_MODE_IPV6, hash_cfg);
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -1683,5 +1871,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_port_config_init(ppe_dev);
++ ret = ppe_port_config_init(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_rss_hash_init(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -23,6 +23,12 @@
+ /* The service code is used by EDMA port to transmit packet to PPE. */
+ #define PPE_EDMA_SC_BYPASS_ID 1
+
++/* The PPE RSS hash configured for IPv4 and IPv6 packet separately. */
++#define PPE_RSS_HASH_MODE_IPV4 BIT(0)
++#define PPE_RSS_HASH_MODE_IPV6 BIT(1)
++#define PPE_RSS_HASH_IP_LENGTH 4
++#define PPE_RSS_HASH_TUPLES 5
++
+ /**
+ * enum ppe_scheduler_frame_mode - PPE scheduler frame mode.
+ * @PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC: The scheduled frame includes IPG,
+@@ -247,6 +253,37 @@ enum ppe_action_type {
+ PPE_ACTION_REDIRECT_TO_CPU = 3,
+ };
+
++/**
++ * struct ppe_rss_hash_cfg - PPE RSS hash configuration.
++ * @hash_mask: Mask of the generated hash value.
++ * @hash_fragment_mode: Hash generation mode for the first fragment of TCP,
++ * UDP and UDP-Lite packets, to use either 3 tuple or 5 tuple for RSS hash
++ * key computation.
++ * @hash_seed: Seed to generate RSS hash.
++ * @hash_sip_mix: Source IP selection.
++ * @hash_dip_mix: Destination IP selection.
++ * @hash_protocol_mix: Protocol selection.
++ * @hash_sport_mix: Source L4 port selection.
++ * @hash_dport_mix: Destination L4 port selection.
++ * @hash_fin_inner: RSS hash value first selection.
++ * @hash_fin_outer: RSS hash value second selection.
++ *
++ * PPE RSS hash value is generated for the packet based on the RSS hash
++ * configured.
++ */
++struct ppe_rss_hash_cfg {
++ u32 hash_mask;
++ bool hash_fragment_mode;
++ u32 hash_seed;
++ u8 hash_sip_mix[PPE_RSS_HASH_IP_LENGTH];
++ u8 hash_dip_mix[PPE_RSS_HASH_IP_LENGTH];
++ u8 hash_protocol_mix;
++ u8 hash_sport_mix;
++ u8 hash_dport_mix;
++ u8 hash_fin_inner[PPE_RSS_HASH_TUPLES];
++ u8 hash_fin_outer[PPE_RSS_HASH_TUPLES];
++};
++
+ int ppe_hw_config(struct ppe_device *ppe_dev);
+ int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
+ int node_id, bool flow_level, int port,
+@@ -269,4 +306,6 @@ int ppe_port_resource_get(struct ppe_dev
+ int ppe_sc_config_set(struct ppe_device *ppe_dev, int sc,
+ struct ppe_sc_cfg cfg);
+ int ppe_counter_enable_set(struct ppe_device *ppe_dev, int port);
++int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode,
++ struct ppe_rss_hash_cfg hash_cfg);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -16,6 +16,46 @@
+ #define PPE_BM_SCH_CTRL_SCH_OFFSET GENMASK(14, 8)
+ #define PPE_BM_SCH_CTRL_SCH_EN BIT(31)
+
++/* RSS settings are to calculate the random RSS hash value generated during
++ * packet receive to ARM cores. This hash is then used to generate the queue
++ * offset used to determine the queue used to transmit the packet to ARM cores.
++ */
++#define PPE_RSS_HASH_MASK_ADDR 0xb4318
++#define PPE_RSS_HASH_MASK_HASH_MASK GENMASK(20, 0)
++#define PPE_RSS_HASH_MASK_FRAGMENT BIT(28)
++
++#define PPE_RSS_HASH_SEED_ADDR 0xb431c
++#define PPE_RSS_HASH_SEED_VAL GENMASK(31, 0)
++
++#define PPE_RSS_HASH_MIX_ADDR 0xb4320
++#define PPE_RSS_HASH_MIX_ENTRIES 11
++#define PPE_RSS_HASH_MIX_INC 4
++#define PPE_RSS_HASH_MIX_VAL GENMASK(4, 0)
++
++#define PPE_RSS_HASH_FIN_ADDR 0xb4350
++#define PPE_RSS_HASH_FIN_ENTRIES 5
++#define PPE_RSS_HASH_FIN_INC 4
++#define PPE_RSS_HASH_FIN_INNER GENMASK(4, 0)
++#define PPE_RSS_HASH_FIN_OUTER GENMASK(9, 5)
++
++#define PPE_RSS_HASH_MASK_IPV4_ADDR 0xb4380
++#define PPE_RSS_HASH_MASK_IPV4_HASH_MASK GENMASK(20, 0)
++#define PPE_RSS_HASH_MASK_IPV4_FRAGMENT BIT(28)
++
++#define PPE_RSS_HASH_SEED_IPV4_ADDR 0xb4384
++#define PPE_RSS_HASH_SEED_IPV4_VAL GENMASK(31, 0)
++
++#define PPE_RSS_HASH_MIX_IPV4_ADDR 0xb4390
++#define PPE_RSS_HASH_MIX_IPV4_ENTRIES 5
++#define PPE_RSS_HASH_MIX_IPV4_INC 4
++#define PPE_RSS_HASH_MIX_IPV4_VAL GENMASK(4, 0)
++
++#define PPE_RSS_HASH_FIN_IPV4_ADDR 0xb43b0
++#define PPE_RSS_HASH_FIN_IPV4_ENTRIES 5
++#define PPE_RSS_HASH_FIN_IPV4_INC 4
++#define PPE_RSS_HASH_FIN_IPV4_INNER GENMASK(4, 0)
++#define PPE_RSS_HASH_FIN_IPV4_OUTER GENMASK(9, 5)
++
+ #define PPE_BM_SCH_CFG_TBL_ADDR 0xc000
+ #define PPE_BM_SCH_CFG_TBL_ENTRIES 128
+ #define PPE_BM_SCH_CFG_TBL_INC 0x10
--- /dev/null
+From c4a321bc120fabc318df165a7fcdeddfcf052253 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:45 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE queue to Ethernet DMA
+ ring mapping
+
+Configure the selected queues to map with an Ethernet DMA ring for the
+packet to receive on ARM cores.
+
+As default initialization, all queues assigned to CPU port 0 are mapped
+to the EDMA ring 0. This configuration is later updated during Ethernet
+DMA initialization.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 47 ++++++++++++++++++-
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 6 +++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 5 ++
+ 3 files changed, 57 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -1328,6 +1328,28 @@ int ppe_rss_hash_config_set(struct ppe_d
+ return 0;
+ }
+
++/**
++ * ppe_ring_queue_map_set - Set the PPE queue to Ethernet DMA ring mapping
++ * @ppe_dev: PPE device
++ * @ring_id: Ethernet DMA ring ID
++ * @queue_map: Bit map of queue IDs to given Ethernet DMA ring
++ *
++ * Configure the mapping from a set of PPE queues to a given Ethernet DMA ring.
++ *
++ * Return: 0 on success, negative error code on failure.
++ */
++int ppe_ring_queue_map_set(struct ppe_device *ppe_dev, int ring_id, u32 *queue_map)
++{
++ u32 reg, queue_bitmap_val[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT];
++
++ memcpy(queue_bitmap_val, queue_map, sizeof(queue_bitmap_val));
++ reg = PPE_RING_Q_MAP_TBL_ADDR + PPE_RING_Q_MAP_TBL_INC * ring_id;
++
++ return regmap_bulk_write(ppe_dev->regmap, reg,
++ queue_bitmap_val,
++ ARRAY_SIZE(queue_bitmap_val));
++}
++
+ static int ppe_config_bm_threshold(struct ppe_device *ppe_dev, int bm_port_id,
+ const struct ppe_bm_port_config port_cfg)
+ {
+@@ -1847,6 +1869,25 @@ static int ppe_rss_hash_init(struct ppe_
+ return ppe_rss_hash_config_set(ppe_dev, PPE_RSS_HASH_MODE_IPV6, hash_cfg);
+ }
+
++/* Initialize mapping between PPE queues assigned to CPU port 0
++ * to Ethernet DMA ring 0.
++ */
++static int ppe_queues_to_ring_init(struct ppe_device *ppe_dev)
++{
++ u32 queue_bmap[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT] = {};
++ int ret, queue_id, queue_max;
++
++ ret = ppe_port_resource_get(ppe_dev, 0, PPE_RES_UCAST,
++ &queue_id, &queue_max);
++ if (ret)
++ return ret;
++
++ for (; queue_id <= queue_max; queue_id++)
++ queue_bmap[queue_id / 32] |= BIT_MASK(queue_id % 32);
++
++ return ppe_ring_queue_map_set(ppe_dev, 0, queue_bmap);
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -1875,5 +1916,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_rss_hash_init(ppe_dev);
++ ret = ppe_rss_hash_init(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_queues_to_ring_init(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -29,6 +29,9 @@
+ #define PPE_RSS_HASH_IP_LENGTH 4
+ #define PPE_RSS_HASH_TUPLES 5
+
++/* PPE supports 300 queues, each bit presents as one queue. */
++#define PPE_RING_TO_QUEUE_BITMAP_WORD_CNT 10
++
+ /**
+ * enum ppe_scheduler_frame_mode - PPE scheduler frame mode.
+ * @PPE_SCH_WITH_IPG_PREAMBLE_FRAME_CRC: The scheduled frame includes IPG,
+@@ -308,4 +311,7 @@ int ppe_sc_config_set(struct ppe_device
+ int ppe_counter_enable_set(struct ppe_device *ppe_dev, int port);
+ int ppe_rss_hash_config_set(struct ppe_device *ppe_dev, int mode,
+ struct ppe_rss_hash_cfg hash_cfg);
++int ppe_ring_queue_map_set(struct ppe_device *ppe_dev,
++ int ring_id,
++ u32 *queue_map);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -207,6 +207,11 @@
+ #define PPE_L0_COMP_CFG_TBL_SHAPER_METER_LEN GENMASK(1, 0)
+ #define PPE_L0_COMP_CFG_TBL_NODE_METER_LEN GENMASK(3, 2)
+
++/* PPE queue to Ethernet DMA ring mapping table. */
++#define PPE_RING_Q_MAP_TBL_ADDR 0x42a000
++#define PPE_RING_Q_MAP_TBL_ENTRIES 24
++#define PPE_RING_Q_MAP_TBL_INC 0x40
++
+ /* Table addresses for per-queue dequeue setting. */
+ #define PPE_DEQ_OPR_TBL_ADDR 0x430000
+ #define PPE_DEQ_OPR_TBL_ENTRIES 300
--- /dev/null
+From cf7282d1e5712953516fa1cc0ffaae405491b3ca Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:46 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Initialize PPE L2 bridge settings
+
+Initialize the L2 bridge settings for the PPE ports to only enable
+L2 frame forwarding between CPU port and PPE Ethernet ports.
+
+The per-port L2 bridge settings are initialized as follows:
+For PPE CPU port, the PPE bridge TX is enabled and FDB learning is
+disabled. For PPE physical ports, the default L2 forwarding action
+is initialized to forward to CPU port only.
+
+L2/FDB learning and forwarding will not be enabled for PPE physical
+ports yet, since the port's VSI (Virtual Switch Instance) and VSI
+membership are not yet configured, which are required for FDB
+forwarding. The VSI and FDB forwarding will later be enabled when
+switchdev is enabled.
+
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 80 ++++++++++++++++++-
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 50 ++++++++++++
+ 2 files changed, 129 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -1888,6 +1888,80 @@ static int ppe_queues_to_ring_init(struc
+ return ppe_ring_queue_map_set(ppe_dev, 0, queue_bmap);
+ }
+
++/* Initialize PPE bridge settings to only enable L2 frame receive and
++ * transmit between CPU port and PPE Ethernet ports.
++ */
++static int ppe_bridge_init(struct ppe_device *ppe_dev)
++{
++ u32 reg, mask, port_cfg[4], vsi_cfg[2];
++ int ret, i;
++
++ /* Configure the following settings for CPU port0:
++ * a.) Enable Bridge TX
++ * b.) Disable FDB new address learning
++ * c.) Disable station move address learning
++ */
++ mask = PPE_PORT_BRIDGE_TXMAC_EN;
++ mask |= PPE_PORT_BRIDGE_NEW_LRN_EN;
++ mask |= PPE_PORT_BRIDGE_STA_MOVE_LRN_EN;
++ ret = regmap_update_bits(ppe_dev->regmap,
++ PPE_PORT_BRIDGE_CTRL_ADDR,
++ mask,
++ PPE_PORT_BRIDGE_TXMAC_EN);
++ if (ret)
++ return ret;
++
++ for (i = 1; i < ppe_dev->num_ports; i++) {
++ /* Enable invalid VSI forwarding for all the physical ports
++ * to CPU port0, in case no VSI is assigned to the physical
++ * port.
++ */
++ reg = PPE_L2_VP_PORT_TBL_ADDR + PPE_L2_VP_PORT_TBL_INC * i;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ port_cfg, ARRAY_SIZE(port_cfg));
++
++ if (ret)
++ return ret;
++
++ PPE_L2_PORT_SET_INVALID_VSI_FWD_EN(port_cfg, true);
++ PPE_L2_PORT_SET_DST_INFO(port_cfg, 0);
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ port_cfg, ARRAY_SIZE(port_cfg));
++ if (ret)
++ return ret;
++ }
++
++ for (i = 0; i < PPE_VSI_TBL_ENTRIES; i++) {
++ /* Set the VSI forward membership to include only CPU port0.
++ * FDB learning and forwarding take place only after switchdev
++ * is supported later to create the VSI and join the physical
++ * ports to the VSI port member.
++ */
++ reg = PPE_VSI_TBL_ADDR + PPE_VSI_TBL_INC * i;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ vsi_cfg, ARRAY_SIZE(vsi_cfg));
++ if (ret)
++ return ret;
++
++ PPE_VSI_SET_MEMBER_PORT_BITMAP(vsi_cfg, BIT(0));
++ PPE_VSI_SET_UUC_BITMAP(vsi_cfg, BIT(0));
++ PPE_VSI_SET_UMC_BITMAP(vsi_cfg, BIT(0));
++ PPE_VSI_SET_BC_BITMAP(vsi_cfg, BIT(0));
++ PPE_VSI_SET_NEW_ADDR_LRN_EN(vsi_cfg, true);
++ PPE_VSI_SET_NEW_ADDR_FWD_CMD(vsi_cfg, PPE_ACTION_FORWARD);
++ PPE_VSI_SET_STATION_MOVE_LRN_EN(vsi_cfg, true);
++ PPE_VSI_SET_STATION_MOVE_FWD_CMD(vsi_cfg, PPE_ACTION_FORWARD);
++
++ ret = regmap_bulk_write(ppe_dev->regmap, reg,
++ vsi_cfg, ARRAY_SIZE(vsi_cfg));
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
+ int ppe_hw_config(struct ppe_device *ppe_dev)
+ {
+ int ret;
+@@ -1920,5 +1994,9 @@ int ppe_hw_config(struct ppe_device *ppe
+ if (ret)
+ return ret;
+
+- return ppe_queues_to_ring_init(ppe_dev);
++ ret = ppe_queues_to_ring_init(ppe_dev);
++ if (ret)
++ return ret;
++
++ return ppe_bridge_init(ppe_dev);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -117,6 +117,14 @@
+ #define PPE_EG_SERVICE_SET_TX_CNT_EN(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_EG_SERVICE_W1_TX_CNT_EN)
+
++/* PPE port bridge configuration */
++#define PPE_PORT_BRIDGE_CTRL_ADDR 0x60300
++#define PPE_PORT_BRIDGE_CTRL_ENTRIES 8
++#define PPE_PORT_BRIDGE_CTRL_INC 4
++#define PPE_PORT_BRIDGE_NEW_LRN_EN BIT(0)
++#define PPE_PORT_BRIDGE_STA_MOVE_LRN_EN BIT(3)
++#define PPE_PORT_BRIDGE_TXMAC_EN BIT(16)
++
+ /* PPE port control configurations for the traffic to the multicast queues. */
+ #define PPE_MC_MTU_CTRL_TBL_ADDR 0x60a00
+ #define PPE_MC_MTU_CTRL_TBL_ENTRIES 8
+@@ -125,6 +133,36 @@
+ #define PPE_MC_MTU_CTRL_TBL_MTU_CMD GENMASK(15, 14)
+ #define PPE_MC_MTU_CTRL_TBL_TX_CNT_EN BIT(16)
+
++/* PPE VSI configurations */
++#define PPE_VSI_TBL_ADDR 0x63800
++#define PPE_VSI_TBL_ENTRIES 64
++#define PPE_VSI_TBL_INC 0x10
++#define PPE_VSI_W0_MEMBER_PORT_BITMAP GENMASK(7, 0)
++#define PPE_VSI_W0_UUC_BITMAP GENMASK(15, 8)
++#define PPE_VSI_W0_UMC_BITMAP GENMASK(23, 16)
++#define PPE_VSI_W0_BC_BITMAP GENMASK(31, 24)
++#define PPE_VSI_W1_NEW_ADDR_LRN_EN BIT(0)
++#define PPE_VSI_W1_NEW_ADDR_FWD_CMD GENMASK(2, 1)
++#define PPE_VSI_W1_STATION_MOVE_LRN_EN BIT(3)
++#define PPE_VSI_W1_STATION_MOVE_FWD_CMD GENMASK(5, 4)
++
++#define PPE_VSI_SET_MEMBER_PORT_BITMAP(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_VSI_W0_MEMBER_PORT_BITMAP)
++#define PPE_VSI_SET_UUC_BITMAP(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_VSI_W0_UUC_BITMAP)
++#define PPE_VSI_SET_UMC_BITMAP(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_VSI_W0_UMC_BITMAP)
++#define PPE_VSI_SET_BC_BITMAP(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_VSI_W0_BC_BITMAP)
++#define PPE_VSI_SET_NEW_ADDR_LRN_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_VSI_W1_NEW_ADDR_LRN_EN)
++#define PPE_VSI_SET_NEW_ADDR_FWD_CMD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_VSI_W1_NEW_ADDR_FWD_CMD)
++#define PPE_VSI_SET_STATION_MOVE_LRN_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_VSI_W1_STATION_MOVE_LRN_EN)
++#define PPE_VSI_SET_STATION_MOVE_FWD_CMD(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_VSI_W1_STATION_MOVE_FWD_CMD)
++
+ /* PPE port control configurations for the traffic to the unicast queues. */
+ #define PPE_MRU_MTU_CTRL_TBL_ADDR 0x65000
+ #define PPE_MRU_MTU_CTRL_TBL_ENTRIES 256
+@@ -163,6 +201,18 @@
+ #define PPE_IN_L2_SERVICE_TBL_RX_CNT_EN BIT(30)
+ #define PPE_IN_L2_SERVICE_TBL_TX_CNT_EN BIT(31)
+
++/* L2 Port configurations */
++#define PPE_L2_VP_PORT_TBL_ADDR 0x98000
++#define PPE_L2_VP_PORT_TBL_ENTRIES 256
++#define PPE_L2_VP_PORT_TBL_INC 0x10
++#define PPE_L2_VP_PORT_W0_INVALID_VSI_FWD_EN BIT(0)
++#define PPE_L2_VP_PORT_W0_DST_INFO GENMASK(9, 2)
++
++#define PPE_L2_PORT_SET_INVALID_VSI_FWD_EN(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_L2_VP_PORT_W0_INVALID_VSI_FWD_EN)
++#define PPE_L2_PORT_SET_DST_INFO(tbl_cfg, value) \
++ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_L2_VP_PORT_W0_DST_INFO)
++
+ /* PPE service code configuration for the tunnel packet. */
+ #define PPE_TL_SERVICE_TBL_ADDR 0x306000
+ #define PPE_TL_SERVICE_TBL_ENTRIES 256
--- /dev/null
+From fc25088f79cccb934d69e563221068589565926f Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:47 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE debugfs support for PPE
+ counters
+
+The PPE hardware counters maintain counters for packets handled by
+the various functional blocks of PPE. They help in tracing the packets
+passed through PPE and debugging any packet drops.
+
+The counters displayed by this debugfs file are ones that are common
+for all Ethernet ports, and they do not include the counters that are
+specific for a MAC port. Hence they cannot be displayed using ethtool.
+The per-MAC counters will be supported using "ethtool -S" along with
+the netdevice driver.
+
+The PPE hardware packet counters are made available through
+the debugfs entry "/sys/kernel/debug/ppe/packet_counters".
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 11 +
+ drivers/net/ethernet/qualcomm/ppe/ppe.h | 3 +
+ .../net/ethernet/qualcomm/ppe/ppe_debugfs.c | 692 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/ppe_debugfs.h | 16 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 102 +++
+ 6 files changed, 825 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -4,4 +4,4 @@
+ #
+
+ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+-qcom-ppe-objs := ppe.o ppe_config.o
++qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -16,6 +16,7 @@
+
+ #include "ppe.h"
+ #include "ppe_config.h"
++#include "ppe_debugfs.h"
+
+ #define PPE_PORT_MAX 8
+ #define PPE_CLK_RATE 353000000
+@@ -199,11 +200,20 @@ static int qcom_ppe_probe(struct platfor
+ if (ret)
+ return dev_err_probe(dev, ret, "PPE HW config failed\n");
+
++ ppe_debugfs_setup(ppe_dev);
+ platform_set_drvdata(pdev, ppe_dev);
+
+ return 0;
+ }
+
++static void qcom_ppe_remove(struct platform_device *pdev)
++{
++ struct ppe_device *ppe_dev;
++
++ ppe_dev = platform_get_drvdata(pdev);
++ ppe_debugfs_teardown(ppe_dev);
++}
++
+ static const struct of_device_id qcom_ppe_of_match[] = {
+ { .compatible = "qcom,ipq9574-ppe" },
+ {}
+@@ -216,6 +226,7 @@ static struct platform_driver qcom_ppe_d
+ .of_match_table = qcom_ppe_of_match,
+ },
+ .probe = qcom_ppe_probe,
++ .remove = qcom_ppe_remove,
+ };
+ module_platform_driver(qcom_ppe_driver);
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.h
+@@ -11,6 +11,7 @@
+
+ struct device;
+ struct regmap;
++struct dentry;
+
+ /**
+ * struct ppe_device - PPE device private data.
+@@ -18,6 +19,7 @@ struct regmap;
+ * @regmap: PPE register map.
+ * @clk_rate: PPE clock rate.
+ * @num_ports: Number of PPE ports.
++ * @debugfs_root: Debugfs root entry.
+ * @num_icc_paths: Number of interconnect paths.
+ * @icc_paths: Interconnect path array.
+ *
+@@ -30,6 +32,7 @@ struct ppe_device {
+ struct regmap *regmap;
+ unsigned long clk_rate;
+ unsigned int num_ports;
++ struct dentry *debugfs_root;
+ unsigned int num_icc_paths;
+ struct icc_bulk_data icc_paths[] __counted_by(num_icc_paths);
+ };
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.c
+@@ -0,0 +1,692 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE debugfs routines for display of PPE counters useful for debug. */
++
++#include <linux/bitfield.h>
++#include <linux/debugfs.h>
++#include <linux/regmap.h>
++#include <linux/seq_file.h>
++
++#include "ppe.h"
++#include "ppe_config.h"
++#include "ppe_debugfs.h"
++#include "ppe_regs.h"
++
++#define PPE_PKT_CNT_TBL_SIZE 3
++#define PPE_DROP_PKT_CNT_TBL_SIZE 5
++
++#define PPE_W0_PKT_CNT GENMASK(31, 0)
++#define PPE_W2_DROP_PKT_CNT_LOW GENMASK(31, 8)
++#define PPE_W3_DROP_PKT_CNT_HIGH GENMASK(7, 0)
++
++#define PPE_GET_PKT_CNT(tbl_cnt) \
++ u32_get_bits(*((u32 *)(tbl_cnt)), PPE_W0_PKT_CNT)
++#define PPE_GET_DROP_PKT_CNT_LOW(tbl_cnt) \
++ u32_get_bits(*((u32 *)(tbl_cnt) + 0x2), PPE_W2_DROP_PKT_CNT_LOW)
++#define PPE_GET_DROP_PKT_CNT_HIGH(tbl_cnt) \
++ u32_get_bits(*((u32 *)(tbl_cnt) + 0x3), PPE_W3_DROP_PKT_CNT_HIGH)
++
++#define PRINT_COUNTER_PREFIX(desc, cnt_type) \
++ seq_printf(seq, "%-16s %16s", desc, cnt_type)
++
++#define PRINT_CPU_CODE_COUNTER(cnt, code) \
++ seq_printf(seq, "%10u(cpucode:%d)", cnt, code)
++
++#define PRINT_DROP_CODE_COUNTER(cnt, port, code) \
++ seq_printf(seq, "%10u(port=%d),dropcode:%d", cnt, port, code)
++
++#define PRINT_SINGLE_COUNTER(tag, cnt, str, index) \
++do { \
++ if (!((tag) % 4)) \
++ seq_printf(seq, "\n%-16s %16s", "", ""); \
++ seq_printf(seq, "%10u(%s=%04d)", cnt, str, index); \
++} while (0)
++
++#define PRINT_TWO_COUNTERS(tag, cnt0, cnt1, str, index) \
++do { \
++ if (!((tag) % 4)) \
++ seq_printf(seq, "\n%-16s %16s", "", ""); \
++ seq_printf(seq, "%10u/%u(%s=%04d)", cnt0, cnt1, str, index); \
++} while (0)
++
++/**
++ * enum ppe_cnt_size_type - PPE counter size type
++ * @PPE_PKT_CNT_SIZE_1WORD: Counter size with single register
++ * @PPE_PKT_CNT_SIZE_3WORD: Counter size with table of 3 words
++ * @PPE_PKT_CNT_SIZE_5WORD: Counter size with table of 5 words
++ *
++ * PPE takes the different register size to record the packet counters.
++ * It uses single register, or register table with 3 words or 5 words.
++ * The counter with table size 5 words also records the drop counter.
++ * There are also some other counter types occupying sizes less than 32
++ * bits, which is not covered by this enumeration type.
++ */
++enum ppe_cnt_size_type {
++ PPE_PKT_CNT_SIZE_1WORD,
++ PPE_PKT_CNT_SIZE_3WORD,
++ PPE_PKT_CNT_SIZE_5WORD,
++};
++
++static int ppe_pkt_cnt_get(struct ppe_device *ppe_dev, u32 reg,
++ enum ppe_cnt_size_type cnt_type,
++ u32 *cnt, u32 *drop_cnt)
++{
++ u32 drop_pkt_cnt[PPE_DROP_PKT_CNT_TBL_SIZE];
++ u32 pkt_cnt[PPE_PKT_CNT_TBL_SIZE];
++ u32 value;
++ int ret;
++
++ switch (cnt_type) {
++ case PPE_PKT_CNT_SIZE_1WORD:
++ ret = regmap_read(ppe_dev->regmap, reg, &value);
++ if (ret)
++ return ret;
++
++ *cnt = value;
++ break;
++ case PPE_PKT_CNT_SIZE_3WORD:
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ pkt_cnt, ARRAY_SIZE(pkt_cnt));
++ if (ret)
++ return ret;
++
++ *cnt = PPE_GET_PKT_CNT(pkt_cnt);
++ break;
++ case PPE_PKT_CNT_SIZE_5WORD:
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ drop_pkt_cnt, ARRAY_SIZE(drop_pkt_cnt));
++ if (ret)
++ return ret;
++
++ *cnt = PPE_GET_PKT_CNT(drop_pkt_cnt);
++
++ /* Drop counter with low 24 bits. */
++ value = PPE_GET_DROP_PKT_CNT_LOW(drop_pkt_cnt);
++ *drop_cnt = FIELD_PREP(GENMASK(23, 0), value);
++
++ /* Drop counter with high 8 bits. */
++ value = PPE_GET_DROP_PKT_CNT_HIGH(drop_pkt_cnt);
++ *drop_cnt |= FIELD_PREP(GENMASK(31, 24), value);
++ break;
++ }
++
++ return 0;
++}
++
++static void ppe_tbl_pkt_cnt_clear(struct ppe_device *ppe_dev, u32 reg,
++ enum ppe_cnt_size_type cnt_type)
++{
++ u32 drop_pkt_cnt[PPE_DROP_PKT_CNT_TBL_SIZE] = {};
++ u32 pkt_cnt[PPE_PKT_CNT_TBL_SIZE] = {};
++
++ switch (cnt_type) {
++ case PPE_PKT_CNT_SIZE_1WORD:
++ regmap_write(ppe_dev->regmap, reg, 0);
++ break;
++ case PPE_PKT_CNT_SIZE_3WORD:
++ regmap_bulk_write(ppe_dev->regmap, reg,
++ pkt_cnt, ARRAY_SIZE(pkt_cnt));
++ break;
++ case PPE_PKT_CNT_SIZE_5WORD:
++ regmap_bulk_write(ppe_dev->regmap, reg,
++ drop_pkt_cnt, ARRAY_SIZE(drop_pkt_cnt));
++ break;
++ }
++}
++
++/* The number of packets dropped because of no buffer available, no PPE
++ * buffer assigned to these packets.
++ */
++static void ppe_port_rx_drop_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, drop_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("PRX_DROP_CNT", "SILENT_DROP:");
++ for (i = 0; i < PPE_DROP_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_DROP_CNT_TBL_ADDR + i * PPE_DROP_CNT_TBL_INC;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD,
++ &drop_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (drop_cnt > 0) {
++ tag++;
++ PRINT_SINGLE_COUNTER(tag, drop_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets dropped because hardware buffers were available
++ * only partially for the packet.
++ */
++static void ppe_port_rx_bm_drop_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("PRX_BM_DROP_CNT", "OVERFLOW_DROP:");
++ for (i = 0; i < PPE_DROP_STAT_TBL_ENTRIES; i++) {
++ reg = PPE_DROP_STAT_TBL_ADDR + PPE_DROP_STAT_TBL_INC * i;
++
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0) {
++ tag++;
++ PRINT_SINGLE_COUNTER(tag, pkt_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of currently occupied buffers, that can't be flushed. */
++static void ppe_port_rx_bm_port_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ int used_cnt, react_cnt;
++ int ret, i, tag = 0;
++ u32 reg, val;
++
++ PRINT_COUNTER_PREFIX("PRX_BM_PORT_CNT", "USED/REACT:");
++ for (i = 0; i < PPE_BM_USED_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_BM_USED_CNT_TBL_ADDR + i * PPE_BM_USED_CNT_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ /* The number of PPE buffers used for caching the received
++ * packets before the pause frame sent.
++ */
++ used_cnt = FIELD_GET(PPE_BM_USED_CNT_VAL, val);
++
++ reg = PPE_BM_REACT_CNT_TBL_ADDR + i * PPE_BM_REACT_CNT_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ /* The number of PPE buffers used for caching the received
++ * packets after pause frame sent out.
++ */
++ react_cnt = FIELD_GET(PPE_BM_REACT_CNT_VAL, val);
++
++ if (used_cnt > 0 || react_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, used_cnt, react_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets processed by the ingress parser module of PPE. */
++static void ppe_parse_pkt_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, cnt = 0, tunnel_cnt = 0;
++ int i, ret, tag = 0;
++
++ PRINT_COUNTER_PREFIX("IPR_PKT_CNT", "TPRX/IPRX:");
++ for (i = 0; i < PPE_IPR_PKT_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_TPR_PKT_CNT_TBL_ADDR + i * PPE_TPR_PKT_CNT_TBL_INC;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD,
++ &tunnel_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ reg = PPE_IPR_PKT_CNT_TBL_ADDR + i * PPE_IPR_PKT_CNT_TBL_INC;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD,
++ &cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (tunnel_cnt > 0 || cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, tunnel_cnt, cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets received or dropped on the ingress direction. */
++static void ppe_port_rx_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0, drop_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("PORT_RX_CNT", "RX/RX_DROP:");
++ for (i = 0; i < PPE_PHY_PORT_RX_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_PHY_PORT_RX_CNT_TBL_ADDR + PPE_PHY_PORT_RX_CNT_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD,
++ &pkt_cnt, &drop_cnt);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, pkt_cnt, drop_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets received or dropped by the port. */
++static void ppe_vp_rx_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0, drop_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("VPORT_RX_CNT", "RX/RX_DROP:");
++ for (i = 0; i < PPE_PORT_RX_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_PORT_RX_CNT_TBL_ADDR + PPE_PORT_RX_CNT_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD,
++ &pkt_cnt, &drop_cnt);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, pkt_cnt, drop_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets received or dropped by layer 2 processing. */
++static void ppe_pre_l2_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0, drop_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("PRE_L2_CNT", "RX/RX_DROP:");
++ for (i = 0; i < PPE_PRE_L2_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_PRE_L2_CNT_TBL_ADDR + PPE_PRE_L2_CNT_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD,
++ &pkt_cnt, &drop_cnt);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, pkt_cnt, drop_cnt, "vsi", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of VLAN packets received by PPE. */
++static void ppe_vlan_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("VLAN_CNT", "RX:");
++ for (i = 0; i < PPE_VLAN_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_VLAN_CNT_TBL_ADDR + PPE_VLAN_CNT_TBL_INC * i;
++
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0) {
++ tag++;
++ PRINT_SINGLE_COUNTER(tag, pkt_cnt, "vsi", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets handed to CPU by PPE. */
++static void ppe_cpu_code_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0;
++ int ret, i;
++
++ PRINT_COUNTER_PREFIX("CPU_CODE_CNT", "CODE:");
++ for (i = 0; i < PPE_DROP_CPU_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_DROP_CPU_CNT_TBL_ADDR + PPE_DROP_CPU_CNT_TBL_INC * i;
++
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (!pkt_cnt)
++ continue;
++
++ /* There are 256 CPU codes saved in the first 256 entries
++ * of register table, and 128 drop codes for each PPE port
++ * (0-7), the total entries is 256 + 8 * 128.
++ */
++ if (i < 256)
++ PRINT_CPU_CODE_COUNTER(pkt_cnt, i);
++ else
++ PRINT_DROP_CODE_COUNTER(pkt_cnt, (i - 256) % 8,
++ (i - 256) / 8);
++ seq_putc(seq, '\n');
++ PRINT_COUNTER_PREFIX("", "");
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets forwarded by VLAN on the egress direction. */
++static void ppe_eg_vsi_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("EG_VSI_CNT", "TX:");
++ for (i = 0; i < PPE_EG_VSI_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_EG_VSI_COUNTER_TBL_ADDR + PPE_EG_VSI_COUNTER_TBL_INC * i;
++
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0) {
++ tag++;
++ PRINT_SINGLE_COUNTER(tag, pkt_cnt, "vsi", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets trasmitted or dropped by port. */
++static void ppe_vp_tx_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0, drop_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("VPORT_TX_CNT", "TX/TX_DROP:");
++ for (i = 0; i < PPE_VPORT_TX_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_VPORT_TX_COUNTER_TBL_ADDR + PPE_VPORT_TX_COUNTER_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ reg = PPE_VPORT_TX_DROP_CNT_TBL_ADDR + PPE_VPORT_TX_DROP_CNT_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &drop_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0 || drop_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, pkt_cnt, drop_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets trasmitted or dropped on the egress direction. */
++static void ppe_port_tx_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, pkt_cnt = 0, drop_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("PORT_TX_CNT", "TX/TX_DROP:");
++ for (i = 0; i < PPE_PORT_TX_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_PORT_TX_COUNTER_TBL_ADDR + PPE_PORT_TX_COUNTER_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ reg = PPE_PORT_TX_DROP_CNT_TBL_ADDR + PPE_PORT_TX_DROP_CNT_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &drop_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (pkt_cnt > 0 || drop_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, pkt_cnt, drop_cnt, "port", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* The number of packets transmitted or pending by the PPE queue. */
++static void ppe_queue_tx_counter_get(struct ppe_device *ppe_dev,
++ struct seq_file *seq)
++{
++ u32 reg, val, pkt_cnt = 0, pend_cnt = 0;
++ int ret, i, tag = 0;
++
++ PRINT_COUNTER_PREFIX("QUEUE_TX_CNT", "TX/PEND:");
++ for (i = 0; i < PPE_QUEUE_TX_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_QUEUE_TX_COUNTER_TBL_ADDR + PPE_QUEUE_TX_COUNTER_TBL_INC * i;
++ ret = ppe_pkt_cnt_get(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD,
++ &pkt_cnt, NULL);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ if (i < PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES) {
++ reg = PPE_AC_UNICAST_QUEUE_CNT_TBL_ADDR +
++ PPE_AC_UNICAST_QUEUE_CNT_TBL_INC * i;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ pend_cnt = FIELD_GET(PPE_AC_UNICAST_QUEUE_CNT_TBL_PEND_CNT, val);
++ } else {
++ reg = PPE_AC_MULTICAST_QUEUE_CNT_TBL_ADDR +
++ PPE_AC_MULTICAST_QUEUE_CNT_TBL_INC *
++ (i - PPE_AC_UNICAST_QUEUE_CFG_TBL_ENTRIES);
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ seq_printf(seq, "ERROR %d\n", ret);
++ return;
++ }
++
++ pend_cnt = FIELD_GET(PPE_AC_MULTICAST_QUEUE_CNT_TBL_PEND_CNT, val);
++ }
++
++ if (pkt_cnt > 0 || pend_cnt > 0) {
++ tag++;
++ PRINT_TWO_COUNTERS(tag, pkt_cnt, pend_cnt, "queue", i);
++ }
++ }
++
++ seq_putc(seq, '\n');
++}
++
++/* Display the various packet counters of PPE. */
++static int ppe_packet_counter_show(struct seq_file *seq, void *v)
++{
++ struct ppe_device *ppe_dev = seq->private;
++
++ ppe_port_rx_drop_counter_get(ppe_dev, seq);
++ ppe_port_rx_bm_drop_counter_get(ppe_dev, seq);
++ ppe_port_rx_bm_port_counter_get(ppe_dev, seq);
++ ppe_parse_pkt_counter_get(ppe_dev, seq);
++ ppe_port_rx_counter_get(ppe_dev, seq);
++ ppe_vp_rx_counter_get(ppe_dev, seq);
++ ppe_pre_l2_counter_get(ppe_dev, seq);
++ ppe_vlan_counter_get(ppe_dev, seq);
++ ppe_cpu_code_counter_get(ppe_dev, seq);
++ ppe_eg_vsi_counter_get(ppe_dev, seq);
++ ppe_vp_tx_counter_get(ppe_dev, seq);
++ ppe_port_tx_counter_get(ppe_dev, seq);
++ ppe_queue_tx_counter_get(ppe_dev, seq);
++
++ return 0;
++}
++
++static int ppe_packet_counter_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, ppe_packet_counter_show, inode->i_private);
++}
++
++static ssize_t ppe_packet_counter_clear(struct file *file,
++ const char __user *buf,
++ size_t count, loff_t *pos)
++{
++ struct ppe_device *ppe_dev = file_inode(file)->i_private;
++ u32 reg;
++ int i;
++
++ for (i = 0; i < PPE_DROP_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_DROP_CNT_TBL_ADDR + i * PPE_DROP_CNT_TBL_INC;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD);
++ }
++
++ for (i = 0; i < PPE_DROP_STAT_TBL_ENTRIES; i++) {
++ reg = PPE_DROP_STAT_TBL_ADDR + PPE_DROP_STAT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ for (i = 0; i < PPE_IPR_PKT_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_IPR_PKT_CNT_TBL_ADDR + i * PPE_IPR_PKT_CNT_TBL_INC;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD);
++
++ reg = PPE_TPR_PKT_CNT_TBL_ADDR + i * PPE_TPR_PKT_CNT_TBL_INC;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_1WORD);
++ }
++
++ for (i = 0; i < PPE_VLAN_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_VLAN_CNT_TBL_ADDR + PPE_VLAN_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ for (i = 0; i < PPE_PRE_L2_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_PRE_L2_CNT_TBL_ADDR + PPE_PRE_L2_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD);
++ }
++
++ for (i = 0; i < PPE_PORT_TX_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_PORT_TX_DROP_CNT_TBL_ADDR + PPE_PORT_TX_DROP_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++
++ reg = PPE_PORT_TX_COUNTER_TBL_ADDR + PPE_PORT_TX_COUNTER_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ for (i = 0; i < PPE_EG_VSI_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_EG_VSI_COUNTER_TBL_ADDR + PPE_EG_VSI_COUNTER_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ for (i = 0; i < PPE_VPORT_TX_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_VPORT_TX_COUNTER_TBL_ADDR + PPE_VPORT_TX_COUNTER_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++
++ reg = PPE_VPORT_TX_DROP_CNT_TBL_ADDR + PPE_VPORT_TX_DROP_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ for (i = 0; i < PPE_QUEUE_TX_COUNTER_TBL_ENTRIES; i++) {
++ reg = PPE_QUEUE_TX_COUNTER_TBL_ADDR + PPE_QUEUE_TX_COUNTER_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ ppe_tbl_pkt_cnt_clear(ppe_dev, PPE_EPE_DBG_IN_CNT_ADDR, PPE_PKT_CNT_SIZE_1WORD);
++ ppe_tbl_pkt_cnt_clear(ppe_dev, PPE_EPE_DBG_OUT_CNT_ADDR, PPE_PKT_CNT_SIZE_1WORD);
++
++ for (i = 0; i < PPE_DROP_CPU_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_DROP_CPU_CNT_TBL_ADDR + PPE_DROP_CPU_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_3WORD);
++ }
++
++ for (i = 0; i < PPE_PORT_RX_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_PORT_RX_CNT_TBL_ADDR + PPE_PORT_RX_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD);
++ }
++
++ for (i = 0; i < PPE_PHY_PORT_RX_CNT_TBL_ENTRIES; i++) {
++ reg = PPE_PHY_PORT_RX_CNT_TBL_ADDR + PPE_PHY_PORT_RX_CNT_TBL_INC * i;
++ ppe_tbl_pkt_cnt_clear(ppe_dev, reg, PPE_PKT_CNT_SIZE_5WORD);
++ }
++
++ return count;
++}
++
++static const struct file_operations ppe_debugfs_packet_counter_fops = {
++ .owner = THIS_MODULE,
++ .open = ppe_packet_counter_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = single_release,
++ .write = ppe_packet_counter_clear,
++};
++
++void ppe_debugfs_setup(struct ppe_device *ppe_dev)
++{
++ ppe_dev->debugfs_root = debugfs_create_dir("ppe", NULL);
++ debugfs_create_file("packet_counters", 0444,
++ ppe_dev->debugfs_root,
++ ppe_dev,
++ &ppe_debugfs_packet_counter_fops);
++}
++
++void ppe_debugfs_teardown(struct ppe_device *ppe_dev)
++{
++ debugfs_remove_recursive(ppe_dev->debugfs_root);
++ ppe_dev->debugfs_root = NULL;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ *
++ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE debugfs counters setup. */
++
++#ifndef __PPE_DEBUGFS_H__
++#define __PPE_DEBUGFS_H__
++
++#include "ppe.h"
++
++void ppe_debugfs_setup(struct ppe_device *ppe_dev);
++void ppe_debugfs_teardown(struct ppe_device *ppe_dev);
++
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -16,6 +16,39 @@
+ #define PPE_BM_SCH_CTRL_SCH_OFFSET GENMASK(14, 8)
+ #define PPE_BM_SCH_CTRL_SCH_EN BIT(31)
+
++/* PPE drop counters. */
++#define PPE_DROP_CNT_TBL_ADDR 0xb024
++#define PPE_DROP_CNT_TBL_ENTRIES 8
++#define PPE_DROP_CNT_TBL_INC 4
++
++/* BM port drop counters. */
++#define PPE_DROP_STAT_TBL_ADDR 0xe000
++#define PPE_DROP_STAT_TBL_ENTRIES 30
++#define PPE_DROP_STAT_TBL_INC 0x10
++
++#define PPE_EPE_DBG_IN_CNT_ADDR 0x26054
++#define PPE_EPE_DBG_OUT_CNT_ADDR 0x26070
++
++/* Egress VLAN counters. */
++#define PPE_EG_VSI_COUNTER_TBL_ADDR 0x41000
++#define PPE_EG_VSI_COUNTER_TBL_ENTRIES 64
++#define PPE_EG_VSI_COUNTER_TBL_INC 0x10
++
++/* Port TX counters. */
++#define PPE_PORT_TX_COUNTER_TBL_ADDR 0x45000
++#define PPE_PORT_TX_COUNTER_TBL_ENTRIES 8
++#define PPE_PORT_TX_COUNTER_TBL_INC 0x10
++
++/* Virtual port TX counters. */
++#define PPE_VPORT_TX_COUNTER_TBL_ADDR 0x47000
++#define PPE_VPORT_TX_COUNTER_TBL_ENTRIES 256
++#define PPE_VPORT_TX_COUNTER_TBL_INC 0x10
++
++/* Queue counters. */
++#define PPE_QUEUE_TX_COUNTER_TBL_ADDR 0x4a000
++#define PPE_QUEUE_TX_COUNTER_TBL_ENTRIES 300
++#define PPE_QUEUE_TX_COUNTER_TBL_INC 0x10
++
+ /* RSS settings are to calculate the random RSS hash value generated during
+ * packet receive to ARM cores. This hash is then used to generate the queue
+ * offset used to determine the queue used to transmit the packet to ARM cores.
+@@ -213,6 +246,51 @@
+ #define PPE_L2_PORT_SET_DST_INFO(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)tbl_cfg, value, PPE_L2_VP_PORT_W0_DST_INFO)
+
++/* Port RX and RX drop counters. */
++#define PPE_PORT_RX_CNT_TBL_ADDR 0x150000
++#define PPE_PORT_RX_CNT_TBL_ENTRIES 256
++#define PPE_PORT_RX_CNT_TBL_INC 0x20
++
++/* Physical port RX and RX drop counters. */
++#define PPE_PHY_PORT_RX_CNT_TBL_ADDR 0x156000
++#define PPE_PHY_PORT_RX_CNT_TBL_ENTRIES 8
++#define PPE_PHY_PORT_RX_CNT_TBL_INC 0x20
++
++/* Counters for the packet to CPU port. */
++#define PPE_DROP_CPU_CNT_TBL_ADDR 0x160000
++#define PPE_DROP_CPU_CNT_TBL_ENTRIES 1280
++#define PPE_DROP_CPU_CNT_TBL_INC 0x10
++
++/* VLAN counters. */
++#define PPE_VLAN_CNT_TBL_ADDR 0x178000
++#define PPE_VLAN_CNT_TBL_ENTRIES 64
++#define PPE_VLAN_CNT_TBL_INC 0x10
++
++/* PPE L2 counters. */
++#define PPE_PRE_L2_CNT_TBL_ADDR 0x17c000
++#define PPE_PRE_L2_CNT_TBL_ENTRIES 64
++#define PPE_PRE_L2_CNT_TBL_INC 0x20
++
++/* Port TX drop counters. */
++#define PPE_PORT_TX_DROP_CNT_TBL_ADDR 0x17d000
++#define PPE_PORT_TX_DROP_CNT_TBL_ENTRIES 8
++#define PPE_PORT_TX_DROP_CNT_TBL_INC 0x10
++
++/* Virtual port TX counters. */
++#define PPE_VPORT_TX_DROP_CNT_TBL_ADDR 0x17e000
++#define PPE_VPORT_TX_DROP_CNT_TBL_ENTRIES 256
++#define PPE_VPORT_TX_DROP_CNT_TBL_INC 0x10
++
++/* Counters for the tunnel packet. */
++#define PPE_TPR_PKT_CNT_TBL_ADDR 0x1d0080
++#define PPE_TPR_PKT_CNT_TBL_ENTRIES 8
++#define PPE_TPR_PKT_CNT_TBL_INC 4
++
++/* Counters for the all packet received. */
++#define PPE_IPR_PKT_CNT_TBL_ADDR 0x1e0080
++#define PPE_IPR_PKT_CNT_TBL_ENTRIES 8
++#define PPE_IPR_PKT_CNT_TBL_INC 4
++
+ /* PPE service code configuration for the tunnel packet. */
+ #define PPE_TL_SERVICE_TBL_ADDR 0x306000
+ #define PPE_TL_SERVICE_TBL_ENTRIES 256
+@@ -325,6 +403,18 @@
+ #define PPE_BM_PORT_GROUP_ID_INC 0x4
+ #define PPE_BM_PORT_GROUP_ID_SHARED_GROUP_ID GENMASK(1, 0)
+
++/* Counters for PPE buffers used for packets cached. */
++#define PPE_BM_USED_CNT_TBL_ADDR 0x6001c0
++#define PPE_BM_USED_CNT_TBL_ENTRIES 15
++#define PPE_BM_USED_CNT_TBL_INC 0x4
++#define PPE_BM_USED_CNT_VAL GENMASK(10, 0)
++
++/* Counters for PPE buffers used for packets received after pause frame sent. */
++#define PPE_BM_REACT_CNT_TBL_ADDR 0x600240
++#define PPE_BM_REACT_CNT_TBL_ENTRIES 15
++#define PPE_BM_REACT_CNT_TBL_INC 0x4
++#define PPE_BM_REACT_CNT_VAL GENMASK(8, 0)
++
+ #define PPE_BM_SHARED_GROUP_CFG_ADDR 0x600290
+ #define PPE_BM_SHARED_GROUP_CFG_ENTRIES 4
+ #define PPE_BM_SHARED_GROUP_CFG_INC 0x4
+@@ -449,6 +539,18 @@
+ #define PPE_AC_GRP_SET_BUF_LIMIT(tbl_cfg, value) \
+ u32p_replace_bits((u32 *)(tbl_cfg) + 0x1, value, PPE_AC_GRP_W1_BUF_LIMIT)
+
++/* Counters for packets handled by unicast queues (0-255). */
++#define PPE_AC_UNICAST_QUEUE_CNT_TBL_ADDR 0x84e000
++#define PPE_AC_UNICAST_QUEUE_CNT_TBL_ENTRIES 256
++#define PPE_AC_UNICAST_QUEUE_CNT_TBL_INC 0x10
++#define PPE_AC_UNICAST_QUEUE_CNT_TBL_PEND_CNT GENMASK(12, 0)
++
++/* Counters for packets handled by multicast queues (256-299). */
++#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_ADDR 0x852000
++#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_ENTRIES 44
++#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_INC 0x10
++#define PPE_AC_MULTICAST_QUEUE_CNT_TBL_PEND_CNT GENMASK(12, 0)
++
+ /* Table addresses for per-queue enqueue setting. */
+ #define PPE_ENQ_OPR_TBL_ADDR 0x85c000
+ #define PPE_ENQ_OPR_TBL_ENTRIES 300
--- /dev/null
+From 28098c348414fa97531449d4e27ba1587e67c2d9 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Sun, 9 Feb 2025 22:29:48 +0800
+Subject: [PATCH] MAINTAINERS: Add maintainer for Qualcomm PPE driver
+
+Add maintainer entry for PPE (Packet Process Engine) driver
+supported for Qualcomm IPQ SoCs.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ MAINTAINERS | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -19133,6 +19133,14 @@ S: Maintained
+ F: Documentation/devicetree/bindings/mtd/qcom,nandc.yaml
+ F: drivers/mtd/nand/raw/qcom_nandc.c
+
++QUALCOMM PPE DRIVER
++M: Luo Jie <quic_luoj@quicinc.com>
++L: netdev@vger.kernel.org
++S: Supported
++F: Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml
++F: Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst
++F: drivers/net/ethernet/qualcomm/ppe/
++
+ QUALCOMM QSEECOM DRIVER
+ M: Maximilian Luz <luzmaximilian@gmail.com>
+ L: linux-arm-msm@vger.kernel.org
--- /dev/null
+From 93cf3297818ee61607f0a8d1d34e4fb7fcde3cdf Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Tue, 26 Dec 2023 20:18:09 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE scheduler config
+
+PPE scheduler config determines the priority of scheduling the
+packet. The scheduler config is used for supporting the QoS
+offload in PPE hardware.
+
+Change-Id: I4811bd133074757371775a6a69a1cc3cfaa8d0d0
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: rebase patch on top of PPE driver submission from 20250209.
+ Add the ppe_queue_priority_set() function and its
+ dependencies. They will be used in the edma support in
+ susequent changes.
+ ppe_queue_priority_set() used to be part of ppe_api.c, and
+ is hereby moved to ppe_config.c .
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ .../net/ethernet/qualcomm/ppe/ppe_config.c | 141 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/ppe_config.h | 5 +
+ 2 files changed, 146 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.c
+@@ -864,6 +864,51 @@ static int ppe_scheduler_l0_queue_map_se
+ val);
+ }
+
++/* Get the first level scheduler configuration. */
++static int ppe_scheduler_l0_queue_map_get(struct ppe_device *ppe_dev,
++ int node_id, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg)
++{
++ u32 val, reg;
++ int ret;
++
++ reg = PPE_L0_FLOW_MAP_TBL_ADDR + node_id * PPE_L0_FLOW_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->flow_id = FIELD_GET(PPE_L0_FLOW_MAP_TBL_FLOW_ID, val);
++ scheduler_cfg->pri = FIELD_GET(PPE_L0_FLOW_MAP_TBL_C_PRI, val);
++ scheduler_cfg->drr_node_wt = FIELD_GET(PPE_L0_FLOW_MAP_TBL_C_NODE_WT, val);
++
++ reg = PPE_L0_C_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg->flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg->pri) *
++ PPE_L0_C_FLOW_CFG_TBL_INC;
++
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->drr_node_id = FIELD_GET(PPE_L0_C_FLOW_CFG_TBL_NODE_ID, val);
++ scheduler_cfg->unit_is_packet = FIELD_GET(PPE_L0_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT, val);
++
++ reg = PPE_L0_FLOW_PORT_MAP_TBL_ADDR + node_id * PPE_L0_FLOW_PORT_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ *port = FIELD_GET(PPE_L0_FLOW_PORT_MAP_TBL_PORT_NUM, val);
++
++ reg = PPE_L0_COMP_CFG_TBL_ADDR + node_id * PPE_L0_COMP_CFG_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->frame_mode = FIELD_GET(PPE_L0_COMP_CFG_TBL_NODE_METER_LEN, val);
++
++ return 0;
++}
++
+ /* Set the PPE flow level scheduler configuration. */
+ static int ppe_scheduler_l1_queue_map_set(struct ppe_device *ppe_dev,
+ int node_id, int port,
+@@ -916,6 +961,50 @@ static int ppe_scheduler_l1_queue_map_se
+ return regmap_update_bits(ppe_dev->regmap, reg, PPE_L1_COMP_CFG_TBL_NODE_METER_LEN, val);
+ }
+
++/* Get the second level scheduler configuration. */
++static int ppe_scheduler_l1_queue_map_get(struct ppe_device *ppe_dev,
++ int node_id, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg)
++{
++ u32 val, reg;
++ int ret;
++
++ reg = PPE_L1_FLOW_MAP_TBL_ADDR + node_id * PPE_L1_FLOW_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->flow_id = FIELD_GET(PPE_L1_FLOW_MAP_TBL_FLOW_ID, val);
++ scheduler_cfg->pri = FIELD_GET(PPE_L1_FLOW_MAP_TBL_C_PRI, val);
++ scheduler_cfg->drr_node_wt = FIELD_GET(PPE_L1_FLOW_MAP_TBL_C_NODE_WT, val);
++
++ reg = PPE_L1_C_FLOW_CFG_TBL_ADDR +
++ (scheduler_cfg->flow_id * PPE_QUEUE_SCH_PRI_NUM + scheduler_cfg->pri) *
++ PPE_L1_C_FLOW_CFG_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->drr_node_id = FIELD_GET(PPE_L1_C_FLOW_CFG_TBL_NODE_ID, val);
++ scheduler_cfg->unit_is_packet = FIELD_GET(PPE_L1_C_FLOW_CFG_TBL_NODE_CREDIT_UNIT, val);
++
++ reg = PPE_L1_FLOW_PORT_MAP_TBL_ADDR + node_id * PPE_L1_FLOW_PORT_MAP_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ *port = FIELD_GET(PPE_L1_FLOW_PORT_MAP_TBL_PORT_NUM, val);
++
++ reg = PPE_L1_COMP_CFG_TBL_ADDR + node_id * PPE_L1_COMP_CFG_TBL_INC;
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret)
++ return ret;
++
++ scheduler_cfg->frame_mode = FIELD_GET(PPE_L1_COMP_CFG_TBL_NODE_METER_LEN, val);
++
++ return 0;
++}
++
+ /**
+ * ppe_queue_scheduler_set - Configure scheduler for PPE hardware queue
+ * @ppe_dev: PPE device
+@@ -942,6 +1031,58 @@ int ppe_queue_scheduler_set(struct ppe_d
+ }
+
+ /**
++ * ppe_queue_scheduler_get - get QoS scheduler of PPE hardware queue
++ * @ppe_dev: PPE device
++ * @node_id: PPE node ID
++ * @flow_level: Flow level scheduler or queue level scheduler
++ * @port: PPE port ID to get scheduler config
++ * @scheduler_cfg: QoS scheduler configuration
++ *
++ * The hardware QoS function is supported by PPE, the current scheduler
++ * configuration can be acquired based on the queue ID of PPE port.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_queue_scheduler_get(struct ppe_device *ppe_dev,
++ int node_id, bool flow_level, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg)
++{
++ if (flow_level)
++ return ppe_scheduler_l1_queue_map_get(ppe_dev, node_id,
++ port, scheduler_cfg);
++
++ return ppe_scheduler_l0_queue_map_get(ppe_dev, node_id,
++ port, scheduler_cfg);
++}
++
++
++/**
++ * ppe_queue_priority_set - set scheduler priority of PPE hardware queue
++ * @ppe_dev: PPE device
++ * @node_id: PPE hardware node ID, which is either queue ID or flow ID
++ * @priority: Qos scheduler priority
++ *
++ * Configure scheduler priority of PPE hardware queque, the maximum node
++ * ID supported is PPE_QUEUE_ID_NUM added by PPE_FLOW_ID_NUM, queue ID
++ * belongs to level 0, flow ID belongs to level 1 in the packet pipeline.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_queue_priority_set(struct ppe_device *ppe_dev,
++ int node_id, int priority)
++{
++ struct ppe_scheduler_cfg sch_cfg;
++ int ret, port, level = 0;
++
++ ret = ppe_queue_scheduler_get(ppe_dev, node_id, level, &port, &sch_cfg);
++ if (ret)
++ return ret;
++
++ sch_cfg.pri = priority;
++ return ppe_queue_scheduler_set(ppe_dev, node_id, level, port, sch_cfg);
++}
++
++/**
+ * ppe_queue_ucast_base_set - Set PPE unicast queue base ID and profile ID
+ * @ppe_dev: PPE device
+ * @queue_dst: PPE queue destination configuration
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_config.h
+@@ -291,6 +291,11 @@ int ppe_hw_config(struct ppe_device *ppe
+ int ppe_queue_scheduler_set(struct ppe_device *ppe_dev,
+ int node_id, bool flow_level, int port,
+ struct ppe_scheduler_cfg scheduler_cfg);
++int ppe_queue_scheduler_get(struct ppe_device *ppe_dev,
++ int node_id, bool flow_level, int *port,
++ struct ppe_scheduler_cfg *scheduler_cfg);
++int ppe_queue_priority_set(struct ppe_device *ppe_dev,
++ int queue_id, int priority);
+ int ppe_queue_ucast_base_set(struct ppe_device *ppe_dev,
+ struct ppe_queue_ucast_dest queue_dst,
+ int queue_base,
--- /dev/null
+From dbb3711ab25ea410ad5286b2f39dccd954cda225 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Thu, 29 Feb 2024 16:59:53 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add phylink support for PPE MAC
+ ports
+
+Add MAC initialization and phylink functions for PPE MAC ports.
+
+Change-Id: I39dcba671732392bcfa2e734473fd083989bfbec
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 3 +
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 9 +
+ drivers/net/ethernet/qualcomm/ppe/ppe.h | 2 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 728 +++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 76 ++
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 124 ++++
+ 7 files changed, 943 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -66,6 +66,9 @@ config QCOM_PPE
+ depends on HAS_IOMEM && OF
+ depends on COMMON_CLK
+ select REGMAP_MMIO
++ select PHYLINK
++ select PCS_QCOM_IPQ_UNIPHY
++ select SFP
+ help
+ This driver supports the Qualcomm Technologies, Inc. packet
+ process engine (PPE) available with IPQ SoC. The PPE includes
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -4,4 +4,4 @@
+ #
+
+ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+-qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o
++qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -17,6 +17,7 @@
+ #include "ppe.h"
+ #include "ppe_config.h"
+ #include "ppe_debugfs.h"
++#include "ppe_port.h"
+
+ #define PPE_PORT_MAX 8
+ #define PPE_CLK_RATE 353000000
+@@ -200,6 +201,11 @@ static int qcom_ppe_probe(struct platfor
+ if (ret)
+ return dev_err_probe(dev, ret, "PPE HW config failed\n");
+
++ ret = ppe_port_mac_init(ppe_dev);
++ if (ret)
++ return dev_err_probe(dev, ret,
++ "PPE Port MAC initialization failed\n");
++
+ ppe_debugfs_setup(ppe_dev);
+ platform_set_drvdata(pdev, ppe_dev);
+
+@@ -212,6 +218,9 @@ static void qcom_ppe_remove(struct platf
+
+ ppe_dev = platform_get_drvdata(pdev);
+ ppe_debugfs_teardown(ppe_dev);
++ ppe_port_mac_deinit(ppe_dev);
++
++ platform_set_drvdata(pdev, NULL);
+ }
+
+ static const struct of_device_id qcom_ppe_of_match[] = {
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.h
+@@ -20,6 +20,7 @@ struct dentry;
+ * @clk_rate: PPE clock rate.
+ * @num_ports: Number of PPE ports.
+ * @debugfs_root: Debugfs root entry.
++ * @ports: PPE MAC ports.
+ * @num_icc_paths: Number of interconnect paths.
+ * @icc_paths: Interconnect path array.
+ *
+@@ -33,6 +34,7 @@ struct ppe_device {
+ unsigned long clk_rate;
+ unsigned int num_ports;
+ struct dentry *debugfs_root;
++ struct ppe_ports *ports;
+ unsigned int num_icc_paths;
+ struct icc_bulk_data icc_paths[] __counted_by(num_icc_paths);
+ };
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -0,0 +1,728 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* PPE Port MAC initialization and PPE port MAC functions. */
++
++#include <linux/clk.h>
++#include <linux/of_net.h>
++#include <linux/pcs/pcs-qcom-ipq9574.h>
++#include <linux/phylink.h>
++#include <linux/reset.h>
++#include <linux/regmap.h>
++#include <linux/rtnetlink.h>
++
++#include "ppe.h"
++#include "ppe_port.h"
++#include "ppe_regs.h"
++
++/* PPE MAC max frame size which including 4bytes FCS */
++#define PPE_PORT_MAC_MAX_FRAME_SIZE 0x3000
++
++/* PPE BM port start for PPE MAC ports */
++#define PPE_BM_PORT_MAC_START 7
++
++/* PPE port clock and reset name */
++static const char * const ppe_port_clk_rst_name[] = {
++ [PPE_PORT_CLK_RST_MAC] = "port_mac",
++ [PPE_PORT_CLK_RST_RX] = "port_rx",
++ [PPE_PORT_CLK_RST_TX] = "port_tx",
++};
++
++/* PPE port and MAC reset */
++static int ppe_port_mac_reset(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret;
++
++ ret = reset_control_assert(ppe_port->rstcs[PPE_PORT_CLK_RST_MAC]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_assert(ppe_port->rstcs[PPE_PORT_CLK_RST_RX]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_assert(ppe_port->rstcs[PPE_PORT_CLK_RST_TX]);
++ if (ret)
++ goto error;
++
++ /* 150ms delay is required by hardware to reset PPE port and MAC */
++ msleep(150);
++
++ ret = reset_control_deassert(ppe_port->rstcs[PPE_PORT_CLK_RST_MAC]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_deassert(ppe_port->rstcs[PPE_PORT_CLK_RST_RX]);
++ if (ret)
++ goto error;
++
++ ret = reset_control_deassert(ppe_port->rstcs[PPE_PORT_CLK_RST_TX]);
++ if (ret)
++ goto error;
++
++ return ret;
++
++error:
++ dev_err(ppe_dev->dev, "%s: port %d reset fail %d\n",
++ __func__, ppe_port->port_id, ret);
++ return ret;
++}
++
++/* PPE port MAC configuration for phylink */
++static void ppe_port_mac_config(struct phylink_config *config,
++ unsigned int mode,
++ const struct phylink_link_state *state)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int port = ppe_port->port_id;
++ enum ppe_mac_type mac_type;
++ u32 val, mask;
++ int ret;
++
++ switch (state->interface) {
++ case PHY_INTERFACE_MODE_2500BASEX:
++ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
++ case PHY_INTERFACE_MODE_10G_QXGMII:
++ mac_type = PPE_MAC_TYPE_XGMAC;
++ break;
++ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
++ case PHY_INTERFACE_MODE_SGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ mac_type = PPE_MAC_TYPE_GMAC;
++ break;
++ default:
++ dev_err(ppe_dev->dev, "%s: Unsupport interface %s\n",
++ __func__, phy_modes(state->interface));
++ return;
++ }
++
++ /* Reset Port MAC for GMAC */
++ if (mac_type == PPE_MAC_TYPE_GMAC) {
++ ret = ppe_port_mac_reset(ppe_port);
++ if (ret)
++ goto err_mac_config;
++ }
++
++ /* Port mux to select GMAC or XGMAC */
++ mask = PPE_PORT_SEL_XGMAC(port);
++ val = mac_type == PPE_MAC_TYPE_GMAC ? 0 : mask;
++ ret = regmap_update_bits(ppe_dev->regmap,
++ PPE_PORT_MUX_CTRL_ADDR,
++ mask, val);
++ if (ret)
++ goto err_mac_config;
++
++ ppe_port->mac_type = mac_type;
++
++ return;
++
++err_mac_config:
++ dev_err(ppe_dev->dev, "%s: port %d MAC config fail %d\n",
++ __func__, port, ret);
++}
++
++/* PPE port GMAC link up configuration */
++static int ppe_port_gmac_link_up(struct ppe_port *ppe_port, int speed,
++ int duplex, bool tx_pause, bool rx_pause)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ /* Set GMAC speed */
++ switch (speed) {
++ case SPEED_1000:
++ val = GMAC_SPEED_1000;
++ break;
++ case SPEED_100:
++ val = GMAC_SPEED_100;
++ break;
++ case SPEED_10:
++ val = GMAC_SPEED_10;
++ break;
++ default:
++ dev_err(ppe_dev->dev, "%s: Invalid GMAC speed %s\n",
++ __func__, phy_speed_to_str(speed));
++ return -EINVAL;
++ }
++
++ reg = PPE_PORT_GMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_SPEED_ADDR,
++ GMAC_SPEED_M, val);
++ if (ret)
++ return ret;
++
++ /* Set duplex, flow control and enable GMAC */
++ val = GMAC_TRXEN;
++ if (duplex == DUPLEX_FULL)
++ val |= GMAC_DUPLEX_FULL;
++ if (tx_pause)
++ val |= GMAC_TXFCEN;
++ if (rx_pause)
++ val |= GMAC_RXFCEN;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_ENABLE_ADDR,
++ GMAC_ENABLE_ALL, val);
++
++ return ret;
++}
++
++/* PPE port XGMAC link up configuration */
++static int ppe_port_xgmac_link_up(struct ppe_port *ppe_port,
++ phy_interface_t interface,
++ int speed, int duplex,
++ bool tx_pause, bool rx_pause)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ /* Set XGMAC TX speed and enable TX */
++ switch (speed) {
++ case SPEED_10000:
++ if (interface == PHY_INTERFACE_MODE_USXGMII)
++ val = XGMAC_SPEED_10000_USXGMII;
++ else
++ val = XGMAC_SPEED_10000;
++ break;
++ case SPEED_5000:
++ val = XGMAC_SPEED_5000;
++ break;
++ case SPEED_2500:
++ if (interface == PHY_INTERFACE_MODE_USXGMII ||
++ interface == PHY_INTERFACE_MODE_10G_QXGMII)
++ val = XGMAC_SPEED_2500_USXGMII;
++ else
++ val = XGMAC_SPEED_2500;
++ break;
++ case SPEED_1000:
++ val = XGMAC_SPEED_1000;
++ break;
++ case SPEED_100:
++ val = XGMAC_SPEED_100;
++ break;
++ case SPEED_10:
++ val = XGMAC_SPEED_10;
++ break;
++ default:
++ dev_err(ppe_dev->dev, "%s: Invalid XGMAC speed %s\n",
++ __func__, phy_speed_to_str(speed));
++ return -EINVAL;
++ }
++
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ val |= XGMAC_TXEN;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_TX_CONFIG_ADDR,
++ XGMAC_SPEED_M | XGMAC_TXEN, val);
++ if (ret)
++ return ret;
++
++ /* Set XGMAC TX flow control */
++ val = FIELD_PREP(XGMAC_PAUSE_TIME_M, FIELD_MAX(XGMAC_PAUSE_TIME_M));
++ val |= tx_pause ? XGMAC_TXFCEN : 0;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_TX_FLOW_CTRL_ADDR,
++ XGMAC_PAUSE_TIME_M | XGMAC_TXFCEN, val);
++ if (ret)
++ return ret;
++
++ /* Set XGMAC RX flow control */
++ val = rx_pause ? XGMAC_RXFCEN : 0;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_RX_FLOW_CTRL_ADDR,
++ XGMAC_RXFCEN, val);
++ if (ret)
++ return ret;
++
++ /* Enable XGMAC RX*/
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_RX_CONFIG_ADDR,
++ XGMAC_RXEN, XGMAC_RXEN);
++
++ return ret;
++}
++
++/* PPE port MAC link up configuration for phylink */
++static void ppe_port_mac_link_up(struct phylink_config *config,
++ struct phy_device *phy,
++ unsigned int mode,
++ phy_interface_t interface,
++ int speed, int duplex,
++ bool tx_pause, bool rx_pause)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ enum ppe_mac_type mac_type = ppe_port->mac_type;
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ if (mac_type == PPE_MAC_TYPE_GMAC)
++ ret = ppe_port_gmac_link_up(ppe_port,
++ speed, duplex, tx_pause, rx_pause);
++ else
++ ret = ppe_port_xgmac_link_up(ppe_port, interface,
++ speed, duplex, tx_pause, rx_pause);
++ if (ret)
++ goto err_port_mac_link_up;
++
++ /* Set PPE port BM flow control */
++ reg = PPE_BM_PORT_FC_MODE_ADDR +
++ PPE_BM_PORT_FC_MODE_INC * (port + PPE_BM_PORT_MAC_START);
++ val = tx_pause ? PPE_BM_PORT_FC_MODE_EN : 0;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_BM_PORT_FC_MODE_EN, val);
++ if (ret)
++ goto err_port_mac_link_up;
++
++ /* Enable PPE port TX */
++ reg = PPE_PORT_BRIDGE_CTRL_ADDR + PPE_PORT_BRIDGE_CTRL_INC * port;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_PORT_BRIDGE_TXMAC_EN,
++ PPE_PORT_BRIDGE_TXMAC_EN);
++ if (ret)
++ goto err_port_mac_link_up;
++
++ return;
++
++err_port_mac_link_up:
++ dev_err(ppe_dev->dev, "%s: port %d link up fail %d\n",
++ __func__, port, ret);
++}
++
++/* PPE port MAC link down configuration for phylink */
++static void ppe_port_mac_link_down(struct phylink_config *config,
++ unsigned int mode,
++ phy_interface_t interface)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ enum ppe_mac_type mac_type = ppe_port->mac_type;
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg;
++
++ /* Disable PPE port TX */
++ reg = PPE_PORT_BRIDGE_CTRL_ADDR + PPE_PORT_BRIDGE_CTRL_INC * port;
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_PORT_BRIDGE_TXMAC_EN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++
++ /* Disable PPE MAC */
++ if (mac_type == PPE_MAC_TYPE_GMAC) {
++ reg = PPE_PORT_GMAC_ADDR(port) + GMAC_ENABLE_ADDR;
++ ret = regmap_update_bits(ppe_dev->regmap, reg, GMAC_TRXEN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++ } else {
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap,
++ reg + XGMAC_RX_CONFIG_ADDR,
++ XGMAC_RXEN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++
++ ret = regmap_update_bits(ppe_dev->regmap,
++ reg + XGMAC_TX_CONFIG_ADDR,
++ XGMAC_TXEN, 0);
++ if (ret)
++ goto err_port_mac_link_down;
++ }
++
++ return;
++
++err_port_mac_link_down:
++ dev_err(ppe_dev->dev, "%s: port %d link down fail %d\n",
++ __func__, port, ret);
++}
++
++/* PPE port MAC PCS selection for phylink */
++static
++struct phylink_pcs *ppe_port_mac_select_pcs(struct phylink_config *config,
++ phy_interface_t interface)
++{
++ struct ppe_port *ppe_port = container_of(config, struct ppe_port,
++ phylink_config);
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 val;
++
++ /* PPE port5 can connects with PCS0 or PCS1. In PSGMII
++ * mode, it selects PCS0; otherwise, it selects PCS1.
++ */
++ if (port == 5) {
++ val = interface == PHY_INTERFACE_MODE_PSGMII ?
++ 0 : PPE_PORT5_SEL_PCS1;
++ ret = regmap_update_bits(ppe_dev->regmap,
++ PPE_PORT_MUX_CTRL_ADDR,
++ PPE_PORT5_SEL_PCS1, val);
++ if (ret) {
++ dev_err(ppe_dev->dev, "%s: port5 select PCS fail %d\n",
++ __func__, ret);
++ return NULL;
++ }
++ }
++
++ return ppe_port->pcs;
++}
++
++static const struct phylink_mac_ops ppe_phylink_ops = {
++ .mac_config = ppe_port_mac_config,
++ .mac_link_up = ppe_port_mac_link_up,
++ .mac_link_down = ppe_port_mac_link_down,
++ .mac_select_pcs = ppe_port_mac_select_pcs,
++};
++
++/**
++ * ppe_port_phylink_setup() - Set phylink instance for the given PPE port
++ * @ppe_port: PPE port
++ * @netdev: Netdevice
++ *
++ * Description: Wrapper function to help setup phylink for the PPE port
++ * specified by @ppe_port and associated with the net device @netdev.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_phylink_setup(struct ppe_port *ppe_port, struct net_device *netdev)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ struct device_node *pcs_node;
++ int ret;
++
++ /* Create PCS */
++ pcs_node = of_parse_phandle(ppe_port->np, "pcs-handle", 0);
++ if (!pcs_node)
++ return -ENODEV;
++
++ ppe_port->pcs = ipq_pcs_get(pcs_node);
++ of_node_put(pcs_node);
++ if (IS_ERR(ppe_port->pcs)) {
++ dev_err(ppe_dev->dev, "%s: port %d failed to create PCS\n",
++ __func__, ppe_port->port_id);
++ return PTR_ERR(ppe_port->pcs);
++ }
++
++ /* Port phylink capability */
++ ppe_port->phylink_config.dev = &netdev->dev;
++ ppe_port->phylink_config.type = PHYLINK_NETDEV;
++ ppe_port->phylink_config.mac_capabilities = MAC_ASYM_PAUSE |
++ MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000 |
++ MAC_2500FD | MAC_5000FD | MAC_10000FD;
++ __set_bit(PHY_INTERFACE_MODE_QSGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_PSGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_SGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_USXGMII,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_10GBASER,
++ ppe_port->phylink_config.supported_interfaces);
++ __set_bit(PHY_INTERFACE_MODE_10G_QXGMII,
++ ppe_port->phylink_config.supported_interfaces);
++
++ /* Create phylink */
++ ppe_port->phylink = phylink_create(&ppe_port->phylink_config,
++ of_fwnode_handle(ppe_port->np),
++ ppe_port->interface,
++ &ppe_phylink_ops);
++ if (IS_ERR(ppe_port->phylink)) {
++ dev_err(ppe_dev->dev, "%s: port %d failed to create phylink\n",
++ __func__, ppe_port->port_id);
++ ret = PTR_ERR(ppe_port->phylink);
++ goto err_free_pcs;
++ }
++
++ /* Connect phylink */
++ ret = phylink_of_phy_connect(ppe_port->phylink, ppe_port->np, 0);
++ if (ret) {
++ dev_err(ppe_dev->dev, "%s: port %d failed to connect phylink\n",
++ __func__, ppe_port->port_id);
++ goto err_free_phylink;
++ }
++
++ return 0;
++
++err_free_phylink:
++ phylink_destroy(ppe_port->phylink);
++ ppe_port->phylink = NULL;
++err_free_pcs:
++ ipq_pcs_put(ppe_port->pcs);
++ ppe_port->pcs = NULL;
++ return ret;
++}
++
++/**
++ * ppe_port_phylink_destroy() - Destroy phylink instance for the given PPE port
++ * @ppe_port: PPE port
++ *
++ * Description: Wrapper function to help destroy phylink for the PPE port
++ * specified by @ppe_port.
++ */
++void ppe_port_phylink_destroy(struct ppe_port *ppe_port)
++{
++ /* Destroy phylink */
++ if (ppe_port->phylink) {
++ rtnl_lock();
++ phylink_disconnect_phy(ppe_port->phylink);
++ rtnl_unlock();
++ phylink_destroy(ppe_port->phylink);
++ ppe_port->phylink = NULL;
++ }
++
++ /* Destroy PCS */
++ if (ppe_port->pcs) {
++ ipq_pcs_put(ppe_port->pcs);
++ ppe_port->pcs = NULL;
++ }
++}
++
++/* PPE port clock initialization */
++static int ppe_port_clock_init(struct ppe_port *ppe_port)
++{
++ struct device_node *port_node = ppe_port->np;
++ struct reset_control *rstc;
++ struct clk *clk;
++ int i, j, ret;
++
++ for (i = 0; i < PPE_PORT_CLK_RST_MAX; i++) {
++ /* Get PPE port resets which will be used to reset PPE
++ * port and MAC.
++ */
++ rstc = of_reset_control_get_exclusive(port_node,
++ ppe_port_clk_rst_name[i]);
++ if (IS_ERR(rstc)) {
++ ret = PTR_ERR(rstc);
++ goto err_rst;
++ }
++
++ clk = of_clk_get_by_name(port_node, ppe_port_clk_rst_name[i]);
++ if (IS_ERR(clk)) {
++ ret = PTR_ERR(clk);
++ goto err_clk_get;
++ }
++
++ ret = clk_prepare_enable(clk);
++ if (ret)
++ goto err_clk_en;
++
++ ppe_port->clks[i] = clk;
++ ppe_port->rstcs[i] = rstc;
++ }
++
++ return 0;
++
++err_clk_en:
++ clk_put(clk);
++err_clk_get:
++ reset_control_put(rstc);
++err_rst:
++ for (j = 0; j < i; j++) {
++ clk_disable_unprepare(ppe_port->clks[j]);
++ clk_put(ppe_port->clks[j]);
++ reset_control_put(ppe_port->rstcs[j]);
++ }
++
++ return ret;
++}
++
++/* PPE port clock deinitialization */
++static void ppe_port_clock_deinit(struct ppe_port *ppe_port)
++{
++ int i;
++
++ for (i = 0; i < PPE_PORT_CLK_RST_MAX; i++) {
++ clk_disable_unprepare(ppe_port->clks[i]);
++ clk_put(ppe_port->clks[i]);
++ reset_control_put(ppe_port->rstcs[i]);
++ }
++}
++
++/* PPE port MAC hardware init configuration */
++static int ppe_port_mac_hw_init(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int ret, port = ppe_port->port_id;
++ u32 reg, val;
++
++ /* GMAC RX and TX are initialized as disabled */
++ reg = PPE_PORT_GMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap,
++ reg + GMAC_ENABLE_ADDR, GMAC_TRXEN, 0);
++ if (ret)
++ return ret;
++
++ /* GMAC max frame size configuration */
++ val = FIELD_PREP(GMAC_JUMBO_SIZE_M, PPE_PORT_MAC_MAX_FRAME_SIZE);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_JUMBO_SIZE_ADDR,
++ GMAC_JUMBO_SIZE_M, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(GMAC_MAXFRAME_SIZE_M, PPE_PORT_MAC_MAX_FRAME_SIZE);
++ val |= FIELD_PREP(GMAC_TX_THD_M, 0x1);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_CTRL_ADDR,
++ GMAC_CTRL_MASK, val);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(GMAC_HIGH_IPG_M, 0xc);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_DBG_CTRL_ADDR,
++ GMAC_HIGH_IPG_M, val);
++ if (ret)
++ return ret;
++
++ /* Enable and reset GMAC MIB counters and set as read clear
++ * mode, the GMAC MIB counters will be cleared after reading.
++ */
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_MIB_CTRL_ADDR,
++ GMAC_MIB_CTRL_MASK, GMAC_MIB_CTRL_MASK);
++ if (ret)
++ return ret;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + GMAC_MIB_CTRL_ADDR,
++ GMAC_MIB_RST, 0);
++ if (ret)
++ return ret;
++
++ /* XGMAC RX and TX disabled and max frame size configuration */
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_TX_CONFIG_ADDR,
++ XGMAC_TXEN | XGMAC_JD, XGMAC_JD);
++ if (ret)
++ return ret;
++
++ val = FIELD_PREP(XGMAC_GPSL_M, PPE_PORT_MAC_MAX_FRAME_SIZE);
++ val |= XGMAC_GPSLEN;
++ val |= XGMAC_CST;
++ val |= XGMAC_ACS;
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_RX_CONFIG_ADDR,
++ XGMAC_RX_CONFIG_MASK, val);
++ if (ret)
++ return ret;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_WD_TIMEOUT_ADDR,
++ XGMAC_WD_TIMEOUT_MASK, XGMAC_WD_TIMEOUT_VAL);
++ if (ret)
++ return ret;
++
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_PKT_FILTER_ADDR,
++ XGMAC_PKT_FILTER_MASK, XGMAC_PKT_FILTER_VAL);
++ if (ret)
++ return ret;
++
++ /* Enable and reset XGMAC MIB counters */
++ ret = regmap_update_bits(ppe_dev->regmap, reg + XGMAC_MMC_CTRL_ADDR,
++ XGMAC_MCF | XGMAC_CNTRST, XGMAC_CNTRST);
++
++ return ret;
++}
++
++/**
++ * ppe_port_mac_init() - Initialization of PPE ports for the PPE device
++ * @ppe_dev: PPE device
++ *
++ * Description: Initialize the PPE MAC ports on the PPE device specified
++ * by @ppe_dev.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_mac_init(struct ppe_device *ppe_dev)
++{
++ struct device_node *ports_node, *port_node;
++ int port, num, ret, j, i = 0;
++ struct ppe_ports *ppe_ports;
++ phy_interface_t phy_mode;
++
++ ports_node = of_get_child_by_name(ppe_dev->dev->of_node,
++ "ethernet-ports");
++ if (!ports_node) {
++ dev_err(ppe_dev->dev, "Failed to get ports node\n");
++ return -ENODEV;
++ }
++
++ num = of_get_available_child_count(ports_node);
++
++ ppe_ports = devm_kzalloc(ppe_dev->dev,
++ struct_size(ppe_ports, port, num),
++ GFP_KERNEL);
++ if (!ppe_ports) {
++ ret = -ENOMEM;
++ goto err_ports_node;
++ }
++
++ ppe_dev->ports = ppe_ports;
++ ppe_ports->num = num;
++
++ for_each_available_child_of_node(ports_node, port_node) {
++ ret = of_property_read_u32(port_node, "reg", &port);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to get port id\n");
++ goto err_port_node;
++ }
++
++ ret = of_get_phy_mode(port_node, &phy_mode);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to get phy mode\n");
++ goto err_port_node;
++ }
++
++ ppe_ports->port[i].ppe_dev = ppe_dev;
++ ppe_ports->port[i].port_id = port;
++ ppe_ports->port[i].np = port_node;
++ ppe_ports->port[i].interface = phy_mode;
++
++ ret = ppe_port_clock_init(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to initialize port clocks\n");
++ goto err_port_clk;
++ }
++
++ ret = ppe_port_mac_hw_init(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to initialize MAC hardware\n");
++ goto err_port_node;
++ }
++
++ i++;
++ }
++
++ of_node_put(ports_node);
++ return 0;
++
++err_port_clk:
++ for (j = 0; j < i; j++)
++ ppe_port_clock_deinit(&ppe_ports->port[j]);
++err_port_node:
++ of_node_put(port_node);
++err_ports_node:
++ of_node_put(ports_node);
++ return ret;
++}
++
++/**
++ * ppe_port_mac_deinit() - Deinitialization of PPE ports for the PPE device
++ * @ppe_dev: PPE device
++ *
++ * Description: Deinitialize the PPE MAC ports on the PPE device specified
++ * by @ppe_dev.
++ */
++void ppe_port_mac_deinit(struct ppe_device *ppe_dev)
++{
++ struct ppe_port *ppe_port;
++ int i;
++
++ for (i = 0; i < ppe_dev->ports->num; i++) {
++ ppe_port = &ppe_dev->ports->port[i];
++ ppe_port_clock_deinit(ppe_port);
++ }
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -0,0 +1,76 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ *
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __PPE_PORT_H__
++#define __PPE_PORT_H__
++
++#include <linux/phylink.h>
++
++/**
++ * enum ppe_port_clk_rst_type - PPE port clock and reset ID type
++ * @PPE_PORT_CLK_RST_MAC: The clock and reset ID for port MAC
++ * @PPE_PORT_CLK_RST_RX: The clock and reset ID for port receive path
++ * @PPE_PORT_CLK_RST_TX: The clock and reset for port transmit path
++ * @PPE_PORT_CLK_RST_MAX: The maximum of port clock and reset
++ */
++enum ppe_port_clk_rst_type {
++ PPE_PORT_CLK_RST_MAC,
++ PPE_PORT_CLK_RST_RX,
++ PPE_PORT_CLK_RST_TX,
++ PPE_PORT_CLK_RST_MAX,
++};
++
++/**
++ * enum ppe_mac_type - PPE MAC type
++ * @PPE_MAC_TYPE_GMAC: GMAC type
++ * @PPE_MAC_TYPE_XGMAC: XGMAC type
++ */
++enum ppe_mac_type {
++ PPE_MAC_TYPE_GMAC,
++ PPE_MAC_TYPE_XGMAC,
++};
++
++/**
++ * struct ppe_port - Private data for each PPE port
++ * @phylink: Linux phylink instance
++ * @phylink_config: Linux phylink configurations
++ * @pcs: Linux phylink PCS instance
++ * @np: Port device tree node
++ * @ppe_dev: Back pointer to PPE device private data
++ * @interface: Port interface mode
++ * @mac_type: Port MAC type, GMAC or XGMAC
++ * @port_id: Port ID
++ * @clks: Port clocks
++ * @rstcs: Port resets
++ */
++struct ppe_port {
++ struct phylink *phylink;
++ struct phylink_config phylink_config;
++ struct phylink_pcs *pcs;
++ struct device_node *np;
++ struct ppe_device *ppe_dev;
++ phy_interface_t interface;
++ enum ppe_mac_type mac_type;
++ int port_id;
++ struct clk *clks[PPE_PORT_CLK_RST_MAX];
++ struct reset_control *rstcs[PPE_PORT_CLK_RST_MAX];
++};
++
++/**
++ * struct ppe_ports - Array of PPE ports
++ * @num: Number of PPE ports
++ * @port: Each PPE port private data
++ */
++struct ppe_ports {
++ unsigned int num;
++ struct ppe_port port[] __counted_by(num);
++};
++
++int ppe_port_mac_init(struct ppe_device *ppe_dev);
++void ppe_port_mac_deinit(struct ppe_device *ppe_dev);
++int ppe_port_phylink_setup(struct ppe_port *ppe_port,
++ struct net_device *netdev);
++void ppe_port_phylink_destroy(struct ppe_port *ppe_port);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -9,6 +9,17 @@
+
+ #include <linux/bitfield.h>
+
++/* PPE port mux select control register */
++#define PPE_PORT_MUX_CTRL_ADDR 0x10
++#define PPE_PORT6_SEL_XGMAC BIT(13)
++#define PPE_PORT5_SEL_XGMAC BIT(12)
++#define PPE_PORT4_SEL_XGMAC BIT(11)
++#define PPE_PORT3_SEL_XGMAC BIT(10)
++#define PPE_PORT2_SEL_XGMAC BIT(9)
++#define PPE_PORT1_SEL_XGMAC BIT(8)
++#define PPE_PORT5_SEL_PCS1 BIT(4)
++#define PPE_PORT_SEL_XGMAC(x) (BIT(8) << ((x) - 1))
++
+ /* PPE scheduler configurations for buffer manager block. */
+ #define PPE_BM_SCH_CTRL_ADDR 0xb000
+ #define PPE_BM_SCH_CTRL_INC 4
+@@ -556,4 +567,117 @@
+ #define PPE_ENQ_OPR_TBL_ENTRIES 300
+ #define PPE_ENQ_OPR_TBL_INC 0x10
+ #define PPE_ENQ_OPR_TBL_ENQ_DISABLE BIT(0)
++
++/* PPE GMAC and XGMAC register base address */
++#define PPE_PORT_GMAC_ADDR(x) (0x001000 + ((x) - 1) * 0x200)
++#define PPE_PORT_XGMAC_ADDR(x) (0x500000 + ((x) - 1) * 0x4000)
++
++/* GMAC enable register */
++#define GMAC_ENABLE_ADDR 0x0
++#define GMAC_TXFCEN BIT(6)
++#define GMAC_RXFCEN BIT(5)
++#define GMAC_DUPLEX_FULL BIT(4)
++#define GMAC_TXEN BIT(1)
++#define GMAC_RXEN BIT(0)
++
++#define GMAC_TRXEN \
++ (GMAC_TXEN | GMAC_RXEN)
++#define GMAC_ENABLE_ALL \
++ (GMAC_TXFCEN | GMAC_RXFCEN | GMAC_DUPLEX_FULL | GMAC_TXEN | GMAC_RXEN)
++
++/* GMAC speed register */
++#define GMAC_SPEED_ADDR 0x4
++#define GMAC_SPEED_M GENMASK(1, 0)
++#define GMAC_SPEED_10 0
++#define GMAC_SPEED_100 1
++#define GMAC_SPEED_1000 2
++
++/* GMAC control register */
++#define GMAC_CTRL_ADDR 0x18
++#define GMAC_TX_THD_M GENMASK(27, 24)
++#define GMAC_MAXFRAME_SIZE_M GENMASK(21, 8)
++#define GMAC_CRS_SEL BIT(6)
++
++#define GMAC_CTRL_MASK \
++ (GMAC_TX_THD_M | GMAC_MAXFRAME_SIZE_M | GMAC_CRS_SEL)
++
++/* GMAC debug control register */
++#define GMAC_DBG_CTRL_ADDR 0x1c
++#define GMAC_HIGH_IPG_M GENMASK(15, 8)
++
++/* GMAC jumbo size register */
++#define GMAC_JUMBO_SIZE_ADDR 0x30
++#define GMAC_JUMBO_SIZE_M GENMASK(13, 0)
++
++/* GMAC MIB control register */
++#define GMAC_MIB_CTRL_ADDR 0x34
++#define GMAC_MIB_RD_CLR BIT(2)
++#define GMAC_MIB_RST BIT(1)
++#define GMAC_MIB_EN BIT(0)
++
++#define GMAC_MIB_CTRL_MASK \
++ (GMAC_MIB_RD_CLR | GMAC_MIB_RST | GMAC_MIB_EN)
++
++/* XGMAC TX configuration register */
++#define XGMAC_TX_CONFIG_ADDR 0x0
++#define XGMAC_SPEED_M GENMASK(31, 29)
++#define XGMAC_SPEED_10000_USXGMII FIELD_PREP(XGMAC_SPEED_M, 4)
++#define XGMAC_SPEED_10000 FIELD_PREP(XGMAC_SPEED_M, 0)
++#define XGMAC_SPEED_5000 FIELD_PREP(XGMAC_SPEED_M, 5)
++#define XGMAC_SPEED_2500_USXGMII FIELD_PREP(XGMAC_SPEED_M, 6)
++#define XGMAC_SPEED_2500 FIELD_PREP(XGMAC_SPEED_M, 2)
++#define XGMAC_SPEED_1000 FIELD_PREP(XGMAC_SPEED_M, 3)
++#define XGMAC_SPEED_100 XGMAC_SPEED_1000
++#define XGMAC_SPEED_10 XGMAC_SPEED_1000
++#define XGMAC_JD BIT(16)
++#define XGMAC_TXEN BIT(0)
++
++/* XGMAC RX configuration register */
++#define XGMAC_RX_CONFIG_ADDR 0x4
++#define XGMAC_GPSL_M GENMASK(29, 16)
++#define XGMAC_WD BIT(7)
++#define XGMAC_GPSLEN BIT(6)
++#define XGMAC_CST BIT(2)
++#define XGMAC_ACS BIT(1)
++#define XGMAC_RXEN BIT(0)
++
++#define XGMAC_RX_CONFIG_MASK \
++ (XGMAC_GPSL_M | XGMAC_WD | XGMAC_GPSLEN | XGMAC_CST | \
++ XGMAC_ACS | XGMAC_RXEN)
++
++/* XGMAC packet filter register */
++#define XGMAC_PKT_FILTER_ADDR 0x8
++#define XGMAC_RA BIT(31)
++#define XGMAC_PCF_M GENMASK(7, 6)
++#define XGMAC_PR BIT(0)
++
++#define XGMAC_PKT_FILTER_MASK \
++ (XGMAC_RA | XGMAC_PCF_M | XGMAC_PR)
++#define XGMAC_PKT_FILTER_VAL \
++ (XGMAC_RA | XGMAC_PR | FIELD_PREP(XGMAC_PCF_M, 0x2))
++
++/* XGMAC watchdog timeout register */
++#define XGMAC_WD_TIMEOUT_ADDR 0xc
++#define XGMAC_PWE BIT(8)
++#define XGMAC_WTO_M GENMASK(3, 0)
++
++#define XGMAC_WD_TIMEOUT_MASK \
++ (XGMAC_PWE | XGMAC_WTO_M)
++#define XGMAC_WD_TIMEOUT_VAL \
++ (XGMAC_PWE | FIELD_PREP(XGMAC_WTO_M, 0xb))
++
++/* XGMAC TX flow control register */
++#define XGMAC_TX_FLOW_CTRL_ADDR 0x70
++#define XGMAC_PAUSE_TIME_M GENMASK(31, 16)
++#define XGMAC_TXFCEN BIT(1)
++
++/* XGMAC RX flow control register */
++#define XGMAC_RX_FLOW_CTRL_ADDR 0x90
++#define XGMAC_RXFCEN BIT(0)
++
++/* XGMAC management counters control register */
++#define XGMAC_MMC_CTRL_ADDR 0x800
++#define XGMAC_MCF BIT(3)
++#define XGMAC_CNTRST BIT(0)
++
+ #endif
--- /dev/null
+From dbcc0d01241a1353d8e11e764cf7fcd390ae3f1f Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Thu, 29 Feb 2024 20:16:14 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE port MAC MIB statistics
+ functions
+
+Add PPE port MAC MIB statistics functions which are used by netdev
+ops and ethtool. For GMAC, a polling task is scheduled to read the
+MIB counters periodically to avoid 32bit register counter overflow.
+
+Change-Id: Ic20e240061278f77d703f652e1f7d959db8fac37
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 465 +++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 13 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 91 ++++
+ 3 files changed, 569 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -23,6 +23,122 @@
+ /* PPE BM port start for PPE MAC ports */
+ #define PPE_BM_PORT_MAC_START 7
+
++/* Poll interval time to poll GMAC MIBs for overflow protection,
++ * the time should ensure that the 32bit GMAC packet counter
++ * register would not overflow within this time at line rate
++ * speed for 64B packet size.
++ */
++#define PPE_GMIB_POLL_INTERVAL_MS 120000
++
++#define PPE_MAC_MIB_DESC(_s, _o, _n) \
++ { \
++ .size = (_s), \
++ .offset = (_o), \
++ .name = (_n), \
++ }
++
++/* PPE MAC MIB description */
++struct ppe_mac_mib_info {
++ u32 size;
++ u32 offset;
++ const char *name;
++};
++
++/* PPE GMAC MIB statistics type */
++enum ppe_gmib_stats_type {
++ gmib_rx_broadcast,
++ gmib_rx_pause,
++ gmib_rx_multicast,
++ gmib_rx_fcserr,
++ gmib_rx_alignerr,
++ gmib_rx_runt,
++ gmib_rx_frag,
++ gmib_rx_jumbofcserr,
++ gmib_rx_jumboalignerr,
++ gmib_rx_pkt64,
++ gmib_rx_pkt65to127,
++ gmib_rx_pkt128to255,
++ gmib_rx_pkt256to511,
++ gmib_rx_pkt512to1023,
++ gmib_rx_pkt1024to1518,
++ gmib_rx_pkt1519tomax,
++ gmib_rx_toolong,
++ gmib_rx_bytes_g,
++ gmib_rx_bytes_b,
++ gmib_rx_unicast,
++ gmib_tx_broadcast,
++ gmib_tx_pause,
++ gmib_tx_multicast,
++ gmib_tx_underrun,
++ gmib_tx_pkt64,
++ gmib_tx_pkt65to127,
++ gmib_tx_pkt128to255,
++ gmib_tx_pkt256to511,
++ gmib_tx_pkt512to1023,
++ gmib_tx_pkt1024to1518,
++ gmib_tx_pkt1519tomax,
++ gmib_tx_bytes,
++ gmib_tx_collisions,
++ gmib_tx_abortcol,
++ gmib_tx_multicol,
++ gmib_tx_singlecol,
++ gmib_tx_excdeffer,
++ gmib_tx_deffer,
++ gmib_tx_latecol,
++ gmib_tx_unicast,
++};
++
++/* PPE XGMAC MIB statistics type */
++enum ppe_xgmib_stats_type {
++ xgmib_tx_bytes,
++ xgmib_tx_frames,
++ xgmib_tx_broadcast_g,
++ xgmib_tx_multicast_g,
++ xgmib_tx_pkt64,
++ xgmib_tx_pkt65to127,
++ xgmib_tx_pkt128to255,
++ xgmib_tx_pkt256to511,
++ xgmib_tx_pkt512to1023,
++ xgmib_tx_pkt1024tomax,
++ xgmib_tx_unicast,
++ xgmib_tx_multicast,
++ xgmib_tx_broadcast,
++ xgmib_tx_underflow_err,
++ xgmib_tx_bytes_g,
++ xgmib_tx_frames_g,
++ xgmib_tx_pause,
++ xgmib_tx_vlan_g,
++ xgmib_tx_lpi_usec,
++ xgmib_tx_lpi_tran,
++ xgmib_rx_frames,
++ xgmib_rx_bytes,
++ xgmib_rx_bytes_g,
++ xgmib_rx_broadcast_g,
++ xgmib_rx_multicast_g,
++ xgmib_rx_crc_err,
++ xgmib_rx_runt_err,
++ xgmib_rx_jabber_err,
++ xgmib_rx_undersize_g,
++ xgmib_rx_oversize_g,
++ xgmib_rx_pkt64,
++ xgmib_rx_pkt65to127,
++ xgmib_rx_pkt128to255,
++ xgmib_rx_pkt256to511,
++ xgmib_rx_pkt512to1023,
++ xgmib_rx_pkt1024tomax,
++ xgmib_rx_unicast_g,
++ xgmib_rx_len_err,
++ xgmib_rx_outofrange_err,
++ xgmib_rx_pause,
++ xgmib_rx_fifo_overflow,
++ xgmib_rx_vlan,
++ xgmib_rx_wdog_err,
++ xgmib_rx_lpi_usec,
++ xgmib_rx_lpi_tran,
++ xgmib_rx_drop_frames,
++ xgmib_rx_drop_bytes,
++};
++
+ /* PPE port clock and reset name */
+ static const char * const ppe_port_clk_rst_name[] = {
+ [PPE_PORT_CLK_RST_MAC] = "port_mac",
+@@ -30,6 +146,322 @@ static const char * const ppe_port_clk_r
+ [PPE_PORT_CLK_RST_TX] = "port_tx",
+ };
+
++/* PPE GMAC MIB statistics description information */
++static const struct ppe_mac_mib_info gmib_info[] = {
++ PPE_MAC_MIB_DESC(4, GMAC_RXBROAD_ADDR, "rx_broadcast"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPAUSE_ADDR, "rx_pause"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXMULTI_ADDR, "rx_multicast"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXFCSERR_ADDR, "rx_fcserr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXALIGNERR_ADDR, "rx_alignerr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXRUNT_ADDR, "rx_runt"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXFRAG_ADDR, "rx_frag"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXJUMBOFCSERR_ADDR, "rx_jumbofcserr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXJUMBOALIGNERR_ADDR, "rx_jumboalignerr"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT64_ADDR, "rx_pkt64"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT65TO127_ADDR, "rx_pkt65to127"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT128TO255_ADDR, "rx_pkt128to255"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT256TO511_ADDR, "rx_pkt256to511"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT512TO1023_ADDR, "rx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT1024TO1518_ADDR, "rx_pkt1024to1518"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXPKT1519TOX_ADDR, "rx_pkt1519tomax"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXTOOLONG_ADDR, "rx_toolong"),
++ PPE_MAC_MIB_DESC(8, GMAC_RXBYTE_G_ADDR, "rx_bytes_g"),
++ PPE_MAC_MIB_DESC(8, GMAC_RXBYTE_B_ADDR, "rx_bytes_b"),
++ PPE_MAC_MIB_DESC(4, GMAC_RXUNI_ADDR, "rx_unicast"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXBROAD_ADDR, "tx_broadcast"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPAUSE_ADDR, "tx_pause"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXMULTI_ADDR, "tx_multicast"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXUNDERRUN_ADDR, "tx_underrun"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT64_ADDR, "tx_pkt64"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT65TO127_ADDR, "tx_pkt65to127"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT128TO255_ADDR, "tx_pkt128to255"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT256TO511_ADDR, "tx_pkt256to511"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT512TO1023_ADDR, "tx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT1024TO1518_ADDR, "tx_pkt1024to1518"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXPKT1519TOX_ADDR, "tx_pkt1519tomax"),
++ PPE_MAC_MIB_DESC(8, GMAC_TXBYTE_ADDR, "tx_bytes"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXCOLLISIONS_ADDR, "tx_collisions"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXABORTCOL_ADDR, "tx_abortcol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXMULTICOL_ADDR, "tx_multicol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXSINGLECOL_ADDR, "tx_singlecol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXEXCESSIVEDEFER_ADDR, "tx_excdeffer"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXDEFER_ADDR, "tx_deffer"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXLATECOL_ADDR, "tx_latecol"),
++ PPE_MAC_MIB_DESC(4, GMAC_TXUNI_ADDR, "tx_unicast"),
++};
++
++/* PPE XGMAC MIB statistics description information */
++static const struct ppe_mac_mib_info xgmib_info[] = {
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBYTE_GB_ADDR, "tx_bytes"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT_GB_ADDR, "tx_frames"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBROAD_G_ADDR, "tx_broadcast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXMULTI_G_ADDR, "tx_multicast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT64_GB_ADDR, "tx_pkt64"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT65TO127_GB_ADDR, "tx_pkt65to127"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT128TO255_GB_ADDR, "tx_pkt128to255"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT256TO511_GB_ADDR, "tx_pkt256to511"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT512TO1023_GB_ADDR, "tx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT1024TOMAX_GB_ADDR, "tx_pkt1024tomax"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXUNI_GB_ADDR, "tx_unicast"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXMULTI_GB_ADDR, "tx_multicast"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBROAD_GB_ADDR, "tx_broadcast"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXUNDERFLOW_ERR_ADDR, "tx_underflow_err"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXBYTE_G_ADDR, "tx_bytes_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPKT_G_ADDR, "tx_frames_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXPAUSE_ADDR, "tx_pause"),
++ PPE_MAC_MIB_DESC(8, XGMAC_TXVLAN_G_ADDR, "tx_vlan_g"),
++ PPE_MAC_MIB_DESC(4, XGMAC_TXLPI_USEC_ADDR, "tx_lpi_usec"),
++ PPE_MAC_MIB_DESC(4, XGMAC_TXLPI_TRAN_ADDR, "tx_lpi_tran"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT_GB_ADDR, "rx_frames"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXBYTE_GB_ADDR, "rx_bytes"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXBYTE_G_ADDR, "rx_bytes_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXBROAD_G_ADDR, "rx_broadcast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXMULTI_G_ADDR, "rx_multicast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXCRC_ERR_ADDR, "rx_crc_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXRUNT_ERR_ADDR, "rx_runt_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXJABBER_ERR_ADDR, "rx_jabber_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXUNDERSIZE_G_ADDR, "rx_undersize_g"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXOVERSIZE_G_ADDR, "rx_oversize_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT64_GB_ADDR, "rx_pkt64"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT65TO127_GB_ADDR, "rx_pkt65to127"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT128TO255_GB_ADDR, "rx_pkt128to255"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT256TO511_GB_ADDR, "rx_pkt256to511"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT512TO1023_GB_ADDR, "rx_pkt512to1023"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPKT1024TOMAX_GB_ADDR, "rx_pkt1024tomax"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXUNI_G_ADDR, "rx_unicast_g"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXLEN_ERR_ADDR, "rx_len_err"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXOUTOFRANGE_ADDR, "rx_outofrange_err"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXPAUSE_ADDR, "rx_pause"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXFIFOOVERFLOW_ADDR, "rx_fifo_overflow"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXVLAN_GB_ADDR, "rx_vlan"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXWATCHDOG_ERR_ADDR, "rx_wdog_err"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXLPI_USEC_ADDR, "rx_lpi_usec"),
++ PPE_MAC_MIB_DESC(4, XGMAC_RXLPI_TRAN_ADDR, "rx_lpi_tran"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXDISCARD_GB_ADDR, "rx_drop_frames"),
++ PPE_MAC_MIB_DESC(8, XGMAC_RXDISCARDBYTE_GB_ADDR, "rx_drop_bytes"),
++};
++
++/* Get GMAC MIBs from registers and accumulate to PPE port GMIB stats array */
++static void ppe_port_gmib_update(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ const struct ppe_mac_mib_info *mib;
++ int port = ppe_port->port_id;
++ u32 reg, val;
++ int i, ret;
++
++ for (i = 0; i < ARRAY_SIZE(gmib_info); i++) {
++ mib = &gmib_info[i];
++ reg = PPE_PORT_GMAC_ADDR(port) + mib->offset;
++
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n", __func__, ret);
++ continue;
++ }
++
++ ppe_port->gmib_stats[i] += val;
++ if (mib->size == 8) {
++ ret = regmap_read(ppe_dev->regmap, reg + 4, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n",
++ __func__, ret);
++ continue;
++ }
++
++ ppe_port->gmib_stats[i] += (u64)val << 32;
++ }
++ }
++}
++
++/* Polling task to read GMIB statistics to avoid GMIB 32bit register overflow */
++static void ppe_port_gmib_stats_poll(struct work_struct *work)
++{
++ struct ppe_port *ppe_port = container_of(work, struct ppe_port,
++ gmib_read.work);
++ spin_lock(&ppe_port->gmib_stats_lock);
++ ppe_port_gmib_update(ppe_port);
++ spin_unlock(&ppe_port->gmib_stats_lock);
++
++ schedule_delayed_work(&ppe_port->gmib_read,
++ msecs_to_jiffies(PPE_GMIB_POLL_INTERVAL_MS));
++}
++
++/* Get the XGMAC MIB counter based on the specific MIB stats type */
++static u64 ppe_port_xgmib_get(struct ppe_port *ppe_port,
++ enum ppe_xgmib_stats_type xgmib_type)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ const struct ppe_mac_mib_info *mib;
++ int port = ppe_port->port_id;
++ u32 reg, val;
++ u64 data = 0;
++ int ret;
++
++ mib = &xgmib_info[xgmib_type];
++ reg = PPE_PORT_XGMAC_ADDR(port) + mib->offset;
++
++ ret = regmap_read(ppe_dev->regmap, reg, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n", __func__, ret);
++ goto data_return;
++ }
++
++ data = val;
++ if (mib->size == 8) {
++ ret = regmap_read(ppe_dev->regmap, reg + 4, &val);
++ if (ret) {
++ dev_warn(ppe_dev->dev, "%s: %d\n", __func__, ret);
++ goto data_return;
++ }
++
++ data |= (u64)val << 32;
++ }
++
++data_return:
++ return data;
++}
++
++/**
++ * ppe_port_get_sset_count() - Get PPE port statistics string count
++ * @ppe_port: PPE port
++ * @sset: string set ID
++ *
++ * Description: Get the MAC statistics string count for the PPE port
++ * specified by @ppe_port.
++ *
++ * Return: The count of the statistics string.
++ */
++int ppe_port_get_sset_count(struct ppe_port *ppe_port, int sset)
++{
++ if (sset != ETH_SS_STATS)
++ return 0;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC)
++ return ARRAY_SIZE(gmib_info);
++ else
++ return ARRAY_SIZE(xgmib_info);
++}
++
++/**
++ * ppe_port_get_strings() - Get PPE port statistics strings
++ * @ppe_port: PPE port
++ * @stringset: string set ID
++ * @data: pointer to statistics strings
++ *
++ * Description: Get the MAC statistics stings for the PPE port
++ * specified by @ppe_port. The strings are stored in the buffer
++ * indicated by @data which used in the ethtool ops.
++ */
++void ppe_port_get_strings(struct ppe_port *ppe_port, u32 stringset, u8 *data)
++{
++ int i;
++
++ if (stringset != ETH_SS_STATS)
++ return;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ for (i = 0; i < ARRAY_SIZE(gmib_info); i++)
++ strscpy(data + i * ETH_GSTRING_LEN, gmib_info[i].name,
++ ETH_GSTRING_LEN);
++ } else {
++ for (i = 0; i < ARRAY_SIZE(xgmib_info); i++)
++ strscpy(data + i * ETH_GSTRING_LEN, xgmib_info[i].name,
++ ETH_GSTRING_LEN);
++ }
++}
++
++/**
++ * ppe_port_get_ethtool_stats() - Get PPE port ethtool statistics
++ * @ppe_port: PPE port
++ * @data: pointer to statistics data
++ *
++ * Description: Get the MAC statistics for the PPE port specified
++ * by @ppe_port. The statistics are stored in the buffer indicated
++ * by @data which used in the ethtool ops.
++ */
++void ppe_port_get_ethtool_stats(struct ppe_port *ppe_port, u64 *data)
++{
++ int i;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ spin_lock(&ppe_port->gmib_stats_lock);
++
++ ppe_port_gmib_update(ppe_port);
++ for (i = 0; i < ARRAY_SIZE(gmib_info); i++)
++ data[i] = ppe_port->gmib_stats[i];
++
++ spin_unlock(&ppe_port->gmib_stats_lock);
++ } else {
++ for (i = 0; i < ARRAY_SIZE(xgmib_info); i++)
++ data[i] = ppe_port_xgmib_get(ppe_port, i);
++ }
++}
++
++/**
++ * ppe_port_get_stats64() - Get PPE port statistics
++ * @ppe_port: PPE port
++ * @s: statistics pointer
++ *
++ * Description: Get the MAC statistics for the PPE port specified
++ * by @ppe_port.
++ */
++void ppe_port_get_stats64(struct ppe_port *ppe_port,
++ struct rtnl_link_stats64 *s)
++{
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ u64 *src = ppe_port->gmib_stats;
++
++ spin_lock(&ppe_port->gmib_stats_lock);
++
++ ppe_port_gmib_update(ppe_port);
++
++ s->rx_packets = src[gmib_rx_unicast] +
++ src[gmib_rx_broadcast] + src[gmib_rx_multicast];
++
++ s->tx_packets = src[gmib_tx_unicast] +
++ src[gmib_tx_broadcast] + src[gmib_tx_multicast];
++
++ s->rx_bytes = src[gmib_rx_bytes_g];
++ s->tx_bytes = src[gmib_tx_bytes];
++ s->multicast = src[gmib_rx_multicast];
++
++ s->rx_crc_errors = src[gmib_rx_fcserr] + src[gmib_rx_frag];
++ s->rx_frame_errors = src[gmib_rx_alignerr];
++ s->rx_errors = s->rx_crc_errors + s->rx_frame_errors;
++ s->rx_dropped = src[gmib_rx_toolong] + s->rx_errors;
++
++ s->tx_fifo_errors = src[gmib_tx_underrun];
++ s->tx_aborted_errors = src[gmib_tx_abortcol];
++ s->tx_errors = s->tx_fifo_errors + s->tx_aborted_errors;
++ s->collisions = src[gmib_tx_collisions];
++
++ spin_unlock(&ppe_port->gmib_stats_lock);
++ } else {
++ s->multicast = ppe_port_xgmib_get(ppe_port, xgmib_rx_multicast_g);
++
++ s->rx_packets = s->multicast;
++ s->rx_packets += ppe_port_xgmib_get(ppe_port, xgmib_rx_unicast_g);
++ s->rx_packets += ppe_port_xgmib_get(ppe_port, xgmib_rx_broadcast_g);
++
++ s->tx_packets = ppe_port_xgmib_get(ppe_port, xgmib_tx_frames);
++ s->rx_bytes = ppe_port_xgmib_get(ppe_port, xgmib_rx_bytes);
++ s->tx_bytes = ppe_port_xgmib_get(ppe_port, xgmib_tx_bytes);
++
++ s->rx_crc_errors = ppe_port_xgmib_get(ppe_port, xgmib_rx_crc_err);
++ s->rx_fifo_errors = ppe_port_xgmib_get(ppe_port, xgmib_rx_fifo_overflow);
++
++ s->rx_length_errors = ppe_port_xgmib_get(ppe_port, xgmib_rx_len_err);
++ s->rx_errors = s->rx_crc_errors +
++ s->rx_fifo_errors + s->rx_length_errors;
++ s->rx_dropped = s->rx_errors;
++
++ s->tx_fifo_errors = ppe_port_xgmib_get(ppe_port, xgmib_tx_underflow_err);
++ s->tx_errors = s->tx_packets -
++ ppe_port_xgmib_get(ppe_port, xgmib_tx_frames_g);
++ }
++}
++
+ /* PPE port and MAC reset */
+ static int ppe_port_mac_reset(struct ppe_port *ppe_port)
+ {
+@@ -261,6 +693,9 @@ static void ppe_port_mac_link_up(struct
+ int ret, port = ppe_port->port_id;
+ u32 reg, val;
+
++ /* Start GMIB statistics polling */
++ schedule_delayed_work(&ppe_port->gmib_read, 0);
++
+ if (mac_type == PPE_MAC_TYPE_GMAC)
+ ret = ppe_port_gmac_link_up(ppe_port,
+ speed, duplex, tx_pause, rx_pause);
+@@ -306,6 +741,9 @@ static void ppe_port_mac_link_down(struc
+ int ret, port = ppe_port->port_id;
+ u32 reg;
+
++ /* Stop GMIB statistics polling */
++ cancel_delayed_work_sync(&ppe_port->gmib_read);
++
+ /* Disable PPE port TX */
+ reg = PPE_PORT_BRIDGE_CTRL_ADDR + PPE_PORT_BRIDGE_CTRL_INC * port;
+ ret = regmap_update_bits(ppe_dev->regmap, reg,
+@@ -627,6 +1065,27 @@ static int ppe_port_mac_hw_init(struct p
+ return ret;
+ }
+
++/* PPE port MAC MIB work task initialization */
++static int ppe_port_mac_mib_work_init(struct ppe_port *ppe_port)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ u64 *gstats;
++
++ gstats = devm_kzalloc(ppe_dev->dev,
++ sizeof(*gstats) * ARRAY_SIZE(gmib_info),
++ GFP_KERNEL);
++ if (!gstats)
++ return -ENOMEM;
++
++ ppe_port->gmib_stats = gstats;
++
++ spin_lock_init(&ppe_port->gmib_stats_lock);
++ INIT_DELAYED_WORK(&ppe_port->gmib_read,
++ ppe_port_gmib_stats_poll);
++
++ return 0;
++}
++
+ /**
+ * ppe_port_mac_init() - Initialization of PPE ports for the PPE device
+ * @ppe_dev: PPE device
+@@ -693,6 +1152,12 @@ int ppe_port_mac_init(struct ppe_device
+ goto err_port_node;
+ }
+
++ ret = ppe_port_mac_mib_work_init(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Failed to initialize MAC MIB work\n");
++ goto err_port_node;
++ }
++
+ i++;
+ }
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -8,6 +8,8 @@
+
+ #include <linux/phylink.h>
+
++struct rtnl_link_stats64;
++
+ /**
+ * enum ppe_port_clk_rst_type - PPE port clock and reset ID type
+ * @PPE_PORT_CLK_RST_MAC: The clock and reset ID for port MAC
+@@ -44,6 +46,9 @@ enum ppe_mac_type {
+ * @port_id: Port ID
+ * @clks: Port clocks
+ * @rstcs: Port resets
++ * @gmib_read: Delay work task for GMAC MIB statistics polling function
++ * @gmib_stats: GMAC MIB statistics array
++ * @gmib_stats_lock: Lock to protect GMAC MIB statistics
+ */
+ struct ppe_port {
+ struct phylink *phylink;
+@@ -56,6 +61,9 @@ struct ppe_port {
+ int port_id;
+ struct clk *clks[PPE_PORT_CLK_RST_MAX];
+ struct reset_control *rstcs[PPE_PORT_CLK_RST_MAX];
++ struct delayed_work gmib_read;
++ u64 *gmib_stats;
++ spinlock_t gmib_stats_lock; /* Protects GMIB stats */
+ };
+
+ /**
+@@ -73,4 +81,9 @@ void ppe_port_mac_deinit(struct ppe_devi
+ int ppe_port_phylink_setup(struct ppe_port *ppe_port,
+ struct net_device *netdev);
+ void ppe_port_phylink_destroy(struct ppe_port *ppe_port);
++int ppe_port_get_sset_count(struct ppe_port *ppe_port, int sset);
++void ppe_port_get_strings(struct ppe_port *ppe_port, u32 stringset, u8 *data);
++void ppe_port_get_ethtool_stats(struct ppe_port *ppe_port, u64 *data);
++void ppe_port_get_stats64(struct ppe_port *ppe_port,
++ struct rtnl_link_stats64 *s);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -618,6 +618,48 @@
+ #define GMAC_MIB_CTRL_MASK \
+ (GMAC_MIB_RD_CLR | GMAC_MIB_RST | GMAC_MIB_EN)
+
++/* GMAC MIB counter registers */
++#define GMAC_RXBROAD_ADDR 0x40
++#define GMAC_RXPAUSE_ADDR 0x44
++#define GMAC_RXMULTI_ADDR 0x48
++#define GMAC_RXFCSERR_ADDR 0x4C
++#define GMAC_RXALIGNERR_ADDR 0x50
++#define GMAC_RXRUNT_ADDR 0x54
++#define GMAC_RXFRAG_ADDR 0x58
++#define GMAC_RXJUMBOFCSERR_ADDR 0x5C
++#define GMAC_RXJUMBOALIGNERR_ADDR 0x60
++#define GMAC_RXPKT64_ADDR 0x64
++#define GMAC_RXPKT65TO127_ADDR 0x68
++#define GMAC_RXPKT128TO255_ADDR 0x6C
++#define GMAC_RXPKT256TO511_ADDR 0x70
++#define GMAC_RXPKT512TO1023_ADDR 0x74
++#define GMAC_RXPKT1024TO1518_ADDR 0x78
++#define GMAC_RXPKT1519TOX_ADDR 0x7C
++#define GMAC_RXTOOLONG_ADDR 0x80
++#define GMAC_RXBYTE_G_ADDR 0x84
++#define GMAC_RXBYTE_B_ADDR 0x8C
++#define GMAC_RXUNI_ADDR 0x94
++#define GMAC_TXBROAD_ADDR 0xA0
++#define GMAC_TXPAUSE_ADDR 0xA4
++#define GMAC_TXMULTI_ADDR 0xA8
++#define GMAC_TXUNDERRUN_ADDR 0xAC
++#define GMAC_TXPKT64_ADDR 0xB0
++#define GMAC_TXPKT65TO127_ADDR 0xB4
++#define GMAC_TXPKT128TO255_ADDR 0xB8
++#define GMAC_TXPKT256TO511_ADDR 0xBC
++#define GMAC_TXPKT512TO1023_ADDR 0xC0
++#define GMAC_TXPKT1024TO1518_ADDR 0xC4
++#define GMAC_TXPKT1519TOX_ADDR 0xC8
++#define GMAC_TXBYTE_ADDR 0xCC
++#define GMAC_TXCOLLISIONS_ADDR 0xD4
++#define GMAC_TXABORTCOL_ADDR 0xD8
++#define GMAC_TXMULTICOL_ADDR 0xDC
++#define GMAC_TXSINGLECOL_ADDR 0xE0
++#define GMAC_TXEXCESSIVEDEFER_ADDR 0xE4
++#define GMAC_TXDEFER_ADDR 0xE8
++#define GMAC_TXLATECOL_ADDR 0xEC
++#define GMAC_TXUNI_ADDR 0xF0
++
+ /* XGMAC TX configuration register */
+ #define XGMAC_TX_CONFIG_ADDR 0x0
+ #define XGMAC_SPEED_M GENMASK(31, 29)
+@@ -680,4 +722,53 @@
+ #define XGMAC_MCF BIT(3)
+ #define XGMAC_CNTRST BIT(0)
+
++/* XGMAC MIB counter registers */
++#define XGMAC_TXBYTE_GB_ADDR 0x814
++#define XGMAC_TXPKT_GB_ADDR 0x81C
++#define XGMAC_TXBROAD_G_ADDR 0x824
++#define XGMAC_TXMULTI_G_ADDR 0x82C
++#define XGMAC_TXPKT64_GB_ADDR 0x834
++#define XGMAC_TXPKT65TO127_GB_ADDR 0x83C
++#define XGMAC_TXPKT128TO255_GB_ADDR 0x844
++#define XGMAC_TXPKT256TO511_GB_ADDR 0x84C
++#define XGMAC_TXPKT512TO1023_GB_ADDR 0x854
++#define XGMAC_TXPKT1024TOMAX_GB_ADDR 0x85C
++#define XGMAC_TXUNI_GB_ADDR 0x864
++#define XGMAC_TXMULTI_GB_ADDR 0x86C
++#define XGMAC_TXBROAD_GB_ADDR 0x874
++#define XGMAC_TXUNDERFLOW_ERR_ADDR 0x87C
++#define XGMAC_TXBYTE_G_ADDR 0x884
++#define XGMAC_TXPKT_G_ADDR 0x88C
++#define XGMAC_TXPAUSE_ADDR 0x894
++#define XGMAC_TXVLAN_G_ADDR 0x89C
++#define XGMAC_TXLPI_USEC_ADDR 0x8A4
++#define XGMAC_TXLPI_TRAN_ADDR 0x8A8
++#define XGMAC_RXPKT_GB_ADDR 0x900
++#define XGMAC_RXBYTE_GB_ADDR 0x908
++#define XGMAC_RXBYTE_G_ADDR 0x910
++#define XGMAC_RXBROAD_G_ADDR 0x918
++#define XGMAC_RXMULTI_G_ADDR 0x920
++#define XGMAC_RXCRC_ERR_ADDR 0x928
++#define XGMAC_RXRUNT_ERR_ADDR 0x930
++#define XGMAC_RXJABBER_ERR_ADDR 0x934
++#define XGMAC_RXUNDERSIZE_G_ADDR 0x938
++#define XGMAC_RXOVERSIZE_G_ADDR 0x93C
++#define XGMAC_RXPKT64_GB_ADDR 0x940
++#define XGMAC_RXPKT65TO127_GB_ADDR 0x948
++#define XGMAC_RXPKT128TO255_GB_ADDR 0x950
++#define XGMAC_RXPKT256TO511_GB_ADDR 0x958
++#define XGMAC_RXPKT512TO1023_GB_ADDR 0x960
++#define XGMAC_RXPKT1024TOMAX_GB_ADDR 0x968
++#define XGMAC_RXUNI_G_ADDR 0x970
++#define XGMAC_RXLEN_ERR_ADDR 0x978
++#define XGMAC_RXOUTOFRANGE_ADDR 0x980
++#define XGMAC_RXPAUSE_ADDR 0x988
++#define XGMAC_RXFIFOOVERFLOW_ADDR 0x990
++#define XGMAC_RXVLAN_GB_ADDR 0x998
++#define XGMAC_RXWATCHDOG_ERR_ADDR 0x9A0
++#define XGMAC_RXLPI_USEC_ADDR 0x9A4
++#define XGMAC_RXLPI_TRAN_ADDR 0x9A8
++#define XGMAC_RXDISCARD_GB_ADDR 0x9AC
++#define XGMAC_RXDISCARDBYTE_GB_ADDR 0x9B4
++
+ #endif
--- /dev/null
+From 55fbbc8ef90df27a16bca1613a793a578b79a384 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Fri, 1 Mar 2024 13:36:26 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add PPE port MAC address and EEE
+ functions
+
+Add PPE port MAC address set and EEE set API functions which
+will be used by netdev ops and ethtool.
+
+Change-Id: Id2b3b06ae940b3b6f5227d927316329cdf3caeaa
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Alex G: use struct ethtool_keee instead of ethtool_eee
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 75 ++++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 3 +
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 29 ++++++++
+ 3 files changed, 107 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -462,6 +462,81 @@ void ppe_port_get_stats64(struct ppe_por
+ }
+ }
+
++/**
++ * ppe_port_set_mac_address() - Set PPE port MAC address
++ * @ppe_port: PPE port
++ * @addr: MAC address
++ *
++ * Description: Set MAC address for the given PPE port.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_set_mac_address(struct ppe_port *ppe_port, const u8 *addr)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int port = ppe_port->port_id;
++ u32 reg, val;
++ int ret;
++
++ if (ppe_port->mac_type == PPE_MAC_TYPE_GMAC) {
++ reg = PPE_PORT_GMAC_ADDR(port);
++ val = (addr[5] << 8) | addr[4];
++ ret = regmap_write(ppe_dev->regmap, reg + GMAC_GOL_ADDR0_ADDR, val);
++ if (ret)
++ return ret;
++
++ val = (addr[0] << 24) | (addr[1] << 16) |
++ (addr[2] << 8) | addr[3];
++ ret = regmap_write(ppe_dev->regmap, reg + GMAC_GOL_ADDR1_ADDR, val);
++ if (ret)
++ return ret;
++ } else {
++ reg = PPE_PORT_XGMAC_ADDR(port);
++ val = (addr[5] << 8) | addr[4] | XGMAC_ADDR_EN;
++ ret = regmap_write(ppe_dev->regmap, reg + XGMAC_ADDR0_H_ADDR, val);
++ if (ret)
++ return ret;
++
++ val = (addr[3] << 24) | (addr[2] << 16) |
++ (addr[1] << 8) | addr[0];
++ ret = regmap_write(ppe_dev->regmap, reg + XGMAC_ADDR0_L_ADDR, val);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++/**
++ * ppe_port_set_mac_eee() - Set EEE configuration for PPE port MAC
++ * @ppe_port: PPE port
++ * @eee: EEE settings
++ *
++ * Description: Set port MAC EEE settings for the given PPE port.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_set_mac_eee(struct ppe_port *ppe_port, struct ethtool_keee *eee)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ int port = ppe_port->port_id;
++ u32 val;
++ int ret;
++
++ ret = regmap_read(ppe_dev->regmap, PPE_LPI_EN_ADDR, &val);
++ if (ret)
++ return ret;
++
++ if (eee->tx_lpi_enabled)
++ val |= PPE_LPI_PORT_EN(port);
++ else
++ val &= ~PPE_LPI_PORT_EN(port);
++
++ ret = regmap_write(ppe_dev->regmap, PPE_LPI_EN_ADDR, val);
++
++ return ret;
++}
++
+ /* PPE port and MAC reset */
+ static int ppe_port_mac_reset(struct ppe_port *ppe_port)
+ {
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -8,6 +8,7 @@
+
+ #include <linux/phylink.h>
+
++struct ethtool_keee;
+ struct rtnl_link_stats64;
+
+ /**
+@@ -86,4 +87,6 @@ void ppe_port_get_strings(struct ppe_por
+ void ppe_port_get_ethtool_stats(struct ppe_port *ppe_port, u64 *data);
+ void ppe_port_get_stats64(struct ppe_port *ppe_port,
+ struct rtnl_link_stats64 *s);
++int ppe_port_set_mac_address(struct ppe_port *ppe_port, const u8 *addr);
++int ppe_port_set_mac_eee(struct ppe_port *ppe_port, struct ethtool_keee *eee);
+ #endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -20,6 +20,16 @@
+ #define PPE_PORT5_SEL_PCS1 BIT(4)
+ #define PPE_PORT_SEL_XGMAC(x) (BIT(8) << ((x) - 1))
+
++/* PPE port LPI enable register */
++#define PPE_LPI_EN_ADDR 0x400
++#define PPE_LPI_PORT1_EN BIT(0)
++#define PPE_LPI_PORT2_EN BIT(1)
++#define PPE_LPI_PORT3_EN BIT(2)
++#define PPE_LPI_PORT4_EN BIT(3)
++#define PPE_LPI_PORT5_EN BIT(4)
++#define PPE_LPI_PORT6_EN BIT(5)
++#define PPE_LPI_PORT_EN(x) (BIT(0) << ((x) - 1))
++
+ /* PPE scheduler configurations for buffer manager block. */
+ #define PPE_BM_SCH_CTRL_ADDR 0xb000
+ #define PPE_BM_SCH_CTRL_INC 4
+@@ -592,6 +602,17 @@
+ #define GMAC_SPEED_100 1
+ #define GMAC_SPEED_1000 2
+
++/* GMAC MAC address register */
++#define GMAC_GOL_ADDR0_ADDR 0x8
++#define GMAC_ADDR_BYTE5 GENMASK(15, 8)
++#define GMAC_ADDR_BYTE4 GENMASK(7, 0)
++
++#define GMAC_GOL_ADDR1_ADDR 0xC
++#define GMAC_ADDR_BYTE0 GENMASK(31, 24)
++#define GMAC_ADDR_BYTE1 GENMASK(23, 16)
++#define GMAC_ADDR_BYTE2 GENMASK(15, 8)
++#define GMAC_ADDR_BYTE3 GENMASK(7, 0)
++
+ /* GMAC control register */
+ #define GMAC_CTRL_ADDR 0x18
+ #define GMAC_TX_THD_M GENMASK(27, 24)
+@@ -717,6 +738,14 @@
+ #define XGMAC_RX_FLOW_CTRL_ADDR 0x90
+ #define XGMAC_RXFCEN BIT(0)
+
++/* XGMAC MAC address register */
++#define XGMAC_ADDR0_H_ADDR 0x300
++#define XGMAC_ADDR_EN BIT(31)
++#define XGMAC_ADDRH GENMASK(15, 0)
++
++#define XGMAC_ADDR0_L_ADDR 0x304
++#define XGMAC_ADDRL GENMASK(31, 0)
++
+ /* XGMAC management counters control register */
+ #define XGMAC_MMC_CTRL_ADDR 0x800
+ #define XGMAC_MCF BIT(3)
--- /dev/null
+From 3981aeae5dd43dea94a0ec10f0b2977ebd102560 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Tue, 5 Mar 2024 16:42:56 +0800
+Subject: [PATCH] net: ethernet: qualcomm: Add API to configure PPE port max
+ frame size
+
+This function is called when the MTU of an ethernet port is
+configured. It limits the size of packet passed through the
+ethernet port.
+
+Change-Id: I2a4dcd04407156d73770d2becbb7cbc0d56b3754
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 44 ++++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.h | 1 +
+ 2 files changed, 45 insertions(+)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -537,6 +537,50 @@ int ppe_port_set_mac_eee(struct ppe_port
+ return ret;
+ }
+
++/**
++ * ppe_port_set_maxframe() - Set port maximum frame size
++ * @ppe_port: PPE port structure
++ * @maxframe_size: Maximum frame size supported by PPE port
++ *
++ * Description: Set MTU of network interface specified by @ppe_port.
++ *
++ * Return: 0 upon success or a negative error upon failure.
++ */
++int ppe_port_set_maxframe(struct ppe_port *ppe_port, int maxframe_size)
++{
++ struct ppe_device *ppe_dev = ppe_port->ppe_dev;
++ u32 reg, val, mru_mtu_val[3];
++ int port = ppe_port->port_id;
++ int ret;
++
++ /* The max frame size should be MTU added by ETH_HLEN in PPE. */
++ maxframe_size += ETH_HLEN;
++
++ /* MAC takes cover the FCS for the calculation of frame size. */
++ if (maxframe_size > PPE_PORT_MAC_MAX_FRAME_SIZE - ETH_FCS_LEN)
++ return -EINVAL;
++
++ reg = PPE_MC_MTU_CTRL_TBL_ADDR + PPE_MC_MTU_CTRL_TBL_INC * port;
++ val = FIELD_PREP(PPE_MC_MTU_CTRL_TBL_MTU, maxframe_size);
++ ret = regmap_update_bits(ppe_dev->regmap, reg,
++ PPE_MC_MTU_CTRL_TBL_MTU,
++ val);
++ if (ret)
++ return ret;
++
++ reg = PPE_MRU_MTU_CTRL_TBL_ADDR + PPE_MRU_MTU_CTRL_TBL_INC * port;
++ ret = regmap_bulk_read(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++ if (ret)
++ return ret;
++
++ PPE_MRU_MTU_CTRL_SET_MRU(mru_mtu_val, maxframe_size);
++ PPE_MRU_MTU_CTRL_SET_MTU(mru_mtu_val, maxframe_size);
++
++ return regmap_bulk_write(ppe_dev->regmap, reg,
++ mru_mtu_val, ARRAY_SIZE(mru_mtu_val));
++}
++
+ /* PPE port and MAC reset */
+ static int ppe_port_mac_reset(struct ppe_port *ppe_port)
+ {
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.h
+@@ -89,4 +89,5 @@ void ppe_port_get_stats64(struct ppe_por
+ struct rtnl_link_stats64 *s);
+ int ppe_port_set_mac_address(struct ppe_port *ppe_port, const u8 *addr);
+ int ppe_port_set_mac_eee(struct ppe_port *ppe_port, struct ethtool_keee *eee);
++int ppe_port_set_maxframe(struct ppe_port *ppe_port, int maxframe_size);
+ #endif
--- /dev/null
+From 00d4f3cb4f5d1e6924151a4551f06b6a82bf0146 Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Wed, 28 Feb 2024 11:25:15 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add EDMA support for QCOM IPQ9574
+ chipset.
+
+Add the infrastructure functions such as Makefile,
+EDMA hardware configuration, clock and IRQ initializations.
+
+Change-Id: I64f65e554e70e9095b0cf3636fec421569ae6895
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Co-developed-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Alex G: use "ppe_config.h" header instead of "ppe_api.h"
+ add missing definitions and functions from ppe_api:
+ - enum ppe_queue_class_type {}
+ - ppe_edma_queue_offset_config()
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 3 +
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 480 +++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 113 +++++
+ drivers/net/ethernet/qualcomm/ppe/ppe.c | 10 +-
+ drivers/net/ethernet/qualcomm/ppe/ppe_regs.h | 253 ++++++++++
+ 5 files changed, 858 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -5,3 +5,6 @@
+
+ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
++
++#EDMA
++qcom-ppe-objs += edma.o
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -0,0 +1,480 @@
++// SPDX-License-Identifier: GPL-2.0-only
++ /* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++ /* Qualcomm Ethernet DMA driver setup, HW configuration, clocks and
++ * interrupt initializations.
++ */
++
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/netdevice.h>
++#include <linux/of_irq.h>
++#include <linux/platform_device.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++#include <linux/reset.h>
++
++#include "edma.h"
++#include "ppe_regs.h"
++
++#define EDMA_IRQ_NAME_SIZE 32
++
++/* Global EDMA context. */
++struct edma_context *edma_ctx;
++
++/* Priority to multi-queue mapping. */
++static u8 edma_pri_map[PPE_QUEUE_INTER_PRI_NUM] = {
++ 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7};
++
++enum edma_clk_id {
++ EDMA_CLK,
++ EDMA_CFG_CLK,
++ EDMA_CLK_MAX
++};
++
++static const char * const clock_name[EDMA_CLK_MAX] = {
++ [EDMA_CLK] = "edma",
++ [EDMA_CFG_CLK] = "edma-cfg",
++};
++
++/* Rx Fill ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_rxfill_ring_info = {
++ .max_rings = 8,
++ .ring_start = 4,
++ .num_rings = 4,
++};
++
++/* Rx ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_rx_ring_info = {
++ .max_rings = 24,
++ .ring_start = 20,
++ .num_rings = 4,
++};
++
++/* Tx ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_tx_ring_info = {
++ .max_rings = 32,
++ .ring_start = 8,
++ .num_rings = 24,
++};
++
++/* Tx complete ring info for IPQ9574. */
++static struct edma_ring_info ipq9574_txcmpl_ring_info = {
++ .max_rings = 32,
++ .ring_start = 8,
++ .num_rings = 24,
++};
++
++/* HW info for IPQ9574. */
++static struct edma_hw_info ipq9574_hw_info = {
++ .rxfill = &ipq9574_rxfill_ring_info,
++ .rx = &ipq9574_rx_ring_info,
++ .tx = &ipq9574_tx_ring_info,
++ .txcmpl = &ipq9574_txcmpl_ring_info,
++ .max_ports = 6,
++ .napi_budget_rx = 128,
++ .napi_budget_tx = 512,
++};
++
++static int edma_clock_set_and_enable(struct device *dev,
++ const char *id, unsigned long rate)
++{
++ struct device_node *edma_np;
++ struct clk *clk = NULL;
++ int ret;
++
++ edma_np = of_get_child_by_name(dev->of_node, "edma");
++
++ clk = devm_get_clk_from_child(dev, edma_np, id);
++ if (IS_ERR(clk)) {
++ dev_err(dev, "clk %s get failed\n", id);
++ of_node_put(edma_np);
++ return PTR_ERR(clk);
++ }
++
++ ret = clk_set_rate(clk, rate);
++ if (ret) {
++ dev_err(dev, "set %lu rate for %s failed\n", rate, id);
++ of_node_put(edma_np);
++ return ret;
++ }
++
++ ret = clk_prepare_enable(clk);
++ if (ret) {
++ dev_err(dev, "clk %s enable failed\n", id);
++ of_node_put(edma_np);
++ return ret;
++ }
++
++ of_node_put(edma_np);
++
++ dev_dbg(dev, "set %lu rate for %s\n", rate, id);
++
++ return 0;
++}
++
++static int edma_clock_init(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ unsigned long ppe_rate;
++ int ret;
++
++ ppe_rate = ppe_dev->clk_rate;
++
++ ret = edma_clock_set_and_enable(dev, clock_name[EDMA_CLK],
++ ppe_rate);
++ if (ret)
++ return ret;
++
++ ret = edma_clock_set_and_enable(dev, clock_name[EDMA_CFG_CLK],
++ ppe_rate);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++/**
++ * edma_configure_ucast_prio_map_tbl - Configure unicast priority map table.
++ *
++ * Map int_priority values to priority class and initialize
++ * unicast priority map table for default profile_id.
++ */
++static int edma_configure_ucast_prio_map_tbl(void)
++{
++ u8 pri_class, int_pri;
++ int ret = 0;
++
++ /* Set the priority class value for every possible priority. */
++ for (int_pri = 0; int_pri < PPE_QUEUE_INTER_PRI_NUM; int_pri++) {
++ pri_class = edma_pri_map[int_pri];
++
++ /* Priority offset should be less than maximum supported
++ * queue priority.
++ */
++ if (pri_class > EDMA_PRI_MAX_PER_CORE - 1) {
++ pr_err("Configured incorrect priority offset: %d\n",
++ pri_class);
++ return -EINVAL;
++ }
++
++ ret = ppe_edma_queue_offset_config(edma_ctx->ppe_dev,
++ PPE_QUEUE_CLASS_PRIORITY, int_pri, pri_class);
++
++ if (ret) {
++ pr_err("Failed with error: %d to set queue priority class for int_pri: %d for profile_id: %d\n",
++ ret, int_pri, 0);
++ return ret;
++ }
++
++ pr_debug("profile_id: %d, int_priority: %d, pri_class: %d\n",
++ 0, int_pri, pri_class);
++ }
++
++ return ret;
++}
++
++static int edma_irq_init(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_ring_info *rx = hw_info->rx;
++ char edma_irq_name[EDMA_IRQ_NAME_SIZE];
++ struct device *dev = ppe_dev->dev;
++ struct platform_device *pdev;
++ struct device_node *edma_np;
++ u32 i;
++
++ pdev = to_platform_device(dev);
++ edma_np = of_get_child_by_name(dev->of_node, "edma");
++ edma_ctx->intr_info.intr_txcmpl = kzalloc((sizeof(*edma_ctx->intr_info.intr_txcmpl) *
++ txcmpl->num_rings), GFP_KERNEL);
++ if (!edma_ctx->intr_info.intr_txcmpl) {
++ of_node_put(edma_np);
++ return -ENOMEM;
++ }
++
++ /* Get TXCMPL rings IRQ numbers. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ snprintf(edma_irq_name, sizeof(edma_irq_name), "edma_txcmpl_%d",
++ txcmpl->ring_start + i);
++ edma_ctx->intr_info.intr_txcmpl[i] = of_irq_get_byname(edma_np, edma_irq_name);
++ if (edma_ctx->intr_info.intr_txcmpl[i] < 0) {
++ dev_err(dev, "%s: txcmpl_info.intr[%u] irq get failed\n",
++ edma_np->name, i);
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return edma_ctx->intr_info.intr_txcmpl[i];
++ }
++
++ dev_dbg(dev, "%s: intr_info.intr_txcmpl[%u] = %u\n",
++ edma_np->name, i, edma_ctx->intr_info.intr_txcmpl[i]);
++ }
++
++ edma_ctx->intr_info.intr_rx = kzalloc((sizeof(*edma_ctx->intr_info.intr_rx) *
++ rx->num_rings), GFP_KERNEL);
++ if (!edma_ctx->intr_info.intr_rx) {
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return -ENOMEM;
++ }
++
++ /* Get RXDESC rings IRQ numbers. */
++ for (i = 0; i < rx->num_rings; i++) {
++ snprintf(edma_irq_name, sizeof(edma_irq_name), "edma_rxdesc_%d",
++ rx->ring_start + i);
++ edma_ctx->intr_info.intr_rx[i] = of_irq_get_byname(edma_np, edma_irq_name);
++ if (edma_ctx->intr_info.intr_rx[i] < 0) {
++ dev_err(dev, "%s: rx_queue_map_info.intr[%u] irq get failed\n",
++ edma_np->name, i);
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return edma_ctx->intr_info.intr_rx[i];
++ }
++
++ dev_dbg(dev, "%s: intr_info.intr_rx[%u] = %u\n",
++ edma_np->name, i, edma_ctx->intr_info.intr_rx[i]);
++ }
++
++ /* Get misc IRQ number. */
++ edma_ctx->intr_info.intr_misc = of_irq_get_byname(edma_np, "edma_misc");
++ if (edma_ctx->intr_info.intr_misc < 0) {
++ dev_err(dev, "%s: misc_intr irq get failed\n", edma_np->name);
++ of_node_put(edma_np);
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return edma_ctx->intr_info.intr_misc;
++ }
++
++ of_node_put(edma_np);
++
++ dev_dbg(dev, "%s: misc IRQ:%u\n", edma_np->name,
++ edma_ctx->intr_info.intr_misc);
++
++ return 0;
++}
++
++static int edma_hw_reset(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ struct reset_control *edma_hw_rst;
++ struct device_node *edma_np;
++ const char *reset_string;
++ u32 count, i;
++ int ret;
++
++ /* Count and parse reset names from DTSI. */
++ edma_np = of_get_child_by_name(dev->of_node, "edma");
++ count = of_property_count_strings(edma_np, "reset-names");
++ if (count < 0) {
++ dev_err(dev, "EDMA reset entry not found\n");
++ of_node_put(edma_np);
++ return -EINVAL;
++ }
++
++ for (i = 0; i < count; i++) {
++ ret = of_property_read_string_index(edma_np, "reset-names",
++ i, &reset_string);
++ if (ret) {
++ dev_err(dev, "Error reading reset-names");
++ of_node_put(edma_np);
++ return -EINVAL;
++ }
++
++ edma_hw_rst = of_reset_control_get_exclusive(edma_np, reset_string);
++ if (IS_ERR(edma_hw_rst)) {
++ of_node_put(edma_np);
++ return PTR_ERR(edma_hw_rst);
++ }
++
++ /* 100ms delay is required by hardware to reset EDMA. */
++ reset_control_assert(edma_hw_rst);
++ fsleep(100);
++
++ reset_control_deassert(edma_hw_rst);
++ fsleep(100);
++
++ reset_control_put(edma_hw_rst);
++ dev_dbg(dev, "EDMA HW reset, i:%d reset_string:%s\n", i, reset_string);
++ }
++
++ of_node_put(edma_np);
++
++ return 0;
++}
++
++static int edma_hw_configure(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++ int ret;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MAS_CTRL_ADDR;
++ ret = regmap_read(regmap, reg, &data);
++ if (ret)
++ return ret;
++
++ pr_debug("EDMA ver %d hw init\n", data);
++
++ /* Setup private data structure. */
++ edma_ctx->intr_info.intr_mask_rx = EDMA_RXDESC_INT_MASK_PKT_INT;
++ edma_ctx->intr_info.intr_mask_txcmpl = EDMA_TX_INT_MASK_PKT_INT;
++
++ /* Reset EDMA. */
++ ret = edma_hw_reset();
++ if (ret) {
++ pr_err("Error in resetting the hardware. ret: %d\n", ret);
++ return ret;
++ }
++
++ /* Allocate memory for netdevices. */
++ edma_ctx->netdev_arr = kzalloc((sizeof(**edma_ctx->netdev_arr) *
++ hw_info->max_ports),
++ GFP_KERNEL);
++ if (!edma_ctx->netdev_arr)
++ return -ENOMEM;
++
++ /* Configure DMA request priority, DMA read burst length,
++ * and AXI write size.
++ */
++ data = FIELD_PREP(EDMA_DMAR_BURST_LEN_MASK, EDMA_BURST_LEN_ENABLE);
++ data |= FIELD_PREP(EDMA_DMAR_REQ_PRI_MASK, 0);
++ data |= FIELD_PREP(EDMA_DMAR_TXDATA_OUTSTANDING_NUM_MASK, 31);
++ data |= FIELD_PREP(EDMA_DMAR_TXDESC_OUTSTANDING_NUM_MASK, 7);
++ data |= FIELD_PREP(EDMA_DMAR_RXFILL_OUTSTANDING_NUM_MASK, 7);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_DMAR_CTRL_ADDR;
++ ret = regmap_write(regmap, reg, data);
++ if (ret)
++ return ret;
++
++ /* Configure Tx Timeout Threshold. */
++ data = EDMA_TX_TIMEOUT_THRESH_VAL;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_TIMEOUT_THRESH_ADDR;
++ ret = regmap_write(regmap, reg, data);
++ if (ret)
++ return ret;
++
++ /* Set Miscellaneous error mask. */
++ data = EDMA_MISC_AXI_RD_ERR_MASK |
++ EDMA_MISC_AXI_WR_ERR_MASK |
++ EDMA_MISC_RX_DESC_FIFO_FULL_MASK |
++ EDMA_MISC_RX_ERR_BUF_SIZE_MASK |
++ EDMA_MISC_TX_SRAM_FULL_MASK |
++ EDMA_MISC_TX_CMPL_BUF_FULL_MASK |
++ EDMA_MISC_DATA_LEN_ERR_MASK;
++ data |= EDMA_MISC_TX_TIMEOUT_MASK;
++ edma_ctx->intr_info.intr_mask_misc = data;
++
++ /* Global EDMA enable and padding enable. */
++ data = EDMA_PORT_PAD_EN | EDMA_PORT_EDMA_EN;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_PORT_CTRL_ADDR;
++ ret = regmap_write(regmap, reg, data);
++ if (ret)
++ return ret;
++
++ /* Initialize unicast priority map table. */
++ ret = (int)edma_configure_ucast_prio_map_tbl();
++ if (ret) {
++ pr_err("Failed to initialize unicast priority map table: %d\n",
++ ret);
++ kfree(edma_ctx->netdev_arr);
++ return ret;
++ }
++
++ return 0;
++}
++
++/**
++ * edma_destroy - EDMA Destroy.
++ * @ppe_dev: PPE device
++ *
++ * Free the memory allocated during setup.
++ */
++void edma_destroy(struct ppe_device *ppe_dev)
++{
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ kfree(edma_ctx->netdev_arr);
++}
++
++/**
++ * edma_setup - EDMA Setup.
++ * @ppe_dev: PPE device
++ *
++ * Configure Ethernet global ctx, clocks, hardware and interrupts.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int edma_setup(struct ppe_device *ppe_dev)
++{
++ struct device *dev = ppe_dev->dev;
++ int ret;
++
++ edma_ctx = devm_kzalloc(dev, sizeof(*edma_ctx), GFP_KERNEL);
++ if (!edma_ctx)
++ return -ENOMEM;
++
++ edma_ctx->hw_info = &ipq9574_hw_info;
++ edma_ctx->ppe_dev = ppe_dev;
++
++ /* Configure the EDMA common clocks. */
++ ret = edma_clock_init();
++ if (ret) {
++ dev_err(dev, "Error in configuring the EDMA clocks\n");
++ return ret;
++ }
++
++ dev_dbg(dev, "QCOM EDMA common clocks are configured\n");
++
++ ret = edma_hw_configure();
++ if (ret) {
++ dev_err(dev, "Error in edma configuration\n");
++ return ret;
++ }
++
++ ret = edma_irq_init();
++ if (ret) {
++ dev_err(dev, "Error in irq initialization\n");
++ return ret;
++ }
++
++ dev_info(dev, "EDMA configuration successful\n");
++
++ return 0;
++}
++
++/**
++ * ppe_edma_queue_offset_config - Configure queue offset for EDMA interface
++ * @ppe_dev: PPE device
++ * @class: The class to configure queue offset
++ * @index: Class index, internal priority or hash value
++ * @queue_offset: Queue offset value
++ *
++ * PPE EDMA queue offset is configured based on the PPE internal priority or
++ * RSS hash value, the profile ID is fixed to 0 for EDMA interface.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
++ enum ppe_queue_class_type class,
++ int index, int queue_offset)
++{
++ if (class == PPE_QUEUE_CLASS_PRIORITY)
++ return ppe_queue_ucast_offset_pri_set(ppe_dev, 0,
++ index, queue_offset);
++
++ return ppe_queue_ucast_offset_hash_set(ppe_dev, 0,
++ index, queue_offset);
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -0,0 +1,113 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_MAIN__
++#define __EDMA_MAIN__
++
++#include "ppe_config.h"
++
++/* One clock cycle = 1/(EDMA clock frequency in Mhz) micro seconds.
++ *
++ * One timer unit is 128 clock cycles.
++ *
++ * So, therefore the microsecond to timer unit calculation is:
++ * Timer unit = time in microseconds / (one clock cycle in microsecond * cycles in 1 timer unit)
++ * = ('x' microsecond * EDMA clock frequency in MHz ('y') / 128).
++ *
++ */
++#define EDMA_CYCLE_PER_TIMER_UNIT 128
++#define EDMA_MICROSEC_TO_TIMER_UNIT(x, y) ((x) * (y) / EDMA_CYCLE_PER_TIMER_UNIT)
++#define MHZ 1000000UL
++
++/* EDMA profile ID. */
++#define EDMA_CPU_PORT_PROFILE_ID 0
++
++/* Number of PPE queue priorities supported per ARM core. */
++#define EDMA_PRI_MAX_PER_CORE 8
++
++/**
++ * enum ppe_queue_class_type - PPE queue class type
++ * @PPE_QUEUE_CLASS_PRIORITY: Queue offset configured from internal priority
++ * @PPE_QUEUE_CLASS_HASH: Queue offset configured from RSS hash.
++ */
++enum ppe_queue_class_type {
++ PPE_QUEUE_CLASS_PRIORITY,
++ PPE_QUEUE_CLASS_HASH,
++};
++
++/**
++ * struct edma_ring_info - EDMA ring data structure.
++ * @max_rings: Maximum number of rings
++ * @ring_start: Ring start ID
++ * @num_rings: Number of rings
++ */
++struct edma_ring_info {
++ u32 max_rings;
++ u32 ring_start;
++ u32 num_rings;
++};
++
++/**
++ * struct edma_hw_info - EDMA hardware data structure.
++ * @rxfill: Rx Fill ring information
++ * @rx: Rx Desc ring information
++ * @tx: Tx Desc ring information
++ * @txcmpl: Tx complete ring information
++ * @max_ports: Maximum number of ports
++ * @napi_budget_rx: Rx NAPI budget
++ * @napi_budget_tx: Tx NAPI budget
++ */
++struct edma_hw_info {
++ struct edma_ring_info *rxfill;
++ struct edma_ring_info *rx;
++ struct edma_ring_info *tx;
++ struct edma_ring_info *txcmpl;
++ u32 max_ports;
++ u32 napi_budget_rx;
++ u32 napi_budget_tx;
++};
++
++/**
++ * struct edma_intr_info - EDMA interrupt data structure.
++ * @intr_mask_rx: RX interrupt mask
++ * @intr_rx: Rx interrupts
++ * @intr_mask_txcmpl: Tx completion interrupt mask
++ * @intr_txcmpl: Tx completion interrupts
++ * @intr_mask_misc: Miscellaneous interrupt mask
++ * @intr_misc: Miscellaneous interrupts
++ */
++struct edma_intr_info {
++ u32 intr_mask_rx;
++ u32 *intr_rx;
++ u32 intr_mask_txcmpl;
++ u32 *intr_txcmpl;
++ u32 intr_mask_misc;
++ u32 intr_misc;
++};
++
++/**
++ * struct edma_context - EDMA context.
++ * @netdev_arr: Net device for each EDMA port
++ * @ppe_dev: PPE device
++ * @hw_info: EDMA Hardware info
++ * @intr_info: EDMA Interrupt info
++ */
++struct edma_context {
++ struct net_device **netdev_arr;
++ struct ppe_device *ppe_dev;
++ struct edma_hw_info *hw_info;
++ struct edma_intr_info intr_info;
++};
++
++/* Global EDMA context. */
++extern struct edma_context *edma_ctx;
++
++void edma_destroy(struct ppe_device *ppe_dev);
++int edma_setup(struct ppe_device *ppe_dev);
++int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
++ enum ppe_queue_class_type class,
++ int index, int queue_offset);
++
++
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
+@@ -14,6 +14,7 @@
+ #include <linux/regmap.h>
+ #include <linux/reset.h>
+
++#include "edma.h"
+ #include "ppe.h"
+ #include "ppe_config.h"
+ #include "ppe_debugfs.h"
+@@ -201,10 +202,16 @@ static int qcom_ppe_probe(struct platfor
+ if (ret)
+ return dev_err_probe(dev, ret, "PPE HW config failed\n");
+
+- ret = ppe_port_mac_init(ppe_dev);
++ ret = edma_setup(ppe_dev);
+ if (ret)
++ return dev_err_probe(dev, ret, "EDMA setup failed\n");
++
++ ret = ppe_port_mac_init(ppe_dev);
++ if (ret) {
++ edma_destroy(ppe_dev);
+ return dev_err_probe(dev, ret,
+ "PPE Port MAC initialization failed\n");
++ }
+
+ ppe_debugfs_setup(ppe_dev);
+ platform_set_drvdata(pdev, ppe_dev);
+@@ -219,6 +226,7 @@ static void qcom_ppe_remove(struct platf
+ ppe_dev = platform_get_drvdata(pdev);
+ ppe_debugfs_teardown(ppe_dev);
+ ppe_port_mac_deinit(ppe_dev);
++ edma_destroy(ppe_dev);
+
+ platform_set_drvdata(pdev, NULL);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_regs.h
+@@ -800,4 +800,257 @@
+ #define XGMAC_RXDISCARD_GB_ADDR 0x9AC
+ #define XGMAC_RXDISCARDBYTE_GB_ADDR 0x9B4
+
++#define EDMA_BASE_OFFSET 0xb00000
++
++/* EDMA register offsets */
++#define EDMA_REG_MAS_CTRL_ADDR 0x0
++#define EDMA_REG_PORT_CTRL_ADDR 0x4
++#define EDMA_REG_VLAN_CTRL_ADDR 0x8
++#define EDMA_REG_RXDESC2FILL_MAP_0_ADDR 0x14
++#define EDMA_REG_RXDESC2FILL_MAP_1_ADDR 0x18
++#define EDMA_REG_RXDESC2FILL_MAP_2_ADDR 0x1c
++#define EDMA_REG_TXQ_CTRL_ADDR 0x20
++#define EDMA_REG_TXQ_CTRL_2_ADDR 0x24
++#define EDMA_REG_TXQ_FC_0_ADDR 0x28
++#define EDMA_REG_TXQ_FC_1_ADDR 0x30
++#define EDMA_REG_TXQ_FC_2_ADDR 0x34
++#define EDMA_REG_TXQ_FC_3_ADDR 0x38
++#define EDMA_REG_RXQ_CTRL_ADDR 0x3c
++#define EDMA_REG_MISC_ERR_QID_ADDR 0x40
++#define EDMA_REG_RXQ_FC_THRE_ADDR 0x44
++#define EDMA_REG_DMAR_CTRL_ADDR 0x48
++#define EDMA_REG_AXIR_CTRL_ADDR 0x4c
++#define EDMA_REG_AXIW_CTRL_ADDR 0x50
++#define EDMA_REG_MIN_MSS_ADDR 0x54
++#define EDMA_REG_LOOPBACK_CTRL_ADDR 0x58
++#define EDMA_REG_MISC_INT_STAT_ADDR 0x5c
++#define EDMA_REG_MISC_INT_MASK_ADDR 0x60
++#define EDMA_REG_DBG_CTRL_ADDR 0x64
++#define EDMA_REG_DBG_DATA_ADDR 0x68
++#define EDMA_REG_TX_TIMEOUT_THRESH_ADDR 0x6c
++#define EDMA_REG_REQ0_FIFO_THRESH_ADDR 0x80
++#define EDMA_REG_WB_OS_THRESH_ADDR 0x84
++#define EDMA_REG_MISC_ERR_QID_REG2_ADDR 0x88
++#define EDMA_REG_TXDESC2CMPL_MAP_0_ADDR 0x8c
++#define EDMA_REG_TXDESC2CMPL_MAP_1_ADDR 0x90
++#define EDMA_REG_TXDESC2CMPL_MAP_2_ADDR 0x94
++#define EDMA_REG_TXDESC2CMPL_MAP_3_ADDR 0x98
++#define EDMA_REG_TXDESC2CMPL_MAP_4_ADDR 0x9c
++#define EDMA_REG_TXDESC2CMPL_MAP_5_ADDR 0xa0
++
++/* Tx descriptor ring configuration register addresses */
++#define EDMA_REG_TXDESC_BA(n) (0x1000 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_PROD_IDX(n) (0x1004 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_CONS_IDX(n) (0x1008 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_RING_SIZE(n) (0x100c + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_CTRL(n) (0x1010 + (0x1000 * (n)))
++#define EDMA_REG_TXDESC_BA2(n) (0x1014 + (0x1000 * (n)))
++
++/* RxFill ring configuration register addresses */
++#define EDMA_REG_RXFILL_BA(n) (0x29000 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_PROD_IDX(n) (0x29004 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_CONS_IDX(n) (0x29008 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_RING_SIZE(n) (0x2900c + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_BUFFER1_SIZE(n) (0x29010 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_FC_THRE(n) (0x29014 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_UGT_THRE(n) (0x29018 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_RING_EN(n) (0x2901c + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_DISABLE(n) (0x29020 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_DISABLE_DONE(n) (0x29024 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_INT_STAT(n) (0x31000 + (0x1000 * (n)))
++#define EDMA_REG_RXFILL_INT_MASK(n) (0x31004 + (0x1000 * (n)))
++
++/* Rx descriptor ring configuration register addresses */
++#define EDMA_REG_RXDESC_BA(n) (0x39000 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_PROD_IDX(n) (0x39004 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_CONS_IDX(n) (0x39008 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_RING_SIZE(n) (0x3900c + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_FC_THRE(n) (0x39010 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_UGT_THRE(n) (0x39014 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_CTRL(n) (0x39018 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_BPC(n) (0x3901c + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_DISABLE(n) (0x39020 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_DISABLE_DONE(n) (0x39024 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_PREHEADER_BA(n) (0x39028 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_INT_STAT(n) (0x59000 + (0x1000 * (n)))
++#define EDMA_REG_RXDESC_INT_MASK(n) (0x59004 + (0x1000 * (n)))
++
++#define EDMA_REG_RX_MOD_TIMER(n) (0x59008 + (0x1000 * (n)))
++#define EDMA_REG_RX_INT_CTRL(n) (0x5900c + (0x1000 * (n)))
++
++/* Tx completion ring configuration register addresses */
++#define EDMA_REG_TXCMPL_BA(n) (0x79000 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_PROD_IDX(n) (0x79004 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_CONS_IDX(n) (0x79008 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_RING_SIZE(n) (0x7900c + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_UGT_THRE(n) (0x79010 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_CTRL(n) (0x79014 + (0x1000 * (n)))
++#define EDMA_REG_TXCMPL_BPC(n) (0x79018 + (0x1000 * (n)))
++
++#define EDMA_REG_TX_INT_STAT(n) (0x99000 + (0x1000 * (n)))
++#define EDMA_REG_TX_INT_MASK(n) (0x99004 + (0x1000 * (n)))
++#define EDMA_REG_TX_MOD_TIMER(n) (0x99008 + (0x1000 * (n)))
++#define EDMA_REG_TX_INT_CTRL(n) (0x9900c + (0x1000 * (n)))
++
++/* EDMA_QID2RID_TABLE_MEM register field masks */
++#define EDMA_RX_RING_ID_QUEUE0_MASK GENMASK(7, 0)
++#define EDMA_RX_RING_ID_QUEUE1_MASK GENMASK(15, 8)
++#define EDMA_RX_RING_ID_QUEUE2_MASK GENMASK(23, 16)
++#define EDMA_RX_RING_ID_QUEUE3_MASK GENMASK(31, 24)
++
++/* EDMA_REG_PORT_CTRL register bit definitions */
++#define EDMA_PORT_PAD_EN 0x1
++#define EDMA_PORT_EDMA_EN 0x2
++
++/* EDMA_REG_DMAR_CTRL register field masks */
++#define EDMA_DMAR_REQ_PRI_MASK GENMASK(2, 0)
++#define EDMA_DMAR_BURST_LEN_MASK BIT(3)
++#define EDMA_DMAR_TXDATA_OUTSTANDING_NUM_MASK GENMASK(8, 4)
++#define EDMA_DMAR_TXDESC_OUTSTANDING_NUM_MASK GENMASK(11, 9)
++#define EDMA_DMAR_RXFILL_OUTSTANDING_NUM_MASK GENMASK(14, 12)
++
++#define EDMA_BURST_LEN_ENABLE 0
++
++/* Tx timeout threshold */
++#define EDMA_TX_TIMEOUT_THRESH_VAL 0xFFFF
++
++/* Rx descriptor ring base address mask */
++#define EDMA_RXDESC_BA_MASK 0xffffffff
++
++/* Rx Descriptor ring pre-header base address mask */
++#define EDMA_RXDESC_PREHEADER_BA_MASK 0xffffffff
++
++/* Tx descriptor prod ring index mask */
++#define EDMA_TXDESC_PROD_IDX_MASK 0xffff
++
++/* Tx descriptor consumer ring index mask */
++#define EDMA_TXDESC_CONS_IDX_MASK 0xffff
++
++/* Tx descriptor ring size mask */
++#define EDMA_TXDESC_RING_SIZE_MASK 0xffff
++
++/* Tx descriptor ring enable */
++#define EDMA_TXDESC_TX_ENABLE 0x1
++
++#define EDMA_TXDESC_CTRL_TXEN_MASK BIT(0)
++#define EDMA_TXDESC_CTRL_FC_GRP_ID_MASK GENMASK(3, 1)
++
++/* Tx completion ring prod index mask */
++#define EDMA_TXCMPL_PROD_IDX_MASK 0xffff
++
++/* Tx completion ring urgent threshold mask */
++#define EDMA_TXCMPL_LOW_THRE_MASK 0xffff
++#define EDMA_TXCMPL_LOW_THRE_SHIFT 0
++
++/* EDMA_REG_TX_MOD_TIMER mask */
++#define EDMA_TX_MOD_TIMER_INIT_MASK 0xffff
++#define EDMA_TX_MOD_TIMER_INIT_SHIFT 0
++
++/* Rx fill ring prod index mask */
++#define EDMA_RXFILL_PROD_IDX_MASK 0xffff
++
++/* Rx fill ring consumer index mask */
++#define EDMA_RXFILL_CONS_IDX_MASK 0xffff
++
++/* Rx fill ring size mask */
++#define EDMA_RXFILL_RING_SIZE_MASK 0xffff
++
++/* Rx fill ring flow control threshold masks */
++#define EDMA_RXFILL_FC_XON_THRE_MASK 0x7ff
++#define EDMA_RXFILL_FC_XON_THRE_SHIFT 12
++#define EDMA_RXFILL_FC_XOFF_THRE_MASK 0x7ff
++#define EDMA_RXFILL_FC_XOFF_THRE_SHIFT 0
++
++/* Rx fill ring enable bit */
++#define EDMA_RXFILL_RING_EN 0x1
++
++/* Rx desc ring prod index mask */
++#define EDMA_RXDESC_PROD_IDX_MASK 0xffff
++
++/* Rx descriptor ring cons index mask */
++#define EDMA_RXDESC_CONS_IDX_MASK 0xffff
++
++/* Rx descriptor ring size masks */
++#define EDMA_RXDESC_RING_SIZE_MASK 0xffff
++#define EDMA_RXDESC_PL_OFFSET_MASK 0x1ff
++#define EDMA_RXDESC_PL_OFFSET_SHIFT 16
++#define EDMA_RXDESC_PL_DEFAULT_VALUE 0
++
++/* Rx descriptor ring flow control threshold masks */
++#define EDMA_RXDESC_FC_XON_THRE_MASK 0x7ff
++#define EDMA_RXDESC_FC_XON_THRE_SHIFT 12
++#define EDMA_RXDESC_FC_XOFF_THRE_MASK 0x7ff
++#define EDMA_RXDESC_FC_XOFF_THRE_SHIFT 0
++
++/* Rx descriptor ring urgent threshold mask */
++#define EDMA_RXDESC_LOW_THRE_MASK 0xffff
++#define EDMA_RXDESC_LOW_THRE_SHIFT 0
++
++/* Rx descriptor ring enable bit */
++#define EDMA_RXDESC_RX_EN 0x1
++
++/* Tx interrupt status bit */
++#define EDMA_TX_INT_MASK_PKT_INT 0x1
++
++/* Rx interrupt mask */
++#define EDMA_RXDESC_INT_MASK_PKT_INT 0x1
++
++#define EDMA_MASK_INT_DISABLE 0x0
++#define EDMA_MASK_INT_CLEAR 0x0
++
++/* EDMA_REG_RX_MOD_TIMER register field masks */
++#define EDMA_RX_MOD_TIMER_INIT_MASK 0xffff
++#define EDMA_RX_MOD_TIMER_INIT_SHIFT 0
++
++/* EDMA Ring mask */
++#define EDMA_RING_DMA_MASK 0xffffffff
++
++/* RXDESC threshold interrupt. */
++#define EDMA_RXDESC_UGT_INT_STAT 0x2
++
++/* RXDESC timer interrupt */
++#define EDMA_RXDESC_PKT_INT_STAT 0x1
++
++/* RXDESC Interrupt status mask */
++#define EDMA_RXDESC_RING_INT_STATUS_MASK \
++ (EDMA_RXDESC_UGT_INT_STAT | EDMA_RXDESC_PKT_INT_STAT)
++
++/* TXCMPL threshold interrupt. */
++#define EDMA_TXCMPL_UGT_INT_STAT 0x2
++
++/* TXCMPL timer interrupt */
++#define EDMA_TXCMPL_PKT_INT_STAT 0x1
++
++/* TXCMPL Interrupt status mask */
++#define EDMA_TXCMPL_RING_INT_STATUS_MASK \
++ (EDMA_TXCMPL_UGT_INT_STAT | EDMA_TXCMPL_PKT_INT_STAT)
++
++#define EDMA_TXCMPL_RETMODE_OPAQUE 0x0
++
++#define EDMA_RXDESC_LOW_THRE 0
++#define EDMA_RX_MOD_TIMER_INIT 1000
++#define EDMA_RX_NE_INT_EN 0x2
++
++#define EDMA_TX_MOD_TIMER 150
++
++#define EDMA_TX_INITIAL_PROD_IDX 0x0
++#define EDMA_TX_NE_INT_EN 0x2
++
++/* EDMA misc error mask */
++#define EDMA_MISC_AXI_RD_ERR_MASK BIT(0)
++#define EDMA_MISC_AXI_WR_ERR_MASK BIT(1)
++#define EDMA_MISC_RX_DESC_FIFO_FULL_MASK BIT(2)
++#define EDMA_MISC_RX_ERR_BUF_SIZE_MASK BIT(3)
++#define EDMA_MISC_TX_SRAM_FULL_MASK BIT(4)
++#define EDMA_MISC_TX_CMPL_BUF_FULL_MASK BIT(5)
++
++#define EDMA_MISC_DATA_LEN_ERR_MASK BIT(6)
++#define EDMA_MISC_TX_TIMEOUT_MASK BIT(7)
++
++/* EDMA txdesc2cmpl map */
++#define EDMA_TXDESC2CMPL_MAP_TXDESC_MASK 0x1F
++
++/* EDMA rxdesc2fill map */
++#define EDMA_RXDESC2FILL_MAP_RXDESC_MASK 0x7
++
+ #endif
--- /dev/null
+From 5dc80c468c668d855d76b323f09bbadb95cc3147 Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:14:46 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add netdevice support for QCOM
+ IPQ9574 chipset.
+
+Add EDMA ports and netdevice operations for QCOM IPQ9574 chipset.
+
+Change-Id: I08b2eff52b4ef0d6d428c1c416f5580ef010973f
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 3 +
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 270 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_port.h | 31 ++
+ drivers/net/ethernet/qualcomm/ppe/ppe_port.c | 19 ++
+ 5 files changed, 324 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_port.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_port.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o
++qcom-ppe-objs += edma.o edma_port.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -26,6 +26,9 @@
+ /* Number of PPE queue priorities supported per ARM core. */
+ #define EDMA_PRI_MAX_PER_CORE 8
+
++/* Interface ID start. */
++#define EDMA_START_IFNUM 1
++
+ /**
+ * enum ppe_queue_class_type - PPE queue class type
+ * @PPE_QUEUE_CLASS_PRIORITY: Queue offset configured from internal priority
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -0,0 +1,270 @@
++// SPDX-License-Identifier: GPL-2.0-only
++ /* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* EDMA port initialization, configuration and netdevice ops handling */
++
++#include <linux/etherdevice.h>
++#include <linux/net.h>
++#include <linux/netdevice.h>
++#include <linux/of_net.h>
++#include <linux/phylink.h>
++#include <linux/printk.h>
++
++#include "edma.h"
++#include "edma_port.h"
++#include "ppe_regs.h"
++
++/* Number of netdev queues. */
++#define EDMA_NETDEV_QUEUE_NUM 4
++
++static u16 __maybe_unused edma_port_select_queue(__maybe_unused struct net_device *netdev,
++ __maybe_unused struct sk_buff *skb,
++ __maybe_unused struct net_device *sb_dev)
++{
++ int cpu = get_cpu();
++
++ put_cpu();
++
++ return cpu;
++}
++
++static int edma_port_open(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ /* Inform the Linux Networking stack about the hardware capability of
++ * checksum offloading and other features. Each port is
++ * responsible to maintain the feature set it supports.
++ */
++ netdev->features |= EDMA_NETDEV_FEATURES;
++ netdev->hw_features |= EDMA_NETDEV_FEATURES;
++ netdev->vlan_features |= EDMA_NETDEV_FEATURES;
++ netdev->wanted_features |= EDMA_NETDEV_FEATURES;
++
++ ppe_port = port_priv->ppe_port;
++
++ if (ppe_port->phylink)
++ phylink_start(ppe_port->phylink);
++
++ netif_start_queue(netdev);
++
++ return 0;
++}
++
++static int edma_port_close(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ netif_stop_queue(netdev);
++
++ ppe_port = port_priv->ppe_port;
++
++ /* Phylink close. */
++ if (ppe_port->phylink)
++ phylink_stop(ppe_port->phylink);
++
++ return 0;
++}
++
++static int edma_port_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *ppe_port;
++ int ret = -EINVAL;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ ppe_port = port_priv->ppe_port;
++ if (ppe_port->phylink)
++ return phylink_mii_ioctl(ppe_port->phylink, ifr, cmd);
++
++ return ret;
++}
++
++static int edma_port_change_mtu(struct net_device *netdev, int mtu)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ if (!port_priv)
++ return -EINVAL;
++
++ netdev->mtu = mtu;
++
++ return ppe_port_set_maxframe(port_priv->ppe_port, mtu);
++}
++
++static netdev_features_t edma_port_feature_check(__maybe_unused struct sk_buff *skb,
++ __maybe_unused struct net_device *netdev,
++ netdev_features_t features)
++{
++ return features;
++}
++
++static void edma_port_get_stats64(struct net_device *netdev,
++ struct rtnl_link_stats64 *stats)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ if (!port_priv)
++ return;
++
++ ppe_port_get_stats64(port_priv->ppe_port, stats);
++}
++
++static int edma_port_set_mac_address(struct net_device *netdev, void *macaddr)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct sockaddr *addr = (struct sockaddr *)macaddr;
++ int ret;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ netdev_dbg(netdev, "AddrFamily: %d, %0x:%0x:%0x:%0x:%0x:%0x\n",
++ addr->sa_family, addr->sa_data[0], addr->sa_data[1],
++ addr->sa_data[2], addr->sa_data[3], addr->sa_data[4],
++ addr->sa_data[5]);
++
++ ret = eth_prepare_mac_addr_change(netdev, addr);
++ if (ret)
++ return ret;
++
++ if (ppe_port_set_mac_address(port_priv->ppe_port, (u8 *)addr)) {
++ netdev_err(netdev, "set mac address failed for dev: %s\n", netdev->name);
++ return -EINVAL;
++ }
++
++ eth_commit_mac_addr_change(netdev, addr);
++
++ return 0;
++}
++
++static const struct net_device_ops edma_port_netdev_ops = {
++ .ndo_open = edma_port_open,
++ .ndo_stop = edma_port_close,
++ .ndo_get_stats64 = edma_port_get_stats64,
++ .ndo_set_mac_address = edma_port_set_mac_address,
++ .ndo_validate_addr = eth_validate_addr,
++ .ndo_change_mtu = edma_port_change_mtu,
++ .ndo_eth_ioctl = edma_port_ioctl,
++ .ndo_features_check = edma_port_feature_check,
++ .ndo_select_queue = edma_port_select_queue,
++};
++
++/**
++ * edma_port_destroy - EDMA port destroy.
++ * @port: PPE port
++ *
++ * Unregister and free the netdevice.
++ */
++void edma_port_destroy(struct ppe_port *port)
++{
++ int port_id = port->port_id;
++ struct net_device *netdev = edma_ctx->netdev_arr[port_id - 1];
++
++ unregister_netdev(netdev);
++ free_netdev(netdev);
++ ppe_port_phylink_destroy(port);
++ edma_ctx->netdev_arr[port_id - 1] = NULL;
++}
++
++/**
++ * edma_port_setup - EDMA port Setup.
++ * @port: PPE port
++ *
++ * Initialize and register the netdevice.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int edma_port_setup(struct ppe_port *port)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device_node *np = port->np;
++ struct edma_port_priv *port_priv;
++ int port_id = port->port_id;
++ struct net_device *netdev;
++ u8 mac_addr[ETH_ALEN];
++ int ret = 0;
++ u8 *maddr;
++
++ netdev = alloc_etherdev_mqs(sizeof(struct edma_port_priv),
++ EDMA_NETDEV_QUEUE_NUM, EDMA_NETDEV_QUEUE_NUM);
++ if (!netdev) {
++ pr_err("alloc_etherdev() failed\n");
++ return -ENOMEM;
++ }
++
++ SET_NETDEV_DEV(netdev, ppe_dev->dev);
++ netdev->dev.of_node = np;
++
++ /* max_mtu is set to 1500 in ether_setup(). */
++ netdev->max_mtu = ETH_MAX_MTU;
++
++ port_priv = netdev_priv(netdev);
++ memset((void *)port_priv, 0, sizeof(struct edma_port_priv));
++
++ port_priv->ppe_port = port;
++ port_priv->netdev = netdev;
++ netdev->watchdog_timeo = 5 * HZ;
++ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
++ netdev->netdev_ops = &edma_port_netdev_ops;
++ netdev->gso_max_segs = GSO_MAX_SEGS;
++
++ maddr = mac_addr;
++ if (of_get_mac_address(np, maddr))
++ maddr = NULL;
++
++ if (maddr && is_valid_ether_addr(maddr)) {
++ eth_hw_addr_set(netdev, maddr);
++ } else {
++ eth_hw_addr_random(netdev);
++ netdev_info(netdev, "GMAC%d Using random MAC address - %pM\n",
++ port_id, netdev->dev_addr);
++ }
++
++ netdev_dbg(netdev, "Configuring the port %s(qcom-id:%d)\n",
++ netdev->name, port_id);
++
++ /* We expect 'port_id' to correspond to ports numbers on SoC.
++ * These begin from '1' and hence we subtract
++ * one when using it as an array index.
++ */
++ edma_ctx->netdev_arr[port_id - 1] = netdev;
++
++ /* Setup phylink. */
++ ret = ppe_port_phylink_setup(port, netdev);
++ if (ret) {
++ netdev_dbg(netdev, "EDMA port phylink setup for netdevice %s\n",
++ netdev->name);
++ goto port_phylink_setup_fail;
++ }
++
++ /* Register the network interface. */
++ ret = register_netdev(netdev);
++ if (ret) {
++ netdev_dbg(netdev, "Error registering netdevice %s\n",
++ netdev->name);
++ goto register_netdev_fail;
++ }
++
++ netdev_dbg(netdev, "Setup EDMA port GMAC%d done\n", port_id);
++ return ret;
++
++register_netdev_fail:
++ ppe_port_phylink_destroy(port);
++port_phylink_setup_fail:
++ free_netdev(netdev);
++ edma_ctx->netdev_arr[port_id - 1] = NULL;
++
++ return ret;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.h
+@@ -0,0 +1,31 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_PORTS__
++#define __EDMA_PORTS__
++
++#include "ppe_port.h"
++
++#define EDMA_NETDEV_FEATURES (NETIF_F_FRAGLIST \
++ | NETIF_F_SG \
++ | NETIF_F_RXCSUM \
++ | NETIF_F_HW_CSUM \
++ | NETIF_F_TSO \
++ | NETIF_F_TSO6)
++
++/**
++ * struct edma_port_priv - EDMA port priv structure.
++ * @ppe_port: Pointer to PPE port
++ * @netdev: Corresponding netdevice
++ * @flags: Feature flags
++ */
++struct edma_port_priv {
++ struct ppe_port *ppe_port;
++ struct net_device *netdev;
++ unsigned long flags;
++};
++
++void edma_port_destroy(struct ppe_port *port);
++int edma_port_setup(struct ppe_port *port);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_port.c
+@@ -13,6 +13,7 @@
+ #include <linux/regmap.h>
+ #include <linux/rtnetlink.h>
+
++#include "edma_port.h"
+ #include "ppe.h"
+ #include "ppe_port.h"
+ #include "ppe_regs.h"
+@@ -1277,12 +1278,26 @@ int ppe_port_mac_init(struct ppe_device
+ goto err_port_node;
+ }
+
++ ret = edma_port_setup(&ppe_ports->port[i]);
++ if (ret) {
++ dev_err(ppe_dev->dev, "QCOM EDMA port setup failed\n");
++ i--;
++ goto err_port_setup;
++ }
++
+ i++;
+ }
+
+ of_node_put(ports_node);
+ return 0;
+
++err_port_setup:
++ /* Destroy edma ports created till now */
++ while (i >= 0) {
++ edma_port_destroy(&ppe_ports->port[i]);
++ i--;
++ }
++
+ err_port_clk:
+ for (j = 0; j < i; j++)
+ ppe_port_clock_deinit(&ppe_ports->port[j]);
+@@ -1307,6 +1322,10 @@ void ppe_port_mac_deinit(struct ppe_devi
+
+ for (i = 0; i < ppe_dev->ports->num; i++) {
+ ppe_port = &ppe_dev->ports->port[i];
++
++ /* Destroy all phylinks and edma ports */
++ edma_port_destroy(ppe_port);
++
+ ppe_port_clock_deinit(ppe_port);
+ }
+ }
--- /dev/null
+From b5c8c5d3888328321e8be1db50b75dff8f514e51 Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:21:19 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add Rx Ethernet DMA support
+
+Add Rx queues, rings, descriptors configurations and
+DMA support for the EDMA.
+
+Change-Id: I612bcd661e74d5bf3ecb33de10fd5298d18ff7e9
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Alex G: add missing functions that were previously in ppe_api.c:
+ - ppe_edma_queue_resource_get()
+ - ppe_edma_ring_to_queues_config()
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 214 +++-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 22 +-
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.c | 964 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.h | 48 +
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 39 +-
+ drivers/net/ethernet/qualcomm/ppe/edma_port.h | 31 +
+ drivers/net/ethernet/qualcomm/ppe/edma_rx.c | 622 +++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_rx.h | 287 ++++++
+ 9 files changed, 2224 insertions(+), 5 deletions(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_rx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_rx.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_port.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_port.o edma_rx.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -18,12 +18,23 @@
+ #include <linux/reset.h>
+
+ #include "edma.h"
++#include "edma_cfg_rx.h"
+ #include "ppe_regs.h"
+
+ #define EDMA_IRQ_NAME_SIZE 32
+
+ /* Global EDMA context. */
+ struct edma_context *edma_ctx;
++static char **edma_rxdesc_irq_name;
++
++/* Module params. */
++static int page_mode;
++module_param(page_mode, int, 0);
++MODULE_PARM_DESC(page_mode, "Enable page mode (default:0)");
++
++static int rx_buff_size;
++module_param(rx_buff_size, int, 0640);
++MODULE_PARM_DESC(rx_buff_size, "Rx Buffer size for Jumbo MRU value (default:0)");
+
+ /* Priority to multi-queue mapping. */
+ static u8 edma_pri_map[PPE_QUEUE_INTER_PRI_NUM] = {
+@@ -178,6 +189,59 @@ static int edma_configure_ucast_prio_map
+ return ret;
+ }
+
++static int edma_irq_register(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ int ret;
++ u32 i;
++
++ /* Request IRQ for RXDESC rings. */
++ edma_rxdesc_irq_name = kzalloc((sizeof(char *) * rx->num_rings),
++ GFP_KERNEL);
++ if (!edma_rxdesc_irq_name)
++ return -ENOMEM;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ edma_rxdesc_irq_name[i] = kzalloc((sizeof(char *) * EDMA_IRQ_NAME_SIZE),
++ GFP_KERNEL);
++ if (!edma_rxdesc_irq_name[i]) {
++ ret = -ENOMEM;
++ goto rxdesc_irq_name_alloc_fail;
++ }
++
++ snprintf(edma_rxdesc_irq_name[i], 20, "edma_rxdesc_%d",
++ rx->ring_start + i);
++
++ irq_set_status_flags(edma_ctx->intr_info.intr_rx[i], IRQ_DISABLE_UNLAZY);
++
++ ret = request_irq(edma_ctx->intr_info.intr_rx[i],
++ edma_rx_handle_irq, IRQF_SHARED,
++ edma_rxdesc_irq_name[i],
++ (void *)&edma_ctx->rx_rings[i]);
++ if (ret) {
++ pr_err("RXDESC ring IRQ:%d request failed\n",
++ edma_ctx->intr_info.intr_rx[i]);
++ goto rx_desc_ring_intr_req_fail;
++ }
++
++ pr_debug("RXDESC ring: %d IRQ:%d request success: %s\n",
++ rx->ring_start + i,
++ edma_ctx->intr_info.intr_rx[i],
++ edma_rxdesc_irq_name[i]);
++ }
++
++ return 0;
++
++rx_desc_ring_intr_req_fail:
++ for (i = 0; i < rx->num_rings; i++)
++ kfree(edma_rxdesc_irq_name[i]);
++rxdesc_irq_name_alloc_fail:
++ kfree(edma_rxdesc_irq_name);
++
++ return ret;
++}
++
+ static int edma_irq_init(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+@@ -260,6 +324,16 @@ static int edma_irq_init(void)
+ return 0;
+ }
+
++static int edma_alloc_rings(void)
++{
++ if (edma_cfg_rx_rings_alloc()) {
++ pr_err("Error in allocating Rx rings\n");
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
+ static int edma_hw_reset(void)
+ {
+ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
+@@ -343,6 +417,40 @@ static int edma_hw_configure(void)
+ if (!edma_ctx->netdev_arr)
+ return -ENOMEM;
+
++ edma_ctx->dummy_dev = alloc_netdev_dummy(0);
++ if (!edma_ctx->dummy_dev) {
++ ret = -ENOMEM;
++ pr_err("Failed to allocate dummy device. ret: %d\n", ret);
++ goto dummy_dev_alloc_failed;
++ }
++
++ /* Set EDMA jumbo MRU if enabled or set page mode. */
++ if (edma_ctx->rx_buf_size) {
++ edma_ctx->rx_page_mode = false;
++ pr_debug("Rx Jumbo mru is enabled: %d\n", edma_ctx->rx_buf_size);
++ } else {
++ edma_ctx->rx_page_mode = page_mode;
++ }
++
++ ret = edma_alloc_rings();
++ if (ret) {
++ pr_err("Error in initializaing the rings. ret: %d\n", ret);
++ goto edma_alloc_rings_failed;
++ }
++
++ /* Disable interrupts. */
++ edma_cfg_rx_disable_interrupts();
++
++ edma_cfg_rx_rings_disable();
++
++ edma_cfg_rx_ring_mappings();
++
++ ret = edma_cfg_rx_rings();
++ if (ret) {
++ pr_err("Error in configuring Rx rings. ret: %d\n", ret);
++ goto edma_cfg_rx_rings_failed;
++ }
++
+ /* Configure DMA request priority, DMA read burst length,
+ * and AXI write size.
+ */
+@@ -376,6 +484,10 @@ static int edma_hw_configure(void)
+ data |= EDMA_MISC_TX_TIMEOUT_MASK;
+ edma_ctx->intr_info.intr_mask_misc = data;
+
++ edma_cfg_rx_rings_enable();
++ edma_cfg_rx_napi_add();
++ edma_cfg_rx_napi_enable();
++
+ /* Global EDMA enable and padding enable. */
+ data = EDMA_PORT_PAD_EN | EDMA_PORT_EDMA_EN;
+
+@@ -389,11 +501,32 @@ static int edma_hw_configure(void)
+ if (ret) {
+ pr_err("Failed to initialize unicast priority map table: %d\n",
+ ret);
+- kfree(edma_ctx->netdev_arr);
+- return ret;
++ goto configure_ucast_prio_map_tbl_failed;
++ }
++
++ /* Initialize RPS hash map table. */
++ ret = edma_cfg_rx_rps_hash_map();
++ if (ret) {
++ pr_err("Failed to configure rps hash table: %d\n",
++ ret);
++ goto edma_cfg_rx_rps_hash_map_failed;
+ }
+
+ return 0;
++
++edma_cfg_rx_rps_hash_map_failed:
++configure_ucast_prio_map_tbl_failed:
++ edma_cfg_rx_napi_disable();
++ edma_cfg_rx_napi_delete();
++ edma_cfg_rx_rings_disable();
++edma_cfg_rx_rings_failed:
++ edma_cfg_rx_rings_cleanup();
++edma_alloc_rings_failed:
++ free_netdev(edma_ctx->dummy_dev);
++dummy_dev_alloc_failed:
++ kfree(edma_ctx->netdev_arr);
++
++ return ret;
+ }
+
+ /**
+@@ -404,8 +537,31 @@ static int edma_hw_configure(void)
+ */
+ void edma_destroy(struct ppe_device *ppe_dev)
+ {
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ /* Disable interrupts. */
++ edma_cfg_rx_disable_interrupts();
++
++ /* Free IRQ for RXDESC rings. */
++ for (i = 0; i < rx->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_rx[i]);
++ free_irq(edma_ctx->intr_info.intr_rx[i],
++ (void *)&edma_ctx->rx_rings[i]);
++ kfree(edma_rxdesc_irq_name[i]);
++ }
++ kfree(edma_rxdesc_irq_name);
++
+ kfree(edma_ctx->intr_info.intr_rx);
+ kfree(edma_ctx->intr_info.intr_txcmpl);
++
++ edma_cfg_rx_napi_disable();
++ edma_cfg_rx_napi_delete();
++ edma_cfg_rx_rings_disable();
++ edma_cfg_rx_rings_cleanup();
++
++ free_netdev(edma_ctx->dummy_dev);
+ kfree(edma_ctx->netdev_arr);
+ }
+
+@@ -428,6 +584,7 @@ int edma_setup(struct ppe_device *ppe_de
+
+ edma_ctx->hw_info = &ipq9574_hw_info;
+ edma_ctx->ppe_dev = ppe_dev;
++ edma_ctx->rx_buf_size = rx_buff_size;
+
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+@@ -450,6 +607,16 @@ int edma_setup(struct ppe_device *ppe_de
+ return ret;
+ }
+
++ ret = edma_irq_register();
++ if (ret) {
++ dev_err(dev, "Error in irq registration\n");
++ kfree(edma_ctx->intr_info.intr_rx);
++ kfree(edma_ctx->intr_info.intr_txcmpl);
++ return ret;
++ }
++
++ edma_cfg_rx_enable_interrupts();
++
+ dev_info(dev, "EDMA configuration successful\n");
+
+ return 0;
+@@ -478,3 +645,46 @@ int ppe_edma_queue_offset_config(struct
+ return ppe_queue_ucast_offset_hash_set(ppe_dev, 0,
+ index, queue_offset);
+ }
++
++/**
++ * ppe_edma_queue_resource_get - Get EDMA queue resource
++ * @ppe_dev: PPE device
++ * @type: Resource type
++ * @res_start: Resource start ID returned
++ * @res_end: Resource end ID returned
++ *
++ * PPE EDMA queue resource includes unicast queue and multicast queue.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_edma_queue_resource_get(struct ppe_device *ppe_dev, int type,
++ int *res_start, int *res_end)
++{
++ if (type != PPE_RES_UCAST && type != PPE_RES_MCAST)
++ return -EINVAL;
++
++ return ppe_port_resource_get(ppe_dev, 0, type, res_start, res_end);
++};
++
++/**
++ * ppe_edma_ring_to_queues_config - Map EDMA ring to PPE queues
++ * @ppe_dev: PPE device
++ * @ring_id: EDMA ring ID
++ * @num: Number of queues mapped to EDMA ring
++ * @queues: PPE queue IDs
++ *
++ * PPE queues are configured to map with the special EDMA ring ID.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int ppe_edma_ring_to_queues_config(struct ppe_device *ppe_dev, int ring_id,
++ int num, int queues[] __counted_by(num))
++{
++ u32 queue_bmap[PPE_RING_TO_QUEUE_BITMAP_WORD_CNT] = {};
++ int index;
++
++ for (index = 0; index < num; index++)
++ queue_bmap[queues[index] / 32] |= BIT_MASK(queues[index] % 32);
++
++ return ppe_ring_queue_map_set(ppe_dev, ring_id, queue_bmap);
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -6,6 +6,7 @@
+ #define __EDMA_MAIN__
+
+ #include "ppe_config.h"
++#include "edma_rx.h"
+
+ /* One clock cycle = 1/(EDMA clock frequency in Mhz) micro seconds.
+ *
+@@ -29,6 +30,11 @@
+ /* Interface ID start. */
+ #define EDMA_START_IFNUM 1
+
++#define EDMA_DESC_AVAIL_COUNT(head, tail, _max) ({ \
++ typeof(_max) (max) = (_max); \
++ ((((head) - (tail)) + \
++ (max)) & ((max) - 1)); })
++
+ /**
+ * enum ppe_queue_class_type - PPE queue class type
+ * @PPE_QUEUE_CLASS_PRIORITY: Queue offset configured from internal priority
+@@ -92,18 +98,28 @@ struct edma_intr_info {
+ /**
+ * struct edma_context - EDMA context.
+ * @netdev_arr: Net device for each EDMA port
++ * @dummy_dev: Dummy netdevice for RX DMA
+ * @ppe_dev: PPE device
+ * @hw_info: EDMA Hardware info
+ * @intr_info: EDMA Interrupt info
++ * @rxfill_rings: Rx fill Rings, SW is producer
++ * @rx_rings: Rx Desc Rings, SW is consumer
++ * @rx_page_mode: Page mode enabled or disabled
++ * @rx_buf_size: Rx buffer size for Jumbo MRU
+ */
+ struct edma_context {
+ struct net_device **netdev_arr;
++ struct net_device *dummy_dev;
+ struct ppe_device *ppe_dev;
+ struct edma_hw_info *hw_info;
+ struct edma_intr_info intr_info;
++ struct edma_rxfill_ring *rxfill_rings;
++ struct edma_rxdesc_ring *rx_rings;
++ u32 rx_page_mode;
++ u32 rx_buf_size;
+ };
+
+-/* Global EDMA context. */
++/* Global EDMA context */
+ extern struct edma_context *edma_ctx;
+
+ void edma_destroy(struct ppe_device *ppe_dev);
+@@ -111,6 +127,10 @@ int edma_setup(struct ppe_device *ppe_de
+ int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
+ enum ppe_queue_class_type class,
+ int index, int queue_offset);
++int ppe_edma_queue_resource_get(struct ppe_device *ppe_dev, int type,
++ int *res_start, int *res_end);
++int ppe_edma_ring_to_queues_config(struct ppe_device *ppe_dev, int ring_id,
++ int num, int queues[] __counted_by(num));
+
+
+ #endif
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+@@ -0,0 +1,964 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Configure rings, Buffers and NAPI for receive path along with
++ * providing APIs to enable, disable, clean and map the Rx rings.
++ */
++
++#include <linux/cpumask.h>
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++#include <linux/skbuff.h>
++
++#include "edma.h"
++#include "edma_cfg_rx.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++/* EDMA Queue ID to Ring ID Table. */
++#define EDMA_QID2RID_TABLE_MEM(q) (0xb9000 + (0x4 * (q)))
++
++/* Rx ring queue offset. */
++#define EDMA_QUEUE_OFFSET(q_id) ((q_id) / EDMA_MAX_PRI_PER_CORE)
++
++/* Rx EDMA maximum queue supported. */
++#define EDMA_CPU_PORT_QUEUE_MAX(queue_start) \
++ ((queue_start) + (EDMA_MAX_PRI_PER_CORE * num_possible_cpus()) - 1)
++
++/* EDMA Queue ID to Ring ID configuration. */
++#define EDMA_QID2RID_NUM_PER_REG 4
++
++int rx_queues[] = {0, 8, 16, 24};
++
++static u32 edma_rx_ring_queue_map[][EDMA_MAX_CORE] = {{ 0, 8, 16, 24 },
++ { 1, 9, 17, 25 },
++ { 2, 10, 18, 26 },
++ { 3, 11, 19, 27 },
++ { 4, 12, 20, 28 },
++ { 5, 13, 21, 29 },
++ { 6, 14, 22, 30 },
++ { 7, 15, 23, 31 }};
++
++static int edma_cfg_rx_desc_rings_reset_queue_mapping(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, ret;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ ret = ppe_edma_ring_to_queues_config(edma_ctx->ppe_dev, rxdesc_ring->ring_id,
++ ARRAY_SIZE(rx_queues), rx_queues);
++ if (ret) {
++ pr_err("Error in unmapping rxdesc ring %d to PPE queue mapping to disable its backpressure configuration\n",
++ i);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_reset_queue_priority(u32 rxdesc_ring_idx)
++{
++ u32 i, queue_id, ret;
++
++ for (i = 0; i < EDMA_MAX_PRI_PER_CORE; i++) {
++ queue_id = edma_rx_ring_queue_map[i][rxdesc_ring_idx];
++
++ ret = ppe_queue_priority_set(edma_ctx->ppe_dev, queue_id, i);
++ if (ret) {
++ pr_err("Error in resetting %u queue's priority\n",
++ queue_id);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_reset_queue_config(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, ret;
++
++ if (unlikely(rx->num_rings > num_possible_cpus())) {
++ pr_err("Invalid count of rxdesc rings: %d\n",
++ rx->num_rings);
++ return -EINVAL;
++ }
++
++ /* Unmap Rxdesc ring to PPE queue mapping */
++ ret = edma_cfg_rx_desc_rings_reset_queue_mapping();
++ if (ret) {
++ pr_err("Error in resetting Rx desc ring backpressure config\n");
++ return ret;
++ }
++
++ /* Reset the priority for PPE queues mapped to Rx rings */
++ for (i = 0; i < rx->num_rings; i++) {
++ ret = edma_cfg_rx_desc_ring_reset_queue_priority(i);
++ if (ret) {
++ pr_err("Error in resetting ring:%d queue's priority\n",
++ i + rx->ring_start);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_to_queue_mapping(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++ int ret;
++
++ /* Rxdesc ring to PPE queue mapping */
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ ret = ppe_edma_ring_to_queues_config(edma_ctx->ppe_dev,
++ rxdesc_ring->ring_id,
++ ARRAY_SIZE(rx_queues), rx_queues);
++ if (ret) {
++ pr_err("Error in configuring Rx ring to PPE queue mapping, ret: %d, id: %d\n",
++ ret, rxdesc_ring->ring_id);
++ if (!edma_cfg_rx_desc_rings_reset_queue_mapping())
++ pr_err("Error in resetting Rx desc ringbackpressure configurations\n");
++
++ return ret;
++ }
++
++ pr_debug("Rx desc ring %d to PPE queue mapping for backpressure:\n",
++ rxdesc_ring->ring_id);
++ }
++
++ return 0;
++}
++
++static void edma_cfg_rx_desc_ring_configure(struct edma_rxdesc_ring *rxdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_BA(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, (u32)(rxdesc_ring->pdma & EDMA_RXDESC_BA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_PREHEADER_BA(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, (u32)(rxdesc_ring->sdma & EDMA_RXDESC_PREHEADER_BA_MASK));
++
++ data = rxdesc_ring->count & EDMA_RXDESC_RING_SIZE_MASK;
++ data |= (EDMA_RXDESC_PL_DEFAULT_VALUE & EDMA_RXDESC_PL_OFFSET_MASK)
++ << EDMA_RXDESC_PL_OFFSET_SHIFT;
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_RING_SIZE(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++
++ /* Configure the Mitigation timer */
++ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_RX_MITIGATION_TIMER_DEF,
++ ppe_dev->clk_rate / MHZ);
++ data = ((data & EDMA_RX_MOD_TIMER_INIT_MASK)
++ << EDMA_RX_MOD_TIMER_INIT_SHIFT);
++ pr_debug("EDMA Rx mitigation timer value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RX_MOD_TIMER(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++
++ /* Configure the Mitigation packet count */
++ data = (EDMA_RX_MITIGATION_PKT_CNT_DEF & EDMA_RXDESC_LOW_THRE_MASK)
++ << EDMA_RXDESC_LOW_THRE_SHIFT;
++ pr_debug("EDMA Rx mitigation packet count value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_UGT_THRE(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++
++ /* Enable ring. Set ret mode to 'opaque'. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RX_INT_CTRL(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, EDMA_RX_NE_INT_EN);
++}
++
++static void edma_cfg_rx_qid_to_rx_desc_ring_mapping(void)
++{
++ u32 desc_index, ring_index, reg_index, data, q_id;
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 mcast_start, mcast_end, reg;
++ int ret;
++
++ desc_index = (rx->ring_start & EDMA_RX_RING_ID_MASK);
++
++ /* Here map all the queues to ring. */
++ for (q_id = EDMA_RX_QUEUE_START;
++ q_id <= EDMA_CPU_PORT_QUEUE_MAX(EDMA_RX_QUEUE_START);
++ q_id += EDMA_QID2RID_NUM_PER_REG) {
++ reg_index = q_id / EDMA_QID2RID_NUM_PER_REG;
++ ring_index = desc_index + EDMA_QUEUE_OFFSET(q_id);
++
++ data = FIELD_PREP(EDMA_RX_RING_ID_QUEUE0_MASK, ring_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE1_MASK, ring_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE2_MASK, ring_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE3_MASK, ring_index);
++
++ reg = EDMA_BASE_OFFSET + EDMA_QID2RID_TABLE_MEM(reg_index);
++ regmap_write(regmap, reg, data);
++ pr_debug("Configure QID2RID: %d reg:0x%x to 0x%x, desc_index: %d, reg_index: %d\n",
++ q_id, EDMA_QID2RID_TABLE_MEM(reg_index), data, desc_index, reg_index);
++ }
++
++ ret = ppe_edma_queue_resource_get(edma_ctx->ppe_dev, PPE_RES_MCAST,
++ &mcast_start, &mcast_end);
++ if (ret < 0) {
++ pr_err("Error in extracting multicast queue values\n");
++ return;
++ }
++
++ /* Map multicast queues to the first Rx ring. */
++ desc_index = (rx->ring_start & EDMA_RX_RING_ID_MASK);
++ for (q_id = mcast_start; q_id <= mcast_end;
++ q_id += EDMA_QID2RID_NUM_PER_REG) {
++ reg_index = q_id / EDMA_QID2RID_NUM_PER_REG;
++
++ data = FIELD_PREP(EDMA_RX_RING_ID_QUEUE0_MASK, desc_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE1_MASK, desc_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE2_MASK, desc_index);
++ data |= FIELD_PREP(EDMA_RX_RING_ID_QUEUE3_MASK, desc_index);
++
++ reg = EDMA_BASE_OFFSET + EDMA_QID2RID_TABLE_MEM(reg_index);
++ regmap_write(regmap, reg, data);
++
++ pr_debug("Configure QID2RID: %d reg:0x%x to 0x%x\n",
++ q_id, EDMA_QID2RID_TABLE_MEM(reg_index), data);
++ }
++}
++
++static void edma_cfg_rx_rings_to_rx_fill_mapping(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, data, reg;
++
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_0_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_1_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_2_ADDR, 0);
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring = &edma_ctx->rx_rings[i];
++ u32 data, reg, ring_id;
++
++ ring_id = rxdesc_ring->ring_id;
++ if (ring_id >= 0 && ring_id <= 9)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_0_ADDR;
++ else if (ring_id >= 10 && ring_id <= 19)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_1_ADDR;
++ else
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_2_ADDR;
++
++ pr_debug("Configure RXDESC:%u to use RXFILL:%u\n",
++ ring_id,
++ rxdesc_ring->rxfill->ring_id);
++
++ /* Set the Rx fill ring number in the mapping register. */
++ regmap_read(regmap, reg, &data);
++ data |= (rxdesc_ring->rxfill->ring_id &
++ EDMA_RXDESC2FILL_MAP_RXDESC_MASK) <<
++ ((ring_id % 10) * 3);
++ regmap_write(regmap, reg, data);
++ }
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_0_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_RXDESC2FILL_MAP_0_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_1_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_RXDESC2FILL_MAP_1_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC2FILL_MAP_2_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_RXDESC2FILL_MAP_2_ADDR: 0x%x\n", data);
++}
++
++/**
++ * edma_cfg_rx_rings_enable - Enable Rx and Rxfill rings
++ *
++ * Enable Rx and Rxfill rings.
++ */
++void edma_cfg_rx_rings_enable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ /* Enable Rx rings */
++ for (i = rx->ring_start; i < rx->ring_start + rx->num_rings; i++) {
++ u32 data;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CTRL(i);
++ regmap_read(regmap, reg, &data);
++ data |= EDMA_RXDESC_RX_EN;
++ regmap_write(regmap, reg, data);
++ }
++
++ for (i = rxfill->ring_start; i < rxfill->ring_start + rxfill->num_rings; i++) {
++ u32 data;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_RING_EN(i);
++ regmap_read(regmap, reg, &data);
++ data |= EDMA_RXFILL_RING_EN;
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_rx_rings_disable - Disable Rx and Rxfill rings
++ *
++ * Disable Rx and Rxfill rings.
++ */
++void edma_cfg_rx_rings_disable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ /* Disable Rx rings */
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring = NULL;
++ u32 data;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CTRL(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &data);
++ data &= ~EDMA_RXDESC_RX_EN;
++ regmap_write(regmap, reg, data);
++ }
++
++ /* Disable RxFill Rings */
++ for (i = 0; i < rxfill->num_rings; i++) {
++ struct edma_rxfill_ring *rxfill_ring = NULL;
++ u32 data;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_RING_EN(rxfill_ring->ring_id);
++ regmap_read(regmap, reg, &data);
++ data &= ~EDMA_RXFILL_RING_EN;
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_rx_mappings - Setup RX ring mapping
++ *
++ * Setup queue ID to Rx desc ring mapping.
++ */
++void edma_cfg_rx_ring_mappings(void)
++{
++ edma_cfg_rx_qid_to_rx_desc_ring_mapping();
++ edma_cfg_rx_rings_to_rx_fill_mapping();
++}
++
++static void edma_cfg_rx_fill_ring_cleanup(struct edma_rxfill_ring *rxfill_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct device *dev = ppe_dev->dev;
++ u16 cons_idx, curr_idx;
++ u32 data, reg;
++
++ /* Get RxFill ring producer index */
++ curr_idx = rxfill_ring->prod_idx & EDMA_RXFILL_PROD_IDX_MASK;
++
++ /* Get RxFill ring consumer index */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_CONS_IDX(rxfill_ring->ring_id);
++ regmap_read(regmap, reg, &data);
++ cons_idx = data & EDMA_RXFILL_CONS_IDX_MASK;
++
++ while (curr_idx != cons_idx) {
++ struct edma_rxfill_desc *rxfill_desc;
++ struct sk_buff *skb;
++
++ /* Get RxFill descriptor */
++ rxfill_desc = EDMA_RXFILL_DESC(rxfill_ring, cons_idx);
++
++ cons_idx = (cons_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++
++ /* Get skb from opaque */
++ skb = (struct sk_buff *)EDMA_RXFILL_OPAQUE_GET(rxfill_desc);
++ if (unlikely(!skb)) {
++ pr_err("Empty skb reference at index:%d\n",
++ cons_idx);
++ continue;
++ }
++
++ dev_kfree_skb_any(skb);
++ }
++
++ /* Free RxFill ring descriptors */
++ dma_free_coherent(dev, (sizeof(struct edma_rxfill_desc)
++ * rxfill_ring->count),
++ rxfill_ring->desc, rxfill_ring->dma);
++ rxfill_ring->desc = NULL;
++ rxfill_ring->dma = (dma_addr_t)0;
++}
++
++static int edma_cfg_rx_fill_ring_dma_alloc(struct edma_rxfill_ring *rxfill_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Allocate RxFill ring descriptors */
++ rxfill_ring->desc = dma_alloc_coherent(dev, (sizeof(struct edma_rxfill_desc)
++ * rxfill_ring->count),
++ &rxfill_ring->dma,
++ GFP_KERNEL | __GFP_ZERO);
++ if (unlikely(!rxfill_ring->desc))
++ return -ENOMEM;
++
++ return 0;
++}
++
++static int edma_cfg_rx_desc_ring_dma_alloc(struct edma_rxdesc_ring *rxdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ rxdesc_ring->pdesc = dma_alloc_coherent(dev, (sizeof(struct edma_rxdesc_pri)
++ * rxdesc_ring->count),
++ &rxdesc_ring->pdma, GFP_KERNEL | __GFP_ZERO);
++ if (unlikely(!rxdesc_ring->pdesc))
++ return -ENOMEM;
++
++ rxdesc_ring->sdesc = dma_alloc_coherent(dev, (sizeof(struct edma_rxdesc_sec)
++ * rxdesc_ring->count),
++ &rxdesc_ring->sdma, GFP_KERNEL | __GFP_ZERO);
++ if (unlikely(!rxdesc_ring->sdesc)) {
++ dma_free_coherent(dev, (sizeof(struct edma_rxdesc_pri)
++ * rxdesc_ring->count),
++ rxdesc_ring->pdesc,
++ rxdesc_ring->pdma);
++ rxdesc_ring->pdesc = NULL;
++ rxdesc_ring->pdma = (dma_addr_t)0;
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void edma_cfg_rx_desc_ring_cleanup(struct edma_rxdesc_ring *rxdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct device *dev = ppe_dev->dev;
++ u32 prod_idx, cons_idx, reg;
++
++ /* Get Rxdesc consumer & producer indices */
++ cons_idx = rxdesc_ring->cons_idx & EDMA_RXDESC_CONS_IDX_MASK;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_PROD_IDX(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &prod_idx);
++ prod_idx = prod_idx & EDMA_RXDESC_PROD_IDX_MASK;
++
++ /* Free any buffers assigned to any descriptors */
++ while (cons_idx != prod_idx) {
++ struct edma_rxdesc_pri *rxdesc_pri =
++ EDMA_RXDESC_PRI_DESC(rxdesc_ring, cons_idx);
++ struct sk_buff *skb;
++
++ /* Update consumer index */
++ cons_idx = (cons_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++
++ /* Get opaque from Rxdesc */
++ skb = (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(rxdesc_pri);
++ if (unlikely(!skb)) {
++ pr_warn("Empty skb reference at index:%d\n",
++ cons_idx);
++ continue;
++ }
++
++ dev_kfree_skb_any(skb);
++ }
++
++ /* Update the consumer index */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CONS_IDX(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, cons_idx);
++
++ /* Free Rxdesc ring descriptor */
++ dma_free_coherent(dev, (sizeof(struct edma_rxdesc_pri)
++ * rxdesc_ring->count), rxdesc_ring->pdesc,
++ rxdesc_ring->pdma);
++ rxdesc_ring->pdesc = NULL;
++ rxdesc_ring->pdma = (dma_addr_t)0;
++
++ /* Free any buffers assigned to any secondary ring descriptors */
++ dma_free_coherent(dev, (sizeof(struct edma_rxdesc_sec)
++ * rxdesc_ring->count), rxdesc_ring->sdesc,
++ rxdesc_ring->sdma);
++ rxdesc_ring->sdesc = NULL;
++ rxdesc_ring->sdma = (dma_addr_t)0;
++}
++
++static int edma_cfg_rx_rings_setup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 ring_idx, alloc_size, buf_len;
++
++ /* Set buffer allocation size */
++ if (edma_ctx->rx_buf_size) {
++ alloc_size = edma_ctx->rx_buf_size +
++ EDMA_RX_SKB_HEADROOM + NET_IP_ALIGN;
++ buf_len = alloc_size - EDMA_RX_SKB_HEADROOM - NET_IP_ALIGN;
++ } else if (edma_ctx->rx_page_mode) {
++ alloc_size = EDMA_RX_PAGE_MODE_SKB_SIZE +
++ EDMA_RX_SKB_HEADROOM + NET_IP_ALIGN;
++ buf_len = PAGE_SIZE;
++ } else {
++ alloc_size = EDMA_RX_BUFFER_SIZE;
++ buf_len = alloc_size - EDMA_RX_SKB_HEADROOM - NET_IP_ALIGN;
++ }
++
++ pr_debug("EDMA ctx:%p rx_ring alloc_size=%d, buf_len=%d\n",
++ edma_ctx, alloc_size, buf_len);
++
++ /* Allocate Rx fill ring descriptors */
++ for (ring_idx = 0; ring_idx < rxfill->num_rings; ring_idx++) {
++ u32 ret;
++ struct edma_rxfill_ring *rxfill_ring = NULL;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[ring_idx];
++ rxfill_ring->count = EDMA_RX_RING_SIZE;
++ rxfill_ring->ring_id = rxfill->ring_start + ring_idx;
++ rxfill_ring->alloc_size = alloc_size;
++ rxfill_ring->buf_len = buf_len;
++ rxfill_ring->page_mode = edma_ctx->rx_page_mode;
++
++ ret = edma_cfg_rx_fill_ring_dma_alloc(rxfill_ring);
++ if (ret) {
++ pr_err("Error in setting up %d rxfill ring. ret: %d",
++ rxfill_ring->ring_id, ret);
++ while (--ring_idx >= 0)
++ edma_cfg_rx_fill_ring_cleanup(&edma_ctx->rxfill_rings[ring_idx]);
++
++ return -ENOMEM;
++ }
++ }
++
++ /* Allocate RxDesc ring descriptors */
++ for (ring_idx = 0; ring_idx < rx->num_rings; ring_idx++) {
++ u32 index, queue_id = EDMA_RX_QUEUE_START;
++ struct edma_rxdesc_ring *rxdesc_ring = NULL;
++ u32 ret;
++
++ rxdesc_ring = &edma_ctx->rx_rings[ring_idx];
++ rxdesc_ring->count = EDMA_RX_RING_SIZE;
++ rxdesc_ring->ring_id = rx->ring_start + ring_idx;
++
++ if (queue_id > EDMA_CPU_PORT_QUEUE_MAX(EDMA_RX_QUEUE_START)) {
++ pr_err("Invalid queue_id: %d\n", queue_id);
++ while (--ring_idx >= 0)
++ edma_cfg_rx_desc_ring_cleanup(&edma_ctx->rx_rings[ring_idx]);
++
++ goto rxdesc_mem_alloc_fail;
++ }
++
++ /* Create a mapping between RX Desc ring and Rx fill ring.
++ * Number of fill rings are lesser than the descriptor rings
++ * Share the fill rings across descriptor rings.
++ */
++ index = rxfill->ring_start +
++ (ring_idx % rxfill->num_rings);
++ rxdesc_ring->rxfill = &edma_ctx->rxfill_rings[index
++ - rxfill->ring_start];
++
++ ret = edma_cfg_rx_desc_ring_dma_alloc(rxdesc_ring);
++ if (ret) {
++ pr_err("Error in setting up %d rxdesc ring. ret: %d",
++ rxdesc_ring->ring_id, ret);
++ while (--ring_idx >= 0)
++ edma_cfg_rx_desc_ring_cleanup(&edma_ctx->rx_rings[ring_idx]);
++
++ goto rxdesc_mem_alloc_fail;
++ }
++ }
++
++ pr_debug("Rx descriptor count for Rx desc and Rx fill rings : %d\n",
++ EDMA_RX_RING_SIZE);
++
++ return 0;
++
++rxdesc_mem_alloc_fail:
++ for (ring_idx = 0; ring_idx < rxfill->num_rings; ring_idx++)
++ edma_cfg_rx_fill_ring_cleanup(&edma_ctx->rxfill_rings[ring_idx]);
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_rx_buff_size_setup - Configure EDMA Rx jumbo buffer
++ *
++ * Configure EDMA Rx jumbo buffer
++ */
++void edma_cfg_rx_buff_size_setup(void)
++{
++ if (edma_ctx->rx_buf_size) {
++ edma_ctx->rx_page_mode = false;
++ pr_debug("Rx Jumbo mru is enabled: %d\n", edma_ctx->rx_buf_size);
++ }
++}
++
++/**
++ * edma_cfg_rx_rings_alloc - Allocate EDMA Rx rings
++ *
++ * Allocate EDMA Rx rings.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++int edma_cfg_rx_rings_alloc(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ int ret;
++
++ edma_ctx->rxfill_rings = kzalloc((sizeof(*edma_ctx->rxfill_rings) *
++ rxfill->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->rxfill_rings)
++ return -ENOMEM;
++
++ edma_ctx->rx_rings = kzalloc((sizeof(*edma_ctx->rx_rings) *
++ rx->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->rx_rings)
++ goto rxdesc_ring_alloc_fail;
++
++ pr_debug("RxDesc:%u rx (%u-%u) RxFill:%u (%u-%u)\n",
++ rx->num_rings, rx->ring_start,
++ (rx->ring_start + rx->num_rings - 1),
++ rxfill->num_rings, rxfill->ring_start,
++ (rxfill->ring_start + rxfill->num_rings - 1));
++
++ if (edma_cfg_rx_rings_setup()) {
++ pr_err("Error in setting up Rx rings\n");
++ goto rx_rings_setup_fail;
++ }
++
++ /* Reset Rx descriptor ring mapped queue's configurations */
++ ret = edma_cfg_rx_desc_ring_reset_queue_config();
++ if (ret) {
++ pr_err("Error in resetting the Rx descriptor rings configurations\n");
++ edma_cfg_rx_rings_cleanup();
++ return ret;
++ }
++
++ return 0;
++
++rx_rings_setup_fail:
++ kfree(edma_ctx->rx_rings);
++ edma_ctx->rx_rings = NULL;
++rxdesc_ring_alloc_fail:
++ kfree(edma_ctx->rxfill_rings);
++ edma_ctx->rxfill_rings = NULL;
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_rx_rings_cleanup - Cleanup EDMA Rx rings
++ *
++ * Cleanup EDMA Rx rings
++ */
++void edma_cfg_rx_rings_cleanup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ /* Free RxFill ring descriptors */
++ for (i = 0; i < rxfill->num_rings; i++)
++ edma_cfg_rx_fill_ring_cleanup(&edma_ctx->rxfill_rings[i]);
++
++ /* Free Rx completion ring descriptors */
++ for (i = 0; i < rx->num_rings; i++)
++ edma_cfg_rx_desc_ring_cleanup(&edma_ctx->rx_rings[i]);
++
++ kfree(edma_ctx->rxfill_rings);
++ kfree(edma_ctx->rx_rings);
++ edma_ctx->rxfill_rings = NULL;
++ edma_ctx->rx_rings = NULL;
++}
++
++static void edma_cfg_rx_fill_ring_configure(struct edma_rxfill_ring *rxfill_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 ring_sz, reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_BA(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, (u32)(rxfill_ring->dma & EDMA_RING_DMA_MASK));
++
++ ring_sz = rxfill_ring->count & EDMA_RXFILL_RING_SIZE_MASK;
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_RING_SIZE(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, ring_sz);
++
++ edma_rx_alloc_buffer(rxfill_ring, rxfill_ring->count - 1);
++}
++
++static void edma_cfg_rx_desc_ring_flow_control(u32 threshold_xoff, u32 threshold_xon)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 data, i, reg;
++
++ data = (threshold_xoff & EDMA_RXDESC_FC_XOFF_THRE_MASK) << EDMA_RXDESC_FC_XOFF_THRE_SHIFT;
++ data |= ((threshold_xon & EDMA_RXDESC_FC_XON_THRE_MASK) << EDMA_RXDESC_FC_XON_THRE_SHIFT);
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_FC_THRE(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, data);
++ }
++}
++
++static void edma_cfg_rx_fill_ring_flow_control(int threshold_xoff, int threshold_xon)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, i, reg;
++
++ data = (threshold_xoff & EDMA_RXFILL_FC_XOFF_THRE_MASK) << EDMA_RXFILL_FC_XOFF_THRE_SHIFT;
++ data |= ((threshold_xon & EDMA_RXFILL_FC_XON_THRE_MASK) << EDMA_RXFILL_FC_XON_THRE_SHIFT);
++
++ for (i = 0; i < rxfill->num_rings; i++) {
++ struct edma_rxfill_ring *rxfill_ring;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_FC_THRE(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_rx_rings - Configure EDMA Rx rings.
++ *
++ * Configure EDMA Rx rings.
++ */
++int edma_cfg_rx_rings(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rxfill->num_rings; i++)
++ edma_cfg_rx_fill_ring_configure(&edma_ctx->rxfill_rings[i]);
++
++ for (i = 0; i < rx->num_rings; i++)
++ edma_cfg_rx_desc_ring_configure(&edma_ctx->rx_rings[i]);
++
++ /* Configure Rx flow control configurations */
++ edma_cfg_rx_desc_ring_flow_control(EDMA_RX_FC_XOFF_DEF, EDMA_RX_FC_XON_DEF);
++ edma_cfg_rx_fill_ring_flow_control(EDMA_RX_FC_XOFF_DEF, EDMA_RX_FC_XON_DEF);
++
++ return edma_cfg_rx_desc_ring_to_queue_mapping();
++}
++
++/**
++ * edma_cfg_rx_disable_interrupts - EDMA disable RX interrupts
++ *
++ * Disable RX interrupt masks
++ */
++void edma_cfg_rx_disable_interrupts(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring =
++ &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_CLEAR);
++ }
++}
++
++/**
++ * edma_cfg_rx_enable_interrupts - EDMA enable RX interrupts
++ *
++ * Enable RX interrupt masks
++ */
++void edma_cfg_rx_enable_interrupts(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i, reg;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring =
++ &edma_ctx->rx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_rx);
++ }
++}
++
++/**
++ * edma_cfg_rx_napi_disable - Disable NAPI for Rx
++ *
++ * Disable NAPI for Rx
++ */
++void edma_cfg_rx_napi_disable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ if (!rxdesc_ring->napi_added)
++ continue;
++
++ napi_disable(&rxdesc_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_rx_napi_enable - Enable NAPI for Rx
++ *
++ * Enable NAPI for Rx
++ */
++void edma_cfg_rx_napi_enable(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ if (!rxdesc_ring->napi_added)
++ continue;
++
++ napi_enable(&rxdesc_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_rx_napi_delete - Delete Rx NAPI
++ *
++ * Delete RX NAPI
++ */
++void edma_cfg_rx_napi_delete(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ if (!rxdesc_ring->napi_added)
++ continue;
++
++ netif_napi_del(&rxdesc_ring->napi);
++ rxdesc_ring->napi_added = false;
++ }
++}
++
++/* Add Rx NAPI */
++/**
++ * edma_cfg_rx_napi_add - Add Rx NAPI
++ * @netdev: Netdevice
++ *
++ * Add RX NAPI
++ */
++void edma_cfg_rx_napi_add(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rx = hw_info->rx;
++ u32 i;
++
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring = &edma_ctx->rx_rings[i];
++
++ netif_napi_add_weight(edma_ctx->dummy_dev, &rxdesc_ring->napi,
++ edma_rx_napi_poll, hw_info->napi_budget_rx);
++ rxdesc_ring->napi_added = true;
++ }
++
++ netdev_dbg(edma_ctx->dummy_dev, "Rx NAPI budget: %d\n", hw_info->napi_budget_rx);
++}
++
++/**
++ * edma_cfg_rx_rps_hash_map - Configure rx rps hash map.
++ *
++ * Initialize and configure RPS hash map for queues
++ */
++int edma_cfg_rx_rps_hash_map(void)
++{
++ cpumask_t edma_rps_cpumask = {{EDMA_RX_DEFAULT_BITMAP}};
++ int map_len = 0, idx = 0, ret = 0;
++ u32 q_off = EDMA_RX_QUEUE_START;
++ u32 q_map[EDMA_MAX_CORE] = {0};
++ u32 hash, cpu;
++
++ /* Map all possible hash values to queues used by the EDMA Rx
++ * rings based on a bitmask, which represents the cores to be mapped.
++ * These queues are expected to be mapped to different Rx rings
++ * which are assigned to different cores using IRQ affinity configuration.
++ */
++ for_each_cpu(cpu, &edma_rps_cpumask) {
++ q_map[map_len] = q_off + (cpu * EDMA_MAX_PRI_PER_CORE);
++ map_len++;
++ }
++
++ for (hash = 0; hash < PPE_QUEUE_HASH_NUM; hash++) {
++ ret = ppe_edma_queue_offset_config(edma_ctx->ppe_dev,
++ PPE_QUEUE_CLASS_HASH, hash, q_map[idx]);
++ if (ret)
++ return ret;
++
++ pr_debug("profile_id: %u, hash: %u, q_off: %u\n",
++ EDMA_CPU_PORT_PROFILE_ID, hash, q_map[idx]);
++ idx = (idx + 1) % map_len;
++ }
++
++ return 0;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+@@ -0,0 +1,48 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_CFG_RX__
++#define __EDMA_CFG_RX__
++
++/* SKB payload size used in page mode */
++#define EDMA_RX_PAGE_MODE_SKB_SIZE 256
++
++/* Rx flow control X-OFF default value */
++#define EDMA_RX_FC_XOFF_DEF 32
++
++/* Rx flow control X-ON default value */
++#define EDMA_RX_FC_XON_DEF 64
++
++/* Rx AC flow control original threshold */
++#define EDMA_RX_AC_FC_THRE_ORIG 0x190
++
++/* Rx AC flow control default threshold */
++#define EDMA_RX_AC_FC_THRES_DEF 0x104
++/* Rx mitigation timer's default value in microseconds */
++#define EDMA_RX_MITIGATION_TIMER_DEF 25
++
++/* Rx mitigation packet count's default value */
++#define EDMA_RX_MITIGATION_PKT_CNT_DEF 16
++
++/* Default bitmap of cores for RPS to ARM cores */
++#define EDMA_RX_DEFAULT_BITMAP ((1 << EDMA_MAX_CORE) - 1)
++
++int edma_cfg_rx_rings(void);
++int edma_cfg_rx_rings_alloc(void);
++void edma_cfg_rx_ring_mappings(void);
++void edma_cfg_rx_rings_cleanup(void);
++void edma_cfg_rx_disable_interrupts(void);
++void edma_cfg_rx_enable_interrupts(void);
++void edma_cfg_rx_napi_disable(void);
++void edma_cfg_rx_napi_enable(void);
++void edma_cfg_rx_napi_delete(void);
++void edma_cfg_rx_napi_add(void);
++void edma_cfg_rx_mapping(void);
++void edma_cfg_rx_rings_enable(void);
++void edma_cfg_rx_rings_disable(void);
++void edma_cfg_rx_buff_size_setup(void);
++int edma_cfg_rx_rps_hash_map(void);
++int edma_cfg_rx_rps(struct ctl_table *table, int write,
++ void *buffer, size_t *lenp, loff_t *ppos);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -12,12 +12,39 @@
+ #include <linux/printk.h>
+
+ #include "edma.h"
++#include "edma_cfg_rx.h"
+ #include "edma_port.h"
+ #include "ppe_regs.h"
+
+ /* Number of netdev queues. */
+ #define EDMA_NETDEV_QUEUE_NUM 4
+
++static int edma_port_stats_alloc(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ if (!port_priv)
++ return -EINVAL;
++
++ /* Allocate per-cpu stats memory. */
++ port_priv->pcpu_stats.rx_stats =
++ netdev_alloc_pcpu_stats(struct edma_port_rx_stats);
++ if (!port_priv->pcpu_stats.rx_stats) {
++ netdev_err(netdev, "Per-cpu EDMA Rx stats alloc failed for %s\n",
++ netdev->name);
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void edma_port_stats_free(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++
++ free_percpu(port_priv->pcpu_stats.rx_stats);
++}
++
+ static u16 __maybe_unused edma_port_select_queue(__maybe_unused struct net_device *netdev,
+ __maybe_unused struct sk_buff *skb,
+ __maybe_unused struct net_device *sb_dev)
+@@ -172,6 +199,7 @@ void edma_port_destroy(struct ppe_port *
+ int port_id = port->port_id;
+ struct net_device *netdev = edma_ctx->netdev_arr[port_id - 1];
+
++ edma_port_stats_free(netdev);
+ unregister_netdev(netdev);
+ free_netdev(netdev);
+ ppe_port_phylink_destroy(port);
+@@ -232,6 +260,13 @@ int edma_port_setup(struct ppe_port *por
+ port_id, netdev->dev_addr);
+ }
+
++ /* Allocate memory for EDMA port statistics. */
++ ret = edma_port_stats_alloc(netdev);
++ if (ret) {
++ netdev_dbg(netdev, "EDMA port stats alloc failed\n");
++ goto stats_alloc_fail;
++ }
++
+ netdev_dbg(netdev, "Configuring the port %s(qcom-id:%d)\n",
+ netdev->name, port_id);
+
+@@ -263,8 +298,10 @@ int edma_port_setup(struct ppe_port *por
+ register_netdev_fail:
+ ppe_port_phylink_destroy(port);
+ port_phylink_setup_fail:
+- free_netdev(netdev);
+ edma_ctx->netdev_arr[port_id - 1] = NULL;
++ edma_port_stats_free(netdev);
++stats_alloc_fail:
++ free_netdev(netdev);
+
+ return ret;
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.h
+@@ -15,14 +15,45 @@
+ | NETIF_F_TSO6)
+
+ /**
++ * struct edma_port_rx_stats - EDMA RX per CPU stats for the port.
++ * @rx_pkts: Number of Rx packets
++ * @rx_bytes: Number of Rx bytes
++ * @rx_drops: Number of Rx drops
++ * @rx_nr_frag_pkts: Number of Rx nr_frags packets
++ * @rx_fraglist_pkts: Number of Rx fraglist packets
++ * @rx_nr_frag_headroom_err: nr_frags headroom error packets
++ * @syncp: Synchronization pointer
++ */
++struct edma_port_rx_stats {
++ u64 rx_pkts;
++ u64 rx_bytes;
++ u64 rx_drops;
++ u64 rx_nr_frag_pkts;
++ u64 rx_fraglist_pkts;
++ u64 rx_nr_frag_headroom_err;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_port_pcpu_stats - EDMA per cpu stats data structure for the port.
++ * @rx_stats: Per CPU Rx statistics
++ */
++struct edma_port_pcpu_stats {
++ struct edma_port_rx_stats __percpu *rx_stats;
++};
++
++/**
+ * struct edma_port_priv - EDMA port priv structure.
+ * @ppe_port: Pointer to PPE port
+ * @netdev: Corresponding netdevice
++ * @pcpu_stats: Per CPU netdev statistics
++ * @txr_map: Tx ring per-core mapping
+ * @flags: Feature flags
+ */
+ struct edma_port_priv {
+ struct ppe_port *ppe_port;
+ struct net_device *netdev;
++ struct edma_port_pcpu_stats pcpu_stats;
+ unsigned long flags;
+ };
+
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_rx.c
+@@ -0,0 +1,622 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Provides APIs to alloc Rx Buffers, reap the buffers, receive and
++ * process linear and Scatter Gather packets.
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/etherdevice.h>
++#include <linux/irqreturn.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/platform_device.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++
++#include "edma.h"
++#include "edma_cfg_rx.h"
++#include "edma_port.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++static int edma_rx_alloc_buffer_list(struct edma_rxfill_ring *rxfill_ring, int alloc_count)
++{
++ struct edma_rxfill_stats *rxfill_stats = &rxfill_ring->rxfill_stats;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 rx_alloc_size = rxfill_ring->alloc_size;
++ struct regmap *regmap = ppe_dev->regmap;
++ bool page_mode = rxfill_ring->page_mode;
++ struct edma_rxfill_desc *rxfill_desc;
++ u32 buf_len = rxfill_ring->buf_len;
++ struct device *dev = ppe_dev->dev;
++ u16 prod_idx, start_idx;
++ u16 num_alloc = 0;
++ u32 reg;
++
++ prod_idx = rxfill_ring->prod_idx;
++ start_idx = prod_idx;
++
++ while (likely(alloc_count--)) {
++ dma_addr_t buff_addr;
++ struct sk_buff *skb;
++ struct page *pg;
++
++ rxfill_desc = EDMA_RXFILL_DESC(rxfill_ring, prod_idx);
++
++ skb = dev_alloc_skb(rx_alloc_size);
++ if (unlikely(!skb)) {
++ u64_stats_update_begin(&rxfill_stats->syncp);
++ ++rxfill_stats->alloc_failed;
++ u64_stats_update_end(&rxfill_stats->syncp);
++ break;
++ }
++
++ skb_reserve(skb, EDMA_RX_SKB_HEADROOM + NET_IP_ALIGN);
++
++ if (likely(!page_mode)) {
++ buff_addr = dma_map_single(dev, skb->data, rx_alloc_size, DMA_FROM_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "edma_context:%p Unable to dma for non page mode",
++ edma_ctx);
++ dev_kfree_skb_any(skb);
++ break;
++ }
++ } else {
++ pg = alloc_page(GFP_ATOMIC);
++ if (unlikely(!pg)) {
++ u64_stats_update_begin(&rxfill_stats->syncp);
++ ++rxfill_stats->page_alloc_failed;
++ u64_stats_update_end(&rxfill_stats->syncp);
++ dev_kfree_skb_any(skb);
++ dev_dbg(dev, "edma_context:%p Unable to allocate page",
++ edma_ctx);
++ break;
++ }
++
++ buff_addr = dma_map_page(dev, pg, 0, PAGE_SIZE, DMA_FROM_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "edma_context:%p Mapping error for page mode",
++ edma_ctx);
++ __free_page(pg);
++ dev_kfree_skb_any(skb);
++ break;
++ }
++
++ skb_fill_page_desc(skb, 0, pg, 0, PAGE_SIZE);
++ }
++
++ EDMA_RXFILL_BUFFER_ADDR_SET(rxfill_desc, buff_addr);
++
++ EDMA_RXFILL_OPAQUE_LO_SET(rxfill_desc, skb);
++#ifdef __LP64__
++ EDMA_RXFILL_OPAQUE_HI_SET(rxfill_desc, skb);
++#endif
++ EDMA_RXFILL_PACKET_LEN_SET(rxfill_desc,
++ (u32)(buf_len) & EDMA_RXFILL_BUF_SIZE_MASK);
++ prod_idx = (prod_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++ num_alloc++;
++ }
++
++ if (likely(num_alloc)) {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXFILL_PROD_IDX(rxfill_ring->ring_id);
++ regmap_write(regmap, reg, prod_idx);
++ rxfill_ring->prod_idx = prod_idx;
++ }
++
++ return num_alloc;
++}
++
++/**
++ * edma_rx_alloc_buffer - EDMA Rx alloc buffer.
++ * @rxfill_ring: EDMA Rxfill ring
++ * @alloc_count: Number of rings to alloc
++ *
++ * Alloc Rx buffers for RxFill ring.
++ *
++ * Return the number of rings allocated.
++ */
++int edma_rx_alloc_buffer(struct edma_rxfill_ring *rxfill_ring, int alloc_count)
++{
++ return edma_rx_alloc_buffer_list(rxfill_ring, alloc_count);
++}
++
++/* Mark ip_summed appropriately in the skb as per the L3/L4 checksum
++ * status in descriptor.
++ */
++static void edma_rx_checksum_verify(struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ u8 pid = EDMA_RXDESC_PID_GET(rxdesc_pri);
++
++ skb_checksum_none_assert(skb);
++
++ if (likely(EDMA_RX_PID_IS_IPV4(pid))) {
++ if (likely(EDMA_RXDESC_L3CSUM_STATUS_GET(rxdesc_pri)) &&
++ likely(EDMA_RXDESC_L4CSUM_STATUS_GET(rxdesc_pri)))
++ skb->ip_summed = CHECKSUM_UNNECESSARY;
++ } else if (likely(EDMA_RX_PID_IS_IPV6(pid))) {
++ if (likely(EDMA_RXDESC_L4CSUM_STATUS_GET(rxdesc_pri)))
++ skb->ip_summed = CHECKSUM_UNNECESSARY;
++ }
++}
++
++static void edma_rx_process_last_segment(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ bool page_mode = rxdesc_ring->rxfill->page_mode;
++ struct edma_port_pcpu_stats *pcpu_stats;
++ struct edma_port_rx_stats *rx_stats;
++ struct edma_port_priv *port_dev;
++ struct sk_buff *skb_head;
++ struct net_device *dev;
++ u32 pkt_length;
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++
++ skb_head = rxdesc_ring->head;
++ dev = skb_head->dev;
++
++ /* Check Rx checksum offload status. */
++ if (likely(dev->features & NETIF_F_RXCSUM))
++ edma_rx_checksum_verify(rxdesc_pri, skb_head);
++
++ /* Get stats for the netdevice. */
++ port_dev = netdev_priv(dev);
++ pcpu_stats = &port_dev->pcpu_stats;
++ rx_stats = this_cpu_ptr(pcpu_stats->rx_stats);
++
++ if (unlikely(page_mode)) {
++ if (unlikely(!pskb_may_pull(skb_head, ETH_HLEN))) {
++ /* Discard the SKB that we have been building,
++ * in addition to the SKB linked to current descriptor.
++ */
++ dev_kfree_skb_any(skb_head);
++ rxdesc_ring->head = NULL;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = NULL;
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_nr_frag_headroom_err++;
++ u64_stats_update_end(&rx_stats->syncp);
++
++ return;
++ }
++ }
++
++ if (unlikely(!pskb_pull(skb_head, EDMA_RXDESC_DATA_OFFSET_GET(rxdesc_ring->pdesc_head)))) {
++ dev_kfree_skb_any(skb_head);
++ rxdesc_ring->head = NULL;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = NULL;
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_nr_frag_headroom_err++;
++ u64_stats_update_end(&rx_stats->syncp);
++
++ return;
++ }
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_pkts++;
++ rx_stats->rx_bytes += skb_head->len;
++ rx_stats->rx_nr_frag_pkts += (u64)page_mode;
++ rx_stats->rx_fraglist_pkts += (u64)(!page_mode);
++ u64_stats_update_end(&rx_stats->syncp);
++
++ pr_debug("edma_context:%p skb:%p Jumbo pkt_length:%u\n",
++ edma_ctx, skb_head, skb_head->len);
++
++ skb_head->protocol = eth_type_trans(skb_head, dev);
++
++ /* Send packet up the stack. */
++ if (dev->features & NETIF_F_GRO)
++ napi_gro_receive(&rxdesc_ring->napi, skb_head);
++ else
++ netif_receive_skb(skb_head);
++
++ rxdesc_ring->head = NULL;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = NULL;
++}
++
++static void edma_rx_handle_frag_list(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ u32 pkt_length;
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++ pr_debug("edma_context:%p skb:%p fragment pkt_length:%u\n",
++ edma_ctx, skb, pkt_length);
++
++ if (!(rxdesc_ring->head)) {
++ skb_put(skb, pkt_length);
++ rxdesc_ring->head = skb;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = rxdesc_pri;
++
++ return;
++ }
++
++ /* Append it to the fraglist of head if this is second frame
++ * If not second frame append to tail.
++ */
++ skb_put(skb, pkt_length);
++ if (!skb_has_frag_list(rxdesc_ring->head))
++ skb_shinfo(rxdesc_ring->head)->frag_list = skb;
++ else
++ rxdesc_ring->last->next = skb;
++
++ rxdesc_ring->last = skb;
++ rxdesc_ring->last->next = NULL;
++ rxdesc_ring->head->len += pkt_length;
++ rxdesc_ring->head->data_len += pkt_length;
++ rxdesc_ring->head->truesize += skb->truesize;
++
++ /* If there are more segments for this packet,
++ * then we have nothing to do. Otherwise process
++ * last segment and send packet to stack.
++ */
++ if (EDMA_RXDESC_MORE_BIT_GET(rxdesc_pri))
++ return;
++
++ edma_rx_process_last_segment(rxdesc_ring, rxdesc_pri, skb);
++}
++
++static void edma_rx_handle_nr_frags(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ skb_frag_t *frag = NULL;
++ u32 pkt_length;
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++ pr_debug("edma_context:%p skb:%p fragment pkt_length:%u\n",
++ edma_ctx, skb, pkt_length);
++
++ if (!(rxdesc_ring->head)) {
++ skb->len = pkt_length;
++ skb->data_len = pkt_length;
++ skb->truesize = SKB_TRUESIZE(PAGE_SIZE);
++ rxdesc_ring->head = skb;
++ rxdesc_ring->last = NULL;
++ rxdesc_ring->pdesc_head = rxdesc_pri;
++
++ return;
++ }
++
++ frag = &skb_shinfo(skb)->frags[0];
++
++ /* Append current frag at correct index as nr_frag of parent. */
++ skb_add_rx_frag(rxdesc_ring->head, skb_shinfo(rxdesc_ring->head)->nr_frags,
++ skb_frag_page(frag), 0, pkt_length, PAGE_SIZE);
++ skb_shinfo(skb)->nr_frags = 0;
++
++ /* Free the SKB after we have appended its frag page to the head skb. */
++ dev_kfree_skb_any(skb);
++
++ /* If there are more segments for this packet,
++ * then we have nothing to do. Otherwise process
++ * last segment and send packet to stack.
++ */
++ if (EDMA_RXDESC_MORE_BIT_GET(rxdesc_pri))
++ return;
++
++ edma_rx_process_last_segment(rxdesc_ring, rxdesc_pri, skb);
++}
++
++static bool edma_rx_handle_linear_packets(struct edma_rxdesc_ring *rxdesc_ring,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ bool page_mode = rxdesc_ring->rxfill->page_mode;
++ struct edma_port_pcpu_stats *pcpu_stats;
++ struct edma_port_rx_stats *rx_stats;
++ struct edma_port_priv *port_dev;
++ skb_frag_t *frag = NULL;
++ u32 pkt_length;
++
++ /* Get stats for the netdevice. */
++ port_dev = netdev_priv(skb->dev);
++ pcpu_stats = &port_dev->pcpu_stats;
++ rx_stats = this_cpu_ptr(pcpu_stats->rx_stats);
++
++ /* Get packet length. */
++ pkt_length = EDMA_RXDESC_PACKET_LEN_GET(rxdesc_pri);
++
++ if (likely(!page_mode)) {
++ skb_put(skb, pkt_length);
++ goto send_to_stack;
++ }
++
++ /* Handle linear packet in page mode. */
++ frag = &skb_shinfo(skb)->frags[0];
++ skb_add_rx_frag(skb, 0, skb_frag_page(frag), 0, pkt_length, PAGE_SIZE);
++
++ /* Pull ethernet header into SKB data area for header processing. */
++ if (unlikely(!pskb_may_pull(skb, ETH_HLEN))) {
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_nr_frag_headroom_err++;
++ u64_stats_update_end(&rx_stats->syncp);
++ dev_kfree_skb_any(skb);
++
++ return false;
++ }
++
++send_to_stack:
++
++ __skb_pull(skb, EDMA_RXDESC_DATA_OFFSET_GET(rxdesc_pri));
++
++ /* Check Rx checksum offload status. */
++ if (likely(skb->dev->features & NETIF_F_RXCSUM))
++ edma_rx_checksum_verify(rxdesc_pri, skb);
++
++ u64_stats_update_begin(&rx_stats->syncp);
++ rx_stats->rx_pkts++;
++ rx_stats->rx_bytes += pkt_length;
++ rx_stats->rx_nr_frag_pkts += (u64)page_mode;
++ u64_stats_update_end(&rx_stats->syncp);
++
++ skb->protocol = eth_type_trans(skb, skb->dev);
++ if (skb->dev->features & NETIF_F_GRO)
++ napi_gro_receive(&rxdesc_ring->napi, skb);
++ else
++ netif_receive_skb(skb);
++
++ netdev_dbg(skb->dev, "edma_context:%p, skb:%p pkt_length:%u\n",
++ edma_ctx, skb, skb->len);
++
++ return true;
++}
++
++static struct net_device *edma_rx_get_src_dev(struct edma_rxdesc_stats *rxdesc_stats,
++ struct edma_rxdesc_pri *rxdesc_pri,
++ struct sk_buff *skb)
++{
++ u32 src_info = EDMA_RXDESC_SRC_INFO_GET(rxdesc_pri);
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct net_device *ndev = NULL;
++ u8 src_port_num;
++
++ /* Check src_info. */
++ if (likely((src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK)
++ == EDMA_RXDESC_SRCINFO_TYPE_PORTID)) {
++ src_port_num = src_info & EDMA_RXDESC_PORTNUM_BITS;
++ } else {
++ if (net_ratelimit()) {
++ pr_warn("Invalid src info_type:0x%x. Drop skb:%p\n",
++ (src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK), skb);
++ }
++
++ u64_stats_update_begin(&rxdesc_stats->syncp);
++ ++rxdesc_stats->src_port_inval_type;
++ u64_stats_update_end(&rxdesc_stats->syncp);
++
++ return NULL;
++ }
++
++ /* Packet with PP source. */
++ if (likely(src_port_num <= hw_info->max_ports)) {
++ if (unlikely(src_port_num < EDMA_START_IFNUM)) {
++ if (net_ratelimit())
++ pr_warn("Port number error :%d. Drop skb:%p\n",
++ src_port_num, skb);
++
++ u64_stats_update_begin(&rxdesc_stats->syncp);
++ ++rxdesc_stats->src_port_inval;
++ u64_stats_update_end(&rxdesc_stats->syncp);
++
++ return NULL;
++ }
++
++ /* Get netdev for this port using the source port
++ * number as index into the netdev array. We need to
++ * subtract one since the indices start form '0' and
++ * port numbers start from '1'.
++ */
++ ndev = edma_ctx->netdev_arr[src_port_num - 1];
++ }
++
++ if (likely(ndev))
++ return ndev;
++
++ if (net_ratelimit())
++ pr_warn("Netdev Null src_info_type:0x%x src port num:%d Drop skb:%p\n",
++ (src_info & EDMA_RXDESC_SRCINFO_TYPE_MASK),
++ src_port_num, skb);
++
++ u64_stats_update_begin(&rxdesc_stats->syncp);
++ ++rxdesc_stats->src_port_inval_netdev;
++ u64_stats_update_end(&rxdesc_stats->syncp);
++
++ return NULL;
++}
++
++static int edma_rx_reap(struct edma_rxdesc_ring *rxdesc_ring, int budget)
++{
++ struct edma_rxdesc_stats *rxdesc_stats = &rxdesc_ring->rxdesc_stats;
++ u32 alloc_size = rxdesc_ring->rxfill->alloc_size;
++ bool page_mode = rxdesc_ring->rxfill->page_mode;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_rxdesc_pri *next_rxdesc_pri;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct device *dev = ppe_dev->dev;
++ u32 prod_idx, cons_idx, end_idx;
++ u32 work_to_do, work_done = 0;
++ struct sk_buff *next_skb;
++ u32 work_leftover, reg;
++
++ /* Get Rx ring producer and consumer indices. */
++ cons_idx = rxdesc_ring->cons_idx;
++
++ if (likely(rxdesc_ring->work_leftover > EDMA_RX_MAX_PROCESS)) {
++ work_to_do = rxdesc_ring->work_leftover;
++ } else {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_PROD_IDX(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &prod_idx);
++ prod_idx = prod_idx & EDMA_RXDESC_PROD_IDX_MASK;
++ work_to_do = EDMA_DESC_AVAIL_COUNT(prod_idx,
++ cons_idx, EDMA_RX_RING_SIZE);
++ rxdesc_ring->work_leftover = work_to_do;
++ }
++
++ if (work_to_do > budget)
++ work_to_do = budget;
++
++ rxdesc_ring->work_leftover -= work_to_do;
++ end_idx = (cons_idx + work_to_do) & EDMA_RX_RING_SIZE_MASK;
++ next_rxdesc_pri = EDMA_RXDESC_PRI_DESC(rxdesc_ring, cons_idx);
++
++ /* Get opaque from RXDESC. */
++ next_skb = (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(next_rxdesc_pri);
++
++ work_leftover = work_to_do & (EDMA_RX_MAX_PROCESS - 1);
++ while (likely(work_to_do--)) {
++ struct edma_rxdesc_pri *rxdesc_pri;
++ struct net_device *ndev;
++ struct sk_buff *skb;
++ dma_addr_t dma_addr;
++
++ skb = next_skb;
++ rxdesc_pri = next_rxdesc_pri;
++ dma_addr = EDMA_RXDESC_BUFFER_ADDR_GET(rxdesc_pri);
++
++ if (!page_mode)
++ dma_unmap_single(dev, dma_addr, alloc_size,
++ DMA_TO_DEVICE);
++ else
++ dma_unmap_page(dev, dma_addr, PAGE_SIZE, DMA_TO_DEVICE);
++
++ /* Update consumer index. */
++ cons_idx = (cons_idx + 1) & EDMA_RX_RING_SIZE_MASK;
++
++ /* Get the next Rx descriptor. */
++ next_rxdesc_pri = EDMA_RXDESC_PRI_DESC(rxdesc_ring, cons_idx);
++
++ /* Handle linear packets or initial segments first. */
++ if (likely(!(rxdesc_ring->head))) {
++ ndev = edma_rx_get_src_dev(rxdesc_stats, rxdesc_pri, skb);
++ if (unlikely(!ndev)) {
++ dev_kfree_skb_any(skb);
++ goto next_rx_desc;
++ }
++
++ /* Update skb fields for head skb. */
++ skb->dev = ndev;
++ skb->skb_iif = ndev->ifindex;
++
++ /* Handle linear packets. */
++ if (likely(!EDMA_RXDESC_MORE_BIT_GET(rxdesc_pri))) {
++ next_skb =
++ (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(next_rxdesc_pri);
++
++ if (unlikely(!
++ edma_rx_handle_linear_packets(rxdesc_ring,
++ rxdesc_pri, skb)))
++ dev_kfree_skb_any(skb);
++
++ goto next_rx_desc;
++ }
++ }
++
++ next_skb = (struct sk_buff *)EDMA_RXDESC_OPAQUE_GET(next_rxdesc_pri);
++
++ /* Handle scatter frame processing for first/middle/last segments. */
++ page_mode ? edma_rx_handle_nr_frags(rxdesc_ring, rxdesc_pri, skb) :
++ edma_rx_handle_frag_list(rxdesc_ring, rxdesc_pri, skb);
++
++next_rx_desc:
++ /* Update work done. */
++ work_done++;
++
++ /* Check if we can refill EDMA_RX_MAX_PROCESS worth buffers,
++ * if yes, refill and update index before continuing.
++ */
++ if (unlikely(!(work_done & (EDMA_RX_MAX_PROCESS - 1)))) {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CONS_IDX(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, cons_idx);
++ rxdesc_ring->cons_idx = cons_idx;
++ edma_rx_alloc_buffer_list(rxdesc_ring->rxfill, EDMA_RX_MAX_PROCESS);
++ }
++ }
++
++ /* Check if we need to refill and update
++ * index for any buffers before exit.
++ */
++ if (unlikely(work_leftover)) {
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_CONS_IDX(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, cons_idx);
++ rxdesc_ring->cons_idx = cons_idx;
++ edma_rx_alloc_buffer_list(rxdesc_ring->rxfill, work_leftover);
++ }
++
++ return work_done;
++}
++
++/**
++ * edma_rx_napi_poll - EDMA Rx napi poll.
++ * @napi: NAPI structure
++ * @budget: Rx NAPI budget
++ *
++ * EDMA RX NAPI handler to handle the NAPI poll.
++ *
++ * Return the number of packets processed.
++ */
++int edma_rx_napi_poll(struct napi_struct *napi, int budget)
++{
++ struct edma_rxdesc_ring *rxdesc_ring = (struct edma_rxdesc_ring *)napi;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ int work_done = 0;
++ u32 status, reg;
++
++ do {
++ work_done += edma_rx_reap(rxdesc_ring, budget - work_done);
++ if (likely(work_done >= budget))
++ return work_done;
++
++ /* Check if there are more packets to process. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_STAT(rxdesc_ring->ring_id);
++ regmap_read(regmap, reg, &status);
++ status = status & EDMA_RXDESC_RING_INT_STATUS_MASK;
++ } while (likely(status));
++
++ napi_complete(napi);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_rx);
++
++ return work_done;
++}
++
++/**
++ * edma_rx_handle_irq - EDMA Rx handle irq.
++ * @irq: Interrupt to handle
++ * @ctx: Context
++ *
++ * Process RX IRQ and schedule NAPI.
++ *
++ * Return IRQ_HANDLED(1) on success.
++ */
++irqreturn_t edma_rx_handle_irq(int irq, void *ctx)
++{
++ struct edma_rxdesc_ring *rxdesc_ring = (struct edma_rxdesc_ring *)ctx;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ if (likely(napi_schedule_prep(&rxdesc_ring->napi))) {
++ /* Disable RxDesc interrupt. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->ring_id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_DISABLE);
++ __napi_schedule(&rxdesc_ring->napi);
++ }
++
++ return IRQ_HANDLED;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_rx.h
+@@ -0,0 +1,287 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_RX__
++#define __EDMA_RX__
++
++#include <linux/netdevice.h>
++
++#define EDMA_RXFILL_RING_PER_CORE_MAX 1
++#define EDMA_RXDESC_RING_PER_CORE_MAX 1
++
++/* Max Rx processing without replenishing RxFill ring. */
++#define EDMA_RX_MAX_PROCESS 32
++
++#define EDMA_RX_SKB_HEADROOM 128
++#define EDMA_RX_QUEUE_START 0
++#define EDMA_RX_BUFFER_SIZE 1984
++#define EDMA_MAX_CORE 4
++
++#define EDMA_GET_DESC(R, i, type) (&(((type *)((R)->desc))[(i)]))
++#define EDMA_GET_PDESC(R, i, type) (&(((type *)((R)->pdesc))[(i)]))
++#define EDMA_GET_SDESC(R, i, type) (&(((type *)((R)->sdesc))[(i)]))
++#define EDMA_RXFILL_DESC(R, i) EDMA_GET_DESC(R, i, \
++ struct edma_rxfill_desc)
++#define EDMA_RXDESC_PRI_DESC(R, i) EDMA_GET_PDESC(R, i, \
++ struct edma_rxdesc_pri)
++#define EDMA_RXDESC_SEC_DESC(R, i) EDMA_GET_SDESC(R, i, \
++ struct edma_rxdesc_sec)
++
++#define EDMA_RX_RING_SIZE 2048
++
++#define EDMA_RX_RING_SIZE_MASK (EDMA_RX_RING_SIZE - 1)
++#define EDMA_RX_RING_ID_MASK 0x1F
++
++#define EDMA_MAX_PRI_PER_CORE 8
++#define EDMA_RX_PID_IPV4_MAX 0x3
++#define EDMA_RX_PID_IPV6 0x4
++#define EDMA_RX_PID_IS_IPV4(pid) (!((pid) & (~EDMA_RX_PID_IPV4_MAX)))
++#define EDMA_RX_PID_IS_IPV6(pid) (!(!((pid) & EDMA_RX_PID_IPV6)))
++
++#define EDMA_RXDESC_BUFFER_ADDR_GET(desc) \
++ ((u32)(le32_to_cpu((__force __le32)((desc)->word0))))
++#define EDMA_RXDESC_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ ((uintptr_t)((u64)((desc)->word2) | \
++ ((u64)((desc)->word3) << 0x20))); })
++
++#define EDMA_RXDESC_SRCINFO_TYPE_PORTID 0x2000
++#define EDMA_RXDESC_SRCINFO_TYPE_MASK 0xF000
++#define EDMA_RXDESC_L3CSUM_STATUS_MASK BIT(13)
++#define EDMA_RXDESC_L4CSUM_STATUS_MASK BIT(12)
++#define EDMA_RXDESC_PORTNUM_BITS 0x0FFF
++
++#define EDMA_RXDESC_PACKET_LEN_MASK 0x3FFFF
++#define EDMA_RXDESC_PACKET_LEN_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ ((le32_to_cpu((__force __le32)((desc)->word5))) & \
++ EDMA_RXDESC_PACKET_LEN_MASK); })
++
++#define EDMA_RXDESC_MORE_BIT_MASK 0x40000000
++#define EDMA_RXDESC_MORE_BIT_GET(desc) ((le32_to_cpu((__force __le32)((desc)->word1))) & \
++ EDMA_RXDESC_MORE_BIT_MASK)
++#define EDMA_RXDESC_SRC_DST_INFO_GET(desc) \
++ ((u32)((le32_to_cpu((__force __le32)((desc)->word4)))))
++
++#define EDMA_RXDESC_L3_OFFSET_MASK GENMASK(23, 16)
++#define EDMA_RXDESC_L3_OFFSET_GET(desc) FIELD_GET(EDMA_RXDESC_L3_OFFSET_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word7)))
++
++#define EDMA_RXDESC_PID_MASK GENMASK(15, 12)
++#define EDMA_RXDESC_PID_GET(desc) FIELD_GET(EDMA_RXDESC_PID_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word7)))
++
++#define EDMA_RXDESC_DST_INFO_MASK GENMASK(31, 16)
++#define EDMA_RXDESC_DST_INFO_GET(desc) FIELD_GET(EDMA_RXDESC_DST_INFO_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word4)))
++
++#define EDMA_RXDESC_SRC_INFO_MASK GENMASK(15, 0)
++#define EDMA_RXDESC_SRC_INFO_GET(desc) FIELD_GET(EDMA_RXDESC_SRC_INFO_MASK, \
++ le32_to_cpu((__force __le32)((desc)->word4)))
++
++#define EDMA_RXDESC_PORT_ID_MASK GENMASK(11, 0)
++#define EDMA_RXDESC_PORT_ID_GET(x) FIELD_GET(EDMA_RXDESC_PORT_ID_MASK, x)
++
++#define EDMA_RXDESC_SRC_PORT_ID_GET(desc) (EDMA_RXDESC_PORT_ID_GET \
++ (EDMA_RXDESC_SRC_INFO_GET(desc)))
++#define EDMA_RXDESC_DST_PORT_ID_GET(desc) (EDMA_RXDESC_PORT_ID_GET \
++ (EDMA_RXDESC_DST_INFO_GET(desc)))
++
++#define EDMA_RXDESC_DST_PORT (0x2 << EDMA_RXDESC_PID_SHIFT)
++
++#define EDMA_RXDESC_L3CSUM_STATUS_GET(desc) FIELD_GET(EDMA_RXDESC_L3CSUM_STATUS_MASK, \
++ le32_to_cpu((__force __le32)(desc)->word6))
++#define EDMA_RXDESC_L4CSUM_STATUS_GET(desc) FIELD_GET(EDMA_RXDESC_L4CSUM_STATUS_MASK, \
++ le32_to_cpu((__force __le32)(desc)->word6))
++
++#define EDMA_RXDESC_DATA_OFFSET_MASK GENMASK(11, 0)
++#define EDMA_RXDESC_DATA_OFFSET_GET(desc) FIELD_GET(EDMA_RXDESC_DATA_OFFSET_MASK, \
++ le32_to_cpu((__force __le32)(desc)->word6))
++
++#define EDMA_RXFILL_BUF_SIZE_MASK 0xFFFF
++#define EDMA_RXFILL_BUF_SIZE_SHIFT 16
++
++/* Opaque values are not accessed by the EDMA HW,
++ * so endianness conversion is not needed.
++ */
++
++#define EDMA_RXFILL_OPAQUE_LO_SET(desc, ptr) (((desc)->word2) = \
++ (u32)(uintptr_t)(ptr))
++#ifdef __LP64__
++#define EDMA_RXFILL_OPAQUE_HI_SET(desc, ptr) (((desc)->word3) = \
++ (u32)((u64)(ptr) >> 0x20))
++#endif
++
++#define EDMA_RXFILL_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ ((uintptr_t)((u64)((desc)->word2) | \
++ ((u64)((desc)->word3) << 0x20))); })
++
++#define EDMA_RXFILL_PACKET_LEN_SET(desc, len) { \
++ (((desc)->word1) = (u32)((((u32)len) << EDMA_RXFILL_BUF_SIZE_SHIFT) & \
++ 0xFFFF0000)); \
++}
++
++#define EDMA_RXFILL_BUFFER_ADDR_SET(desc, addr) (((desc)->word0) = (u32)(addr))
++
++/* Opaque values are set in word2 and word3, they are not accessed by the EDMA HW,
++ * so endianness conversion is not needed.
++ */
++#define EDMA_RXFILL_ENDIAN_SET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ cpu_to_le32s(&((desc)->word0)); \
++ cpu_to_le32s(&((desc)->word1)); \
++})
++
++/* RX DESC size shift to obtain index from descriptor pointer. */
++#define EDMA_RXDESC_SIZE_SHIFT 5
++
++/**
++ * struct edma_rxdesc_stats - RX descriptor ring stats.
++ * @src_port_inval: Invalid source port number
++ * @src_port_inval_type: Source type is not PORT ID
++ * @src_port_inval_netdev: Invalid net device for the source port
++ * @syncp: Synchronization pointer
++ */
++struct edma_rxdesc_stats {
++ u64 src_port_inval;
++ u64 src_port_inval_type;
++ u64 src_port_inval_netdev;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_rxfill_stats - Rx fill descriptor ring stats.
++ * @alloc_failed: Buffer allocation failure count
++ * @page_alloc_failed: Page allocation failure count for page mode
++ * @syncp: Synchronization pointer
++ */
++struct edma_rxfill_stats {
++ u64 alloc_failed;
++ u64 page_alloc_failed;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_rxdesc_pri - Rx descriptor.
++ * @word0: Buffer address
++ * @word1: More bit, priority bit, service code
++ * @word2: Opaque low bits
++ * @word3: Opaque high bits
++ * @word4: Destination and source information
++ * @word5: WiFi QoS, data length
++ * @word6: Hash value, check sum status
++ * @word7: DSCP, packet offsets
++ */
++struct edma_rxdesc_pri {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++ /**
++ * struct edma_rxdesc_sec - Rx secondary descriptor.
++ * @word0: Timestamp
++ * @word1: Secondary checksum status
++ * @word2: QoS tag
++ * @word3: Flow index details
++ * @word4: Secondary packet offsets
++ * @word5: Multicast bit, checksum
++ * @word6: SVLAN, CVLAN
++ * @word7: Secondary SVLAN, CVLAN
++ */
++struct edma_rxdesc_sec {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++/**
++ * struct edma_rxfill_desc - RxFill descriptor.
++ * @word0: Buffer address
++ * @word1: Buffer size
++ * @word2: Opaque low bits
++ * @word3: Opaque high bits
++ */
++struct edma_rxfill_desc {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++};
++
++/**
++ * struct edma_rxfill_ring - RxFill ring
++ * @ring_id: RxFill ring number
++ * @count: Number of descriptors in the ring
++ * @prod_idx: Ring producer index
++ * @alloc_size: Buffer size to allocate
++ * @desc: Descriptor ring virtual address
++ * @dma: Descriptor ring physical address
++ * @buf_len: Buffer length for rxfill descriptor
++ * @page_mode: Page mode for Rx processing
++ * @rx_fill_stats: Rx fill ring statistics
++ */
++struct edma_rxfill_ring {
++ u32 ring_id;
++ u32 count;
++ u32 prod_idx;
++ u32 alloc_size;
++ struct edma_rxfill_desc *desc;
++ dma_addr_t dma;
++ u32 buf_len;
++ bool page_mode;
++ struct edma_rxfill_stats rxfill_stats;
++};
++
++/**
++ * struct edma_rxdesc_ring - RxDesc ring
++ * @napi: Pointer to napi
++ * @ring_id: Rxdesc ring number
++ * @count: Number of descriptors in the ring
++ * @work_leftover: Leftover descriptors to be processed
++ * @cons_idx: Ring consumer index
++ * @pdesc: Primary descriptor ring virtual address
++ * @pdesc_head: Primary descriptor head in case of scatter-gather frame
++ * @sdesc: Secondary descriptor ring virtual address
++ * @rxdesc_stats: Rx descriptor ring statistics
++ * @rxfill: RxFill ring used
++ * @napi_added: Flag to indicate NAPI add status
++ * @pdma: Primary descriptor ring physical address
++ * @sdma: Secondary descriptor ring physical address
++ * @head: Head of the skb list in case of scatter-gather frame
++ * @last: Last skb of the skb list in case of scatter-gather frame
++ */
++struct edma_rxdesc_ring {
++ struct napi_struct napi;
++ u32 ring_id;
++ u32 count;
++ u32 work_leftover;
++ u32 cons_idx;
++ struct edma_rxdesc_pri *pdesc;
++ struct edma_rxdesc_pri *pdesc_head;
++ struct edma_rxdesc_sec *sdesc;
++ struct edma_rxdesc_stats rxdesc_stats;
++ struct edma_rxfill_ring *rxfill;
++ bool napi_added;
++ dma_addr_t pdma;
++ dma_addr_t sdma;
++ struct sk_buff *head;
++ struct sk_buff *last;
++};
++
++irqreturn_t edma_rx_handle_irq(int irq, void *ctx);
++int edma_rx_alloc_buffer(struct edma_rxfill_ring *rxfill_ring, int alloc_count);
++int edma_rx_napi_poll(struct napi_struct *napi, int budget);
++#endif
--- /dev/null
+From 339d3a5365f150a78ed405684e379fee3acdbe90 Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:26:29 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add Tx Ethernet DMA support
+
+Add Tx queues, rings, descriptors configurations and
+DMA support for the EDMA.
+
+Change-Id: Idfb0e1fe5ac494d614097d6c97dd15d63bbce8e6
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 97 ++-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 7 +
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.c | 648 ++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.h | 28 +
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 136 +++
+ drivers/net/ethernet/qualcomm/ppe/edma_port.h | 35 +
+ drivers/net/ethernet/qualcomm/ppe/edma_tx.c | 808 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_tx.h | 302 +++++++
+ 9 files changed, 2055 insertions(+), 8 deletions(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_tx.c
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_tx.h
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_cfg_rx.o edma_port.o edma_rx.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_port.o edma_rx.o edma_tx.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -18,6 +18,7 @@
+ #include <linux/reset.h>
+
+ #include "edma.h"
++#include "edma_cfg_tx.h"
+ #include "edma_cfg_rx.h"
+ #include "ppe_regs.h"
+
+@@ -25,6 +26,7 @@
+
+ /* Global EDMA context. */
+ struct edma_context *edma_ctx;
++static char **edma_txcmpl_irq_name;
+ static char **edma_rxdesc_irq_name;
+
+ /* Module params. */
+@@ -192,22 +194,59 @@ static int edma_configure_ucast_prio_map
+ static int edma_irq_register(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
+ struct edma_ring_info *rx = hw_info->rx;
+ int ret;
+ u32 i;
+
++ /* Request IRQ for TXCMPL rings. */
++ edma_txcmpl_irq_name = kzalloc((sizeof(char *) * txcmpl->num_rings), GFP_KERNEL);
++ if (!edma_txcmpl_irq_name)
++ return -ENOMEM;
++
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ edma_txcmpl_irq_name[i] = kzalloc((sizeof(char *) * EDMA_IRQ_NAME_SIZE),
++ GFP_KERNEL);
++ if (!edma_txcmpl_irq_name[i]) {
++ ret = -ENOMEM;
++ goto txcmpl_ring_irq_name_alloc_fail;
++ }
++
++ snprintf(edma_txcmpl_irq_name[i], EDMA_IRQ_NAME_SIZE, "edma_txcmpl_%d",
++ txcmpl->ring_start + i);
++
++ irq_set_status_flags(edma_ctx->intr_info.intr_txcmpl[i], IRQ_DISABLE_UNLAZY);
++
++ ret = request_irq(edma_ctx->intr_info.intr_txcmpl[i],
++ edma_tx_handle_irq, IRQF_SHARED,
++ edma_txcmpl_irq_name[i],
++ (void *)&edma_ctx->txcmpl_rings[i]);
++ if (ret) {
++ pr_err("TXCMPL ring IRQ:%d request %d failed\n",
++ edma_ctx->intr_info.intr_txcmpl[i], i);
++ goto txcmpl_ring_intr_req_fail;
++ }
++
++ pr_debug("TXCMPL ring: %d IRQ:%d request success: %s\n",
++ txcmpl->ring_start + i,
++ edma_ctx->intr_info.intr_txcmpl[i],
++ edma_txcmpl_irq_name[i]);
++ }
++
+ /* Request IRQ for RXDESC rings. */
+ edma_rxdesc_irq_name = kzalloc((sizeof(char *) * rx->num_rings),
+ GFP_KERNEL);
+- if (!edma_rxdesc_irq_name)
+- return -ENOMEM;
++ if (!edma_rxdesc_irq_name) {
++ ret = -ENOMEM;
++ goto rxdesc_irq_name_alloc_fail;
++ }
+
+ for (i = 0; i < rx->num_rings; i++) {
+ edma_rxdesc_irq_name[i] = kzalloc((sizeof(char *) * EDMA_IRQ_NAME_SIZE),
+ GFP_KERNEL);
+ if (!edma_rxdesc_irq_name[i]) {
+ ret = -ENOMEM;
+- goto rxdesc_irq_name_alloc_fail;
++ goto rxdesc_ring_irq_name_alloc_fail;
+ }
+
+ snprintf(edma_rxdesc_irq_name[i], 20, "edma_rxdesc_%d",
+@@ -236,8 +275,19 @@ static int edma_irq_register(void)
+ rx_desc_ring_intr_req_fail:
+ for (i = 0; i < rx->num_rings; i++)
+ kfree(edma_rxdesc_irq_name[i]);
+-rxdesc_irq_name_alloc_fail:
++rxdesc_ring_irq_name_alloc_fail:
+ kfree(edma_rxdesc_irq_name);
++rxdesc_irq_name_alloc_fail:
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_txcmpl[i]);
++ free_irq(edma_ctx->intr_info.intr_txcmpl[i],
++ (void *)&edma_ctx->txcmpl_rings[i]);
++ }
++txcmpl_ring_intr_req_fail:
++ for (i = 0; i < txcmpl->num_rings; i++)
++ kfree(edma_txcmpl_irq_name[i]);
++txcmpl_ring_irq_name_alloc_fail:
++ kfree(edma_txcmpl_irq_name);
+
+ return ret;
+ }
+@@ -326,12 +376,22 @@ static int edma_irq_init(void)
+
+ static int edma_alloc_rings(void)
+ {
++ if (edma_cfg_tx_rings_alloc()) {
++ pr_err("Error in allocating Tx rings\n");
++ return -ENOMEM;
++ }
++
+ if (edma_cfg_rx_rings_alloc()) {
+ pr_err("Error in allocating Rx rings\n");
+- return -ENOMEM;
++ goto rx_rings_alloc_fail;
+ }
+
+ return 0;
++
++rx_rings_alloc_fail:
++ edma_cfg_tx_rings_cleanup();
++
++ return -ENOMEM;
+ }
+
+ static int edma_hw_reset(void)
+@@ -389,7 +449,7 @@ static int edma_hw_configure(void)
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
+ struct regmap *regmap = ppe_dev->regmap;
+- u32 data, reg;
++ u32 data, reg, i;
+ int ret;
+
+ reg = EDMA_BASE_OFFSET + EDMA_REG_MAS_CTRL_ADDR;
+@@ -439,11 +499,17 @@ static int edma_hw_configure(void)
+ }
+
+ /* Disable interrupts. */
++ for (i = 1; i <= hw_info->max_ports; i++)
++ edma_cfg_tx_disable_interrupts(i);
++
+ edma_cfg_rx_disable_interrupts();
+
+ edma_cfg_rx_rings_disable();
+
+ edma_cfg_rx_ring_mappings();
++ edma_cfg_tx_ring_mappings();
++
++ edma_cfg_tx_rings();
+
+ ret = edma_cfg_rx_rings();
+ if (ret) {
+@@ -520,6 +586,7 @@ configure_ucast_prio_map_tbl_failed:
+ edma_cfg_rx_napi_delete();
+ edma_cfg_rx_rings_disable();
+ edma_cfg_rx_rings_failed:
++ edma_cfg_tx_rings_cleanup();
+ edma_cfg_rx_rings_cleanup();
+ edma_alloc_rings_failed:
+ free_netdev(edma_ctx->dummy_dev);
+@@ -538,13 +605,27 @@ dummy_dev_alloc_failed:
+ void edma_destroy(struct ppe_device *ppe_dev)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
+ struct edma_ring_info *rx = hw_info->rx;
+ u32 i;
+
+ /* Disable interrupts. */
++ for (i = 1; i <= hw_info->max_ports; i++)
++ edma_cfg_tx_disable_interrupts(i);
++
+ edma_cfg_rx_disable_interrupts();
+
+- /* Free IRQ for RXDESC rings. */
++ /* Free IRQ for TXCMPL rings. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_txcmpl[i]);
++
++ free_irq(edma_ctx->intr_info.intr_txcmpl[i],
++ (void *)&edma_ctx->txcmpl_rings[i]);
++ kfree(edma_txcmpl_irq_name[i]);
++ }
++ kfree(edma_txcmpl_irq_name);
++
++ /* Free IRQ for RXDESC rings */
+ for (i = 0; i < rx->num_rings; i++) {
+ synchronize_irq(edma_ctx->intr_info.intr_rx[i]);
+ free_irq(edma_ctx->intr_info.intr_rx[i],
+@@ -560,6 +641,7 @@ void edma_destroy(struct ppe_device *ppe
+ edma_cfg_rx_napi_delete();
+ edma_cfg_rx_rings_disable();
+ edma_cfg_rx_rings_cleanup();
++ edma_cfg_tx_rings_cleanup();
+
+ free_netdev(edma_ctx->dummy_dev);
+ kfree(edma_ctx->netdev_arr);
+@@ -585,6 +667,7 @@ int edma_setup(struct ppe_device *ppe_de
+ edma_ctx->hw_info = &ipq9574_hw_info;
+ edma_ctx->ppe_dev = ppe_dev;
+ edma_ctx->rx_buf_size = rx_buff_size;
++ edma_ctx->tx_requeue_stop = false;
+
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -7,6 +7,7 @@
+
+ #include "ppe_config.h"
+ #include "edma_rx.h"
++#include "edma_tx.h"
+
+ /* One clock cycle = 1/(EDMA clock frequency in Mhz) micro seconds.
+ *
+@@ -104,8 +105,11 @@ struct edma_intr_info {
+ * @intr_info: EDMA Interrupt info
+ * @rxfill_rings: Rx fill Rings, SW is producer
+ * @rx_rings: Rx Desc Rings, SW is consumer
++ * @tx_rings: Tx Descriptor Ring, SW is producer
++ * @txcmpl_rings: Tx complete Ring, SW is consumer
+ * @rx_page_mode: Page mode enabled or disabled
+ * @rx_buf_size: Rx buffer size for Jumbo MRU
++ * @tx_requeue_stop: Tx requeue stop enabled or disabled
+ */
+ struct edma_context {
+ struct net_device **netdev_arr;
+@@ -115,8 +119,11 @@ struct edma_context {
+ struct edma_intr_info intr_info;
+ struct edma_rxfill_ring *rxfill_rings;
+ struct edma_rxdesc_ring *rx_rings;
++ struct edma_txdesc_ring *tx_rings;
++ struct edma_txcmpl_ring *txcmpl_rings;
+ u32 rx_page_mode;
+ u32 rx_buf_size;
++ bool tx_requeue_stop;
+ };
+
+ /* Global EDMA context */
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
+@@ -0,0 +1,648 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Configure rings, Buffers and NAPI for transmit path along with
++ * providing APIs to enable, disable, clean and map the Tx rings.
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/printk.h>
++#include <linux/regmap.h>
++#include <linux/skbuff.h>
++
++#include "edma.h"
++#include "edma_cfg_tx.h"
++#include "edma_port.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++static void edma_cfg_txcmpl_ring_cleanup(struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Free any buffers assigned to any descriptors. */
++ edma_tx_complete(EDMA_TX_RING_SIZE - 1, txcmpl_ring);
++
++ /* Free TxCmpl ring descriptors. */
++ dma_free_coherent(dev, sizeof(struct edma_txcmpl_desc)
++ * txcmpl_ring->count, txcmpl_ring->desc,
++ txcmpl_ring->dma);
++ txcmpl_ring->desc = NULL;
++ txcmpl_ring->dma = (dma_addr_t)0;
++}
++
++static int edma_cfg_txcmpl_ring_setup(struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Allocate RxFill ring descriptors. */
++ txcmpl_ring->desc = dma_alloc_coherent(dev, sizeof(struct edma_txcmpl_desc)
++ * txcmpl_ring->count,
++ &txcmpl_ring->dma,
++ GFP_KERNEL | __GFP_ZERO);
++
++ if (unlikely(!txcmpl_ring->desc))
++ return -ENOMEM;
++
++ return 0;
++}
++
++static void edma_cfg_tx_desc_ring_cleanup(struct edma_txdesc_ring *txdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_pri *txdesc = NULL;
++ struct device *dev = ppe_dev->dev;
++ u32 prod_idx, cons_idx, data, reg;
++ struct sk_buff *skb = NULL;
++
++ /* Free any buffers assigned to any descriptors. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_PROD_IDX(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ prod_idx = data & EDMA_TXDESC_PROD_IDX_MASK;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CONS_IDX(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ cons_idx = data & EDMA_TXDESC_CONS_IDX_MASK;
++
++ /* Walk active list, obtain skb from descriptor and free it. */
++ while (cons_idx != prod_idx) {
++ txdesc = EDMA_TXDESC_PRI_DESC(txdesc_ring, cons_idx);
++ skb = (struct sk_buff *)EDMA_TXDESC_OPAQUE_GET(txdesc);
++ dev_kfree_skb_any(skb);
++
++ cons_idx = ((cons_idx + 1) & EDMA_TX_RING_SIZE_MASK);
++ }
++
++ /* Free Tx ring descriptors. */
++ dma_free_coherent(dev, (sizeof(struct edma_txdesc_pri)
++ * txdesc_ring->count),
++ txdesc_ring->pdesc,
++ txdesc_ring->pdma);
++ txdesc_ring->pdesc = NULL;
++ txdesc_ring->pdma = (dma_addr_t)0;
++
++ /* Free any buffers assigned to any secondary descriptors. */
++ dma_free_coherent(dev, (sizeof(struct edma_txdesc_sec)
++ * txdesc_ring->count),
++ txdesc_ring->sdesc,
++ txdesc_ring->sdma);
++ txdesc_ring->sdesc = NULL;
++ txdesc_ring->sdma = (dma_addr_t)0;
++}
++
++static int edma_cfg_tx_desc_ring_setup(struct edma_txdesc_ring *txdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++
++ /* Allocate RxFill ring descriptors. */
++ txdesc_ring->pdesc = dma_alloc_coherent(dev, sizeof(struct edma_txdesc_pri)
++ * txdesc_ring->count,
++ &txdesc_ring->pdma,
++ GFP_KERNEL | __GFP_ZERO);
++
++ if (unlikely(!txdesc_ring->pdesc))
++ return -ENOMEM;
++
++ txdesc_ring->sdesc = dma_alloc_coherent(dev, sizeof(struct edma_txdesc_sec)
++ * txdesc_ring->count,
++ &txdesc_ring->sdma,
++ GFP_KERNEL | __GFP_ZERO);
++
++ if (unlikely(!txdesc_ring->sdesc)) {
++ dma_free_coherent(dev, (sizeof(struct edma_txdesc_pri)
++ * txdesc_ring->count),
++ txdesc_ring->pdesc,
++ txdesc_ring->pdma);
++ txdesc_ring->pdesc = NULL;
++ txdesc_ring->pdma = (dma_addr_t)0;
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void edma_cfg_tx_desc_ring_configure(struct edma_txdesc_ring *txdesc_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_BA(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)(txdesc_ring->pdma & EDMA_RING_DMA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_BA2(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)(txdesc_ring->sdma & EDMA_RING_DMA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_RING_SIZE(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)(txdesc_ring->count & EDMA_TXDESC_RING_SIZE_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_PROD_IDX(txdesc_ring->id);
++ regmap_write(regmap, reg, (u32)EDMA_TX_INITIAL_PROD_IDX);
++
++ data = FIELD_PREP(EDMA_TXDESC_CTRL_FC_GRP_ID_MASK, txdesc_ring->fc_grp_id);
++
++ /* Configure group ID for flow control for this Tx ring. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CTRL(txdesc_ring->id);
++ regmap_write(regmap, reg, data);
++}
++
++static void edma_cfg_txcmpl_ring_configure(struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 data, reg;
++
++ /* Configure TxCmpl ring base address. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_BA(txcmpl_ring->id);
++ regmap_write(regmap, reg, (u32)(txcmpl_ring->dma & EDMA_RING_DMA_MASK));
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_RING_SIZE(txcmpl_ring->id);
++ regmap_write(regmap, reg, (u32)(txcmpl_ring->count & EDMA_TXDESC_RING_SIZE_MASK));
++
++ /* Set TxCmpl ret mode to opaque. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_CTRL(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_TXCMPL_RETMODE_OPAQUE);
++
++ /* Configure the Mitigation timer. */
++ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_TX_MITIGATION_TIMER_DEF,
++ ppe_dev->clk_rate / MHZ);
++ data = ((data & EDMA_TX_MOD_TIMER_INIT_MASK)
++ << EDMA_TX_MOD_TIMER_INIT_SHIFT);
++ pr_debug("EDMA Tx mitigation timer value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_MOD_TIMER(txcmpl_ring->id);
++ regmap_write(regmap, reg, data);
++
++ /* Configure the Mitigation packet count. */
++ data = (EDMA_TX_MITIGATION_PKT_CNT_DEF & EDMA_TXCMPL_LOW_THRE_MASK)
++ << EDMA_TXCMPL_LOW_THRE_SHIFT;
++ pr_debug("EDMA Tx mitigation packet count value: %d\n", data);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_UGT_THRE(txcmpl_ring->id);
++ regmap_write(regmap, reg, data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_CTRL(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_TX_NE_INT_EN);
++}
++
++/**
++ * edma_cfg_tx_fill_per_port_tx_map - Fill Tx ring mapping.
++ * @netdev: Netdevice.
++ * @port_id: Port ID.
++ *
++ * Fill per-port Tx ring mapping in net device private area.
++ */
++void edma_cfg_tx_fill_per_port_tx_map(struct net_device *netdev, u32 port_id)
++{
++ u32 i;
++
++ /* Ring to core mapping is done in order starting from 0 for port 1. */
++ for_each_possible_cpu(i) {
++ struct edma_port_priv *port_dev = (struct edma_port_priv *)netdev_priv(netdev);
++ struct edma_txdesc_ring *txdesc_ring;
++ u32 txdesc_ring_id;
++
++ txdesc_ring_id = ((port_id - 1) * num_possible_cpus()) + i;
++ txdesc_ring = &edma_ctx->tx_rings[txdesc_ring_id];
++ port_dev->txr_map[i] = txdesc_ring;
++ }
++}
++
++/**
++ * edma_cfg_tx_rings_enable - Enable Tx rings.
++ *
++ * Enable Tx rings.
++ */
++void edma_cfg_tx_rings_enable(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_ring *txdesc_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txdesc_ring = &edma_ctx->tx_rings[ring_idx];
++ u32 data;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CTRL(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ data |= FIELD_PREP(EDMA_TXDESC_CTRL_TXEN_MASK, EDMA_TXDESC_TX_ENABLE);
++
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_tx_rings_disable - Disable Tx rings.
++ *
++ * Disable Tx rings.
++ */
++void edma_cfg_tx_rings_disable(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_ring *txdesc_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txdesc_ring = &edma_ctx->tx_rings[ring_idx];
++ u32 data;
++
++ txdesc_ring = &edma_ctx->tx_rings[i];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CTRL(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ data &= ~EDMA_TXDESC_TX_ENABLE;
++ regmap_write(regmap, reg, data);
++ }
++}
++
++/**
++ * edma_cfg_tx_ring_mappings - Map Tx to Tx complete rings.
++ *
++ * Map Tx to Tx complete rings.
++ */
++void edma_cfg_tx_ring_mappings(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 desc_index, i, data, reg;
++
++ /* Clear the TXDESC2CMPL_MAP_xx reg before setting up
++ * the mapping. This register holds TXDESC to TXFILL ring
++ * mapping.
++ */
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_0_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_1_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_2_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_3_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_4_ADDR, 0);
++ regmap_write(regmap, EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_5_ADDR, 0);
++ desc_index = txcmpl->ring_start;
++
++ /* 6 registers to hold the completion mapping for total 32
++ * TX desc rings (0-5, 6-11, 12-17, 18-23, 24-29 and rest).
++ * In each entry 5 bits hold the mapping for a particular TX desc ring.
++ */
++ for (i = tx->ring_start; i < tx->ring_start + tx->num_rings; i++) {
++ u32 reg, data;
++
++ if (i >= 0 && i <= 5)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_0_ADDR;
++ else if (i >= 6 && i <= 11)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_1_ADDR;
++ else if (i >= 12 && i <= 17)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_2_ADDR;
++ else if (i >= 18 && i <= 23)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_3_ADDR;
++ else if (i >= 24 && i <= 29)
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_4_ADDR;
++ else
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_5_ADDR;
++
++ pr_debug("Configure Tx desc:%u to use TxCmpl:%u\n", i, desc_index);
++
++ /* Set the Tx complete descriptor ring number in the mapping register.
++ * E.g. If (txcmpl ring)desc_index = 31, (txdesc ring)i = 28.
++ * reg = EDMA_REG_TXDESC2CMPL_MAP_4_ADDR
++ * data |= (desc_index & 0x1F) << ((i % 6) * 5);
++ * data |= (0x1F << 20); -
++ * This sets 11111 at 20th bit of register EDMA_REG_TXDESC2CMPL_MAP_4_ADDR.
++ */
++ regmap_read(regmap, reg, &data);
++ data |= (desc_index & EDMA_TXDESC2CMPL_MAP_TXDESC_MASK) << ((i % 6) * 5);
++ regmap_write(regmap, reg, data);
++
++ desc_index++;
++ if (desc_index == txcmpl->ring_start + txcmpl->num_rings)
++ desc_index = txcmpl->ring_start;
++ }
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_0_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_0_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_1_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_1_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_2_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_2_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_3_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_3_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_4_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_4_ADDR: 0x%x\n", data);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC2CMPL_MAP_5_ADDR;
++ regmap_read(regmap, reg, &data);
++ pr_debug("EDMA_REG_TXDESC2CMPL_MAP_5_ADDR: 0x%x\n", data);
++}
++
++static int edma_cfg_tx_rings_setup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 i, j = 0;
++
++ /* Set Txdesc flow control group id, same as port number. */
++ for (i = 0; i < hw_info->max_ports; i++) {
++ for_each_possible_cpu(j) {
++ struct edma_txdesc_ring *txdesc_ring = NULL;
++ u32 txdesc_idx = (i * num_possible_cpus()) + j;
++
++ txdesc_ring = &edma_ctx->tx_rings[txdesc_idx];
++ txdesc_ring->fc_grp_id = i + 1;
++ }
++ }
++
++ /* Allocate TxDesc ring descriptors. */
++ for (i = 0; i < tx->num_rings; i++) {
++ struct edma_txdesc_ring *txdesc_ring = NULL;
++ int ret;
++
++ txdesc_ring = &edma_ctx->tx_rings[i];
++ txdesc_ring->count = EDMA_TX_RING_SIZE;
++ txdesc_ring->id = tx->ring_start + i;
++
++ ret = edma_cfg_tx_desc_ring_setup(txdesc_ring);
++ if (ret) {
++ pr_err("Error in setting up %d txdesc ring. ret: %d",
++ txdesc_ring->id, ret);
++ while (i-- >= 0)
++ edma_cfg_tx_desc_ring_cleanup(&edma_ctx->tx_rings[i]);
++
++ return -ENOMEM;
++ }
++ }
++
++ /* Allocate TxCmpl ring descriptors. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ struct edma_txcmpl_ring *txcmpl_ring = NULL;
++ int ret;
++
++ txcmpl_ring = &edma_ctx->txcmpl_rings[i];
++ txcmpl_ring->count = EDMA_TX_RING_SIZE;
++ txcmpl_ring->id = txcmpl->ring_start + i;
++
++ ret = edma_cfg_txcmpl_ring_setup(txcmpl_ring);
++ if (ret != 0) {
++ pr_err("Error in setting up %d TxCmpl ring. ret: %d",
++ txcmpl_ring->id, ret);
++ while (i-- >= 0)
++ edma_cfg_txcmpl_ring_cleanup(&edma_ctx->txcmpl_rings[i]);
++
++ goto txcmpl_mem_alloc_fail;
++ }
++ }
++
++ pr_debug("Tx descriptor count for Tx desc and Tx complete rings: %d\n",
++ EDMA_TX_RING_SIZE);
++
++ return 0;
++
++txcmpl_mem_alloc_fail:
++ for (i = 0; i < tx->num_rings; i++)
++ edma_cfg_tx_desc_ring_cleanup(&edma_ctx->tx_rings[i]);
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_tx_rings_alloc - Allocate EDMA Tx rings.
++ *
++ * Allocate EDMA Tx rings.
++ */
++int edma_cfg_tx_rings_alloc(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++
++ edma_ctx->tx_rings = kzalloc((sizeof(*edma_ctx->tx_rings) * tx->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->tx_rings)
++ return -ENOMEM;
++
++ edma_ctx->txcmpl_rings = kzalloc((sizeof(*edma_ctx->txcmpl_rings) * txcmpl->num_rings),
++ GFP_KERNEL);
++ if (!edma_ctx->txcmpl_rings)
++ goto txcmpl_ring_alloc_fail;
++
++ pr_debug("Num rings - TxDesc:%u (%u-%u) TxCmpl:%u (%u-%u)\n",
++ tx->num_rings, tx->ring_start,
++ (tx->ring_start + tx->num_rings - 1),
++ txcmpl->num_rings, txcmpl->ring_start,
++ (txcmpl->ring_start + txcmpl->num_rings - 1));
++
++ if (edma_cfg_tx_rings_setup()) {
++ pr_err("Error in setting up tx rings\n");
++ goto tx_rings_setup_fail;
++ }
++
++ return 0;
++
++tx_rings_setup_fail:
++ kfree(edma_ctx->txcmpl_rings);
++ edma_ctx->txcmpl_rings = NULL;
++
++txcmpl_ring_alloc_fail:
++ kfree(edma_ctx->tx_rings);
++ edma_ctx->tx_rings = NULL;
++
++ return -ENOMEM;
++}
++
++/**
++ * edma_cfg_tx_rings_cleanup - Cleanup EDMA Tx rings.
++ *
++ * Cleanup EDMA Tx rings.
++ */
++void edma_cfg_tx_rings_cleanup(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 i;
++
++ /* Free any buffers assigned to any descriptors. */
++ for (i = 0; i < tx->num_rings; i++)
++ edma_cfg_tx_desc_ring_cleanup(&edma_ctx->tx_rings[i]);
++
++ /* Free Tx completion descriptors. */
++ for (i = 0; i < txcmpl->num_rings; i++)
++ edma_cfg_txcmpl_ring_cleanup(&edma_ctx->txcmpl_rings[i]);
++
++ kfree(edma_ctx->tx_rings);
++ kfree(edma_ctx->txcmpl_rings);
++ edma_ctx->tx_rings = NULL;
++ edma_ctx->txcmpl_rings = NULL;
++}
++
++/**
++ * edma_cfg_tx_rings - Configure EDMA Tx rings.
++ *
++ * Configure EDMA Tx rings.
++ */
++void edma_cfg_tx_rings(void)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ u32 i;
++
++ /* Configure Tx desc ring. */
++ for (i = 0; i < tx->num_rings; i++)
++ edma_cfg_tx_desc_ring_configure(&edma_ctx->tx_rings[i]);
++
++ /* Configure TxCmpl ring. */
++ for (i = 0; i < txcmpl->num_rings; i++)
++ edma_cfg_txcmpl_ring_configure(&edma_ctx->txcmpl_rings[i]);
++}
++
++/**
++ * edma_cfg_tx_disable_interrupts - EDMA disable TX interrupts.
++ *
++ * Disable TX interrupt masks.
++ */
++void edma_cfg_tx_disable_interrupts(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_CLEAR);
++ }
++}
++
++/**
++ * edma_cfg_tx_enable_interrupts - EDMA enable TX interrupts.
++ *
++ * Enable TX interrupt masks.
++ */
++void edma_cfg_tx_enable_interrupts(u32 port_id)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx, reg;
++
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_txcmpl);
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_enable - EDMA Tx NAPI.
++ * @port_id: Port ID.
++ *
++ * Enable Tx NAPI.
++ */
++void edma_cfg_tx_napi_enable(u32 port_id)
++{
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Enabling Tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ if (!txcmpl_ring->napi_added)
++ continue;
++
++ napi_enable(&txcmpl_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_disable - Disable Tx NAPI.
++ * @port_id: Port ID.
++ *
++ * Disable Tx NAPI.
++ */
++void edma_cfg_tx_napi_disable(u32 port_id)
++{
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Disabling Tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ if (!txcmpl_ring->napi_added)
++ continue;
++
++ napi_disable(&txcmpl_ring->napi);
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_delete - Delete Tx NAPI.
++ * @port_id: Port ID.
++ *
++ * Delete Tx NAPI.
++ */
++void edma_cfg_tx_napi_delete(u32 port_id)
++{
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Disabling Tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ if (!txcmpl_ring->napi_added)
++ continue;
++
++ netif_napi_del(&txcmpl_ring->napi);
++ txcmpl_ring->napi_added = false;
++ }
++}
++
++/**
++ * edma_cfg_tx_napi_add - TX NAPI add.
++ * @netdev: Netdevice.
++ * @port_id: Port ID.
++ *
++ * TX NAPI add.
++ */
++void edma_cfg_tx_napi_add(struct net_device *netdev, u32 port_id)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_txcmpl_ring *txcmpl_ring;
++ u32 i, ring_idx;
++
++ /* Adding tx napi for a interface with each queue. */
++ for_each_possible_cpu(i) {
++ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
++ txcmpl_ring = &edma_ctx->txcmpl_rings[ring_idx];
++ netif_napi_add_weight(netdev, &txcmpl_ring->napi,
++ edma_tx_napi_poll, hw_info->napi_budget_tx);
++ txcmpl_ring->napi_added = true;
++ netdev_dbg(netdev, "Napi added for txcmpl ring: %u\n", txcmpl_ring->id);
++ }
++
++ netdev_dbg(netdev, "Tx NAPI budget: %d\n", hw_info->napi_budget_tx);
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_CFG_TX__
++#define __EDMA_CFG_TX__
++
++/* Tx mitigation timer's default value. */
++#define EDMA_TX_MITIGATION_TIMER_DEF 250
++
++/* Tx mitigation packet count default value. */
++#define EDMA_TX_MITIGATION_PKT_CNT_DEF 16
++
++void edma_cfg_tx_rings(void);
++int edma_cfg_tx_rings_alloc(void);
++void edma_cfg_tx_rings_cleanup(void);
++void edma_cfg_tx_disable_interrupts(u32 port_id);
++void edma_cfg_tx_enable_interrupts(u32 port_id);
++void edma_cfg_tx_napi_enable(u32 port_id);
++void edma_cfg_tx_napi_disable(u32 port_id);
++void edma_cfg_tx_napi_delete(u32 port_id);
++void edma_cfg_tx_napi_add(struct net_device *netdevice, u32 macid);
++void edma_cfg_tx_ring_mappings(void);
++void edma_cfg_txcmpl_mapping_fill(void);
++void edma_cfg_tx_rings_enable(u32 port_id);
++void edma_cfg_tx_rings_disable(u32 port_id);
++void edma_cfg_tx_fill_per_port_tx_map(struct net_device *netdev, u32 macid);
++#endif
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -13,6 +13,7 @@
+
+ #include "edma.h"
+ #include "edma_cfg_rx.h"
++#include "edma_cfg_tx.h"
+ #include "edma_port.h"
+ #include "ppe_regs.h"
+
+@@ -35,6 +36,15 @@ static int edma_port_stats_alloc(struct
+ return -ENOMEM;
+ }
+
++ port_priv->pcpu_stats.tx_stats =
++ netdev_alloc_pcpu_stats(struct edma_port_tx_stats);
++ if (!port_priv->pcpu_stats.tx_stats) {
++ netdev_err(netdev, "Per-cpu EDMA Tx stats alloc failed for %s\n",
++ netdev->name);
++ free_percpu(port_priv->pcpu_stats.rx_stats);
++ return -ENOMEM;
++ }
++
+ return 0;
+ }
+
+@@ -43,6 +53,28 @@ static void edma_port_stats_free(struct
+ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
+
+ free_percpu(port_priv->pcpu_stats.rx_stats);
++ free_percpu(port_priv->pcpu_stats.tx_stats);
++}
++
++static void edma_port_configure(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++ int port_id = port->port_id;
++
++ edma_cfg_tx_fill_per_port_tx_map(netdev, port_id);
++ edma_cfg_tx_rings_enable(port_id);
++ edma_cfg_tx_napi_add(netdev, port_id);
++}
++
++static void edma_port_deconfigure(struct net_device *netdev)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++ int port_id = port->port_id;
++
++ edma_cfg_tx_napi_delete(port_id);
++ edma_cfg_tx_rings_disable(port_id);
+ }
+
+ static u16 __maybe_unused edma_port_select_queue(__maybe_unused struct net_device *netdev,
+@@ -60,6 +92,7 @@ static int edma_port_open(struct net_dev
+ {
+ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
+ struct ppe_port *ppe_port;
++ int port_id;
+
+ if (!port_priv)
+ return -EINVAL;
+@@ -74,10 +107,14 @@ static int edma_port_open(struct net_dev
+ netdev->wanted_features |= EDMA_NETDEV_FEATURES;
+
+ ppe_port = port_priv->ppe_port;
++ port_id = ppe_port->port_id;
+
+ if (ppe_port->phylink)
+ phylink_start(ppe_port->phylink);
+
++ edma_cfg_tx_napi_enable(port_id);
++ edma_cfg_tx_enable_interrupts(port_id);
++
+ netif_start_queue(netdev);
+
+ return 0;
+@@ -87,13 +124,21 @@ static int edma_port_close(struct net_de
+ {
+ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
+ struct ppe_port *ppe_port;
++ int port_id;
+
+ if (!port_priv)
+ return -EINVAL;
+
+ netif_stop_queue(netdev);
+
++ /* 20ms delay would provide a plenty of margin to take care of in-flight packets. */
++ msleep(20);
++
+ ppe_port = port_priv->ppe_port;
++ port_id = ppe_port->port_id;
++
++ edma_cfg_tx_disable_interrupts(port_id);
++ edma_cfg_tx_napi_disable(port_id);
+
+ /* Phylink close. */
+ if (ppe_port->phylink)
+@@ -137,6 +182,92 @@ static netdev_features_t edma_port_featu
+ return features;
+ }
+
++static netdev_tx_t edma_port_xmit(struct sk_buff *skb,
++ struct net_device *dev)
++{
++ struct edma_port_priv *port_priv = NULL;
++ struct edma_port_pcpu_stats *pcpu_stats;
++ struct edma_txdesc_ring *txdesc_ring;
++ struct edma_port_tx_stats *stats;
++ enum edma_tx_gso_status result;
++ struct sk_buff *segs = NULL;
++ u8 cpu_id;
++ u32 skbq;
++ int ret;
++
++ if (!skb || !dev)
++ return NETDEV_TX_OK;
++
++ port_priv = netdev_priv(dev);
++
++ /* Select a TX ring. */
++ skbq = (skb_get_queue_mapping(skb) & (num_possible_cpus() - 1));
++
++ txdesc_ring = (struct edma_txdesc_ring *)port_priv->txr_map[skbq];
++
++ pcpu_stats = &port_priv->pcpu_stats;
++ stats = this_cpu_ptr(pcpu_stats->tx_stats);
++
++ /* HW does not support TSO for packets with more than or equal to
++ * 32 segments. Perform SW GSO for such packets.
++ */
++ result = edma_tx_gso_segment(skb, dev, &segs);
++ if (likely(result == EDMA_TX_GSO_NOT_NEEDED)) {
++ /* Transmit the packet. */
++ ret = edma_tx_ring_xmit(dev, skb, txdesc_ring, stats);
++
++ if (unlikely(ret == EDMA_TX_FAIL_NO_DESC)) {
++ if (likely(!edma_ctx->tx_requeue_stop)) {
++ cpu_id = smp_processor_id();
++ netdev_dbg(dev, "Stopping tx queue due to lack oftx descriptors\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_queue_stopped[cpu_id];
++ u64_stats_update_end(&stats->syncp);
++ netif_tx_stop_queue(netdev_get_tx_queue(dev, skbq));
++ return NETDEV_TX_BUSY;
++ }
++ }
++
++ if (unlikely(ret != EDMA_TX_OK)) {
++ dev_kfree_skb_any(skb);
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_drops;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ return NETDEV_TX_OK;
++ } else if (unlikely(result == EDMA_TX_GSO_FAIL)) {
++ netdev_dbg(dev, "%p: SW GSO failed for segment size: %d\n",
++ skb, skb_shinfo(skb)->gso_segs);
++ dev_kfree_skb_any(skb);
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_gso_drop_pkts;
++ u64_stats_update_end(&stats->syncp);
++ return NETDEV_TX_OK;
++ }
++
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_gso_pkts;
++ u64_stats_update_end(&stats->syncp);
++
++ dev_kfree_skb_any(skb);
++ while (segs) {
++ skb = segs;
++ segs = segs->next;
++
++ /* Transmit the packet. */
++ ret = edma_tx_ring_xmit(dev, skb, txdesc_ring, stats);
++ if (unlikely(ret != EDMA_TX_OK)) {
++ dev_kfree_skb_any(skb);
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->tx_drops;
++ u64_stats_update_end(&stats->syncp);
++ }
++ }
++
++ return NETDEV_TX_OK;
++}
++
+ static void edma_port_get_stats64(struct net_device *netdev,
+ struct rtnl_link_stats64 *stats)
+ {
+@@ -179,6 +310,7 @@ static int edma_port_set_mac_address(str
+ static const struct net_device_ops edma_port_netdev_ops = {
+ .ndo_open = edma_port_open,
+ .ndo_stop = edma_port_close,
++ .ndo_start_xmit = edma_port_xmit,
+ .ndo_get_stats64 = edma_port_get_stats64,
+ .ndo_set_mac_address = edma_port_set_mac_address,
+ .ndo_validate_addr = eth_validate_addr,
+@@ -199,6 +331,7 @@ void edma_port_destroy(struct ppe_port *
+ int port_id = port->port_id;
+ struct net_device *netdev = edma_ctx->netdev_arr[port_id - 1];
+
++ edma_port_deconfigure(netdev);
+ edma_port_stats_free(netdev);
+ unregister_netdev(netdev);
+ free_netdev(netdev);
+@@ -276,6 +409,8 @@ int edma_port_setup(struct ppe_port *por
+ */
+ edma_ctx->netdev_arr[port_id - 1] = netdev;
+
++ edma_port_configure(netdev);
++
+ /* Setup phylink. */
+ ret = ppe_port_phylink_setup(port, netdev);
+ if (ret) {
+@@ -298,6 +433,7 @@ int edma_port_setup(struct ppe_port *por
+ register_netdev_fail:
+ ppe_port_phylink_destroy(port);
+ port_phylink_setup_fail:
++ edma_port_deconfigure(netdev);
+ edma_ctx->netdev_arr[port_id - 1] = NULL;
+ edma_port_stats_free(netdev);
+ stats_alloc_fail:
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.h
+@@ -7,6 +7,8 @@
+
+ #include "ppe_port.h"
+
++#define EDMA_PORT_MAX_CORE 4
++
+ #define EDMA_NETDEV_FEATURES (NETIF_F_FRAGLIST \
+ | NETIF_F_SG \
+ | NETIF_F_RXCSUM \
+@@ -35,11 +37,43 @@ struct edma_port_rx_stats {
+ };
+
+ /**
++ * struct edma_port_tx_stats - EDMA TX port per CPU stats for the port.
++ * @tx_pkts: Number of Tx packets
++ * @tx_bytes: Number of Tx bytes
++ * @tx_drops: Number of Tx drops
++ * @tx_nr_frag_pkts: Number of Tx nr_frag packets
++ * @tx_fraglist_pkts: Number of Tx fraglist packets
++ * @tx_fraglist_with_nr_frags_pkts: Number of Tx packets with fraglist and nr_frags
++ * @tx_tso_pkts: Number of Tx TSO packets
++ * @tx_tso_drop_pkts: Number of Tx TSO drop packets
++ * @tx_gso_pkts: Number of Tx GSO packets
++ * @tx_gso_drop_pkts: Number of Tx GSO drop packets
++ * @tx_queue_stopped: Number of Tx queue stopped packets
++ * @syncp: Synchronization pointer
++ */
++struct edma_port_tx_stats {
++ u64 tx_pkts;
++ u64 tx_bytes;
++ u64 tx_drops;
++ u64 tx_nr_frag_pkts;
++ u64 tx_fraglist_pkts;
++ u64 tx_fraglist_with_nr_frags_pkts;
++ u64 tx_tso_pkts;
++ u64 tx_tso_drop_pkts;
++ u64 tx_gso_pkts;
++ u64 tx_gso_drop_pkts;
++ u64 tx_queue_stopped[EDMA_PORT_MAX_CORE];
++ struct u64_stats_sync syncp;
++};
++
++/**
+ * struct edma_port_pcpu_stats - EDMA per cpu stats data structure for the port.
+ * @rx_stats: Per CPU Rx statistics
++ * @tx_stats: Per CPU Tx statistics
+ */
+ struct edma_port_pcpu_stats {
+ struct edma_port_rx_stats __percpu *rx_stats;
++ struct edma_port_tx_stats __percpu *tx_stats;
+ };
+
+ /**
+@@ -54,6 +88,7 @@ struct edma_port_priv {
+ struct ppe_port *ppe_port;
+ struct net_device *netdev;
+ struct edma_port_pcpu_stats pcpu_stats;
++ struct edma_txdesc_ring *txr_map[EDMA_PORT_MAX_CORE];
+ unsigned long flags;
+ };
+
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_tx.c
+@@ -0,0 +1,808 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* Provide APIs to alloc Tx Buffers, fill the Tx descriptors and transmit
++ * Scatter Gather and linear packets, Tx complete to free the skb after transmit.
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/platform_device.h>
++#include <linux/printk.h>
++#include <net/gso.h>
++#include <linux/regmap.h>
++
++#include "edma.h"
++#include "edma_cfg_tx.h"
++#include "edma_port.h"
++#include "ppe.h"
++#include "ppe_regs.h"
++
++static u32 edma_tx_num_descs_for_sg(struct sk_buff *skb)
++{
++ u32 nr_frags_first = 0, num_tx_desc_needed = 0;
++
++ /* Check if we have enough Tx descriptors for SG. */
++ if (unlikely(skb_shinfo(skb)->nr_frags)) {
++ nr_frags_first = skb_shinfo(skb)->nr_frags;
++ WARN_ON_ONCE(nr_frags_first > MAX_SKB_FRAGS);
++ num_tx_desc_needed += nr_frags_first;
++ }
++
++ /* Walk through fraglist skbs making a note of nr_frags
++ * One Tx desc for fraglist skb. Fraglist skb may have
++ * further nr_frags.
++ */
++ if (unlikely(skb_has_frag_list(skb))) {
++ struct sk_buff *iter_skb;
++
++ skb_walk_frags(skb, iter_skb) {
++ u32 nr_frags = skb_shinfo(iter_skb)->nr_frags;
++
++ WARN_ON_ONCE(nr_frags > MAX_SKB_FRAGS);
++ num_tx_desc_needed += (1 + nr_frags);
++ }
++ }
++
++ return (num_tx_desc_needed + 1);
++}
++
++/**
++ * edma_tx_gso_segment - Tx GSO.
++ * @skb: Socket Buffer.
++ * @netdev: Netdevice.
++ * @segs: SKB segments from GSO.
++ *
++ * Format skbs into GSOs.
++ *
++ * Return 1 on success, error code on failure.
++ */
++enum edma_tx_gso_status edma_tx_gso_segment(struct sk_buff *skb,
++ struct net_device *netdev, struct sk_buff **segs)
++{
++ u32 num_tx_desc_needed;
++
++ /* Check is skb is non-linear to proceed. */
++ if (likely(!skb_is_nonlinear(skb)))
++ return EDMA_TX_GSO_NOT_NEEDED;
++
++ /* Check if TSO is enabled. If so, return as skb doesn't
++ * need to be segmented by linux.
++ */
++ if (netdev->features & (NETIF_F_TSO | NETIF_F_TSO6)) {
++ num_tx_desc_needed = edma_tx_num_descs_for_sg(skb);
++ if (likely(num_tx_desc_needed <= EDMA_TX_TSO_SEG_MAX))
++ return EDMA_TX_GSO_NOT_NEEDED;
++ }
++
++ /* GSO segmentation of the skb into multiple segments. */
++ *segs = skb_gso_segment(skb, netdev->features
++ & ~(NETIF_F_TSO | NETIF_F_TSO6));
++
++ /* Check for error in GSO segmentation. */
++ if (IS_ERR_OR_NULL(*segs)) {
++ netdev_info(netdev, "Tx gso fail\n");
++ return EDMA_TX_GSO_FAIL;
++ }
++
++ return EDMA_TX_GSO_SUCCEED;
++}
++
++/**
++ * edma_tx_complete - Reap Tx completion descriptors.
++ * @work_to_do: Work to do.
++ * @txcmpl_ring: Tx Completion ring.
++ *
++ * Reap Tx completion descriptors of the transmitted
++ * packets and free the corresponding SKBs.
++ *
++ * Return the number descriptors for which Tx complete is done.
++ */
++u32 edma_tx_complete(u32 work_to_do, struct edma_txcmpl_ring *txcmpl_ring)
++{
++ struct edma_txcmpl_stats *txcmpl_stats = &txcmpl_ring->txcmpl_stats;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 cons_idx, end_idx, data, cpu_id;
++ struct device *dev = ppe_dev->dev;
++ u32 avail, count, txcmpl_errors;
++ struct edma_txcmpl_desc *txcmpl;
++ u32 prod_idx = 0, more_bit = 0;
++ struct netdev_queue *nq;
++ struct sk_buff *skb;
++ u32 reg;
++
++ cons_idx = txcmpl_ring->cons_idx;
++
++ if (likely(txcmpl_ring->avail_pkt >= work_to_do)) {
++ avail = work_to_do;
++ } else {
++ /* Get TXCMPL ring producer index. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_PROD_IDX(txcmpl_ring->id);
++ regmap_read(regmap, reg, &data);
++ prod_idx = data & EDMA_TXCMPL_PROD_IDX_MASK;
++
++ avail = EDMA_DESC_AVAIL_COUNT(prod_idx, cons_idx, EDMA_TX_RING_SIZE);
++ txcmpl_ring->avail_pkt = avail;
++
++ if (unlikely(!avail)) {
++ dev_dbg(dev, "No available descriptors are pending for %d txcmpl ring\n",
++ txcmpl_ring->id);
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->no_pending_desc;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ return 0;
++ }
++
++ avail = min(avail, work_to_do);
++ }
++
++ count = avail;
++
++ end_idx = (cons_idx + avail) & EDMA_TX_RING_SIZE_MASK;
++ txcmpl = EDMA_TXCMPL_DESC(txcmpl_ring, cons_idx);
++
++ /* Instead of freeing the skb, it might be better to save and use
++ * for Rxfill.
++ */
++ while (likely(avail--)) {
++ /* The last descriptor holds the SKB pointer for scattered frames.
++ * So skip the descriptors with more bit set.
++ */
++ more_bit = EDMA_TXCMPL_MORE_BIT_GET(txcmpl);
++ if (unlikely(more_bit)) {
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->desc_with_more_bit;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ cons_idx = ((cons_idx + 1) & EDMA_TX_RING_SIZE_MASK);
++ txcmpl = EDMA_TXCMPL_DESC(txcmpl_ring, cons_idx);
++ continue;
++ }
++
++ /* Find and free the skb for Tx completion. */
++ skb = (struct sk_buff *)EDMA_TXCMPL_OPAQUE_GET(txcmpl);
++ if (unlikely(!skb)) {
++ if (net_ratelimit())
++ dev_warn(dev, "Invalid cons_idx:%u prod_idx:%u word2:%x word3:%x\n",
++ cons_idx, prod_idx, txcmpl->word2, txcmpl->word3);
++
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->invalid_buffer;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ } else {
++ dev_dbg(dev, "TXCMPL: skb:%p, skb->len %d, skb->data_len %d, cons_idx:%d prod_idx:%d word2:0x%x word3:0x%x\n",
++ skb, skb->len, skb->data_len, cons_idx, prod_idx,
++ txcmpl->word2, txcmpl->word3);
++
++ txcmpl_errors = EDMA_TXCOMP_RING_ERROR_GET(txcmpl->word3);
++ if (unlikely(txcmpl_errors)) {
++ if (net_ratelimit())
++ dev_err(dev, "Error 0x%0x observed in tx complete %d ring\n",
++ txcmpl_errors, txcmpl_ring->id);
++
++ u64_stats_update_begin(&txcmpl_stats->syncp);
++ ++txcmpl_stats->errors;
++ u64_stats_update_end(&txcmpl_stats->syncp);
++ }
++
++ /* Retrieve pool id for unmapping.
++ * 0 for linear skb and (pool id - 1) represents nr_frag index.
++ */
++ if (!EDMA_TXCOMP_POOL_ID_GET(txcmpl)) {
++ dma_unmap_single(dev, virt_to_phys(skb->data),
++ skb->len, DMA_TO_DEVICE);
++ } else {
++ u8 frag_index = (EDMA_TXCOMP_POOL_ID_GET(txcmpl) - 1);
++ skb_frag_t *frag = &skb_shinfo(skb)->frags[frag_index];
++
++ dma_unmap_page(dev, virt_to_phys(frag),
++ PAGE_SIZE, DMA_TO_DEVICE);
++ }
++
++ dev_kfree_skb(skb);
++ }
++
++ cons_idx = ((cons_idx + 1) & EDMA_TX_RING_SIZE_MASK);
++ txcmpl = EDMA_TXCMPL_DESC(txcmpl_ring, cons_idx);
++ }
++
++ txcmpl_ring->cons_idx = cons_idx;
++ txcmpl_ring->avail_pkt -= count;
++
++ dev_dbg(dev, "TXCMPL:%u count:%u prod_idx:%u cons_idx:%u\n",
++ txcmpl_ring->id, count, prod_idx, cons_idx);
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_CONS_IDX(txcmpl_ring->id);
++ regmap_write(regmap, reg, cons_idx);
++
++ /* If tx_requeue_stop disabled (tx_requeue_stop = 0)
++ * Fetch the tx queue of interface and check if it is stopped.
++ * if queue is stopped and interface is up, wake up this queue.
++ */
++ if (unlikely(!edma_ctx->tx_requeue_stop)) {
++ cpu_id = smp_processor_id();
++ nq = netdev_get_tx_queue(txcmpl_ring->napi.dev, cpu_id);
++ if (unlikely(netif_tx_queue_stopped(nq)) &&
++ netif_carrier_ok(txcmpl_ring->napi.dev)) {
++ dev_dbg(dev, "Waking queue number %d, for interface %s\n",
++ cpu_id, txcmpl_ring->napi.dev->name);
++ __netif_tx_lock(nq, cpu_id);
++ netif_tx_wake_queue(nq);
++ __netif_tx_unlock(nq);
++ }
++ }
++
++ return count;
++}
++
++/**
++ * edma_tx_napi_poll - EDMA TX NAPI handler.
++ * @napi: NAPI structure.
++ * @budget: Tx NAPI Budget.
++ *
++ * EDMA TX NAPI handler.
++ */
++int edma_tx_napi_poll(struct napi_struct *napi, int budget)
++{
++ struct edma_txcmpl_ring *txcmpl_ring = (struct edma_txcmpl_ring *)napi;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 txcmpl_intr_status;
++ int work_done = 0;
++ u32 data, reg;
++
++ do {
++ work_done += edma_tx_complete(budget - work_done, txcmpl_ring);
++ if (work_done >= budget)
++ return work_done;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_STAT(txcmpl_ring->id);
++ regmap_read(regmap, reg, &data);
++ txcmpl_intr_status = data & EDMA_TXCMPL_RING_INT_STATUS_MASK;
++ } while (txcmpl_intr_status);
++
++ /* No more packets to process. Finish NAPI processing. */
++ napi_complete(napi);
++
++ /* Set TXCMPL ring interrupt mask. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_txcmpl);
++
++ return work_done;
++}
++
++/**
++ * edma_tx_handle_irq - Tx IRQ Handler.
++ * @irq: Interrupt request.
++ * @ctx: Context.
++ *
++ * Process TX IRQ and schedule NAPI.
++ *
++ * Return IRQ handler code.
++ */
++irqreturn_t edma_tx_handle_irq(int irq, void *ctx)
++{
++ struct edma_txcmpl_ring *txcmpl_ring = (struct edma_txcmpl_ring *)ctx;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ pr_debug("irq: irq=%d txcmpl_ring_id=%u\n", irq, txcmpl_ring->id);
++ if (likely(napi_schedule_prep(&txcmpl_ring->napi))) {
++ /* Disable TxCmpl intr. */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TX_INT_MASK(txcmpl_ring->id);
++ regmap_write(regmap, reg, EDMA_MASK_INT_DISABLE);
++ __napi_schedule(&txcmpl_ring->napi);
++ }
++
++ return IRQ_HANDLED;
++}
++
++static void edma_tx_dma_unmap_frags(struct sk_buff *skb, u32 nr_frags)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ u32 buf_len = 0;
++ u8 i = 0;
++
++ for (i = 0; i < skb_shinfo(skb)->nr_frags - nr_frags; i++) {
++ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
++
++ /* DMA mapping was not done for zero size segments. */
++ buf_len = skb_frag_size(frag);
++ if (unlikely(buf_len == 0))
++ continue;
++
++ dma_unmap_page(dev, virt_to_phys(frag), PAGE_SIZE,
++ DMA_TO_DEVICE);
++ }
++}
++
++static u32 edma_tx_skb_nr_frags(struct edma_txdesc_ring *txdesc_ring,
++ struct edma_txdesc_pri **txdesc, struct sk_buff *skb,
++ u32 *hw_next_to_use, u32 *invalid_frag)
++{
++ u32 nr_frags = 0, buf_len = 0, num_descs = 0, start_idx = 0, end_idx = 0;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 start_hw_next_to_use = *hw_next_to_use;
++ struct edma_txdesc_pri *txd = *txdesc;
++ struct device *dev = ppe_dev->dev;
++ u8 i = 0;
++
++ /* Hold onto the index mapped to *txdesc.
++ * This will be the index previous to that of current *hw_next_to_use.
++ */
++ start_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK)
++ & EDMA_TX_RING_SIZE_MASK);
++
++ /* Handle if the skb has nr_frags. */
++ nr_frags = skb_shinfo(skb)->nr_frags;
++ num_descs = nr_frags;
++ i = 0;
++
++ while (nr_frags--) {
++ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
++ dma_addr_t buff_addr;
++
++ buf_len = skb_frag_size(frag);
++
++ /* Zero size segment can lead EDMA HW to hang so, we don't want to
++ * process them. Zero size segment can happen during TSO operation
++ * if there is nothing but header in the primary segment.
++ */
++ if (unlikely(buf_len == 0)) {
++ num_descs--;
++ i++;
++ continue;
++ }
++
++ /* Setting the MORE bit on the previous Tx descriptor.
++ * Note: We will flush this descriptor as well later.
++ */
++ EDMA_TXDESC_MORE_BIT_SET(txd, 1);
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ txd = EDMA_TXDESC_PRI_DESC(txdesc_ring, *hw_next_to_use);
++ memset(txd, 0, sizeof(struct edma_txdesc_pri));
++ buff_addr = skb_frag_dma_map(dev, frag, 0, buf_len,
++ DMA_TO_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "Unable to dma first descriptor for nr_frags tx\n");
++ *hw_next_to_use = start_hw_next_to_use;
++ *invalid_frag = nr_frags;
++ return 0;
++ }
++
++ EDMA_TXDESC_BUFFER_ADDR_SET(txd, buff_addr);
++ EDMA_TXDESC_DATA_LEN_SET(txd, buf_len);
++ EDMA_TXDESC_POOL_ID_SET(txd, (i + 1));
++
++ *hw_next_to_use = ((*hw_next_to_use + 1) & EDMA_TX_RING_SIZE_MASK);
++ i++;
++ }
++
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ /* This will be the index previous to that of current *hw_next_to_use. */
++ end_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK) & EDMA_TX_RING_SIZE_MASK);
++
++ *txdesc = txd;
++
++ return num_descs;
++}
++
++static void edma_tx_fill_pp_desc(struct edma_port_priv *port_priv,
++ struct edma_txdesc_pri *txd, struct sk_buff *skb,
++ struct edma_port_tx_stats *stats)
++{
++ struct ppe_port *port = port_priv->ppe_port;
++ int port_id = port->port_id;
++
++ /* Offload L3/L4 checksum computation. */
++ if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {
++ EDMA_TXDESC_ADV_OFFLOAD_SET(txd);
++ EDMA_TXDESC_IP_CSUM_SET(txd);
++ EDMA_TXDESC_L4_CSUM_SET(txd);
++ }
++
++ /* Check if the packet needs TSO
++ * This will be mostly true for SG packets.
++ */
++ if (unlikely(skb_is_gso(skb))) {
++ if ((skb_shinfo(skb)->gso_type == SKB_GSO_TCPV4) ||
++ (skb_shinfo(skb)->gso_type == SKB_GSO_TCPV6)) {
++ u32 mss = skb_shinfo(skb)->gso_size;
++
++ /* If MSS<256, HW will do TSO using MSS=256,
++ * if MSS>10K, HW will do TSO using MSS=10K,
++ * else HW will report error 0x200000 in Tx Cmpl.
++ */
++ if (mss < EDMA_TX_TSO_MSS_MIN)
++ mss = EDMA_TX_TSO_MSS_MIN;
++ else if (mss > EDMA_TX_TSO_MSS_MAX)
++ mss = EDMA_TX_TSO_MSS_MAX;
++
++ EDMA_TXDESC_TSO_ENABLE_SET(txd, 1);
++ EDMA_TXDESC_MSS_SET(txd, mss);
++
++ /* Update tso stats. */
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_tso_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ }
++ }
++
++ /* Set destination information in the descriptor. */
++ EDMA_TXDESC_SERVICE_CODE_SET(txd, PPE_EDMA_SC_BYPASS_ID);
++ EDMA_DST_INFO_SET(txd, port_id);
++}
++
++static struct edma_txdesc_pri *edma_tx_skb_first_desc(struct edma_port_priv *port_priv,
++ struct edma_txdesc_ring *txdesc_ring,
++ struct sk_buff *skb, u32 *hw_next_to_use,
++ struct edma_port_tx_stats *stats)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct edma_txdesc_pri *txd = NULL;
++ struct device *dev = ppe_dev->dev;
++ dma_addr_t buff_addr;
++ u32 buf_len = 0;
++
++ /* Get the packet length. */
++ buf_len = skb_headlen(skb);
++ txd = EDMA_TXDESC_PRI_DESC(txdesc_ring, *hw_next_to_use);
++ memset(txd, 0, sizeof(struct edma_txdesc_pri));
++
++ /* Set the data pointer as the buffer address in the descriptor. */
++ buff_addr = dma_map_single(dev, skb->data, buf_len, DMA_TO_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "Unable to dma first descriptor for tx\n");
++ return NULL;
++ }
++
++ EDMA_TXDESC_BUFFER_ADDR_SET(txd, buff_addr);
++ EDMA_TXDESC_POOL_ID_SET(txd, 0);
++ edma_tx_fill_pp_desc(port_priv, txd, skb, stats);
++
++ /* Set packet length in the descriptor. */
++ EDMA_TXDESC_DATA_LEN_SET(txd, buf_len);
++ *hw_next_to_use = (*hw_next_to_use + 1) & EDMA_TX_RING_SIZE_MASK;
++
++ return txd;
++}
++
++static void edma_tx_handle_dma_err(struct sk_buff *skb, u32 num_sg_frag_list)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct device *dev = ppe_dev->dev;
++ struct sk_buff *iter_skb = NULL;
++ u32 cnt_sg_frag_list = 0;
++
++ /* Walk through all fraglist skbs. */
++ skb_walk_frags(skb, iter_skb) {
++ if (skb_headlen(iter_skb)) {
++ dma_unmap_single(dev, virt_to_phys(iter_skb->data),
++ skb_headlen(iter_skb), DMA_TO_DEVICE);
++ cnt_sg_frag_list += 1;
++ }
++
++ if (cnt_sg_frag_list == num_sg_frag_list)
++ return;
++
++ /* skb fraglist skb had nr_frags, unmap that memory. */
++ u32 nr_frags = skb_shinfo(iter_skb)->nr_frags;
++
++ if (nr_frags == 0)
++ continue;
++
++ for (int i = 0; i < nr_frags; i++) {
++ skb_frag_t *frag = &skb_shinfo(iter_skb)->frags[i];
++
++ /* DMA mapping was not done for zero size segments. */
++ if (unlikely(skb_frag_size(frag) == 0))
++ continue;
++
++ dma_unmap_page(dev, virt_to_phys(frag),
++ PAGE_SIZE, DMA_TO_DEVICE);
++ cnt_sg_frag_list += 1;
++ if (cnt_sg_frag_list == num_sg_frag_list)
++ return;
++ }
++ }
++}
++
++static u32 edma_tx_skb_sg_fill_desc(struct edma_txdesc_ring *txdesc_ring,
++ struct edma_txdesc_pri **txdesc,
++ struct sk_buff *skb, u32 *hw_next_to_use,
++ struct edma_port_tx_stats *stats)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 start_hw_next_to_use = 0, invalid_frag = 0;
++ struct edma_txdesc_pri *txd = *txdesc;
++ struct device *dev = ppe_dev->dev;
++ struct sk_buff *iter_skb = NULL;
++ u32 buf_len = 0, num_descs = 0;
++ u32 num_sg_frag_list = 0;
++
++ /* Head skb processed already. */
++ num_descs++;
++
++ if (unlikely(skb_has_frag_list(skb))) {
++ struct edma_txdesc_pri *start_desc = NULL;
++ u32 start_idx = 0, end_idx = 0;
++
++ /* Hold onto the index mapped to txd.
++ * This will be the index previous to that of current *hw_next_to_use.
++ */
++ start_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK)
++ & EDMA_TX_RING_SIZE_MASK);
++ start_desc = txd;
++ start_hw_next_to_use = *hw_next_to_use;
++
++ /* Walk through all fraglist skbs. */
++ skb_walk_frags(skb, iter_skb) {
++ dma_addr_t buff_addr;
++ u32 num_nr_frag = 0;
++
++ /* This case could happen during the packet decapsulation.
++ * All header content might be removed.
++ */
++ buf_len = skb_headlen(iter_skb);
++ if (unlikely(buf_len == 0))
++ goto skip_primary;
++
++ /* We make sure to flush this descriptor later. */
++ EDMA_TXDESC_MORE_BIT_SET(txd, 1);
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ txd = EDMA_TXDESC_PRI_DESC(txdesc_ring, *hw_next_to_use);
++ memset(txd, 0, sizeof(struct edma_txdesc_pri));
++ buff_addr = dma_map_single(dev, iter_skb->data,
++ buf_len, DMA_TO_DEVICE);
++ if (dma_mapping_error(dev, buff_addr)) {
++ dev_dbg(dev, "Unable to dma for fraglist\n");
++ goto dma_err;
++ }
++
++ EDMA_TXDESC_BUFFER_ADDR_SET(txd, buff_addr);
++ EDMA_TXDESC_DATA_LEN_SET(txd, buf_len);
++ EDMA_TXDESC_POOL_ID_SET(txd, 0);
++
++ *hw_next_to_use = (*hw_next_to_use + 1) & EDMA_TX_RING_SIZE_MASK;
++ num_descs += 1;
++ num_sg_frag_list += 1;
++
++ /* skb fraglist skb can have nr_frags. */
++skip_primary:
++ if (unlikely(skb_shinfo(iter_skb)->nr_frags)) {
++ num_nr_frag = edma_tx_skb_nr_frags(txdesc_ring, &txd,
++ iter_skb, hw_next_to_use,
++ &invalid_frag);
++ if (unlikely(!num_nr_frag)) {
++ dev_dbg(dev, "No descriptor available for ring %d\n",
++ txdesc_ring->id);
++ edma_tx_dma_unmap_frags(iter_skb, invalid_frag);
++ goto dma_err;
++ }
++
++ num_descs += num_nr_frag;
++ num_sg_frag_list += num_nr_frag;
++
++ /* Update fraglist with nr_frag stats. */
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_fraglist_with_nr_frags_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ }
++ }
++
++ EDMA_TXDESC_ENDIAN_SET(txd);
++
++ /* This will be the index previous to
++ * that of current *hw_next_to_use.
++ */
++ end_idx = (((*hw_next_to_use) + EDMA_TX_RING_SIZE_MASK) &
++ EDMA_TX_RING_SIZE_MASK);
++
++ /* Update frag_list stats. */
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_fraglist_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ } else {
++ /* Process skb with nr_frags. */
++ num_descs += edma_tx_skb_nr_frags(txdesc_ring, &txd, skb,
++ hw_next_to_use, &invalid_frag);
++ if (unlikely(!num_descs)) {
++ dev_dbg(dev, "No descriptor available for ring %d\n", txdesc_ring->id);
++ edma_tx_dma_unmap_frags(skb, invalid_frag);
++ *txdesc = NULL;
++ return num_descs;
++ }
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_nr_frag_pkts++;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ dev_dbg(dev, "skb:%p num_descs_filled: %u, nr_frags %u, frag_list fragments %u\n",
++ skb, num_descs, skb_shinfo(skb)->nr_frags, num_sg_frag_list);
++
++ *txdesc = txd;
++
++ return num_descs;
++
++dma_err:
++ if (!num_sg_frag_list)
++ goto reset_state;
++
++ edma_tx_handle_dma_err(skb, num_sg_frag_list);
++
++reset_state:
++ *hw_next_to_use = start_hw_next_to_use;
++ *txdesc = NULL;
++
++ return 0;
++}
++
++static u32 edma_tx_avail_desc(struct edma_txdesc_ring *txdesc_ring,
++ u32 hw_next_to_use)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ u32 data = 0, avail = 0, hw_next_to_clean = 0;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_CONS_IDX(txdesc_ring->id);
++ regmap_read(regmap, reg, &data);
++ hw_next_to_clean = data & EDMA_TXDESC_CONS_IDX_MASK;
++
++ avail = EDMA_DESC_AVAIL_COUNT(hw_next_to_clean - 1,
++ hw_next_to_use, EDMA_TX_RING_SIZE);
++
++ return avail;
++}
++
++/**
++ * edma_tx_ring_xmit - Transmit a packet.
++ * @netdev: Netdevice.
++ * @skb: Socket Buffer.
++ * @txdesc_ring: Tx Descriptor ring.
++ * @stats: EDMA Tx Statistics.
++ *
++ * Check for available descriptors, fill the descriptors
++ * and transmit both linear and non linear packets.
++ *
++ * Return 0 on success, negative error code on failure.
++ */
++enum edma_tx_status edma_tx_ring_xmit(struct net_device *netdev,
++ struct sk_buff *skb, struct edma_txdesc_ring *txdesc_ring,
++ struct edma_port_tx_stats *stats)
++{
++ struct edma_txdesc_stats *txdesc_stats = &txdesc_ring->txdesc_stats;
++ struct edma_port_priv *port_priv = netdev_priv(netdev);
++ u32 num_tx_desc_needed = 0, num_desc_filled = 0;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct ppe_port *port = port_priv->ppe_port;
++ struct regmap *regmap = ppe_dev->regmap;
++ struct edma_txdesc_pri *txdesc = NULL;
++ struct device *dev = ppe_dev->dev;
++ int port_id = port->port_id;
++ u32 hw_next_to_use = 0;
++ u32 reg;
++
++ hw_next_to_use = txdesc_ring->prod_idx;
++
++ if (unlikely(!(txdesc_ring->avail_desc))) {
++ txdesc_ring->avail_desc = edma_tx_avail_desc(txdesc_ring,
++ hw_next_to_use);
++ if (unlikely(!txdesc_ring->avail_desc)) {
++ netdev_dbg(netdev, "No available descriptors are present at %d ring\n",
++ txdesc_ring->id);
++
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++ }
++
++ /* Process head skb for linear skb.
++ * Process head skb + nr_frags + fraglist for non linear skb.
++ */
++ if (likely(!skb_is_nonlinear(skb))) {
++ txdesc = edma_tx_skb_first_desc(port_priv, txdesc_ring, skb,
++ &hw_next_to_use, stats);
++ if (unlikely(!txdesc)) {
++ netdev_dbg(netdev, "No descriptor available for ring %d\n",
++ txdesc_ring->id);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++
++ EDMA_TXDESC_ENDIAN_SET(txdesc);
++ num_desc_filled++;
++ } else {
++ num_tx_desc_needed = edma_tx_num_descs_for_sg(skb);
++
++ /* HW does not support TSO for packets with more than 32 segments.
++ * HW hangs up if it sees more than 32 segments. Kernel Perform GSO
++ * for such packets with netdev gso_max_segs set to 32.
++ */
++ if (unlikely(num_tx_desc_needed > EDMA_TX_TSO_SEG_MAX)) {
++ netdev_dbg(netdev, "Number of segments %u more than %u for %d ring\n",
++ num_tx_desc_needed, EDMA_TX_TSO_SEG_MAX, txdesc_ring->id);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->tso_max_seg_exceed;
++ u64_stats_update_end(&txdesc_stats->syncp);
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_tso_drop_pkts++;
++ u64_stats_update_end(&stats->syncp);
++
++ return EDMA_TX_FAIL;
++ }
++
++ if (unlikely(num_tx_desc_needed > txdesc_ring->avail_desc)) {
++ txdesc_ring->avail_desc = edma_tx_avail_desc(txdesc_ring,
++ hw_next_to_use);
++ if (num_tx_desc_needed > txdesc_ring->avail_desc) {
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ netdev_dbg(netdev, "Not enough available descriptors are present at %d ring for SG packet. Needed %d, currently available %d\n",
++ txdesc_ring->id, num_tx_desc_needed,
++ txdesc_ring->avail_desc);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++ }
++
++ txdesc = edma_tx_skb_first_desc(port_priv, txdesc_ring, skb,
++ &hw_next_to_use, stats);
++ if (unlikely(!txdesc)) {
++ netdev_dbg(netdev, "No non-linear descriptor available for ring %d\n",
++ txdesc_ring->id);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++
++ num_desc_filled = edma_tx_skb_sg_fill_desc(txdesc_ring,
++ &txdesc, skb, &hw_next_to_use, stats);
++ if (unlikely(!txdesc)) {
++ netdev_dbg(netdev, "No descriptor available for ring %d\n",
++ txdesc_ring->id);
++ dma_unmap_single(dev, virt_to_phys(skb->data),
++ skb->len, DMA_TO_DEVICE);
++ u64_stats_update_begin(&txdesc_stats->syncp);
++ ++txdesc_stats->no_desc_avail;
++ u64_stats_update_end(&txdesc_stats->syncp);
++ return EDMA_TX_FAIL_NO_DESC;
++ }
++ }
++
++ /* Set the skb pointer to the descriptor's opaque field/s
++ * on the last descriptor of the packet/SG packet.
++ */
++ EDMA_TXDESC_OPAQUE_SET(txdesc, skb);
++
++ /* Update producer index. */
++ txdesc_ring->prod_idx = hw_next_to_use & EDMA_TXDESC_PROD_IDX_MASK;
++ txdesc_ring->avail_desc -= num_desc_filled;
++
++ netdev_dbg(netdev, "%s: skb:%p tx_ring:%u proto:0x%x skb->len:%d\n port:%u prod_idx:%u ip_summed:0x%x\n",
++ netdev->name, skb, txdesc_ring->id, ntohs(skb->protocol),
++ skb->len, port_id, hw_next_to_use, skb->ip_summed);
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_TXDESC_PROD_IDX(txdesc_ring->id);
++ regmap_write(regmap, reg, txdesc_ring->prod_idx);
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->tx_pkts++;
++ stats->tx_bytes += skb->len;
++ u64_stats_update_end(&stats->syncp);
++
++ return EDMA_TX_OK;
++}
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_tx.h
+@@ -0,0 +1,302 @@
++/* SPDX-License-Identifier: GPL-2.0-only
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef __EDMA_TX__
++#define __EDMA_TX__
++
++#include "edma_port.h"
++
++#define EDMA_GET_DESC(R, i, type) (&(((type *)((R)->desc))[(i)]))
++#define EDMA_GET_PDESC(R, i, type) (&(((type *)((R)->pdesc))[(i)]))
++#define EDMA_GET_SDESC(R, i, type) (&(((type *)((R)->sdesc))[(i)]))
++#define EDMA_TXCMPL_DESC(R, i) EDMA_GET_DESC(R, i, \
++ struct edma_txcmpl_desc)
++#define EDMA_TXDESC_PRI_DESC(R, i) EDMA_GET_PDESC(R, i, \
++ struct edma_txdesc_pri)
++#define EDMA_TXDESC_SEC_DESC(R, i) EDMA_GET_SDESC(R, i, \
++ struct edma_txdesc_sec)
++
++#define EDMA_DESC_AVAIL_COUNT(head, tail, _max) ({ \
++ typeof(_max) (max) = (_max); \
++ ((((head) - (tail)) + \
++ (max)) & ((max) - 1)); })
++
++#define EDMA_TX_RING_SIZE 2048
++#define EDMA_TX_RING_SIZE_MASK (EDMA_TX_RING_SIZE - 1)
++
++/* Max segment processing capacity of HW for TSO. */
++#define EDMA_TX_TSO_SEG_MAX 32
++
++/* HW defined low and high MSS size. */
++#define EDMA_TX_TSO_MSS_MIN 256
++#define EDMA_TX_TSO_MSS_MAX 10240
++
++#define EDMA_DST_PORT_TYPE 2
++#define EDMA_DST_PORT_TYPE_SHIFT 28
++#define EDMA_DST_PORT_TYPE_MASK (0xf << EDMA_DST_PORT_TYPE_SHIFT)
++#define EDMA_DST_PORT_ID_SHIFT 16
++#define EDMA_DST_PORT_ID_MASK (0xfff << EDMA_DST_PORT_ID_SHIFT)
++
++#define EDMA_DST_PORT_TYPE_SET(x) (((x) << EDMA_DST_PORT_TYPE_SHIFT) & \
++ EDMA_DST_PORT_TYPE_MASK)
++#define EDMA_DST_PORT_ID_SET(x) (((x) << EDMA_DST_PORT_ID_SHIFT) & \
++ EDMA_DST_PORT_ID_MASK)
++#define EDMA_DST_INFO_SET(desc, x) ((desc)->word4 |= \
++ (EDMA_DST_PORT_TYPE_SET(EDMA_DST_PORT_TYPE) | EDMA_DST_PORT_ID_SET(x)))
++
++#define EDMA_TXDESC_TSO_ENABLE_MASK BIT(24)
++#define EDMA_TXDESC_TSO_ENABLE_SET(desc, x) ((desc)->word5 |= \
++ FIELD_PREP(EDMA_TXDESC_TSO_ENABLE_MASK, x))
++#define EDMA_TXDESC_MSS_MASK GENMASK(31, 16)
++#define EDMA_TXDESC_MSS_SET(desc, x) ((desc)->word6 |= \
++ FIELD_PREP(EDMA_TXDESC_MSS_MASK, x))
++#define EDMA_TXDESC_MORE_BIT_MASK BIT(30)
++#define EDMA_TXDESC_MORE_BIT_SET(desc, x) ((desc)->word1 |= \
++ FIELD_PREP(EDMA_TXDESC_MORE_BIT_MASK, x))
++
++#define EDMA_TXDESC_ADV_OFFSET_BIT BIT(31)
++#define EDMA_TXDESC_ADV_OFFLOAD_SET(desc) ((desc)->word5 |= \
++ FIELD_PREP(EDMA_TXDESC_ADV_OFFSET_BIT, 1))
++#define EDMA_TXDESC_IP_CSUM_BIT BIT(25)
++#define EDMA_TXDESC_IP_CSUM_SET(desc) ((desc)->word5 |= \
++ FIELD_PREP(EDMA_TXDESC_IP_CSUM_BIT, 1))
++
++#define EDMA_TXDESC_L4_CSUM_SET_MASK GENMASK(27, 26)
++#define EDMA_TXDESC_L4_CSUM_SET(desc) ((desc)->word5 |= \
++ (FIELD_PREP(EDMA_TXDESC_L4_CSUM_SET_MASK, 1)))
++
++#define EDMA_TXDESC_POOL_ID_SET_MASK GENMASK(24, 18)
++#define EDMA_TXDESC_POOL_ID_SET(desc, x) ((desc)->word5 |= \
++ (FIELD_PREP(EDMA_TXDESC_POOL_ID_SET_MASK, x)))
++
++#define EDMA_TXDESC_DATA_LEN_SET(desc, x) ((desc)->word5 |= ((x) & 0x1ffff))
++#define EDMA_TXDESC_SERVICE_CODE_MASK GENMASK(24, 16)
++#define EDMA_TXDESC_SERVICE_CODE_SET(desc, x) ((desc)->word1 |= \
++ (FIELD_PREP(EDMA_TXDESC_SERVICE_CODE_MASK, x)))
++#define EDMA_TXDESC_BUFFER_ADDR_SET(desc, addr) (((desc)->word0) = (addr))
++
++#ifdef __LP64__
++#define EDMA_TXDESC_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ (((u64)(desc)->word3 << 32) | (desc)->word2); })
++
++#define EDMA_TXCMPL_OPAQUE_GET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ (((u64)(desc)->word1 << 32) | \
++ (desc)->word0); })
++
++#define EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr) ((desc)->word2 = \
++ (u32)(uintptr_t)(ptr))
++
++#define EDMA_TXDESC_OPAQUE_HI_SET(desc, ptr) ((desc)->word3 = \
++ (u32)((u64)(ptr) >> 32))
++
++#define EDMA_TXDESC_OPAQUE_SET(_desc, _ptr) do { \
++ typeof(_desc) (desc) = (_desc); \
++ typeof(_ptr) (ptr) = (_ptr); \
++ EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr); \
++ EDMA_TXDESC_OPAQUE_HI_SET(desc, ptr); \
++} while (0)
++#else
++#define EDMA_TXCMPL_OPAQUE_GET(desc) ((desc)->word0)
++#define EDMA_TXDESC_OPAQUE_GET(desc) ((desc)->word2)
++#define EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr) ((desc)->word2 = (u32)(uintptr_t)ptr)
++
++#define EDMA_TXDESC_OPAQUE_SET(desc, ptr) \
++ EDMA_TXDESC_OPAQUE_LO_SET(desc, ptr)
++#endif
++#define EDMA_TXCMPL_MORE_BIT_MASK BIT(30)
++
++#define EDMA_TXCMPL_MORE_BIT_GET(desc) ((le32_to_cpu((__force __le32)((desc)->word2))) & \
++ EDMA_TXCMPL_MORE_BIT_MASK)
++
++#define EDMA_TXCOMP_RING_ERROR_MASK GENMASK(22, 0)
++
++#define EDMA_TXCOMP_RING_ERROR_GET(x) ((le32_to_cpu((__force __le32)x)) & \
++ EDMA_TXCOMP_RING_ERROR_MASK)
++
++#define EDMA_TXCOMP_POOL_ID_MASK GENMASK(5, 0)
++
++#define EDMA_TXCOMP_POOL_ID_GET(desc) ((le32_to_cpu((__force __le32)((desc)->word2))) & \
++ EDMA_TXCOMP_POOL_ID_MASK)
++
++/* Opaque values are set in word2 and word3,
++ * they are not accessed by the EDMA HW,
++ * so endianness conversion is not needed.
++ */
++#define EDMA_TXDESC_ENDIAN_SET(_desc) ({ \
++ typeof(_desc) (desc) = (_desc); \
++ cpu_to_le32s(&((desc)->word0)); \
++ cpu_to_le32s(&((desc)->word1)); \
++ cpu_to_le32s(&((desc)->word4)); \
++ cpu_to_le32s(&((desc)->word5)); \
++ cpu_to_le32s(&((desc)->word6)); \
++ cpu_to_le32s(&((desc)->word7)); \
++})
++
++/* EDMA Tx GSO status */
++enum edma_tx_status {
++ EDMA_TX_OK = 0, /* Tx success. */
++ EDMA_TX_FAIL_NO_DESC = 1, /* Not enough descriptors. */
++ EDMA_TX_FAIL = 2, /* Tx failure. */
++};
++
++/* EDMA TX GSO status */
++enum edma_tx_gso_status {
++ EDMA_TX_GSO_NOT_NEEDED = 0,
++ /* Packet has segment count less than TX_TSO_SEG_MAX. */
++ EDMA_TX_GSO_SUCCEED = 1,
++ /* GSO Succeed. */
++ EDMA_TX_GSO_FAIL = 2,
++ /* GSO failed, drop the packet. */
++};
++
++/**
++ * struct edma_txcmpl_stats - EDMA TX complete ring statistics.
++ * @invalid_buffer: Invalid buffer address received.
++ * @errors: Other Tx complete descriptor errors indicated by the hardware.
++ * @desc_with_more_bit: Packet's segment transmit count.
++ * @no_pending_desc: No descriptor is pending for processing.
++ * @syncp: Synchronization pointer.
++ */
++struct edma_txcmpl_stats {
++ u64 invalid_buffer;
++ u64 errors;
++ u64 desc_with_more_bit;
++ u64 no_pending_desc;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_txdesc_stats - EDMA Tx descriptor ring statistics.
++ * @no_desc_avail: No descriptor available to transmit.
++ * @tso_max_seg_exceed: Packets extending EDMA_TX_TSO_SEG_MAX segments.
++ * @syncp: Synchronization pointer.
++ */
++struct edma_txdesc_stats {
++ u64 no_desc_avail;
++ u64 tso_max_seg_exceed;
++ struct u64_stats_sync syncp;
++};
++
++/**
++ * struct edma_txdesc_pri - EDMA primary TX descriptor.
++ * @word0: Low 32-bit of buffer address.
++ * @word1: Buffer recycling, PTP tag flag, PRI valid flag.
++ * @word2: Low 32-bit of opaque value.
++ * @word3: High 32-bit of opaque value.
++ * @word4: Source/Destination port info.
++ * @word5: VLAN offload, csum mode, ip_csum_en, tso_en, data len.
++ * @word6: MSS/hash_value/PTP tag, data offset.
++ * @word7: L4/L3 offset, PROT type, L2 type, CVLAN/SVLAN tag, service code.
++ */
++struct edma_txdesc_pri {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++/**
++ * struct edma_txdesc_sec - EDMA secondary TX descriptor.
++ * @word0: Reserved.
++ * @word1: Custom csum offset, payload offset, TTL/NAT action.
++ * @word2: NAPT translated port, DSCP value, TTL value.
++ * @word3: Flow index value and valid flag.
++ * @word4: Reserved.
++ * @word5: Reserved.
++ * @word6: CVLAN/SVLAN command.
++ * @word7: CVLAN/SVLAN tag value.
++ */
++struct edma_txdesc_sec {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++ u32 word4;
++ u32 word5;
++ u32 word6;
++ u32 word7;
++};
++
++/**
++ * struct edma_txcmpl_desc - EDMA TX complete descriptor.
++ * @word0: Low 32-bit opaque value.
++ * @word1: High 32-bit opaque value.
++ * @word2: More fragment, transmit ring id, pool id.
++ * @word3: Error indications.
++ */
++struct edma_txcmpl_desc {
++ u32 word0;
++ u32 word1;
++ u32 word2;
++ u32 word3;
++};
++
++/**
++ * struct edma_txdesc_ring - EDMA TX descriptor ring
++ * @prod_idx: Producer index
++ * @id: Tx ring number
++ * @avail_desc: Number of available descriptor to process
++ * @pdesc: Primary descriptor ring virtual address
++ * @pdma: Primary descriptor ring physical address
++ * @sdesc: Secondary descriptor ring virtual address
++ * @tx_desc_stats: Tx descriptor ring statistics
++ * @sdma: Secondary descriptor ring physical address
++ * @count: Number of descriptors
++ * @fc_grp_id: Flow control group ID
++ */
++struct edma_txdesc_ring {
++ u32 prod_idx;
++ u32 id;
++ u32 avail_desc;
++ struct edma_txdesc_pri *pdesc;
++ dma_addr_t pdma;
++ struct edma_txdesc_sec *sdesc;
++ struct edma_txdesc_stats txdesc_stats;
++ dma_addr_t sdma;
++ u32 count;
++ u8 fc_grp_id;
++};
++
++/**
++ * struct edma_txcmpl_ring - EDMA TX complete ring
++ * @napi: NAPI
++ * @cons_idx: Consumer index
++ * @avail_pkt: Number of available packets to process
++ * @desc: Descriptor ring virtual address
++ * @id: Txcmpl ring number
++ * @tx_cmpl_stats: Tx complete ring statistics
++ * @dma: Descriptor ring physical address
++ * @count: Number of descriptors in the ring
++ * @napi_added: Flag to indicate NAPI add status
++ */
++struct edma_txcmpl_ring {
++ struct napi_struct napi;
++ u32 cons_idx;
++ u32 avail_pkt;
++ struct edma_txcmpl_desc *desc;
++ u32 id;
++ struct edma_txcmpl_stats txcmpl_stats;
++ dma_addr_t dma;
++ u32 count;
++ bool napi_added;
++};
++
++enum edma_tx_status edma_tx_ring_xmit(struct net_device *netdev,
++ struct sk_buff *skb,
++ struct edma_txdesc_ring *txdesc_ring,
++ struct edma_port_tx_stats *stats);
++u32 edma_tx_complete(u32 work_to_do,
++ struct edma_txcmpl_ring *txcmpl_ring);
++irqreturn_t edma_tx_handle_irq(int irq, void *ctx);
++int edma_tx_napi_poll(struct napi_struct *napi, int budget);
++enum edma_tx_gso_status edma_tx_gso_segment(struct sk_buff *skb,
++ struct net_device *netdev, struct sk_buff **segs);
++
++#endif
--- /dev/null
+From 8a924457c0b71acee96c8f78ef386e2a354a2aca Mon Sep 17 00:00:00 2001
+From: Suruchi Agarwal <quic_suruchia@quicinc.com>
+Date: Thu, 21 Mar 2024 16:31:04 -0700
+Subject: [PATCH] net: ethernet: qualcomm: Add miscellaneous error interrupts
+ and counters
+
+Miscellaneous error interrupts, EDMA Tx/Rx and error counters are supported
+using debugfs framework.
+
+Change-Id: I7da8b978a7e93947b03a45269a81b401f35da31c
+Co-developed-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Signed-off-by: Suruchi Agarwal <quic_suruchia@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 162 ++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 30 ++
+ .../net/ethernet/qualcomm/ppe/edma_debugfs.c | 370 ++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/ppe_debugfs.c | 17 +
+ 5 files changed, 580 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_debugfs.c
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_port.o edma_rx.o edma_tx.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_debugfs.o edma_port.o edma_rx.o edma_tx.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -152,6 +152,42 @@ static int edma_clock_init(void)
+ }
+
+ /**
++ * edma_err_stats_alloc - Allocate stats memory
++ *
++ * Allocate memory for per-CPU error stats.
++ */
++int edma_err_stats_alloc(void)
++{
++ u32 i;
++
++ edma_ctx->err_stats = alloc_percpu(*edma_ctx->err_stats);
++ if (!edma_ctx->err_stats)
++ return -ENOMEM;
++
++ for_each_possible_cpu(i) {
++ struct edma_err_stats *stats;
++
++ stats = per_cpu_ptr(edma_ctx->err_stats, i);
++ u64_stats_init(&stats->syncp);
++ }
++
++ return 0;
++}
++
++/**
++ * edma_err_stats_free - Free stats memory
++ *
++ * Free memory of per-CPU error stats.
++ */
++void edma_err_stats_free(void)
++{
++ if (edma_ctx->err_stats) {
++ free_percpu(edma_ctx->err_stats);
++ edma_ctx->err_stats = NULL;
++ }
++}
++
++/**
+ * edma_configure_ucast_prio_map_tbl - Configure unicast priority map table.
+ *
+ * Map int_priority values to priority class and initialize
+@@ -191,11 +227,113 @@ static int edma_configure_ucast_prio_map
+ return ret;
+ }
+
++static void edma_disable_misc_interrupt(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MISC_INT_MASK_ADDR;
++ regmap_write(regmap, reg, EDMA_MASK_INT_CLEAR);
++}
++
++static void edma_enable_misc_interrupt(void)
++{
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 reg;
++
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MISC_INT_MASK_ADDR;
++ regmap_write(regmap, reg, edma_ctx->intr_info.intr_mask_misc);
++}
++
++static irqreturn_t edma_misc_handle_irq(int irq,
++ __maybe_unused void *ctx)
++{
++ struct edma_err_stats *stats = this_cpu_ptr(edma_ctx->err_stats);
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
++ struct regmap *regmap = ppe_dev->regmap;
++ u32 misc_intr_status, data, reg;
++
++ /* Read Misc intr status */
++ reg = EDMA_BASE_OFFSET + EDMA_REG_MISC_INT_STAT_ADDR;
++ regmap_read(regmap, reg, &data);
++ misc_intr_status = data & edma_ctx->intr_info.intr_mask_misc;
++
++ pr_debug("Received misc irq %d, status: %d\n", irq, misc_intr_status);
++
++ if (FIELD_GET(EDMA_MISC_AXI_RD_ERR_MASK, misc_intr_status)) {
++ pr_err("MISC AXI read error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_axi_read_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_AXI_WR_ERR_MASK, misc_intr_status)) {
++ pr_err("MISC AXI write error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_axi_write_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_RX_DESC_FIFO_FULL_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Rx descriptor fifo full error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_rxdesc_fifo_full;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_RX_ERR_BUF_SIZE_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Rx buffer size error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_rx_buf_size_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_TX_SRAM_FULL_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Tx SRAM full error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_tx_sram_full;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_TX_CMPL_BUF_FULL_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Tx complete buffer full error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_txcmpl_buf_full;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_DATA_LEN_ERR_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC data length error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_tx_data_len_err;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ if (FIELD_GET(EDMA_MISC_TX_TIMEOUT_MASK, misc_intr_status)) {
++ if (net_ratelimit())
++ pr_err("MISC Tx timeout error received\n");
++ u64_stats_update_begin(&stats->syncp);
++ ++stats->edma_tx_timeout;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ return IRQ_HANDLED;
++}
++
+ static int edma_irq_register(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct ppe_device *ppe_dev = edma_ctx->ppe_dev;
+ struct edma_ring_info *rx = hw_info->rx;
++ struct device *dev = ppe_dev->dev;
+ int ret;
+ u32 i;
+
+@@ -270,8 +408,25 @@ static int edma_irq_register(void)
+ edma_rxdesc_irq_name[i]);
+ }
+
++ /* Request Misc IRQ */
++ ret = request_irq(edma_ctx->intr_info.intr_misc, edma_misc_handle_irq,
++ IRQF_SHARED, "edma_misc",
++ (void *)dev);
++ if (ret) {
++ pr_err("MISC IRQ:%d request failed\n",
++ edma_ctx->intr_info.intr_misc);
++ goto misc_intr_req_fail;
++ }
++
+ return 0;
+
++misc_intr_req_fail:
++ /* Free IRQ for RXDESC rings */
++ for (i = 0; i < rx->num_rings; i++) {
++ synchronize_irq(edma_ctx->intr_info.intr_rx[i]);
++ free_irq(edma_ctx->intr_info.intr_rx[i],
++ (void *)&edma_ctx->rx_rings[i]);
++ }
+ rx_desc_ring_intr_req_fail:
+ for (i = 0; i < rx->num_rings; i++)
+ kfree(edma_rxdesc_irq_name[i]);
+@@ -503,6 +658,7 @@ static int edma_hw_configure(void)
+ edma_cfg_tx_disable_interrupts(i);
+
+ edma_cfg_rx_disable_interrupts();
++ edma_disable_misc_interrupt();
+
+ edma_cfg_rx_rings_disable();
+
+@@ -614,6 +770,7 @@ void edma_destroy(struct ppe_device *ppe
+ edma_cfg_tx_disable_interrupts(i);
+
+ edma_cfg_rx_disable_interrupts();
++ edma_disable_misc_interrupt();
+
+ /* Free IRQ for TXCMPL rings. */
+ for (i = 0; i < txcmpl->num_rings; i++) {
+@@ -634,6 +791,10 @@ void edma_destroy(struct ppe_device *ppe
+ }
+ kfree(edma_rxdesc_irq_name);
+
++ /* Free Misc IRQ */
++ synchronize_irq(edma_ctx->intr_info.intr_misc);
++ free_irq(edma_ctx->intr_info.intr_misc, (void *)(ppe_dev->dev));
++
+ kfree(edma_ctx->intr_info.intr_rx);
+ kfree(edma_ctx->intr_info.intr_txcmpl);
+
+@@ -699,6 +860,7 @@ int edma_setup(struct ppe_device *ppe_de
+ }
+
+ edma_cfg_rx_enable_interrupts();
++ edma_enable_misc_interrupt();
+
+ dev_info(dev, "EDMA configuration successful\n");
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -47,6 +47,30 @@ enum ppe_queue_class_type {
+ };
+
+ /**
++ * struct edma_err_stats - EDMA error stats
++ * @edma_axi_read_err: AXI read error
++ * @edma_axi_write_err: AXI write error
++ * @edma_rxdesc_fifo_full: Rx desc FIFO full error
++ * @edma_rx_buf_size_err: Rx buffer size too small error
++ * @edma_tx_sram_full: Tx packet SRAM buffer full error
++ * @edma_tx_data_len_err: Tx data length error
++ * @edma_tx_timeout: Tx timeout error
++ * @edma_txcmpl_buf_full: Tx completion buffer full error
++ * @syncp: Synchronization pointer
++ */
++struct edma_err_stats {
++ u64 edma_axi_read_err;
++ u64 edma_axi_write_err;
++ u64 edma_rxdesc_fifo_full;
++ u64 edma_rx_buf_size_err;
++ u64 edma_tx_sram_full;
++ u64 edma_tx_data_len_err;
++ u64 edma_tx_timeout;
++ u64 edma_txcmpl_buf_full;
++ struct u64_stats_sync syncp;
++};
++
++/**
+ * struct edma_ring_info - EDMA ring data structure.
+ * @max_rings: Maximum number of rings
+ * @ring_start: Ring start ID
+@@ -107,6 +131,7 @@ struct edma_intr_info {
+ * @rx_rings: Rx Desc Rings, SW is consumer
+ * @tx_rings: Tx Descriptor Ring, SW is producer
+ * @txcmpl_rings: Tx complete Ring, SW is consumer
++ * @err_stats: Per CPU error statistics
+ * @rx_page_mode: Page mode enabled or disabled
+ * @rx_buf_size: Rx buffer size for Jumbo MRU
+ * @tx_requeue_stop: Tx requeue stop enabled or disabled
+@@ -121,6 +146,7 @@ struct edma_context {
+ struct edma_rxdesc_ring *rx_rings;
+ struct edma_txdesc_ring *tx_rings;
+ struct edma_txcmpl_ring *txcmpl_rings;
++ struct edma_err_stats __percpu *err_stats;
+ u32 rx_page_mode;
+ u32 rx_buf_size;
+ bool tx_requeue_stop;
+@@ -129,8 +155,12 @@ struct edma_context {
+ /* Global EDMA context */
+ extern struct edma_context *edma_ctx;
+
++int edma_err_stats_alloc(void);
++void edma_err_stats_free(void);
+ void edma_destroy(struct ppe_device *ppe_dev);
+ int edma_setup(struct ppe_device *ppe_dev);
++void edma_debugfs_teardown(void);
++int edma_debugfs_setup(struct ppe_device *ppe_dev);
+ int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
+ enum ppe_queue_class_type class,
+ int index, int queue_offset);
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_debugfs.c
+@@ -0,0 +1,370 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* EDMA debugfs routines for display of Tx/Rx counters. */
++
++#include <linux/cpumask.h>
++#include <linux/debugfs.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/printk.h>
++
++#include "edma.h"
++
++#define EDMA_STATS_BANNER_MAX_LEN 80
++#define EDMA_RX_RING_STATS_NODE_NAME "EDMA_RX"
++#define EDMA_TX_RING_STATS_NODE_NAME "EDMA_TX"
++#define EDMA_ERR_STATS_NODE_NAME "EDMA_ERR"
++
++static struct dentry *edma_dentry;
++static struct dentry *stats_dentry;
++
++static void edma_debugfs_print_banner(struct seq_file *m, char *node)
++{
++ u32 banner_char_len, i;
++
++ for (i = 0; i < EDMA_STATS_BANNER_MAX_LEN; i++)
++ seq_puts(m, "_");
++ banner_char_len = (EDMA_STATS_BANNER_MAX_LEN - (strlen(node) + 2)) / 2;
++ seq_puts(m, "\n\n");
++
++ for (i = 0; i < banner_char_len; i++)
++ seq_puts(m, "<");
++ seq_printf(m, " %s ", node);
++
++ for (i = 0; i < banner_char_len; i++)
++ seq_puts(m, ">");
++ seq_puts(m, "\n");
++
++ for (i = 0; i < EDMA_STATS_BANNER_MAX_LEN; i++)
++ seq_puts(m, "_");
++ seq_puts(m, "\n\n");
++}
++
++static int edma_debugfs_rx_rings_stats_show(struct seq_file *m,
++ void __maybe_unused *p)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *rxfill = hw_info->rxfill;
++ struct edma_rxfill_stats *rxfill_stats;
++ struct edma_rxdesc_stats *rxdesc_stats;
++ struct edma_ring_info *rx = hw_info->rx;
++ unsigned int start;
++ u32 i;
++
++ rxfill_stats = kcalloc(rxfill->num_rings, sizeof(*rxfill_stats), GFP_KERNEL);
++ if (!rxfill_stats)
++ return -ENOMEM;
++
++ rxdesc_stats = kcalloc(rx->num_rings, sizeof(*rxdesc_stats), GFP_KERNEL);
++ if (!rxdesc_stats) {
++ kfree(rxfill_stats);
++ return -ENOMEM;
++ }
++
++ /* Get stats for Rx fill rings. */
++ for (i = 0; i < rxfill->num_rings; i++) {
++ struct edma_rxfill_ring *rxfill_ring;
++ struct edma_rxfill_stats *stats;
++
++ rxfill_ring = &edma_ctx->rxfill_rings[i];
++ stats = &rxfill_ring->rxfill_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ rxfill_stats[i].alloc_failed = stats->alloc_failed;
++ rxfill_stats[i].page_alloc_failed = stats->page_alloc_failed;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ /* Get stats for Rx Desc rings. */
++ for (i = 0; i < rx->num_rings; i++) {
++ struct edma_rxdesc_ring *rxdesc_ring;
++ struct edma_rxdesc_stats *stats;
++
++ rxdesc_ring = &edma_ctx->rx_rings[i];
++ stats = &rxdesc_ring->rxdesc_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ rxdesc_stats[i].src_port_inval = stats->src_port_inval;
++ rxdesc_stats[i].src_port_inval_type = stats->src_port_inval_type;
++ rxdesc_stats[i].src_port_inval_netdev = stats->src_port_inval_netdev;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ edma_debugfs_print_banner(m, EDMA_RX_RING_STATS_NODE_NAME);
++
++ seq_puts(m, "\n#EDMA RX descriptor rings stats:\n\n");
++ for (i = 0; i < rx->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA RX descriptor %d ring stats:\n", i + rx->ring_start);
++ seq_printf(m, "\t\t rxdesc[%d]:src_port_inval = %llu\n",
++ i + rx->ring_start, rxdesc_stats[i].src_port_inval);
++ seq_printf(m, "\t\t rxdesc[%d]:src_port_inval_type = %llu\n",
++ i + rx->ring_start, rxdesc_stats[i].src_port_inval_type);
++ seq_printf(m, "\t\t rxdesc[%d]:src_port_inval_netdev = %llu\n",
++ i + rx->ring_start,
++ rxdesc_stats[i].src_port_inval_netdev);
++ seq_puts(m, "\n");
++ }
++
++ seq_puts(m, "\n#EDMA RX fill rings stats:\n\n");
++ for (i = 0; i < rxfill->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA RX fill %d ring stats:\n", i + rxfill->ring_start);
++ seq_printf(m, "\t\t rxfill[%d]:alloc_failed = %llu\n",
++ i + rxfill->ring_start, rxfill_stats[i].alloc_failed);
++ seq_printf(m, "\t\t rxfill[%d]:page_alloc_failed = %llu\n",
++ i + rxfill->ring_start, rxfill_stats[i].page_alloc_failed);
++ seq_puts(m, "\n");
++ }
++
++ kfree(rxfill_stats);
++ kfree(rxdesc_stats);
++ return 0;
++}
++
++static int edma_debugfs_tx_rings_stats_show(struct seq_file *m,
++ void __maybe_unused *p)
++{
++ struct edma_hw_info *hw_info = edma_ctx->hw_info;
++ struct edma_ring_info *txcmpl = hw_info->txcmpl;
++ struct edma_ring_info *tx = hw_info->tx;
++ struct edma_txcmpl_stats *txcmpl_stats;
++ struct edma_txdesc_stats *txdesc_stats;
++ unsigned int start;
++ u32 i;
++
++ txcmpl_stats = kcalloc(txcmpl->num_rings, sizeof(*txcmpl_stats), GFP_KERNEL);
++ if (!txcmpl_stats)
++ return -ENOMEM;
++
++ txdesc_stats = kcalloc(tx->num_rings, sizeof(*txdesc_stats), GFP_KERNEL);
++ if (!txdesc_stats) {
++ kfree(txcmpl_stats);
++ return -ENOMEM;
++ }
++
++ /* Get stats for Tx desc rings. */
++ for (i = 0; i < tx->num_rings; i++) {
++ struct edma_txdesc_ring *txdesc_ring;
++ struct edma_txdesc_stats *stats;
++
++ txdesc_ring = &edma_ctx->tx_rings[i];
++ stats = &txdesc_ring->txdesc_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ txdesc_stats[i].no_desc_avail = stats->no_desc_avail;
++ txdesc_stats[i].tso_max_seg_exceed = stats->tso_max_seg_exceed;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ /* Get stats for Tx Complete rings. */
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ struct edma_txcmpl_ring *txcmpl_ring;
++ struct edma_txcmpl_stats *stats;
++
++ txcmpl_ring = &edma_ctx->txcmpl_rings[i];
++ stats = &txcmpl_ring->txcmpl_stats;
++ do {
++ start = u64_stats_fetch_begin(&stats->syncp);
++ txcmpl_stats[i].invalid_buffer = stats->invalid_buffer;
++ txcmpl_stats[i].errors = stats->errors;
++ txcmpl_stats[i].desc_with_more_bit = stats->desc_with_more_bit;
++ txcmpl_stats[i].no_pending_desc = stats->no_pending_desc;
++ } while (u64_stats_fetch_retry(&stats->syncp, start));
++ }
++
++ edma_debugfs_print_banner(m, EDMA_TX_RING_STATS_NODE_NAME);
++
++ seq_puts(m, "\n#EDMA TX complete rings stats:\n\n");
++ for (i = 0; i < txcmpl->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA TX complete %d ring stats:\n", i + txcmpl->ring_start);
++ seq_printf(m, "\t\t txcmpl[%d]:invalid_buffer = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].invalid_buffer);
++ seq_printf(m, "\t\t txcmpl[%d]:errors = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].errors);
++ seq_printf(m, "\t\t txcmpl[%d]:desc_with_more_bit = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].desc_with_more_bit);
++ seq_printf(m, "\t\t txcmpl[%d]:no_pending_desc = %llu\n",
++ i + txcmpl->ring_start, txcmpl_stats[i].no_pending_desc);
++ seq_puts(m, "\n");
++ }
++
++ seq_puts(m, "\n#EDMA TX descriptor rings stats:\n\n");
++ for (i = 0; i < tx->num_rings; i++) {
++ seq_printf(m, "\t\tEDMA TX descriptor %d ring stats:\n", i + tx->ring_start);
++ seq_printf(m, "\t\t txdesc[%d]:no_desc_avail = %llu\n",
++ i + tx->ring_start, txdesc_stats[i].no_desc_avail);
++ seq_printf(m, "\t\t txdesc[%d]:tso_max_seg_exceed = %llu\n",
++ i + tx->ring_start, txdesc_stats[i].tso_max_seg_exceed);
++ seq_puts(m, "\n");
++ }
++
++ kfree(txcmpl_stats);
++ kfree(txdesc_stats);
++ return 0;
++}
++
++static int edma_debugfs_err_stats_show(struct seq_file *m,
++ void __maybe_unused *p)
++{
++ struct edma_err_stats *err_stats, *pcpu_err_stats;
++ unsigned int start;
++ u32 cpu;
++
++ err_stats = kzalloc(sizeof(*err_stats), GFP_KERNEL);
++ if (!err_stats)
++ return -ENOMEM;
++
++ /* Get percpu EDMA miscellaneous stats. */
++ for_each_possible_cpu(cpu) {
++ pcpu_err_stats = per_cpu_ptr(edma_ctx->err_stats, cpu);
++ do {
++ start = u64_stats_fetch_begin(&pcpu_err_stats->syncp);
++ err_stats->edma_axi_read_err +=
++ pcpu_err_stats->edma_axi_read_err;
++ err_stats->edma_axi_write_err +=
++ pcpu_err_stats->edma_axi_write_err;
++ err_stats->edma_rxdesc_fifo_full +=
++ pcpu_err_stats->edma_rxdesc_fifo_full;
++ err_stats->edma_rx_buf_size_err +=
++ pcpu_err_stats->edma_rx_buf_size_err;
++ err_stats->edma_tx_sram_full +=
++ pcpu_err_stats->edma_tx_sram_full;
++ err_stats->edma_tx_data_len_err +=
++ pcpu_err_stats->edma_tx_data_len_err;
++ err_stats->edma_tx_timeout +=
++ pcpu_err_stats->edma_tx_timeout;
++ err_stats->edma_txcmpl_buf_full +=
++ pcpu_err_stats->edma_txcmpl_buf_full;
++ } while (u64_stats_fetch_retry(&pcpu_err_stats->syncp, start));
++ }
++
++ edma_debugfs_print_banner(m, EDMA_ERR_STATS_NODE_NAME);
++
++ seq_puts(m, "\n#EDMA error stats:\n\n");
++ seq_printf(m, "\t\t axi read error = %llu\n",
++ err_stats->edma_axi_read_err);
++ seq_printf(m, "\t\t axi write error = %llu\n",
++ err_stats->edma_axi_write_err);
++ seq_printf(m, "\t\t Rx descriptor fifo full = %llu\n",
++ err_stats->edma_rxdesc_fifo_full);
++ seq_printf(m, "\t\t Rx buffer size error = %llu\n",
++ err_stats->edma_rx_buf_size_err);
++ seq_printf(m, "\t\t Tx SRAM full = %llu\n",
++ err_stats->edma_tx_sram_full);
++ seq_printf(m, "\t\t Tx data length error = %llu\n",
++ err_stats->edma_tx_data_len_err);
++ seq_printf(m, "\t\t Tx timeout = %llu\n",
++ err_stats->edma_tx_timeout);
++ seq_printf(m, "\t\t Tx completion buffer full = %llu\n",
++ err_stats->edma_txcmpl_buf_full);
++
++ kfree(err_stats);
++ return 0;
++}
++
++static int edma_debugs_rx_rings_stats_open(struct inode *inode,
++ struct file *file)
++{
++ return single_open(file, edma_debugfs_rx_rings_stats_show,
++ inode->i_private);
++}
++
++static const struct file_operations edma_debugfs_rx_rings_file_ops = {
++ .open = edma_debugs_rx_rings_stats_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release
++};
++
++static int edma_debugs_tx_rings_stats_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, edma_debugfs_tx_rings_stats_show, inode->i_private);
++}
++
++static const struct file_operations edma_debugfs_tx_rings_file_ops = {
++ .open = edma_debugs_tx_rings_stats_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release
++};
++
++static int edma_debugs_err_stats_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, edma_debugfs_err_stats_show, inode->i_private);
++}
++
++static const struct file_operations edma_debugfs_misc_file_ops = {
++ .open = edma_debugs_err_stats_open,
++ .read = seq_read,
++ .llseek = seq_lseek,
++ .release = seq_release
++};
++
++/**
++ * edma_debugfs_teardown - EDMA debugfs teardown.
++ *
++ * EDMA debugfs teardown and free stats memory.
++ */
++void edma_debugfs_teardown(void)
++{
++ /* Free EDMA miscellaneous stats memory */
++ edma_err_stats_free();
++
++ debugfs_remove_recursive(edma_dentry);
++ edma_dentry = NULL;
++ stats_dentry = NULL;
++}
++
++/**
++ * edma_debugfs_setup - EDMA debugfs setup.
++ * @ppe_dev: PPE Device
++ *
++ * EDMA debugfs setup.
++ */
++int edma_debugfs_setup(struct ppe_device *ppe_dev)
++{
++ edma_dentry = debugfs_create_dir("edma", ppe_dev->debugfs_root);
++ if (!edma_dentry) {
++ pr_err("Unable to create debugfs edma directory in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ stats_dentry = debugfs_create_dir("stats", edma_dentry);
++ if (!stats_dentry) {
++ pr_err("Unable to create debugfs stats directory in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ if (!debugfs_create_file("rx_ring_stats", 0444, stats_dentry,
++ NULL, &edma_debugfs_rx_rings_file_ops)) {
++ pr_err("Unable to create Rx rings statistics file entry in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ if (!debugfs_create_file("tx_ring_stats", 0444, stats_dentry,
++ NULL, &edma_debugfs_tx_rings_file_ops)) {
++ pr_err("Unable to create Tx rings statistics file entry in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ /* Allocate memory for EDMA miscellaneous stats */
++ if (edma_err_stats_alloc() < 0) {
++ pr_err("Unable to allocate miscellaneous percpu stats\n");
++ goto debugfs_dir_failed;
++ }
++
++ if (!debugfs_create_file("err_stats", 0444, stats_dentry,
++ NULL, &edma_debugfs_misc_file_ops)) {
++ pr_err("Unable to create EDMA miscellaneous statistics file entry in debugfs\n");
++ goto debugfs_dir_failed;
++ }
++
++ return 0;
++
++debugfs_dir_failed:
++ debugfs_remove_recursive(edma_dentry);
++ edma_dentry = NULL;
++ stats_dentry = NULL;
++ return -ENOMEM;
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.c
++++ b/drivers/net/ethernet/qualcomm/ppe/ppe_debugfs.c
+@@ -7,9 +7,11 @@
+
+ #include <linux/bitfield.h>
+ #include <linux/debugfs.h>
++#include <linux/netdevice.h>
+ #include <linux/regmap.h>
+ #include <linux/seq_file.h>
+
++#include "edma.h"
+ #include "ppe.h"
+ #include "ppe_config.h"
+ #include "ppe_debugfs.h"
+@@ -678,15 +680,30 @@ static const struct file_operations ppe_
+
+ void ppe_debugfs_setup(struct ppe_device *ppe_dev)
+ {
++ int ret;
++
+ ppe_dev->debugfs_root = debugfs_create_dir("ppe", NULL);
+ debugfs_create_file("packet_counters", 0444,
+ ppe_dev->debugfs_root,
+ ppe_dev,
+ &ppe_debugfs_packet_counter_fops);
++
++ if (!ppe_dev->debugfs_root) {
++ dev_err(ppe_dev->dev, "Error in PPE debugfs setup\n");
++ return;
++ }
++
++ ret = edma_debugfs_setup(ppe_dev);
++ if (ret) {
++ dev_err(ppe_dev->dev, "Error in EDMA debugfs setup API. ret: %d\n", ret);
++ debugfs_remove_recursive(ppe_dev->debugfs_root);
++ ppe_dev->debugfs_root = NULL;
++ }
+ }
+
+ void ppe_debugfs_teardown(struct ppe_device *ppe_dev)
+ {
++ edma_debugfs_teardown();
+ debugfs_remove_recursive(ppe_dev->debugfs_root);
+ ppe_dev->debugfs_root = NULL;
+ }
--- /dev/null
+From bd61a680fb657eb65272225f18c93fe338c700da Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Thu, 30 May 2024 20:46:36 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add ethtool support for EDMA
+
+ethtool ops can be used for EDMA netdevice configuration and statistics.
+
+Change-Id: I57fc19415dacbe51fed000520336463938220609
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Alex G: use struct ethtool_keee instead of ethtool_eee
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/Makefile | 2 +-
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 1 +
+ .../net/ethernet/qualcomm/ppe/edma_ethtool.c | 294 ++++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 1 +
+ 4 files changed, 297 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/ethernet/qualcomm/ppe/edma_ethtool.c
+
+--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
++++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
+@@ -7,4 +7,4 @@ obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
+ qcom-ppe-objs := ppe.o ppe_config.o ppe_debugfs.o ppe_port.o
+
+ #EDMA
+-qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_debugfs.o edma_port.o edma_rx.o edma_tx.o
++qcom-ppe-objs += edma.o edma_cfg_rx.o edma_cfg_tx.o edma_debugfs.o edma_port.o edma_rx.o edma_tx.o edma_ethtool.o
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -161,6 +161,7 @@ void edma_destroy(struct ppe_device *ppe
+ int edma_setup(struct ppe_device *ppe_dev);
+ void edma_debugfs_teardown(void);
+ int edma_debugfs_setup(struct ppe_device *ppe_dev);
++void edma_set_ethtool_ops(struct net_device *netdev);
+ int ppe_edma_queue_offset_config(struct ppe_device *ppe_dev,
+ enum ppe_queue_class_type class,
+ int index, int queue_offset);
+--- /dev/null
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_ethtool.c
+@@ -0,0 +1,294 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++/* ethtool support for EDMA */
++
++#include <linux/cpumask.h>
++#include <linux/ethtool.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/phylink.h>
++
++#include "edma.h"
++#include "edma_port.h"
++
++struct edma_ethtool_stats {
++ u8 stat_string[ETH_GSTRING_LEN];
++ u32 stat_offset;
++};
++
++/**
++ * struct edma_gmac_stats - Per-GMAC statistics.
++ * @rx_packets: Number of RX packets
++ * @rx_bytes: Number of RX bytes
++ * @rx_dropped: Number of RX dropped packets
++ * @rx_fraglist_packets: Number of RX fraglist packets
++ * @rx_nr_frag_packets: Number of RX nr fragment packets
++ * @rx_nr_frag_headroom_err: Number of RX nr fragment packets with headroom error
++ * @tx_packets: Number of TX packets
++ * @tx_bytes: Number of TX bytes
++ * @tx_dropped: Number of TX dropped packets
++ * @tx_nr_frag_packets: Number of TX nr fragment packets
++ * @tx_fraglist_packets: Number of TX fraglist packets
++ * @tx_fraglist_with_nr_frags_packets: Number of TX fraglist packets with nr fragments
++ * @tx_tso_packets: Number of TX TCP segmentation offload packets
++ * @tx_tso_drop_packets: Number of TX TCP segmentation dropped packets
++ * @tx_gso_packets: Number of TX SW GSO packets
++ * @tx_gso_drop_packets: Number of TX SW GSO dropped packets
++ * @tx_queue_stopped: Number of times Queue got stopped
++ */
++struct edma_gmac_stats {
++ u64 rx_packets;
++ u64 rx_bytes;
++ u64 rx_dropped;
++ u64 rx_fraglist_packets;
++ u64 rx_nr_frag_packets;
++ u64 rx_nr_frag_headroom_err;
++ u64 tx_packets;
++ u64 tx_bytes;
++ u64 tx_dropped;
++ u64 tx_nr_frag_packets;
++ u64 tx_fraglist_packets;
++ u64 tx_fraglist_with_nr_frags_packets;
++ u64 tx_tso_packets;
++ u64 tx_tso_drop_packets;
++ u64 tx_gso_packets;
++ u64 tx_gso_drop_packets;
++ u64 tx_queue_stopped[EDMA_MAX_CORE];
++};
++
++#define EDMA_STAT(m) offsetof(struct edma_gmac_stats, m)
++
++static const struct edma_ethtool_stats edma_gstrings_stats[] = {
++ {"rx_bytes", EDMA_STAT(rx_bytes)},
++ {"rx_packets", EDMA_STAT(rx_packets)},
++ {"rx_dropped", EDMA_STAT(rx_dropped)},
++ {"rx_fraglist_packets", EDMA_STAT(rx_fraglist_packets)},
++ {"rx_nr_frag_packets", EDMA_STAT(rx_nr_frag_packets)},
++ {"rx_nr_frag_headroom_err", EDMA_STAT(rx_nr_frag_headroom_err)},
++ {"tx_bytes", EDMA_STAT(tx_bytes)},
++ {"tx_packets", EDMA_STAT(tx_packets)},
++ {"tx_dropped", EDMA_STAT(tx_dropped)},
++ {"tx_nr_frag_packets", EDMA_STAT(tx_nr_frag_packets)},
++ {"tx_fraglist_packets", EDMA_STAT(tx_fraglist_packets)},
++ {"tx_fraglist_nr_frags_packets", EDMA_STAT(tx_fraglist_with_nr_frags_packets)},
++ {"tx_tso_packets", EDMA_STAT(tx_tso_packets)},
++ {"tx_tso_drop_packets", EDMA_STAT(tx_tso_drop_packets)},
++ {"tx_gso_packets", EDMA_STAT(tx_gso_packets)},
++ {"tx_gso_drop_packets", EDMA_STAT(tx_gso_drop_packets)},
++ {"tx_queue_stopped_cpu0", EDMA_STAT(tx_queue_stopped[0])},
++ {"tx_queue_stopped_cpu1", EDMA_STAT(tx_queue_stopped[1])},
++ {"tx_queue_stopped_cpu2", EDMA_STAT(tx_queue_stopped[2])},
++ {"tx_queue_stopped_cpu3", EDMA_STAT(tx_queue_stopped[3])},
++};
++
++#define EDMA_STATS_LEN ARRAY_SIZE(edma_gstrings_stats)
++
++static void edma_port_get_stats(struct net_device *netdev,
++ struct edma_gmac_stats *stats)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct edma_port_rx_stats *pcpu_rx_stats;
++ struct edma_port_tx_stats *pcpu_tx_stats;
++ int i;
++
++ memset(stats, 0, sizeof(struct edma_port_pcpu_stats));
++
++ for_each_possible_cpu(i) {
++ struct edma_port_rx_stats rxp;
++ struct edma_port_tx_stats txp;
++ unsigned int start;
++
++ pcpu_rx_stats = per_cpu_ptr(port_priv->pcpu_stats.rx_stats, i);
++
++ do {
++ start = u64_stats_fetch_begin(&pcpu_rx_stats->syncp);
++ memcpy(&rxp, pcpu_rx_stats, sizeof(*pcpu_rx_stats));
++ } while (u64_stats_fetch_retry(&pcpu_rx_stats->syncp, start));
++
++ stats->rx_packets += rxp.rx_pkts;
++ stats->rx_bytes += rxp.rx_bytes;
++ stats->rx_dropped += rxp.rx_drops;
++ stats->rx_nr_frag_packets += rxp.rx_nr_frag_pkts;
++ stats->rx_fraglist_packets += rxp.rx_fraglist_pkts;
++ stats->rx_nr_frag_headroom_err += rxp.rx_nr_frag_headroom_err;
++
++ pcpu_tx_stats = per_cpu_ptr(port_priv->pcpu_stats.tx_stats, i);
++
++ do {
++ start = u64_stats_fetch_begin(&pcpu_tx_stats->syncp);
++ memcpy(&txp, pcpu_tx_stats, sizeof(*pcpu_tx_stats));
++ } while (u64_stats_fetch_retry(&pcpu_tx_stats->syncp, start));
++
++ stats->tx_packets += txp.tx_pkts;
++ stats->tx_bytes += txp.tx_bytes;
++ stats->tx_dropped += txp.tx_drops;
++ stats->tx_nr_frag_packets += txp.tx_nr_frag_pkts;
++ stats->tx_fraglist_packets += txp.tx_fraglist_pkts;
++ stats->tx_fraglist_with_nr_frags_packets += txp.tx_fraglist_with_nr_frags_pkts;
++ stats->tx_tso_packets += txp.tx_tso_pkts;
++ stats->tx_tso_drop_packets += txp.tx_tso_drop_pkts;
++ stats->tx_gso_packets += txp.tx_gso_pkts;
++ stats->tx_gso_drop_packets += txp.tx_gso_drop_pkts;
++ stats->tx_queue_stopped[i] += txp.tx_queue_stopped[i];
++ }
++}
++
++static void edma_get_ethtool_stats(struct net_device *netdev,
++ __maybe_unused struct ethtool_stats *stats,
++ u64 *data)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct edma_gmac_stats edma_stats;
++ u64 *mib_data;
++ int i;
++ u8 *p;
++
++ if (!port_priv)
++ return;
++
++ /* Get the DMA Driver statistics from the data plane if available. */
++ memset(&edma_stats, 0, sizeof(struct edma_gmac_stats));
++ edma_port_get_stats(netdev, &edma_stats);
++
++ /* Populate data plane statistics. */
++ for (i = 0; i < EDMA_STATS_LEN; i++) {
++ p = ((u8 *)(&edma_stats) + edma_gstrings_stats[i].stat_offset);
++ data[i] = *(u64 *)p;
++ }
++
++ /* Get the GMAC MIB statistics along with the DMA driver statistics. */
++ mib_data = &data[EDMA_STATS_LEN];
++ ppe_port_get_ethtool_stats(port_priv->ppe_port, mib_data);
++}
++
++static int edma_get_strset_count(struct net_device *netdev, int sset)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ int sset_count = 0;
++
++ if (!port_priv || sset != ETH_SS_STATS)
++ return 0;
++
++ sset_count = ppe_port_get_sset_count(port_priv->ppe_port, sset);
++
++ return (EDMA_STATS_LEN + sset_count);
++}
++
++static void edma_get_strings(struct net_device *netdev, u32 stringset,
++ u8 *data)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ int i;
++
++ if (!port_priv || stringset != ETH_SS_STATS)
++ return;
++
++ for (i = 0; i < EDMA_STATS_LEN; i++) {
++ memcpy(data, edma_gstrings_stats[i].stat_string,
++ strlen(edma_gstrings_stats[i].stat_string));
++ data += ETH_GSTRING_LEN;
++ }
++
++ ppe_port_get_strings(port_priv->ppe_port, stringset, data);
++}
++
++static int edma_get_link_ksettings(struct net_device *netdev,
++ struct ethtool_link_ksettings *cmd)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_ksettings_get(port->phylink, cmd);
++}
++
++static int edma_set_link_ksettings(struct net_device *netdev,
++ const struct ethtool_link_ksettings *cmd)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_ksettings_set(port->phylink, cmd);
++}
++
++static void edma_get_pauseparam(struct net_device *netdev,
++ struct ethtool_pauseparam *pause)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return;
++
++ phylink_ethtool_get_pauseparam(port->phylink, pause);
++}
++
++static int edma_set_pauseparam(struct net_device *netdev,
++ struct ethtool_pauseparam *pause)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_set_pauseparam(port->phylink, pause);
++}
++
++static int edma_get_eee(struct net_device *netdev, struct ethtool_keee *eee)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ return phylink_ethtool_get_eee(port->phylink, eee);
++}
++
++static int edma_set_eee(struct net_device *netdev, struct ethtool_keee *eee)
++{
++ struct edma_port_priv *port_priv = (struct edma_port_priv *)netdev_priv(netdev);
++ struct ppe_port *port = port_priv->ppe_port;
++ int ret;
++
++ if (!port_priv)
++ return -EINVAL;
++
++ ret = ppe_port_set_mac_eee(port_priv->ppe_port, eee);
++ if (ret)
++ return ret;
++
++ return phylink_ethtool_set_eee(port->phylink, eee);
++}
++
++static const struct ethtool_ops edma_ethtool_ops = {
++ .get_strings = &edma_get_strings,
++ .get_sset_count = &edma_get_strset_count,
++ .get_ethtool_stats = &edma_get_ethtool_stats,
++ .get_link = ðtool_op_get_link,
++ .get_link_ksettings = edma_get_link_ksettings,
++ .set_link_ksettings = edma_set_link_ksettings,
++ .get_pauseparam = &edma_get_pauseparam,
++ .set_pauseparam = &edma_set_pauseparam,
++ .get_eee = &edma_get_eee,
++ .set_eee = &edma_set_eee,
++};
++
++/**
++ * edma_set_ethtool_ops - Set ethtool operations
++ * @netdev: Netdevice
++ *
++ * Set ethtool operations.
++ */
++void edma_set_ethtool_ops(struct net_device *netdev)
++{
++ netdev->ethtool_ops = &edma_ethtool_ops;
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -380,6 +380,7 @@ int edma_port_setup(struct ppe_port *por
+ netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
+ netdev->netdev_ops = &edma_port_netdev_ops;
+ netdev->gso_max_segs = GSO_MAX_SEGS;
++ edma_set_ethtool_ops(netdev);
+
+ maddr = mac_addr;
+ if (of_get_mac_address(np, maddr))
--- /dev/null
+From 2ecec7e47e269e05cdd393c34aae51d4866070c6 Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Tue, 11 Jun 2024 00:00:46 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add module parameters for driver
+ tunings
+
+Add module params and corresponding functionality for Tx/Rx
+mitigation timer/packet count, napi budget and tx requeue stop.
+
+Change-Id: I1717559c931bba4f355ee06ab89f289818400ca2
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 35 +++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.c | 29 +++++++++++++--
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.h | 21 +++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.c | 29 +++++++++++++--
+ .../net/ethernet/qualcomm/ppe/edma_cfg_tx.h | 16 +++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma_rx.h | 4 +++
+ drivers/net/ethernet/qualcomm/ppe/edma_tx.h | 4 +++
+ 7 files changed, 134 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -38,6 +38,38 @@ static int rx_buff_size;
+ module_param(rx_buff_size, int, 0640);
+ MODULE_PARM_DESC(rx_buff_size, "Rx Buffer size for Jumbo MRU value (default:0)");
+
++int edma_rx_napi_budget = EDMA_RX_NAPI_WORK_DEF;
++module_param(edma_rx_napi_budget, int, 0444);
++MODULE_PARM_DESC(edma_rx_napi_budget, "Rx NAPI budget (default:128, min:16, max:512)");
++
++int edma_tx_napi_budget = EDMA_TX_NAPI_WORK_DEF;
++module_param(edma_tx_napi_budget, int, 0444);
++MODULE_PARM_DESC(edma_tx_napi_budget, "Tx NAPI budget (default:512 for ipq95xx, min:16, max:512)");
++
++int edma_rx_mitigation_pkt_cnt = EDMA_RX_MITIGATION_PKT_CNT_DEF;
++module_param(edma_rx_mitigation_pkt_cnt, int, 0444);
++MODULE_PARM_DESC(edma_rx_mitigation_pkt_cnt,
++ "Rx mitigation packet count value (default:16, min:0, max: 256)");
++
++s32 edma_rx_mitigation_timer = EDMA_RX_MITIGATION_TIMER_DEF;
++module_param(edma_rx_mitigation_timer, int, 0444);
++MODULE_PARM_DESC(edma_dp_rx_mitigation_timer,
++ "Rx mitigation timer value in microseconds (default:25, min:0, max: 1000)");
++
++int edma_tx_mitigation_timer = EDMA_TX_MITIGATION_TIMER_DEF;
++module_param(edma_tx_mitigation_timer, int, 0444);
++MODULE_PARM_DESC(edma_tx_mitigation_timer,
++ "Tx mitigation timer value in microseconds (default:250, min:0, max: 1000)");
++
++int edma_tx_mitigation_pkt_cnt = EDMA_TX_MITIGATION_PKT_CNT_DEF;
++module_param(edma_tx_mitigation_pkt_cnt, int, 0444);
++MODULE_PARM_DESC(edma_tx_mitigation_pkt_cnt,
++ "Tx mitigation packet count value (default:16, min:0, max: 256)");
++
++static int tx_requeue_stop;
++module_param(tx_requeue_stop, int, 0640);
++MODULE_PARM_DESC(tx_requeue_stop, "Disable Tx requeue function (default:0)");
++
+ /* Priority to multi-queue mapping. */
+ static u8 edma_pri_map[PPE_QUEUE_INTER_PRI_NUM] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7};
+@@ -828,7 +860,10 @@ int edma_setup(struct ppe_device *ppe_de
+ edma_ctx->hw_info = &ipq9574_hw_info;
+ edma_ctx->ppe_dev = ppe_dev;
+ edma_ctx->rx_buf_size = rx_buff_size;
++
+ edma_ctx->tx_requeue_stop = false;
++ if (tx_requeue_stop != 0)
++ edma_ctx->tx_requeue_stop = true;
+
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+@@ -166,6 +166,24 @@ static void edma_cfg_rx_desc_ring_config
+ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_RING_SIZE(rxdesc_ring->ring_id);
+ regmap_write(regmap, reg, data);
+
++ /* Validate mitigation timer value */
++ if (edma_rx_mitigation_timer < EDMA_RX_MITIGATION_TIMER_MIN ||
++ edma_rx_mitigation_timer > EDMA_RX_MITIGATION_TIMER_MAX) {
++ pr_err("Invalid Rx mitigation timer configured:%d for ring:%d. Using the default timer value:%d\n",
++ edma_rx_mitigation_timer, rxdesc_ring->ring_id,
++ EDMA_RX_MITIGATION_TIMER_DEF);
++ edma_rx_mitigation_timer = EDMA_RX_MITIGATION_TIMER_DEF;
++ }
++
++ /* Validate mitigation packet count value */
++ if (edma_rx_mitigation_pkt_cnt < EDMA_RX_MITIGATION_PKT_CNT_MIN ||
++ edma_rx_mitigation_pkt_cnt > EDMA_RX_MITIGATION_PKT_CNT_MAX) {
++ pr_err("Invalid Rx mitigation packet count configured:%d for ring:%d. Using the default packet counter value:%d\n",
++ edma_rx_mitigation_timer, rxdesc_ring->ring_id,
++ EDMA_RX_MITIGATION_PKT_CNT_DEF);
++ edma_rx_mitigation_pkt_cnt = EDMA_RX_MITIGATION_PKT_CNT_DEF;
++ }
++
+ /* Configure the Mitigation timer */
+ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_RX_MITIGATION_TIMER_DEF,
+ ppe_dev->clk_rate / MHZ);
+@@ -176,7 +194,7 @@ static void edma_cfg_rx_desc_ring_config
+ regmap_write(regmap, reg, data);
+
+ /* Configure the Mitigation packet count */
+- data = (EDMA_RX_MITIGATION_PKT_CNT_DEF & EDMA_RXDESC_LOW_THRE_MASK)
++ data = (edma_rx_mitigation_pkt_cnt & EDMA_RXDESC_LOW_THRE_MASK)
+ << EDMA_RXDESC_LOW_THRE_SHIFT;
+ pr_debug("EDMA Rx mitigation packet count value: %d\n", data);
+ reg = EDMA_BASE_OFFSET + EDMA_REG_RXDESC_UGT_THRE(rxdesc_ring->ring_id);
+@@ -915,6 +933,13 @@ void edma_cfg_rx_napi_add(void)
+ struct edma_ring_info *rx = hw_info->rx;
+ u32 i;
+
++ if (edma_rx_napi_budget < EDMA_RX_NAPI_WORK_MIN ||
++ edma_rx_napi_budget > EDMA_RX_NAPI_WORK_MAX) {
++ pr_err("Incorrect Rx NAPI budget: %d, setting to default: %d",
++ edma_rx_napi_budget, hw_info->napi_budget_rx);
++ edma_rx_napi_budget = hw_info->napi_budget_rx;
++ }
++
+ for (i = 0; i < rx->num_rings; i++) {
+ struct edma_rxdesc_ring *rxdesc_ring = &edma_ctx->rx_rings[i];
+
+@@ -923,7 +948,7 @@ void edma_cfg_rx_napi_add(void)
+ rxdesc_ring->napi_added = true;
+ }
+
+- netdev_dbg(edma_ctx->dummy_dev, "Rx NAPI budget: %d\n", hw_info->napi_budget_rx);
++ netdev_dbg(edma_ctx->dummy_dev, "Rx NAPI budget: %d\n", edma_rx_napi_budget);
+ }
+
+ /**
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+@@ -5,6 +5,15 @@
+ #ifndef __EDMA_CFG_RX__
+ #define __EDMA_CFG_RX__
+
++/* Rx default NAPI budget */
++#define EDMA_RX_NAPI_WORK_DEF 128
++
++/* RX minimum NAPI budget */
++#define EDMA_RX_NAPI_WORK_MIN 16
++
++/* Rx maximum NAPI budget */
++#define EDMA_RX_NAPI_WORK_MAX 512
++
+ /* SKB payload size used in page mode */
+ #define EDMA_RX_PAGE_MODE_SKB_SIZE 256
+
+@@ -22,9 +31,21 @@
+ /* Rx mitigation timer's default value in microseconds */
+ #define EDMA_RX_MITIGATION_TIMER_DEF 25
+
++/* Rx mitigation timer's minimum value in microseconds */
++#define EDMA_RX_MITIGATION_TIMER_MIN 0
++
++/* Rx mitigation timer's maximum value in microseconds */
++#define EDMA_RX_MITIGATION_TIMER_MAX 1000
++
+ /* Rx mitigation packet count's default value */
+ #define EDMA_RX_MITIGATION_PKT_CNT_DEF 16
+
++/* Rx mitigation packet count's minimum value */
++#define EDMA_RX_MITIGATION_PKT_CNT_MIN 0
++
++/* Rx mitigation packet count's maximum value */
++#define EDMA_RX_MITIGATION_PKT_CNT_MAX 256
++
+ /* Default bitmap of cores for RPS to ARM cores */
+ #define EDMA_RX_DEFAULT_BITMAP ((1 << EDMA_MAX_CORE) - 1)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.c
+@@ -170,6 +170,24 @@ static void edma_cfg_txcmpl_ring_configu
+ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_CTRL(txcmpl_ring->id);
+ regmap_write(regmap, reg, EDMA_TXCMPL_RETMODE_OPAQUE);
+
++ /* Validate mitigation timer value */
++ if (edma_tx_mitigation_timer < EDMA_TX_MITIGATION_TIMER_MIN ||
++ edma_tx_mitigation_timer > EDMA_TX_MITIGATION_TIMER_MAX) {
++ pr_err("Invalid Tx mitigation timer configured:%d for ring:%d. Using the default timer value:%d\n",
++ edma_tx_mitigation_timer, txcmpl_ring->id,
++ EDMA_TX_MITIGATION_TIMER_DEF);
++ edma_tx_mitigation_timer = EDMA_TX_MITIGATION_TIMER_DEF;
++ }
++
++ /* Validate mitigation packet count value */
++ if (edma_tx_mitigation_pkt_cnt < EDMA_TX_MITIGATION_PKT_CNT_MIN ||
++ edma_tx_mitigation_pkt_cnt > EDMA_TX_MITIGATION_PKT_CNT_MAX) {
++ pr_err("Invalid Tx mitigation packet count configured:%d for ring:%d. Using the default packet counter value:%d\n",
++ edma_tx_mitigation_timer, txcmpl_ring->id,
++ EDMA_TX_MITIGATION_PKT_CNT_DEF);
++ edma_tx_mitigation_pkt_cnt = EDMA_TX_MITIGATION_PKT_CNT_DEF;
++ }
++
+ /* Configure the Mitigation timer. */
+ data = EDMA_MICROSEC_TO_TIMER_UNIT(EDMA_TX_MITIGATION_TIMER_DEF,
+ ppe_dev->clk_rate / MHZ);
+@@ -180,7 +198,7 @@ static void edma_cfg_txcmpl_ring_configu
+ regmap_write(regmap, reg, data);
+
+ /* Configure the Mitigation packet count. */
+- data = (EDMA_TX_MITIGATION_PKT_CNT_DEF & EDMA_TXCMPL_LOW_THRE_MASK)
++ data = (edma_tx_mitigation_pkt_cnt & EDMA_TXCMPL_LOW_THRE_MASK)
+ << EDMA_TXCMPL_LOW_THRE_SHIFT;
+ pr_debug("EDMA Tx mitigation packet count value: %d\n", data);
+ reg = EDMA_BASE_OFFSET + EDMA_REG_TXCMPL_UGT_THRE(txcmpl_ring->id);
+@@ -634,6 +652,13 @@ void edma_cfg_tx_napi_add(struct net_dev
+ struct edma_txcmpl_ring *txcmpl_ring;
+ u32 i, ring_idx;
+
++ if (edma_tx_napi_budget < EDMA_TX_NAPI_WORK_MIN ||
++ edma_tx_napi_budget > EDMA_TX_NAPI_WORK_MAX) {
++ pr_err("Incorrect Tx NAPI budget: %d, setting to default: %d",
++ edma_tx_napi_budget, hw_info->napi_budget_tx);
++ edma_tx_napi_budget = hw_info->napi_budget_tx;
++ }
++
+ /* Adding tx napi for a interface with each queue. */
+ for_each_possible_cpu(i) {
+ ring_idx = ((port_id - 1) * num_possible_cpus()) + i;
+@@ -644,5 +669,5 @@ void edma_cfg_tx_napi_add(struct net_dev
+ netdev_dbg(netdev, "Napi added for txcmpl ring: %u\n", txcmpl_ring->id);
+ }
+
+- netdev_dbg(netdev, "Tx NAPI budget: %d\n", hw_info->napi_budget_tx);
++ netdev_dbg(netdev, "Tx NAPI budget: %d\n", edma_tx_napi_budget);
+ }
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_tx.h
+@@ -5,12 +5,28 @@
+ #ifndef __EDMA_CFG_TX__
+ #define __EDMA_CFG_TX__
+
++#define EDMA_TX_NAPI_WORK_DEF 512
++#define EDMA_TX_NAPI_WORK_MIN 16
++#define EDMA_TX_NAPI_WORK_MAX 512
++
+ /* Tx mitigation timer's default value. */
+ #define EDMA_TX_MITIGATION_TIMER_DEF 250
+
++/* Tx mitigation timer's minimum value in microseconds */
++#define EDMA_TX_MITIGATION_TIMER_MIN 0
++
++/* Tx mitigation timer's maximum value in microseconds */
++#define EDMA_TX_MITIGATION_TIMER_MAX 1000
++
+ /* Tx mitigation packet count default value. */
+ #define EDMA_TX_MITIGATION_PKT_CNT_DEF 16
+
++/* Tx mitigation packet count's minimum value */
++#define EDMA_TX_MITIGATION_PKT_CNT_MIN 0
++
++/* Tx mitigation packet count's maximum value */
++#define EDMA_TX_MITIGATION_PKT_CNT_MAX 256
++
+ void edma_cfg_tx_rings(void);
+ int edma_cfg_tx_rings_alloc(void);
+ void edma_cfg_tx_rings_cleanup(void);
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_rx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_rx.h
+@@ -281,6 +281,10 @@ struct edma_rxdesc_ring {
+ struct sk_buff *last;
+ };
+
++extern int edma_rx_napi_budget;
++extern int edma_rx_mitigation_timer;
++extern int edma_rx_mitigation_pkt_cnt;
++
+ irqreturn_t edma_rx_handle_irq(int irq, void *ctx);
+ int edma_rx_alloc_buffer(struct edma_rxfill_ring *rxfill_ring, int alloc_count);
+ int edma_rx_napi_poll(struct napi_struct *napi, int budget);
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_tx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_tx.h
+@@ -288,6 +288,10 @@ struct edma_txcmpl_ring {
+ bool napi_added;
+ };
+
++extern int edma_tx_napi_budget;
++extern int edma_tx_mitigation_timer;
++extern int edma_tx_mitigation_pkt_cnt;
++
+ enum edma_tx_status edma_tx_ring_xmit(struct net_device *netdev,
+ struct sk_buff *skb,
+ struct edma_txdesc_ring *txdesc_ring,
--- /dev/null
+From dcac735a715c13a817d65ae371564cf2793330b2 Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Tue, 11 Jun 2024 01:43:22 +0530
+Subject: [PATCH] net: ethernet: qualcomm: Add sysctl for RPS bitmap
+
+Add sysctl to configure RPS bitmap for EDMA receive.
+This bitmap is used to configure the set of ARM cores
+used to receive packets from EDMA.
+
+Change-Id: Ie0e7d5971db93ea1494608a9e79c4abb13ce69b6
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+Alex G: Use **const** ctl_table argument for .proc_handler
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/edma.c | 23 ++++++++++++++++
+ drivers/net/ethernet/qualcomm/ppe/edma.h | 2 ++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.c | 27 +++++++++++++++++++
+ .../net/ethernet/qualcomm/ppe/edma_cfg_rx.h | 6 ++++-
+ 4 files changed, 57 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.c
+@@ -797,6 +797,11 @@ void edma_destroy(struct ppe_device *ppe
+ struct edma_ring_info *rx = hw_info->rx;
+ u32 i;
+
++ if (edma_ctx->rx_rps_ctl_table_hdr) {
++ unregister_sysctl_table(edma_ctx->rx_rps_ctl_table_hdr);
++ edma_ctx->rx_rps_ctl_table_hdr = NULL;
++ }
++
+ /* Disable interrupts. */
+ for (i = 1; i <= hw_info->max_ports; i++)
+ edma_cfg_tx_disable_interrupts(i);
+@@ -840,6 +845,17 @@ void edma_destroy(struct ppe_device *ppe
+ kfree(edma_ctx->netdev_arr);
+ }
+
++/* EDMA Rx RPS core sysctl table */
++static struct ctl_table edma_rx_rps_core_table[] = {
++ {
++ .procname = "rps_bitmap_cores",
++ .data = &edma_cfg_rx_rps_bitmap_cores,
++ .maxlen = sizeof(int),
++ .mode = 0644,
++ .proc_handler = edma_cfg_rx_rps_bitmap
++ },
++};
++
+ /**
+ * edma_setup - EDMA Setup.
+ * @ppe_dev: PPE device
+@@ -865,6 +881,13 @@ int edma_setup(struct ppe_device *ppe_de
+ if (tx_requeue_stop != 0)
+ edma_ctx->tx_requeue_stop = true;
+
++ edma_ctx->rx_rps_ctl_table_hdr = register_sysctl("net/edma",
++ edma_rx_rps_core_table);
++ if (!edma_ctx->rx_rps_ctl_table_hdr) {
++ pr_err("Rx rps sysctl table configuration failed\n");
++ return -EINVAL;
++ }
++
+ /* Configure the EDMA common clocks. */
+ ret = edma_clock_init();
+ if (ret) {
+--- a/drivers/net/ethernet/qualcomm/ppe/edma.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma.h
+@@ -132,6 +132,7 @@ struct edma_intr_info {
+ * @tx_rings: Tx Descriptor Ring, SW is producer
+ * @txcmpl_rings: Tx complete Ring, SW is consumer
+ * @err_stats: Per CPU error statistics
++ * @rx_rps_ctl_table_hdr: Rx RPS sysctl table
+ * @rx_page_mode: Page mode enabled or disabled
+ * @rx_buf_size: Rx buffer size for Jumbo MRU
+ * @tx_requeue_stop: Tx requeue stop enabled or disabled
+@@ -147,6 +148,7 @@ struct edma_context {
+ struct edma_txdesc_ring *tx_rings;
+ struct edma_txcmpl_ring *txcmpl_rings;
+ struct edma_err_stats __percpu *err_stats;
++ struct ctl_table_header *rx_rps_ctl_table_hdr;
+ u32 rx_page_mode;
+ u32 rx_buf_size;
+ bool tx_requeue_stop;
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.c
+@@ -43,6 +43,8 @@ static u32 edma_rx_ring_queue_map[][EDMA
+ { 6, 14, 22, 30 },
+ { 7, 15, 23, 31 }};
+
++u32 edma_cfg_rx_rps_bitmap_cores = EDMA_RX_DEFAULT_BITMAP;
++
+ static int edma_cfg_rx_desc_rings_reset_queue_mapping(void)
+ {
+ struct edma_hw_info *hw_info = edma_ctx->hw_info;
+@@ -987,3 +989,28 @@ int edma_cfg_rx_rps_hash_map(void)
+
+ return 0;
+ }
++
++/* Configure RPS hash mapping based on bitmap */
++int edma_cfg_rx_rps_bitmap(const struct ctl_table *table, int write,
++ void *buffer, size_t *lenp, loff_t *ppos)
++{
++ int ret;
++
++ ret = proc_dointvec(table, write, buffer, lenp, ppos);
++
++ if (!write)
++ return ret;
++
++ if (!edma_cfg_rx_rps_bitmap_cores ||
++ edma_cfg_rx_rps_bitmap_cores > EDMA_RX_DEFAULT_BITMAP) {
++ pr_warn("Incorrect CPU bitmap: %x. Setting it to default value: %d",
++ edma_cfg_rx_rps_bitmap_cores, EDMA_RX_DEFAULT_BITMAP);
++ edma_cfg_rx_rps_bitmap_cores = EDMA_RX_DEFAULT_BITMAP;
++ }
++
++ ret = edma_cfg_rx_rps_hash_map();
++
++ pr_info("EDMA RPS bitmap value: %d\n", edma_cfg_rx_rps_bitmap_cores);
++
++ return ret;
++}
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_cfg_rx.h
+@@ -49,6 +49,8 @@
+ /* Default bitmap of cores for RPS to ARM cores */
+ #define EDMA_RX_DEFAULT_BITMAP ((1 << EDMA_MAX_CORE) - 1)
+
++extern u32 edma_cfg_rx_rps_bitmap_cores;
++
+ int edma_cfg_rx_rings(void);
+ int edma_cfg_rx_rings_alloc(void);
+ void edma_cfg_rx_ring_mappings(void);
+@@ -64,6 +66,8 @@ void edma_cfg_rx_rings_enable(void);
+ void edma_cfg_rx_rings_disable(void);
+ void edma_cfg_rx_buff_size_setup(void);
+ int edma_cfg_rx_rps_hash_map(void);
+-int edma_cfg_rx_rps(struct ctl_table *table, int write,
++int edma_cfg_rx_rps(const struct ctl_table *table, int write,
+ void *buffer, size_t *lenp, loff_t *ppos);
++int edma_cfg_rx_rps_bitmap(const struct ctl_table *table, int write,
++ void *buffer, size_t *lenp, loff_t *ppos);
+ #endif
--- /dev/null
+From a809433c9b6a418dd886f12a5dcb3376f73bf2a7 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 4 Dec 2024 01:37:05 +0100
+Subject: [PATCH] net: ethernet: qualcomm: Add support for label property for
+ EDMA port
+
+Add support for label property for EDMA port. This is useful to define
+custom name in DTS for specific ethernet port instead of assigning a
+dynamic name at runtime.
+
+This also improve the log output by using modern APIs.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/ppe/edma_port.c | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/ethernet/qualcomm/ppe/edma_port.c
++++ b/drivers/net/ethernet/qualcomm/ppe/edma_port.c
+@@ -355,13 +355,25 @@ int edma_port_setup(struct ppe_port *por
+ int port_id = port->port_id;
+ struct net_device *netdev;
+ u8 mac_addr[ETH_ALEN];
++ const char *name;
++ int assign_type;
+ int ret = 0;
+ u8 *maddr;
+
+- netdev = alloc_etherdev_mqs(sizeof(struct edma_port_priv),
+- EDMA_NETDEV_QUEUE_NUM, EDMA_NETDEV_QUEUE_NUM);
++ name = of_get_property(np, "label", NULL);
++ if (name) {
++ assign_type = NET_NAME_PREDICTABLE;
++ } else {
++ name = "eth%d";
++ assign_type = NET_NAME_ENUM;
++ }
++
++ netdev = alloc_netdev_mqs(sizeof(struct edma_port_priv),
++ name, assign_type,
++ ether_setup,
++ EDMA_NETDEV_QUEUE_NUM, EDMA_NETDEV_QUEUE_NUM);
+ if (!netdev) {
+- pr_err("alloc_etherdev() failed\n");
++ dev_err(ppe_dev->dev, "alloc_netdev_mqs() failed\n");
+ return -ENOMEM;
+ }
+
--- /dev/null
+From 9c4ad75f17788a64c1e37d0b9e19ca157e01c80a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 9 Dec 2024 18:19:06 +0100
+Subject: [PATCH] net: ethernet: qualcomm: ppe: Fix unmet dependency with
+ QCOM_PPE
+
+Fix unmet dependency with QCOM_PPE on selecting SFP.
+
+WARNING: unmet direct dependencies detected for SFP
+ Depends on [m]: NETDEVICES [=y] && PHYLIB [=y] && I2C [=y] && PHYLINK [=y] && (HWMON [=m] || HWMON [=m]=n [=n])
+ Selected by [y]:
+ - QCOM_PPE [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_QUALCOMM [=y] && HAS_IOMEM [=y] && OF [=y] && COMMON_CLK [=y]
+
+This permit correct compilation of the modules with SFP enabled.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -68,7 +68,6 @@ config QCOM_PPE
+ select REGMAP_MMIO
+ select PHYLINK
+ select PCS_QCOM_IPQ_UNIPHY
+- select SFP
+ help
+ This driver supports the Qualcomm Technologies, Inc. packet
+ process engine (PPE) available with IPQ SoC. The PPE includes
--- /dev/null
+From ac41b401d274a4004027fa4000d801cd28c51f4c Mon Sep 17 00:00:00 2001
+From: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+Date: Tue, 13 May 2025 13:41:37 -0500
+Subject: [PATCH] net: ethernet: qualcomm: ppe: select correct PCS dependency
+
+The config symbol for the PCS driver has changed to PCS_QCOM_IPQ9574,
+since the original submission. Update Kconfig accordingly.
+
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/ethernet/qualcomm/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/qualcomm/Kconfig
++++ b/drivers/net/ethernet/qualcomm/Kconfig
+@@ -67,7 +67,7 @@ config QCOM_PPE
+ depends on COMMON_CLK
+ select REGMAP_MMIO
+ select PHYLINK
+- select PCS_QCOM_IPQ_UNIPHY
++ select PCS_QCOM_IPQ9574
+ help
+ This driver supports the Qualcomm Technologies, Inc. packet
+ process engine (PPE) available with IPQ SoC. The PPE includes
--- /dev/null
+From bbf706ecfd4295d73c8217d5220573dd51d7a081 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Fri, 1 Mar 2024 14:46:45 +0800
+Subject: [PATCH] arm64: dts: qcom: Add IPQ9574 PPE base device node
+
+PPE is the packet process engine on the Qualcomm IPQ platform,
+which is connected with the external switch or PHY device via
+the UNIPHY (PCS).
+
+Change-Id: I254bd48c218aa4eab54f697a2ad149f5a93b682c
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Add "qcom_ppe" label to PPE node
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 39 +++++++++++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -13,6 +13,7 @@
+ #include <dt-bindings/interconnect/qcom,ipq9574.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/reset/qcom,ipq9574-gcc.h>
++#include <dt-bindings/reset/qcom,ipq9574-nsscc.h>
+ #include <dt-bindings/thermal/thermal.h>
+
+ / {
+@@ -1271,6 +1272,44 @@
+ #interconnect-cells = <1>;
+ };
+
++ qcom_ppe: ethernet@3a000000 {
++ compatible = "qcom,ipq9574-ppe";
++ reg = <0x3a000000 0xbef800>;
++ ranges;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ clocks = <&nsscc NSS_CC_PPE_SWITCH_CLK>,
++ <&nsscc NSS_CC_PPE_SWITCH_CFG_CLK>,
++ <&nsscc NSS_CC_PPE_SWITCH_IPE_CLK>,
++ <&nsscc NSS_CC_PPE_SWITCH_BTQ_CLK>;
++ clock-names = "ppe",
++ "ppe_cfg",
++ "ppe_ipe",
++ "ppe_btq";
++ resets = <&nsscc PPE_FULL_RESET>;
++ interconnects = <&nsscc MASTER_NSSNOC_PPE
++ &nsscc SLAVE_NSSNOC_PPE>,
++ <&nsscc MASTER_NSSNOC_PPE_CFG
++ &nsscc SLAVE_NSSNOC_PPE_CFG>,
++ <&gcc MASTER_NSSNOC_QOSGEN_REF
++ &gcc SLAVE_NSSNOC_QOSGEN_REF>,
++ <&gcc MASTER_NSSNOC_TIMEOUT_REF
++ &gcc SLAVE_NSSNOC_TIMEOUT_REF>,
++ <&gcc MASTER_MEM_NOC_NSSNOC
++ &gcc SLAVE_MEM_NOC_NSSNOC>,
++ <&gcc MASTER_NSSNOC_MEMNOC
++ &gcc SLAVE_NSSNOC_MEMNOC>,
++ <&gcc MASTER_NSSNOC_MEM_NOC_1
++ &gcc SLAVE_NSSNOC_MEM_NOC_1>;
++ interconnect-names = "ppe",
++ "ppe_cfg",
++ "qos_gen",
++ "timeout_ref",
++ "nssnoc_memnoc",
++ "memnoc_nssnoc",
++ "memnoc_nssnoc_1";
++ };
++
+ pcs0: ethernet-pcs@7a00000 {
+ compatible = "qcom,ipq9574-pcs";
+ reg = <0x7a00000 0x10000>;
--- /dev/null
+From bd50babc7db2a35d98236a0386173dccd6c6374b Mon Sep 17 00:00:00 2001
+From: Pavithra R <quic_pavir@quicinc.com>
+Date: Wed, 6 Mar 2024 22:29:41 +0530
+Subject: [PATCH] arm64: dts: qcom: Add EDMA node for IPQ9574
+
+Add EDMA (Ethernet DMA) device tree node for IPQ9574 to
+enable ethernet support.
+
+Change-Id: I87d7c50f2485c8670948dce305000337f6499f8b
+Signed-off-by: Pavithra R <quic_pavir@quicinc.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 68 +++++++++++++++++++++++++++
+ 1 file changed, 68 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -1308,6 +1308,74 @@
+ "nssnoc_memnoc",
+ "memnoc_nssnoc",
+ "memnoc_nssnoc_1";
++
++ edma {
++ compatible = "qcom,ipq9574-edma";
++ clocks = <&nsscc NSS_CC_PPE_EDMA_CLK>,
++ <&nsscc NSS_CC_PPE_EDMA_CFG_CLK>;
++ clock-names = "edma",
++ "edma-cfg";
++ resets = <&nsscc EDMA_HW_RESET>;
++ reset-names = "edma_rst";
++ interrupts = <GIC_SPI 371 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 372 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 373 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 374 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 375 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 376 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 377 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 378 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 379 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 380 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 381 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 382 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 383 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 384 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 505 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 504 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 503 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 502 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 501 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 500 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 352 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 353 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 354 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 499 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "edma_txcmpl_8",
++ "edma_txcmpl_9",
++ "edma_txcmpl_10",
++ "edma_txcmpl_11",
++ "edma_txcmpl_12",
++ "edma_txcmpl_13",
++ "edma_txcmpl_14",
++ "edma_txcmpl_15",
++ "edma_txcmpl_16",
++ "edma_txcmpl_17",
++ "edma_txcmpl_18",
++ "edma_txcmpl_19",
++ "edma_txcmpl_20",
++ "edma_txcmpl_21",
++ "edma_txcmpl_22",
++ "edma_txcmpl_23",
++ "edma_txcmpl_24",
++ "edma_txcmpl_25",
++ "edma_txcmpl_26",
++ "edma_txcmpl_27",
++ "edma_txcmpl_28",
++ "edma_txcmpl_29",
++ "edma_txcmpl_30",
++ "edma_txcmpl_31",
++ "edma_rxdesc_20",
++ "edma_rxdesc_21",
++ "edma_rxdesc_22",
++ "edma_rxdesc_23",
++ "edma_misc";
++ };
+ };
+
+ pcs0: ethernet-pcs@7a00000 {
--- /dev/null
+From 001b663ecc5f838dac143623badae0e472749d8a Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Tue, 14 May 2024 10:53:27 +0800
+Subject: [PATCH] arm64: dts: qcom: Add IPQ9574 RDP433 port node
+
+There are 6 PPE MAC ports available on RDP433. The port1-port4 are
+connected with QCA8075 QUAD PHYs through UNIPHY0 PCS channel0-channel3.
+The port5 is connected with Aquantia PHY through UNIPHY1 PCS channel0
+and the port6 is connected with Aquantia PHY through UNIPHY2 PCS
+channel0.
+
+Change-Id: Ic16efdef2fe2cff7b1e80245619c0f82afb24cb9
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts | 167 ++++++++++++++++++++
+ 1 file changed, 167 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
+@@ -55,6 +55,46 @@
+ status = "okay";
+ };
+
++&mdio {
++ reset-gpios = <&tlmm 60 GPIO_ACTIVE_LOW>;
++ clock-frequency = <6250000>;
++ status = "okay";
++
++ ethernet-phy-package@0 {
++ compatible = "qcom,qca8075-package";
++ #address-cells = <1>;
++ #size-cells = <0>;
++ reg = <0x10>;
++ qcom,package-mode = "qsgmii";
++
++ phy0: ethernet-phy@10 {
++ reg = <0x10>;
++ };
++
++ phy1: ethernet-phy@11 {
++ reg = <0x11>;
++ };
++
++ phy2: ethernet-phy@12 {
++ reg = <0x12>;
++ };
++
++ phy3: ethernet-phy@13 {
++ reg = <0x13>;
++ };
++ };
++
++ phy4: ethernet-phy@8 {
++ compatible ="ethernet-phy-ieee802.3-c45";
++ reg = <8>;
++ };
++
++ phy5: ethernet-phy@0 {
++ compatible ="ethernet-phy-ieee802.3-c45";
++ reg = <0>;
++ };
++};
++
+ &tlmm {
+
+ pcie1_default: pcie1-default-state {
+@@ -161,3 +201,130 @@
+ };
+ };
+ };
++
++&qcom_ppe {
++ ethernet-ports {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ port@1 {
++ reg = <1>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy0>;
++ pcs-handle = <&pcs0_ch0>;
++ clocks = <&nsscc NSS_CC_PORT1_MAC_CLK>,
++ <&nsscc NSS_CC_PORT1_RX_CLK>,
++ <&nsscc NSS_CC_PORT1_TX_CLK>;
++ clock-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ resets = <&nsscc PORT1_MAC_ARES>,
++ <&nsscc PORT1_RX_ARES>,
++ <&nsscc PORT1_TX_ARES>;
++ reset-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ };
++
++ port@2 {
++ reg = <2>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy1>;
++ pcs-handle = <&pcs0_ch1>;
++ clocks = <&nsscc NSS_CC_PORT2_MAC_CLK>,
++ <&nsscc NSS_CC_PORT2_RX_CLK>,
++ <&nsscc NSS_CC_PORT2_TX_CLK>;
++ clock-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ resets = <&nsscc PORT2_MAC_ARES>,
++ <&nsscc PORT2_RX_ARES>,
++ <&nsscc PORT2_TX_ARES>;
++ reset-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ };
++
++ port@3 {
++ reg = <3>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy2>;
++ pcs-handle = <&pcs0_ch2>;
++ clocks = <&nsscc NSS_CC_PORT3_MAC_CLK>,
++ <&nsscc NSS_CC_PORT3_RX_CLK>,
++ <&nsscc NSS_CC_PORT3_TX_CLK>;
++ clock-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ resets = <&nsscc PORT3_MAC_ARES>,
++ <&nsscc PORT3_RX_ARES>,
++ <&nsscc PORT3_TX_ARES>;
++ reset-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ };
++
++ port@4 {
++ reg = <4>;
++ phy-mode = "qsgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy3>;
++ pcs-handle = <&pcs0_ch3>;
++ clocks = <&nsscc NSS_CC_PORT4_MAC_CLK>,
++ <&nsscc NSS_CC_PORT4_RX_CLK>,
++ <&nsscc NSS_CC_PORT4_TX_CLK>;
++ clock-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ resets = <&nsscc PORT4_MAC_ARES>,
++ <&nsscc PORT4_RX_ARES>,
++ <&nsscc PORT4_TX_ARES>;
++ reset-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ };
++
++ port@5 {
++ reg = <5>;
++ phy-mode = "usxgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy4>;
++ pcs-handle = <&pcs1_ch0>;
++ clocks = <&nsscc NSS_CC_PORT5_MAC_CLK>,
++ <&nsscc NSS_CC_PORT5_RX_CLK>,
++ <&nsscc NSS_CC_PORT5_TX_CLK>;
++ clock-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ resets = <&nsscc PORT5_MAC_ARES>,
++ <&nsscc PORT5_RX_ARES>,
++ <&nsscc PORT5_TX_ARES>;
++ reset-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ };
++
++ port@6 {
++ reg = <6>;
++ phy-mode = "usxgmii";
++ managed = "in-band-status";
++ phy-handle = <&phy5>;
++ pcs-handle = <&pcs2_ch0>;
++ clocks = <&nsscc NSS_CC_PORT6_MAC_CLK>,
++ <&nsscc NSS_CC_PORT6_RX_CLK>,
++ <&nsscc NSS_CC_PORT6_TX_CLK>;
++ clock-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ resets = <&nsscc PORT6_MAC_ARES>,
++ <&nsscc PORT6_RX_ARES>,
++ <&nsscc PORT6_TX_ARES>;
++ reset-names = "port_mac",
++ "port_rx",
++ "port_tx";
++ };
++ };
++};
--- /dev/null
+From 30b751f5984e295f0b5e7a2308b6103fae3322d2 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 9 Dec 2024 18:10:43 +0100
+Subject: [PATCH] arm64: dts: qcom: add AQR NVMEM node for IPQ9574 RDP433 board
+
+Add Aquantia NVMEM node for IPQ9574 RDP433 board to load the firmware
+for the Aquantia PHY.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
+@@ -87,11 +87,17 @@
+ phy4: ethernet-phy@8 {
+ compatible ="ethernet-phy-ieee802.3-c45";
+ reg = <8>;
++
++ nvmem-cells = <&aqr_fw>;
++ nvmem-cell-names = "firmware";
+ };
+
+ phy5: ethernet-phy@0 {
+ compatible ="ethernet-phy-ieee802.3-c45";
+ reg = <0>;
++
++ nvmem-cells = <&aqr_fw>;
++ nvmem-cell-names = "firmware";
+ };
+ };
+
--- /dev/null
+From b297d12d434191845cf8ae359466dcd8312ed21d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 4 Dec 2024 01:49:09 +0100
+Subject: [PATCH] arm64: dts: qcom: Add label to EDMA port for IPQ9574 RDP433
+
+Add label to EDMA port for IPQ9574 RDP433 board.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
++++ b/arch/arm64/boot/dts/qcom/ipq9574-rdp433.dts
+@@ -217,6 +217,7 @@
+ reg = <1>;
+ phy-mode = "qsgmii";
+ managed = "in-band-status";
++ label = "lan1";
+ phy-handle = <&phy0>;
+ pcs-handle = <&pcs0_ch0>;
+ clocks = <&nsscc NSS_CC_PORT1_MAC_CLK>,
+@@ -237,6 +238,7 @@
+ reg = <2>;
+ phy-mode = "qsgmii";
+ managed = "in-band-status";
++ label = "lan2";
+ phy-handle = <&phy1>;
+ pcs-handle = <&pcs0_ch1>;
+ clocks = <&nsscc NSS_CC_PORT2_MAC_CLK>,
+@@ -257,6 +259,7 @@
+ reg = <3>;
+ phy-mode = "qsgmii";
+ managed = "in-band-status";
++ label = "lan3";
+ phy-handle = <&phy2>;
+ pcs-handle = <&pcs0_ch2>;
+ clocks = <&nsscc NSS_CC_PORT3_MAC_CLK>,
+@@ -277,6 +280,7 @@
+ reg = <4>;
+ phy-mode = "qsgmii";
+ managed = "in-band-status";
++ label = "lan4";
+ phy-handle = <&phy3>;
+ pcs-handle = <&pcs0_ch3>;
+ clocks = <&nsscc NSS_CC_PORT4_MAC_CLK>,
+@@ -297,6 +301,7 @@
+ reg = <5>;
+ phy-mode = "usxgmii";
+ managed = "in-band-status";
++ label = "lan5";
+ phy-handle = <&phy4>;
+ pcs-handle = <&pcs1_ch0>;
+ clocks = <&nsscc NSS_CC_PORT5_MAC_CLK>,
+@@ -317,6 +322,7 @@
+ reg = <6>;
+ phy-mode = "usxgmii";
+ managed = "in-band-status";
++ label = "wan";
+ phy-handle = <&phy5>;
+ pcs-handle = <&pcs2_ch0>;
+ clocks = <&nsscc NSS_CC_PORT6_MAC_CLK>,
--- /dev/null
+From 6417cb20e854194a845d4ab092b92fd753c0e405 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 30 Jan 2025 16:11:14 +0100
+Subject: [PATCH] clk: qcom: nsscc: Attach required NSSNOC clock to PM domain
+
+There is currently a problem with ICC clock disabling the NSSNOC clock
+as there isn't any user for them on calling sync_state.
+This cause the kernel to stall if NSS is enabled and reboot with the watchdog.
+
+This is caused by the fact that the NSSNOC clock nsscc, snoc and snoc_1
+are actually required to make the NSS work and make the system continue
+booting.
+
+To attach these clock, setup pm-clk in nsscc and setup the correct
+resume/suspend OPs.
+
+With this change, the clock gets correctly attached and are not disabled
+when ICC call the sync_state.
+
+Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Alex G: Retrieve clocks by name rather than index.
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/clk/qcom/nsscc-ipq9574.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+--- a/drivers/clk/qcom/nsscc-ipq9574.c
++++ b/drivers/clk/qcom/nsscc-ipq9574.c
+@@ -3060,6 +3060,7 @@ MODULE_DEVICE_TABLE(of, nss_cc_ipq9574_m
+
+ static int nss_cc_ipq9574_probe(struct platform_device *pdev)
+ {
++ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ int ret;
+
+@@ -3075,6 +3076,18 @@ static int nss_cc_ipq9574_probe(struct p
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Fail to add bus clock\n");
+
++ ret = pm_clk_add(&pdev->dev, "nssnoc");
++ if (ret)
++ return dev_err_probe(dev, ret,"failed to acquire nssnoc clock\n");
++
++ ret = pm_clk_add(&pdev->dev, "snoc");
++ if (ret)
++ return dev_err_probe(dev, ret,"failed to acquire snoc clock\n");
++
++ ret = pm_clk_add(&pdev->dev, "snoc_1");
++ if (ret)
++ return dev_err_probe(dev, ret,"failed to acquire snoc_1 clock\n");
++
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Fail to resume\n");
+@@ -3089,8 +3102,16 @@ static int nss_cc_ipq9574_probe(struct p
+ clk_alpha_pll_configure(&ubi32_pll_main, regmap, &ubi32_pll_config);
+
+ ret = qcom_cc_really_probe(&pdev->dev, &nss_cc_ipq9574_desc, regmap);
++ if (ret)
++ goto err_put_pm;
++
+ pm_runtime_put(&pdev->dev);
+
++ return 0;
++
++err_put_pm:
++ pm_runtime_put_sync(dev);
++
+ return ret;
+ }
+
--- /dev/null
+From 372bbae100ffe14908bfd8448143c6cdbea17e8d Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 30 Jan 2025 16:23:03 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq9574: add NSSNOC clock to nss node
+
+Add NSSNOC clock to nss node to attach the clock with PM clock and fix
+the boot stall after ICC sync_state.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Alex G: Do not remove GCC_NSSCC_CLK ("bus") clock
+ Add clock-names for the new clocks
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -1255,7 +1255,10 @@
+ <&pcs1 1>,
+ <&pcs2 0>,
+ <&pcs2 1>,
+- <&gcc GCC_NSSCC_CLK>;
++ <&gcc GCC_NSSCC_CLK>,
++ <&gcc GCC_NSSNOC_NSSCC_CLK>,
++ <&gcc GCC_NSSNOC_SNOC_CLK>,
++ <&gcc GCC_NSSNOC_SNOC_1_CLK>;
+ clock-names = "xo",
+ "nss_1200",
+ "ppe_353",
+@@ -1266,7 +1269,10 @@
+ "uniphy1_tx",
+ "uniphy2_rx",
+ "uniphy2_tx",
+- "bus";
++ "bus",
++ "nssnoc",
++ "snoc",
++ "snoc_1";
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ #interconnect-cells = <1>;
--- /dev/null
+From fa691ff57c72a8f0bfeff1a9e86ae2d78765b0da Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Mon, 31 Mar 2025 15:39:59 +0300
+Subject: [PATCH] clk: qcom: nsscc-ipq9574: fix port5 clock config
+
+Currently there is no configuration to derive 25/125MHz port5 clock
+from uniphy1 running at 125MHz. This is needed for SGMII mode when
+port5 is using uniphy1.
+
+Fix this by adding option such clock config option.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ drivers/clk/qcom/nsscc-ipq9574.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/clk/qcom/nsscc-ipq9574.c
++++ b/drivers/clk/qcom/nsscc-ipq9574.c
+@@ -383,11 +383,13 @@ static const struct freq_multi_tbl ftbl_
+
+ static const struct freq_conf ftbl_nss_cc_port5_rx_clk_src_25[] = {
+ C(P_UNIPHY1_NSS_RX_CLK, 12.5, 0, 0),
++ C(P_UNIPHY1_NSS_RX_CLK, 5, 0, 0),
+ C(P_UNIPHY0_NSS_RX_CLK, 5, 0, 0),
+ };
+
+ static const struct freq_conf ftbl_nss_cc_port5_rx_clk_src_125[] = {
+ C(P_UNIPHY1_NSS_RX_CLK, 2.5, 0, 0),
++ C(P_UNIPHY1_NSS_RX_CLK, 1, 0, 0),
+ C(P_UNIPHY0_NSS_RX_CLK, 1, 0, 0),
+ };
+
+@@ -408,11 +410,13 @@ static const struct freq_multi_tbl ftbl_
+
+ static const struct freq_conf ftbl_nss_cc_port5_tx_clk_src_25[] = {
+ C(P_UNIPHY1_NSS_TX_CLK, 12.5, 0, 0),
++ C(P_UNIPHY1_NSS_TX_CLK, 5, 0, 0),
+ C(P_UNIPHY0_NSS_TX_CLK, 5, 0, 0),
+ };
+
+ static const struct freq_conf ftbl_nss_cc_port5_tx_clk_src_125[] = {
+ C(P_UNIPHY1_NSS_TX_CLK, 2.5, 0, 0),
++ C(P_UNIPHY1_NSS_TX_CLK, 1, 0, 0),
+ C(P_UNIPHY0_NSS_TX_CLK, 1, 0, 0),
+ };
+
--- /dev/null
+From 432c2a2da1e0f4a8e2c0fea191361832a7f90f36 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Wed, 6 Mar 2024 17:40:52 +0800
+Subject: [PATCH] net: pcs: Add 10GBASER interface mode support to IPQ UNIPHY
+ PCS driver
+
+10GBASER mode is used when PCS connects with a 10G SFP module.
+
+Change-Id: Ifc3c3bb23811807a9b34e88771aab2c830c2327c
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Alex G: Use regmap to read/write registers
+ Remove xpcs_reset deassert logic (to be implemented later)
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 47 ++++++++++++++++++++++++++++++
+ 1 file changed, 47 insertions(+)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -55,6 +55,9 @@
+ FIELD_PREP(GENMASK(9, 2), \
+ FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
+
++#define XPCS_KR_STS 0x30020
++#define XPCS_KR_LINK_STS BIT(12)
++
+ #define XPCS_DIG_CTRL 0x38000
+ #define XPCS_USXG_ADPT_RESET BIT(10)
+ #define XPCS_USXG_EN BIT(9)
+@@ -196,6 +199,28 @@ static void ipq_pcs_get_state_usxgmii(st
+ state->duplex = DUPLEX_FULL;
+ }
+
++static void ipq_pcs_get_state_10gbaser(struct ipq_pcs *qpcs,
++ struct phylink_link_state *state)
++{
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(qpcs->regmap, XPCS_KR_STS, &val);
++ if (ret) {
++ state->link = 0;
++ return;
++ }
++
++ state->link = !!(val & XPCS_KR_LINK_STS);
++
++ if (!state->link)
++ return;
++
++ state->speed = SPEED_10000;
++ state->duplex = DUPLEX_FULL;
++ state->pause |= MLO_PAUSE_TXRX_MASK;
++}
++
+ static int ipq_pcs_config_mode(struct ipq_pcs *qpcs,
+ phy_interface_t interface)
+ {
+@@ -212,6 +237,7 @@ static int ipq_pcs_config_mode(struct ip
+ val = PCS_MODE_QSGMII;
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
+ val = PCS_MODE_XPCS;
+ rate = 312500000;
+ break;
+@@ -311,6 +337,15 @@ static int ipq_pcs_config_usxgmii(struct
+ return regmap_set_bits(qpcs->regmap, XPCS_MII_CTRL, XPCS_MII_AN_EN);
+ }
+
++static int ipq_pcs_config_10gbaser(struct ipq_pcs *qpcs)
++{
++ /* Configure 10GBASER mode if required */
++ if (qpcs->interface == PHY_INTERFACE_MODE_10GBASER)
++ return 0;
++
++ return ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_10GBASER);
++}
++
+ static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
+ int index,
+ unsigned int neg_mode,
+@@ -399,6 +434,7 @@ static int ipq_pcs_validate(struct phyli
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
+ return 0;
+ case PHY_INTERFACE_MODE_USXGMII:
+ /* USXGMII only supports full duplex mode */
+@@ -418,6 +454,8 @@ static unsigned int ipq_pcs_inband_caps(
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_USXGMII:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++ case PHY_INTERFACE_MODE_10GBASER:
++ return LINK_INBAND_DISABLE;
+ default:
+ return 0;
+ }
+@@ -472,6 +510,9 @@ static void ipq_pcs_get_state(struct phy
+ case PHY_INTERFACE_MODE_USXGMII:
+ ipq_pcs_get_state_usxgmii(qpcs, state);
+ break;
++ case PHY_INTERFACE_MODE_10GBASER:
++ ipq_pcs_get_state_10gbaser(qpcs, state);
++ break;
+ default:
+ break;
+ }
+@@ -500,6 +541,8 @@ static int ipq_pcs_config(struct phylink
+ return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
+ case PHY_INTERFACE_MODE_USXGMII:
+ return ipq_pcs_config_usxgmii(qpcs);
++ case PHY_INTERFACE_MODE_10GBASER:
++ return ipq_pcs_config_10gbaser(qpcs);
+ default:
+ return -EOPNOTSUPP;
+ };
+@@ -524,6 +567,9 @@ static void ipq_pcs_link_up(struct phyli
+ case PHY_INTERFACE_MODE_USXGMII:
+ ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
+ break;
++ case PHY_INTERFACE_MODE_10GBASER:
++ /* Nothing to do here */
++ return;
+ default:
+ return;
+ }
+@@ -603,6 +649,7 @@ static unsigned long ipq_pcs_clk_rate_ge
+ {
+ switch (qpcs->interface) {
+ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10GBASER:
+ return 312500000;
+ default:
+ return 125000000;
--- /dev/null
+From 0d3a93e3a5544daec59d8f10ac5ccab39849536e Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Tue, 2 Apr 2024 18:28:42 +0800
+Subject: [PATCH] net: pcs: Add 2500BASEX interface mode support to IPQ UNIPHY
+ PCS driver
+
+2500BASEX mode is used when PCS connects with QCA8386 switch in a fixed
+2500M link. It is also used when PCS connectes with QCA8081 PHY which
+works at 2500M link speed. In addition, it can be also used when PCS
+connects with a 2.5G SFP module.
+
+Change-Id: I3fe61113c1b3685debc20659736a9488216a029d
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Alex G: use regmap to read/write registers
+ 's/ipq_unipcs/ipq_pcs/' in function names as suggested by Luo Jie
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 67 ++++++++++++++++++++++++++++++
+ 1 file changed, 67 insertions(+)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -26,6 +26,7 @@
+ #define PCS_MODE_SEL_MASK GENMASK(12, 8)
+ #define PCS_MODE_SGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
+ #define PCS_MODE_QSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++#define PCS_MODE_2500BASEX FIELD_PREP(PCS_MODE_SEL_MASK, 0x8)
+ #define PCS_MODE_XPCS FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
+
+ #define PCS_MII_CTRL(x) (0x480 + 0x18 * (x))
+@@ -155,6 +156,29 @@ static void ipq_pcs_get_state_sgmii(stru
+ state->duplex = DUPLEX_HALF;
+ }
+
++static void ipq_pcs_get_state_2500basex(struct ipq_pcs *qpcs,
++ struct phylink_link_state *state)
++{
++ unsigned int val;
++ int ret;
++
++ ret = regmap_read(qpcs->regmap, PCS_MII_STS(0), &val);
++ if (ret) {
++ state->link = 0;
++ return;
++ }
++
++
++ state->link = !!(val & PCS_MII_LINK_STS);
++
++ if (!state->link)
++ return;
++
++ state->speed = SPEED_2500;
++ state->duplex = DUPLEX_FULL;
++ state->pause |= MLO_PAUSE_TXRX_MASK;
++}
++
+ static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
+ struct phylink_link_state *state)
+ {
+@@ -236,6 +260,10 @@ static int ipq_pcs_config_mode(struct ip
+ case PHY_INTERFACE_MODE_QSGMII:
+ val = PCS_MODE_QSGMII;
+ break;
++ case PHY_INTERFACE_MODE_2500BASEX:
++ val = PCS_MODE_2500BASEX;
++ rate = 312500000;
++ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10GBASER:
+ val = PCS_MODE_XPCS;
+@@ -314,6 +342,15 @@ static int ipq_pcs_config_sgmii(struct i
+ PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
+ }
+
++static int ipq_pcs_config_2500basex(struct ipq_pcs *qpcs)
++{
++ /* Configure PCS for 2500BASEX mode if required */
++ if (qpcs->interface == PHY_INTERFACE_MODE_2500BASEX)
++ return 0;
++
++ return ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_2500BASEX);
++}
++
+ static int ipq_pcs_config_usxgmii(struct ipq_pcs *qpcs)
+ {
+ int ret;
+@@ -388,6 +425,22 @@ static int ipq_pcs_link_up_config_sgmii(
+ PCS_MII_CTRL(index), PCS_MII_ADPT_RESET);
+ }
+
++static int ipq_pcs_link_up_config_2500basex(struct ipq_pcs *qpcs, int speed)
++{
++ int ret;
++
++ /* 2500BASEX does not support autoneg and does not need to
++ * configure PCS speed. Only reset PCS adapter here.
++ */
++ ret = regmap_clear_bits(qpcs->regmap,
++ PCS_MII_CTRL(0), PCS_MII_ADPT_RESET);
++ if (ret)
++ return ret;
++
++ return regmap_set_bits(qpcs->regmap,
++ PCS_MII_CTRL(0), PCS_MII_ADPT_RESET);
++}
++
+ static int ipq_pcs_link_up_config_usxgmii(struct ipq_pcs *qpcs, int speed)
+ {
+ unsigned int val;
+@@ -436,6 +489,10 @@ static int ipq_pcs_validate(struct phyli
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_10GBASER:
+ return 0;
++ case PHY_INTERFACE_MODE_2500BASEX:
++ /* In-band autoneg is not supported for 2500BASEX */
++ phylink_clear(supported, Autoneg);
++ return 0;
+ case PHY_INTERFACE_MODE_USXGMII:
+ /* USXGMII only supports full duplex mode */
+ phylink_clear(supported, 100baseT_Half);
+@@ -454,6 +511,7 @@ static unsigned int ipq_pcs_inband_caps(
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_USXGMII:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_10GBASER:
+ return LINK_INBAND_DISABLE;
+ default:
+@@ -507,6 +565,9 @@ static void ipq_pcs_get_state(struct phy
+ case PHY_INTERFACE_MODE_QSGMII:
+ ipq_pcs_get_state_sgmii(qpcs, index, state);
+ break;
++ case PHY_INTERFACE_MODE_2500BASEX:
++ ipq_pcs_get_state_2500basex(qpcs, state);
++ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ ipq_pcs_get_state_usxgmii(qpcs, state);
+ break;
+@@ -539,6 +600,8 @@ static int ipq_pcs_config(struct phylink
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
++ case PHY_INTERFACE_MODE_2500BASEX:
++ return ipq_pcs_config_2500basex(qpcs);
+ case PHY_INTERFACE_MODE_USXGMII:
+ return ipq_pcs_config_usxgmii(qpcs);
+ case PHY_INTERFACE_MODE_10GBASER:
+@@ -564,6 +627,9 @@ static void ipq_pcs_link_up(struct phyli
+ ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
+ neg_mode, speed);
+ break;
++ case PHY_INTERFACE_MODE_2500BASEX:
++ ret = ipq_pcs_link_up_config_2500basex(qpcs, speed);
++ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
+ break;
+@@ -648,6 +714,7 @@ static int ipq_pcs_create_miis(struct ip
+ static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
+ {
+ switch (qpcs->interface) {
++ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10GBASER:
+ return 312500000;
--- /dev/null
+From d82953614a4f09dd7479e1d3904351ff85d1d088 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Tue, 9 Apr 2024 01:07:22 +0800
+Subject: [PATCH] net: pcs: Add 1000BASEX interface mode support to IPQ UNIPHY
+ PCS driver
+
+1000BASEX is used when PCS connects with a 1G SFP module.
+
+Change-Id: Ied7298de3c1ecba74e6457a07fdd6b3ceab79728
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -28,6 +28,9 @@
+ #define PCS_MODE_QSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
+ #define PCS_MODE_2500BASEX FIELD_PREP(PCS_MODE_SEL_MASK, 0x8)
+ #define PCS_MODE_XPCS FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
++#define PCS_MODE_SGMII_MODE_MASK GENMASK(6, 4)
++#define PCS_MODE_SGMII_MODE_1000BASEX FIELD_PREP(PCS_MODE_SGMII_MODE_MASK, \
++ 0x0)
+
+ #define PCS_MII_CTRL(x) (0x480 + 0x18 * (x))
+ #define PCS_MII_ADPT_RESET BIT(11)
+@@ -249,10 +252,11 @@ static int ipq_pcs_config_mode(struct ip
+ phy_interface_t interface)
+ {
+ unsigned long rate = 125000000;
+- unsigned int val;
++ unsigned int val, mask;
+ int ret;
+
+ /* Configure PCS interface mode */
++ mask = PCS_MODE_SEL_MASK;
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ val = PCS_MODE_SGMII;
+@@ -260,6 +264,10 @@ static int ipq_pcs_config_mode(struct ip
+ case PHY_INTERFACE_MODE_QSGMII:
+ val = PCS_MODE_QSGMII;
+ break;
++ case PHY_INTERFACE_MODE_1000BASEX:
++ mask |= PCS_MODE_SGMII_MODE_MASK;
++ val = PCS_MODE_SGMII | PCS_MODE_SGMII_MODE_1000BASEX;
++ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ val = PCS_MODE_2500BASEX;
+ rate = 312500000;
+@@ -273,8 +281,7 @@ static int ipq_pcs_config_mode(struct ip
+ return -EOPNOTSUPP;
+ }
+
+- ret = regmap_update_bits(qpcs->regmap, PCS_MODE_CTRL,
+- PCS_MODE_SEL_MASK, val);
++ ret = regmap_update_bits(qpcs->regmap, PCS_MODE_CTRL, mask, val);
+ if (ret)
+ return ret;
+
+@@ -487,6 +494,7 @@ static int ipq_pcs_validate(struct phyli
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_10GBASER:
+ return 0;
+ case PHY_INTERFACE_MODE_2500BASEX:
+@@ -509,6 +517,7 @@ static unsigned int ipq_pcs_inband_caps(
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_USXGMII:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+ case PHY_INTERFACE_MODE_2500BASEX:
+@@ -563,6 +572,10 @@ static void ipq_pcs_get_state(struct phy
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
++ /* SGMII and 1000BASEX in-band autoneg word format are decoded
++ * by PCS hardware and both placed to the same status register.
++ */
+ ipq_pcs_get_state_sgmii(qpcs, index, state);
+ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+@@ -599,6 +612,7 @@ static int ipq_pcs_config(struct phylink
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
+ return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return ipq_pcs_config_2500basex(qpcs);
+@@ -624,6 +638,7 @@ static void ipq_pcs_link_up(struct phyli
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_1000BASEX:
+ ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
+ neg_mode, speed);
+ break;
--- /dev/null
+From fc26c6f6c69149ce87c88d6878ae929b2a138063 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Mon, 15 Apr 2024 11:06:02 +0800
+Subject: [PATCH] net: pcs: Add 10G_QXGMII interface mode support to IPQ UNIPHY
+ PCS driver
+
+10G_QXGMII is used when PCS connectes with QCA8084 four ports
+2.5G PHYs.
+
+Change-Id: If3dc92a07ac3e51f7c9473fb05fa0668617916fb
+Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 109 +++++++++++++++++++++++------
+ 1 file changed, 87 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -48,6 +48,9 @@
+ #define PCS_MII_STS_SPEED_100 1
+ #define PCS_MII_STS_SPEED_1000 2
+
++#define PCS_QP_USXG_OPTION 0x584
++#define PCS_QP_USXG_GMII_SRC_XPCS BIT(0)
++
+ #define PCS_PLL_RESET 0x780
+ #define PCS_ANA_SW_RESET BIT(6)
+
+@@ -63,10 +66,23 @@
+ #define XPCS_KR_LINK_STS BIT(12)
+
+ #define XPCS_DIG_CTRL 0x38000
++#define XPCS_SOFT_RESET BIT(15)
+ #define XPCS_USXG_ADPT_RESET BIT(10)
+ #define XPCS_USXG_EN BIT(9)
+
++#define XPCS_KR_CTRL 0x38007
++#define XPCS_USXG_MODE_MASK GENMASK(12, 10)
++#define XPCS_10G_QXGMII_MODE FIELD_PREP(XPCS_USXG_MODE_MASK, 0x5)
++
++#define XPCS_DIG_STS 0x3800a
++#define XPCS_DIG_STS_AM_COUNT GENMASK(14, 0)
++
++/* DIG control for MII1 - MII3 */
++#define XPCS_MII1_DIG_CTRL(x) (0x1a8000 + 0x10000 * ((x) - 1))
++#define XPCS_MII1_USXG_ADPT_RESET BIT(5)
++
+ #define XPCS_MII_CTRL 0x1f0000
++#define XPCS_MII1_CTRL(x) (0x1a0000 + 0x10000 * ((x) - 1))
+ #define XPCS_MII_AN_EN BIT(12)
+ #define XPCS_DUPLEX_FULL BIT(8)
+ #define XPCS_SPEED_MASK (BIT(13) | BIT(6) | BIT(5))
+@@ -78,9 +94,11 @@
+ #define XPCS_SPEED_10 0
+
+ #define XPCS_MII_AN_CTRL 0x1f8001
++#define XPCS_MII1_AN_CTRL(x) (0x1a8001 + 0x10000 * ((x) - 1))
+ #define XPCS_MII_AN_8BIT BIT(8)
+
+ #define XPCS_MII_AN_INTR_STS 0x1f8002
++#define XPCS_MII1_AN_INTR_STS(x) (0x1a8002 + 0x10000 * ((x) - 1))
+ #define XPCS_USXG_AN_LINK_STS BIT(14)
+ #define XPCS_USXG_AN_SPEED_MASK GENMASK(12, 10)
+ #define XPCS_USXG_AN_SPEED_10 0
+@@ -90,6 +108,10 @@
+ #define XPCS_USXG_AN_SPEED_5000 5
+ #define XPCS_USXG_AN_SPEED_10000 3
+
++#define XPCS_XAUI_MODE_CTRL 0x1f8004
++#define XPCS_MII1_XAUI_MODE_CTRL(x) (0x1a8004 + 0x10000 * ((x) - 1))
++#define XPCS_TX_IPG_CHECK_DIS BIT(0)
++
+ /* Per PCS MII private data */
+ struct ipq_pcs_mii {
+ struct ipq_pcs *qpcs;
+@@ -182,13 +204,14 @@ static void ipq_pcs_get_state_2500basex(
+ state->pause |= MLO_PAUSE_TXRX_MASK;
+ }
+
+-static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
++static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs, int index,
+ struct phylink_link_state *state)
+ {
+- unsigned int val;
++ unsigned int reg, val;
+ int ret;
+
+- ret = regmap_read(qpcs->regmap, XPCS_MII_AN_INTR_STS, &val);
++ reg = (index == 0) ? XPCS_MII_AN_INTR_STS : XPCS_MII1_AN_INTR_STS(index);
++ ret = regmap_read(qpcs->regmap, reg, &val);
+ if (ret) {
+ state->link = 0;
+ return;
+@@ -273,6 +296,7 @@ static int ipq_pcs_config_mode(struct ip
+ rate = 312500000;
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10G_QXGMII:
+ case PHY_INTERFACE_MODE_10GBASER:
+ val = PCS_MODE_XPCS;
+ rate = 312500000;
+@@ -285,6 +309,13 @@ static int ipq_pcs_config_mode(struct ip
+ if (ret)
+ return ret;
+
++ if (interface == PHY_INTERFACE_MODE_10G_QXGMII) {
++ ret = regmap_set_bits(qpcs->regmap, PCS_QP_USXG_OPTION,
++ PCS_QP_USXG_GMII_SRC_XPCS);
++ if (ret)
++ return ret;
++ }
++
+ /* PCS PLL reset */
+ ret = regmap_clear_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
+ if (ret)
+@@ -358,27 +389,51 @@ static int ipq_pcs_config_2500basex(stru
+ return ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_2500BASEX);
+ }
+
+-static int ipq_pcs_config_usxgmii(struct ipq_pcs *qpcs)
++static int ipq_pcs_config_usxgmii(struct ipq_pcs *qpcs,
++ int index,
++ phy_interface_t interface)
+ {
++ unsigned int reg;
+ int ret;
+
+ /* Configure the XPCS for USXGMII mode if required */
+- if (qpcs->interface == PHY_INTERFACE_MODE_USXGMII)
+- return 0;
+-
+- ret = ipq_pcs_config_mode(qpcs, PHY_INTERFACE_MODE_USXGMII);
+- if (ret)
+- return ret;
++ if (qpcs->interface != interface) {
++ ret = ipq_pcs_config_mode(qpcs, interface);
++ if (ret)
++ return ret;
+
+- ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_USXG_EN);
+- if (ret)
+- return ret;
++ if (interface == PHY_INTERFACE_MODE_10G_QXGMII) {
++ ret = regmap_update_bits(qpcs->regmap, XPCS_KR_CTRL,
++ XPCS_USXG_MODE_MASK, XPCS_10G_QXGMII_MODE);
++ if (ret)
++ return ret;
++
++ /* Set Alignment Marker Interval value as 0x6018 */
++ ret = regmap_update_bits(qpcs->regmap, XPCS_DIG_STS,
++ XPCS_DIG_STS_AM_COUNT, 0x6018);
++ if (ret)
++ return ret;
++
++ ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_SOFT_RESET);
++ if (ret)
++ return ret;
++ }
++
++ /* Disable Tx IPG check for 10G_QXGMII */
++ if (interface == PHY_INTERFACE_MODE_10G_QXGMII) {
++ reg = (index == 0) ? XPCS_XAUI_MODE_CTRL : XPCS_MII1_XAUI_MODE_CTRL(index);
++ ret = regmap_set_bits(qpcs->regmap, reg, XPCS_TX_IPG_CHECK_DIS);
++ if (ret)
++ return ret;
++ }
+
+- ret = regmap_set_bits(qpcs->regmap, XPCS_MII_AN_CTRL, XPCS_MII_AN_8BIT);
++ reg = (index == 0) ? XPCS_MII_AN_CTRL : XPCS_MII1_AN_CTRL(index);
++ ret = regmap_set_bits(qpcs->regmap, reg, XPCS_MII_AN_8BIT);
+ if (ret)
+ return ret;
+
+- return regmap_set_bits(qpcs->regmap, XPCS_MII_CTRL, XPCS_MII_AN_EN);
++ reg = (index == 0) ? XPCS_MII_CTRL : XPCS_MII1_CTRL(index);
++ return regmap_set_bits(qpcs->regmap, reg, XPCS_MII_AN_EN);
+ }
+
+ static int ipq_pcs_config_10gbaser(struct ipq_pcs *qpcs)
+@@ -448,9 +503,10 @@ static int ipq_pcs_link_up_config_2500ba
+ PCS_MII_CTRL(0), PCS_MII_ADPT_RESET);
+ }
+
+-static int ipq_pcs_link_up_config_usxgmii(struct ipq_pcs *qpcs, int speed)
++static int ipq_pcs_link_up_config_usxgmii(struct ipq_pcs *qpcs,
++ int index, int speed)
+ {
+- unsigned int val;
++ unsigned int reg, val;
+ int ret;
+
+ switch (speed) {
+@@ -478,14 +534,17 @@ static int ipq_pcs_link_up_config_usxgmi
+ }
+
+ /* Configure XPCS speed */
+- ret = regmap_update_bits(qpcs->regmap, XPCS_MII_CTRL,
++ reg = (index == 0) ? XPCS_MII_CTRL : XPCS_MII1_CTRL(index);
++ ret = regmap_update_bits(qpcs->regmap, reg,
+ XPCS_SPEED_MASK, val | XPCS_DUPLEX_FULL);
+ if (ret)
+ return ret;
+
+ /* XPCS adapter reset */
+- return regmap_set_bits(qpcs->regmap,
+- XPCS_DIG_CTRL, XPCS_USXG_ADPT_RESET);
++ reg = (index == 0) ? XPCS_DIG_CTRL : XPCS_MII1_DIG_CTRL(index);
++ val = (index == 0) ? XPCS_USXG_ADPT_RESET : XPCS_MII1_USXG_ADPT_RESET;
++ return regmap_set_bits(qpcs->regmap, reg, val);
++
+ }
+
+ static int ipq_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+@@ -502,6 +561,7 @@ static int ipq_pcs_validate(struct phyli
+ phylink_clear(supported, Autoneg);
+ return 0;
+ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10G_QXGMII:
+ /* USXGMII only supports full duplex mode */
+ phylink_clear(supported, 100baseT_Half);
+ phylink_clear(supported, 10baseT_Half);
+@@ -519,6 +579,7 @@ static unsigned int ipq_pcs_inband_caps(
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10G_QXGMII:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_10GBASER:
+@@ -582,7 +643,8 @@ static void ipq_pcs_get_state(struct phy
+ ipq_pcs_get_state_2500basex(qpcs, state);
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+- ipq_pcs_get_state_usxgmii(qpcs, state);
++ case PHY_INTERFACE_MODE_10G_QXGMII:
++ ipq_pcs_get_state_usxgmii(qpcs, index, state);
+ break;
+ case PHY_INTERFACE_MODE_10GBASER:
+ ipq_pcs_get_state_10gbaser(qpcs, state);
+@@ -617,7 +679,8 @@ static int ipq_pcs_config(struct phylink
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return ipq_pcs_config_2500basex(qpcs);
+ case PHY_INTERFACE_MODE_USXGMII:
+- return ipq_pcs_config_usxgmii(qpcs);
++ case PHY_INTERFACE_MODE_10G_QXGMII:
++ return ipq_pcs_config_usxgmii(qpcs, index, interface);
+ case PHY_INTERFACE_MODE_10GBASER:
+ return ipq_pcs_config_10gbaser(qpcs);
+ default:
+@@ -646,7 +709,8 @@ static void ipq_pcs_link_up(struct phyli
+ ret = ipq_pcs_link_up_config_2500basex(qpcs, speed);
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+- ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
++ case PHY_INTERFACE_MODE_10G_QXGMII:
++ ret = ipq_pcs_link_up_config_usxgmii(qpcs, index, speed);
+ break;
+ case PHY_INTERFACE_MODE_10GBASER:
+ /* Nothing to do here */
+@@ -731,6 +795,7 @@ static unsigned long ipq_pcs_clk_rate_ge
+ switch (qpcs->interface) {
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_USXGMII:
++ case PHY_INTERFACE_MODE_10G_QXGMII:
+ case PHY_INTERFACE_MODE_10GBASER:
+ return 312500000;
+ default:
--- /dev/null
+From 87da3bbd25eb0a17e2c698120528e76c26b326d0 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Mon, 2 Jun 2025 17:18:13 +0300
+Subject: [PATCH] net: pcs: ipq-uniphy: control MISC2 register for 2.5G support
+
+When 2500base-x mode is enabled MISC2 regsister needs to have different
+value than for other 1G modes.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 17 ++++++++++++++++-
+ 1 file changed, 16 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -22,6 +22,11 @@
+ #define PCS_CALIBRATION 0x1e0
+ #define PCS_CALIBRATION_DONE BIT(7)
+
++#define PCS_MISC2 0x218
++#define PCS_MISC2_MODE_MASK GENMASK(6, 5)
++#define PCS_MISC2_MODE_SGMII FIELD_PREP(PCS_MISC2_MODE_MASK, 0x1)
++#define PCS_MISC2_MODE_SGMII_PLUS FIELD_PREP(PCS_MISC2_MODE_MASK, 0x2)
++
+ #define PCS_MODE_CTRL 0x46c
+ #define PCS_MODE_SEL_MASK GENMASK(12, 8)
+ #define PCS_MODE_SGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
+@@ -275,7 +280,7 @@ static int ipq_pcs_config_mode(struct ip
+ phy_interface_t interface)
+ {
+ unsigned long rate = 125000000;
+- unsigned int val, mask;
++ unsigned int val, mask, misc2 = 0;
+ int ret;
+
+ /* Configure PCS interface mode */
+@@ -283,6 +288,7 @@ static int ipq_pcs_config_mode(struct ip
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ val = PCS_MODE_SGMII;
++ misc2 = PCS_MISC2_MODE_SGMII;
+ break;
+ case PHY_INTERFACE_MODE_QSGMII:
+ val = PCS_MODE_QSGMII;
+@@ -290,9 +296,11 @@ static int ipq_pcs_config_mode(struct ip
+ case PHY_INTERFACE_MODE_1000BASEX:
+ mask |= PCS_MODE_SGMII_MODE_MASK;
+ val = PCS_MODE_SGMII | PCS_MODE_SGMII_MODE_1000BASEX;
++ misc2 = PCS_MISC2_MODE_SGMII;
+ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ val = PCS_MODE_2500BASEX;
++ misc2 = PCS_MISC2_MODE_SGMII_PLUS;
+ rate = 312500000;
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+@@ -315,6 +323,13 @@ static int ipq_pcs_config_mode(struct ip
+ if (ret)
+ return ret;
+ }
++
++ if (misc2) {
++ ret = regmap_update_bits(qpcs->regmap, PCS_MISC2,
++ PCS_MISC2_MODE_MASK, misc2);
++ if (ret)
++ return ret;
++ }
+
+ /* PCS PLL reset */
+ ret = regmap_clear_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
--- /dev/null
+From bedf56b46ae53c4abb21eebb3e1d5a7483926dda Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Mon, 2 Jun 2025 17:20:58 +0300
+Subject: [PATCH] net: pcs: ipq-uniphy: fix USXGMII link-up failure
+
+USXGMII link-up may fail due to too short delay after PLL reset.
+Increase the delay to fix this.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -336,7 +336,7 @@ static int ipq_pcs_config_mode(struct ip
+ if (ret)
+ return ret;
+
+- fsleep(1000);
++ fsleep(20000);
+ ret = regmap_set_bits(qpcs->regmap, PCS_PLL_RESET, PCS_ANA_SW_RESET);
+ if (ret)
+ return ret;
--- /dev/null
+From b4e07a8a3ec3dc5f676238987556e2aff0b14028 Mon Sep 17 00:00:00 2001
+From: Lei Wei <quic_leiwei@quicinc.com>
+Date: Mon, 29 Jan 2024 11:39:36 +0800
+Subject: [PATCH] net: pcs: qcom-ipq9574: Update IPQ9574 PCS driver
+
+Keep the PCS driver synced with the latest version posted to the kernel
+community and add the XPCS reset support.
+
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ .../bindings/net/pcs/qcom,ipq9574-pcs.yaml | 7 ++
+ drivers/net/pcs/pcs-qcom-ipq9574.c | 68 +++++++++++++++----
+ 2 files changed, 63 insertions(+), 12 deletions(-)
+
+--- a/Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
++++ b/Documentation/devicetree/bindings/net/pcs/qcom,ipq9574-pcs.yaml
+@@ -98,6 +98,10 @@ properties:
+ - const: sys
+ - const: ahb
+
++ resets:
++ maxItems: 1
++ description: XPCS reset
++
+ '#clock-cells':
+ const: 1
+ description: See include/dt-bindings/net/qcom,ipq9574-pcs.h for constants
+@@ -137,6 +141,7 @@ required:
+ - '#size-cells'
+ - clocks
+ - clock-names
++ - resets
+ - '#clock-cells'
+
+ additionalProperties: false
+@@ -144,6 +149,7 @@ additionalProperties: false
+ examples:
+ - |
+ #include <dt-bindings/clock/qcom,ipq9574-gcc.h>
++ #include <dt-bindings/reset/qcom,ipq9574-gcc.h>
+
+ ethernet-pcs@7a00000 {
+ compatible = "qcom,ipq9574-pcs";
+@@ -154,6 +160,7 @@ examples:
+ <&gcc GCC_UNIPHY0_AHB_CLK>;
+ clock-names = "sys",
+ "ahb";
++ resets = <&gcc GCC_UNIPHY0_XPCS_RESET>;
+ #clock-cells = <1>;
+
+ pcs-mii@0 {
+--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
++++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
+@@ -13,6 +13,7 @@
+ #include <linux/phylink.h>
+ #include <linux/platform_device.h>
+ #include <linux/regmap.h>
++#include <linux/reset.h>
+
+ #include <dt-bindings/net/qcom,ipq9574-pcs.h>
+
+@@ -31,9 +32,12 @@
+ #define PCS_MODE_SEL_MASK GENMASK(12, 8)
+ #define PCS_MODE_SGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
+ #define PCS_MODE_QSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
++#define PCS_MODE_PSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x2)
+ #define PCS_MODE_2500BASEX FIELD_PREP(PCS_MODE_SEL_MASK, 0x8)
+ #define PCS_MODE_XPCS FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
+ #define PCS_MODE_SGMII_MODE_MASK GENMASK(6, 4)
++#define PCS_MODE_SGMII_MODE_MAC FIELD_PREP(PCS_MODE_SGMII_MODE_MASK, \
++ 0x2)
+ #define PCS_MODE_SGMII_MODE_1000BASEX FIELD_PREP(PCS_MODE_SGMII_MODE_MASK, \
+ 0x0)
+
+@@ -52,6 +56,8 @@
+ #define PCS_MII_STS_SPEED_10 0
+ #define PCS_MII_STS_SPEED_100 1
+ #define PCS_MII_STS_SPEED_1000 2
++#define PCS_MII_STS_PAUSE_TX_EN BIT(1)
++#define PCS_MII_STS_PAUSE_RX_EN BIT(0)
+
+ #define PCS_QP_USXG_OPTION 0x584
+ #define PCS_QP_USXG_GMII_SRC_XPCS BIT(0)
+@@ -142,6 +148,7 @@ struct ipq_pcs {
+ struct clk_hw tx_hw;
+
+ struct ipq_pcs_mii *qpcs_mii[PCS_MAX_MII_NRS];
++ struct reset_control *xpcs_rstc;
+ };
+
+ #define phylink_pcs_to_qpcs_mii(_pcs) \
+@@ -184,6 +191,11 @@ static void ipq_pcs_get_state_sgmii(stru
+ state->duplex = DUPLEX_FULL;
+ else
+ state->duplex = DUPLEX_HALF;
++
++ if (val & PCS_MII_STS_PAUSE_TX_EN)
++ state->pause |= MLO_PAUSE_TX;
++ if (val & PCS_MII_STS_PAUSE_RX_EN)
++ state->pause |= MLO_PAUSE_RX;
+ }
+
+ static void ipq_pcs_get_state_2500basex(struct ipq_pcs *qpcs,
+@@ -198,7 +210,6 @@ static void ipq_pcs_get_state_2500basex(
+ return;
+ }
+
+-
+ state->link = !!(val & PCS_MII_LINK_STS);
+
+ if (!state->link)
+@@ -281,17 +292,27 @@ static int ipq_pcs_config_mode(struct ip
+ {
+ unsigned long rate = 125000000;
+ unsigned int val, mask, misc2 = 0;
++ bool xpcs_mode = false;
+ int ret;
+
++ /* Assert XPCS reset */
++ reset_control_assert(qpcs->xpcs_rstc);
++
+ /* Configure PCS interface mode */
+ mask = PCS_MODE_SEL_MASK;
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+- val = PCS_MODE_SGMII;
++ mask |= PCS_MODE_SGMII_MODE_MASK;
++ val = PCS_MODE_SGMII | PCS_MODE_SGMII_MODE_MAC;
+ misc2 = PCS_MISC2_MODE_SGMII;
+ break;
+ case PHY_INTERFACE_MODE_QSGMII:
+- val = PCS_MODE_QSGMII;
++ mask |= PCS_MODE_SGMII_MODE_MASK;
++ val = PCS_MODE_QSGMII | PCS_MODE_SGMII_MODE_MAC;
++ break;
++ case PHY_INTERFACE_MODE_PSGMII:
++ mask |= PCS_MODE_SGMII_MODE_MASK;
++ val = PCS_MODE_PSGMII | PCS_MODE_SGMII_MODE_MAC;
+ break;
+ case PHY_INTERFACE_MODE_1000BASEX:
+ mask |= PCS_MODE_SGMII_MODE_MASK;
+@@ -308,6 +329,7 @@ static int ipq_pcs_config_mode(struct ip
+ case PHY_INTERFACE_MODE_10GBASER:
+ val = PCS_MODE_XPCS;
+ rate = 312500000;
++ xpcs_mode = true;
+ break;
+ default:
+ return -EOPNOTSUPP;
+@@ -367,6 +389,10 @@ static int ipq_pcs_config_mode(struct ip
+ return ret;
+ }
+
++ /* Deassert XPCS */
++ if (xpcs_mode)
++ reset_control_deassert(qpcs->xpcs_rstc);
++
+ return 0;
+ }
+
+@@ -384,15 +410,13 @@ static int ipq_pcs_config_sgmii(struct i
+ return ret;
+ }
+
+- /* Nothing to do here as in-band autoneg mode is enabled
+- * by default for each PCS MII port.
+- */
++ /* Set AN mode or force mode */
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+- return 0;
+-
+- /* Set force speed mode */
+- return regmap_set_bits(qpcs->regmap,
+- PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
++ return regmap_clear_bits(qpcs->regmap,
++ PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
++ else
++ return regmap_set_bits(qpcs->regmap,
++ PCS_MII_CTRL(index), PCS_MII_FORCE_MODE);
+ }
+
+ static int ipq_pcs_config_2500basex(struct ipq_pcs *qpcs)
+@@ -417,6 +441,10 @@ static int ipq_pcs_config_usxgmii(struct
+ if (ret)
+ return ret;
+
++ ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_USXG_EN);
++ if (ret)
++ return ret;
++
+ if (interface == PHY_INTERFACE_MODE_10G_QXGMII) {
+ ret = regmap_update_bits(qpcs->regmap, XPCS_KR_CTRL,
+ XPCS_USXG_MODE_MASK, XPCS_10G_QXGMII_MODE);
+@@ -432,6 +460,7 @@ static int ipq_pcs_config_usxgmii(struct
+ ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_SOFT_RESET);
+ if (ret)
+ return ret;
++ }
+ }
+
+ /* Disable Tx IPG check for 10G_QXGMII */
+@@ -559,7 +588,6 @@ static int ipq_pcs_link_up_config_usxgmi
+ reg = (index == 0) ? XPCS_DIG_CTRL : XPCS_MII1_DIG_CTRL(index);
+ val = (index == 0) ? XPCS_USXG_ADPT_RESET : XPCS_MII1_USXG_ADPT_RESET;
+ return regmap_set_bits(qpcs->regmap, reg, val);
+-
+ }
+
+ static int ipq_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+@@ -568,6 +596,7 @@ static int ipq_pcs_validate(struct phyli
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_10GBASER:
+ return 0;
+@@ -592,6 +621,7 @@ static unsigned int ipq_pcs_inband_caps(
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10G_QXGMII:
+@@ -648,6 +678,7 @@ static void ipq_pcs_get_state(struct phy
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ /* SGMII and 1000BASEX in-band autoneg word format are decoded
+ * by PCS hardware and both placed to the same status register.
+@@ -689,6 +720,7 @@ static int ipq_pcs_config(struct phylink
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
+ case PHY_INTERFACE_MODE_2500BASEX:
+@@ -703,6 +735,11 @@ static int ipq_pcs_config(struct phylink
+ };
+ }
+
++static void ipq_pcs_an_restart(struct phylink_pcs *pcs)
++{
++ /* Currently not used */
++}
++
+ static void ipq_pcs_link_up(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ phy_interface_t interface,
+@@ -716,6 +753,7 @@ static void ipq_pcs_link_up(struct phyli
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
++ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
+ neg_mode, speed);
+@@ -746,6 +784,7 @@ static const struct phylink_pcs_ops ipq_
+ .pcs_disable = ipq_pcs_disable,
+ .pcs_get_state = ipq_pcs_get_state,
+ .pcs_config = ipq_pcs_config,
++ .pcs_an_restart = ipq_pcs_an_restart,
+ .pcs_link_up = ipq_pcs_link_up,
+ };
+
+@@ -990,6 +1029,11 @@ static int ipq9574_pcs_probe(struct plat
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "Failed to enable AHB clock\n");
+
++ qpcs->xpcs_rstc = devm_reset_control_get_optional(dev, NULL);
++ if (IS_ERR_OR_NULL(qpcs->xpcs_rstc))
++ return dev_err_probe(dev, PTR_ERR(qpcs->xpcs_rstc),
++ "Failed to get XPCS reset\n");
++
+ ret = ipq_pcs_clk_register(qpcs);
+ if (ret)
+ return ret;
--- /dev/null
+From d11eba3e178a9d42a579c656b2c9b643f4ce3e1e Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Mon, 23 Sep 2024 18:46:34 +0800
+Subject: [PATCH] net: phy: Add phy_package_remove_once helper
+
+QCA8084 PHY package needs to do the PHY package clean up,
+add phy_package_remove_once helper to support.
+
+Change-Id: I3cd73bc7be1b1d531435ef72f48db0682548decf
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ include/linux/phy.h | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -366,6 +366,7 @@ struct phy_package_shared {
+ /* used as bit number in atomic bitops */
+ #define PHY_SHARED_F_INIT_DONE 0
+ #define PHY_SHARED_F_PROBE_DONE 1
++#define PHY_SHARED_F_REMOVE_DONE 2
+
+ /**
+ * struct mii_bus - Represents an MDIO bus
+@@ -2245,6 +2246,11 @@ static inline bool phy_package_probe_onc
+ return __phy_package_set_once(phydev, PHY_SHARED_F_PROBE_DONE);
+ }
+
++static inline bool phy_package_remove_once(struct phy_device *phydev)
++{
++ return __phy_package_set_once(phydev, PHY_SHARED_F_REMOVE_DONE);
++}
++
+ extern const struct bus_type mdio_bus_type;
+
+ struct mdio_board_info {
--- /dev/null
+From c12b79af730116936504afe97234f9afb6ac8fc0 Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Mon, 23 Sep 2024 20:28:24 +0800
+Subject: [PATCH] net: phy: qca808x: Add QCA8084 SerDes probe and remove
+ functions
+
+QCA8084 PHY package integrates the XPCS and PCS, which is used
+to support 10G-QXGMII. XPCS includes 4 channels to connect with
+Quad PHY, and PCS controls the interface mode configured.
+
+XPCS and PCS are probed and removed by PHY package.
+
+Change-Id: Ided0a5cd4c996dc2a2a0d0598e930fab060caaf8
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Use phy_package_get_*() accessors
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/phy/qcom/Makefile | 2 +-
+ drivers/net/phy/qcom/qca8084_serdes.c | 249 ++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qca8084_serdes.h | 18 ++
+ drivers/net/phy/qcom/qca808x.c | 53 ++++++
+ 4 files changed, 321 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/net/phy/qcom/qca8084_serdes.c
+ create mode 100644 drivers/net/phy/qcom/qca8084_serdes.h
+
+--- a/drivers/net/phy/qcom/Makefile
++++ b/drivers/net/phy/qcom/Makefile
+@@ -2,5 +2,5 @@
+ obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-phy-lib.o
+ obj-$(CONFIG_AT803X_PHY) += at803x.o
+ obj-$(CONFIG_QCA83XX_PHY) += qca83xx.o
+-obj-$(CONFIG_QCA808X_PHY) += qca808x.o
++obj-$(CONFIG_QCA808X_PHY) += qca808x.o qca8084_serdes.o
+ obj-$(CONFIG_QCA807X_PHY) += qca807x.o
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca8084_serdes.c
+@@ -0,0 +1,249 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#include <linux/clk.h>
++#include <linux/dev_printk.h>
++#include <linux/mdio.h>
++#include <linux/of.h>
++#include <linux/phy.h>
++#include <linux/property.h>
++#include <linux/reset.h>
++
++#include "qca8084_serdes.h"
++
++/* XPCS includes 4 channels, each channel has the different MMD ID for
++ * configuring auto-negotiation complete interrupt, mii-4bit, auto-
++ * negotiation capabilities and TX configuration for the connected PHY.
++ *
++ * MMD31 is for channel 0;
++ * MMD26 is for channel 1;
++ * MMD27 is for channel 2;
++ * MMD28 is for channel 3;
++ */
++#define QCA8084_CHANNEL_MAX 4
++
++enum pcs_clk_id {
++ PCS_CLK,
++ PCS_RX_ROOT_CLK,
++ PCS_TX_ROOT_CLK,
++ PCS_CLK_MAX
++};
++
++enum xpcs_clk_id {
++ XPCS_XGMII_RX_CLK,
++ XPCS_XGMII_TX_CLK,
++ XPCS_RX_CLK,
++ XPCS_TX_CLK,
++ XPCS_PORT_RX_CLK,
++ XPCS_PORT_TX_CLK,
++ XPCS_RX_SRC_CLK,
++ XPCS_TX_SRC_CLK,
++ XPCS_CLK_MAX
++};
++
++struct qca8084_xpcs_channel_priv {
++ int ch_id;
++ struct reset_control *rstcs;
++ struct clk *clks[XPCS_CLK_MAX];
++};
++
++struct qca8084_pcs_data {
++ struct reset_control *rstc;
++ struct clk *clks[PCS_CLK_MAX];
++};
++
++struct qca8084_xpcs_data {
++ struct reset_control *rstc;
++ struct qca8084_xpcs_channel_priv xpcs_ch[QCA8084_CHANNEL_MAX];
++};
++
++static const char *const xpcs_clock_names[XPCS_CLK_MAX] = {
++ [XPCS_XGMII_RX_CLK] = "xgmii_rx",
++ [XPCS_XGMII_TX_CLK] = "xgmii_tx",
++ [XPCS_RX_CLK] = "xpcs_rx",
++ [XPCS_TX_CLK] = "xpcs_tx",
++ [XPCS_PORT_RX_CLK] = "port_rx",
++ [XPCS_PORT_TX_CLK] = "port_tx",
++ [XPCS_RX_SRC_CLK] = "rx_src",
++ [XPCS_TX_SRC_CLK] = "tx_src",
++};
++
++static const char *const pcs_clock_names[PCS_CLK_MAX] = {
++ [PCS_CLK] = "pcs",
++ [PCS_RX_ROOT_CLK] = "pcs_rx_root",
++ [PCS_TX_ROOT_CLK] = "pcs_tx_root",
++};
++
++struct mdio_device *qca8084_package_pcs_probe(struct device_node *pcs_np)
++{
++ struct qca8084_pcs_data *pcs_data;
++ struct mdio_device *mdiodev;
++ struct reset_control *rstc;
++ struct device *dev;
++ struct clk *clk;
++ int i;
++
++ mdiodev = fwnode_mdio_find_device(of_fwnode_handle(pcs_np));
++ if (!mdiodev)
++ return ERR_PTR(-EPROBE_DEFER);
++
++ dev = &mdiodev->dev;
++ pcs_data = devm_kzalloc(dev, sizeof(*pcs_data), GFP_KERNEL);
++ if (!pcs_data) {
++ dev_err(dev, "Allocate PCS data failed\n");
++ return ERR_PTR(-ENOMEM);
++ }
++
++ rstc = devm_reset_control_get_exclusive(dev, NULL);
++ if (IS_ERR(rstc)) {
++ dev_err(dev, "Get PCS reset failed\n");
++ return ERR_CAST(rstc);
++ }
++
++ pcs_data->rstc = rstc;
++
++ for (i = 0; i < ARRAY_SIZE(pcs_clock_names); i++) {
++ clk = devm_clk_get(dev, pcs_clock_names[i]);
++ if (IS_ERR(clk)) {
++ dev_err(dev, "Failed to get the PCS clock ID %s\n",
++ pcs_clock_names[i]);
++ return ERR_CAST(clk);
++ }
++ pcs_data->clks[i] = clk;
++ }
++
++ mdiodev_set_drvdata(mdiodev, pcs_data);
++
++ return mdiodev;
++}
++
++struct mdio_device *qca8084_package_xpcs_probe(struct device_node *xpcs_np)
++{
++ struct qca8084_xpcs_data *xpcs_data;
++ struct mdio_device *mdiodev;
++ struct reset_control *rstc;
++ struct device_node *child;
++ struct device *dev;
++ struct clk *clk;
++ int i, j, node;
++
++ mdiodev = fwnode_mdio_find_device(of_fwnode_handle(xpcs_np));
++ if (!mdiodev)
++ return ERR_PTR(-EPROBE_DEFER);
++
++ dev = &mdiodev->dev;
++
++ xpcs_data = devm_kzalloc(dev, sizeof(*xpcs_data), GFP_KERNEL);
++ if (!xpcs_data) {
++ dev_err(dev, "Allocate XPCS data failed\n");
++ return ERR_PTR(-ENOMEM);
++ }
++
++ rstc = devm_reset_control_get_exclusive(dev, NULL);
++ if (IS_ERR(rstc)) {
++ dev_err(dev, "Get XPCS reset failed\n");
++ return ERR_CAST(rstc);
++ }
++
++ xpcs_data->rstc = rstc;
++
++ /* Sanity check the number of channel sub nodes */
++ node = of_get_available_child_count(xpcs_np);
++ if (node != QCA8084_CHANNEL_MAX)
++ return ERR_PTR(-EINVAL);
++
++ node = 0;
++ for_each_available_child_of_node(xpcs_np, child) {
++ struct qca8084_xpcs_channel_priv *ch_data;
++ u32 channel;
++
++ /* The subnode name must be 'channel'. */
++ if (!(of_node_name_eq(child, "channel")))
++ continue;
++
++ if (of_property_read_u32(child, "reg", &channel)) {
++ dev_err(dev, "%s: Failed to get reg\n",
++ child->full_name);
++
++ mdiodev = ERR_PTR(-EINVAL);
++ goto put_ch_clk_rst;
++ }
++
++ if (channel >= QCA8084_CHANNEL_MAX) {
++ dev_err(dev, "%s: Invalid reg %d\n",
++ child->full_name, channel);
++
++ mdiodev = ERR_PTR(-EINVAL);
++ goto put_ch_clk_rst;
++ }
++
++ ch_data = &xpcs_data->xpcs_ch[node];
++ ch_data->ch_id = channel;
++
++ ch_data->rstcs = of_reset_control_array_get_exclusive(child);
++ if (IS_ERR(ch_data->rstcs)) {
++ dev_err(dev, "%s: Failed to get reset\n",
++ child->full_name);
++
++ mdiodev = ERR_CAST(ch_data->rstcs);
++ goto put_ch_clk_rst;
++ }
++
++ for (j = 0; j < ARRAY_SIZE(xpcs_clock_names); j++) {
++ clk = of_clk_get_by_name(child, xpcs_clock_names[j]);
++ if (IS_ERR(clk)) {
++ dev_err(dev, "Failed to get the clock ID %s\n",
++ xpcs_clock_names[j]);
++ mdiodev = ERR_CAST(clk);
++ goto put_ch_child;
++ }
++ ch_data->clks[j] = clk;
++ }
++
++ node++;
++ }
++
++ mdiodev_set_drvdata(mdiodev, xpcs_data);
++
++ return mdiodev;
++
++put_ch_child:
++ node++;
++
++put_ch_clk_rst:
++ for (i = 0; i < node; i++) {
++ j--;
++ while (j >= 0) {
++ clk_put(xpcs_data->xpcs_ch[i].clks[j]);
++ j--;
++ }
++
++ j = ARRAY_SIZE(xpcs_clock_names);
++ }
++
++ for (i = 0; i < node; i++)
++ reset_control_put(xpcs_data->xpcs_ch[i].rstcs);
++
++ of_node_put(child);
++
++ return mdiodev;
++}
++
++void qca8084_package_xpcs_and_pcs_remove(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev)
++{
++ struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
++ int i, j;
++
++ for (i = 0; i < ARRAY_SIZE(xpcs_data->xpcs_ch); i++) {
++ reset_control_put(xpcs_data->xpcs_ch[i].rstcs);
++
++ for (j = 0; j < ARRAY_SIZE(xpcs_data->xpcs_ch[i].clks); j++)
++ clk_put(xpcs_data->xpcs_ch[i].clks[j]);
++ }
++
++ mdio_device_put(xpcs_mdiodev);
++ mdio_device_put(pcs_mdiodev);
++}
+--- /dev/null
++++ b/drivers/net/phy/qcom/qca8084_serdes.h
+@@ -0,0 +1,18 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Driver for QCA8084 SerDes
++ *
++ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
++ */
++
++#ifndef _QCA8084_SERDES_H_
++#define _QCA8084_SERDES_H_
++
++#include <linux/mdio.h>
++#include <linux/of.h>
++
++struct mdio_device *qca8084_package_pcs_probe(struct device_node *pcs_np);
++struct mdio_device *qca8084_package_xpcs_probe(struct device_node *xpcs_np);
++void qca8084_package_xpcs_and_pcs_remove(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev);
++#endif /* _QCA8084_SERDES_H_ */
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -8,6 +8,7 @@
+ #include <linux/clk.h>
+
+ #include "../phylib.h"
++#include "qca8084_serdes.h"
+ #include "qcom.h"
+
+ /* ADC threshold */
+@@ -172,11 +173,13 @@ enum {
+
+ struct qca808x_priv {
+ int led_polarity_mode;
++ int channel_id;
+ };
+
+ struct qca808x_shared_priv {
+ int package_mode;
+ struct clk *clk[PACKAGE_CLK_MAX];
++ struct mdio_device *mdiodev[2]; /* PCS and XPCS mdio device */
+ };
+
+ static const char *const qca8084_package_clk_name[PACKAGE_CLK_MAX] = {
+@@ -354,6 +357,8 @@ static int qca808x_probe(struct phy_devi
+ {
+ struct device *dev = &phydev->mdio.dev;
+ struct qca808x_priv *priv;
++ u32 ch_id = 0;
++ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+@@ -362,6 +367,14 @@ static int qca808x_probe(struct phy_devi
+ /* Init LED polarity mode to -1 */
+ priv->led_polarity_mode = -1;
+
++ /* DT property qcom,xpcs-channel" is optional and only available for
++ * 10G-QXGMII mode.
++ */
++ ret = of_property_read_u32(dev->of_node, "qcom,xpcs-channel", &ch_id);
++ if (ret && ret != -EINVAL)
++ return ret;
++
++ priv->channel_id = ch_id;
+ phydev->priv = priv;
+
+ return 0;
+@@ -1012,6 +1025,7 @@ static int qca8084_phy_package_probe_onc
+ struct device_node *np = phy_package_get_node(phydev);
+ struct qca808x_shared_priv *shared_priv;
+ struct reset_control *rstc;
++ struct device_node *child;
+ int i, ret, clear, set;
+ struct clk *clk;
+
+@@ -1072,6 +1086,26 @@ static int qca8084_phy_package_probe_onc
+ if (ret && ret != -EINVAL)
+ return ret;
+
++ for_each_available_child_of_node(np, child) {
++ struct mdio_device *mdiodev;
++
++ if (of_node_name_eq(child, "pcs-phy")) {
++ mdiodev = qca8084_package_pcs_probe(child);
++ if (IS_ERR(mdiodev))
++ return PTR_ERR(mdiodev);
++
++ shared_priv->mdiodev[0] = mdiodev;
++ }
++
++ if (of_node_name_eq(child, "xpcs-phy")) {
++ mdiodev = qca8084_package_xpcs_probe(child);
++ if (IS_ERR(mdiodev))
++ return PTR_ERR(mdiodev);
++
++ shared_priv->mdiodev[1] = mdiodev;
++ }
++ }
++
+ rstc = of_reset_control_get_exclusive(np, NULL);
+ if (IS_ERR(rstc))
+ return dev_err_probe(&phydev->mdio.dev, PTR_ERR(rstc),
+@@ -1081,6 +1115,14 @@ static int qca8084_phy_package_probe_onc
+ return reset_control_deassert(rstc);
+ }
+
++static void qca8084_phy_package_remove_once(struct phy_device *phydev)
++{
++ struct qca808x_shared_priv *shared_priv = phy_package_get_priv(phydev);;
++
++ qca8084_package_xpcs_and_pcs_remove(shared_priv->mdiodev[1],
++ shared_priv->mdiodev[0]);
++}
++
+ static int qca8084_probe(struct phy_device *phydev)
+ {
+ struct qca808x_shared_priv *shared_priv;
+@@ -1099,6 +1141,10 @@ static int qca8084_probe(struct phy_devi
+ return ret;
+ }
+
++ ret = qca808x_probe(phydev);
++ if (ret)
++ return ret;
++
+ /* Enable clock of PHY device, so that the PHY register
+ * can be accessed to get PHY features.
+ */
+@@ -1116,6 +1162,12 @@ static int qca8084_probe(struct phy_devi
+ return reset_control_deassert(rstc);
+ }
+
++static void qca8084_remove(struct phy_device *phydev)
++{
++ if (phy_package_remove_once(phydev))
++ qca8084_phy_package_remove_once(phydev);
++}
++
+ static struct phy_driver qca808x_driver[] = {
+ {
+ /* Qualcomm QCA8081 */
+@@ -1167,6 +1219,7 @@ static struct phy_driver qca808x_driver[
+ .config_init = qca8084_config_init,
+ .link_change_notify = qca8084_link_change_notify,
+ .probe = qca8084_probe,
++ .remove = qca8084_remove,
+ }, };
+
+ module_phy_driver(qca808x_driver);
--- /dev/null
+From d137b725f8f4a7d49a809dcd73c5b836495ec44d Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Mon, 23 Sep 2024 20:59:40 +0800
+Subject: [PATCH] net: phy: qca808x: Add QCA8084 SerDes init function
+
+When QCA8084 works on 10G-QXGMII, the XPCS and PCS need to be
+configured in the PHY package init function.
+
+Change-Id: Iac48c44f0e80adf055fa9c2095e99a04ba24c4bb
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+---
+ drivers/net/phy/qcom/qca8084_serdes.c | 374 ++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qca8084_serdes.h | 2 +
+ drivers/net/phy/qcom/qca808x.c | 11 +
+ 3 files changed, 387 insertions(+)
+
+--- a/drivers/net/phy/qcom/qca8084_serdes.c
++++ b/drivers/net/phy/qcom/qca8084_serdes.c
+@@ -24,6 +24,92 @@
+ */
+ #define QCA8084_CHANNEL_MAX 4
+
++/* MII registers */
++#define PLL_POWER_ON_AND_RESET 0x0
++#define PCS_ANA_SW_RESET BIT(6)
++
++#define PLL_CONTROL 6
++#define PLL_CONTROL_CMLDIV2_IBSEL_MASK GENMASK(5, 4)
++
++/* MMD_PMAPMD registers */
++#define CDR_CONTRL 0x20
++#define SSC_FIX_MODE BIT(3)
++
++#define CALIBRATION4 0x78
++#define CALIBRATION_DONE BIT(7)
++
++#define MODE_CONTROL 0x11b
++#define MODE_CONTROL_SEL_MASK GENMASK(12, 8)
++#define MODE_CONTROL_XPCS 0x10
++#define MODE_CONTROL_SGMII_PLUS 0x8
++#define MODE_CONTROL_SGMII 0x4
++#define MODE_CONTROL_SGMII_SEL_MASK GENMASK(6, 4)
++#define MODE_CONTROL_SGMII_PHY 1
++#define MODE_CONTROL_SGMII_MAC 2
++
++#define QP_USXG_OPTION1 0x180
++#define QP_USXG_OPTION1_DATAPASS BIT(0)
++#define QP_USXG_OPTION1_DATAPASS_SGMII 0
++#define QP_USXG_OPTION1_DATAPASS_USXGMII 1
++
++#define BYPASS_TUNNING_IPG 0x189
++#define BYPASS_TUNNING_IPG_MASK GENMASK(11, 0)
++
++/* MDIO_MMD_PCS register */
++#define PCS_CONTROL2 0x7
++#define PCS_TYPE_MASK GENMASK(3, 0)
++#define PCS_TYPE_BASER 0
++
++#define PCS_EEE_CONTROL 0x14
++#define EEE_CAPABILITY BIT(6)
++
++#define PCS_STATUS1 0x20
++#define PCS_BASER_UP BIT(12)
++
++#define DIG_CTRL1 0x8000
++#define DIG_CTRL1_USXGMII_EN BIT(9)
++#define DIG_CTRL1_XPCS_RESET BIT(15)
++#define FIFO_RESET_CH0 BIT(10)
++#define FIFO_RESET_CH1_CH2_CH3 BIT(5)
++
++#define EEE_MODE_CONTROL 0x8006
++#define EEE_LCT_RES GENMASK(11, 8)
++#define EEE_SIGN BIT(6)
++#define EEE_LRX_EN BIT(1)
++#define EEE_LTX_EN BIT(0)
++
++#define PCS_TPC 0x8007
++#define PCS_QXGMII_MODE_MASK GENMASK(12, 10)
++#define PCS_QXGMII_EN 0x5
++
++#define EEE_RX_TIMER 0x8009
++#define EEE_RX_TIMER_100US_RES GENMASK(7, 0)
++#define EEE_RX_TIMER_RWR_RES GENMASK(12, 8)
++
++#define AM_LINK_TIMER 0x800a
++#define AM_LINK_TIMER_VAL 0x6018
++
++#define EEE_MODE_CONTROL1 0x800b
++#define TRANS_LPI_MODE BIT(0)
++#define TRANS_RX_LPI_MODE BIT(8)
++
++/* QXGMII channel MMD register */
++#define MII_CONTROL 0x0
++#define AUTO_NEGOTIATION_EN BIT(12)
++#define AUTO_NEGOTIATION_RESTART BIT(9)
++#define PCS_SPEED_2500 BIT(5)
++#define PCS_SPEED_1000 BIT(6)
++#define PCS_SPEED_100 BIT(13)
++#define PCS_SPEED_10 0
++
++#define DIG_CONTROL2 0x8001
++#define MII_BIT_CONTROL BIT(8)
++#define TX_CONFIG BIT(3)
++#define AUTO_NEGOTIATION_CMPLT_INTR BIT(0)
++
++#define XAUI_CONTROL 0x8004
++#define TX_IPG_CHECK_DISABLE BIT(0)
++
+ enum pcs_clk_id {
+ PCS_CLK,
+ PCS_RX_ROOT_CLK,
+@@ -76,6 +162,8 @@ static const char *const pcs_clock_names
+ [PCS_TX_ROOT_CLK] = "pcs_tx_root",
+ };
+
++static const int qca8084_xpcs_ch_mmd[QCA8084_CHANNEL_MAX] = { 31, 26, 27, 28 };
++
+ struct mdio_device *qca8084_package_pcs_probe(struct device_node *pcs_np)
+ {
+ struct qca8084_pcs_data *pcs_data;
+@@ -247,3 +335,289 @@ void qca8084_package_xpcs_and_pcs_remove
+ mdio_device_put(xpcs_mdiodev);
+ mdio_device_put(pcs_mdiodev);
+ }
++
++static int qca8084_pcs_set_interface_mode(struct mdio_device *mdio_dev,
++ phy_interface_t ifmode)
++{
++ int ret, hw_ifmode, data;
++
++ switch (ifmode) {
++ case PHY_INTERFACE_MODE_SGMII:
++ hw_ifmode = MODE_CONTROL_SGMII;
++ data = QP_USXG_OPTION1_DATAPASS_SGMII;
++ break;
++ case PHY_INTERFACE_MODE_2500BASEX:
++ hw_ifmode = MODE_CONTROL_SGMII_PLUS;
++ data = QP_USXG_OPTION1_DATAPASS_SGMII;
++ break;
++ case PHY_INTERFACE_MODE_10G_QXGMII:
++ hw_ifmode = MODE_CONTROL_XPCS;
++ data = QP_USXG_OPTION1_DATAPASS_USXGMII;
++ break;
++ default:
++ return -EOPNOTSUPP;
++ }
++
++ /* For PLL stable under high temperature */
++ ret = mdiodev_modify(mdio_dev, PLL_CONTROL,
++ PLL_CONTROL_CMLDIV2_IBSEL_MASK,
++ FIELD_PREP(PLL_CONTROL_CMLDIV2_IBSEL_MASK, 3));
++ if (ret)
++ return ret;
++
++ /* Configure the interface mode of PCS */
++ ret = mdiodev_c45_modify(mdio_dev, MDIO_MMD_PMAPMD, MODE_CONTROL,
++ MODE_CONTROL_SEL_MASK,
++ FIELD_PREP(MODE_CONTROL_SEL_MASK, hw_ifmode));
++ if (ret)
++ return ret;
++
++ /* Data pass selects SGMII or USXGMII */
++ return mdiodev_c45_modify(mdio_dev, MDIO_MMD_PMAPMD, QP_USXG_OPTION1,
++ QP_USXG_OPTION1_DATAPASS,
++ FIELD_PREP(QP_USXG_OPTION1_DATAPASS, data));
++}
++
++static int qca8084_do_calibration(struct mdio_device *mdio_dev)
++{
++ int ret;
++
++ ret = mdiodev_modify(mdio_dev, PLL_POWER_ON_AND_RESET,
++ PCS_ANA_SW_RESET, 0);
++ if (ret)
++ return ret;
++
++ usleep_range(10000, 11000);
++ ret = mdiodev_modify(mdio_dev, PLL_POWER_ON_AND_RESET,
++ PCS_ANA_SW_RESET, PCS_ANA_SW_RESET);
++ if (ret)
++ return ret;
++
++ /* Wait calibration done */
++ return read_poll_timeout(mdiodev_c45_read, ret,
++ (ret & CALIBRATION_DONE),
++ 100, 100000, true, mdio_dev,
++ MDIO_MMD_PMAPMD, CALIBRATION4);
++}
++
++
++static int qca8084_xpcs_set_mode(struct mdio_device *xpcs_mdiodev)
++{
++ int ret, val, i;
++
++ /* Configure BaseR mode */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, PCS_CONTROL2,
++ PCS_TYPE_MASK,
++ FIELD_PREP(PCS_TYPE_MASK, PCS_TYPE_BASER));
++ if (ret)
++ return ret;
++
++ /* Wait BaseR link up */
++ ret = read_poll_timeout(mdiodev_c45_read, val,
++ (val & PCS_BASER_UP), 1000, 100000, true,
++ xpcs_mdiodev,
++ MDIO_MMD_PCS, PCS_STATUS1);
++ if (ret) {
++ dev_err(&xpcs_mdiodev->dev, "BaseR link failed!\n");
++ return ret;
++ }
++
++ /* Enable USXGMII mode */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, DIG_CTRL1,
++ DIG_CTRL1_USXGMII_EN,
++ DIG_CTRL1_USXGMII_EN);
++ if (ret)
++ return ret;
++
++ /* Configure QXGMII mode */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, PCS_TPC,
++ PCS_QXGMII_MODE_MASK,
++ FIELD_PREP(PCS_QXGMII_MODE_MASK,
++ PCS_QXGMII_EN));
++ if (ret)
++ return ret;
++
++ /* Configure AM interval */
++ ret = mdiodev_c45_write(xpcs_mdiodev, MDIO_MMD_PCS, AM_LINK_TIMER,
++ AM_LINK_TIMER_VAL);
++ if (ret)
++ return ret;
++
++ /* Reset XPCS */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, DIG_CTRL1,
++ DIG_CTRL1_XPCS_RESET,
++ DIG_CTRL1_XPCS_RESET);
++ if (ret)
++ return ret;
++
++ /* Wait XPCS reset done */
++ ret = read_poll_timeout(mdiodev_c45_read, val,
++ !(val & DIG_CTRL1_XPCS_RESET),
++ 1000, 100000, true, xpcs_mdiodev,
++ MDIO_MMD_PCS, DIG_CTRL1);
++ if (ret) {
++ dev_err(&xpcs_mdiodev->dev, "XPCS reset failed!\n");
++ return ret;
++ }
++
++ /* Enable auto-negotiation complete interrupt, using mii-4bit
++ * and TX configureation of PHY side on all XPCS channels.
++ */
++ for (i = 0; i < QCA8084_CHANNEL_MAX; i++) {
++ ret = mdiodev_c45_modify(xpcs_mdiodev, qca8084_xpcs_ch_mmd[i],
++ DIG_CONTROL2,
++ (MII_BIT_CONTROL | TX_CONFIG |
++ AUTO_NEGOTIATION_CMPLT_INTR),
++ (TX_CONFIG | AUTO_NEGOTIATION_CMPLT_INTR));
++ if (ret)
++ return ret;
++
++ /* Enable auto-negotiation capability */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, qca8084_xpcs_ch_mmd[i],
++ MII_CONTROL,
++ AUTO_NEGOTIATION_EN,
++ AUTO_NEGOTIATION_EN);
++ if (ret)
++ return ret;
++
++ /* Disable TX IPG check */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, qca8084_xpcs_ch_mmd[i],
++ XAUI_CONTROL,
++ TX_IPG_CHECK_DISABLE,
++ TX_IPG_CHECK_DISABLE);
++ if (ret)
++ return ret;
++ }
++
++ /* Check EEE capability supported or not */
++ ret = mdiodev_c45_read(xpcs_mdiodev, MDIO_MMD_PCS, PCS_EEE_CONTROL);
++ if (ret < 0)
++ return ret;
++
++ if (ret & EEE_CAPABILITY) {
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
++ EEE_MODE_CONTROL,
++ EEE_LCT_RES | EEE_SIGN,
++ FIELD_PREP(EEE_LCT_RES, 1) | EEE_SIGN);
++ if (ret)
++ return ret;
++
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
++ EEE_RX_TIMER,
++ EEE_RX_TIMER_100US_RES | EEE_RX_TIMER_RWR_RES,
++ FIELD_PREP(EEE_RX_TIMER_100US_RES, 0xc8) |
++ FIELD_PREP(EEE_RX_TIMER_RWR_RES, 0x1c));
++ if (ret)
++ return ret;
++
++ /* Enable EEE LPI */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
++ EEE_MODE_CONTROL1,
++ TRANS_LPI_MODE | TRANS_RX_LPI_MODE,
++ TRANS_LPI_MODE | TRANS_RX_LPI_MODE);
++ if (ret)
++ return ret;
++
++ /* Enable TX/RX LPI pattern */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS,
++ EEE_MODE_CONTROL,
++ EEE_LRX_EN | EEE_LTX_EN,
++ EEE_LRX_EN | EEE_LTX_EN);
++ }
++
++ return ret;
++}
++
++static int qca8084_pcs_set_mode(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev)
++{
++ struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
++ struct qca8084_pcs_data *pcs_data = mdiodev_get_drvdata(pcs_mdiodev);
++ struct qca8084_xpcs_channel_priv xpcs_ch;
++ int ret, channel;
++
++ /* Enable clock and de-assert for PCS. */
++ ret = clk_prepare_enable(pcs_data->clks[PCS_CLK]);
++ if (ret)
++ return ret;
++
++ ret = reset_control_deassert(pcs_data->rstc);
++ if (ret)
++ return ret;
++
++ /* IPG tunning selection for RX, TX and XGMII of all channels. */
++ ret = mdiodev_c45_modify(pcs_mdiodev, MDIO_MMD_PMAPMD,
++ BYPASS_TUNNING_IPG,
++ BYPASS_TUNNING_IPG_MASK, 0);
++ if (ret)
++ return ret;
++
++ reset_control_assert(xpcs_data->rstc);
++
++ ret = qca8084_pcs_set_interface_mode(pcs_mdiodev,
++ PHY_INTERFACE_MODE_10G_QXGMII);
++ if (ret)
++ return ret;
++
++ /* Reset of 4 channels */
++ for (channel = 0; channel < QCA8084_CHANNEL_MAX; channel++) {
++ xpcs_ch = xpcs_data->xpcs_ch[channel];
++ ret = reset_control_reset(xpcs_ch.rstcs);
++ if (ret)
++ return ret;
++ }
++
++ ret = qca8084_do_calibration(pcs_mdiodev);
++ if (ret) {
++ dev_err(&pcs_mdiodev->dev, "PCS calibration timeout!\n");
++ return ret;
++ }
++
++ /* Enable PCS SSC to fix mode */
++ ret = mdiodev_c45_modify(pcs_mdiodev, MDIO_MMD_PMAPMD,
++ CDR_CONTRL, SSC_FIX_MODE, SSC_FIX_MODE);
++ if (ret)
++ return ret;
++
++ return reset_control_deassert(xpcs_data->rstc);
++}
++
++static int qca8084_xpcs_clock_parent_set(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev)
++{
++ struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
++ struct qca8084_pcs_data *pcs_data = mdiodev_get_drvdata(pcs_mdiodev);
++ struct qca8084_xpcs_channel_priv xpcs_ch;
++ int ret, channel;
++
++ for (channel = 0; channel < QCA8084_CHANNEL_MAX; channel++) {
++ xpcs_ch = xpcs_data->xpcs_ch[channel];
++ ret = clk_set_parent(xpcs_ch.clks[XPCS_RX_SRC_CLK],
++ pcs_data->clks[PCS_RX_ROOT_CLK]);
++ if (ret)
++ return ret;
++
++ ret = clk_set_parent(xpcs_ch.clks[XPCS_TX_SRC_CLK],
++ pcs_data->clks[PCS_TX_ROOT_CLK]);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++int qca8084_qxgmii_set_mode(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev)
++{
++ int ret;
++
++ ret = qca8084_xpcs_clock_parent_set(xpcs_mdiodev, pcs_mdiodev);
++ if (ret)
++ return ret;
++
++ ret = qca8084_pcs_set_mode(xpcs_mdiodev, pcs_mdiodev);
++ if (ret)
++ return ret;
++
++ return qca8084_xpcs_set_mode(xpcs_mdiodev);
++}
+--- a/drivers/net/phy/qcom/qca8084_serdes.h
++++ b/drivers/net/phy/qcom/qca8084_serdes.h
+@@ -15,4 +15,6 @@ struct mdio_device *qca8084_package_pcs_
+ struct mdio_device *qca8084_package_xpcs_probe(struct device_node *xpcs_np);
+ void qca8084_package_xpcs_and_pcs_remove(struct mdio_device *xpcs_mdiodev,
+ struct mdio_device *pcs_mdiodev);
++int qca8084_qxgmii_set_mode(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev);
+ #endif /* _QCA8084_SERDES_H_ */
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -926,6 +926,14 @@ static int qca8084_phy_package_config_in
+
+ usleep_range(10000, 11000);
+
++ /* Configure PCS working on 10G-QXGMII mode */
++ if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII) {
++ ret = qca8084_qxgmii_set_mode(shared_priv->mdiodev[1],
++ shared_priv->mdiodev[0]);
++ if (ret)
++ return ret;
++ }
++
+ /* Initialize the PHY package clock and reset, which is the
+ * necessary config sequence after GPIO reset on the PHY package.
+ */
+@@ -1164,6 +1172,9 @@ static int qca8084_probe(struct phy_devi
+
+ static void qca8084_remove(struct phy_device *phydev)
+ {
++ if (phydev->interface != PHY_INTERFACE_MODE_10G_QXGMII)
++ return;
++
+ if (phy_package_remove_once(phydev))
+ qca8084_phy_package_remove_once(phydev);
+ }
--- /dev/null
+From 2f5b7e167d847a5b5b74a91f991d48635453c55f Mon Sep 17 00:00:00 2001
+From: Luo Jie <quic_luoj@quicinc.com>
+Date: Mon, 23 Sep 2024 21:24:56 +0800
+Subject: [PATCH] net: phy: qca808x: Add QCA8084 SerDes speed config
+
+When the link of PHY is changed, the XPCS channel needs to be
+configured to adapt the current link status.
+
+Change-Id: I50d8973691dff133fc6bec1e9a1043bb646811fc
+Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
+Alex G: Use phy_package_get_*() accessors
+Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
+---
+ drivers/net/phy/qcom/qca8084_serdes.c | 159 ++++++++++++++++++++++++++
+ drivers/net/phy/qcom/qca8084_serdes.h | 3 +
+ drivers/net/phy/qcom/qca808x.c | 15 ++-
+ 3 files changed, 175 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/qcom/qca8084_serdes.c
++++ b/drivers/net/phy/qcom/qca8084_serdes.c
+@@ -4,6 +4,7 @@
+ */
+
+ #include <linux/clk.h>
++#include <linux/clk-provider.h>
+ #include <linux/dev_printk.h>
+ #include <linux/mdio.h>
+ #include <linux/of.h>
+@@ -55,6 +56,13 @@
+ #define BYPASS_TUNNING_IPG 0x189
+ #define BYPASS_TUNNING_IPG_MASK GENMASK(11, 0)
+
++#define QP_USXG_RESET 0x18c
++#define QP_USXG_SGMII_FUNC_RESET BIT(4)
++#define QP_USXG_P3_FUNC_RESET BIT(3)
++#define QP_USXG_P2_FUNC_RESET BIT(2)
++#define QP_USXG_P1_FUNC_RESET BIT(1)
++#define QP_USXG_P0_FUNC_RESET BIT(0)
++
+ /* MDIO_MMD_PCS register */
+ #define PCS_CONTROL2 0x7
+ #define PCS_TYPE_MASK GENMASK(3, 0)
+@@ -107,6 +115,9 @@
+ #define TX_CONFIG BIT(3)
+ #define AUTO_NEGOTIATION_CMPLT_INTR BIT(0)
+
++#define PCS_ERR_SEL 0x8002
++#define PCS_AN_COMPLETE BIT(0)
++
+ #define XAUI_CONTROL 0x8004
+ #define TX_IPG_CHECK_DISABLE BIT(0)
+
+@@ -621,3 +632,151 @@ int qca8084_qxgmii_set_mode(struct mdio_
+
+ return qca8084_xpcs_set_mode(xpcs_mdiodev);
+ }
++
++static int qca8084_pcs_ipg_tune_reset(struct mdio_device *mdio_dev,
++ int reset_function)
++{
++ int ret;
++
++ ret = mdiodev_c45_modify(mdio_dev, MDIO_MMD_PMAPMD, QP_USXG_RESET,
++ reset_function, 0);
++ if (ret)
++ return ret;
++
++ usleep_range(1000, 1100);
++
++ return mdiodev_c45_modify(mdio_dev, MDIO_MMD_PMAPMD, QP_USXG_RESET,
++ reset_function, reset_function);
++}
++
++static int qca8084_xpcs_an_restart(struct mdio_device *xpcs_mdiodev,
++ int channel)
++{
++ int ret, mmd;
++
++ mmd = qca8084_xpcs_ch_mmd[channel];
++
++ /* Restart auto-negotiation */
++ ret = mdiodev_c45_modify(xpcs_mdiodev, mmd, MII_CONTROL,
++ AUTO_NEGOTIATION_RESTART,
++ AUTO_NEGOTIATION_RESTART);
++ if (ret)
++ return ret;
++
++ usleep_range(1000, 1100);
++
++ /* Clear pcs auto-negotiation complete interrupt */
++ return mdiodev_c45_modify(xpcs_mdiodev, mmd, PCS_ERR_SEL,
++ PCS_AN_COMPLETE, 0);
++}
++
++void qca8084_qxgmii_set_speed(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev,
++ int channel, int speed)
++{
++ struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
++ struct qca8084_xpcs_channel_priv *xpcs_ch;
++ int mmd, i, ret, xpcs_rate;
++ unsigned long rate;
++
++ for (i = 0; i < QCA8084_CHANNEL_MAX; i++) {
++ xpcs_ch = &(xpcs_data->xpcs_ch[channel]);
++ if (channel == xpcs_ch->ch_id)
++ break;
++ }
++
++ if (i == QCA8084_CHANNEL_MAX) {
++ dev_err(&xpcs_mdiodev->dev, "Invalid channel %d\n", channel);
++ return;
++ }
++
++ mmd = qca8084_xpcs_ch_mmd[channel];
++
++ ret = qca8084_xpcs_an_restart(xpcs_mdiodev, channel);
++ if (ret)
++ return;
++
++ switch (speed) {
++ case SPEED_2500:
++ rate = 312500000;
++ xpcs_rate = PCS_SPEED_2500;
++ break;
++ case SPEED_1000:
++ rate = 125000000;
++ xpcs_rate = PCS_SPEED_1000;
++ break;
++ case SPEED_100:
++ rate = 25000000;
++ xpcs_rate = PCS_SPEED_100;
++ break;
++ case SPEED_10:
++ default:
++ rate = 2500000;
++ xpcs_rate = PCS_SPEED_10;
++ break;
++ }
++
++ clk_set_rate(xpcs_ch->clks[XPCS_RX_CLK], rate);
++ clk_set_rate(xpcs_ch->clks[XPCS_TX_CLK], rate);
++
++ /* XGMII takes the different clock rate 78.125Mhz from XPCS clock
++ * when linked at 2500M.
++ */
++ if (speed == SPEED_2500)
++ rate = 78125000;
++
++ clk_set_rate(xpcs_ch->clks[XPCS_XGMII_RX_CLK], rate);
++ clk_set_rate(xpcs_ch->clks[XPCS_XGMII_TX_CLK], rate);
++
++ ret = mdiodev_c45_modify(xpcs_mdiodev, mmd, MII_CONTROL,
++ PCS_SPEED_2500 | PCS_SPEED_1000 |
++ PCS_SPEED_100 | PCS_SPEED_10,
++ xpcs_rate);
++ if (ret)
++ return;
++
++ /* Disable clocks if link down with unknown speed. The channel clocks
++ * are disabled by default, __clk_is_enabled() is used to avoid
++ * disabling the clocks that is already in the disabled status.
++ */
++ if (speed == SPEED_UNKNOWN) {
++ if (__clk_is_enabled(xpcs_ch->clks[XPCS_RX_CLK]))
++ clk_disable_unprepare(xpcs_ch->clks[XPCS_RX_CLK]);
++ if (__clk_is_enabled(xpcs_ch->clks[XPCS_TX_CLK]))
++ clk_disable_unprepare(xpcs_ch->clks[XPCS_TX_CLK]);
++ if (__clk_is_enabled(xpcs_ch->clks[XPCS_PORT_RX_CLK]))
++ clk_disable_unprepare(xpcs_ch->clks[XPCS_PORT_RX_CLK]);
++ if (__clk_is_enabled(xpcs_ch->clks[XPCS_PORT_TX_CLK]))
++ clk_disable_unprepare(xpcs_ch->clks[XPCS_PORT_TX_CLK]);
++ if (__clk_is_enabled(xpcs_ch->clks[XPCS_XGMII_RX_CLK]))
++ clk_disable_unprepare(xpcs_ch->clks[XPCS_XGMII_RX_CLK]);
++ if (__clk_is_enabled(xpcs_ch->clks[XPCS_XGMII_TX_CLK]))
++ clk_disable_unprepare(xpcs_ch->clks[XPCS_XGMII_TX_CLK]);
++ } else {
++ clk_prepare_enable(xpcs_ch->clks[XPCS_RX_CLK]);
++ clk_prepare_enable(xpcs_ch->clks[XPCS_TX_CLK]);
++ clk_prepare_enable(xpcs_ch->clks[XPCS_PORT_RX_CLK]);
++ clk_prepare_enable(xpcs_ch->clks[XPCS_PORT_TX_CLK]);
++ clk_prepare_enable(xpcs_ch->clks[XPCS_XGMII_RX_CLK]);
++ clk_prepare_enable(xpcs_ch->clks[XPCS_XGMII_TX_CLK]);
++ }
++
++ msleep(100);
++
++ ret = reset_control_reset(xpcs_ch->rstcs);
++ if (ret)
++ return;
++
++ /* Reset IPG tune of PCS device. */
++ ret = qca8084_pcs_ipg_tune_reset(pcs_mdiodev, BIT(channel));
++ if (ret)
++ return;
++
++ if (channel == 0)
++ mdiodev_c45_modify(xpcs_mdiodev, MDIO_MMD_PCS, DIG_CTRL1,
++ FIFO_RESET_CH0, FIFO_RESET_CH0);
++ else
++ mdiodev_c45_modify(xpcs_mdiodev, mmd, DIG_CTRL1,
++ FIFO_RESET_CH1_CH2_CH3,
++ FIFO_RESET_CH1_CH2_CH3);
++}
+--- a/drivers/net/phy/qcom/qca8084_serdes.h
++++ b/drivers/net/phy/qcom/qca8084_serdes.h
+@@ -17,4 +17,7 @@ void qca8084_package_xpcs_and_pcs_remove
+ struct mdio_device *pcs_mdiodev);
+ int qca8084_qxgmii_set_mode(struct mdio_device *xpcs_mdiodev,
+ struct mdio_device *pcs_mdiodev);
++void qca8084_qxgmii_set_speed(struct mdio_device *xpcs_mdiodev,
++ struct mdio_device *pcs_mdiodev,
++ int channel, int speed);
+ #endif /* _QCA8084_SERDES_H_ */
+--- a/drivers/net/phy/qcom/qca808x.c
++++ b/drivers/net/phy/qcom/qca808x.c
+@@ -976,6 +976,7 @@ static int qca8084_config_init(struct ph
+
+ static void qca8084_link_change_notify(struct phy_device *phydev)
+ {
++ struct qca808x_shared_priv *shared_priv;
+ int ret;
+
+ /* Assert the FIFO between PHY and MAC. */
+@@ -1007,14 +1008,24 @@ static void qca8084_link_change_notify(s
+ }
+ }
+
+- /* Enable IPG level 10 to 11 tuning for link speed 1000M in the
++ /* Enable IPG level 10 to 11 tuning for link speed 1000M and
++ * configure the related XPCS channel with the phydev in the
+ * 10G_QXGMII mode.
+ */
+- if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII)
++ if (phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII) {
++ shared_priv = phy_package_get_priv(phydev);
++ struct qca808x_priv *priv = phydev->priv;
++
+ phy_modify_mmd(phydev, MDIO_MMD_AN, QCA8084_MMD7_IPG_OP,
+ QCA8084_IPG_10_TO_11_EN,
+ phydev->speed == SPEED_1000 ?
+ QCA8084_IPG_10_TO_11_EN : 0);
++
++ qca8084_qxgmii_set_speed(shared_priv->mdiodev[1],
++ shared_priv->mdiodev[0],
++ priv->channel_id,
++ phydev->speed);
++ }
+ }
+
+ /* QCA8084 is a four-port PHY, which integrates the clock controller,