diff --git a/target/linux/qualcommbe/config-6.18 b/target/linux/qualcommbe/config-6.18 new file mode 100644 index 0000000000..360b2abd3c --- /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 0000000000..c38a7eb09f --- /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 0000000000..b09fccf32b --- /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 0000000000..7bec20d943 --- /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 0000000000..393923a343 --- /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 0000000000..d188539aaa --- /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 0000000000..c094e6e016 --- /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 0000000000..cc7192158a --- /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 0000000000..e0b84b1218 --- /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 0000000000..04314f59ea --- /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 0000000000..a9223c5a35 --- /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 0000000000..360517f9b5 --- /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 0000000000..8ac94c84c7 --- /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 0000000000..e00a582831 --- /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 0000000000..c1673ae645 --- /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 0000000000..e74fc0b09f --- /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 0000000000..50346520b3 --- /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 0000000000..5af66c290f --- /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 0000000000..1112d4e072 --- /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 0000000000..bae262a01c --- /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 0000000000..7d071c2e25 --- /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 0000000000..b1cddffc6a --- /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 0000000000..ccdeb9ded4 --- /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 0000000000..8196ff601c --- /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 0000000000..89c09ff9ea --- /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 0000000000..429006c7bc --- /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 0000000000..f55879ae48 --- /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 0000000000..5f38e25b37 --- /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 0000000000..be16222b96 --- /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 0000000000..86949a2f3b --- /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 0000000000..fe29d76e36 --- /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 0000000000..176b7d6bb4 --- /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 0000000000..f1dcb51a87 --- /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 0000000000..fc0764284d --- /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 0000000000..e05748f0f3 --- /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 0000000000..28a48163a6 --- /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 0000000000..e748bca604 --- /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 0000000000..3517ded100 --- /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 0000000000..d6292f8bf5 --- /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 0000000000..b108366b1a --- /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 0000000000..1430692a6f --- /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 0000000000..856a1ed4cc --- /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 0000000000..ae81ddb19d --- /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 0000000000..0160efdacb --- /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 0000000000..55de486ed3 --- /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 0000000000..eb10a00745 --- /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 0000000000..39d300de80 --- /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 0000000000..0bdfb0c897 --- /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 0000000000..4e0103db57 --- /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 0000000000..65eb3c6c20 --- /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 0000000000..c6970015d7 --- /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 0000000000..79af169c8e --- /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 0000000000..a0d15cf2a0 --- /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 0000000000..3893c5cd8d --- /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 0000000000..640a2a9f02 --- /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 0000000000..f93ed0c37e --- /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 0000000000..b7a703529c --- /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 0000000000..e508bad9a6 --- /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 0000000000..950c9faf3c --- /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 0000000000..372572a24e --- /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 0000000000..c32f077a01 --- /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 0000000000..725079c810 --- /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 0000000000..5bdf3e9299 --- /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 0000000000..fbdebec13a --- /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 0000000000..37aa60d8ff --- /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 0000000000..2563ac8396 --- /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 0000000000..6ec8f2634e --- /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 0000000000..b02782e7b3 --- /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 0000000000..63a523ae1c --- /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 0000000000..e46c6042ae --- /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 0000000000..a0c84bddeb --- /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 0000000000..688d7ac474 --- /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 0000000000..1244837fd5 --- /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,