From: Alexandru Gagniuc Date: Sat, 10 Jan 2026 21:21:08 +0000 (-0600) Subject: qualcommbe: create files for v6.18 from v6.12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0efbee297a73085f71e766859cabbfdd046a3f97;p=thirdparty%2Fopenwrt.git qualcommbe: create files for v6.18 from v6.12 Create the config and relevant patches for 6.18 from 6.12. The "standard" openwrt devel process seems to be to move the files and restore the old ones. I find this process confusing, and I don't see any git benefits for doing things this way. So just copy the files. Signed-off-by: Alexandru Gagniuc Link: https://github.com/openwrt/openwrt/pull/21506 Signed-off-by: Robert Marko --- diff --git a/target/linux/qualcommbe/config-6.18 b/target/linux/qualcommbe/config-6.18 new file mode 100644 index 00000000000..360b2abd3cc --- /dev/null +++ b/target/linux/qualcommbe/config-6.18 @@ -0,0 +1,622 @@ +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 diff --git a/target/linux/qualcommbe/patches-6.18/0100-arm64-dts-qcom-ipq9574-Add-nsscc-node.patch b/target/linux/qualcommbe/patches-6.18/0100-arm64-dts-qcom-ipq9574-Add-nsscc-node.patch new file mode 100644 index 00000000000..c38a7eb09f1 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0100-arm64-dts-qcom-ipq9574-Add-nsscc-node.patch @@ -0,0 +1,54 @@ +From 52ebd52aa1906961142a2aba55d47a53b956847c Mon Sep 17 00:00:00 2001 +From: Devi Priya +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 +Signed-off-by: Devi Priya +Signed-off-by: Manikanta Mylavarapu +Link: https://lore.kernel.org/r/20250313110359.242491-6-quic_mmanikan@quicinc.com +Signed-off-by: Bjorn Andersson +--- + 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 { diff --git a/target/linux/qualcommbe/patches-6.18/0101-arm64-dts-qcom-ipq9574-fix-the-msi-interrupt-numbers.patch b/target/linux/qualcommbe/patches-6.18/0101-arm64-dts-qcom-ipq9574-fix-the-msi-interrupt-numbers.patch new file mode 100644 index 00000000000..b09fccf32b1 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0101-arm64-dts-qcom-ipq9574-fix-the-msi-interrupt-numbers.patch @@ -0,0 +1,44 @@ +From 2f2f5ae4d52ea882ba58f6b2fa6373a3d3db2bce Mon Sep 17 00:00:00 2001 +From: Manikanta Mylavarapu +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 +Link: https://lore.kernel.org/r/20250313071422.510-1-quic_mmanikan@quicinc.com +Signed-off-by: Bjorn Andersson +--- + 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 = , +- , +- , +- , +- , +- , +- , +- ; ++ interrupts = , ++ , ++ , ++ , ++ , ++ , ++ , ++ ; + interrupt-names = "msi0", + "msi1", + "msi2", diff --git a/target/linux/qualcommbe/patches-6.18/0103-arm64-dts-qcom-ipq9574-Add-SPI-nand-support.patch b/target/linux/qualcommbe/patches-6.18/0103-arm64-dts-qcom-ipq9574-Add-SPI-nand-support.patch new file mode 100644 index 00000000000..7bec20d9431 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0103-arm64-dts-qcom-ipq9574-Add-SPI-nand-support.patch @@ -0,0 +1,50 @@ +From 583299efa34c4a484b211f84c63aee78b6c2b469 Mon Sep 17 00:00:00 2001 +From: Md Sadre Alam +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 +Link: https://lore.kernel.org/r/20250306113357.126602-2-quic_mdalam@quicinc.com +Signed-off-by: Bjorn Andersson +--- + 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 = ; ++ 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>; diff --git a/target/linux/qualcommbe/patches-6.18/0104-arm64-dts-qcom-ipq9574-Enable-SPI-NAND-for-ipq9574.patch b/target/linux/qualcommbe/patches-6.18/0104-arm64-dts-qcom-ipq9574-Enable-SPI-NAND-for-ipq9574.patch new file mode 100644 index 00000000000..393923a343a --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0104-arm64-dts-qcom-ipq9574-Enable-SPI-NAND-for-ipq9574.patch @@ -0,0 +1,68 @@ +From a7c88bc81632974c0708308493aefb1f871b65fa Mon Sep 17 00:00:00 2001 +From: Md Sadre Alam +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 +Signed-off-by: Md Sadre Alam +Link: https://lore.kernel.org/r/20250306113357.126602-3-quic_mdalam@quicinc.com +Signed-off-by: Bjorn Andersson +--- + .../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 { diff --git a/target/linux/qualcommbe/patches-6.18/0105-arm64-dts-qcom-ipq9574-Remove-eMMC-node.patch b/target/linux/qualcommbe/patches-6.18/0105-arm64-dts-qcom-ipq9574-Remove-eMMC-node.patch new file mode 100644 index 00000000000..d188539aaa7 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0105-arm64-dts-qcom-ipq9574-Remove-eMMC-node.patch @@ -0,0 +1,37 @@ +From 0156e327aa854be5eb9cbec9d020be1026b5b446 Mon Sep 17 00:00:00 2001 +From: Md Sadre Alam +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 +Signed-off-by: Md Sadre Alam +Link: https://lore.kernel.org/r/20250306113357.126602-4-quic_mdalam@quicinc.com +Signed-off-by: Bjorn Andersson +--- + 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 { diff --git a/target/linux/qualcommbe/patches-6.18/0301-arm64-dts-qcom-Add-IPQ9574-MDIO-device-node.patch b/target/linux/qualcommbe/patches-6.18/0301-arm64-dts-qcom-Add-IPQ9574-MDIO-device-node.patch new file mode 100644 index 00000000000..c094e6e016a --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0301-arm64-dts-qcom-Add-IPQ9574-MDIO-device-node.patch @@ -0,0 +1,49 @@ +From 657833a74f532262d415fa2ca354b69f4a97353c Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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"; diff --git a/target/linux/qualcommbe/patches-6.18/0302-arm64-dts-qcom-ipq9574-Use-usb-phy-for-node-names.patch b/target/linux/qualcommbe/patches-6.18/0302-arm64-dts-qcom-ipq9574-Use-usb-phy-for-node-names.patch new file mode 100644 index 00000000000..cc7192158a0 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0302-arm64-dts-qcom-ipq9574-Use-usb-phy-for-node-names.patch @@ -0,0 +1,34 @@ +From 91467ca0db1654644b2168f882f223d47dcfb9c1 Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +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 +--- + 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>; diff --git a/target/linux/qualcommbe/patches-6.18/0304-arm64-dts-qcom-ipq9574-add-QPIC-SPI-NAND-default-par.patch b/target/linux/qualcommbe/patches-6.18/0304-arm64-dts-qcom-ipq9574-add-QPIC-SPI-NAND-default-par.patch new file mode 100644 index 00000000000..e0b84b12185 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0304-arm64-dts-qcom-ipq9574-add-QPIC-SPI-NAND-default-par.patch @@ -0,0 +1,50 @@ +From be44d0251a2540f3b8d7205e0bc6659704366711 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + .../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>; ++ }; ++ }; + }; + }; + diff --git a/target/linux/qualcommbe/patches-6.18/0305-arm64-dts-qcom-add-partition-table-for-ipq9574-rdp-c.patch b/target/linux/qualcommbe/patches-6.18/0305-arm64-dts-qcom-add-partition-table-for-ipq9574-rdp-c.patch new file mode 100644 index 00000000000..04314f59ea2 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0305-arm64-dts-qcom-add-partition-table-for-ipq9574-rdp-c.patch @@ -0,0 +1,174 @@ +From 47c7ae9715d76054d98e8407dbb8ca1cf42fd587 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + .../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>; ++ }; ++ }; ++ }; ++ }; + }; + }; + diff --git a/target/linux/qualcommbe/patches-6.18/0306-dt-bindings-net-Document-Qualcomm-QCA8084-PHY-packag.patch b/target/linux/qualcommbe/patches-6.18/0306-dt-bindings-net-Document-Qualcomm-QCA8084-PHY-packag.patch new file mode 100644 index 00000000000..a9223c5a351 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0306-dt-bindings-net-Document-Qualcomm-QCA8084-PHY-packag.patch @@ -0,0 +1,536 @@ +From 7b1c4e22532ded6b20ee41936fa38b5ca1e61ff9 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Update to match the patches that will be upstream. +Signed-off-by: Alexandru Gagniuc +--- + .../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 ++ ++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 ++ #include ++ #include ++ ++ 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 = ; ++ 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 diff --git a/target/linux/qualcommbe/patches-6.18/0307-net-phy-qca808x-Add-QCA8084-ethernet-phy-support.patch b/target/linux/qualcommbe/patches-6.18/0307-net-phy-qca808x-Add-QCA8084-ethernet-phy-support.patch new file mode 100644 index 00000000000..360517f9b58 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0307-net-phy-qca808x-Add-QCA8084-ethernet-phy-support.patch @@ -0,0 +1,144 @@ +From 60c44842f9611be237ab3f68afe8ebf2d9595fb2 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Update to match the patches that will be upstream. +Signed-off-by: Alexandru Gagniuc +--- + 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) }, + { } + }; + diff --git a/target/linux/qualcommbe/patches-6.18/0308-net-phy-qca808x-Add-config_init-function-for-QCA8084.patch b/target/linux/qualcommbe/patches-6.18/0308-net-phy-qca808x-Add-config_init-function-for-QCA8084.patch new file mode 100644 index 00000000000..8ac94c84c75 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0308-net-phy-qca808x-Add-config_init-function-for-QCA8084.patch @@ -0,0 +1,85 @@ +From c052b9a4ab869cc54976402b3f9dbdef5bdb9f27 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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); diff --git a/target/linux/qualcommbe/patches-6.18/0309-net-phy-qca808x-Add-link_change_notify-function-for-.patch b/target/linux/qualcommbe/patches-6.18/0309-net-phy-qca808x-Add-link_change_notify-function-for-.patch new file mode 100644 index 00000000000..e00a5828312 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0309-net-phy-qca808x-Add-link_change_notify-function-for-.patch @@ -0,0 +1,90 @@ +From aec49c172cd9c739c1d97ff2d42b9718bb20b609 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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); diff --git a/target/linux/qualcommbe/patches-6.18/0310-net-phy-qca808x-Add-register-access-support-routines.patch b/target/linux/qualcommbe/patches-6.18/0310-net-phy-qca808x-Add-register-access-support-routines.patch new file mode 100644 index 00000000000..c1673ae645c --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0310-net-phy-qca808x-Add-register-access-support-routines.patch @@ -0,0 +1,125 @@ +From cea8043def0c0867370c2efd5a1cd73bf4d3e5ba Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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; diff --git a/target/linux/qualcommbe/patches-6.18/0311-net-phy-qca808x-Add-QCA8084-probe-function.patch b/target/linux/qualcommbe/patches-6.18/0311-net-phy-qca808x-Add-QCA8084-probe-function.patch new file mode 100644 index 00000000000..e74fc0b09f9 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0311-net-phy-qca808x-Add-QCA8084-probe-function.patch @@ -0,0 +1,162 @@ +From a7fe2c13f3188bf01b60fb15063d028c76dd2f1a Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: include + 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 + +freckup c89414adf2ec7c + +Signed-off-by: Alexandru Gagniuc +--- + 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 + #include ++#include ++#include ++#include + ++#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); diff --git a/target/linux/qualcommbe/patches-6.18/0312-net-phy-qca808x-Add-package-clocks-and-resets-for-QC.patch b/target/linux/qualcommbe/patches-6.18/0312-net-phy-qca808x-Add-package-clocks-and-resets-for-QC.patch new file mode 100644 index 00000000000..50346520b31 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0312-net-phy-qca808x-Add-package-clocks-and-resets-for-QC.patch @@ -0,0 +1,135 @@ +From 57379fe257895b374d35ce6578ecd62ce1cc1a4d Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Use accessors for struct phy_package_shared + Update to match the patches that will be upstream. +Signed-off-by: Alexandru Gagniuc +--- + 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; + diff --git a/target/linux/qualcommbe/patches-6.18/0313-net-phy-qca808x-Add-QCA8084-package-init-function.patch b/target/linux/qualcommbe/patches-6.18/0313-net-phy-qca808x-Add-QCA8084-package-init-function.patch new file mode 100644 index 00000000000..5af66c290fc --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0313-net-phy-qca808x-Add-QCA8084-package-init-function.patch @@ -0,0 +1,177 @@ +From d39dc53424bcc778f1e468015490577e7bf0c7b6 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Use phy_package_get_*() accessors + Update to match the patches that will be upstream. +Signed-off-by: Alexandru Gagniuc +--- + 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 + #include + #include + #include +@@ -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), diff --git a/target/linux/qualcommbe/patches-6.18/0314-dt-bindings-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch b/target/linux/qualcommbe/patches-6.18/0314-dt-bindings-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch new file mode 100644 index 00000000000..1112d4e0729 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0314-dt-bindings-net-pcs-Add-Ethernet-PCS-for-Qualcomm-IP.patch @@ -0,0 +1,234 @@ +From 5f650721c4b232a14a1a3e25b686f2234faee961 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Signed-off-by: Lei Wei +--- + .../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 ++ ++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 ++ ++ 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 */ diff --git a/target/linux/qualcommbe/patches-6.18/0315-net-pcs-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch b/target/linux/qualcommbe/patches-6.18/0315-net-pcs-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch new file mode 100644 index 00000000000..bae262a01c1 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0315-net-pcs-Add-PCS-driver-for-Qualcomm-IPQ9574-SoC.patch @@ -0,0 +1,301 @@ +From e404519d9f3e5e7d661cb105d3766d87e37e4ef5 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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 "); diff --git a/target/linux/qualcommbe/patches-6.18/0316-net-pcs-qcom-ipq9574-Add-PCS-instantiation-and-phyli.patch b/target/linux/qualcommbe/patches-6.18/0316-net-pcs-qcom-ipq9574-Add-PCS-instantiation-and-phyli.patch new file mode 100644 index 00000000000..7d071c2e25b --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0316-net-pcs-qcom-ipq9574-Add-PCS-instantiation-and-phyli.patch @@ -0,0 +1,555 @@ +From 10b609ddbf4d369c80098efa39451ef3973759b5 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Alex G: remove phylink_pcs .neg_mode boolean +Signed-off-by: Alexandru Gagniuc +--- + 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 + #include + #include ++#include ++#include ++#include + #include ++#include + #include + #include + + #include + ++/* 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 */ diff --git a/target/linux/qualcommbe/patches-6.18/0317-net-pcs-qcom-ipq9574-Add-USXGMII-interface-mode-supp.patch b/target/linux/qualcommbe/patches-6.18/0317-net-pcs-qcom-ipq9574-Add-USXGMII-interface-mode-supp.patch new file mode 100644 index 00000000000..b1cddffc6af --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0317-net-pcs-qcom-ipq9574-Add-USXGMII-interface-mode-supp.patch @@ -0,0 +1,272 @@ +From 4923ca63214a4e6bbee1b3f8f6b9b79f0fd3a3be Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +--- + 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; + } diff --git a/target/linux/qualcommbe/patches-6.18/0318-MAINTAINERS-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch b/target/linux/qualcommbe/patches-6.18/0318-MAINTAINERS-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch new file mode 100644 index 00000000000..ccdeb9ded48 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0318-MAINTAINERS-Add-maintainer-for-Qualcomm-IPQ9574-PCS-.patch @@ -0,0 +1,31 @@ +From 34d10a4eb8fea32bb79e3012dc9d8bd2dffb0df3 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +--- + 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 ++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 + L: linux-mtd@lists.infradead.org diff --git a/target/linux/qualcommbe/patches-6.18/0322-arm64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch b/target/linux/qualcommbe/patches-6.18/0322-arm64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch new file mode 100644 index 00000000000..8196ff601c4 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0322-arm64-dts-qcom-ipq9574-add-PCS-uniphy-nodes.patch @@ -0,0 +1,139 @@ +From d6f184181b076cbb54f152994f5bc73ce524a67e Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +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 +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 { diff --git a/target/linux/qualcommbe/patches-6.18/0323-dt-bindings-net-Add-PPE-for-Qualcomm-IPQ9574-SoC.patch b/target/linux/qualcommbe/patches-6.18/0323-dt-bindings-net-Add-PPE-for-Qualcomm-IPQ9574-SoC.patch new file mode 100644 index 00000000000..89c09ff9ea4 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0323-dt-bindings-net-Add-PPE-for-Qualcomm-IPQ9574-SoC.patch @@ -0,0 +1,432 @@ +From 48dc6d2fe28865a5c3d271aeb966b984a8085e7c Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 ++ - Lei Wei ++ - Suruchi Agarwal ++ - Pavithra R > ++ ++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 ++ #include ++ #include ++ ++ 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 = , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ ; ++ 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"; ++ }; ++ }; ++ }; diff --git a/target/linux/qualcommbe/patches-6.18/0324-docs-networking-Add-PPE-driver-documentation-for-Qua.patch b/target/linux/qualcommbe/patches-6.18/0324-docs-networking-Add-PPE-driver-documentation-for-Qua.patch new file mode 100644 index 00000000000..429006c7bc2 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0324-docs-networking-Add-PPE-driver-documentation-for-Qua.patch @@ -0,0 +1,227 @@ +From 9973b6610830146af1a12fe02d2d6440eb80b0f9 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Signed-off-by: Luo Jie +--- + .../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 ++ ++ ++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``. diff --git a/target/linux/qualcommbe/patches-6.18/0325-net-ethernet-qualcomm-Add-PPE-driver-for-IPQ9574-SoC.patch b/target/linux/qualcommbe/patches-6.18/0325-net-ethernet-qualcomm-Add-PPE-driver-for-IPQ9574-SoC.patch new file mode 100644 index 00000000000..f55879ae483 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0325-net-ethernet-qualcomm-Add-PPE-driver-for-IPQ9574-SoC.patch @@ -0,0 +1,339 @@ +From d1158f0282304c89217894aa346fc45364b95542 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++#include ++ ++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 diff --git a/target/linux/qualcommbe/patches-6.18/0326-net-ethernet-qualcomm-Initialize-PPE-buffer-manageme.patch b/target/linux/qualcommbe/patches-6.18/0326-net-ethernet-qualcomm-Initialize-PPE-buffer-manageme.patch new file mode 100644 index 00000000000..5f38e25b379 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0326-net-ethernet-qualcomm-Initialize-PPE-buffer-manageme.patch @@ -0,0 +1,328 @@ +From 6e639ab45348ee7a697db8b481fa6f8555280f58 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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 + + #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 ++#include ++#include ++#include ++ ++#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 ++ ++/* 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 diff --git a/target/linux/qualcommbe/patches-6.18/0327-net-ethernet-qualcomm-Initialize-PPE-queue-managemen.patch b/target/linux/qualcommbe/patches-6.18/0327-net-ethernet-qualcomm-Initialize-PPE-queue-managemen.patch new file mode 100644 index 00000000000..be16222b963 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0327-net-ethernet-qualcomm-Initialize-PPE-queue-managemen.patch @@ -0,0 +1,320 @@ +From 9be6c3590ef3c241e6a3cfd05291304a1f973bcf Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 + ++/* 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 diff --git a/target/linux/qualcommbe/patches-6.18/0328-net-ethernet-qualcomm-Initialize-the-PPE-scheduler-s.patch b/target/linux/qualcommbe/patches-6.18/0328-net-ethernet-qualcomm-Initialize-the-PPE-scheduler-s.patch new file mode 100644 index 00000000000..86949a2f3b7 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0328-net-ethernet-qualcomm-Initialize-the-PPE-scheduler-s.patch @@ -0,0 +1,1000 @@ +From 333edaf474cd707b0a04c57f255b56bc3c015789 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 + ++/* 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. diff --git a/target/linux/qualcommbe/patches-6.18/0329-net-ethernet-qualcomm-Initialize-PPE-queue-settings.patch b/target/linux/qualcommbe/patches-6.18/0329-net-ethernet-qualcomm-Initialize-PPE-queue-settings.patch new file mode 100644 index 00000000000..fe29d76e369 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0329-net-ethernet-qualcomm-Initialize-PPE-queue-settings.patch @@ -0,0 +1,522 @@ +From 63874f7c2e46f192e43e6214d66236372e36396c Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 diff --git a/target/linux/qualcommbe/patches-6.18/0330-net-ethernet-qualcomm-Initialize-PPE-service-code-se.patch b/target/linux/qualcommbe/patches-6.18/0330-net-ethernet-qualcomm-Initialize-PPE-service-code-se.patch new file mode 100644 index 00000000000..176b7d6bb44 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0330-net-ethernet-qualcomm-Initialize-PPE-service-code-se.patch @@ -0,0 +1,384 @@ +From 4147ce0d95816bded5c5e6cb276b1aa9f2620045 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 ++#include + #include + #include + #include +@@ -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 ++ + #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 diff --git a/target/linux/qualcommbe/patches-6.18/0331-net-ethernet-qualcomm-Initialize-PPE-port-control-se.patch b/target/linux/qualcommbe/patches-6.18/0331-net-ethernet-qualcomm-Initialize-PPE-port-control-se.patch new file mode 100644 index 00000000000..f1dcb51a877 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0331-net-ethernet-qualcomm-Initialize-PPE-port-control-se.patch @@ -0,0 +1,215 @@ +From 63af46200da794acda25cf8083bde0c1576b0859 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 diff --git a/target/linux/qualcommbe/patches-6.18/0332-net-ethernet-qualcomm-Initialize-PPE-RSS-hash-settin.patch b/target/linux/qualcommbe/patches-6.18/0332-net-ethernet-qualcomm-Initialize-PPE-RSS-hash-settin.patch new file mode 100644 index 00000000000..fc0764284da --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0332-net-ethernet-qualcomm-Initialize-PPE-RSS-hash-settin.patch @@ -0,0 +1,344 @@ +From 796be78fffeebe77237a6464da7ebe9807d670f0 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 diff --git a/target/linux/qualcommbe/patches-6.18/0333-net-ethernet-qualcomm-Initialize-PPE-queue-to-Ethern.patch b/target/linux/qualcommbe/patches-6.18/0333-net-ethernet-qualcomm-Initialize-PPE-queue-to-Ethern.patch new file mode 100644 index 00000000000..e05748f0f3a --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0333-net-ethernet-qualcomm-Initialize-PPE-queue-to-Ethern.patch @@ -0,0 +1,122 @@ +From c4a321bc120fabc318df165a7fcdeddfcf052253 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + .../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 diff --git a/target/linux/qualcommbe/patches-6.18/0334-net-ethernet-qualcomm-Initialize-PPE-L2-bridge-setti.patch b/target/linux/qualcommbe/patches-6.18/0334-net-ethernet-qualcomm-Initialize-PPE-L2-bridge-setti.patch new file mode 100644 index 00000000000..28a48163a60 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0334-net-ethernet-qualcomm-Initialize-PPE-L2-bridge-setti.patch @@ -0,0 +1,193 @@ +From cf7282d1e5712953516fa1cc0ffaae405491b3ca Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Signed-off-by: Luo Jie +--- + .../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 diff --git a/target/linux/qualcommbe/patches-6.18/0335-net-ethernet-qualcomm-Add-PPE-debugfs-support-for-PP.patch b/target/linux/qualcommbe/patches-6.18/0335-net-ethernet-qualcomm-Add-PPE-debugfs-support-for-PP.patch new file mode 100644 index 00000000000..e748bca6045 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0335-net-ethernet-qualcomm-Add-PPE-debugfs-support-for-PP.patch @@ -0,0 +1,950 @@ +From fc25088f79cccb934d69e563221068589565926f Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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 ++#include ++#include ++#include ++ ++#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 diff --git a/target/linux/qualcommbe/patches-6.18/0336-MAINTAINERS-Add-maintainer-for-Qualcomm-PPE-driver.patch b/target/linux/qualcommbe/patches-6.18/0336-MAINTAINERS-Add-maintainer-for-Qualcomm-PPE-driver.patch new file mode 100644 index 00000000000..3517ded1003 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0336-MAINTAINERS-Add-maintainer-for-Qualcomm-PPE-driver.patch @@ -0,0 +1,30 @@ +From 28098c348414fa97531449d4e27ba1587e67c2d9 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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 ++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 + L: linux-arm-msm@vger.kernel.org diff --git a/target/linux/qualcommbe/patches-6.18/0337-net-ethernet-qualcomm-Add-PPE-scheduler-config.patch b/target/linux/qualcommbe/patches-6.18/0337-net-ethernet-qualcomm-Add-PPE-scheduler-config.patch new file mode 100644 index 00000000000..d6292f8bf50 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0337-net-ethernet-qualcomm-Add-PPE-scheduler-config.patch @@ -0,0 +1,201 @@ +From 93cf3297818ee61607f0a8d1d34e4fb7fcde3cdf Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +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 +--- + .../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, diff --git a/target/linux/qualcommbe/patches-6.18/0338-net-ethernet-qualcomm-Add-phylink-support-for-PPE-MA.patch b/target/linux/qualcommbe/patches-6.18/0338-net-ethernet-qualcomm-Add-phylink-support-for-PPE-MA.patch new file mode 100644 index 00000000000..b108366b1a8 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0338-net-ethernet-qualcomm-Add-phylink-support-for-PPE-MA.patch @@ -0,0 +1,1040 @@ +From dbb3711ab25ea410ad5286b2f39dccd954cda225 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++/** ++ * 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 + ++/* 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 diff --git a/target/linux/qualcommbe/patches-6.18/0339-net-ethernet-qualcomm-Add-PPE-port-MAC-MIB-statistic.patch b/target/linux/qualcommbe/patches-6.18/0339-net-ethernet-qualcomm-Add-PPE-port-MAC-MIB-statistic.patch new file mode 100644 index 00000000000..1430692a6fb --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0339-net-ethernet-qualcomm-Add-PPE-port-MAC-MIB-statistic.patch @@ -0,0 +1,673 @@ +From dbcc0d01241a1353d8e11e764cf7fcd390ae3f1f Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +--- + 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 + ++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 diff --git a/target/linux/qualcommbe/patches-6.18/0340-net-ethernet-qualcomm-Add-PPE-port-MAC-address-and-E.patch b/target/linux/qualcommbe/patches-6.18/0340-net-ethernet-qualcomm-Add-PPE-port-MAC-address-and-E.patch new file mode 100644 index 00000000000..856a1ed4ccb --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0340-net-ethernet-qualcomm-Add-PPE-port-MAC-address-and-E.patch @@ -0,0 +1,172 @@ +From 55fbbc8ef90df27a16bca1613a793a578b79a384 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Alex G: use struct ethtool_keee instead of ethtool_eee +Signed-off-by: Alexandru Gagniuc +--- + 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 + ++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) diff --git a/target/linux/qualcommbe/patches-6.18/0341-net-ethernet-qualcomm-Add-API-to-configure-PPE-port-.patch b/target/linux/qualcommbe/patches-6.18/0341-net-ethernet-qualcomm-Add-API-to-configure-PPE-port-.patch new file mode 100644 index 00000000000..ae81ddb19d1 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0341-net-ethernet-qualcomm-Add-API-to-configure-PPE-port-.patch @@ -0,0 +1,78 @@ +From 3981aeae5dd43dea94a0ec10f0b2977ebd102560 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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 diff --git a/target/linux/qualcommbe/patches-6.18/0342-net-ethernet-qualcomm-Add-EDMA-support-for-QCOM-IPQ9.patch b/target/linux/qualcommbe/patches-6.18/0342-net-ethernet-qualcomm-Add-EDMA-support-for-QCOM-IPQ9.patch new file mode 100644 index 00000000000..0160efdacbc --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0342-net-ethernet-qualcomm-Add-EDMA-support-for-QCOM-IPQ9.patch @@ -0,0 +1,932 @@ +From 00d4f3cb4f5d1e6924151a4551f06b6a82bf0146 Mon Sep 17 00:00:00 2001 +From: Pavithra R +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 +Co-developed-by: Suruchi Agarwal +Signed-off-by: Suruchi Agarwal +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 + #include + ++#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 diff --git a/target/linux/qualcommbe/patches-6.18/0343-net-ethernet-qualcomm-Add-netdevice-support-for-QCOM.patch b/target/linux/qualcommbe/patches-6.18/0343-net-ethernet-qualcomm-Add-netdevice-support-for-QCOM.patch new file mode 100644 index 00000000000..55de486ed39 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0343-net-ethernet-qualcomm-Add-netdevice-support-for-QCOM.patch @@ -0,0 +1,397 @@ +From 5dc80c468c668d855d76b323f09bbadb95cc3147 Mon Sep 17 00:00:00 2001 +From: Suruchi Agarwal +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 +Signed-off-by: Pavithra R +Signed-off-by: Suruchi Agarwal +--- + 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 ++#include ++#include ++#include ++#include ++#include ++ ++#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 + #include + ++#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); + } + } diff --git a/target/linux/qualcommbe/patches-6.18/0344-net-ethernet-qualcomm-Add-Rx-Ethernet-DMA-support.patch b/target/linux/qualcommbe/patches-6.18/0344-net-ethernet-qualcomm-Add-Rx-Ethernet-DMA-support.patch new file mode 100644 index 00000000000..eb10a007457 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0344-net-ethernet-qualcomm-Add-Rx-Ethernet-DMA-support.patch @@ -0,0 +1,2454 @@ +From b5c8c5d3888328321e8be1db50b75dff8f514e51 Mon Sep 17 00:00:00 2001 +From: Suruchi Agarwal +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 +Signed-off-by: Pavithra R +Signed-off-by: Suruchi Agarwal +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 +--- + 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 + + #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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 + + #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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++#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 diff --git a/target/linux/qualcommbe/patches-6.18/0345-net-ethernet-qualcomm-Add-Tx-Ethernet-DMA-support.patch b/target/linux/qualcommbe/patches-6.18/0345-net-ethernet-qualcomm-Add-Tx-Ethernet-DMA-support.patch new file mode 100644 index 00000000000..39d300de804 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0345-net-ethernet-qualcomm-Add-Tx-Ethernet-DMA-support.patch @@ -0,0 +1,2363 @@ +From 339d3a5365f150a78ed405684e379fee3acdbe90 Mon Sep 17 00:00:00 2001 +From: Suruchi Agarwal +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 +Signed-off-by: Pavithra R +Signed-off-by: Suruchi Agarwal +--- + 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 + + #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 ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 diff --git a/target/linux/qualcommbe/patches-6.18/0346-net-ethernet-qualcomm-Add-miscellaneous-error-interr.patch b/target/linux/qualcommbe/patches-6.18/0346-net-ethernet-qualcomm-Add-miscellaneous-error-interr.patch new file mode 100644 index 00000000000..0bdfb0c8970 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0346-net-ethernet-qualcomm-Add-miscellaneous-error-interr.patch @@ -0,0 +1,730 @@ +From 8a924457c0b71acee96c8f78ef386e2a354a2aca Mon Sep 17 00:00:00 2001 +From: Suruchi Agarwal +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 +Signed-off-by: Pavithra R +Signed-off-by: Suruchi Agarwal +--- + 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 ++#include ++#include ++#include ++#include ++ ++#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 + #include ++#include + #include + #include + ++#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; + } diff --git a/target/linux/qualcommbe/patches-6.18/0347-net-ethernet-qualcomm-Add-ethtool-support-for-EDMA.patch b/target/linux/qualcommbe/patches-6.18/0347-net-ethernet-qualcomm-Add-ethtool-support-for-EDMA.patch new file mode 100644 index 00000000000..4e0103db574 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0347-net-ethernet-qualcomm-Add-ethtool-support-for-EDMA.patch @@ -0,0 +1,344 @@ +From bd61a680fb657eb65272225f18c93fe338c700da Mon Sep 17 00:00:00 2001 +From: Pavithra R +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 +Alex G: use struct ethtool_keee instead of ethtool_eee +Signed-off-by: Alexandru Gagniuc +--- + 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 ++#include ++#include ++#include ++#include ++ ++#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)) diff --git a/target/linux/qualcommbe/patches-6.18/0348-net-ethernet-qualcomm-Add-module-parameters-for-driv.patch b/target/linux/qualcommbe/patches-6.18/0348-net-ethernet-qualcomm-Add-module-parameters-for-driv.patch new file mode 100644 index 00000000000..65eb3c6c207 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0348-net-ethernet-qualcomm-Add-module-parameters-for-driv.patch @@ -0,0 +1,286 @@ +From 2ecec7e47e269e05cdd393c34aae51d4866070c6 Mon Sep 17 00:00:00 2001 +From: Pavithra R +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 +--- + 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, diff --git a/target/linux/qualcommbe/patches-6.18/0349-net-ethernet-qualcomm-Add-sysctl-for-RPS-bitmap.patch b/target/linux/qualcommbe/patches-6.18/0349-net-ethernet-qualcomm-Add-sysctl-for-RPS-bitmap.patch new file mode 100644 index 00000000000..c6970015d7d --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0349-net-ethernet-qualcomm-Add-sysctl-for-RPS-bitmap.patch @@ -0,0 +1,145 @@ +From dcac735a715c13a817d65ae371564cf2793330b2 Mon Sep 17 00:00:00 2001 +From: Pavithra R +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 +Alex G: Use **const** ctl_table argument for .proc_handler +Signed-off-by: Alexandru Gagniuc +--- + 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 diff --git a/target/linux/qualcommbe/patches-6.18/0350-net-ethernet-qualcomm-Add-support-for-label-property.patch b/target/linux/qualcommbe/patches-6.18/0350-net-ethernet-qualcomm-Add-support-for-label-property.patch new file mode 100644 index 00000000000..79af169c8e7 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0350-net-ethernet-qualcomm-Add-support-for-label-property.patch @@ -0,0 +1,48 @@ +From a809433c9b6a418dd886f12a5dcb3376f73bf2a7 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + 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; + } + diff --git a/target/linux/qualcommbe/patches-6.18/0351-net-ethernet-qualcomm-ppe-Fix-unmet-dependency-with-.patch b/target/linux/qualcommbe/patches-6.18/0351-net-ethernet-qualcomm-ppe-Fix-unmet-dependency-with-.patch new file mode 100644 index 00000000000..a0d15cf2a06 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0351-net-ethernet-qualcomm-ppe-Fix-unmet-dependency-with-.patch @@ -0,0 +1,30 @@ +From 9c4ad75f17788a64c1e37d0b9e19ca157e01c80a Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + 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 diff --git a/target/linux/qualcommbe/patches-6.18/0352-net-ethernet-qualcomm-ppe-select-correct-PCS-depende.patch b/target/linux/qualcommbe/patches-6.18/0352-net-ethernet-qualcomm-ppe-select-correct-PCS-depende.patch new file mode 100644 index 00000000000..3893c5cd8de --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0352-net-ethernet-qualcomm-ppe-select-correct-PCS-depende.patch @@ -0,0 +1,24 @@ +From ac41b401d274a4004027fa4000d801cd28c51f4c Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +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 +--- + 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 diff --git a/target/linux/qualcommbe/patches-6.18/0353-arm64-dts-qcom-Add-IPQ9574-PPE-base-device-node.patch b/target/linux/qualcommbe/patches-6.18/0353-arm64-dts-qcom-Add-IPQ9574-PPE-base-device-node.patch new file mode 100644 index 00000000000..640a2a9f02d --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0353-arm64-dts-qcom-Add-IPQ9574-PPE-base-device-node.patch @@ -0,0 +1,72 @@ +From bbf706ecfd4295d73c8217d5220573dd51d7a081 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Add "qcom_ppe" label to PPE node +Signed-off-by: Alexandru Gagniuc +--- + 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 + #include + #include ++#include + #include + + / { +@@ -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>; diff --git a/target/linux/qualcommbe/patches-6.18/0354-arm64-dts-qcom-Add-EDMA-node-for-IPQ9574.patch b/target/linux/qualcommbe/patches-6.18/0354-arm64-dts-qcom-Add-EDMA-node-for-IPQ9574.patch new file mode 100644 index 00000000000..f93ed0c37e1 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0354-arm64-dts-qcom-Add-EDMA-node-for-IPQ9574.patch @@ -0,0 +1,91 @@ +From bd50babc7db2a35d98236a0386173dccd6c6374b Mon Sep 17 00:00:00 2001 +From: Pavithra R +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 +--- + 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 = , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ ; ++ 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 { diff --git a/target/linux/qualcommbe/patches-6.18/0355-arm64-dts-qcom-Add-IPQ9574-RDP433-port-node.patch b/target/linux/qualcommbe/patches-6.18/0355-arm64-dts-qcom-Add-IPQ9574-RDP433-port-node.patch new file mode 100644 index 00000000000..b7a703529cc --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0355-arm64-dts-qcom-Add-IPQ9574-RDP433-port-node.patch @@ -0,0 +1,197 @@ +From 001b663ecc5f838dac143623badae0e472749d8a Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +--- + 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"; ++ }; ++ }; ++}; diff --git a/target/linux/qualcommbe/patches-6.18/0356-arm64-dts-qcom-add-AQR-NVMEM-node-for-IPQ9574-RDP433.patch b/target/linux/qualcommbe/patches-6.18/0356-arm64-dts-qcom-add-AQR-NVMEM-node-for-IPQ9574-RDP433.patch new file mode 100644 index 00000000000..e508bad9a69 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0356-arm64-dts-qcom-add-AQR-NVMEM-node-for-IPQ9574-RDP433.patch @@ -0,0 +1,33 @@ +From 30b751f5984e295f0b5e7a2308b6103fae3322d2 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + 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"; + }; + }; + diff --git a/target/linux/qualcommbe/patches-6.18/0357-arm64-dts-qcom-Add-label-to-EDMA-port-for-IPQ9574-RD.patch b/target/linux/qualcommbe/patches-6.18/0357-arm64-dts-qcom-Add-label-to-EDMA-port-for-IPQ9574-RD.patch new file mode 100644 index 00000000000..950c9faf3ca --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0357-arm64-dts-qcom-Add-label-to-EDMA-port-for-IPQ9574-RD.patch @@ -0,0 +1,62 @@ +From b297d12d434191845cf8ae359466dcd8312ed21d Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +--- + 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>, diff --git a/target/linux/qualcommbe/patches-6.18/0358-clk-qcom-nsscc-Attach-required-NSSNOC-clock-to-PM-do.patch b/target/linux/qualcommbe/patches-6.18/0358-clk-qcom-nsscc-Attach-required-NSSNOC-clock-to-PM-do.patch new file mode 100644 index 00000000000..372572a24ec --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0358-clk-qcom-nsscc-Attach-required-NSSNOC-clock-to-PM-do.patch @@ -0,0 +1,73 @@ +From 6417cb20e854194a845d4ab092b92fd753c0e405 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +Signed-off-by: Christian Marangi +Alex G: Retrieve clocks by name rather than index. +Signed-off-by: Alexandru Gagniuc +--- + 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; + } + diff --git a/target/linux/qualcommbe/patches-6.18/0359-arm64-dts-qcom-ipq9574-add-NSSNOC-clock-to-nss-node.patch b/target/linux/qualcommbe/patches-6.18/0359-arm64-dts-qcom-ipq9574-add-NSSNOC-clock-to-nss-node.patch new file mode 100644 index 00000000000..c32f077a01a --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0359-arm64-dts-qcom-ipq9574-add-NSSNOC-clock-to-nss-node.patch @@ -0,0 +1,42 @@ +From 372bbae100ffe14908bfd8448143c6cdbea17e8d Mon Sep 17 00:00:00 2001 +From: Christian Marangi +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 +Alex G: Do not remove GCC_NSSCC_CLK ("bus") clock + Add clock-names for the new clocks +Signed-off-by: Alexandru Gagniuc +--- + 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>; diff --git a/target/linux/qualcommbe/patches-6.18/0360-clk-qcom-nsscc-ipq9574-fix-port5-clock-config.patch b/target/linux/qualcommbe/patches-6.18/0360-clk-qcom-nsscc-ipq9574-fix-port5-clock-config.patch new file mode 100644 index 00000000000..725079c8102 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0360-clk-qcom-nsscc-ipq9574-fix-port5-clock-config.patch @@ -0,0 +1,46 @@ +From fa691ff57c72a8f0bfeff1a9e86ae2d78765b0da Mon Sep 17 00:00:00 2001 +From: Mantas Pucka +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 +--- + 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), + }; + diff --git a/target/linux/qualcommbe/patches-6.18/0361-net-pcs-Add-10GBASER-interface-mode-support-to-IPQ-U.patch b/target/linux/qualcommbe/patches-6.18/0361-net-pcs-Add-10GBASER-interface-mode-support-to-IPQ-U.patch new file mode 100644 index 00000000000..5bdf3e9299f --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0361-net-pcs-Add-10GBASER-interface-mode-support-to-IPQ-U.patch @@ -0,0 +1,136 @@ +From 432c2a2da1e0f4a8e2c0fea191361832a7f90f36 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Alex G: Use regmap to read/write registers + Remove xpcs_reset deassert logic (to be implemented later) +Signed-off-by: Alexandru Gagniuc +--- + 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; diff --git a/target/linux/qualcommbe/patches-6.18/0362-net-pcs-Add-2500BASEX-interface-mode-support-to-IPQ-.patch b/target/linux/qualcommbe/patches-6.18/0362-net-pcs-Add-2500BASEX-interface-mode-support-to-IPQ-.patch new file mode 100644 index 00000000000..fbdebec13ac --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0362-net-pcs-Add-2500BASEX-interface-mode-support-to-IPQ-.patch @@ -0,0 +1,166 @@ +From 0d3a93e3a5544daec59d8f10ac5ccab39849536e Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +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 +--- + 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; diff --git a/target/linux/qualcommbe/patches-6.18/0363-net-pcs-Add-1000BASEX-interface-mode-support-to-IPQ-.patch b/target/linux/qualcommbe/patches-6.18/0363-net-pcs-Add-1000BASEX-interface-mode-support-to-IPQ-.patch new file mode 100644 index 00000000000..37aa60d8ff0 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0363-net-pcs-Add-1000BASEX-interface-mode-support-to-IPQ-.patch @@ -0,0 +1,104 @@ +From d82953614a4f09dd7479e1d3904351ff85d1d088 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Signed-off-by: Alexandru Gagniuc +--- + 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; diff --git a/target/linux/qualcommbe/patches-6.18/0364-net-pcs-Add-10G_QXGMII-interface-mode-support-to-IPQ.patch b/target/linux/qualcommbe/patches-6.18/0364-net-pcs-Add-10G_QXGMII-interface-mode-support-to-IPQ.patch new file mode 100644 index 00000000000..2563ac83960 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0364-net-pcs-Add-10G_QXGMII-interface-mode-support-to-IPQ.patch @@ -0,0 +1,267 @@ +From fc26c6f6c69149ce87c88d6878ae929b2a138063 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Signed-off-by: Alexandru Gagniuc +--- + 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: diff --git a/target/linux/qualcommbe/patches-6.18/0365-net-pcs-ipq-uniphy-control-MISC2-register-for-2.5G-s.patch b/target/linux/qualcommbe/patches-6.18/0365-net-pcs-ipq-uniphy-control-MISC2-register-for-2.5G-s.patch new file mode 100644 index 00000000000..6ec8f2634ef --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0365-net-pcs-ipq-uniphy-control-MISC2-register-for-2.5G-s.patch @@ -0,0 +1,70 @@ +From 87da3bbd25eb0a17e2c698120528e76c26b326d0 Mon Sep 17 00:00:00 2001 +From: Mantas Pucka +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 +--- + 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); diff --git a/target/linux/qualcommbe/patches-6.18/0367-net-pcs-ipq-uniphy-fix-USXGMII-link-up-failure.patch b/target/linux/qualcommbe/patches-6.18/0367-net-pcs-ipq-uniphy-fix-USXGMII-link-up-failure.patch new file mode 100644 index 00000000000..b02782e7b39 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0367-net-pcs-ipq-uniphy-fix-USXGMII-link-up-failure.patch @@ -0,0 +1,24 @@ +From bedf56b46ae53c4abb21eebb3e1d5a7483926dda Mon Sep 17 00:00:00 2001 +From: Mantas Pucka +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 +--- + 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; diff --git a/target/linux/qualcommbe/patches-6.18/0368-net-pcs-qcom-ipq9574-Update-IPQ9574-PCS-driver.patch b/target/linux/qualcommbe/patches-6.18/0368-net-pcs-qcom-ipq9574-Update-IPQ9574-PCS-driver.patch new file mode 100644 index 00000000000..63a523ae1c9 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0368-net-pcs-qcom-ipq9574-Update-IPQ9574-PCS-driver.patch @@ -0,0 +1,282 @@ +From b4e07a8a3ec3dc5f676238987556e2aff0b14028 Mon Sep 17 00:00:00 2001 +From: Lei Wei +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 +Signed-off-by: Alexandru Gagniuc +--- + .../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 ++ #include + + 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 + #include + #include ++#include + + #include + +@@ -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; diff --git a/target/linux/qualcommbe/patches-6.18/0370-net-phy-Add-phy_package_remove_once-helper.patch b/target/linux/qualcommbe/patches-6.18/0370-net-phy-Add-phy_package_remove_once-helper.patch new file mode 100644 index 00000000000..e46c6042ae4 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0370-net-phy-Add-phy_package_remove_once-helper.patch @@ -0,0 +1,36 @@ +From d11eba3e178a9d42a579c656b2c9b643f4ce3e1e Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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 { diff --git a/target/linux/qualcommbe/patches-6.18/0371-net-phy-qca808x-Add-QCA8084-SerDes-probe-and-remove-.patch b/target/linux/qualcommbe/patches-6.18/0371-net-phy-qca808x-Add-QCA8084-SerDes-probe-and-remove-.patch new file mode 100644 index 00000000000..a0c84bddeb2 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0371-net-phy-qca808x-Add-QCA8084-SerDes-probe-and-remove-.patch @@ -0,0 +1,437 @@ +From c12b79af730116936504afe97234f9afb6ac8fc0 Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Use phy_package_get_*() accessors +Signed-off-by: Alexandru Gagniuc +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++#include ++ ++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 + + #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); diff --git a/target/linux/qualcommbe/patches-6.18/0372-net-phy-qca808x-Add-QCA8084-SerDes-init-function.patch b/target/linux/qualcommbe/patches-6.18/0372-net-phy-qca808x-Add-QCA8084-SerDes-init-function.patch new file mode 100644 index 00000000000..688d7ac4745 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0372-net-phy-qca808x-Add-QCA8084-SerDes-init-function.patch @@ -0,0 +1,446 @@ +From d137b725f8f4a7d49a809dcd73c5b836495ec44d Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +--- + 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); + } diff --git a/target/linux/qualcommbe/patches-6.18/0373-net-phy-qca808x-Add-QCA8084-SerDes-speed-config.patch b/target/linux/qualcommbe/patches-6.18/0373-net-phy-qca808x-Add-QCA8084-SerDes-speed-config.patch new file mode 100644 index 00000000000..1244837fd52 --- /dev/null +++ b/target/linux/qualcommbe/patches-6.18/0373-net-phy-qca808x-Add-QCA8084-SerDes-speed-config.patch @@ -0,0 +1,251 @@ +From 2f5b7e167d847a5b5b74a91f991d48635453c55f Mon Sep 17 00:00:00 2001 +From: Luo Jie +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 +Alex G: Use phy_package_get_*() accessors +Signed-off-by: Alexandru Gagniuc +--- + 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 ++#include + #include + #include + #include +@@ -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,