diff --git a/config/boards/radxa-cubie-a5e.conf b/config/boards/radxa-cubie-a5e.conf new file mode 100644 index 000000000000..2aa758e2dc35 --- /dev/null +++ b/config/boards/radxa-cubie-a5e.conf @@ -0,0 +1,9 @@ +# Allwinner Cortex-A55 octa core 1/2/4GB RAM SoC +BOARD_NAME="radxa cubie a5e" +BOARDFAMILY="sun55iw3" +BOARD_MAINTAINER="Nick A, Juanesf91" +BOOTCONFIG="radxa-a5e_defconfig" +OVERLAY_PREFIX="sun55i-a527" +#BOOT_LOGO="desktop" +KERNEL_TARGET="dev" +FORCE_BOOTSCRIPT_UPDATE="yes" diff --git a/config/kernel/linux-sun55iw3-dev.config b/config/kernel/linux-sun55iw3-dev.config new file mode 100644 index 000000000000..e52cb09c0249 --- /dev/null +++ b/config/kernel/linux-sun55iw3-dev.config @@ -0,0 +1,1568 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_WATCH_QUEUE=y +CONFIG_AUDIT=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=18 +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BOOT_CONFIG_FORCE=y +CONFIG_EXPERT=y +CONFIG_KALLSYMS_ALL=y +CONFIG_PROFILING=y +CONFIG_ARCH_SUNXI=y +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BCM2835=y +CONFIG_ARCH_BCM_IPROC=y +CONFIG_ARCH_BCMBCA=y +CONFIG_ARCH_BRCMSTB=y +CONFIG_ARCH_MESON=y +CONFIG_ARCH_ROCKCHIP=y +# CONFIG_AMPERE_ERRATUM_AC03_CPU_38 is not set +CONFIG_ARM64_ERRATUM_834220=y +CONFIG_ARM64_ERRATUM_2441007=y +CONFIG_ARM64_ERRATUM_1286807=y +CONFIG_ARM64_ERRATUM_1542419=y +# CONFIG_ARM64_ERRATUM_2051678 is not set +# CONFIG_ARM64_ERRATUM_2077057 is not set +# CONFIG_ARM64_ERRATUM_2054223 is not set +# CONFIG_ARM64_ERRATUM_2067961 is not set +CONFIG_ARM64_ERRATUM_2441009=y +# CONFIG_ARM64_ERRATUM_2966298 is not set +# CONFIG_ARM64_ERRATUM_3194386 is not set +# CONFIG_CAVIUM_ERRATUM_22375 is not set +# CONFIG_CAVIUM_ERRATUM_23154 is not set +# CONFIG_CAVIUM_ERRATUM_27456 is not set +# CONFIG_CAVIUM_ERRATUM_30115 is not set +# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set +# CONFIG_FUJITSU_ERRATUM_010001 is not set +# CONFIG_HISILICON_ERRATUM_161600802 is not set +# CONFIG_HISILICON_ERRATUM_162100801 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set +# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set +# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set +CONFIG_ARM64_VA_BITS_48=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_CLUSTER=y +CONFIG_SCHED_SMT=y +CONFIG_NR_CPUS=8 +CONFIG_HZ_300=y +CONFIG_PARAVIRT_TIME_ACCOUNTING=y +# CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY is not set +# CONFIG_ARM64_PTR_AUTH_KERNEL is not set +CONFIG_ARM64_PSEUDO_NMI=y +CONFIG_ARM64_ACPI_PARKING_PROTOCOL=y +CONFIG_CMDLINE="console=ttyAMA0" +CONFIG_HIBERNATION=y +# CONFIG_HIBERNATION_SNAPSHOT_DEV is not set +CONFIG_PM_DEBUG=y +CONFIG_PM_TEST_SUSPEND=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_CPU_IDLE_GOV_TEO=y +CONFIG_ARM_PSCI_CPUIDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=m +CONFIG_CPU_FREQ_GOV_USERSPACE=m +CONFIG_CPU_FREQ_GOV_ONDEMAND=m +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=m +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPUFREQ_DT=y +CONFIG_ARM_ALLWINNER_SUN50I_CPUFREQ_NVMEM=y +CONFIG_ARM_SCPI_CPUFREQ=y +CONFIG_ARM_RASPBERRYPI_CPUFREQ=m +CONFIG_ARM_SCMI_CPUFREQ=m +CONFIG_ACPI_CPPC_CPUFREQ=y +# CONFIG_ACPI_CPPC_CPUFREQ_FIE is not set +CONFIG_ACPI=y +# CONFIG_ACPI_AC is not set +# CONFIG_ACPI_BATTERY is not set +CONFIG_ACPI_PCI_SLOT=y +CONFIG_ACPI_APEI=y +CONFIG_ACPI_APEI_GHES=y +CONFIG_ACPI_APEI_EINJ=y +# CONFIG_ACPI_APEI_EINJ_CXL is not set +CONFIG_ACPI_CONFIGFS=m +# CONFIG_ACPI_PCC is not set +CONFIG_PMIC_OPREGION=y +# CONFIG_ACPI_PRMT is not set +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_KPROBES=y +CONFIG_JUMP_LABEL=y +CONFIG_SECCOMP_CACHE_DEBUG=y +# CONFIG_RANDOMIZE_KSTACK_OFFSET is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_BLK_DEV_BSGLIB=y +CONFIG_BLK_DEV_INTEGRITY=y +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_BLK_WBT=y +CONFIG_BLK_SED_OPAL=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_BSD_DISKLABEL=y +CONFIG_BINFMT_MISC=y +CONFIG_ZSWAP=y +CONFIG_ZSWAP_DEFAULT_ON=y +CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=y +CONFIG_ZSWAP_COMPRESSOR_DEFAULT_ZSTD=y +CONFIG_ZSMALLOC_STAT=y +CONFIG_SLUB_TINY=y +# CONFIG_COMPAT_BRK is not set +CONFIG_KSM=y +CONFIG_CMA=y +CONFIG_CMA_DEBUGFS=y +CONFIG_CMA_SYSFS=y +CONFIG_CMA_AREAS=7 +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=m +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=m +CONFIG_TLS=m +CONFIG_XFRM_USER=m +CONFIG_XFRM_INTERFACE=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_MIGRATE=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_XDP_SOCKETS=y +CONFIG_XDP_SOCKETS_DIAG=m +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +CONFIG_SYN_COOKIES=y +CONFIG_INET_UDP_DIAG=m +CONFIG_INET_RAW_DIAG=m +CONFIG_INET_DIAG_DESTROY=y +CONFIG_IPV6=m +CONFIG_IPV6_ILA=m +# CONFIG_IPV6_SIT is not set +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +# CONFIG_NETFILTER_EGRESS is not set +CONFIG_NF_CONNTRACK=m +# CONFIG_NF_CT_PROTO_DCCP is not set +# CONFIG_NF_CT_PROTO_SCTP is not set +# CONFIG_NF_CT_PROTO_UDPLITE is not set +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NETFILTER_NETLINK_GLUE_CT=y +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_CT=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NF_SOCKET_IPV4=m +CONFIG_NF_DUP_IPV4=m +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_RAW=m +CONFIG_NF_SOCKET_IPV6=m +CONFIG_NF_DUP_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_BRIDGE=m +# CONFIG_BRIDGE_IGMP_SNOOPING is not set +CONFIG_NET_DSA=m +CONFIG_NET_DSA_TAG_AR9331=m +CONFIG_NET_DSA_TAG_BRCM=m +CONFIG_NET_DSA_TAG_BRCM_PREPEND=m +CONFIG_NET_DSA_TAG_QCA=m +CONFIG_NET_DSA_TAG_LAN9303=m +CONFIG_VSOCKETS=m +# CONFIG_VSOCKETS_LOOPBACK is not set +CONFIG_NETLINK_DIAG=m +# CONFIG_PCPU_DEV_REFCNT is not set +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +# CONFIG_BT_LE_L2CAP_ECRED is not set +CONFIG_BT_LEDS=y +# CONFIG_BT_DEBUGFS is not set +CONFIG_BT_HCIBTUSB=m +# CONFIG_BT_HCIBTUSB_POLL_SYNC is not set +CONFIG_BT_HCIBTSDIO=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_BCSP=y +CONFIG_BT_HCIUART_ATH3K=y +CONFIG_BT_HCIUART_LL=y +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_INTEL=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIUART_QCA=y +CONFIG_BT_HCIUART_MRVL=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_BT_MTKSDIO=m +CONFIG_CFG80211=m +CONFIG_CFG80211_DEBUGFS=y +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_MAC80211_DEBUGFS=y +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_RFKILL_GPIO=m +CONFIG_PSAMPLE=m +# CONFIG_LWTUNNEL_BPF is not set +CONFIG_PAGE_POOL_STATS=y +# CONFIG_ETHTOOL_NETLINK is not set +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +# CONFIG_VGA_ARB is not set +CONFIG_PCIE_BRCMSTB=m +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCIE_ROCKCHIP_HOST=y +# CONFIG_PCI_MESON is not set +CONFIG_PCIE_ROCKCHIP_DW_HOST=y +CONFIG_CXL_BUS=m +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_FW_LOADER_USER_HELPER=y +CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y +CONFIG_FW_LOADER_COMPRESS=y +CONFIG_DEBUG_DEVRES=y +CONFIG_VEXPRESS_CONFIG=y +CONFIG_CONNECTOR=y +CONFIG_ARM_SCMI_PROTOCOL=y +CONFIG_ARM_SCMI_RAW_MODE_SUPPORT=y +CONFIG_ARM_SCMI_RAW_MODE_SUPPORT_COEX=y +CONFIG_ARM_SCMI_TRANSPORT_SMC_ATOMIC_ENABLE=y +CONFIG_ARM_SCMI_POWER_CONTROL=m +CONFIG_ARM_SCPI_PROTOCOL=y +CONFIG_DMI_SYSFS=y +CONFIG_RASPBERRYPI_FIRMWARE=y +# CONFIG_EFI_VARS_PSTORE is not set +# CONFIG_EFI_ARMSTUB_DTB_LOADER is not set +CONFIG_EFI_BOOTLOADER_CONTROL=y +# CONFIG_EFI_CUSTOM_SSDT_OVERLAYS is not set +CONFIG_MTD=y +CONFIG_MTD_OF_PARTS=m +# CONFIG_MTD_OF_PARTS_BCM4908 is not set +CONFIG_MTD_BLOCK=m +CONFIG_MTD_CFI=m +CONFIG_MTD_CFI_INTELEXT=m +CONFIG_MTD_CFI_AMDSTD=m +CONFIG_MTD_CFI_STAA=m +CONFIG_MTD_PHYSMAP=m +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_SPI_NOR=y +CONFIG_MTD_UBI=m +CONFIG_OF_OVERLAY=y +CONFIG_BLK_DEV_NULL_BLK=m +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_LZ4HC=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_DEFLATE=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=m +CONFIG_BLK_DEV_LOOP_MIN_COUNT=0 +CONFIG_BLK_DEV_RAM=m +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_CDROM_PKTCDVD=m +CONFIG_BLK_DEV_NVME=m +CONFIG_TIFM_CORE=y +# CONFIG_TIFM_7XX1 is not set +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_EEPROM_MAX6875=m +CONFIG_CB710_CORE=y +CONFIG_RAID_ATTRS=m +CONFIG_BLK_DEV_SD=y +CONFIG_BLK_DEV_SR=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +# CONFIG_SCSI_LOWLEVEL is not set +CONFIG_ATA=y +# CONFIG_ATA_ACPI is not set +# CONFIG_SATA_PMP is not set +CONFIG_AHCI_DWC=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +CONFIG_WIREGUARD=m +CONFIG_NETCONSOLE=m +CONFIG_NETCONSOLE_DYNAMIC=y +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_NLMON=m +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +CONFIG_SUN4I_EMAC=m +CONFIG_SUNXI55I_GMAC=y +CONFIG_SUNXI55I_GMAC_METADATA=y +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AMD is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_BCM4908_ENET is not set +CONFIG_BCMGENET=m +# CONFIG_BGMAC_PLATFORM is not set +CONFIG_MACB=y +# CONFIG_MACB_USE_HWSTAMP is not set +CONFIG_MACB_PCI=y +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_LITEX is not set +CONFIG_MVMDIO=m +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETERION is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +CONFIG_R8169=m +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +CONFIG_SMC91X=m +CONFIG_SMSC911X=y +# CONFIG_NET_VENDOR_SOCIONEXT is not set +CONFIG_STMMAC_ETH=y +CONFIG_DWMAC_SUN8I=m +# CONFIG_NET_VENDOR_SUN is not set +CONFIG_DWC_XLGMAC=m +# CONFIG_NET_VENDOR_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_LED_TRIGGER_PHY=y +CONFIG_MESON_GXL_PHY=y +CONFIG_BCM87XX_PHY=m +CONFIG_ICPLUS_PHY=y +CONFIG_MICREL_PHY=y +CONFIG_MICROCHIP_PHY=m +CONFIG_MOTORCOMM_PHY=y +CONFIG_REALTEK_PHY=y +CONFIG_ROCKCHIP_PHY=y +CONFIG_MDIO_BITBANG=m +# CONFIG_MDIO_BCM_IPROC is not set +# CONFIG_MDIO_BUS_MUX_BCM_IPROC is not set +CONFIG_MDIO_BUS_MUX_GPIO=m +CONFIG_MDIO_BUS_MUX_MULTIPLEXER=y +CONFIG_MDIO_BUS_MUX_MMIOREG=y +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=m +CONFIG_USB_USBNET=m +# CONFIG_USB_NET_CDC_NCM is not set +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +# CONFIG_USB_NET_NET1080 is not set +CONFIG_USB_NET_RNDIS_HOST=m +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_WLAN_VENDOR_ADMTEK is not set +CONFIG_ATH5K=m +CONFIG_ATH5K_DEBUG=y +CONFIG_ATH9K=m +# CONFIG_ATH9K_PCI is not set +CONFIG_ATH9K_AHB=y +CONFIG_ATH9K_DEBUGFS=y +CONFIG_ATH9K_HWRNG=y +CONFIG_ATH9K_COMMON_SPECTRAL=y +CONFIG_CARL9170=m +# CONFIG_CARL9170_LEDS is not set +CONFIG_ATH6KL=m +CONFIG_ATH6KL_SDIO=m +CONFIG_ATH6KL_USB=m +CONFIG_ATH6KL_DEBUG=y +CONFIG_AR5523=m +CONFIG_ATH10K=m +CONFIG_ATH10K_SDIO=m +CONFIG_ATH10K_USB=m +CONFIG_WCN36XX=m +CONFIG_ATH11K=m +CONFIG_ATH11K_DEBUGFS=y +CONFIG_ATH11K_SPECTRAL=y +# CONFIG_WLAN_VENDOR_ATMEL is not set +CONFIG_B43=m +CONFIG_B43_SDIO=y +CONFIG_B43_DEBUG=y +CONFIG_B43LEGACY=m +CONFIG_BRCMSMAC=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMFMAC_PCIE=y +CONFIG_IPW2100=m +CONFIG_IPW2100_MONITOR=y +CONFIG_IPW2200=m +CONFIG_IPW2200_MONITOR=y +CONFIG_IPW2200_PROMISCUOUS=y +CONFIG_IPW2200_QOS=y +CONFIG_IWL4965=m +CONFIG_IWL3945=m +CONFIG_IWLEGACY_DEBUG=y +CONFIG_IWLEGACY_DEBUGFS=y +CONFIG_IWLWIFI=m +CONFIG_IWLDVM=m +CONFIG_IWLMVM=m +CONFIG_IWLWIFI_DEBUG=y +CONFIG_IWLWIFI_DEBUGFS=y +# CONFIG_IWLWIFI_DEVICE_TRACING is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +# CONFIG_WLAN_VENDOR_MEDIATEK is not set +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# CONFIG_WLAN_VENDOR_PURELIFI is not set +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RT2X00_LIB_DEBUGFS=y +CONFIG_RTL8180=m +CONFIG_RTL8187=m +CONFIG_RTL8192CE=m +CONFIG_RTL8188EE=m +CONFIG_RTL8192EE=m +CONFIG_RTL8821AE=m +CONFIG_RTL8192CU=m +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BS=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CS=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DS=m +CONFIG_RTW88_8723CS=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CS=m +CONFIG_RTW88_8821CU=m +CONFIG_RTW89=m +CONFIG_RTW89_8852BE=m +CONFIG_RTW89_8852BTE=m +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_SILABS is not set +CONFIG_CW1200=m +CONFIG_CW1200_WLAN_SDIO=m +CONFIG_CW1200_WLAN_SPI=m +CONFIG_WL1251=m +CONFIG_WL1251_SPI=m +CONFIG_WL1251_SDIO=m +CONFIG_WL12XX=m +CONFIG_WL18XX=m +CONFIG_WLCORE_SPI=m +CONFIG_WLCORE_SDIO=m +CONFIG_ZD1211RW=m +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +CONFIG_MAC80211_HWSIM=m +CONFIG_INPUT_MATRIXKMAP=y +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_GPIO_POLLED=m +# CONFIG_MOUSE_PS2 is not set +CONFIG_INPUT_MISC=y +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_UINPUT=m +CONFIG_INPUT_RK805_PWRKEY=m +CONFIG_RMI4_CORE=m +CONFIG_RMI4_F03=y +CONFIG_RMI4_F11=y +CONFIG_RMI4_F12=y +CONFIG_RMI4_F30=y +CONFIG_RMI4_F3A=y +CONFIG_SERIO_AMBAKMI=y +CONFIG_SERIO_RAW=m +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LEGACY_TIOCSTI is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +# CONFIG_SERIAL_8250_16550A_VARIANTS is not set +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=32 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_MANY_PORTS=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_RSA=y +CONFIG_SERIAL_8250_BCM2835AUX=y +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL010=y +CONFIG_SERIAL_AMBA_PL010_CONSOLE=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_MESON=y +CONFIG_SERIAL_MESON_CONSOLE=y +# CONFIG_SERIAL_BCM63XX is not set +CONFIG_NULL_TTY=m +CONFIG_SERIAL_DEV_BUS=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_TIMERIOMEM=m +CONFIG_HW_RANDOM_BCM2835=m +# CONFIG_HW_RANDOM_IPROC_RNG200 is not set +CONFIG_HW_RANDOM_MESON=m +CONFIG_HW_RANDOM_CCTRNG=m +# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set +CONFIG_HW_RANDOM_ROCKCHIP=m +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX=y +CONFIG_I2C_ARB_GPIO_CHALLENGE=m +CONFIG_I2C_MUX_GPIO=m +CONFIG_I2C_MUX_GPMUX=m +CONFIG_I2C_MUX_PCA9541=m +CONFIG_I2C_MUX_PCA954x=y +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_MUX_REG=m +CONFIG_I2C_DEMUX_PINCTRL=m +CONFIG_I2C_NFORCE2=m +CONFIG_I2C_SCMI=y +CONFIG_I2C_BCM2835=y +# CONFIG_I2C_BCM_IPROC is not set +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_DESIGNWARE_PCI=m +CONFIG_I2C_GPIO=m +CONFIG_I2C_MESON=y +CONFIG_I2C_MV64XXX=y +CONFIG_I2C_PCA_PLATFORM=m +CONFIG_I2C_RK3X=y +CONFIG_I2C_SIMTEC=m +CONFIG_I2C_TINY_USB=m +CONFIG_I2C_STUB=m +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=m +CONFIG_SPI=y +CONFIG_SPI_AMLOGIC_SPIFC_A1=m +CONFIG_SPI_BCM2835=m +CONFIG_SPI_BCM2835AUX=m +# CONFIG_SPI_BCM_QSPI is not set +CONFIG_SPI_DESIGNWARE=m +CONFIG_SPI_GPIO=y +CONFIG_SPI_MESON_SPICC=m +CONFIG_SPI_MESON_SPIFC=m +CONFIG_SPI_PL022=y +CONFIG_SPI_ROCKCHIP=y +CONFIG_SPI_ROCKCHIP_SFC=m +CONFIG_SPI_SUN4I=y +CONFIG_SPI_SUN6I=y +CONFIG_SPI_MUX=m +CONFIG_SPI_SPIDEV=m +CONFIG_SPMI=y +# CONFIG_PTP_1588_CLOCK_DTE is not set +CONFIG_DP83640_PHY=m +# CONFIG_PTP_1588_CLOCK_KVM is not set +CONFIG_PINCTRL_AMD=y +CONFIG_PINCTRL_MAX77620=y +CONFIG_PINCTRL_RK805=y +CONFIG_PINCTRL_SINGLE=y +# CONFIG_PINCTRL_BCM4908 is not set +# CONFIG_PINCTRL_IPROC_GPIO is not set +# CONFIG_PINCTRL_NS2_MUX is not set +# CONFIG_PINCTRL_AMLOGIC_C3 is not set +# CONFIG_PINCTRL_AMLOGIC_T7 is not set +CONFIG_PINCTRL_SUN8I_A83T=y +CONFIG_PINCTRL_SUN8I_A83T_R=y +CONFIG_PINCTRL_SUN8I_H3=y +CONFIG_PINCTRL_SUN9I_A80=y +CONFIG_PINCTRL_SUN9I_A80_R=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_ALTERA=m +# CONFIG_GPIO_BCM_XGS_IPROC is not set +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_GENERIC_PLATFORM=y +CONFIG_GPIO_MB86S7X=y +CONFIG_GPIO_PL061=y +CONFIG_GPIO_SYSCON=y +CONFIG_GPIO_MAX732X=y +CONFIG_GPIO_MAX732X_IRQ=y +CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_MAX77620=y +CONFIG_POWER_RESET_GPIO=y +CONFIG_POWER_RESET_GPIO_RESTART=y +CONFIG_POWER_RESET_REGULATOR=y +CONFIG_POWER_RESET_RESTART=y +CONFIG_POWER_RESET_VEXPRESS=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_POWER_RESET_SYSCON_POWEROFF=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_NVMEM_REBOOT_MODE=y +CONFIG_CHARGER_RK817=y +CONFIG_SENSORS_ARM_SCMI=m +CONFIG_SENSORS_ARM_SCPI=m +CONFIG_SENSORS_PWM_FAN=m +CONFIG_SENSORS_RASPBERRYPI_HWMON=m +CONFIG_SENSORS_ACPI_POWER=m +CONFIG_THERMAL_DEBUGFS=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_BANG_BANG=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +CONFIG_DEVFREQ_THERMAL=y +CONFIG_THERMAL_EMULATION=y +CONFIG_THERMAL_MMIO=m +CONFIG_SUN8I_THERMAL=m +CONFIG_ROCKCHIP_THERMAL=m +CONFIG_AMLOGIC_THERMAL=m +CONFIG_BCM2711_THERMAL=m +CONFIG_BCM2835_THERMAL=m +# CONFIG_BCM_NS_THERMAL is not set +# CONFIG_BCM_SR_THERMAL is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +CONFIG_SOFT_WATCHDOG=m +CONFIG_GPIO_WATCHDOG=m +CONFIG_ARM_SP805_WATCHDOG=m +CONFIG_ARM_SBSA_WATCHDOG=m +CONFIG_DW_WATCHDOG=m +CONFIG_SUNXI_WATCHDOG=m +CONFIG_MAX77620_WATCHDOG=m +CONFIG_MESON_GXBB_WATCHDOG=m +CONFIG_MESON_WATCHDOG=m +CONFIG_ARM_SMC_WATCHDOG=m +CONFIG_BCM2835_WDT=m +CONFIG_SSB_DRIVER_GPIO=y +# CONFIG_BCMA_HOST_PCI is not set +CONFIG_BCMA_HOST_SOC=y +CONFIG_BCMA_DRIVER_GMAC_CMN=y +CONFIG_BCMA_DRIVER_GPIO=y +CONFIG_MFD_SUN4I_GPADC=m +CONFIG_MFD_BD9571MWV=y +CONFIG_MFD_AXP20X_I2C=y +CONFIG_MFD_AXP20X_RSB=y +CONFIG_MFD_MAX77620=y +CONFIG_MFD_RK8XX_I2C=y +CONFIG_MFD_RK8XX_SPI=y +CONFIG_MFD_SEC_CORE=y +CONFIG_MFD_SUN6I_PRCM=y +CONFIG_MFD_ROHM_BD718XX=y +# CONFIG_MFD_VEXPRESS_SYSREG is not set +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_VIRTUAL_CONSUMER=y +CONFIG_REGULATOR_USERSPACE_CONSUMER=y +CONFIG_REGULATOR_ARM_SCMI=y +CONFIG_REGULATOR_AXP20X=y +CONFIG_REGULATOR_FAN53555=y +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_MAX77620=y +CONFIG_REGULATOR_MAX8973=y +CONFIG_REGULATOR_MP5416=y +CONFIG_REGULATOR_MP8859=y +CONFIG_REGULATOR_MP886X=y +CONFIG_REGULATOR_PCA9450=y +CONFIG_REGULATOR_PFUZE100=y +CONFIG_REGULATOR_PWM=y +CONFIG_REGULATOR_RK808=y +# CONFIG_REGULATOR_SUN20I is not set +CONFIG_REGULATOR_SY8106A=y +CONFIG_REGULATOR_SY8824X=y +CONFIG_REGULATOR_SY8827N=y +CONFIG_REGULATOR_TPS65132=y +CONFIG_REGULATOR_VCTRL=y +CONFIG_REGULATOR_VEXPRESS=y +CONFIG_RC_CORE=m +CONFIG_LIRC=y +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_RCMM_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +CONFIG_IR_ENE=m +CONFIG_IR_FINTEK=m +CONFIG_IR_GPIO_CIR=m +CONFIG_IR_GPIO_TX=m +CONFIG_IR_HIX5HD2=m +CONFIG_IR_IGORPLUGUSB=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_IMON_RAW=m +CONFIG_IR_ITE_CIR=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_MESON=m +CONFIG_IR_MESON_TX=m +CONFIG_IR_NUVOTON=m +CONFIG_IR_PWM_TX=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_SERIAL=m +CONFIG_IR_SERIAL_TRANSMITTER=y +CONFIG_IR_SPI=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_SUNXI=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +CONFIG_CEC_MESON_AO=y +CONFIG_CEC_MESON_G12A_AO=y +CONFIG_CEC_GPIO=y +CONFIG_MEDIA_SUPPORT=m +CONFIG_VIDEO_ADV_DEBUG=y +CONFIG_VIDEO_FIXED_MINOR_RANGES=y +# CONFIG_DVB_NET is not set +# CONFIG_DVB_DYNAMIC_MINORS is not set +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=m +CONFIG_USB_VIDEO_CLASS=m +# CONFIG_RADIO_ADAPTERS is not set +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_V4L_MEM2MEM_DRIVERS=y +CONFIG_VIDEO_MESON_GE2D=m +CONFIG_VIDEO_SUN8I_DEINTERLACE=m +CONFIG_VIDEO_SUN8I_ROTATE=m +CONFIG_VIDEO_HANTRO=m +CONFIG_VIDEO_HANTRO_HEVC_RFC=y +# CONFIG_VIDEO_IR_I2C is not set +# CONFIG_VIDEO_CAMERA_SENSOR is not set +# CONFIG_CXD2880_SPI_DRV is not set +# CONFIG_MEDIA_TUNER_E4000 is not set +# CONFIG_MEDIA_TUNER_FC0011 is not set +# CONFIG_MEDIA_TUNER_FC0012 is not set +# CONFIG_MEDIA_TUNER_FC0013 is not set +# CONFIG_MEDIA_TUNER_FC2580 is not set +# CONFIG_MEDIA_TUNER_IT913X is not set +# CONFIG_MEDIA_TUNER_M88RS6000T is not set +# CONFIG_MEDIA_TUNER_MAX2165 is not set +# CONFIG_MEDIA_TUNER_MC44S803 is not set +# CONFIG_MEDIA_TUNER_MSI001 is not set +# CONFIG_MEDIA_TUNER_MT2060 is not set +# CONFIG_MEDIA_TUNER_MT2063 is not set +# CONFIG_MEDIA_TUNER_MT20XX is not set +# CONFIG_MEDIA_TUNER_MT2131 is not set +# CONFIG_MEDIA_TUNER_MT2266 is not set +# CONFIG_MEDIA_TUNER_MXL301RF is not set +# CONFIG_MEDIA_TUNER_MXL5005S is not set +# CONFIG_MEDIA_TUNER_MXL5007T is not set +# CONFIG_MEDIA_TUNER_QM1D1B0004 is not set +# CONFIG_MEDIA_TUNER_QM1D1C0042 is not set +# CONFIG_MEDIA_TUNER_QT1010 is not set +# CONFIG_MEDIA_TUNER_R820T is not set +# CONFIG_MEDIA_TUNER_SI2157 is not set +# CONFIG_MEDIA_TUNER_SIMPLE is not set +# CONFIG_MEDIA_TUNER_TDA18212 is not set +# CONFIG_MEDIA_TUNER_TDA18218 is not set +# CONFIG_MEDIA_TUNER_TDA18250 is not set +# CONFIG_MEDIA_TUNER_TDA18271 is not set +# CONFIG_MEDIA_TUNER_TDA827X is not set +# CONFIG_MEDIA_TUNER_TDA8290 is not set +# CONFIG_MEDIA_TUNER_TDA9887 is not set +# CONFIG_MEDIA_TUNER_TEA5761 is not set +# CONFIG_MEDIA_TUNER_TEA5767 is not set +# CONFIG_MEDIA_TUNER_TUA9001 is not set +# CONFIG_MEDIA_TUNER_XC2028 is not set +# CONFIG_MEDIA_TUNER_XC4000 is not set +# CONFIG_MEDIA_TUNER_XC5000 is not set +# CONFIG_DVB_M88DS3103 is not set +# CONFIG_DVB_MXL5XX is not set +# CONFIG_DVB_STB0899 is not set +# CONFIG_DVB_STB6100 is not set +# CONFIG_DVB_STV090x is not set +# CONFIG_DVB_STV0910 is not set +# CONFIG_DVB_STV6110x is not set +# CONFIG_DVB_STV6111 is not set +# CONFIG_DVB_DRXK is not set +# CONFIG_DVB_MN88472 is not set +# CONFIG_DVB_MN88473 is not set +# CONFIG_DVB_SI2165 is not set +# CONFIG_DVB_TDA18271C2DD is not set +# CONFIG_DVB_CX24110 is not set +# CONFIG_DVB_CX24116 is not set +# CONFIG_DVB_CX24117 is not set +# CONFIG_DVB_CX24120 is not set +# CONFIG_DVB_CX24123 is not set +# CONFIG_DVB_DS3000 is not set +# CONFIG_DVB_MB86A16 is not set +# CONFIG_DVB_MT312 is not set +# CONFIG_DVB_S5H1420 is not set +# CONFIG_DVB_SI21XX is not set +# CONFIG_DVB_STB6000 is not set +# CONFIG_DVB_STV0288 is not set +# CONFIG_DVB_STV0299 is not set +# CONFIG_DVB_STV0900 is not set +# CONFIG_DVB_STV6110 is not set +# CONFIG_DVB_TDA10071 is not set +# CONFIG_DVB_TDA10086 is not set +# CONFIG_DVB_TDA8083 is not set +# CONFIG_DVB_TDA8261 is not set +# CONFIG_DVB_TDA826X is not set +# CONFIG_DVB_TS2020 is not set +# CONFIG_DVB_TUA6100 is not set +# CONFIG_DVB_TUNER_CX24113 is not set +# CONFIG_DVB_TUNER_ITD1000 is not set +# CONFIG_DVB_VES1X93 is not set +# CONFIG_DVB_ZL10036 is not set +# CONFIG_DVB_ZL10039 is not set +# CONFIG_DVB_AF9013 is not set +# CONFIG_DVB_CX22700 is not set +# CONFIG_DVB_CX22702 is not set +# CONFIG_DVB_CXD2820R is not set +# CONFIG_DVB_CXD2841ER is not set +# CONFIG_DVB_DIB3000MB is not set +# CONFIG_DVB_DIB3000MC is not set +# CONFIG_DVB_DIB7000M is not set +# CONFIG_DVB_DIB7000P is not set +# CONFIG_DVB_DIB9000 is not set +# CONFIG_DVB_DRXD is not set +# CONFIG_DVB_EC100 is not set +# CONFIG_DVB_L64781 is not set +# CONFIG_DVB_MT352 is not set +# CONFIG_DVB_NXT6000 is not set +# CONFIG_DVB_RTL2830 is not set +# CONFIG_DVB_RTL2832 is not set +# CONFIG_DVB_RTL2832_SDR is not set +# CONFIG_DVB_S5H1432 is not set +# CONFIG_DVB_SI2168 is not set +# CONFIG_DVB_SP887X is not set +# CONFIG_DVB_STV0367 is not set +# CONFIG_DVB_TDA10048 is not set +# CONFIG_DVB_TDA1004X is not set +# CONFIG_DVB_ZD1301_DEMOD is not set +# CONFIG_DVB_ZL10353 is not set +# CONFIG_DVB_CXD2880 is not set +# CONFIG_DVB_STV0297 is not set +# CONFIG_DVB_TDA10021 is not set +# CONFIG_DVB_TDA10023 is not set +# CONFIG_DVB_VES1820 is not set +# CONFIG_DVB_AU8522_DTV is not set +# CONFIG_DVB_AU8522_V4L is not set +# CONFIG_DVB_BCM3510 is not set +# CONFIG_DVB_LG2160 is not set +# CONFIG_DVB_LGDT3305 is not set +# CONFIG_DVB_LGDT3306A is not set +# CONFIG_DVB_LGDT330X is not set +# CONFIG_DVB_MXL692 is not set +# CONFIG_DVB_NXT200X is not set +# CONFIG_DVB_OR51132 is not set +# CONFIG_DVB_OR51211 is not set +# CONFIG_DVB_S5H1409 is not set +# CONFIG_DVB_S5H1411 is not set +# CONFIG_DVB_DIB8000 is not set +# CONFIG_DVB_MB86A20S is not set +# CONFIG_DVB_S921 is not set +# CONFIG_DVB_MN88443X is not set +# CONFIG_DVB_TC90522 is not set +# CONFIG_DVB_PLL is not set +# CONFIG_DVB_TUNER_DIB0070 is not set +# CONFIG_DVB_TUNER_DIB0090 is not set +# CONFIG_DVB_A8293 is not set +# CONFIG_DVB_AF9033 is not set +# CONFIG_DVB_ASCOT2E is not set +# CONFIG_DVB_ATBM8830 is not set +# CONFIG_DVB_HELENE is not set +# CONFIG_DVB_HORUS3A is not set +# CONFIG_DVB_ISL6405 is not set +# CONFIG_DVB_ISL6421 is not set +# CONFIG_DVB_ISL6423 is not set +# CONFIG_DVB_IX2505V is not set +# CONFIG_DVB_LGS8GL5 is not set +# CONFIG_DVB_LGS8GXX is not set +# CONFIG_DVB_LNBH25 is not set +# CONFIG_DVB_LNBH29 is not set +# CONFIG_DVB_LNBP21 is not set +# CONFIG_DVB_LNBP22 is not set +# CONFIG_DVB_M88RS2000 is not set +# CONFIG_DVB_TDA665x is not set +# CONFIG_DVB_DRX39XYJ is not set +# CONFIG_DVB_CXD2099 is not set +# CONFIG_DVB_SP2 is not set +CONFIG_AUXDISPLAY=y +CONFIG_DRM=y +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_I2C_CH7006=m +CONFIG_DRM_I2C_SIL164=m +CONFIG_DRM_I2C_NXP_TDA998X=m +CONFIG_DRM_HDLCD=m +CONFIG_DRM_MALI_DISPLAY=m +CONFIG_DRM_VGEM=m +CONFIG_DRM_ROCKCHIP=y +CONFIG_ROCKCHIP_VOP2=y +CONFIG_ROCKCHIP_ANALOGIX_DP=y +CONFIG_ROCKCHIP_CDN_DP=y +CONFIG_ROCKCHIP_DW_HDMI=y +CONFIG_ROCKCHIP_DW_HDMI_QP=y +CONFIG_ROCKCHIP_DW_MIPI_DSI=y +CONFIG_ROCKCHIP_INNO_HDMI=y +CONFIG_ROCKCHIP_LVDS=y +CONFIG_ROCKCHIP_RGB=y +CONFIG_ROCKCHIP_RK3066_HDMI=y +CONFIG_DRM_SUN4I=y +CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_SIL_SII8620=m +CONFIG_DRM_SII902X=m +CONFIG_DRM_SIMPLE_BRIDGE=m +CONFIG_DRM_TOSHIBA_TC358767=m +CONFIG_DRM_TI_TFP410=m +CONFIG_DRM_ANALOGIX_ANX78XX=m +CONFIG_DRM_I2C_ADV7511=m +CONFIG_DRM_I2C_ADV7511_AUDIO=y +# CONFIG_DRM_I2C_ADV7511_CEC is not set +CONFIG_DRM_DW_HDMI_CEC=m +CONFIG_DRM_V3D=m +CONFIG_DRM_VC4=y +CONFIG_DRM_VC4_HDMI_CEC=y +CONFIG_DRM_MESON=y +# CONFIG_DRM_MESON_DW_MIPI_DSI is not set +CONFIG_DRM_LIMA=m +CONFIG_DRM_PANFROST=m +CONFIG_DRM_PANTHOR=m +CONFIG_FB=y +CONFIG_FB_VIRTUAL=y +CONFIG_FB_SIMPLE=m +CONFIG_FIRMWARE_EDID=y +CONFIG_FB_MODE_HELPERS=y +CONFIG_FB_TILEBLITTING=y +CONFIG_LCD_CLASS_DEVICE=m +CONFIG_LCD_PLATFORM=m +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_PWM=m +CONFIG_BACKLIGHT_GPIO=m +CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION=y +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=m +CONFIG_SND_PCM_OSS=m +CONFIG_SND_HRTIMER=m +# CONFIG_SND_SUPPORT_OLD_API is not set +# CONFIG_SND_CTL_FAST_LOOKUP is not set +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_SEQUENCER_OSS=m +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_MPU401=m +# CONFIG_SND_PCI is not set +CONFIG_SND_HDA_PREALLOC_SIZE=4096 +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_SOC=y +CONFIG_SND_BCM2835_SOC_I2S=m +CONFIG_SND_MESON_AXG_SOUND_CARD=m +CONFIG_SND_MESON_GX_SOUND_CARD=m +CONFIG_SND_SOC_ROCKCHIP=m +CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=m +CONFIG_SND_SOC_ROCKCHIP_PDM=m +CONFIG_SND_SOC_ROCKCHIP_SPDIF=m +CONFIG_SND_SOC_ROCKCHIP_MAX98090=m +CONFIG_SND_SOC_ROCKCHIP_RT5645=m +CONFIG_SND_SOC_RK3288_HDMI_ANALOG=m +CONFIG_SND_SOC_RK3399_GRU_SOUND=m +CONFIG_SND_SUN4I_CODEC=y +CONFIG_SND_SUN8I_CODEC=y +CONFIG_SND_SUN8I_CODEC_ANALOG=y +CONFIG_SND_SUN50I_CODEC_ANALOG=y +CONFIG_SND_SUN4I_I2S=y +CONFIG_SND_SUN4I_SPDIF=y +CONFIG_SND_SUN50I_DMIC=m +CONFIG_SND_SOC_ES7134=m +CONFIG_SND_SOC_ES8316=m +CONFIG_SND_SOC_PCM5102A=m +CONFIG_SND_SOC_RK817=m +CONFIG_SND_SOC_RT5616=m +CONFIG_SND_SOC_SIMPLE_AMPLIFIER=y +CONFIG_SND_SOC_SIMPLE_MUX=m +CONFIG_SND_SOC_SPDIF=y +CONFIG_SND_SIMPLE_CARD=y +CONFIG_SND_AUDIO_GRAPH_CARD=m +CONFIG_SND_AUDIO_GRAPH_CARD2=m +CONFIG_HID=m +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_USB_HID=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +# CONFIG_I2C_HID is not set +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_OTG=y +CONFIG_USB_MON=m +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_BRCMSTB=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_OHCI_HCD=y +# CONFIG_USB_OHCI_HCD_PCI is not set +CONFIG_USB_WDM=m +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=y +CONFIG_USB_CDNS_SUPPORT=m +CONFIG_USB_CDNS3=m +# CONFIG_USB_CDNS3_PCI_WRAP is not set +CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_MUSB_SUNXI=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_MESON_G12A=m +CONFIG_USB_DWC2=y +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SAFE_PADDED=y +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_APPLE_MFI_FASTCHARGE=m +CONFIG_USB_SISUSBVGA=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_EHSET_TEST_FIXTURE=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_EZUSB_FX2=y +# CONFIG_BRCM_USB_PINMAP is not set +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +# CONFIG_USB_SNP_UDC_PLAT is not set +# CONFIG_USB_BDC_UDC is not set +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_TYPEC=y +CONFIG_TYPEC_TCPM=y +CONFIG_TYPEC_TCPCI=y +CONFIG_TYPEC_FUSB302=y +CONFIG_TYPEC_TPS6598X=y +CONFIG_TYPEC_HD3SS3220=m +CONFIG_TYPEC_MUX_PI3USB30532=y +CONFIG_TYPEC_DP_ALTMODE=y +CONFIG_MMC=y +CONFIG_PWRSEQ_SD8787=m +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_SDIO_UART=m +CONFIG_MMC_ARMMMCI=y +# CONFIG_MMC_STM32_SDMMC is not set +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_ARASAN=y +CONFIG_MMC_SDHCI_OF_DWCMSHC=y +CONFIG_MMC_MESON_GX=y +CONFIG_MMC_MESON_MX_SDIO=y +CONFIG_MMC_SPI=y +CONFIG_MMC_DW=y +CONFIG_MMC_DW_EXYNOS=y +CONFIG_MMC_DW_K3=y +CONFIG_MMC_DW_PCI=y +CONFIG_MMC_DW_ROCKCHIP=y +CONFIG_MMC_VUB300=m +CONFIG_MMC_USHC=m +CONFIG_MMC_USDHI6ROL0=y +CONFIG_MMC_SUNXI=y +CONFIG_MMC_TOSHIBA_PCI=y +CONFIG_MMC_BCM2835=y +CONFIG_MMC_MTK=y +CONFIG_MMC_SDHCI_XENON=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PWM=y +CONFIG_LEDS_SYSCON=y +CONFIG_LEDS_USER=m +# CONFIG_LEDS_BCM63138 is not set +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_DISK=y +CONFIG_LEDS_TRIGGER_MTD=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_ACTIVITY=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_LEDS_TRIGGER_PATTERN=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_SYSTOHC is not set +CONFIG_RTC_INTF_DEV_UIE_EMUL=y +CONFIG_RTC_DRV_HYM8563=m +CONFIG_RTC_DRV_RK808=m +CONFIG_RTC_DRV_PL030=y +CONFIG_RTC_DRV_PL031=y +CONFIG_RTC_DRV_SUN6I=y +CONFIG_DMADEVICES=y +CONFIG_DMA_BCM2835=y +CONFIG_DMA_SUN6I=y +CONFIG_DW_AXI_DMAC=y +CONFIG_PL330_DMA=y +CONFIG_DW_DMAC=m +CONFIG_DW_DMAC_PCI=m +CONFIG_DW_EDMA=m +CONFIG_DW_EDMA_PCIE=m +CONFIG_ASYNC_TX_DMA=y +CONFIG_UDMABUF=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +CONFIG_DMABUF_HEAPS_CMA=y +CONFIG_UIO=m +CONFIG_VFIO=m +# CONFIG_VFIO_GROUP is not set +CONFIG_VFIO_PCI=m +CONFIG_VFIO_PLATFORM=m +CONFIG_VFIO_AMBA=m +CONFIG_VFIO_PLATFORM_CALXEDAXGMAC_RESET=m +CONFIG_VFIO_PLATFORM_AMDXGBE_RESET=m +# CONFIG_VFIO_PLATFORM_BCMFLEXRM_RESET is not set +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_MENU is not set +CONFIG_STAGING=y +CONFIG_RTL8723BS=m +CONFIG_STAGING_MEDIA=y +CONFIG_VIDEO_MESON_VDEC=m +CONFIG_VIDEO_ROCKCHIP_VDEC=m +CONFIG_VIDEO_SUNXI=y +CONFIG_VIDEO_SUNXI_CEDRUS=m +CONFIG_BCM_VIDEOCORE=m +# CONFIG_VCHIQ_CDEV is not set +CONFIG_SND_BCM2835=m +CONFIG_VIDEO_BCM2835=m +# CONFIG_SURFACE_PLATFORMS is not set +CONFIG_COMMON_CLK_MAX77686=y +CONFIG_COMMON_CLK_RK808=y +CONFIG_COMMON_CLK_SCMI=y +CONFIG_COMMON_CLK_SCPI=y +CONFIG_COMMON_CLK_SI5341=m +CONFIG_COMMON_CLK_SI544=m +CONFIG_COMMON_CLK_CS2000_CP=y +CONFIG_COMMON_CLK_S2MPS11=y +CONFIG_COMMON_CLK_AXI_CLKGEN=m +CONFIG_COMMON_CLK_XGENE=y +CONFIG_COMMON_CLK_PWM=y +CONFIG_COMMON_CLK_VC3=y +CONFIG_COMMON_CLK_VC5=y +CONFIG_COMMON_CLK_BD718XX=m +CONFIG_COMMON_CLK_FIXED_MMIO=y +CONFIG_CLK_RASPBERRYPI=y +CONFIG_COMMON_CLK_AXG_AUDIO=y +# CONFIG_COMMON_CLK_C3_PLL is not set +# CONFIG_COMMON_CLK_C3_PERIPHERALS is not set +# CONFIG_COMMON_CLK_S4_PERIPHERALS is not set +CONFIG_HWSPINLOCK=y +# CONFIG_FSL_ERRATUM_A008585 is not set +# CONFIG_HISILICON_ERRATUM_161010101 is not set +CONFIG_ARM_MHU=y +CONFIG_ARM_MHU_V2=m +CONFIG_ARM_MHU_V3=m +CONFIG_PLATFORM_MHU=y +CONFIG_ROCKCHIP_MBOX=y +CONFIG_BCM2835_MBOX=y +# CONFIG_BCM_FLEXRM_MBOX is not set +CONFIG_SUN6I_MSGBOX=m +CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y +CONFIG_IOMMUFD=m +CONFIG_ROCKCHIP_IOMMU=y +CONFIG_SUN50I_IOMMU=y +CONFIG_ARM_SMMU=y +# CONFIG_ARM_SMMU_DISABLE_BYPASS_BY_DEFAULT is not set +CONFIG_ARM_SMMU_V3=y +CONFIG_ARM_SMMU_V3_SVA=y +CONFIG_ROCKCHIP_IODOMAIN=y +CONFIG_RASPBERRYPI_POWER=y +CONFIG_ROCKCHIP_PM_DOMAINS=y +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_DEVFREQ_GOV_PERFORMANCE=y +CONFIG_DEVFREQ_GOV_POWERSAVE=y +CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_DEVFREQ_GOV_PASSIVE=y +CONFIG_ARM_RK3399_DMC_DEVFREQ=m +CONFIG_ARM_SUN8I_A33_MBUS_DEVFREQ=m +CONFIG_EXTCON_GPIO=y +CONFIG_EXTCON_USB_GPIO=y +CONFIG_MEMORY=y +# CONFIG_BRCMSTB_MEMC is not set +CONFIG_IIO=m +CONFIG_ROCKCHIP_SARADC=m +CONFIG_SUN4I_GPADC=m +CONFIG_SUN20I_GPADC=m +CONFIG_PWM=y +# CONFIG_PWM_BCM_IPROC is not set +CONFIG_PWM_BCM2835=m +CONFIG_PWM_BRCMSTB=m +CONFIG_PWM_CLK=m +CONFIG_PWM_DWC=m +CONFIG_PWM_MESON=m +CONFIG_PWM_ROCKCHIP=y +CONFIG_PWM_SUN4I=m +# CONFIG_BCM7120_L2_IRQ is not set +CONFIG_RESET_MESON_AUDIO_ARB=y +CONFIG_PHY_SUN4I_USB=y +CONFIG_PHY_SUN9I_USB=y +CONFIG_PHY_SUN50I_USB3=y +# CONFIG_PHY_MESON_G12A_MIPI_DPHY_ANALOG is not set +# CONFIG_PHY_MESON_AXG_MIPI_PCIE_ANALOG is not set +# CONFIG_PHY_MESON_AXG_MIPI_DPHY is not set +# CONFIG_PHY_BCM_SR_USB is not set +# CONFIG_PHY_NS2_USB_DRD is not set +# CONFIG_PHY_BRCM_SATA is not set +CONFIG_PHY_CADENCE_DPHY_RX=m +CONFIG_PHY_ROCKCHIP_DP=y +CONFIG_PHY_ROCKCHIP_EMMC=y +CONFIG_PHY_ROCKCHIP_INNO_HDMI=y +CONFIG_PHY_ROCKCHIP_INNO_USB2=y +CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY=y +CONFIG_PHY_ROCKCHIP_PCIE=y +CONFIG_PHY_ROCKCHIP_SAMSUNG_HDPTX=y +CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=m +CONFIG_PHY_ROCKCHIP_TYPEC=y +CONFIG_PHY_ROCKCHIP_USB=y +CONFIG_PHY_ROCKCHIP_USBDP=m +CONFIG_POWERCAP=y +CONFIG_ARM_SCMI_POWERCAP=m +CONFIG_ARM_CCI_PMU=y +CONFIG_ARM_CCN=y +CONFIG_ARM_CMN=y +CONFIG_ARM_SMMU_V3_PMU=y +CONFIG_DWC_PCIE_PMU=m +# CONFIG_NVMEM_BCM_OCOTP is not set +CONFIG_NVMEM_MESON_EFUSE=m +CONFIG_NVMEM_MESON_MX_EFUSE=m +CONFIG_NVMEM_ROCKCHIP_EFUSE=m +CONFIG_NVMEM_ROCKCHIP_OTP=m +CONFIG_NVMEM_SUNXI_SID=y +CONFIG_NVMEM_U_BOOT_ENV=m +CONFIG_MUX_GPIO=m +CONFIG_INTERCONNECT=y +CONFIG_VALIDATE_FS_PARSER=y +CONFIG_EXT2_FS=y +CONFIG_EXT3_FS=y +CONFIG_XFS_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_COMPRESSION=y +CONFIG_F2FS_UNFAIR_RWSEM=y +CONFIG_FANOTIFY=y +CONFIG_FUSE_FS=m +CONFIG_OVERLAY_FS=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=m +CONFIG_VFAT_FS=m +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +CONFIG_EXFAT_FS=m +CONFIG_NTFS_FS=m +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +# CONFIG_EFIVAR_FS is not set +CONFIG_CRAMFS=y +CONFIG_CRAMFS_MTD=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XZ=y +CONFIG_PSTORE_BLK=m +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +# CONFIG_NFS_DISABLE_UDP_SUPPORT is not set +CONFIG_RPCSEC_GSS_KRB5=m +CONFIG_CIFS=m +# CONFIG_CIFS_STATS2 is not set +# CONFIG_CIFS_DEBUG is not set +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_UTF8=m +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_KEY_NOTIFICATIONS=y +CONFIG_LSM="yama,loadpin,safesetid,integrity" +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_AUTHENC=m +CONFIG_CRYPTO_ECDSA=m +CONFIG_CRYPTO_DES=m +CONFIG_CRYPTO_ARC4=m +CONFIG_CRYPTO_CHACHA20=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_KEYWRAP=m +CONFIG_CRYPTO_LRW=m +CONFIG_CRYPTO_PCBC=m +CONFIG_CRYPTO_XTS=y +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_ECHAINIV=m +CONFIG_CRYPTO_GHASH=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_POLY1305=m +CONFIG_CRYPTO_RMD160=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_CHACHA20_NEON=y +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_POLY1305_NEON=y +CONFIG_CRYPTO_SHA1_ARM64_CE=y +CONFIG_CRYPTO_SHA2_ARM64_CE=y +CONFIG_CRYPTO_SHA512_ARM64_CE=m +CONFIG_CRYPTO_SHA3_ARM64=m +CONFIG_CRYPTO_SM3_ARM64_CE=m +CONFIG_CRYPTO_AES_ARM64=y +CONFIG_CRYPTO_AES_ARM64_BS=y +CONFIG_CRYPTO_SM4_ARM64_CE_BLK=y +CONFIG_CRYPTO_SM4_ARM64_NEON_BLK=y +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +CONFIG_CRYPTO_CRCT10DIF_ARM64_CE=m +# CONFIG_CRYPTO_HW is not set +CONFIG_PKCS8_PRIVATE_KEY_PARSER=y +CONFIG_PKCS7_TEST_KEY=y +CONFIG_INDIRECT_PIO=y +CONFIG_LIBCRC32C=y +CONFIG_CRC8=m +# CONFIG_XZ_DEC_RISCV is not set +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=64 +CONFIG_IRQ_POLL=y +CONFIG_PRINTK_TIME=y +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=3 +CONFIG_CONSOLE_LOGLEVEL_QUIET=3 +CONFIG_DYNAMIC_DEBUG=y +# CONFIG_DEBUG_MISC is not set +CONFIG_DEBUG_INFO_DWARF5=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_FRAME_WARN=1024 +CONFIG_STRIP_ASM_SYMS=y +CONFIG_HEADERS_INSTALL=y +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x0 +CONFIG_DEBUG_VM=y +# CONFIG_DEBUG_VM_PGTABLE is not set +CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_SCHEDSTATS=y +CONFIG_DEBUG_PREEMPT=y +CONFIG_DEBUG_LIST=y +CONFIG_RCU_TORTURE_TEST=m +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +# CONFIG_RCU_TRACE is not set +CONFIG_BOOTTIME_TRACING=y +# CONFIG_FUNCTION_GRAPH_TRACER is not set +CONFIG_FUNCTION_PROFILER=y +CONFIG_STACK_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_FTRACE_SYSCALLS=y +CONFIG_BLK_DEV_IO_TRACE=y +# CONFIG_UPROBE_EVENTS is not set +CONFIG_BPF_KPROBE_OVERRIDE=y +CONFIG_RING_BUFFER_BENCHMARK=m +CONFIG_FUNCTION_ERROR_INJECTION=y +# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_MEMTEST=y diff --git a/config/sources/families/sun55iw3.conf b/config/sources/families/sun55iw3.conf new file mode 100644 index 000000000000..424d601ef2ef --- /dev/null +++ b/config/sources/families/sun55iw3.conf @@ -0,0 +1,66 @@ +# +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2013-2023 Igor Pecovnik, igor@armbian.com +# +# This file is a part of the Armbian Build Framework +# https://github.com/armbian/build/ +# +enable_extension "sunxi-tools" +declare -g ARCH=arm64 +declare -g ATFSOURCE='https://github.com/jernejsk/arm-trusted-firmware' +declare -g ATF_TARGET_MAP="PLAT=sun55i_a523 DEBUG=1 bl31;;build/sun55i_a523/debug/bl31.bin" +declare -g ATFBRANCH="branch:a523" +declare -g BOOTSCRIPT='boot-sun50i-next.cmd:boot.cmd' +declare -g BOOTDELAY=1 +declare -g BOOTSOURCE='https://github.com/jernejsk/u-boot/' +declare -g BOOTPATCHDIR="${BOOTPATCHDIR:-"u-boot-a523"}" +declare -g BOOTBRANCH="${BOOTBRANCH:-"branch:a523"}" +declare -g BOOTENV_FILE='sunxi.txt' +declare -g UBOOT_TARGET_MAP="${UBOOT_TARGET_MAP:-BINMAN_ALLOW_MISSING=1;;u-boot-sunxi-with-spl.bin}" +declare -g OVERLAY_DIR="/boot/dtb/allwinner/overlay" +declare -g LINUXFAMILY="sun55iw3" + +case "${BRANCH}" in + + dev) + declare -g KERNELSOURCE='https://github.com/apritzel/linux' + declare -g KERNELBRANCH='branch:a523-v2-WIP' + declare -g KERNEL_MAJOR_MINOR="6.12" # Major and minor versions of this kernel. + KERNELPATCHDIR="archive/sunxi-dev-${KERNEL_MAJOR_MINOR}" + ;; + +esac + +family_tweaks() { + # execute specific tweaks function if present + [[ $(type -t family_tweaks_s) == function ]] && family_tweaks_s + cp $SRC/packages/blobs/splash/armbian-u-boot-24.bmp $SDCARD/boot/boot.bmp +} + +write_uboot_platform() { + dd if=/dev/zero of=$2 bs=1k count=1023 seek=1 status=noxfer > /dev/null 2>&1 + dd if=$1/u-boot-sunxi-with-spl.bin of=$2 bs=1024 seek=8 status=noxfer > /dev/null 2>&1 +} + +setup_write_uboot_platform() { + local tmp part dev + if grep -q "ubootpart" /proc/cmdline; then + # mainline with new boot script + tmp=$(cat /proc/cmdline) + tmp="${tmp##*ubootpart=}" + tmp="${tmp%% *}" + [[ -n $tmp ]] && part=$(findfs PARTUUID=$tmp 2> /dev/null) + [[ -n $part ]] && dev=$(lsblk -n -o PKNAME $part 2> /dev/null) + [[ -n $dev ]] && DEVICE="/dev/$dev" + else + # legacy or old boot script + tmp=$(cat /proc/cmdline) + tmp="${tmp##*root=}" + tmp="${tmp%% *}" + [[ -n $tmp ]] && part=$(findfs $tmp 2> /dev/null) + [[ -n $part ]] && dev=$(lsblk -n -o PKNAME $part 2> /dev/null) + # do not try to write u-boot to USB devices + [[ -n $dev && $dev == mmcblk* ]] && DEVICE="/dev/$dev" + fi +} diff --git a/patch/kernel/archive/sunxi-dev-6.12/add-ethernet-bsp-sun55iw3-dev.patch b/patch/kernel/archive/sunxi-dev-6.12/add-ethernet-bsp-sun55iw3-dev.patch new file mode 100644 index 000000000000..3599e63f2731 --- /dev/null +++ b/patch/kernel/archive/sunxi-dev-6.12/add-ethernet-bsp-sun55iw3-dev.patch @@ -0,0 +1,10228 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: John Doe +Date: Sun, 23 Feb 2025 03:20:11 -0300 +Subject: Fix kernel-sun55iw3-dev.patch + +Signed-off-by: John Doe +--- + arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi | 128 + + arch/arm64/boot/dts/allwinner/sun55i-a527-radxa-a5e.dts | 100 + + drivers/clk/sunxi-ng/ccu-sun55i-a523.c | 18 + + drivers/clk/sunxi-ng/ccu-sun55i-a523.h | 1 + + drivers/net/ethernet/allwinner/Kconfig | 4 + + drivers/net/ethernet/allwinner/Makefile | 3 + + drivers/net/ethernet/allwinner/gmac-200/Kconfig | 34 + + drivers/net/ethernet/allwinner/gmac-200/Makefile | 8 + + drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.c | 927 +++ + drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.h | 19 + + drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.c | 829 +++ + drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.h | 176 + + drivers/net/ethernet/allwinner/gmac-200/sunxi-log.h | 174 + + drivers/net/ethernet/allwinner/gmac-200/sunxi-uio.c | 1015 +++ + drivers/net/ethernet/allwinner/gmac/Kconfig | 37 + + drivers/net/ethernet/allwinner/gmac/Makefile | 7 + + drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c | 315 + + drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c | 3634 ++++++++++ + drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c | 434 ++ + drivers/net/ethernet/allwinner/sunxi-stmmac/Kconfig | 46 + + drivers/net/ethernet/allwinner/sunxi-stmmac/Makefile | 7 + + drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.c | 926 +++ + drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.h | 19 + + drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.c | 869 +++ + drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.h | 176 + + include/dt-bindings/clock/sun55i-a523-ccu.h | 1 + + 26 files changed, 9907 insertions(+) + +diff --git a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi +index 5dbfebfcbde7..7dad9ce1b2d4 100644 +--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi +@@ -5,10 +5,11 @@ + #include + #include + #include + #include + #include ++#include + + / { + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; +@@ -132,10 +133,54 @@ mmc0_pins: mmc0-pins { + function = "mmc0"; + drive-strength = <30>; + bias-pull-up; + }; + ++ /omit-if-no-ref/ ++ gmac0_pins_default: gmac0@0 { ++ pins = "PH0", "PH1", "PH2", "PH3", ++ "PH4", "PH5", "PH6", "PH7", ++ "PH9", "PH10","PH13","PH14", ++ "PH15","PH16","PH17","PH18"; ++ allwinner,pinmux = <5>; ++ function = "gmac0"; ++ drive-strength = <40>; ++ bias-pull-up; ++ }; ++ ++ /omit-if-no-ref/ ++ gmac0_pins_sleep: gmac0@1 { ++ pins = "PH0", "PH1", "PH2", "PH3", ++ "PH4", "PH5", "PH6", "PH7", ++ "PH9", "PH10","PH13","PH14", ++ "PH15","PH16","PH17","PH18"; ++ allwinner,pinmux = <0>; ++ function = "gpio_in"; ++ }; ++ ++ /omit-if-no-ref/ ++ gmac1_pins_default: gmac1@0 { ++ pins = "PJ0", "PJ1", "PJ2", "PJ3", ++ "PJ4", "PJ5", "PJ6", "PJ7", ++ "PJ8", "PJ9", "PJ10", "PJ11", ++ "PJ12","PJ13", "PJ14", "PJ15"; ++ allwinner,pinmux = <5>; ++ function = "gmac1"; ++ drive-strength = <40>; ++ bias-pull-up; ++ }; ++ ++ /omit-if-no-ref/ ++ gmac1_pins_sleep: gmac1@1 { ++ pins = "PJ0", "PJ1", "PJ2", "PJ3", ++ "PJ4", "PJ5", "PJ6", "PJ7", ++ "PJ8", "PJ9", "PJ10", "PJ11", ++ "PJ12","PJ13", "PJ14", "PJ15"; ++ allwinner,pinmux = <0>; ++ function = "gpio_in"; ++ }; ++ + /omit-if-no-ref/ + mmc1_pins: mmc1-pins { + pins = "PG0" ,"PG1", "PG2", "PG3", "PG4", "PG5"; + allwinner,pinmux = <2>; + function = "mmc1"; +@@ -167,10 +212,93 @@ ccu: clock@2001000 { + clock-names = "hosc", "losc", "iosc"; + #clock-cells = <1>; + #reset-cells = <1>; + }; + ++ gmac0: gmac0@4500000 { ++ compatible = "allwinner,sunxi-gmac"; ++ reg = <0x04500000 0x10000>, ++ <0x03000030 0x4>; ++ interrupts = ; ++ interrupt-names = "gmacirq"; ++ clocks = <&ccu CLK_BUS_EMAC0>, <&ccu CLK_EMAC0_25M>; ++ clock-names = "gmac", "phy25m"; ++ resets = <&ccu RST_BUS_EMAC0>; ++ phy-handle = <&gmac0_phy0>; ++ status = "disabled"; ++ }; ++ ++ gmac1: ethernet@4510000 { ++ compatible = "allwinner,sunxi-gmac-200", "snps,dwmac-4.20a"; ++ reg = <0x04510000 0x10000>, ++ <0x03000034 0x4>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ clocks = <&ccu CLK_BUS_EMAC1>, <&ccu CLK_MBUS_EMAC1>, <&ccu CLK_EMAC1_25M>; ++ clock-names = "stmmaceth", "pclk", "phy25m"; ++ resets = <&ccu RST_BUS_EMAC1>; ++ reset-names = "stmmaceth"; ++ phy-handle = <&gmac1_phy0>; ++ //todo power-domains = <&pd1 A523_PCK_VO1>; ++ status = "disabled"; ++ ++ snps,fixed-burst; ++ ++ snps,axi-config = <&gmac1_stmmac_axi_setup>; ++ snps,mtl-rx-config = <&gmac1_mtl_rx_setup>; ++ snps,mtl-tx-config = <&gmac1_mtl_tx_setup>; ++ ++ gmac1_stmmac_axi_setup: stmmac-axi-config { ++ snps,wr_osr_lmt = <0xf>; ++ snps,rd_osr_lmt = <0xf>; ++ snps,blen = <256 128 64 32 16 8 4>; ++ }; ++ ++ gmac1_mtl_rx_setup: rx-queues-config { ++ snps,rx-queues-to-use = <1>; ++ queue0 {}; ++ }; ++ ++ gmac1_mtl_tx_setup: tx_queues-config { ++ snps,tx-queues-to-use = <1>; ++ queue0 {}; ++ }; ++ ++ mdio1: mdio1@1 { ++ compatible = "snps,dwmac-mdio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ gmac1_phy0: ethernet-phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0x1>; ++ max-speed = <1000>; /* Max speed capability */ ++ reset-gpios = <&pio 9 27 GPIO_ACTIVE_LOW>; /* PJ27; 9 is PJ */ ++ /* PHY datasheet rst time */ ++ reset-assert-us = <10000>; ++ reset-deassert-us = <150000>; ++ }; ++ }; ++ }; ++ ++ mdio0: mdio0@4500048 { ++ compatible = "allwinner,sunxi-mdio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x04500048 0x8>; ++ status = "disabled"; ++ ++ gmac0_phy0: ethernet-phy@1 { ++ reg = <1>; ++ max-speed = <1000>; /* Max speed capability */ ++ reset-gpios = <&pio 7 19 GPIO_ACTIVE_LOW>; /* PH19 */ ++ /* PHY datasheet rst time */ ++ reset-assert-us = <10000>; ++ reset-deassert-us = <150000>; ++ }; ++ }; ++ + mmc0: mmc@4020000 { + compatible = "allwinner,sun55i-a523-mmc", + "allwinner,sun20i-d1-mmc"; + reg = <0x04020000 0x1000>; + clocks = <&ccu CLK_BUS_MMC0>, <&ccu CLK_MMC0>; +diff --git a/arch/arm64/boot/dts/allwinner/sun55i-a527-radxa-a5e.dts b/arch/arm64/boot/dts/allwinner/sun55i-a527-radxa-a5e.dts +index ae92905378b6..5eb796bff02d 100644 +--- a/arch/arm64/boot/dts/allwinner/sun55i-a527-radxa-a5e.dts ++++ b/arch/arm64/boot/dts/allwinner/sun55i-a527-radxa-a5e.dts +@@ -11,16 +11,39 @@ / { + model = "Radxa A5E"; + compatible = "radxa,a5e", "allwinner,sun55i-a527"; + + aliases { + serial0 = &uart0; ++ mmc0 = &mmc0; ++ mmc1 = &mmc1; ++ mmc2 = &mmc2; ++ ethernet0 = &gmac0; ++ ethernet1 = &gmac1; ++ ethernet2 = &wlan; ++ + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + ++ leds { ++ compatible = "gpio-leds"; ++ ++ led-0 { ++ label = "radxa:green:power"; ++ gpios = <&r_pio 0 4 GPIO_ACTIVE_LOW>; /* PL4 */ ++ linux,default-trigger = "heartbeat"; ++ }; ++ ++ led-1 { ++ label = "radxa:blue:user"; ++ gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL5 */ ++ linux,default-trigger = "disk-activity"; ++ }; ++ }; ++ + reg_vcc5v: vcc5v { + /* board wide 5V supply from the USB-C connector */ + compatible = "regulator-fixed"; + regulator-name = "vcc-5v"; + regulator-min-microvolt = <5000000>; +@@ -35,27 +58,104 @@ reg_usb_vbus: vbus { + regulator-max-microvolt = <5000000>; + vin-supply = <®_vcc5v>; + gpio = <&r_pio 0 8 GPIO_ACTIVE_HIGH>; /* PL8 */ + enable-active-high; + }; ++ ++ reg_3v3_wifi: 3v3-wifi { ++ compatible = "regulator-fixed"; ++ regulator-name = "3v3-wifi"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ vin-supply = <®_vcc5v>; ++ gpio = <&r_pio 0 7 GPIO_ACTIVE_HIGH>; /* PL7 */ ++ enable-active-high; ++ }; + }; + + &ehci0 { + status = "okay"; + }; + + &ehci1 { + status = "okay"; + }; + ++&gmac0 { ++ phy-mode = "rgmii"; ++ pinctrl-names = "default", "sleep"; ++ pinctrl-0 = <&gmac0_pins_default>; ++ pinctrl-1 = <&gmac0_pins_sleep>; ++ sunxi,phy-clk-type = <0>; ++ tx-delay = <2>; ++ rx-delay = <4>; ++ gmac3v3-supply = <®_cldo3>; ++ status = "okay"; ++}; ++ ++&gmac1 { ++ phy-mode = "rgmii"; ++ pinctrl-names = "default", "sleep"; ++ pinctrl-0 = <&gmac1_pins_default>; ++ pinctrl-1 = <&gmac1_pins_sleep>; ++ aw,soc-phy25m; ++ tx-delay = <3>; ++ rx-delay = <4>; ++ dwmac3v3-supply = <®_cldo3>; ++ status = "okay"; ++ ++ mdio1: mdio1@1 { ++ gmac1_phy0: ethernet-phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0x1>; ++ reset-gpios = <&pio 9 16 GPIO_ACTIVE_LOW>; /* PJ16 */ ++ reset-assert-us = <10000>; ++ reset-deassert-us = <150000>; ++ }; ++ }; ++}; ++ ++&mdio0 { ++ compatible = "allwinner,sunxi-mdio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x04500048 0x8>; ++ status = "okay"; ++ ++ gmac0_phy0: ethernet-phy@1 { ++ compatible = "ethernet-phy-ieee802.3-c22"; ++ reg = <0x1>; ++ reset-gpios = <&pio 7 8 GPIO_ACTIVE_LOW>; /* PH8 */ ++ reset-assert-us = <10000>; ++ reset-deassert-us = <150000>; ++ }; ++}; ++ + &mmc0 { + vmmc-supply = <®_cldo3>; + cd-gpios = <&pio 5 6 (GPIO_ACTIVE_LOW | GPIO_PULL_DOWN)>; /* PF6 */ + bus-width = <4>; + status = "okay"; + }; + ++&mmc1_pins { ++ drive-strength = <40>; ++}; ++ ++&mmc1 { ++ vmmc-supply = <®_3v3_wifi>; ++ bus-width = <4>; ++ non-removable; ++ // todo: investigate why clock above 40MHz makes data errors ++ max-frequency = <35000000>; ++ status = "okay"; ++ ++ wlan: wifi@1 { ++ reg = <1>; ++ }; ++}; ++ + &ohci0 { + status = "okay"; + }; + + &ohci1 { +diff --git a/drivers/clk/sunxi-ng/ccu-sun55i-a523.c b/drivers/clk/sunxi-ng/ccu-sun55i-a523.c +index 748b160f279a..b6dffa692ead 100644 +--- a/drivers/clk/sunxi-ng/ccu-sun55i-a523.c ++++ b/drivers/clk/sunxi-ng/ccu-sun55i-a523.c +@@ -730,12 +730,28 @@ static SUNXI_CCU_GATE_HWS_WITH_PREDIV(emac0_25M_clk, "emac0-25M", + static SUNXI_CCU_GATE_HWS_WITH_PREDIV(emac1_25M_clk, "emac1-25M", + pll_periph0_150M_hws, + 0x974, BIT(31) | BIT(30), 6, 0); + static SUNXI_CCU_GATE_HWS(bus_emac0_clk, "bus-emac0", ahb_hws, 0x97c, + BIT(0), 0); ++/* GMAC1 BSP code ++static SUNXI_CCU_GATE(gmac1_25m_clk, "gmac1-25m", ++ "dcxo24M", ++ 0x0974, BIT(31) | BIT(30), 0); ++ ++static SUNXI_CCU_GATE_ASSOC(gmac1_clk, "gmac1", ++ "dcxo24M", ++ 0x098C, BIT(17) | BIT(16) | BIT(0), ++ 0x0E04, BIT(21) | BIT(9), ++ 0x0); ++static SUNXI_CCU_GATE(gmac1_mbus_gate_clk, "gmac1-mbus-gate", ++ "dcxo24M", ++ 0x0804, BIT(12), 0); ++*/ + static SUNXI_CCU_GATE_HWS(bus_emac1_clk, "bus-emac1", ahb_hws, 0x98c, + BIT(0), 0); ++static SUNXI_CCU_GATE_HWS(mbus_emac1_clk, "mbus-emac1", ahb_hws, 0x804, ++ BIT(12), 0); + + static const struct clk_parent_data ir_rx_parents[] = { + { .fw_name = "losc" }, + { .fw_name = "hosc" }, + }; +@@ -1231,10 +1247,11 @@ static struct ccu_common *sun55i_a523_ccu_clks[] = { + &bus_spifc_clk.common, + &emac0_25M_clk.common, + &emac1_25M_clk.common, + &bus_emac0_clk.common, + &bus_emac1_clk.common, ++ &mbus_emac1_clk.common, + &ir_rx_clk.common, + &bus_ir_rx_clk.common, + &ir_tx_clk.common, + &bus_ir_tx_clk.common, + &gpadc0_clk.common, +@@ -1407,10 +1424,11 @@ static struct clk_hw_onecell_data sun55i_a523_hw_clks = { + [CLK_BUS_SPIFC] = &bus_spifc_clk.common.hw, + [CLK_EMAC0_25M] = &emac0_25M_clk.common.hw, + [CLK_EMAC1_25M] = &emac1_25M_clk.common.hw, + [CLK_BUS_EMAC0] = &bus_emac0_clk.common.hw, + [CLK_BUS_EMAC1] = &bus_emac1_clk.common.hw, ++ [CLK_MBUS_EMAC1] = &mbus_emac1_clk.common.hw, + [CLK_IR_RX] = &ir_rx_clk.common.hw, + [CLK_BUS_IR_RX] = &bus_ir_rx_clk.common.hw, + [CLK_IR_TX] = &ir_tx_clk.common.hw, + [CLK_BUS_IR_TX] = &bus_ir_tx_clk.common.hw, + [CLK_GPADC0] = &gpadc0_clk.common.hw, +diff --git a/drivers/clk/sunxi-ng/ccu-sun55i-a523.h b/drivers/clk/sunxi-ng/ccu-sun55i-a523.h +index fc8dd42f1b47..024c91b7d49d 100644 +--- a/drivers/clk/sunxi-ng/ccu-sun55i-a523.h ++++ b/drivers/clk/sunxi-ng/ccu-sun55i-a523.h +@@ -8,7 +8,8 @@ + + #include + #include + + #define CLK_NUMBER (CLK_FANOUT2 + 1) ++#define CLK_NUMBER (CLK_MBUS_EMAC1 + 1) + + #endif /* _CCU_SUN55I_A523_H */ +diff --git a/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig +index 3e81059f8693..e2dec1068fc2 100644 +--- a/drivers/net/ethernet/allwinner/Kconfig ++++ b/drivers/net/ethernet/allwinner/Kconfig +@@ -32,6 +32,10 @@ config SUN4I_EMAC + Support for Allwinner A10 EMAC ethernet driver. + + To compile this driver as a module, choose M here. The module + will be called sun4i-emac. + ++source "drivers/net/ethernet/allwinner/sunxi-stmmac/Kconfig" ++source "drivers/net/ethernet/allwinner/gmac-200/Kconfig" ++source "drivers/net/ethernet/allwinner/gmac/Kconfig" ++ + endif # NET_VENDOR_ALLWINNER +diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile +index ddd5a5079e8a..39317cac3d5d 100644 +--- a/drivers/net/ethernet/allwinner/Makefile ++++ b/drivers/net/ethernet/allwinner/Makefile +@@ -2,5 +2,8 @@ + # + # Makefile for the Allwinner device drivers. + # + + obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o ++obj-$(CONFIG_NET_VENDOR_ALLWINNER) += sunxi-stmmac/ ++obj-$(CONFIG_NET_VENDOR_ALLWINNER) += gmac-200/ ++obj-$(CONFIG_NET_VENDOR_ALLWINNER) += gmac/ +diff --git a/drivers/net/ethernet/allwinner/gmac-200/Kconfig b/drivers/net/ethernet/allwinner/gmac-200/Kconfig +new file mode 100644 +index 000000000000..8d1631fd305c +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/Kconfig +@@ -0,0 +1,34 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++menu "Stmmac Drivers" ++ ++config SUNXI55I_GMAC200 ++ tristate "Allwinner A523 GMAC-200 driver" ++ depends on OF && (ARCH_SUNXI || COMPILE_TEST) ++ select STMMAC_ETH ++ select STMMAC_PLATFORM ++ select SUNXI55I_STMMAC ++ ++ help ++ Support for Allwinner A523 GMAC-200/GMAC-300 ethernet controllers. ++ ++ This selects Allwinner A523 SoC glue layer support for the ++ stmmac device driver. This driver is used for ++ GMAC-200/GMAC-300 ethernet controller. ++ ++if SUNXI55I_GMAC200 ++config SUNXI55I_STMMAC ++ tristate "Allwinner A523 GMAC-200 STMMAC support" ++ depends on OF && (ARCH_SUNXI || COMPILE_TEST) ++ help ++ Support stmmac device driver for Allwinner A523 GMAC-200/GMAC-300. ++ ++config SUNXI55I_STMMAC_UIO ++ tristate "Allwinner A523 GMAC-200 UIO ethernet controller" ++ default n ++ select UIO ++ help ++ Say M here if you want to use the sunxi-uio.ko for DPDK on A523. ++ ++endif ++ ++endmenu +diff --git a/drivers/net/ethernet/allwinner/gmac-200/Makefile b/drivers/net/ethernet/allwinner/gmac-200/Makefile +new file mode 100644 +index 000000000000..6542d48f1da7 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/Makefile +@@ -0,0 +1,8 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ccflags-y += -I $(srctree)/include/linux/ ++ccflags-y += -I $(srctree)/drivers/net/ethernet/stmicro/ ++ccflags-y += -DDYNAMIC_DEBUG_MODULE ++ ++obj-$(CONFIG_SUNXI55I_STMMAC) += sunxi-stmmac.o ++sunxi-stmmac-objs += dwmac-sunxi.o dwmac-sunxi-sysfs.o ++obj-$(CONFIG_SUNXI55I_STMMAC_UIO) += sunxi-uio.o +diff --git a/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.c b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.c +new file mode 100644 +index 000000000000..0a974d352506 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.c +@@ -0,0 +1,927 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner DWMAC driver sysfs. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++*/ ++ ++#include "sunxi-log.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stmmac/stmmac.h" ++ ++#include "dwmac-sunxi-sysfs.h" ++ ++struct sunxi_dwmac_hdr { ++ __be32 version; ++ __be64 magic; ++ u8 id; ++ u32 tx; ++ u32 rx; ++} __packed; ++ ++#define SUNXI_DWMAC_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \ ++ sizeof(struct sunxi_dwmac_hdr)) ++#define SUNXI_DWMAC_PKT_MAGIC 0xdeadcafecafedeadULL ++#define SUNXI_DWMAC_TIMEOUT msecs_to_jiffies(2) ++ ++struct sunxi_dwmac_packet_attr { ++ u32 tx; ++ u32 rx; ++ unsigned char *src; ++ unsigned char *dst; ++ u32 ip_src; ++ u32 ip_dst; ++ int tcp; ++ int sport; ++ int dport; ++ int dont_wait; ++ int timeout; ++ int size; ++ int max_size; ++ u8 id; ++ u16 queue_mapping; ++ u64 timestamp; ++}; ++ ++struct sunxi_dwmac_loop_priv { ++ struct sunxi_dwmac_packet_attr *packet; ++ struct packet_type pt; ++ struct completion comp; ++ int ok; ++}; ++ ++struct sunxi_dwmac_calibrate { ++ u8 id; ++ u32 tx_delay; ++ u32 rx_delay; ++ u32 window_tx; ++ u32 window_rx; ++}; ++ ++/** ++ * sunxi_dwmac_parse_read_str - parse the input string for write attri. ++ * @str: string to be parsed, eg: "0x00 0x01". ++ * @addr: store the phy addr. eg: 0x00. ++ * @reg: store the reg addr. eg: 0x01. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++static int sunxi_dwmac_parse_read_str(char *str, u16 *addr, u16 *reg) ++{ ++ char *ptr = str; ++ char *tstr = NULL; ++ int ret; ++ ++ /** ++ * Skip the leading whitespace, find the true split symbol. ++ * And it must be 'address value'. ++ */ ++ tstr = strim(str); ++ ptr = strchr(tstr, ' '); ++ if (!ptr) ++ return -EINVAL; ++ ++ /** ++ * Replaced split symbol with a %NUL-terminator temporary. ++ * Will be fixed at end. ++ */ ++ *ptr = '\0'; ++ ret = kstrtos16(tstr, 16, addr); ++ if (ret) ++ goto out; ++ ++ ret = kstrtos16(skip_spaces(ptr + 1), 16, reg); ++ ++out: ++ return ret; ++} ++ ++/** ++ * sunxi_dwmac_parse_write_str - parse the input string for compare attri. ++ * @str: string to be parsed, eg: "0x00 0x11 0x11". ++ * @addr: store the phy addr. eg: 0x00. ++ * @reg: store the reg addr. eg: 0x11. ++ * @val: store the value. eg: 0x11. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++static int sunxi_dwmac_parse_write_str(char *str, u16 *addr, ++ u16 *reg, u16 *val) ++{ ++ u16 result_addr[3] = { 0 }; ++ char *ptr = str; ++ char *ptr2 = NULL; ++ int i, ret; ++ ++ for (i = 0; i < ARRAY_SIZE(result_addr); i++) { ++ ptr = skip_spaces(ptr); ++ ptr2 = strchr(ptr, ' '); ++ if (ptr2) ++ *ptr2 = '\0'; ++ ++ ret = kstrtou16(ptr, 16, &result_addr[i]); ++ ++ if (!ptr2 || ret) ++ break; ++ ++ ptr = ptr2 + 1; ++ } ++ ++ *addr = result_addr[0]; ++ *reg = result_addr[1]; ++ *val = result_addr[2]; ++ ++ return ret; ++} ++ ++static struct sk_buff *sunxi_dwmac_get_skb(struct stmmac_priv *priv, ++ struct sunxi_dwmac_packet_attr *attr) ++{ ++ struct sk_buff *skb = NULL; ++ struct udphdr *uhdr = NULL; ++ struct tcphdr *thdr = NULL; ++ struct sunxi_dwmac_hdr *shdr; ++ struct ethhdr *ehdr; ++ struct iphdr *ihdr; ++ int iplen, size; ++ ++ size = attr->size + SUNXI_DWMAC_PKT_SIZE; ++ ++ if (attr->tcp) ++ size += sizeof(*thdr); ++ else ++ size += sizeof(*uhdr); ++ ++ if (attr->max_size && (attr->max_size > size)) ++ size = attr->max_size; ++ ++ skb = netdev_alloc_skb(priv->dev, size); ++ if (!skb) ++ return NULL; ++ ++ prefetchw(skb->data); ++ ++ ehdr = skb_push(skb, ETH_HLEN); ++ skb_reset_mac_header(skb); ++ ++ skb_set_network_header(skb, skb->len); ++ ihdr = skb_put(skb, sizeof(*ihdr)); ++ ++ skb_set_transport_header(skb, skb->len); ++ if (attr->tcp) ++ thdr = skb_put(skb, sizeof(*thdr)); ++ else ++ uhdr = skb_put(skb, sizeof(*uhdr)); ++ ++ eth_zero_addr(ehdr->h_source); ++ eth_zero_addr(ehdr->h_dest); ++ if (attr->src) ++ ether_addr_copy(ehdr->h_source, attr->src); ++ if (attr->dst) ++ ether_addr_copy(ehdr->h_dest, attr->dst); ++ ++ ehdr->h_proto = htons(ETH_P_IP); ++ ++ if (attr->tcp) { ++ thdr->source = htons(attr->sport); ++ thdr->dest = htons(attr->dport); ++ thdr->doff = sizeof(*thdr) / 4; ++ thdr->check = 0; ++ } else { ++ uhdr->source = htons(attr->sport); ++ uhdr->dest = htons(attr->dport); ++ uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); ++ if (attr->max_size) ++ uhdr->len = htons(attr->max_size - ++ (sizeof(*ihdr) + sizeof(*ehdr))); ++ uhdr->check = 0; ++ } ++ ++ ihdr->ihl = 5; ++ ihdr->ttl = 32; ++ ihdr->version = 4; ++ if (attr->tcp) ++ ihdr->protocol = IPPROTO_TCP; ++ else ++ ihdr->protocol = IPPROTO_UDP; ++ iplen = sizeof(*ihdr) + sizeof(*shdr) + attr->size; ++ if (attr->tcp) ++ iplen += sizeof(*thdr); ++ else ++ iplen += sizeof(*uhdr); ++ ++ if (attr->max_size) ++ iplen = attr->max_size - sizeof(*ehdr); ++ ++ ihdr->tot_len = htons(iplen); ++ ihdr->frag_off = 0; ++ ihdr->saddr = htonl(attr->ip_src); ++ ihdr->daddr = htonl(attr->ip_dst); ++ ihdr->tos = 0; ++ ihdr->id = 0; ++ ip_send_check(ihdr); ++ ++ shdr = skb_put(skb, sizeof(*shdr)); ++ shdr->version = 0; ++ shdr->magic = cpu_to_be64(SUNXI_DWMAC_PKT_MAGIC); ++ shdr->id = attr->id; ++ shdr->tx = attr->tx; ++ shdr->rx = attr->rx; ++ ++ if (attr->size) ++ skb_put(skb, attr->size); ++ if (attr->max_size && (attr->max_size > skb->len)) ++ skb_put(skb, attr->max_size - skb->len); ++ ++ skb->csum = 0; ++ skb->ip_summed = CHECKSUM_PARTIAL; ++ if (attr->tcp) { ++ thdr->check = ~tcp_v4_check(skb->len, ihdr->saddr, ihdr->daddr, 0); ++ skb->csum_start = skb_transport_header(skb) - skb->head; ++ skb->csum_offset = offsetof(struct tcphdr, check); ++ } else { ++ udp4_hwcsum(skb, ihdr->saddr, ihdr->daddr); ++ } ++ ++ skb->protocol = htons(ETH_P_IP); ++ skb->pkt_type = PACKET_HOST; ++ skb->dev = priv->dev; ++ ++ if (attr->timestamp) ++ skb->tstamp = ns_to_ktime(attr->timestamp); ++ ++ return skb; ++} ++ ++static int sunxi_dwmac_loopback_validate(struct sk_buff *skb, ++ struct net_device *ndev, ++ struct packet_type *pt, ++ struct net_device *orig_ndev) ++{ ++ struct sunxi_dwmac_loop_priv *tpriv = pt->af_packet_priv; ++ unsigned char *src = tpriv->packet->src; ++ unsigned char *dst = tpriv->packet->dst; ++ struct sunxi_dwmac_hdr *shdr; ++ struct ethhdr *ehdr; ++ struct udphdr *uhdr; ++ struct tcphdr *thdr; ++ struct iphdr *ihdr; ++ ++ skb = skb_unshare(skb, GFP_ATOMIC); ++ if (!skb) ++ goto out; ++ ++ if (skb_linearize(skb)) ++ goto out; ++ if (skb_headlen(skb) < (SUNXI_DWMAC_PKT_SIZE - ETH_HLEN)) ++ goto out; ++ ++ ehdr = (struct ethhdr *)skb_mac_header(skb); ++ if (dst) { ++ if (!ether_addr_equal_unaligned(ehdr->h_dest, dst)) ++ goto out; ++ } ++ if (src) { ++ if (!ether_addr_equal_unaligned(ehdr->h_source, src)) ++ goto out; ++ } ++ ++ ihdr = ip_hdr(skb); ++ ++ if (tpriv->packet->tcp) { ++ if (ihdr->protocol != IPPROTO_TCP) ++ goto out; ++ ++ thdr = (struct tcphdr *)((u8 *)ihdr + 4 * ihdr->ihl); ++ if (thdr->dest != htons(tpriv->packet->dport)) ++ goto out; ++ ++ shdr = (struct sunxi_dwmac_hdr *)((u8 *)thdr + sizeof(*thdr)); ++ } else { ++ if (ihdr->protocol != IPPROTO_UDP) ++ goto out; ++ ++ uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl); ++ if (uhdr->dest != htons(tpriv->packet->dport)) ++ goto out; ++ ++ shdr = (struct sunxi_dwmac_hdr *)((u8 *)uhdr + sizeof(*uhdr)); ++ } ++ ++ if (shdr->magic != cpu_to_be64(SUNXI_DWMAC_PKT_MAGIC)) ++ goto out; ++ if (tpriv->packet->id != shdr->id) ++ goto out; ++ if (tpriv->packet->tx != shdr->tx || tpriv->packet->rx != shdr->rx) ++ goto out; ++ ++ tpriv->ok = true; ++ complete(&tpriv->comp); ++out: ++ kfree_skb(skb); ++ return 0; ++} ++ ++static int sunxi_dwmac_loopback_run(struct stmmac_priv *priv, ++ struct sunxi_dwmac_packet_attr *attr) ++{ ++ struct sunxi_dwmac_loop_priv *tpriv; ++ struct sk_buff *skb = NULL; ++ int ret = 0; ++ ++ tpriv = kzalloc(sizeof(*tpriv), GFP_KERNEL); ++ if (!tpriv) ++ return -ENOMEM; ++ ++ tpriv->ok = false; ++ init_completion(&tpriv->comp); ++ ++ tpriv->pt.type = htons(ETH_P_IP); ++ tpriv->pt.func = sunxi_dwmac_loopback_validate; ++ tpriv->pt.dev = priv->dev; ++ tpriv->pt.af_packet_priv = tpriv; ++ tpriv->packet = attr; ++ ++ if (!attr->dont_wait) ++ dev_add_pack(&tpriv->pt); ++ ++ skb = sunxi_dwmac_get_skb(priv, attr); ++ if (!skb) { ++ ret = -ENOMEM; ++ goto cleanup; ++ } ++ ++ ret = dev_direct_xmit(skb, attr->queue_mapping); ++ if (ret) ++ goto cleanup; ++ ++ if (attr->dont_wait) ++ goto cleanup; ++ ++ if (!attr->timeout) ++ attr->timeout = SUNXI_DWMAC_TIMEOUT; ++ ++ wait_for_completion_timeout(&tpriv->comp, attr->timeout); ++ ret = tpriv->ok ? 0 : -ETIMEDOUT; ++ ++cleanup: ++ if (!attr->dont_wait) ++ dev_remove_pack(&tpriv->pt); ++ kfree(tpriv); ++ return ret; ++} ++ ++static int sunxi_dwmac_test_delaychain(struct sunxi_dwmac *chip, struct sunxi_dwmac_calibrate *cali) ++{ ++ struct net_device *ndev = dev_get_drvdata(chip->dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ unsigned char src[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; ++ unsigned char dst[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; ++ struct sunxi_dwmac_packet_attr attr = { }; ++ ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, cali->tx_delay); ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, cali->rx_delay); ++ ++ attr.src = src; ++ attr.dst = dst; ++ attr.tcp = true; ++ attr.queue_mapping = 0; ++ stmmac_get_systime(priv, priv->ptpaddr, &attr.timestamp); ++ attr.id = cali->id; ++ attr.tx = cali->tx_delay; ++ attr.rx = cali->rx_delay; ++ ++ return sunxi_dwmac_loopback_run(priv, &attr); ++} ++ ++static int sunxi_dwmac_calibrate_scan_window(struct sunxi_dwmac *chip, struct sunxi_dwmac_calibrate *cali) ++{ ++ struct net_device *ndev = dev_get_drvdata(chip->dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ char *buf, *ptr; ++ int tx_sum, rx_sum, count; ++ u32 tx, rx; ++ int ret = 0; ++ ++ buf = devm_kzalloc(chip->dev, PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ netif_testing_on(ndev); ++ ++ ret = phy_loopback(priv->dev->phydev, true); ++ if (ret) ++ goto err; ++ ++ tx_sum = rx_sum = count = 0; ++ ++ for (tx = 0; tx < cali->window_tx; tx++) { ++ ptr = buf; ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "TX(0x%02x): ", tx); ++ for (rx = 0; rx < cali->window_rx; rx++) { ++ cali->id++; ++ cali->tx_delay = tx; ++ cali->rx_delay = rx; ++ if (sunxi_dwmac_test_delaychain(chip, cali) < 0) { ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "X"); ++ } else { ++ tx_sum += tx; ++ rx_sum += rx; ++ count++; ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "-"); ++ } ++ } ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "\n"); ++ printk(buf); ++ } ++ ++ if (tx_sum && rx_sum && count) { ++ cali->tx_delay = tx_sum / count; ++ cali->rx_delay = rx_sum / count; ++ } else { ++ cali->tx_delay = cali->rx_delay = 0; ++ } ++ ++ phy_loopback(priv->dev->phydev, false); ++ ++err: ++ netif_testing_off(ndev); ++ devm_kfree(chip->dev, buf); ++ return ret; ++} ++ ++static ssize_t sunxi_dwmac_calibrate_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ struct phy_device *phydev = priv->dev->phydev; ++ struct sunxi_dwmac_calibrate *cali; ++ u32 old_tx, old_rx; ++ int ret; ++ ++ if (!ndev || !phydev) { ++ sunxi_err(chip->dev, "Not found netdevice or phy\n"); ++ return -EINVAL; ++ } ++ ++ if (!netif_carrier_ok(ndev) || !phydev->link) { ++ sunxi_err(chip->dev, "Netdevice or phy not link\n"); ++ return -EINVAL; ++ } ++ ++ if (phydev->speed < SPEED_1000) { ++ sunxi_err(chip->dev, "Speed %s no need calibrate\n", phy_speed_to_str(phydev->speed)); ++ return -EINVAL; ++ } ++ ++ cali = devm_kzalloc(dev, sizeof(*cali), GFP_KERNEL); ++ if (!cali) ++ return -ENOMEM; ++ ++ old_tx = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX); ++ old_rx = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX); ++ ++ cali->window_tx = chip->variant->tx_delay_max + 1; ++ cali->window_rx = chip->variant->rx_delay_max + 1; ++ ++ ret = sunxi_dwmac_calibrate_scan_window(chip, cali); ++ if (ret) { ++ sunxi_err(dev, "Calibrate scan window tx:%d rx:%d failed\n", cali->window_tx, cali->window_rx); ++ goto err; ++ } ++ ++ if (cali->tx_delay && cali->rx_delay) { ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, cali->tx_delay); ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, cali->rx_delay); ++ sunxi_info(chip->dev, "Calibrate suitable delay tx:%d rx:%d\n", cali->tx_delay, cali->rx_delay); ++ } else { ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, old_tx); ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, old_rx); ++ sunxi_warn(chip->dev, "Calibrate cannot find suitable delay\n"); ++ } ++ ++err: ++ devm_kfree(dev, cali); ++ return count; ++} ++ ++static int sunxi_dwmac_test_ecc_inject(struct stmmac_priv *priv, enum sunxi_dwmac_ecc_fifo_type type, u8 bit) ++{ ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ static const u32 wdata[2] = {0x55555555, 0x55555555}; ++ u32 rdata[ARRAY_SIZE(wdata)]; ++ u32 mtl_dbg_ctl, mtl_dpp_ecc_eic; ++ u32 val; ++ int i, ret = 0; ++ ++ mtl_dbg_ctl = readl(priv->ioaddr + MTL_DBG_CTL); ++ mtl_dpp_ecc_eic = readl(priv->ioaddr + MTL_DPP_ECC_EIC); ++ ++ mtl_dbg_ctl &= ~EIAEE; /* disable ecc error injection on address */ ++ mtl_dbg_ctl |= DBGMOD | FDBGEN; /* ecc debug mode enable */ ++ mtl_dpp_ecc_eic &= ~EIM; /* indicate error injection on data */ ++ mtl_dpp_ecc_eic |= FIELD_PREP(BLEI, 36); /* inject bit location is bit0 and bit36 */ ++ ++ /* ecc select inject bit */ ++ switch (bit) { ++ case 0: ++ mtl_dbg_ctl &= ~EIEE; /* ecc inject error disable */ ++ break; ++ case 1: ++ mtl_dbg_ctl &= ~EIEC; /* ecc inject insert 1-bit error */ ++ mtl_dbg_ctl |= EIEE; /* ecc inject error enable */ ++ break; ++ case 2: ++ mtl_dbg_ctl |= EIEC; /* ecc inject insert 2-bit error */ ++ mtl_dbg_ctl |= EIEE; /* ecc inject error enable */ ++ break; ++ default: ++ ret = -EINVAL; ++ sunxi_err(chip->dev, "test unsupport ecc inject bit %d\n", bit); ++ goto err; ++ } ++ ++ /* ecc select fifo */ ++ mtl_dbg_ctl &= ~FIFOSEL; ++ switch (type) { ++ case SUNXI_DWMAC_ECC_FIFO_TX: ++ mtl_dbg_ctl |= FIELD_PREP(FIFOSEL, 0x0); ++ break; ++ case SUNXI_DWMAC_ECC_FIFO_RX: ++ mtl_dbg_ctl |= FIELD_PREP(FIFOSEL, 0x3); ++ break; ++ default: ++ ret = -EINVAL; ++ sunxi_err(chip->dev, "test unsupport ecc inject fifo type %d\n", type); ++ goto err; ++ } ++ ++ writel(mtl_dpp_ecc_eic, priv->ioaddr + MTL_DPP_ECC_EIC); ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ++ /* write fifo debug data */ ++ mtl_dbg_ctl &= ~FIFORDEN; ++ mtl_dbg_ctl |= FIFOWREN; ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ writel(wdata[i], priv->ioaddr + MTL_FIFO_DEBUG_DATA); ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ret = readl_poll_timeout_atomic(priv->ioaddr + MTL_DBG_STS, val, !(val & FIFOBUSY), 10, 200000); ++ if (ret) { ++ sunxi_err(chip->dev, "timeout with ecc debug fifo write busy (%#x)\n", val); ++ goto err; ++ } ++ } ++ ++ /* read fifo debug data */ ++ mtl_dbg_ctl &= ~FIFOWREN; ++ mtl_dbg_ctl |= FIFORDEN; ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ret = readl_poll_timeout_atomic(priv->ioaddr + MTL_DBG_STS, val, !(val & FIFOBUSY), 10, 200000); ++ if (ret) { ++ sunxi_err(chip->dev, "test timeout with ecc debug fifo read busy (%#x)\n", val); ++ goto err; ++ } ++ rdata[i] = readl(priv->ioaddr + MTL_FIFO_DEBUG_DATA); ++ } ++ ++ /* compare data */ ++ switch (bit) { ++ case 0: ++ case 1: ++ /* for ecc error inject 0/1 bit, read should be same with write */ ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ if (rdata[i] != wdata[i]) { ++ ret = -EINVAL; ++ break; ++ } ++ } ++ break; ++ case 2: ++ /* for ecc error inject 2 bit, read should be different with write */ ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ if (rdata[i] == wdata[i]) { ++ ret = -EINVAL; ++ break; ++ } ++ } ++ break; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) ++ sunxi_info(chip->dev, "fifo %d write [%#x] -> read [%#x]\n", i, wdata[i], rdata[i]); ++ ++err: ++ /* ecc debug mode disable */ ++ mtl_dbg_ctl &= ~(EIEE | EIEC | FIFOWREN | FIFORDEN); ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ++ return ret; ++} ++ ++static ssize_t sunxi_dwmac_ecc_inject_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return scnprintf(buf, PAGE_SIZE, ++ "Usage:\n" ++ "echo \"[dir] [inject_bit]\" > ecc_inject\n\n" ++ "[dir] : 0(tx) 1(rx)\n" ++ "[inject_bit] : 0/1/2\n"); ++} ++ ++static ssize_t sunxi_dwmac_ecc_inject_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ struct phy_device *phydev = priv->dev->phydev; ++ static const char *dir_str[] = {"tx", "rx"}; ++ u16 dir, inject_bit; ++ u64 ret; ++ ++ if (!ndev || !phydev) { ++ sunxi_err(chip->dev, "netdevice or phy not found\n"); ++ return -EINVAL; ++ } ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(chip->dev, "netdevice is not running\n"); ++ return -EINVAL; ++ } ++ ++ if (!(chip->variant->flags & SUNXI_DWMAC_MEM_ECC)) { ++ sunxi_err(chip->dev, "ecc not support or enabled\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ ret = sunxi_dwmac_parse_read_str((char *)buf, &dir, &inject_bit); ++ if (ret) ++ return ret; ++ ++ switch (dir) { ++ case 0: ++ dir = SUNXI_DWMAC_ECC_FIFO_TX; ++ break; ++ case 1: ++ dir = SUNXI_DWMAC_ECC_FIFO_RX; ++ break; ++ default: ++ sunxi_err(chip->dev, "test unsupport ecc dir %d\n", dir); ++ return -EINVAL; ++ } ++ ++ netif_testing_on(ndev); ++ ++ /* ecc inject test */ ++ ret = sunxi_dwmac_test_ecc_inject(priv, dir, inject_bit); ++ if (ret) ++ sunxi_info(chip->dev, "test ecc %s inject %d bit : FAILED\n", dir_str[dir], inject_bit); ++ else ++ sunxi_info(chip->dev, "test ecc %s inject %d bit : PASS\n", dir_str[dir], inject_bit); ++ ++ netif_testing_off(ndev); ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_tx_delay_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u32 delay = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX); ++ ++ return scnprintf(buf, PAGE_SIZE, ++ "Usage:\n" ++ "echo [0~%d] > tx_delay\n\n" ++ "now tx_delay: %d\n", ++ chip->variant->tx_delay_max, delay); ++} ++ ++static ssize_t sunxi_dwmac_tx_delay_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u32 delay; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = kstrtou32(buf, 0, &delay); ++ if (ret) ++ return ret; ++ ++ if (delay > chip->variant->tx_delay_max) { ++ sunxi_err(dev, "Tx_delay exceed max %d\n", chip->variant->tx_delay_max); ++ return -EINVAL; ++ } ++ ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, delay); ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_rx_delay_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u32 delay = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX); ++ ++ return scnprintf(buf, PAGE_SIZE, ++ "Usage:\n" ++ "echo [0~%d] > rx_delay\n\n" ++ "now rx_delay: %d\n", ++ chip->variant->rx_delay_max, delay); ++} ++ ++static ssize_t sunxi_dwmac_rx_delay_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u32 delay; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = kstrtou32(buf, 0, &delay); ++ if (ret) ++ return ret; ++ ++ if (delay > chip->variant->rx_delay_max) { ++ sunxi_err(dev, "Rx_delay exceed max %d\n", chip->variant->rx_delay_max); ++ return -EINVAL; ++ } ++ ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, delay); ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_mii_read_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return 0; ++ } ++ ++ chip->mii_reg.value = mdiobus_read(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg); ++ return sprintf(buf, "ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ chip->mii_reg.addr, chip->mii_reg.reg, chip->mii_reg.value); ++} ++ ++static ssize_t sunxi_dwmac_mii_read_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u16 reg, addr; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = sunxi_dwmac_parse_read_str(ptr, &addr, ®); ++ if (ret) ++ return ret; ++ ++ chip->mii_reg.addr = addr; ++ chip->mii_reg.reg = reg; ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_mii_write_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u16 bef_val, aft_val; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return 0; ++ } ++ ++ bef_val = mdiobus_read(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg); ++ mdiobus_write(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg, chip->mii_reg.value); ++ aft_val = mdiobus_read(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg); ++ return sprintf(buf, "before ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n" ++ "after ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ chip->mii_reg.addr, chip->mii_reg.reg, bef_val, ++ chip->mii_reg.addr, chip->mii_reg.reg, aft_val); ++} ++ ++static ssize_t sunxi_dwmac_mii_write_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u16 reg, addr, val; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = sunxi_dwmac_parse_write_str(ptr, &addr, ®, &val); ++ if (ret) ++ return ret; ++ ++ chip->mii_reg.reg = reg; ++ chip->mii_reg.addr = addr; ++ chip->mii_reg.value = val; ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_version_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u16 ip_tag, ip_vrm; ++ ssize_t count = 0; ++ ++ if (chip->variant->get_version) { ++ chip->variant->get_version(chip, &ip_tag, &ip_vrm); ++ count = sprintf(buf, "IP TAG: %x\nIP VRM: %x\n", ip_tag, ip_vrm); ++ } ++ ++ return count; ++} ++ ++static struct device_attribute sunxi_dwmac_tool_attr[] = { ++ __ATTR(calibrate, 0220, NULL, sunxi_dwmac_calibrate_store), ++ __ATTR(rx_delay, 0664, sunxi_dwmac_rx_delay_show, sunxi_dwmac_rx_delay_store), ++ __ATTR(tx_delay, 0664, sunxi_dwmac_tx_delay_show, sunxi_dwmac_tx_delay_store), ++ __ATTR(mii_read, 0664, sunxi_dwmac_mii_read_show, sunxi_dwmac_mii_read_store), ++ __ATTR(mii_write, 0664, sunxi_dwmac_mii_write_show, sunxi_dwmac_mii_write_store), ++ __ATTR(ecc_inject, 0664, sunxi_dwmac_ecc_inject_show, sunxi_dwmac_ecc_inject_store), ++ __ATTR(version, 0444, sunxi_dwmac_version_show, NULL), ++}; ++ ++void sunxi_dwmac_sysfs_init(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(sunxi_dwmac_tool_attr); i++) ++ device_create_file(dev, &sunxi_dwmac_tool_attr[i]); ++} ++ ++void sunxi_dwmac_sysfs_exit(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(sunxi_dwmac_tool_attr); i++) ++ device_remove_file(dev, &sunxi_dwmac_tool_attr[i]); ++} +diff --git a/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.h b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.h +new file mode 100644 +index 000000000000..1031278e197c +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi-sysfs.h +@@ -0,0 +1,19 @@ +++/* SPDX-License-Identifier: GPL-2.0-or-later */ +++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +++/* +++* +++* Allwinner DWMAC driver sysfs haeder. +++* +++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. +++* +++*/ +++ ++#ifndef _DWMAC_SUNXI_SYSFS_H_ ++#define _DWMAC_SUNXI_SYSFS_H_ ++ ++#include "dwmac-sunxi.h" ++ ++void sunxi_dwmac_sysfs_init(struct device *dev); ++void sunxi_dwmac_sysfs_exit(struct device *dev); ++ ++#endif /* _DWMAC_SUNXI_SYSFS_H_ */ +diff --git a/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.c b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.c +new file mode 100644 +index 000000000000..dc8e1e6e7c88 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.c +@@ -0,0 +1,829 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner DWMAC driver. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++* This file is licensed under the terms of the GNU General Public ++* License version 2. This program is licensed "as is" without any ++* warranty of any kind, whether express or implied. ++*/ ++#define SUNXI_MODNAME "stmmac" ++#include "sunxi-log.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stmmac/stmmac.h" ++#include "stmmac/stmmac_platform.h" ++ ++#include "dwmac-sunxi.h" ++ ++#define DWMAC_MODULE_VERSION "0.3.0" ++ ++#define MAC_ADDR_LEN 18 ++#define SUNXI_DWMAC_MAC_ADDRESS "80:3f:5d:09:8b:26" ++#define MAC_IRQ_NAME 8 ++ ++static char mac_str[MAC_ADDR_LEN] = SUNXI_DWMAC_MAC_ADDRESS; ++module_param_string(mac_str, mac_str, MAC_ADDR_LEN, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)"); ++ ++//todo #ifdef MODULE ++//extern int get_custom_mac_address(int fmt, char *name, char *addr); ++//#endif ++ ++static int sunxi_dwmac200_set_syscon(struct sunxi_dwmac *chip) ++{ ++ u32 reg_val = 0; ++ ++ /* Clear interface mode bits */ ++ reg_val &= ~(SUNXI_DWMAC200_SYSCON_ETCS | SUNXI_DWMAC200_SYSCON_EPIT); ++ if (chip->variant->interface & PHY_INTERFACE_MODE_RMII) ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_RMII_EN; ++ ++ switch (chip->interface) { ++ case PHY_INTERFACE_MODE_MII: ++ /* default */ ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ case PHY_INTERFACE_MODE_RGMII_ID: ++ case PHY_INTERFACE_MODE_RGMII_RXID: ++ case PHY_INTERFACE_MODE_RGMII_TXID: ++ reg_val |= SUNXI_DWMAC200_SYSCON_EPIT; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC200_SYSCON_ETCS, ++ chip->rgmii_clk_ext ? SUNXI_DWMAC_ETCS_EXT_GMII : SUNXI_DWMAC_ETCS_INT_GMII); ++ if (chip->rgmii_clk_ext) ++ sunxi_info(chip->dev, "RGMII use external transmit clock\n"); ++ else ++ sunxi_info(chip->dev, "RGMII use internal transmit clock\n"); ++ break; ++ case PHY_INTERFACE_MODE_RMII: ++ reg_val |= SUNXI_DWMAC200_SYSCON_RMII_EN; ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_ETCS; ++ break; ++ default: ++ sunxi_err(chip->dev, "Unsupported interface mode: %s", phy_modes(chip->interface)); ++ return -EINVAL; ++ } ++ ++ writel(reg_val, chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ return 0; ++} ++ ++static int sunxi_dwmac200_set_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir, u32 delay) ++{ ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ int ret = -EINVAL; ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ if (delay <= chip->variant->tx_delay_max) { ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_ETXDC; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC200_SYSCON_ETXDC, delay); ++ ret = 0; ++ } ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ if (delay <= chip->variant->rx_delay_max) { ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_ERXDC; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC200_SYSCON_ERXDC, delay); ++ ret = 0; ++ } ++ break; ++ } ++ ++ if (!ret) ++ writel(reg_val, chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ ++ return ret; ++} ++ ++static u32 sunxi_dwmac200_get_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir) ++{ ++ u32 delay = 0; ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ delay = FIELD_GET(SUNXI_DWMAC200_SYSCON_ETXDC, reg_val); ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ delay = FIELD_GET(SUNXI_DWMAC200_SYSCON_ERXDC, reg_val); ++ break; ++ default: ++ sunxi_err(chip->dev, "Unknow delaychain dir %d\n", dir); ++ } ++ ++ return delay; ++} ++ ++static int sunxi_dwmac210_set_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir, u32 delay) ++{ ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC210_CFG_REG); ++ int ret = -EINVAL; ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ if (delay <= chip->variant->tx_delay_max) { ++ reg_val &= ~(SUNXI_DWMAC210_CFG_ETXDC_H | SUNXI_DWMAC210_CFG_ETXDC_L); ++ reg_val |= FIELD_PREP(SUNXI_DWMAC210_CFG_ETXDC_H, delay >> 3); ++ reg_val |= FIELD_PREP(SUNXI_DWMAC210_CFG_ETXDC_L, delay); ++ ret = 0; ++ } ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ if (delay <= chip->variant->rx_delay_max) { ++ reg_val &= ~SUNXI_DWMAC210_CFG_ERXDC; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC210_CFG_ERXDC, delay); ++ ret = 0; ++ } ++ break; ++ } ++ ++ if (!ret) ++ writel(reg_val, chip->syscfg_base + SUNXI_DWMAC210_CFG_REG); ++ ++ return ret; ++} ++ ++static u32 sunxi_dwmac210_get_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir) ++{ ++ u32 delay = 0; ++ u32 tx_l, tx_h; ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC210_CFG_REG); ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ tx_h = FIELD_GET(SUNXI_DWMAC210_CFG_ETXDC_H, reg_val); ++ tx_l = FIELD_GET(SUNXI_DWMAC210_CFG_ETXDC_L, reg_val); ++ delay = (tx_h << 3 | tx_l); ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ delay = FIELD_GET(SUNXI_DWMAC210_CFG_ERXDC, reg_val); ++ break; ++ } ++ ++ return delay; ++} ++ ++static int sunxi_dwmac110_get_version(struct sunxi_dwmac *chip, u16 *ip_tag, u16 *ip_vrm) ++{ ++ u32 reg_val; ++ ++ if (!ip_tag || !ip_vrm) ++ return -EINVAL; ++ ++ reg_val = readl(chip->syscfg_base + SUNXI_DWMAC110_VERSION_REG); ++ *ip_tag = FIELD_GET(SUNXI_DWMAC110_VERSION_IP_TAG, reg_val); ++ *ip_vrm = FIELD_GET(SUNXI_DWMAC110_VERSION_IP_VRM, reg_val); ++ return 0; ++} ++ ++static int sunxi_dwmac_power_on(struct sunxi_dwmac *chip) ++{ ++ int ret; ++ ++ /* set dwmac pin bank voltage to 3.3v */ ++ if (!IS_ERR(chip->dwmac3v3_supply)) { ++ ret = regulator_set_voltage(chip->dwmac3v3_supply, 3300000, 3300000); ++ if (ret) { ++ sunxi_err(chip->dev, "Set dwmac3v3-supply voltage 3300000 failed %d\n", ret); ++ goto err_dwmac3v3; ++ } ++ ++ ret = regulator_enable(chip->dwmac3v3_supply); ++ if (ret) { ++ sunxi_err(chip->dev, "Enable dwmac3v3-supply failed %d\n", ret); ++ goto err_dwmac3v3; ++ } ++ } ++ ++ /* set phy voltage to 3.3v */ ++ if (!IS_ERR(chip->phy3v3_supply)) { ++ ret = regulator_set_voltage(chip->phy3v3_supply, 3300000, 3300000); ++ if (ret) { ++ sunxi_err(chip->dev, "Set phy3v3-supply voltage 3300000 failed %d\n", ret); ++ goto err_phy3v3; ++ } ++ ++ ret = regulator_enable(chip->phy3v3_supply); ++ if (ret) { ++ sunxi_err(chip->dev, "Enable phy3v3-supply failed\n"); ++ goto err_phy3v3; ++ } ++ } ++ ++ return 0; ++ ++err_phy3v3: ++ regulator_disable(chip->dwmac3v3_supply); ++err_dwmac3v3: ++ return ret; ++} ++ ++static void sunxi_dwmac_power_off(struct sunxi_dwmac *chip) ++{ ++ if (!IS_ERR(chip->phy3v3_supply)) ++ regulator_disable(chip->phy3v3_supply); ++ if (!IS_ERR(chip->dwmac3v3_supply)) ++ regulator_disable(chip->dwmac3v3_supply); ++} ++ ++static int sunxi_dwmac_clk_init(struct sunxi_dwmac *chip) ++{ ++ int ret; ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) ++ reset_control_deassert(chip->hsi_rst); ++ reset_control_deassert(chip->ahb_rst); ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ ret = clk_prepare_enable(chip->hsi_ahb); ++ if (ret) { ++ sunxi_err(chip->dev, "enable hsi_ahb failed\n"); ++ goto err_ahb; ++ } ++ ret = clk_prepare_enable(chip->hsi_axi); ++ if (ret) { ++ sunxi_err(chip->dev, "enable hsi_axi failed\n"); ++ goto err_axi; ++ } ++ } ++ ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) { ++ ret = clk_prepare_enable(chip->nsi_clk); ++ if (ret) { ++ sunxi_err(chip->dev, "enable nsi clk failed\n"); ++ goto err_nsi; ++ } ++ } ++ ++ if (chip->soc_phy_clk_en) { ++ ret = clk_prepare_enable(chip->phy_clk); ++ if (ret) { ++ sunxi_err(chip->dev, "Enable phy clk failed\n"); ++ goto err_phy; ++ } ++ } ++ ++ return 0; ++ ++err_phy: ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) ++ clk_disable_unprepare(chip->nsi_clk); ++err_nsi: ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ clk_disable_unprepare(chip->hsi_axi); ++err_axi: ++ clk_disable_unprepare(chip->hsi_ahb); ++ } ++err_ahb: ++ reset_control_assert(chip->ahb_rst); ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) ++ reset_control_assert(chip->hsi_rst); ++ return ret; ++} ++ ++static void sunxi_dwmac_clk_exit(struct sunxi_dwmac *chip) ++{ ++ if (chip->soc_phy_clk_en) ++ clk_disable_unprepare(chip->phy_clk); ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) ++ clk_disable_unprepare(chip->nsi_clk); ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ clk_disable_unprepare(chip->hsi_axi); ++ clk_disable_unprepare(chip->hsi_ahb); ++ } ++ reset_control_assert(chip->ahb_rst); ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) ++ reset_control_assert(chip->hsi_rst); ++} ++ ++static int sunxi_dwmac_hw_init(struct sunxi_dwmac *chip) ++{ ++ int ret; ++ ++ ret = chip->variant->set_syscon(chip); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Set syscon failed\n"); ++ goto err; ++ } ++ ++ ret = chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, chip->tx_delay); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Invalid TX clock delay: %d\n", chip->tx_delay); ++ goto err; ++ } ++ ++ ret = chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, chip->rx_delay); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Invalid RX clock delay: %d\n", chip->rx_delay); ++ goto err; ++ } ++ ++err: ++ return ret; ++} ++ ++static void sunxi_dwmac_hw_exit(struct sunxi_dwmac *chip) ++{ ++ writel(0, chip->syscfg_base); ++} ++ ++static int sunxi_dwmac_ecc_init(struct sunxi_dwmac *chip) ++{ ++ struct net_device *ndev = dev_get_drvdata(chip->dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct plat_stmmacenet_data *plat_dat = priv->plat; ++ ++ plat_dat->safety_feat_cfg = devm_kzalloc(chip->dev, sizeof(*plat_dat->safety_feat_cfg), GFP_KERNEL); ++ if (!plat_dat->safety_feat_cfg) ++ return -ENOMEM; ++ ++ plat_dat->safety_feat_cfg->tsoee = 0; /* TSO memory ECC Disabled */ ++ plat_dat->safety_feat_cfg->mrxpee = 0; /* MTL Rx Parser ECC Disabled */ ++ plat_dat->safety_feat_cfg->mestee = 0; /* MTL EST ECC Disabled */ ++ plat_dat->safety_feat_cfg->mrxee = 1; /* MTL Rx FIFO ECC Enable */ ++ plat_dat->safety_feat_cfg->mtxee = 1; /* MTL Tx FIFO ECC Enable */ ++ plat_dat->safety_feat_cfg->epsi = 0; /* Not Enable Parity on Slave Interface port */ ++ plat_dat->safety_feat_cfg->edpp = 1; /* Enable Data path Parity Protection */ ++ plat_dat->safety_feat_cfg->prtyen = 1; /* Enable FSM parity feature */ ++ plat_dat->safety_feat_cfg->tmouten = 1; /* Enable FSM timeout feature */ ++ ++ return 0; ++} ++ ++static int sunxi_dwmac_init(struct platform_device *pdev, void *priv) ++{ ++ struct sunxi_dwmac *chip = priv; ++ int ret; ++ ++ ret = sunxi_dwmac_power_on(chip); ++ if (ret) { ++ sunxi_err(&pdev->dev, "Power on dwmac failed\n"); ++ return ret; ++ } ++ ++ ret = sunxi_dwmac_clk_init(chip); ++ if (ret) { ++ sunxi_err(&pdev->dev, "Clk init dwmac failed\n"); ++ goto err_clk; ++ } ++ ++ ret = sunxi_dwmac_hw_init(chip); ++ if (ret) ++ sunxi_warn(&pdev->dev, "Hw init dwmac failed\n"); ++ ++ return 0; ++ ++err_clk: ++ sunxi_dwmac_power_off(chip); ++ return ret; ++} ++ ++static void sunxi_dwmac_exit(struct platform_device *pdev, void *priv) ++{ ++ struct sunxi_dwmac *chip = priv; ++ ++ sunxi_dwmac_hw_exit(chip); ++ sunxi_dwmac_clk_exit(chip); ++ sunxi_dwmac_power_off(chip); ++} ++ ++static void sunxi_dwmac_parse_delay_maps(struct sunxi_dwmac *chip) ++{ ++ struct platform_device *pdev = to_platform_device(chip->dev); ++ struct device_node *np = pdev->dev.of_node; ++ int ret, maps_cnt; ++ u32 *maps; ++ ++ maps_cnt = of_property_count_elems_of_size(np, "delay-maps", sizeof(u32)); ++ if (maps_cnt <= 0) { ++ sunxi_info(&pdev->dev, "Not found delay-maps in dts\n"); ++ return; ++ } ++ ++ maps = devm_kcalloc(&pdev->dev, maps_cnt, sizeof(u32), GFP_KERNEL); ++ if (!maps) ++ return; ++ ++ ret = of_property_read_u32_array(np, "delay-maps", maps, maps_cnt); ++ if (ret) { ++ sunxi_err(&pdev->dev, "Failed to parse delay-maps\n"); ++ goto err_parse_maps; ++ } ++/* todo ++ int i; ++ const u8 array_size = 3; ++ u16 soc_ver; ++ ++ soc_ver = (u16)sunxi_get_soc_ver(); ++ for (i = 0; i < (maps_cnt / array_size); i++) { ++ if (soc_ver == maps[i * array_size]) { ++ chip->rx_delay = maps[i * array_size + 1]; ++ chip->tx_delay = maps[i * array_size + 2]; ++ sunxi_info(&pdev->dev, "Overwrite delay-maps parameters, rx-delay:%d, tx-delay:%d\n", ++ chip->rx_delay, chip->tx_delay); ++ } ++ } ++*/ ++err_parse_maps: ++ devm_kfree(&pdev->dev, maps); ++} ++ ++static void sunxi_dwmac_request_mtl_irq(struct platform_device *pdev, struct sunxi_dwmac *chip, ++ struct plat_stmmacenet_data *plat_dat) ++{ ++ u32 queues; ++ char int_name[MAC_IRQ_NAME]; ++ ++ for (queues = 0; queues < plat_dat->tx_queues_to_use; queues++) { ++ sprintf(int_name, "%s%d_%s", "tx", queues, "irq"); ++ chip->res->tx_irq[queues] = platform_get_irq_byname_optional(pdev, int_name); ++ if (chip->res->tx_irq[queues] < 0) ++ chip->res->tx_irq[queues] = 0; ++ } ++ ++ for (queues = 0; queues < plat_dat->rx_queues_to_use; queues++) { ++ sprintf(int_name, "%s%d_%s", "rx", queues, "irq"); ++ chip->res->rx_irq[queues] = platform_get_irq_byname_optional(pdev, int_name); ++ if (chip->res->rx_irq[queues] < 0) ++ chip->res->rx_irq[queues] = 0; ++ } ++} ++ ++static int sunxi_dwmac_resource_get(struct platform_device *pdev, struct sunxi_dwmac *chip, ++ struct plat_stmmacenet_data *plat_dat) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ int ret; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ if (!res) { ++ sunxi_err(dev, "Get phy memory failed\n"); ++ return -ENODEV; ++ } ++ ++ chip->syscfg_base = devm_ioremap_resource(dev, res); ++ if (!chip->syscfg_base) { ++ sunxi_err(dev, "Phy memory mapping failed\n"); ++ return -ENOMEM; ++ } ++ ++ chip->rgmii_clk_ext = of_property_read_bool(np, "aw,rgmii-clk-ext"); ++ chip->soc_phy_clk_en = of_property_read_bool(np, "aw,soc-phy-clk-en") || ++ of_property_read_bool(np, "aw,soc-phy25m"); ++ if (chip->soc_phy_clk_en) { ++ chip->phy_clk = devm_clk_get(dev, "phy"); ++ if (IS_ERR(chip->phy_clk)) { ++ chip->phy_clk = devm_clk_get(dev, "phy25m"); ++ if (IS_ERR(chip->phy_clk)) { ++ sunxi_err(dev, "Get phy25m clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ sunxi_info(dev, "Phy use soc fanout\n"); ++ } else ++ sunxi_info(dev, "Phy use ext osc\n"); ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ chip->hsi_ahb = devm_clk_get(dev, "hsi_ahb"); ++ if (IS_ERR(chip->hsi_ahb)) { ++ sunxi_err(dev, "Get hsi_ahb clk failed\n"); ++ return -EINVAL; ++ } ++ chip->hsi_axi = devm_clk_get(dev, "hsi_axi"); ++ if (IS_ERR(chip->hsi_axi)) { ++ sunxi_err(dev, "Get hsi_axi clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) { ++ chip->nsi_clk = devm_clk_get(dev, "nsi"); ++ if (IS_ERR(chip->nsi_clk)) { ++ sunxi_err(dev, "Get nsi clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (chip->variant->flags & SUNXI_DWMAC_MEM_ECC) { ++ sunxi_info(dev, "Support mem ecc\n"); ++ chip->res->sfty_ce_irq = platform_get_irq_byname_optional(pdev, "mac_eccirq"); ++ if (chip->res->sfty_ce_irq < 0) { ++ sunxi_err(&pdev->dev, "Get ecc irq failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ chip->hsi_rst = devm_reset_control_get_shared(chip->dev, "hsi"); ++ if (IS_ERR(chip->hsi_rst)) { ++ sunxi_err(dev, "Get hsi reset failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ chip->ahb_rst = devm_reset_control_get_optional_shared(chip->dev, "ahb"); ++ if (IS_ERR(chip->ahb_rst)) { ++ sunxi_err(dev, "Get mac reset failed\n"); ++ return -EINVAL; ++ } ++ ++ chip->dwmac3v3_supply = devm_regulator_get_optional(&pdev->dev, "dwmac3v3"); ++ if (IS_ERR(chip->dwmac3v3_supply)) ++ sunxi_warn(dev, "Not found dwmac3v3-supply\n"); ++ ++ chip->phy3v3_supply = devm_regulator_get_optional(&pdev->dev, "phy3v3"); ++ if (IS_ERR(chip->phy3v3_supply)) ++ sunxi_warn(dev, "Not found phy3v3-supply\n"); ++ ++ ret = of_property_read_u32(np, "tx-delay", &chip->tx_delay); ++ if (ret) { ++ sunxi_warn(dev, "Get gmac tx-delay failed, use default 0\n"); ++ chip->tx_delay = 0; ++ } ++ ++ ret = of_property_read_u32(np, "rx-delay", &chip->rx_delay); ++ if (ret) { ++ sunxi_warn(dev, "Get gmac rx-delay failed, use default 0\n"); ++ chip->rx_delay = 0; ++ } ++ ++ sunxi_dwmac_parse_delay_maps(chip); ++ ++ if (chip->variant->flags & SUNXI_DWMAC_MULTI_MSI) ++ sunxi_dwmac_request_mtl_irq(pdev, chip, plat_dat); ++ ++ return 0; ++} ++ ++#ifndef MODULE ++static void sunxi_dwmac_set_mac(u8 *dst, u8 *src) ++{ ++ int i; ++ char *p = src; ++ ++ for (i = 0; i < ETH_ALEN; i++, p++) ++ dst[i] = simple_strtoul(p, &p, 16); ++} ++#endif ++ ++static int sunxi_dwmac_probe(struct platform_device *pdev) ++{ ++ struct plat_stmmacenet_data *plat_dat; ++ struct stmmac_resources stmmac_res; ++ struct sunxi_dwmac *chip; ++ struct device *dev = &pdev->dev; ++ int ret; ++ ++ ret = stmmac_get_platform_resources(pdev, &stmmac_res); ++ if (ret) ++ return ret; ++ ++ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); ++ if (!chip) { ++ sunxi_err(&pdev->dev, "Alloc sunxi dwmac err\n"); ++ return -ENOMEM; ++ } ++ ++ chip->variant = of_device_get_match_data(&pdev->dev); ++ if (!chip->variant) { ++ sunxi_err(&pdev->dev, "Missing dwmac-sunxi variant\n"); ++ return -EINVAL; ++ } ++ ++ chip->dev = dev; ++ chip->res = &stmmac_res; ++ ++ plat_dat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac); ++ ++ if (IS_ERR(plat_dat)) ++ return PTR_ERR(plat_dat); ++ ++ ret = sunxi_dwmac_resource_get(pdev, chip, plat_dat); ++ if (ret < 0) ++ return -EINVAL; ++ ++#ifdef MODULE ++//todo get_custom_mac_address(1, "eth", stmmac_res.mac); ++#else ++//todo sunxi_dwmac_set_mac(stmmac_res.mac, mac_str); ++#endif ++ ++ ++ plat_dat->bsp_priv = chip; ++ plat_dat->init = sunxi_dwmac_init; ++ plat_dat->exit = sunxi_dwmac_exit; ++ /* must use 0~4G space */ ++ plat_dat->host_dma_width = 32; ++ ++ /* Disable Split Header (SPH) feature for sunxi platfrom as default ++ * The same issue also detect on intel platfrom, see 41eebbf90dfbcc8ad16d4755fe2cdb8328f5d4a7. ++ */ ++ if (chip->variant->flags & SUNXI_DWMAC_SPH_DISABLE) ++ plat_dat->flags |= STMMAC_FLAG_SPH_DISABLE; ++ if (chip->variant->flags & SUNXI_DWMAC_MULTI_MSI) ++ plat_dat->flags |= STMMAC_FLAG_MULTI_MSI_EN; ++ chip->interface = plat_dat->mac_interface; ++ ++ plat_dat->clk_csr = 4; /* MDC = AHB(200M)/102 = 2M */ ++ ++ ret = sunxi_dwmac_init(pdev, plat_dat->bsp_priv); ++ if (ret) ++ goto err_init; ++ ++ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); ++ if (ret) ++ goto err_dvr_probe; ++ ++ if (chip->variant->flags & SUNXI_DWMAC_MEM_ECC) { ++ ret = sunxi_dwmac_ecc_init(chip); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Init ecc failed\n"); ++ goto err_cfg; ++ } ++ } ++ ++ sunxi_dwmac_sysfs_init(&pdev->dev); ++ ++ sunxi_info(&pdev->dev, "probe success (Version %s)\n", DWMAC_MODULE_VERSION); ++ ++ return 0; ++ ++err_cfg: ++ stmmac_dvr_remove(&pdev->dev); ++err_dvr_probe: ++ sunxi_dwmac_exit(pdev, chip); ++err_init: ++ //stmmac_remove_config_dt(pdev, plat_dat); ++ return ret; ++} ++ ++static void sunxi_dwmac_remove(struct platform_device *pdev) ++{ ++ sunxi_dwmac_sysfs_exit(&pdev->dev); ++ stmmac_pltfr_remove(pdev); ++} ++ ++static void sunxi_dwmac_shutdown(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ ++ sunxi_dwmac_exit(pdev, chip); ++} ++ ++static int __maybe_unused sunxi_dwmac_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ ++ /* suspend error workaround */ ++ if (ndev && ndev->phydev) { ++ chip->uevent_suppress = dev_get_uevent_suppress(&ndev->phydev->mdio.dev); ++ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, true); ++ } ++ ++ ret = stmmac_suspend(dev); ++ sunxi_dwmac_exit(pdev, chip); ++ stmmac_bus_clks_config(priv, false); ++ ++ return ret; ++} ++ ++static int __maybe_unused sunxi_dwmac_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ ++ stmmac_bus_clks_config(priv, true); ++ sunxi_dwmac_init(pdev, chip); ++ ret = stmmac_resume(dev); ++ ++ if (ndev && ndev->phydev) { ++ /* State machine change phy state too early before mdio bus resume. ++ * WARN_ON would print in mdio_bus_phy_resume if state not equal to PHY_HALTED/PHY_READY/PHY_UP. ++ * Workaround is change the state back to PHY_UP and modify the state machine work so the judgment can be passed. ++ */ ++ rtnl_lock(); ++ mutex_lock(&ndev->phydev->lock); ++ if (ndev->phydev->state == PHY_UP || ndev->phydev->state == PHY_NOLINK) { ++ if (ndev->phydev->state == PHY_NOLINK) ++ ndev->phydev->state = PHY_UP; ++ phy_queue_state_machine(ndev->phydev, HZ); ++ } ++ mutex_unlock(&ndev->phydev->lock); ++ rtnl_unlock(); ++ ++ /* suspend error workaround */ ++ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, chip->uevent_suppress); ++ } ++ ++ return ret; ++} ++ ++static SIMPLE_DEV_PM_OPS(sunxi_dwmac_pm_ops, sunxi_dwmac_suspend, sunxi_dwmac_resume); ++ ++static const struct sunxi_dwmac_variant dwmac200_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE, ++ .rx_delay_max = 31, ++ .tx_delay_max = 7, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac200_set_delaychain, ++ .get_delaychain = sunxi_dwmac200_get_delaychain, ++}; ++ ++static const struct sunxi_dwmac_variant dwmac210_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE | SUNXI_DWMAC_MULTI_MSI, ++ .rx_delay_max = 31, ++ .tx_delay_max = 31, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac210_set_delaychain, ++ .get_delaychain = sunxi_dwmac210_get_delaychain, ++}; ++ ++static const struct sunxi_dwmac_variant dwmac220_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE | SUNXI_DWMAC_NSI_CLK_GATE | SUNXI_DWMAC_MULTI_MSI | SUNXI_DWMAC_MEM_ECC, ++ .rx_delay_max = 31, ++ .tx_delay_max = 31, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac210_set_delaychain, ++ .get_delaychain = sunxi_dwmac210_get_delaychain, ++}; ++ ++static const struct sunxi_dwmac_variant dwmac110_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE | SUNXI_DWMAC_NSI_CLK_GATE | SUNXI_DWMAC_HSI_CLK_GATE | SUNXI_DWMAC_MULTI_MSI, ++ .rx_delay_max = 31, ++ .tx_delay_max = 31, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac210_set_delaychain, ++ .get_delaychain = sunxi_dwmac210_get_delaychain, ++ .get_version = sunxi_dwmac110_get_version, ++}; ++ ++static const struct of_device_id sunxi_dwmac_match[] = { ++ { .compatible = "allwinner,sunxi-gmac-200", .data = &dwmac200_variant }, ++ { .compatible = "allwinner,sunxi-gmac-210", .data = &dwmac210_variant }, ++ { .compatible = "allwinner,sunxi-gmac-220", .data = &dwmac220_variant }, ++ { .compatible = "allwinner,sunxi-gmac-110", .data = &dwmac110_variant }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, sunxi_dwmac_match); ++ ++static struct platform_driver sunxi_dwmac_driver = { ++ .probe = sunxi_dwmac_probe, ++ .remove = sunxi_dwmac_remove, ++ .shutdown = sunxi_dwmac_shutdown, ++ .driver = { ++ .name = "dwmac-sunxi", ++ .pm = &sunxi_dwmac_pm_ops, ++ .of_match_table = sunxi_dwmac_match, ++ }, ++}; ++module_platform_driver(sunxi_dwmac_driver); ++ ++#ifndef MODULE ++static int __init sunxi_dwmac_set_mac_addr(char *str) ++{ ++ char *p = str; ++ ++ if (str && strlen(str)) ++ memcpy(mac_str, p, MAC_ADDR_LEN); ++ ++ return 0; ++} ++__setup("mac_addr=", sunxi_dwmac_set_mac_addr); ++#endif /* MODULE */ ++ ++MODULE_DESCRIPTION("Allwinner DWMAC driver"); ++MODULE_AUTHOR("wujiayi "); ++MODULE_AUTHOR("xuminghui "); ++MODULE_AUTHOR("Piotr Oniszczuk "); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_VERSION(DWMAC_MODULE_VERSION); +diff --git a/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.h b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.h +new file mode 100644 +index 000000000000..ff8faa3afa18 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/dwmac-sunxi.h +@@ -0,0 +1,176 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner DWMAC driver header. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++* This file is licensed under the terms of the GNU General Public ++* License version 2. This program is licensed "as is" without any ++* warranty of any kind, whether express or implied. ++*/ ++ ++#ifndef _DWMAC_SUNXI_H_ ++#define _DWMAC_SUNXI_H_ ++ ++#include ++#include ++ ++/* DWCMAC5 ECC Debug Register ++ * These macro do not defined in mainline code dwmac5.h ++ */ ++#define MTL_DBG_CTL 0x00000c08 ++#define EIEC BIT(18) ++#define EIAEE BIT(17) ++#define EIEE BIT(16) ++#define FIFOSEL GENMASK(13, 12) ++#define FIFOWREN BIT(11) ++#define FIFORDEN BIT(10) ++#define RSTSEL BIT(9) ++#define RSTALL BIT(8) ++#define DBGMOD BIT(1) ++#define FDBGEN BIT(0) ++#define MTL_DBG_STS 0x00000c0c ++#define FIFOBUSY BIT(0) ++#define MTL_FIFO_DEBUG_DATA 0x00000c10 ++#define MTL_ECC_ERR_STS_RCTL 0x00000cd0 ++#define CUES BIT(5) ++#define CCES BIT(4) ++#define EMS GENMASK(3, 1) ++#define EESRE BIT(0) ++#define MTL_ECC_ERR_ADDR_STATUS 0x00000cd4 ++#define EUEAS GENMASK(31, 16) ++#define ECEAS GENMASK(15, 0) ++#define MTL_ECC_ERR_CNTR_STATUS 0x00000cd8 ++#define EUECS GENMASK(19, 16) ++#define ECECS GENMASK(7, 0) ++#define MTL_DPP_ECC_EIC 0x00000ce4 ++#define EIM BIT(16) ++#define BLEI GENMASK(7, 0) ++ ++/* GMAC-200 Register */ ++#define SUNXI_DWMAC200_SYSCON_REG (0x00) ++ #define SUNXI_DWMAC200_SYSCON_BPS_EFUSE GENMASK(31, 28) ++ #define SUNXI_DWMAC200_SYSCON_XMII_SEL BIT(27) ++ #define SUNXI_DWMAC200_SYSCON_EPHY_MODE GENMASK(26, 25) ++ #define SUNXI_DWMAC200_SYSCON_PHY_ADDR GENMASK(24, 20) ++ #define SUNXI_DWMAC200_SYSCON_BIST_CLK_EN BIT(19) ++ #define SUNXI_DWMAC200_SYSCON_CLK_SEL BIT(18) ++ #define SUNXI_DWMAC200_SYSCON_LED_POL BIT(17) ++ #define SUNXI_DWMAC200_SYSCON_SHUTDOWN BIT(16) ++ #define SUNXI_DWMAC200_SYSCON_PHY_SEL BIT(15) ++ #define SUNXI_DWMAC200_SYSCON_ENDIAN_MODE BIT(14) ++ #define SUNXI_DWMAC200_SYSCON_RMII_EN BIT(13) ++ #define SUNXI_DWMAC200_SYSCON_ETXDC GENMASK(12, 10) ++ #define SUNXI_DWMAC200_SYSCON_ERXDC GENMASK(9, 5) ++ #define SUNXI_DWMAC200_SYSCON_ERXIE BIT(4) ++ #define SUNXI_DWMAC200_SYSCON_ETXIE BIT(3) ++ #define SUNXI_DWMAC200_SYSCON_EPIT BIT(2) ++ #define SUNXI_DWMAC200_SYSCON_ETCS GENMASK(1, 0) ++ ++/* GMAC-210 Register */ ++#define SUNXI_DWMAC210_CFG_REG (0x00) ++ #define SUNXI_DWMAC210_CFG_ETXDC_H GENMASK(17, 16) ++ #define SUNXI_DWMAC210_CFG_PHY_SEL BIT(15) ++ #define SUNXI_DWMAC210_CFG_ENDIAN_MODE BIT(14) ++ #define SUNXI_DWMAC210_CFG_RMII_EN BIT(13) ++ #define SUNXI_DWMAC210_CFG_ETXDC_L GENMASK(12, 10) ++ #define SUNXI_DWMAC210_CFG_ERXDC GENMASK(9, 5) ++ #define SUNXI_DWMAC210_CFG_ERXIE BIT(4) ++ #define SUNXI_DWMAC210_CFG_ETXIE BIT(3) ++ #define SUNXI_DWMAC210_CFG_EPIT BIT(2) ++ #define SUNXI_DWMAC210_CFG_ETCS GENMASK(1, 0) ++#define SUNXI_DWMAC210_PTP_TIMESTAMP_L_REG (0x40) ++#define SUNXI_DWMAC210_PTP_TIMESTAMP_H_REG (0x48) ++#define SUNXI_DWMAC210_STAT_INT_REG (0x4C) ++ #define SUNXI_DWMAC210_STAT_PWR_DOWN_ACK BIT(4) ++ #define SUNXI_DWMAC210_STAT_SBD_TX_CLK_GATE BIT(3) ++ #define SUNXI_DWMAC210_STAT_LPI_INT BIT(1) ++ #define SUNXI_DWMAC210_STAT_PMT_INT BIT(0) ++#define SUNXI_DWMAC210_CLK_GATE_CFG_REG (0x80) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_RX BIT(7) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_PTP_REF BIT(6) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_CSR BIT(5) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_TX BIT(4) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_APP BIT(3) ++ ++/* GMAC-110 Register */ ++#define SUNXI_DWMAC110_CFG_REG SUNXI_DWMAC210_CFG_REG ++ /* SUNXI_DWMAC110_CFG_REG is same with SUNXI_DWMAC210_CFG_REG */ ++#define SUNXI_DWMAC110_CLK_GATE_CFG_REG (0x04) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_RX BIT(3) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_TX BIT(2) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_APP BIT(1) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_CSR BIT(0) ++#define SUNXI_DWMAC110_VERSION_REG (0xfc) ++ #define SUNXI_DWMAC110_VERSION_IP_TAG GENMASK(31, 16) ++ #define SUNXI_DWMAC110_VERSION_IP_VRM GENMASK(15, 0) ++ ++#define SUNXI_DWMAC_ETCS_MII 0x0 ++#define SUNXI_DWMAC_ETCS_EXT_GMII 0x1 ++#define SUNXI_DWMAC_ETCS_INT_GMII 0x2 ++ ++/* MAC flags defined */ ++#define SUNXI_DWMAC_SPH_DISABLE BIT(0) ++#define SUNXI_DWMAC_NSI_CLK_GATE BIT(1) ++#define SUNXI_DWMAC_MULTI_MSI BIT(2) ++#define SUNXI_DWMAC_MEM_ECC BIT(3) ++#define SUNXI_DWMAC_HSI_CLK_GATE BIT(4) ++ ++struct sunxi_dwmac; ++ ++enum sunxi_dwmac_delaychain_dir { ++ SUNXI_DWMAC_DELAYCHAIN_TX, ++ SUNXI_DWMAC_DELAYCHAIN_RX, ++}; ++ ++enum sunxi_dwmac_ecc_fifo_type { ++ SUNXI_DWMAC_ECC_FIFO_TX, ++ SUNXI_DWMAC_ECC_FIFO_RX, ++}; ++ ++struct sunxi_dwmac_variant { ++ u32 flags; ++ u32 interface; ++ u32 rx_delay_max; ++ u32 tx_delay_max; ++ int (*set_syscon)(struct sunxi_dwmac *chip); ++ int (*set_delaychain)(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir, u32 delay); ++ u32 (*get_delaychain)(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir); ++ int (*get_version)(struct sunxi_dwmac *chip, u16 *ip_tag, u16 *ip_vrm); ++}; ++ ++struct sunxi_dwmac_mii_reg { ++ u32 addr; ++ u16 reg; ++ u16 value; ++}; ++ ++struct sunxi_dwmac { ++ const struct sunxi_dwmac_variant *variant; ++ struct sunxi_dwmac_mii_reg mii_reg; ++ struct clk *phy_clk; ++ struct clk *nsi_clk; ++ struct clk *hsi_ahb; ++ struct clk *hsi_axi; ++ struct reset_control *ahb_rst; ++ struct reset_control *hsi_rst; ++ struct device *dev; ++ void __iomem *syscfg_base; ++ struct regulator *dwmac3v3_supply; ++ struct regulator *phy3v3_supply; ++ ++ u32 tx_delay; /* adjust transmit clock delay */ ++ u32 rx_delay; /* adjust receive clock delay */ ++ ++ bool rgmii_clk_ext; ++ bool soc_phy_clk_en; ++ int interface; ++ unsigned int uevent_suppress; /* suspend error workaround: control kobject_uevent_env */ ++ ++ struct stmmac_resources *res; ++}; ++ ++#include "dwmac-sunxi-sysfs.h" ++ ++#endif /* _DWMAC_SUNXI_H_ */ +diff --git a/drivers/net/ethernet/allwinner/gmac-200/sunxi-log.h b/drivers/net/ethernet/allwinner/gmac-200/sunxi-log.h +new file mode 100644 +index 000000000000..0e88c44218ae +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/sunxi-log.h +@@ -0,0 +1,174 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Allwinner's log functions ++ * ++ * Copyright (c) 2023, lvda ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ * ++ */ ++ ++#ifndef __SUNXI_LOG_H__ ++#define __SUNXI_LOG_H__ ++ ++#define SUNXI_LOG_VERSION "V0.7" ++/* Allow user to define their own MODNAME with `SUNXI_MODNAME` */ ++#ifndef SUNXI_MODNAME ++#define SUNXI_MODNAME KBUILD_MODNAME ++#endif ++ ++#ifdef pr_fmt ++#undef pr_fmt ++#endif ++ ++#ifdef dev_fmt ++#undef dev_fmt ++#endif ++ ++#define pr_fmt(fmt) "sunxi:" SUNXI_MODNAME fmt ++#define dev_fmt pr_fmt ++ ++#include ++#include ++ ++/* ++ * Copy from dev_name(). Someone like to use "dev_name" as local variable, ++ * which will case compile error. ++ */ ++static inline const char *sunxi_log_dev_name(const struct device *dev) ++{ ++ /* Use the init name until the kobject becomes available */ ++ if (dev->init_name) ++ return dev->init_name; ++ ++ return kobject_name(&dev->kobj); ++} ++ ++/* ++ * Parameter Description: ++ * 1. dev: Optional parameter. If the context cannot obtain dev, fill in NULL ++ * 2. fmt: Format specifier ++ * 3. err_code: Error code. Only used in sunxi_err_std() ++ * 4. ...: Variable arguments ++ */ ++ ++#if IS_ENABLED(CONFIG_AW_LOG_VERBOSE) ++ ++/* void sunxi_err(struct device *dev, char *fmt, ...); */ ++#define sunxi_err(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_err("-%s:[ERR]:%s +%d %s(): "fmt, sunxi_log_dev_name(dev), __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ else \ ++ pr_err(":[ERR]:%s +%d %s(): "fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_err_std(struct device *dev, int err_code, char *fmt, ...); */ ++#define sunxi_err_std(dev, err_code, fmt, ...) \ ++ do { if (dev) \ ++ pr_err("-%s:[ERR%d]:%s +%d %s(): "fmt, sunxi_log_dev_name(dev), err_code, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ else \ ++ pr_err(":[ERR%d]:%s +%d %s(): "fmt, err_code, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_warn(struct device *dev, char *fmt, ...); */ ++#define sunxi_warn(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_warn("-%s:[WARN]:%s +%d %s(): "fmt, sunxi_log_dev_name(dev), __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ else \ ++ pr_warn(":[WARN]:%s +%d %s(): "fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_info(struct device *dev, char *fmt, ...); */ ++#define sunxi_info(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_info("-%s:[INFO]:%s +%d %s(): "fmt, sunxi_log_dev_name(dev), __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ else \ ++ pr_info(":[INFO]:%s +%d %s(): "fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_debug(struct device *dev, char *fmt, ...); */ ++#define sunxi_debug(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_debug("-%s:[DEBUG]:%s +%d %s(): "fmt, sunxi_log_dev_name(dev), __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ else \ ++ pr_debug(":[DEBUG]:%s +%d %s(): "fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ } while (0) ++ ++#else /* !CONFIG_AW_LOG_VERBOSE */ ++ ++/* void sunxi_err(struct device *dev, char *fmt, ...); */ ++#define sunxi_err(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_err("-%s:[ERR]: "fmt, sunxi_log_dev_name(dev), ## __VA_ARGS__); \ ++ else \ ++ pr_err(":[ERR]: "fmt, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_err_std(struct device *dev, int err_code, char *fmt, ...); */ ++#define sunxi_err_std(dev, err_code, fmt, ...) \ ++ do { if (dev) \ ++ pr_err("-%s:[ERR%d]: "fmt, sunxi_log_dev_name(dev), err_code, ## __VA_ARGS__); \ ++ else \ ++ pr_err(":[ERR%d]: "fmt, err_code, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_warn(struct device *dev, char *fmt, ...); */ ++#define sunxi_warn(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_warn("-%s:[WARN]: "fmt, sunxi_log_dev_name(dev), ## __VA_ARGS__); \ ++ else \ ++ pr_warn(":[WARN]: "fmt, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_info(struct device *dev, char *fmt, ...); */ ++#define sunxi_info(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_info("-%s:[INFO]: "fmt, sunxi_log_dev_name(dev), ## __VA_ARGS__); \ ++ else \ ++ pr_info(":[INFO]: "fmt, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_debug(struct device *dev, char *fmt, ...); */ ++#define sunxi_debug(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_debug("-%s:[DEBUG]: "fmt, sunxi_log_dev_name(dev), ## __VA_ARGS__); \ ++ else \ ++ pr_debug(":[DEBUG]: "fmt, ## __VA_ARGS__); \ ++ } while (0) ++ ++#endif /* CONFIG_AW_LOG_VERBOSE */ ++ ++/* void sunxi_debug_verbose(struct device *dev, char *fmt, ...); */ ++#define sunxi_debug_verbose(dev, fmt, ...) \ ++ do { if (dev) \ ++ pr_debug("-%s:[DEBUG]:%s +%d %s(): "fmt, sunxi_log_dev_name(dev), __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ else \ ++ pr_debug(":[DEBUG]:%s +%d %s(): "fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ ++ } while (0) ++ ++/* void sunxi_debug_line(struct device *dev); */ ++#define sunxi_debug_line(dev) \ ++ do { if (dev) \ ++ pr_debug("-%s:[DEBUG]:%s +%d %s()\n", sunxi_log_dev_name(dev), __FILE__, __LINE__, __func__); \ ++ else \ ++ pr_debug(":[DEBUG]:%s +%d %s()\n", __FILE__, __LINE__, __func__); \ ++ } while (0) ++ ++/* ++ * TODO: ++ * print_hex_dump_debug ++ * print_hex_dump_bytes ++ * trace_printk ++ * printk_ratelimited ++*/ ++ ++#endif +diff --git a/drivers/net/ethernet/allwinner/gmac-200/sunxi-uio.c b/drivers/net/ethernet/allwinner/gmac-200/sunxi-uio.c +new file mode 100644 +index 000000000000..570f304d3195 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac-200/sunxi-uio.c +@@ -0,0 +1,1015 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/** ++ * Copyright 2023 Allwinnertech ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stmmac/stmmac_ptp.h" ++#include "stmmac/stmmac.h" ++#include "hwif.h" ++ ++#define DRIVER_NAME "sunxi_uio" ++#define DRIVER_VERSION "0.0.1" ++ ++#define TC_DEFAULT 64 ++static int tc = TC_DEFAULT; ++ ++#define DEFAULT_BUFSIZE 1536 ++static int buf_sz = DEFAULT_BUFSIZE; ++ ++#define STMMAC_RX_COPYBREAK 256 ++ ++/** ++ * sunxi_uio ++ * local information for uio module driver ++ * ++ * @dev: device pointer ++ * @ndev: network device pointer ++ * @name: uio name ++ * @uio: uio information ++ * @map_num: number of uio memory regions ++ */ ++struct sunxi_uio { ++ struct device *dev; ++ struct net_device *ndev; ++ char name[16]; ++ struct uio_info uio; ++ int map_num; ++}; ++ ++static int sunxi_uio_open(struct uio_info *info, struct inode *inode) ++{ ++ return 0; ++} ++ ++static int sunxi_uio_release(struct uio_info *info, ++ struct inode *inode) ++{ ++ return 0; ++} ++ ++static int sunxi_uio_mmap(struct uio_info *info, ++ struct vm_area_struct *vma) ++{ ++ u32 ret, pfn; ++ ++ pfn = (info->mem[vma->vm_pgoff].addr) >> PAGE_SHIFT; ++ ++ if (vma->vm_pgoff) ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ else ++ vma->vm_page_prot = pgprot_device(vma->vm_page_prot); ++ ++ ret = remap_pfn_range(vma, vma->vm_start, pfn, ++ vma->vm_end - vma->vm_start, vma->vm_page_prot); ++ if (ret) { ++ /* Error Handle */ ++ pr_err("remap_pfn_range failed"); ++ } ++ return ret; ++} ++ ++/** ++ * sunxi_uio_free_dma_rx_desc_resources - free RX dma desc resources ++ * @priv: private structure ++ */ ++static void sunxi_uio_free_dma_rx_desc_resources(struct stmmac_priv *priv) ++{ ++ u32 queue, rx_count = priv->plat->rx_queues_to_use; ++ ++ /* Free RX queue resources */ ++ for (queue = 0; queue < rx_count; queue++) { ++ struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue]; ++ ++ /* Free DMA regions of consistent memory previously allocated */ ++ if (!priv->extend_desc) ++ dma_free_coherent(priv->device, priv->dma_rx_size * ++ sizeof(struct dma_desc), ++ rx_q->dma_rx, rx_q->dma_rx_phy); ++ else ++ dma_free_coherent(priv->device, priv->dma_rx_size * ++ sizeof(struct dma_extended_desc), ++ rx_q->dma_erx, rx_q->dma_rx_phy); ++ } ++} ++ ++/** ++ * sunxi_uio_free_dma_tx_desc_resources - free TX dma desc resources ++ * @priv: private structure ++ */ ++static void sunxi_uio_free_dma_tx_desc_resources(struct stmmac_priv *priv) ++{ ++ u32 queue, tx_count = priv->plat->tx_queues_to_use; ++ ++ /* Free TX queue resources */ ++ for (queue = 0; queue < tx_count; queue++) { ++ struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue]; ++ size_t size; ++ void *addr; ++ ++ if (priv->extend_desc) { ++ size = sizeof(struct dma_extended_desc); ++ addr = tx_q->dma_etx; ++ } else if (tx_q->tbs & STMMAC_TBS_AVAIL) { ++ size = sizeof(struct dma_edesc); ++ addr = tx_q->dma_entx; ++ } else { ++ size = sizeof(struct dma_desc); ++ addr = tx_q->dma_tx; ++ } ++ ++ size *= priv->dma_tx_size; ++ ++ dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy); ++ } ++} ++ ++/** ++ * sunxi_uio_alloc_dma_rx_desc_resources - alloc RX resources. ++ * @priv: private structure ++ * Description: according to which descriptor can be used (extend or basic) ++ * this function allocates the resources for TX and RX paths. In case of ++ * reception, for example, it pre-allocated the RX socket buffer in order to ++ * allow zero-copy mechanism. ++ */ ++static int sunxi_uio_alloc_dma_rx_desc_resources(struct stmmac_priv *priv) ++{ ++ u32 queue, rx_count = priv->plat->rx_queues_to_use; ++ int ret = -ENOMEM; ++ ++ /* RX queues buffers and DMA */ ++ for (queue = 0; queue < rx_count; queue++) { ++ struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue]; ++ ++ if (priv->extend_desc) { ++ rx_q->dma_erx = dma_alloc_coherent(priv->device, ++ priv->dma_rx_size * ++ sizeof(struct dma_extended_desc), ++ &rx_q->dma_rx_phy, ++ GFP_KERNEL); ++ if (!rx_q->dma_erx) ++ goto err_dma; ++ } else { ++ rx_q->dma_rx = dma_alloc_coherent(priv->device, ++ priv->dma_rx_size * ++ sizeof(struct dma_desc), ++ &rx_q->dma_rx_phy, ++ GFP_KERNEL); ++ if (!rx_q->dma_rx) ++ goto err_dma; ++ } ++ } ++ ++ return 0; ++ ++err_dma: ++ sunxi_uio_free_dma_rx_desc_resources(priv); ++ ++ return ret; ++} ++ ++/** ++ * sunxi_uio_alloc_dma_tx_desc_resources - alloc TX resources. ++ * @priv: private structure ++ * Description: according to which descriptor can be used (extend or basic) ++ * this function allocates the resources for TX and RX paths. In case of ++ * reception, for example, it pre-allocated the RX socket buffer in order to ++ * allow zero-copy mechanism. ++ */ ++static int sunxi_uio_alloc_dma_tx_desc_resources(struct stmmac_priv *priv) ++{ ++ u32 queue, tx_count = priv->plat->tx_queues_to_use; ++ int ret = -ENOMEM; ++ ++ /* TX queues buffers and DMA */ ++ for (queue = 0; queue < tx_count; queue++) { ++ struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue]; ++ size_t size; ++ void *addr; ++ ++ tx_q->queue_index = queue; ++ tx_q->priv_data = priv; ++ ++ if (priv->extend_desc) ++ size = sizeof(struct dma_extended_desc); ++ else if (tx_q->tbs & STMMAC_TBS_AVAIL) ++ size = sizeof(struct dma_edesc); ++ else ++ size = sizeof(struct dma_desc); ++ ++ size *= priv->dma_tx_size; ++ ++ addr = dma_alloc_coherent(priv->device, size, ++ &tx_q->dma_tx_phy, GFP_KERNEL); ++ if (!addr) ++ goto err_dma; ++ ++ if (priv->extend_desc) ++ tx_q->dma_etx = addr; ++ else if (tx_q->tbs & STMMAC_TBS_AVAIL) ++ tx_q->dma_entx = addr; ++ else ++ tx_q->dma_tx = addr; ++ } ++ ++ return 0; ++ ++err_dma: ++ sunxi_uio_free_dma_tx_desc_resources(priv); ++ return ret; ++} ++ ++/** ++ * sunxi_uio_alloc_dma_desc_resources - alloc TX/RX resources. ++ * @priv: private structure ++ * Description: according to which descriptor can be used (extend or basic) ++ * this function allocates the resources for TX and RX paths. In case of ++ * reception, for example, it pre-allocated the RX socket buffer in order to ++ * allow zero-copy mechanism. ++ */ ++static int sunxi_uio_alloc_dma_desc_resources(struct stmmac_priv *priv) ++{ ++ /* RX Allocation */ ++ int ret = sunxi_uio_alloc_dma_rx_desc_resources(priv); ++ ++ if (ret) ++ return ret; ++ ++ ret = sunxi_uio_alloc_dma_tx_desc_resources(priv); ++ ++ return ret; ++} ++ ++/** ++ * sunxi_uio_free_dma_desc_resources - free dma desc resources ++ * @priv: private structure ++ */ ++static void sunxi_uio_free_dma_desc_resources(struct stmmac_priv *priv) ++{ ++ /* Release the DMA RX socket buffers */ ++ sunxi_uio_free_dma_rx_desc_resources(priv); ++ ++ /* Release the DMA TX socket buffers */ ++ sunxi_uio_free_dma_tx_desc_resources(priv); ++} ++ ++/** ++ * sunxi_uio_init_phy - PHY initialization ++ * @dev: net device structure ++ * Description: it initializes the driver's PHY state, and attaches the PHY ++ * to the mac driver. ++ * Return value: ++ * 0 on success ++ */ ++static int sunxi_uio_init_phy(struct net_device *dev) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ struct device_node *node; ++ int ret; ++ ++ node = priv->plat->phylink_node; ++ ++ if (node) ++ ret = phylink_of_phy_connect(priv->phylink, node, 0); ++ ++ /* Some DT bindings do not set-up the PHY handle. Let's try to ++ * manually parse it ++ */ ++ if (!node || ret) { ++ int addr = priv->plat->phy_addr; ++ struct phy_device *phydev; ++ ++ phydev = mdiobus_get_phy(priv->mii, addr); ++ if (!phydev) { ++ netdev_err(priv->dev, "no phy at addr %d\n", addr); ++ return -ENODEV; ++ } ++ ++ ret = phylink_connect_phy(priv->phylink, phydev); ++ } ++ ++ if (!priv->plat->pmt) { ++ struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; ++ ++ phylink_ethtool_get_wol(priv->phylink, &wol); ++ device_set_wakeup_capable(priv->device, !!wol.supported); ++ } ++ ++ return ret; ++} ++ ++/** ++ * sunxi_uio_init_dma_engine - DMA init. ++ * @priv: driver private structure ++ * Description: ++ * It inits the DMA invoking the specific MAC/GMAC callback. ++ * Some DMA parameters can be passed from the platform; ++ * in case of these are not passed a default is kept for the MAC or GMAC. ++ */ ++static int sunxi_uio_init_dma_engine(struct stmmac_priv *priv) ++{ ++ u32 rx_channels_count = priv->plat->rx_queues_to_use; ++ u32 tx_channels_count = priv->plat->tx_queues_to_use; ++ u32 dma_csr_ch = max(rx_channels_count, tx_channels_count); ++ struct stmmac_rx_queue *rx_q; ++ struct stmmac_tx_queue *tx_q; ++ u32 chan = 0; ++ int atds = 0, ret = 0; ++ ++ if (!priv->plat->dma_cfg || !priv->plat->dma_cfg->pbl) { ++ dev_err(priv->device, "Invalid DMA configuration\n"); ++ return -EINVAL; ++ } ++ ++ if (priv->extend_desc && priv->mode == STMMAC_RING_MODE) ++ atds = 1; ++ ++ ret = stmmac_reset(priv, priv->ioaddr); ++ if (ret) { ++ dev_err(priv->device, "Failed to reset the dma\n"); ++ return ret; ++ } ++ ++ /* DMA Configuration */ ++ stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg, atds); ++ ++ if (priv->plat->axi) ++ stmmac_axi(priv, priv->ioaddr, priv->plat->axi); ++ ++ /* DMA CSR Channel configuration */ ++ for (chan = 0; chan < dma_csr_ch; chan++) ++ stmmac_init_chan(priv, priv->ioaddr, priv->plat->dma_cfg, chan); ++ ++ /* DMA RX Channel Configuration */ ++ for (chan = 0; chan < rx_channels_count; chan++) { ++ rx_q = &priv->rx_queue[chan]; ++ ++ stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, ++ rx_q->dma_rx_phy, chan); ++ ++ rx_q->rx_tail_addr = rx_q->dma_rx_phy + ++ (priv->dma_rx_size * ++ sizeof(struct dma_desc)); ++ stmmac_set_rx_tail_ptr(priv, priv->ioaddr, ++ rx_q->rx_tail_addr, chan); ++ } ++ ++ /* DMA TX Channel Configuration */ ++ for (chan = 0; chan < tx_channels_count; chan++) { ++ tx_q = &priv->tx_queue[chan]; ++ ++ stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, ++ tx_q->dma_tx_phy, chan); ++ ++ tx_q->tx_tail_addr = tx_q->dma_tx_phy; ++ stmmac_set_tx_tail_ptr(priv, priv->ioaddr, ++ tx_q->tx_tail_addr, chan); ++ } ++ ++ return ret; ++} ++ ++static void sunxi_uio_set_rings_length(struct stmmac_priv *priv) ++{ ++ u32 rx_channels_count = priv->plat->rx_queues_to_use; ++ u32 tx_channels_count = priv->plat->tx_queues_to_use; ++ u32 chan; ++ ++ /* set TX ring length */ ++ for (chan = 0; chan < tx_channels_count; chan++) ++ stmmac_set_tx_ring_len(priv, priv->ioaddr, ++ (priv->dma_tx_size - 1), chan); ++ ++ /* set RX ring length */ ++ for (chan = 0; chan < rx_channels_count; chan++) ++ stmmac_set_rx_ring_len(priv, priv->ioaddr, ++ (priv->dma_rx_size - 1), chan); ++} ++ ++/** ++ * sunxi_uio_set_tx_queue_weight - Set TX queue weight ++ * @priv: driver private structure ++ * Description: It is used for setting TX queues weight ++ */ ++static void sunxi_uio_set_tx_queue_weight(struct stmmac_priv *priv) ++{ ++ u32 tx_queues_count = priv->plat->tx_queues_to_use; ++ u32 weight, queue; ++ ++ for (queue = 0; queue < tx_queues_count; queue++) { ++ weight = priv->plat->tx_queues_cfg[queue].weight; ++ stmmac_set_mtl_tx_queue_weight(priv, priv->hw, weight, queue); ++ } ++} ++ ++/** ++ * sunxi_uio_configure_cbs - Configure CBS in TX queue ++ * @priv: driver private structure ++ * Description: It is used for configuring CBS in AVB TX queues ++ */ ++static void sunxi_uio_configure_cbs(struct stmmac_priv *priv) ++{ ++ u32 tx_queues_count = priv->plat->tx_queues_to_use; ++ u32 mode_to_use, queue; ++ ++ /* queue 0 is reserved for legacy traffic */ ++ for (queue = 1; queue < tx_queues_count; queue++) { ++ mode_to_use = priv->plat->tx_queues_cfg[queue].mode_to_use; ++ if (mode_to_use == MTL_QUEUE_DCB) ++ continue; ++ ++ stmmac_config_cbs(priv, priv->hw, ++ priv->plat->tx_queues_cfg[queue].send_slope, ++ priv->plat->tx_queues_cfg[queue].idle_slope, ++ priv->plat->tx_queues_cfg[queue].high_credit, ++ priv->plat->tx_queues_cfg[queue].low_credit, ++ queue); ++ } ++} ++ ++/** ++ * sunxi_uio_rx_queue_dma_chan_map - Map RX queue to RX dma channel ++ * @priv: driver private structure ++ * Description: It is used for mapping RX queues to RX dma channels ++ */ ++static void sunxi_uio_rx_queue_dma_chan_map(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = priv->plat->rx_queues_to_use; ++ u32 queue, chan; ++ ++ for (queue = 0; queue < rx_queues_count; queue++) { ++ chan = priv->plat->rx_queues_cfg[queue].chan; ++ stmmac_map_mtl_to_dma(priv, priv->hw, queue, chan); ++ } ++} ++ ++/** ++ * sunxi_uio_mac_config_rx_queues_prio - Configure RX Queue priority ++ * @priv: driver private structure ++ * Description: It is used for configuring the RX Queue Priority ++ */ ++static void sunxi_uio_mac_config_rx_queues_prio(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = priv->plat->rx_queues_to_use; ++ u32 queue, prio; ++ ++ for (queue = 0; queue < rx_queues_count; queue++) { ++ if (!priv->plat->rx_queues_cfg[queue].use_prio) ++ continue; ++ ++ prio = priv->plat->rx_queues_cfg[queue].prio; ++ stmmac_rx_queue_prio(priv, priv->hw, prio, queue); ++ } ++} ++ ++/** ++ * sunxi_uio_mac_config_tx_queues_prio - Configure TX Queue priority ++ * @priv: driver private structure ++ * Description: It is used for configuring the TX Queue Priority ++ */ ++static void sunxi_uio_mac_config_tx_queues_prio(struct stmmac_priv *priv) ++{ ++ u32 tx_queues_count = priv->plat->tx_queues_to_use; ++ u32 queue, prio; ++ ++ for (queue = 0; queue < tx_queues_count; queue++) { ++ if (!priv->plat->tx_queues_cfg[queue].use_prio) ++ continue; ++ ++ prio = priv->plat->tx_queues_cfg[queue].prio; ++ stmmac_tx_queue_prio(priv, priv->hw, prio, queue); ++ } ++} ++ ++/** ++ * sunxi_uio_mac_config_rx_queues_routing - Configure RX Queue Routing ++ * @priv: driver private structure ++ * Description: It is used for configuring the RX queue routing ++ */ ++static void sunxi_uio_mac_config_rx_queues_routing(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = priv->plat->rx_queues_to_use; ++ u32 queue; ++ u8 packet; ++ ++ for (queue = 0; queue < rx_queues_count; queue++) { ++ /* no specific packet type routing specified for the queue */ ++ if (priv->plat->rx_queues_cfg[queue].pkt_route == 0x0) ++ continue; ++ ++ packet = priv->plat->rx_queues_cfg[queue].pkt_route; ++ stmmac_rx_queue_routing(priv, priv->hw, packet, queue); ++ } ++} ++ ++static void sunxi_uio_mac_config_rss(struct stmmac_priv *priv) ++{ ++ if (!priv->dma_cap.rssen || !priv->plat->rss_en) { ++ priv->rss.enable = false; ++ return; ++ } ++ ++ if (priv->dev->features & NETIF_F_RXHASH) ++ priv->rss.enable = true; ++ else ++ priv->rss.enable = false; ++ ++ stmmac_rss_configure(priv, priv->hw, &priv->rss, ++ priv->plat->rx_queues_to_use); ++} ++ ++/** ++ * sunxi_uio_mac_enable_rx_queues - Enable MAC rx queues ++ * @priv: driver private structure ++ * Description: It is used for enabling the rx queues in the MAC ++ */ ++static void sunxi_uio_mac_enable_rx_queues(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = priv->plat->rx_queues_to_use; ++ int queue; ++ u8 mode; ++ ++ for (queue = 0; queue < rx_queues_count; queue++) { ++ mode = priv->plat->rx_queues_cfg[queue].mode_to_use; ++ stmmac_rx_queue_enable(priv, priv->hw, mode, queue); ++ } ++} ++ ++/** ++ * sunxi_uio_mtl_configuration - Configure MTL ++ * @priv: driver private structure ++ * Description: It is used for configuring MTL ++ */ ++static void sunxi_uio_mtl_configuration(struct stmmac_priv *priv) ++{ ++ u32 rx_queues_count = priv->plat->rx_queues_to_use; ++ u32 tx_queues_count = priv->plat->tx_queues_to_use; ++ ++ if (tx_queues_count > 1) ++ sunxi_uio_set_tx_queue_weight(priv); ++ ++ /* Configure MTL RX algorithms */ ++ if (rx_queues_count > 1) ++ stmmac_prog_mtl_rx_algorithms(priv, priv->hw, ++ priv->plat->rx_sched_algorithm); ++ ++ /* Configure MTL TX algorithms */ ++ if (tx_queues_count > 1) ++ stmmac_prog_mtl_tx_algorithms(priv, priv->hw, ++ priv->plat->tx_sched_algorithm); ++ ++ /* Configure CBS in AVB TX queues */ ++ if (tx_queues_count > 1) ++ sunxi_uio_configure_cbs(priv); ++ ++ /* Map RX MTL to DMA channels */ ++ sunxi_uio_rx_queue_dma_chan_map(priv); ++ ++ /* Enable MAC RX Queues */ ++ sunxi_uio_mac_enable_rx_queues(priv); ++ ++ /* Set RX priorities */ ++ if (rx_queues_count > 1) ++ sunxi_uio_mac_config_rx_queues_prio(priv); ++ ++ /* Set TX priorities */ ++ if (tx_queues_count > 1) ++ sunxi_uio_mac_config_tx_queues_prio(priv); ++ ++ /* Set RX routing */ ++ if (rx_queues_count > 1) ++ sunxi_uio_mac_config_rx_queues_routing(priv); ++ ++ /* Receive Side Scaling */ ++ if (rx_queues_count > 1) ++ sunxi_uio_mac_config_rss(priv); ++} ++ ++static void sunxi_uio_safety_feat_configuration(struct stmmac_priv *priv) ++{ ++ if (priv->dma_cap.asp) { ++ netdev_info(priv->dev, "Enabling Safety Features\n"); ++#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) ++ stmmac_safety_feat_config(priv, priv->ioaddr, priv->dma_cap.asp); ++#else ++ stmmac_safety_feat_config(priv, priv->ioaddr, priv->dma_cap.asp, ++ priv->plat->safety_feat_cfg); ++#endif ++ } else { ++ netdev_info(priv->dev, "No Safety Features support found\n"); ++ } ++} ++ ++/** ++ * sunxi_uio_dma_operation_mode - HW DMA operation mode ++ * @priv: driver private structure ++ * Description: it is used for configuring the DMA operation mode register in ++ * order to program the tx/rx DMA thresholds or Store-And-Forward mode. ++ */ ++static void sunxi_uio_dma_operation_mode(struct stmmac_priv *priv) ++{ ++ u32 rx_channels_count = priv->plat->rx_queues_to_use; ++ u32 tx_channels_count = priv->plat->tx_queues_to_use; ++ int rxfifosz = priv->plat->rx_fifo_size; ++ int txfifosz = priv->plat->tx_fifo_size; ++ u32 txmode = 0, rxmode = 0, chan = 0; ++ u8 qmode = 0; ++ ++ if (rxfifosz == 0) ++ rxfifosz = priv->dma_cap.rx_fifo_size; ++ if (txfifosz == 0) ++ txfifosz = priv->dma_cap.tx_fifo_size; ++ ++ /* Adjust for real per queue fifo size */ ++ rxfifosz /= rx_channels_count; ++ txfifosz /= tx_channels_count; ++ ++ if (priv->plat->force_thresh_dma_mode) { ++ txmode = tc; ++ rxmode = tc; ++ } else if (priv->plat->force_sf_dma_mode || priv->plat->tx_coe) { ++ /* In case of GMAC, SF mode can be enabled ++ * to perform the TX COE in HW. This depends on: ++ * 1) TX COE if actually supported ++ * 2) There is no bugged Jumbo frame support ++ * that needs to not insert csum in the TDES. ++ */ ++ txmode = SF_DMA_MODE; ++ rxmode = SF_DMA_MODE; ++ priv->xstats.threshold = SF_DMA_MODE; ++ } else { ++ txmode = tc; ++ rxmode = SF_DMA_MODE; ++ } ++ ++ /* configure all channels */ ++ for (chan = 0; chan < rx_channels_count; chan++) { ++ qmode = priv->plat->rx_queues_cfg[chan].mode_to_use; ++ ++ stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, ++ rxfifosz, qmode); ++ stmmac_set_dma_bfsize(priv, priv->ioaddr, priv->dma_buf_sz, ++ chan); ++ } ++ ++ for (chan = 0; chan < tx_channels_count; chan++) { ++ qmode = priv->plat->tx_queues_cfg[chan].mode_to_use; ++ ++ stmmac_dma_tx_mode(priv, priv->ioaddr, txmode, chan, ++ txfifosz, qmode); ++ } ++} ++ ++/** ++ * sunxi_uio_hw_setup - setup mac in a usable state. ++ * @dev : pointer to the device structure. ++ * @init_ptp: initialize PTP if set ++ * Description: ++ * this is the main function to setup the HW in a usable state because the ++ * dma engine is reset, the core registers are configured (e.g. AXI, ++ * Checksum features, timers). The DMA is ready to start receiving and ++ * transmitting. ++ * Return value: ++ * 0 on success and an appropriate (-)ve integer as defined in errno.h ++ * file on failure. ++ */ ++static int sunxi_uio_hw_setup(struct net_device *dev, bool init_ptp) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ int ret; ++ ++ /* DMA initialization and SW reset */ ++ ret = sunxi_uio_init_dma_engine(priv); ++ if (ret < 0) { ++ netdev_err(priv->dev, "%s: DMA engine initialization failed\n", ++ __func__); ++ return ret; ++ } ++ ++ /* Copy the MAC addr into the HW */ ++ stmmac_set_umac_addr(priv, priv->hw, dev->dev_addr, 0); ++ ++ /* PS and related bits will be programmed according to the speed */ ++ if (priv->hw->pcs) { ++ int speed = priv->plat->mac_port_sel_speed; ++ ++ if (speed == SPEED_10 || speed == SPEED_100 || ++ speed == SPEED_1000) { ++ priv->hw->ps = speed; ++ } else { ++ dev_warn(priv->device, "invalid port speed\n"); ++ priv->hw->ps = 0; ++ } ++ } ++ ++ /* Initialize the MAC Core */ ++ stmmac_core_init(priv, priv->hw, dev); ++ ++ /* Initialize MTL*/ ++ sunxi_uio_mtl_configuration(priv); ++ ++ /* Initialize Safety Features */ ++ sunxi_uio_safety_feat_configuration(priv); ++ ++ ret = stmmac_rx_ipc(priv, priv->hw); ++ if (!ret) { ++ netdev_warn(priv->dev, "RX IPC Checksum Offload disabled\n"); ++ priv->plat->rx_coe = STMMAC_RX_COE_NONE; ++ priv->hw->rx_csum = 0; ++ } ++ ++ /* Enable the MAC Rx/Tx */ ++ stmmac_mac_set(priv, priv->ioaddr, true); ++ ++ /* Set the HW DMA mode and the COE */ ++ sunxi_uio_dma_operation_mode(priv); ++ ++ if (priv->hw->pcs) ++ stmmac_pcs_ctrl_ane(priv, priv->hw, 1, priv->hw->ps, 0); ++ ++ /* set TX and RX rings length */ ++ sunxi_uio_set_rings_length(priv); ++ ++ return 0; ++} ++ ++static int sunxi_uio_set_bfsize(int mtu, int bufsize) ++{ ++ int ret = bufsize; ++ ++ if (mtu >= BUF_SIZE_8KiB) ++ ret = BUF_SIZE_16KiB; ++ else if (mtu >= BUF_SIZE_4KiB) ++ ret = BUF_SIZE_8KiB; ++ else if (mtu >= BUF_SIZE_2KiB) ++ ret = BUF_SIZE_4KiB; ++ else if (mtu > DEFAULT_BUFSIZE) ++ ret = BUF_SIZE_2KiB; ++ else ++ ret = DEFAULT_BUFSIZE; ++ ++ return ret; ++} ++ ++/** ++ * sunxi_uio_init - open entry point of the driver ++ * @dev : pointer to the device structure. ++ * Description: ++ * This function is the open entry point of the driver. ++ * Return value: ++ * 0 on success and an appropriate (-)ve integer as defined in errno.h ++ * file on failure. ++ */ ++static int sunxi_uio_init(struct net_device *dev) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ int ret, bfsize = 0; ++ ++ if (priv->hw->pcs != STMMAC_PCS_TBI && ++ priv->hw->pcs != STMMAC_PCS_RTBI && ++ !priv->hw->xpcs) { ++ ret = sunxi_uio_init_phy(dev); ++ if (ret) { ++ netdev_err(priv->dev, ++ "%s: Cannot attach to PHY (error: %d)\n", ++ __func__, ret); ++ return ret; ++ } ++ } ++ ++ /* Extra statistics */ ++ priv->xstats.threshold = tc; ++ ++ bfsize = stmmac_set_16kib_bfsize(priv, dev->mtu); ++ if (bfsize < 0) ++ bfsize = 0; ++ ++ if (bfsize < BUF_SIZE_16KiB) ++ bfsize = sunxi_uio_set_bfsize(dev->mtu, priv->dma_buf_sz); ++ ++ priv->dma_buf_sz = bfsize; ++ buf_sz = bfsize; ++ ++ priv->rx_copybreak = STMMAC_RX_COPYBREAK; ++ ++ if (!priv->dma_tx_size) ++ priv->dma_tx_size = DMA_DEFAULT_TX_SIZE; ++ if (!priv->dma_rx_size) ++ priv->dma_rx_size = DMA_DEFAULT_RX_SIZE; ++ ++ ret = sunxi_uio_alloc_dma_desc_resources(priv); ++ if (ret < 0) { ++ netdev_err(priv->dev, "%s: DMA descriptors allocation failed\n", ++ __func__); ++ goto dma_desc_error; ++ } ++ ++ ret = sunxi_uio_hw_setup(dev, true); ++ if (ret < 0) { ++ netdev_err(priv->dev, "%s: Hw setup failed\n", __func__); ++ goto init_error; ++ } ++ ++ phylink_start(priv->phylink); ++ /* We may have called phylink_speed_down before */ ++ phylink_speed_up(priv->phylink); ++ ++ return 0; ++ ++init_error: ++ sunxi_uio_free_dma_desc_resources(priv); ++dma_desc_error: ++ phylink_disconnect_phy(priv->phylink); ++ return ret; ++} ++ ++/** ++ * sunxi_uio_exit - close entry point of the driver ++ * @dev : device pointer. ++ * Description: ++ * This is the stop entry point of the driver. ++ */ ++static int sunxi_uio_exit(struct net_device *dev) ++{ ++ struct stmmac_priv *priv = netdev_priv(dev); ++ ++ /* Stop and disconnect the PHY */ ++ if (dev->phydev) { ++ phy_stop(dev->phydev); ++ phy_disconnect(dev->phydev); ++ } ++ ++ /* Release and free the Rx/Tx resources */ ++ sunxi_uio_free_dma_desc_resources(priv); ++ ++ /* Disable the MAC Rx/Tx */ ++ stmmac_mac_set(priv, priv->ioaddr, false); ++ ++ netif_carrier_off(dev); ++ ++ return 0; ++} ++ ++/** ++ * sunxi_uio_probe() platform driver probe routine ++ * - register uio devices filled with memory maps retrieved ++ * from device tree ++ */ ++static int sunxi_uio_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node, *mac_node; ++ struct sunxi_uio *chip; ++ struct net_device *netdev; ++ struct stmmac_priv *priv; ++ struct uio_info *uio; ++ struct resource *res; ++ int err = 0; ++ ++ chip = devm_kzalloc(dev, sizeof(struct sunxi_uio), ++ GFP_KERNEL); ++ if (!chip) ++ return -ENOMEM; ++ ++ uio = &chip->uio; ++ chip->dev = dev; ++ mac_node = of_parse_phandle(np, "sunxi,ethernet", 0); ++ if (!mac_node) ++ return -ENODEV; ++ ++ if (of_device_is_available(mac_node)) { ++ netdev = of_find_net_device_by_node(mac_node); ++ of_node_put(mac_node); ++ if (!netdev) ++ return -ENODEV; ++ } else { ++ of_node_put(mac_node); ++ return -EINVAL; ++ } ++ ++ chip->ndev = netdev; ++ rtnl_lock(); ++ dev_close(netdev); ++ rtnl_unlock(); ++ ++ rtnl_lock(); ++ err = sunxi_uio_init(netdev); ++ if (err) { ++ rtnl_unlock(); ++ dev_err(dev, "Failed to open stmmac resource: %d\n", err); ++ return err; ++ } ++ rtnl_unlock(); ++ ++ priv = netdev_priv(netdev); ++ snprintf(chip->name, sizeof(chip->name), "uio_%s", ++ netdev->name); ++ uio->name = chip->name; ++ uio->version = DRIVER_VERSION; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) ++ return -ENODEV; ++ ++ uio->mem[0].name = "eth_regs"; ++ uio->mem[0].addr = res->start & PAGE_MASK; ++ uio->mem[0].size = PAGE_ALIGN(resource_size(res)); ++ uio->mem[0].memtype = UIO_MEM_PHYS; ++ ++ uio->mem[1].name = "eth_rx_bd"; ++ uio->mem[1].addr = priv->rx_queue[0].dma_rx_phy; ++ uio->mem[1].size = priv->dma_rx_size * sizeof(struct dma_desc); ++ uio->mem[1].memtype = UIO_MEM_PHYS; ++ ++ uio->mem[2].name = "eth_tx_bd"; ++ uio->mem[2].addr = priv->tx_queue[0].dma_tx_phy; ++ uio->mem[2].size = priv->dma_tx_size * sizeof(struct dma_desc); ++ uio->mem[2].memtype = UIO_MEM_PHYS; ++ ++ uio->open = sunxi_uio_open; ++ uio->release = sunxi_uio_release; ++ /* Custom mmap function. */ ++ uio->mmap = sunxi_uio_mmap; ++ uio->priv = chip; ++ ++ err = uio_register_device(dev, uio); ++ if (err) { ++ dev_err(dev, "Failed to register uio device: %d\n", err); ++ return err; ++ } ++ ++ chip->map_num = 3; ++ ++ dev_info(dev, "Registered %s uio devices, %d register maps attached\n", ++ chip->name, chip->map_num); ++ ++ platform_set_drvdata(pdev, chip); ++ ++ return 0; ++} ++ ++/** ++ * sunxi_uio_remove() - UIO platform driver release ++ * routine - unregister uio devices ++ */ ++static int sunxi_uio_remove(struct platform_device *pdev) ++{ ++ struct sunxi_uio *chip = platform_get_drvdata(pdev); ++ struct net_device *netdev; ++ ++ if (!chip) ++ return -EINVAL; ++ ++ netdev = chip->ndev; ++ ++ uio_unregister_device(&chip->uio); ++ ++ if (netdev) { ++ rtnl_lock(); ++ sunxi_uio_exit(netdev); ++ rtnl_unlock(); ++ } ++ ++ platform_set_drvdata(pdev, NULL); ++ ++ if (netdev) { ++ rtnl_lock(); ++ dev_open(netdev, NULL); ++ rtnl_unlock(); ++ } ++ ++ return 0; ++} ++ ++static const struct of_device_id sunxi_uio_of_match[] = { ++ { .compatible = "allwinner,sunxi-uio", }, ++ { } ++}; ++ ++static struct platform_driver sunxi_uio_driver = { ++ .driver = { ++ .owner = THIS_MODULE, ++ .name = DRIVER_NAME, ++ .of_match_table = sunxi_uio_of_match, ++ }, ++ .probe = sunxi_uio_probe, ++ .remove = sunxi_uio_remove, ++}; ++module_platform_driver(sunxi_uio_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("xuminghui "); ++MODULE_VERSION(DRIVER_VERSION); +diff --git a/drivers/net/ethernet/allwinner/gmac/Kconfig b/drivers/net/ethernet/allwinner/gmac/Kconfig +new file mode 100644 +index 000000000000..3eeb93b055d8 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac/Kconfig +@@ -0,0 +1,37 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++menu "Gmac Drivers" ++ ++config SUNXI55I_GMAC ++ tristate "Allwinner A523 GMAC support" ++ depends on OF ++ select SUNXI55I_GMAC_MDIO ++ select CRC32 ++ help ++ Support for Allwinner A523 GMAC ethernet driver. ++ ++ To compile this driver as a module, choose M here. The module ++ will be called sunxi-gmac.ko. ++ ++config SUNXI55I_GMAC_MDIO ++ tristate "Allwinner A523 GMAC MDIO support" ++ select MDIO_BUS ++ select MDIO_DEVICE ++ select PHYLIB ++ select MII ++ help ++ This driver supports the A523 GMAC MDIO interface in the network ++ ++config SUNXI55I_GMAC_METADATA ++ bool "Allwinner A523 GMAC metadata support" ++ depends on SUNXI55I_GMAC ++ help ++ Support Allwinner A523 GMAC to transmit and receive metadata ++ ++# todo (not backported from bsp yet) ++#config SUNXI55I_EPHY ++# tristate "Drivers for A523 Allwinner EPHY" ++# depends on SUNXI55I_GMAC ++# help ++# Support Allwinner A523 EPHY ++ ++endmenu +diff --git a/drivers/net/ethernet/allwinner/gmac/Makefile b/drivers/net/ethernet/allwinner/gmac/Makefile +new file mode 100644 +index 000000000000..d6375496b9d5 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++ccflags-y += -I $(srctree)/drivers/net/ethernet/allwinner/gmac ++ ++obj-$(CONFIG_SUNXI55I_GMAC) += sunxi-gmac.o ++obj-$(CONFIG_SUNXI55I_GMAC_MDIO) += sunxi-mdio.o ++obj-$(CONFIG_SUNXI55I_EPHY) += sunxi-ephy.o +diff --git a/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c b/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c +new file mode 100644 +index 000000000000..ab883e227d0a +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c +@@ -0,0 +1,315 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++* Allwinner ephy driver. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++* This file is licensed under the terms of the GNU General Public ++* License version 2. This program is licensed "as is" without any ++* warranty of any kind, whether express or implied. ++*/ ++ ++/* #define DEBUG */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++//#include ++#include ++#include ++ ++#define AC300_EPHY "ac300-ephy" ++#define AC300_DEV "ac300" ++ ++#define EPHY_CALI_BASE 0 ++#define EPHY_CALI_BIT BIT(29) ++#define EPHY_BGS_MASK 0x0f000000 ++#define EPHY_BGS_OFFSET 24 ++/* ++ * Ephy diagram test ++ * This macro will cause all cpu stuck ++ * Use it carefully ++ */ ++/* #define EPHY_100M_ED_TEST */ ++ ++struct ephy_res { ++ struct phy_device *ac300; ++ spinlock_t lock; ++ atomic_t ephy_en; ++}; ++ ++static struct ephy_res ac300_ephy; ++ ++static int sunxi_ephy_read_sid(u32 *buf) ++{ ++ int ret; ++ ++ if (!buf) ++ return -EINVAL; ++ ++ //todo ret = sunxi_efuse_readn(EFUSE_FTCP_NAME, buf, 4); ++ //if (ret) ++ // return ret; ++ ++ return 0; ++} ++ ++void sunxi_ephy_config_new_init(struct phy_device *phydev) ++{ ++ phy_write(phydev, 0x1f, 0x0100); /* switch to Page 1 */ ++ phy_write(phydev, 0x12, 0x4824); /* Disable APS */ ++ ++ phy_write(phydev, 0x1f, 0x0200); /* switch to Page 2 */ ++ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */ ++ ++ phy_write(phydev, 0x1f, 0x0600); /* switch to Page 6 */ ++ phy_write(phydev, 0x14, 0x7809); /* PHYAFE TX optimization */ ++ phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */ ++ phy_write(phydev, 0x10, 0x5523); ++ phy_write(phydev, 0x15, 0x3533); ++ ++ phy_write(phydev, 0x1f, 0x0800); /* switch to Page 8 */ ++ phy_write(phydev, 0x1d, 0x0844); /* disable auto offset */ ++ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */ ++ ++ phy_write(phydev, 0x1f, 0x0000); /* switch to Page 0 */ ++} ++ ++void sunxi_ephy_config_old_init(struct phy_device *phydev) ++{ ++ phy_write(phydev, 0x1f, 0x0100); /* switch to Page 1 */ ++ phy_write(phydev, 0x12, 0x4824); /* Disable APS */ ++ ++ phy_write(phydev, 0x1f, 0x0200); /* switch to Page 2 */ ++ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */ ++ ++ phy_write(phydev, 0x1f, 0x0600); /* switch to Page 6 */ ++ phy_write(phydev, 0x14, 0x780b); /* PHYAFE TX optimization */ ++ phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */ ++ phy_write(phydev, 0x15, 0x1530); ++ phy_write(phydev, 0x1f, 0x0800); /* switch to Page 8 */ ++ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */ ++ ++ phy_write(phydev, 0x1f, 0x0000); /* switch to Page 0 */ ++} ++ ++void sunxi_ephy_config_cali(struct phy_device *phydev, u32 ephy_cali) ++{ ++ int value, bgs_adjust; ++ ++ /* Adjust BGS value of 0x06 reg */ ++ value = phy_read(phydev, 0x06); ++ value &= ~(0x0F << 12); ++ bgs_adjust = (ephy_cali & EPHY_BGS_MASK) >> EPHY_BGS_OFFSET; ++ value |= (0xF & (EPHY_CALI_BASE bgs_adjust)) << 12; ++ phy_write(phydev, 0x06, value); ++} ++ ++void sunxi_ephy_disable_intelligent_ieee(struct phy_device *phydev) ++{ ++ unsigned int value; ++ ++ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */ ++ value = phy_read(phydev, 0x17); /* read address 0 0x17 register */ ++ value &= ~(1 << 3); /* reg 0x17 bit 3, set 0 to disable IEEE */ ++ phy_write(phydev, 0x17, value); ++ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */ ++} ++ ++void sunxi_ephy_disable_802_3az_ieee(struct phy_device *phydev) ++{ ++ unsigned int value; ++ ++ phy_write(phydev, 0xd, 0x7); ++ phy_write(phydev, 0xe, 0x3c); ++ phy_write(phydev, 0xd, 0x1 << 14 | 0x7); ++ value = phy_read(phydev, 0xe); ++ value &= ~(0x1 << 1); ++ phy_write(phydev, 0xd, 0x7); ++ phy_write(phydev, 0xe, 0x3c); ++ phy_write(phydev, 0xd, 0x1 << 14 | 0x7); ++ phy_write(phydev, 0xe, value); ++ ++ phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */ ++ phy_write(phydev, 0x18, 0x0000); ++} ++ ++#ifdef EPHY_100M_ED_TEST ++static void ephy_debug_test(void *info) ++{ ++ while (1) ++ ; ++} ++#endif ++ ++static int ephy_config_init(struct phy_device *phydev) ++{ ++ int value; ++ int ret; ++ u32 ephy_cali = 0; ++ ++ ret = sunxi_ephy_read_sid(&ephy_cali); ++ if (ret) { ++ pr_err("ephy cali efuse read fail, use default 0\n"); ++ } ++ ++ sunxi_ephy_config_cali(ac300_ephy.ac300, ephy_cali); ++ ++ /* ++ * EPHY_CALI_BIT: the flag of calibration value ++ * 0: Normal ++ * 1: Low level of calibration value ++ */ ++ if (ephy_cali & EPHY_CALI_BIT) { ++ pr_debug("Low level ephy, use new init\n"); ++ sunxi_ephy_config_new_init(phydev); ++ } else { ++ pr_debug("Normal ephy, use old init\n"); ++ sunxi_ephy_config_old_init(phydev); ++ } ++ ++ sunxi_ephy_disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */ ++ sunxi_ephy_disable_802_3az_ieee(phydev); /* Disable 802.3az IEEE */ ++ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */ ++ ++#ifdef EPHY_100M_ED_TEST ++ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */ ++ phy_write(phydev, 0x00, 0x2100); /* Force 100M Mode */ ++ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */ ++ phy_write(phydev, 0x13, 0x0100); /* Force TX output@TXP/TXN */ ++ on_each_cpu(ephy_debug_test, NULL, 1); /* Stuck all cpu for ephy eye diagram test */ ++#endif ++ ++ value = phy_read(ac300_ephy.ac300, 0x06); ++ if (phydev->interface == PHY_INTERFACE_MODE_RMII) ++ value |= (1 << 11); ++ else ++ value &= (~(1 << 11)); ++ ++ phy_write(ac300_ephy.ac300, 0x06, value); ++ ++ return 0; ++} ++ ++static int ephy_probe(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int ephy_suspend(struct phy_device *phydev) ++{ ++ return genphy_suspend(phydev); ++} ++ ++static int ephy_resume(struct phy_device *phydev) ++{ ++ return genphy_resume(phydev); ++} ++ ++static void ac300_enable(struct phy_device *phydev) ++{ ++ /* release reset */ ++ phy_write(phydev, 0x00, 0x1f40); /* reset ephy */ ++ phy_write(phydev, 0x00, 0x1f43); /* de-reset ephy */ ++ ++ /* clk gating */ ++ phy_write(phydev, 0x00, 0x1fb7); ++ ++ /* io enable */ ++ phy_write(phydev, 0x05, 0xa81f); ++ ++ mdelay(10); ++ phy_write(phydev, 0x06, 0x0811); ++ ++ mdelay(10); ++ phy_write(phydev, 0x06, 0x0810); ++} ++ ++static void ac300_disable(struct phy_device *phydev) ++{ ++ phy_write(phydev, 0x00, 0x1f40); ++ phy_write(phydev, 0x05, 0xa800); ++ ++ phy_write(phydev, 0x06, 0x01); ++} ++ ++static int ac300_suspend(struct phy_device *phydev) ++{ ++ ac300_disable(phydev); ++ return 0; ++} ++ ++static int ac300_resume(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int ac300_probe(struct phy_device *phydev) ++{ ++ ac300_enable(phydev); ++ ++ return 0; ++} ++ ++static int ac300_init(struct phy_device *phydev) ++{ ++ /* save ac300 message */ ++ ac300_ephy.ac300 = phydev; ++ ++ /* ac300 enable */ ++ ac300_enable(phydev); ++ ++ /* FIXME: delay may be required after AC300 reset*/ ++ msleep(50); ++ ++ return 0; ++} ++ ++static struct phy_driver ac300_driver[] = { ++{ ++ .phy_id = 0xc0000000, ++ .name = AC300_DEV, ++ .phy_id_mask = 0xffffffff, ++ .config_init = ac300_init, ++ .suspend = ac300_suspend, ++ .resume = ac300_resume, ++ .probe = ac300_probe, ++}, ++{ .phy_id = 0x00441400, ++ .name = AC300_EPHY, ++ .phy_id_mask = 0x0ffffff0, ++ .config_init = ephy_config_init, ++ .config_aneg = &genphy_config_aneg, ++ .read_status = &genphy_read_status, ++ .suspend = ephy_suspend, ++ .resume = ephy_resume, ++ .probe = ephy_probe, ++ ++} }; ++ ++module_phy_driver(ac300_driver); ++ ++static struct mdio_device_id __maybe_unused ac300_tbl[] = { ++ { 0xc0000000, 0x0fffffff }, ++ { 0x00441400, 0x0ffffff0 }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, ac300_tbl); ++MODULE_DESCRIPTION("Allwinner phy drivers"); ++MODULE_AUTHOR("xuminghui "); ++MODULE_LICENSE("GPL"); ++MODULE_VERSION("1.1.1"); +diff --git a/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c b/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c +new file mode 100644 +index 000000000000..6751d60e516c +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c +@@ -0,0 +1,3634 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner GMAC driver. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++* This file is licensed under the terms of the GNU General Public ++* License version 2. This program is licensed "as is" without any ++* warranty of any kind, whether express or implied. ++*/ ++ ++/* #define DEBUG */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++//todo #include ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++#include ++#endif /* CONFIG_SUNXI55I_EPHY */ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++#include ++#endif /* CONFIG_SUNXI55I_GMAC_METADATA */ ++#include ++ ++/* All sunxi-gmac tracepoints are defined by the include below, which ++ * must be included exactly once across the whole kernel with ++ * CREATE_TRACE_POINTS defined ++ */ ++//#define CREATE_TRACE_POINTS ++//#include "sunxi-gmac-trace.h" ++ ++#define SUNXI_GMAC_MODULE_VERSION "2.4.3" ++ ++#define SUNXI_GMAC_DMA_DESC_RX 256 ++#define SUNXI_GMAC_DMA_DESC_TX 256 ++#define SUNXI_GMAC_BUDGET (sunxi_gmac_dma_desc_rx / 4) ++#define SUNXI_GMAC_TX_THRESH (sunxi_gmac_dma_desc_tx / 4) ++ ++#define SUNXI_GMAC_HASH_TABLE_SIZE 64 ++#define SUNXI_GMAC_MAX_BUF_SZ (SZ_2K - 1) ++/* Under the premise that each descriptor currently transmits 2k data, jumbo frame max is 8100 */ ++#define SUNXI_GMAC_MAX_MTU_SZ 8100 ++ ++/* SUNXI_GMAC_FRAME_FILTER register value */ ++#define SUNXI_GMAC_FRAME_FILTER_PR 0x80000000 /* Promiscuous Mode */ ++#define SUNXI_GMAC_FRAME_FILTER_HUC 0x00000100 /* Hash Unicast */ ++#define SUNXI_GMAC_FRAME_FILTER_HMC 0x00000200 /* Hash Multicast */ ++#define SUNXI_GMAC_FRAME_FILTER_DAIF 0x00000010 /* DA Inverse Filtering */ ++#define SUNXI_GMAC_FRAME_FILTER_PM 0x00010000 /* Pass all multicast */ ++#define SUNXI_GMAC_FRAME_FILTER_DBF 0x00020000 /* Disable Broadcast frames */ ++#define SUNXI_GMAC_FRAME_FILTER_SAIF 0x00000020 /* Inverse Filtering */ ++#define SUNXI_GMAC_FRAME_FILTER_SAF 0x00000040 /* Source Address Filter */ ++#define SUNXI_GMAC_FRAME_FILTER_RA 0x00000001 /* Receive all mode */ ++ ++/* Default tx descriptor */ ++#define SUNXI_GMAC_TX_SINGLE_DESC0 0x80000000 ++#define SUNXI_GMAC_TX_SINGLE_DESC1 0x63000000 ++ ++/* Default rx descriptor */ ++#define SUNXI_GMAC_RX_SINGLE_DESC0 0x80000000 ++#define SUNXI_GMAC_RX_SINGLE_DESC1 0x83000000 ++ ++/****************************************************************************** ++ * sunxi gmac reg offset ++ *****************************************************************************/ ++#define SUNXI_GMAC_BASIC_CTL0 0x00 ++#define SUNXI_GMAC_BASIC_CTL1 0x04 ++#define SUNXI_GMAC_INT_STA 0x08 ++#define SUNXI_GMAC_INT_EN 0x0C ++#define SUNXI_GMAC_TX_CTL0 0x10 ++#define SUNXI_GMAC_TX_CTL1 0x14 ++#define SUNXI_GMAC_TX_FLOW_CTL 0x1C ++#define SUNXI_GMAC_TX_DESC_LIST 0x20 ++#define SUNXI_GMAC_RX_CTL0 0x24 ++#define SUNXI_GMAC_RX_CTL1 0x28 ++#define SUNXI_GMAC_RX_DESC_LIST 0x34 ++#define SUNXI_GMAC_RX_FRM_FLT 0x38 ++#define SUNXI_GMAC_RX_HASH0 0x40 ++#define SUNXI_GMAC_RX_HASH1 0x44 ++#define SUNXI_GMAC_MDIO_ADDR 0x48 ++#define SUNXI_GMAC_MDIO_DATA 0x4C ++#define SUNXI_GMAC_ADDR_HI(reg) (0x50 + ((reg) << 3)) ++#define SUNXI_GMAC_ADDR_LO(reg) (0x54 + ((reg) << 3)) ++#define SUNXI_GMAC_TX_DMA_STA 0xB0 ++#define SUNXI_GMAC_TX_CUR_DESC 0xB4 ++#define SUNXI_GMAC_TX_CUR_BUF 0xB8 ++#define SUNXI_GMAC_RX_DMA_STA 0xC0 ++#define SUNXI_GMAC_RX_CUR_DESC 0xC4 ++#define SUNXI_GMAC_RX_CUR_BUF 0xC8 ++#define SUNXI_GMAC_RGMII_STA 0xD0 ++ ++#define SUNXI_GMAC_RGMII_IRQ 0x00000001 ++ ++#define SUNXI_GMAC_CTL0_LM 0x02 ++#define SUNXI_GMAC_CTL0_DM 0x01 ++#define SUNXI_GMAC_CTL0_SPEED 0x04 ++ ++#define SUNXI_GMAC_BURST_LEN 0x3F000000 ++#define SUNXI_GMAC_RX_TX_PRI 0x02 ++#define SUNXI_GMAC_SOFT_RST 0x01 ++ ++#define SUNXI_GMAC_TX_FLUSH 0x01 ++#define SUNXI_GMAC_TX_MD 0x02 ++#define SUNXI_GMAC_TX_NEXT_FRM 0x04 ++#define SUNXI_GMAC_TX_TH 0x0700 ++#define SUNXI_GMAC_TX_FLOW_CTL_BIT 0x01 ++ ++#define SUNXI_GMAC_RX_FLUSH 0x01 ++#define SUNXI_GMAC_RX_MD 0x02 ++#define SUNXI_GMAC_RX_RUNT_FRM 0x04 ++#define SUNXI_GMAC_RX_ERR_FRM 0x08 ++#define SUNXI_GMAC_RX_TH 0x0030 ++#define SUNXI_GMAC_RX_FLOW_CTL 0x1000000 ++ ++#define SUNXI_GMAC_TX_INT 0x00001 ++#define SUNXI_GMAC_TX_STOP_INT 0x00002 ++#define SUNXI_GMAC_TX_UA_INT 0x00004 ++#define SUNXI_GMAC_TX_TOUT_INT 0x00008 ++#define SUNXI_GMAC_TX_UNF_INT 0x00010 ++#define SUNXI_GMAC_TX_EARLY_INT 0x00020 ++#define SUNXI_GMAC_RX_INT 0x00100 ++#define SUNXI_GMAC_RX_UA_INT 0x00200 ++#define SUNXI_GMAC_RX_STOP_INT 0x00400 ++#define SUNXI_GMAC_RX_TOUT_INT 0x00800 ++#define SUNXI_GMAC_RX_OVF_INT 0x01000 ++#define SUNXI_GMAC_RX_EARLY_INT 0x02000 ++#define SUNXI_GMAC_LINK_STA_INT 0x10000 ++ ++#define SUNXI_GMAC_CHAIN_MODE_OFFSET 24 ++#define SUNXI_GMAC_LOOPBACK_OFFSET 2 ++#define SUNXI_GMAC_LOOPBACK 0x00000002 ++#define SUNXI_GMAC_CLEAR_SPEED 0x03 ++#define SUNXI_GMAC_1000M_SPEED ~0x0c ++#define SUNXI_GMAC_100M_SPEED 0x0c ++#define SUNXI_GMAC_10M_SPEED 0x08 ++#define SUNXI_GMAC_RX_FLOW_EN 0x10000 ++#define SUNXI_GMAC_TX_FLOW_EN 0x00001 ++#define SUNXI_GMAC_PAUSE_OFFSET 4 ++#define SUNXI_GMAC_INT_OFFSET 0x3fff ++#define SUNXI_GMAC_RX_DMA_EN 0x40000000 ++#define SUNXI_GMAC_TX_DMA_EN 0x40000000 ++#define SUNXI_GMAC_BURST_VALUE 8 ++#define SUNXI_GMAC_BURST_OFFSET 24 ++#define SUNXI_GMAC_SF_DMA_MODE 1 ++#define SUNXI_GMAC_TX_FRM_LEN_OFFSET 30 ++#define SUNXI_GMAC_CRC_OFFSET 27 ++#define SUNXI_GMAC_STRIP_FCS_OFFSET 28 ++#define SUNXI_GMAC_JUMBO_EN_OFFSET 29 ++#define SUNXI_GMAC_MDC_DIV_RATIO_M 0x03 ++#define SUNXI_GMAC_MDC_DIV_OFFSET 20 ++#define SUNXI_GMAC_TX_DMA_TH64 64 ++#define SUNXI_GMAC_TX_DMA_TH128 128 ++#define SUNXI_GMAC_TX_DMA_TH192 192 ++#define SUNXI_GMAC_TX_DMA_TH256 256 ++#define SUNXI_GMAC_TX_DMA_TH64_VAL 0x00000000 ++#define SUNXI_GMAC_TX_DMA_TH128_VAL 0X00000100 ++#define SUNXI_GMAC_TX_DMA_TH192_VAL 0x00000200 ++#define SUNXI_GMAC_TX_DMA_TH256_VAL 0x00000300 ++#define SUNXI_GMAC_RX_DMA_TH32 32 ++#define SUNXI_GMAC_RX_DMA_TH64 64 ++#define SUNXI_GMAC_RX_DMA_TH96 96 ++#define SUNXI_GMAC_RX_DMA_TH128 128 ++#define SUNXI_GMAC_RX_DMA_TH32_VAL 0x10 ++#define SUNXI_GMAC_RX_DMA_TH64_VAL 0x00 ++#define SUNXI_GMAC_RX_DMA_TH96_VAL 0x20 ++#define SUNXI_GMAC_RX_DMA_TH128_VAL 0x30 ++#define SUNXI_GMAC_TX_DMA_START 31 ++#define SUNXI_GMAC_RX_DMA_START 31 ++#define SUNXI_GMAC_DMA_DESC_BUFSIZE 11 ++#define SUNXI_GMAC_LOOPBACK_OFF 0 ++#define SUNXI_GMAC_MAC_LOOPBACK_ON 1 ++#define SUNXI_GMAC_PHY_LOOPBACK_ON 2 ++#define SUNXI_GMAC_OWN_DMA 0x80000000 ++#define SUNXI_GMAC_GPHY_TEST_OFFSET 13 ++#define SUNXI_GMAC_GPHY_TEST_MASK 0x07 ++#define SUNXI_GMAC_PHY_RGMII_MASK 0x00000004 ++#define SUNXI_GMAC_ETCS_RMII_MASK 0x00002003 ++#define SUNXI_GMAC_RGMII_INTCLK_MASK 0x00000002 ++#define SUNXI_GMAC_RMII_MASK 0x00002000 ++#define SUNXI_GMAC_TX_DELAY_MASK 0x07 ++#define SUNXI_GMAC_TX_DELAY_OFFSET 10 ++#define SUNXI_GMAC_RX_DELAY_MASK 0x1F ++#define SUNXI_GMAC_RX_DELAY_OFFSET 5 ++/* Flow Control defines */ ++#define SUNXI_GMAC_FLOW_OFF 0 ++#define SUNXI_GMAC_FLOW_RX 1 ++#define SUNXI_GMAC_FLOW_TX 2 ++#define SUNXI_GMAC_FLOW_AUTO (SUNXI_GMAC_FLOW_TX | SUNXI_GMAC_FLOW_RX) ++ ++/* Ring buffer caculate method */ ++#define circ_cnt(head, tail, size) (((head) > (tail)) ? \ ++ ((head) - (tail)) : \ ++ ((head) - (tail)) & ((size) - 1)) ++ ++#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size)) ++ ++#define circ_inc(n, s) (((n) + 1) % (s)) ++ ++#define MAC_ADDR_LEN 18 ++#define SUNXI_GMAC_MAC_ADDRESS "80:3f:5d:09:8b:26" ++ ++/* loopback test */ ++#define LOOPBACK_PKT_CNT 64 ++#define LOOPBACK_PKT_LEN 1514 ++#define LOOPBACK_DEFAULT_TIME 5 ++enum self_test_index { ++ INTERNAL_LOOPBACK_TEST = 0, ++ EXTERNAL_LOOPBACK_TEST = 1, ++ SELF_TEST_MAX = 2, ++}; ++ ++static char sunxi_gmac_test_strings[][ETH_GSTRING_LEN] = { ++ "Internal lb test (mac loopback)", ++ "External lb test (phy loopback)", ++}; ++ ++//todo #ifdef MODULE ++//extern int get_custom_mac_address(int fmt, char *name, char *addr); ++//#endif ++ ++static char mac_str[MAC_ADDR_LEN] = SUNXI_GMAC_MAC_ADDRESS; ++module_param_string(mac_str, mac_str, MAC_ADDR_LEN, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)"); ++ ++static int rxmode = 1; ++module_param(rxmode, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(rxmode, "DMA threshold control value"); ++ ++static int txmode = 1; ++module_param(txmode, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(txmode, "DMA threshold control value"); ++ ++static int pause = 0x400; ++module_param(pause, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(pause, "Flow Control Pause Time"); ++ ++#define TX_TIMEO 5000 ++static int watchdog = TX_TIMEO; ++module_param(watchdog, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds"); ++ ++static int sunxi_gmac_dma_desc_rx = SUNXI_GMAC_DMA_DESC_RX; ++module_param(sunxi_gmac_dma_desc_rx, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(sunxi_gmac_dma_desc_rx, "The number of receive's descriptors"); ++ ++static int sunxi_gmac_dma_desc_tx = SUNXI_GMAC_DMA_DESC_TX; ++module_param(sunxi_gmac_dma_desc_tx, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(sunxi_gmac_dma_desc_tx, "The number of transmit's descriptors"); ++ ++static int user_tx_delay = -1; ++module_param(user_tx_delay, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(user_tx_delay, "RGMII tx-delay parameter"); ++ ++static int user_rx_delay = -1; ++module_param(user_rx_delay, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(user_rx_delay, "RGMII rx-delay parameter"); ++ ++/* - 0: Flow Off ++ * - 1: Rx Flow ++ * - 2: Tx Flow ++ * - 3: Rx & Tx Flow ++ */ ++static int flow_ctrl; ++module_param(flow_ctrl, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]"); ++ ++typedef union { ++ struct { ++ /* TDES0 */ ++ unsigned int deferred:1; /* Deferred bit (only half-duplex) */ ++ unsigned int under_err:1; /* Underflow error */ ++ unsigned int ex_deferral:1; /* Excessive deferral */ ++ unsigned int coll_cnt:4; /* Collision count */ ++ unsigned int vlan_tag:1; /* VLAN Frame */ ++ unsigned int ex_coll:1; /* Excessive collision */ ++ unsigned int late_coll:1; /* Late collision */ ++ unsigned int no_carr:1; /* No carrier */ ++ unsigned int loss_carr:1; /* Loss of collision */ ++ unsigned int ipdat_err:1; /* IP payload error */ ++ unsigned int frm_flu:1; /* Frame flushed */ ++ unsigned int jab_timeout:1; /* Jabber timeout */ ++ unsigned int err_sum:1; /* Error summary */ ++ unsigned int iphead_err:1; /* IP header error */ ++ unsigned int ttss:1; /* Transmit time stamp status */ ++ unsigned int reserved0:13; ++ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */ ++ } tx; ++ ++ /* bits 5 7 0 | Frame status ++ * ---------------------------------------------------------- ++ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects) ++ * 1 0 0 | IPv4/6 No CSUM errorS. ++ * 1 0 1 | IPv4/6 CSUM PAYLOAD error ++ * 1 1 0 | IPv4/6 CSUM IP HR error ++ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS ++ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD ++ * 0 1 1 | COE bypassed.. no IPv4/6 frame ++ * 0 1 0 | Reserved. ++ */ ++ struct { ++ /* RDES0 */ ++ unsigned int chsum_err:1; /* Payload checksum error */ ++ unsigned int crc_err:1; /* CRC error */ ++ unsigned int dribbling:1; /* Dribble bit error */ ++ unsigned int mii_err:1; /* Received error (bit3) */ ++ unsigned int recv_wt:1; /* Received watchdog timeout */ ++ unsigned int frm_type:1; /* Frame type */ ++ unsigned int late_coll:1; /* Late Collision */ ++ unsigned int ipch_err:1; /* IPv header checksum error (bit7) */ ++ unsigned int last_desc:1; /* Laset descriptor */ ++ unsigned int first_desc:1; /* First descriptor */ ++ unsigned int vlan_tag:1; /* VLAN Tag */ ++ unsigned int over_err:1; /* Overflow error (bit11) */ ++ unsigned int len_err:1; /* Length error */ ++ unsigned int sou_filter:1; /* Source address filter fail */ ++ unsigned int desc_err:1; /* Descriptor error */ ++ unsigned int err_sum:1; /* Error summary (bit15) */ ++ unsigned int frm_len:14; /* Frame length */ ++ unsigned int des_filter:1; /* Destination address filter fail */ ++ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */ ++ #define RX_PKT_OK 0x7FFFB77C ++ #define RX_LEN 0x3FFF0000 ++ } rx; ++ ++ unsigned int all; ++} sunxi_gmac_desc0_u; ++ ++typedef union { ++ struct { ++ /* TDES1 */ ++ unsigned int buf1_size:11; /* Transmit buffer1 size */ ++ unsigned int buf2_size:11; /* Transmit buffer2 size */ ++ unsigned int ttse:1; /* Transmit time stamp enable */ ++ unsigned int dis_pad:1; /* Disable pad (bit23) */ ++ unsigned int adr_chain:1; /* Second address chained */ ++ unsigned int end_ring:1; /* Transmit end of ring */ ++ unsigned int crc_dis:1; /* Disable CRC */ ++ unsigned int cic:2; /* Checksum insertion control (bit27:28) */ ++ unsigned int first_sg:1; /* First Segment */ ++ unsigned int last_seg:1; /* Last Segment */ ++ unsigned int interrupt:1; /* Interrupt on completion */ ++ } tx; ++ ++ struct { ++ /* RDES1 */ ++ unsigned int buf1_size:11; /* Received buffer1 size */ ++ unsigned int buf2_size:11; /* Received buffer2 size */ ++ unsigned int reserved1:2; ++ unsigned int adr_chain:1; /* Second address chained */ ++ unsigned int end_ring:1; /* Received end of ring */ ++ unsigned int reserved2:5; ++ unsigned int dis_ic:1; /* Disable interrupt on completion */ ++ } rx; ++ ++ unsigned int all; ++} sunxi_gmac_desc1_u; ++ ++typedef struct sunxi_gmac_dma_desc { ++ sunxi_gmac_desc0_u desc0; ++ sunxi_gmac_desc1_u desc1; ++ /* The address of buffers */ ++ unsigned int desc2; ++ /* Next desc's address */ ++ unsigned int desc3; ++} __attribute__((packed)) sunxi_gmac_dma_desc_t; ++ ++enum rx_frame_status { /* IPC status */ ++ good_frame = 0, ++ discard_frame = 1, ++ csum_none = 2, ++ incomplete_frame = 3, /* use for jumbo frame */ ++ llc_snap = 4, ++}; ++ ++enum tx_dma_irq_status { ++ tx_hard_error = 1, ++ tx_hard_error_bump_tc = 2, ++ handle_tx_rx = 3, ++}; ++ ++struct sunxi_gmac_extra_stats { ++ /* Transmit errors */ ++ unsigned long tx_underflow; ++ unsigned long tx_carrier; ++ unsigned long tx_losscarrier; ++ unsigned long vlan_tag; ++ unsigned long tx_deferred; ++ unsigned long tx_vlan; ++ unsigned long tx_jabber; ++ unsigned long tx_frame_flushed; ++ unsigned long tx_payload_error; ++ unsigned long tx_ip_header_error; ++ ++ /* Receive errors */ ++ unsigned long rx_desc; ++ unsigned long sa_filter_fail; ++ unsigned long overflow_error; ++ unsigned long ipc_csum_error; ++ unsigned long rx_collision; ++ unsigned long rx_crc; ++ unsigned long dribbling_bit; ++ unsigned long rx_length; ++ unsigned long rx_mii; ++ unsigned long rx_multicast; ++ unsigned long rx_gmac_overflow; ++ unsigned long rx_watchdog; ++ unsigned long da_rx_filter_fail; ++ unsigned long sa_rx_filter_fail; ++ unsigned long rx_missed_cntr; ++ unsigned long rx_overflow_cntr; ++ unsigned long rx_vlan; ++ ++ /* Tx/Rx IRQ errors */ ++ unsigned long tx_undeflow_irq; ++ unsigned long tx_process_stopped_irq; ++ unsigned long tx_jabber_irq; ++ unsigned long rx_overflow_irq; ++ unsigned long rx_buf_unav_irq; ++ unsigned long rx_process_stopped_irq; ++ unsigned long rx_watchdog_irq; ++ unsigned long tx_early_irq; ++ unsigned long fatal_bus_error_irq; ++ ++ /* Extra info */ ++ unsigned long threshold; ++ unsigned long tx_pkt_n; ++ unsigned long rx_pkt_n; ++ unsigned long poll_n; ++ unsigned long sched_timer_n; ++ unsigned long normal_irq_n; ++}; ++ ++struct sunxi_gmac; ++ ++struct sunxi_gmac_ephy_ops { ++ int (*resource_get)(struct platform_device *pdev); ++ void (*resource_put)(struct platform_device *pdev); ++ int (*hardware_init)(struct sunxi_gmac *chip); ++ void (*hardware_deinit)(struct sunxi_gmac *chip); ++}; ++ ++struct sunxi_gmac { ++ struct sunxi_gmac_dma_desc *dma_tx; /* Tx dma descriptor */ ++ struct sk_buff **tx_skb; /* Tx socket buffer array */ ++ unsigned int tx_clean; /* Tx ring buffer data consumer */ ++ unsigned int tx_dirty; /* Tx ring buffer data provider */ ++ dma_addr_t dma_tx_phy; /* Tx dma physical address */ ++ ++ unsigned long buf_sz; /* Size of buffer specified by current descriptor */ ++ ++ struct sunxi_gmac_dma_desc *dma_rx; /* Rx dma descriptor */ ++ struct sk_buff **rx_skb; /* Rx socket buffer array */ ++ unsigned int rx_clean; /* Rx ring buffer data consumer */ ++ unsigned int rx_dirty; /* Rx ring buffer data provider */ ++ dma_addr_t dma_rx_phy; /* Rx dma physical address */ ++ ++ struct net_device *ndev; ++ struct device *dev; ++ struct napi_struct napi; ++ ++ struct sunxi_gmac_extra_stats xstats; /* Additional network statistics */ ++ ++ bool link; /* Phy link status */ ++ int speed; /* NIC network speed */ ++ int duplex; /* NIC network duplex capability */ ++ ++ /* suspend error workaround */ ++ unsigned int gmac_uevent_suppress; /* control kobject_uevent_env */ ++ ++#define SUNXI_EXTERNAL_PHY 1 ++#define SUNXI_INTERNAL_PHY 0 ++ u32 phy_type; /* 1: External phy, 0: Internal phy */ ++ ++#define SUNXI_PHY_USE_CLK25M 0 /* External phy use phy25m clk provided by Soc */ ++#define SUNXI_PHY_USE_EXT_OSC 1 /* External phy use extern osc 25m */ ++ u32 phy_clk_type; ++ ++ phy_interface_t phy_interface; ++ void __iomem *base; ++ void __iomem *syscfg_base; ++ struct clk *gmac_clk; ++ struct clk *phy25m_clk; ++ struct reset_control *reset; ++ struct pinctrl *pinctrl; ++ struct regulator *gmac_supply; ++ ++ /* definition spinlock */ ++ spinlock_t universal_lock; /* universal spinlock */ ++ spinlock_t tx_lock; /* tx tramsmit spinlock */ ++ ++ /* adjust transmit clock delay, value: 0~7 */ ++ /* adjust receive clock delay, value: 0~31 */ ++ u32 tx_delay; ++ u32 rx_delay; ++ ++ struct device_node *phy_node; ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ struct device_node *ac300_np; ++ struct phy_device *ac300_dev; ++ struct pwm_device *ac300_pwm; ++ u32 pwm_channel; ++#define PWM_DUTY_NS 205 ++#define PWM_PERIOD_NS 410 ++ struct sunxi_gmac_ephy_ops *ephy_ops; ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++ /* loopback test */ ++ int loopback_pkt_len; ++ int loopback_test_rx_idx; ++ bool is_loopback_test; ++ u8 *loopback_test_rx_buf; ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++ u8 *metadata_buff; ++ struct miscdevice mdev; ++ struct completion metadata_done; ++ u32 metadata_len; ++#endif ++ ++ struct sk_buff *skb; /* for jumbo frame */ ++ ++ u32 irq_affinity; ++}; ++ ++/** ++ * sunxi_gmac_desc_init_chain - GMAC dma descriptor chain table initialization ++ * ++ * @desc: Dma descriptor ++ * @addr: Dma descriptor physical address ++ * @size: Dma descriptor numsa ++ * ++ * Called when the NIC is up. We init Tx/Rx dma descriptor table. ++ */ ++static void sunxi_gmac_desc_init_chain(struct sunxi_gmac_dma_desc *desc, unsigned long addr, unsigned int size) ++{ ++ /* In chained mode the desc3 points to the next element in the ring. ++ * The latest element has to point to the head. ++ */ ++ int i; ++ struct sunxi_gmac_dma_desc *p = desc; ++ unsigned long dma_phy = addr; ++ ++ for (i = 0; i < (size - 1); i++) { ++ dma_phy += sizeof(*p); ++ p->desc3 = (unsigned int)dma_phy; ++ /* Chain mode */ ++ p->desc1.all |= (1 << SUNXI_GMAC_CHAIN_MODE_OFFSET); ++ p++; ++ } ++ p->desc1.all |= (1 << SUNXI_GMAC_CHAIN_MODE_OFFSET); ++ p->desc3 = (unsigned int)addr; ++} ++ ++/** ++ * sunxi_gmac_set_link_mode - GMAC speed/duplex set func ++ * ++ * @iobase: Gmac membase ++ * @duplex: Duplex capability:half/full ++ * @speed: Speed:10M/100M/1000M ++ * ++ * Updates phy status and takes action for network queue if required ++ * based upon link status. ++ */ ++static void sunxi_gmac_set_link_mode(void *iobase, int duplex, int speed) ++{ ++ unsigned int ctrl = readl(iobase + SUNXI_GMAC_BASIC_CTL0); ++ ++ if (!duplex) ++ ctrl &= ~SUNXI_GMAC_CTL0_DM; ++ else ++ ctrl |= SUNXI_GMAC_CTL0_DM; ++ ++ /* clear ctrl speed */ ++ ctrl &= SUNXI_GMAC_CLEAR_SPEED; ++ ++ switch (speed) { ++ case 1000: ++ ctrl &= SUNXI_GMAC_1000M_SPEED; ++ break; ++ case 100: ++ ctrl |= SUNXI_GMAC_100M_SPEED; ++ break; ++ case 10: ++ ctrl |= SUNXI_GMAC_10M_SPEED; ++ break; ++ default: ++ break; ++ } ++ ++ writel(ctrl, iobase + SUNXI_GMAC_BASIC_CTL0); ++} ++ ++/** ++ * sunxi_gmac_loop - GMAC loopback mode set func ++ * ++ * @iobase: Gmac membase ++ * @loopback_enable: Loopback status ++ */ ++static void sunxi_gmac_loopback(void *iobase, int loopback_enable) ++{ ++ int reg; ++ ++ reg = readl(iobase + SUNXI_GMAC_BASIC_CTL0); ++ if (loopback_enable) ++ reg |= SUNXI_GMAC_LOOPBACK_OFFSET; ++ else ++ reg &= ~SUNXI_GMAC_LOOPBACK_OFFSET; ++ writel(reg, iobase + SUNXI_GMAC_BASIC_CTL0); ++} ++ ++static void sunxi_gmac_crc(void *iobase, bool enable) ++{ ++ int reg; ++ ++ reg = readl(iobase + SUNXI_GMAC_RX_CTL0); ++ if (enable) ++ reg |= (1 << SUNXI_GMAC_CRC_OFFSET); ++ else ++ reg &= ~(1 << SUNXI_GMAC_CRC_OFFSET); ++ ++ writel(reg, iobase + SUNXI_GMAC_RX_CTL0); ++} ++ ++/** ++ * sunxi_gmac_flow_ctrl - GMAC flow ctrl set func ++ * ++ * @iobase: Gmac membase ++ * @duolex: Duplex capability ++ * @fc: Flow control option ++ * @pause: Flow control pause time ++ */ ++static void sunxi_gmac_flow_ctrl(void *iobase, int duplex, int fc, int pause) ++{ ++ unsigned int flow; ++ ++ if (fc & SUNXI_GMAC_FLOW_RX) { ++ flow = readl(iobase + SUNXI_GMAC_RX_CTL0); ++ flow |= SUNXI_GMAC_RX_FLOW_EN; ++ writel(flow, iobase + SUNXI_GMAC_RX_CTL0); ++ } ++ ++ if (fc & SUNXI_GMAC_FLOW_TX) { ++ flow = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL); ++ flow |= SUNXI_GMAC_TX_FLOW_EN; ++ writel(flow, iobase + SUNXI_GMAC_TX_FLOW_CTL); ++ } ++ ++ if (duplex) { ++ flow = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL); ++ flow |= (pause << SUNXI_GMAC_PAUSE_OFFSET); ++ writel(flow, iobase + SUNXI_GMAC_TX_FLOW_CTL); ++ } ++} ++ ++/** ++ * sunxi_gmac_int_status - GMAC get int status func ++ * ++ * @iobase: Gmac membase ++ * @x: Extra statistics ++ */ ++static int sunxi_gmac_int_status(void *iobase, struct sunxi_gmac_extra_stats *x) ++{ ++ int ret; ++ /* read the status register (CSR5) */ ++ unsigned int intr_status; ++ ++ intr_status = readl(iobase + SUNXI_GMAC_RGMII_STA); ++ if (intr_status & SUNXI_GMAC_RGMII_IRQ) ++ readl(iobase + SUNXI_GMAC_RGMII_STA); ++ ++ intr_status = readl(iobase + SUNXI_GMAC_INT_STA); ++ ++ /* ABNORMAL interrupts */ ++ if (intr_status & SUNXI_GMAC_TX_UNF_INT) { ++ ret = tx_hard_error_bump_tc; ++ x->tx_undeflow_irq++; ++ } ++ if (intr_status & SUNXI_GMAC_TX_TOUT_INT) ++ x->tx_jabber_irq++; ++ ++ if (intr_status & SUNXI_GMAC_RX_OVF_INT) ++ x->rx_overflow_irq++; ++ ++ if (intr_status & SUNXI_GMAC_RX_UA_INT) ++ x->rx_buf_unav_irq++; ++ ++ if (intr_status & SUNXI_GMAC_RX_STOP_INT) ++ x->rx_process_stopped_irq++; ++ ++ if (intr_status & SUNXI_GMAC_RX_TOUT_INT) ++ x->rx_watchdog_irq++; ++ ++ if (intr_status & SUNXI_GMAC_TX_EARLY_INT) ++ x->tx_early_irq++; ++ ++ if (intr_status & SUNXI_GMAC_TX_STOP_INT) { ++ x->tx_process_stopped_irq++; ++ ret = tx_hard_error; ++ } ++ ++ /* TX/RX NORMAL interrupts */ ++ if (intr_status & (SUNXI_GMAC_TX_INT | SUNXI_GMAC_RX_INT | SUNXI_GMAC_RX_EARLY_INT | SUNXI_GMAC_TX_UA_INT)) { ++ x->normal_irq_n++; ++ if (intr_status & (SUNXI_GMAC_TX_INT | SUNXI_GMAC_RX_INT)) ++ ret = handle_tx_rx; ++ } ++ /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */ ++ writel(intr_status & SUNXI_GMAC_INT_OFFSET, iobase + SUNXI_GMAC_INT_STA); ++ ++ return ret; ++} ++ ++/** ++ * sunxi_gmac_enable_rx - enable gmac rx dma ++ * ++ * @iobase: Gmac membase ++ * @rxbase: Base address of Rx descriptor ++ */ ++static void sunxi_gmac_enable_rx(void *iobase, unsigned long rxbase) ++{ ++ unsigned int value; ++ ++ /* Write the base address of Rx descriptor lists into registers */ ++ writel(rxbase, iobase + SUNXI_GMAC_RX_DESC_LIST); ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL1); ++ value |= SUNXI_GMAC_RX_DMA_EN; ++ writel(value, iobase + SUNXI_GMAC_RX_CTL1); ++} ++ ++static int sunxi_gmac_read_rx_flowctl(void *iobase) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL1); ++ ++ return value & SUNXI_GMAC_RX_FLOW_CTL; ++} ++ ++static int sunxi_gmac_read_tx_flowctl(void *iobase) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL); ++ ++ return value & SUNXI_GMAC_TX_FLOW_CTL_BIT; ++} ++ ++static void sunxi_gmac_write_rx_flowctl(void *iobase, bool flag) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL1); ++ ++ if (flag) ++ value |= SUNXI_GMAC_RX_FLOW_CTL; ++ else ++ value &= ~SUNXI_GMAC_RX_FLOW_CTL; ++ ++ writel(value, iobase + SUNXI_GMAC_RX_CTL1); ++} ++ ++static void sunxi_gmac_write_tx_flowctl(void *iobase, bool flag) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL); ++ ++ if (flag) ++ value |= SUNXI_GMAC_TX_FLOW_CTL_BIT; ++ else ++ value &= ~SUNXI_GMAC_TX_FLOW_CTL_BIT; ++ ++ writel(value, iobase + SUNXI_GMAC_TX_FLOW_CTL); ++} ++ ++/** ++ * sunxi_gmac_enable_tx - enable gmac tx dma ++ * ++ * @iobase: Gmac membase ++ * @rxbase: Base address of Tx descriptor ++ */ ++static void sunxi_gmac_enable_tx(void *iobase, unsigned long txbase) ++{ ++ unsigned int value; ++ ++ /* Write the base address of Tx descriptor lists into registers */ ++ writel(txbase, iobase + SUNXI_GMAC_TX_DESC_LIST); ++ ++ value = readl(iobase + SUNXI_GMAC_TX_CTL1); ++ value |= SUNXI_GMAC_TX_DMA_EN; ++ writel(value, iobase + SUNXI_GMAC_TX_CTL1); ++} ++ ++/** ++ * sunxi_gmac_disable_tx - disable gmac tx dma ++ * ++ * @iobase: Gmac membase ++ * @rxbase: Base address of Tx descriptor ++ */ ++static void sunxi_gmac_disable_tx(void *iobase) ++{ ++ unsigned int value = readl(iobase + SUNXI_GMAC_TX_CTL1); ++ ++ value &= ~SUNXI_GMAC_TX_DMA_EN; ++ writel(value, iobase + SUNXI_GMAC_TX_CTL1); ++} ++ ++static int sunxi_gmac_dma_init(void *iobase) ++{ ++ unsigned int value; ++ ++ /* Burst should be 8 */ ++ value = (SUNXI_GMAC_BURST_VALUE << SUNXI_GMAC_BURST_OFFSET); ++ ++#ifdef CONFIG_SUNXI_GMAC_DA ++ value |= SUNXI_GMAC_RX_TX_PRI; /* Rx has priority over tx */ ++#endif ++ writel(value, iobase + SUNXI_GMAC_BASIC_CTL1); ++ ++ /* Mask interrupts by writing to CSR7 */ ++ writel(SUNXI_GMAC_RX_INT | SUNXI_GMAC_TX_UNF_INT, iobase + SUNXI_GMAC_INT_EN); ++ ++ return 0; ++} ++ ++/** ++ * sunxi_gmac_init - init gmac config ++ * ++ * @iobase: Gmac membase ++ * @txmode: tx flow control mode ++ * @rxmode: rx flow control mode ++ */ ++static int sunxi_gmac_init(void *iobase, int txmode, int rxmode) ++{ ++ unsigned int value; ++ ++ sunxi_gmac_dma_init(iobase); ++ ++ /* Initialize the core component */ ++ value = readl(iobase + SUNXI_GMAC_TX_CTL0); ++ value |= (1 << SUNXI_GMAC_TX_FRM_LEN_OFFSET); ++ writel(value, iobase + SUNXI_GMAC_TX_CTL0); ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL0); ++ value |= (1 << SUNXI_GMAC_CRC_OFFSET); /* Enable CRC & IPv4 Header Checksum */ ++ value |= (1 << SUNXI_GMAC_STRIP_FCS_OFFSET); /* Automatic Pad/CRC Stripping */ ++ value |= (1 << SUNXI_GMAC_JUMBO_EN_OFFSET); /* Jumbo Frame Enable */ ++ writel(value, iobase + SUNXI_GMAC_RX_CTL0); ++ ++ writel((SUNXI_GMAC_MDC_DIV_RATIO_M << SUNXI_GMAC_MDC_DIV_OFFSET), ++ iobase + SUNXI_GMAC_MDIO_ADDR); /* MDC_DIV_RATIO */ ++ ++ /* Set the Rx&Tx mode */ ++ value = readl(iobase + SUNXI_GMAC_TX_CTL1); ++ if (txmode == SUNXI_GMAC_SF_DMA_MODE) { ++ /* Transmit COE type 2 cannot be done in cut-through mode. */ ++ value |= SUNXI_GMAC_TX_MD; ++ /* Operating on second frame increase the performance ++ * especially when transmit store-and-forward is used. ++ */ ++ value |= SUNXI_GMAC_TX_NEXT_FRM; ++ } else { ++ value &= ~SUNXI_GMAC_TX_MD; ++ value &= ~SUNXI_GMAC_TX_TH; ++ /* Set the transmit threshold */ ++ if (txmode <= SUNXI_GMAC_TX_DMA_TH64) ++ value |= SUNXI_GMAC_TX_DMA_TH64_VAL; ++ else if (txmode <= SUNXI_GMAC_TX_DMA_TH128) ++ value |= SUNXI_GMAC_TX_DMA_TH128_VAL; ++ else if (txmode <= SUNXI_GMAC_TX_DMA_TH192) ++ value |= SUNXI_GMAC_TX_DMA_TH192_VAL; ++ else ++ value |= SUNXI_GMAC_TX_DMA_TH256_VAL; ++ } ++ writel(value, iobase + SUNXI_GMAC_TX_CTL1); ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL1); ++ if (rxmode == SUNXI_GMAC_SF_DMA_MODE) { ++ value |= SUNXI_GMAC_RX_MD; ++ } else { ++ value &= ~SUNXI_GMAC_RX_MD; ++ value &= ~SUNXI_GMAC_RX_TH; ++ if (rxmode <= SUNXI_GMAC_RX_DMA_TH32) ++ value |= SUNXI_GMAC_RX_DMA_TH32_VAL; ++ else if (rxmode <= SUNXI_GMAC_RX_DMA_TH64) ++ value |= SUNXI_GMAC_RX_DMA_TH64_VAL; ++ else if (rxmode <= SUNXI_GMAC_RX_DMA_TH96) ++ value |= SUNXI_GMAC_RX_DMA_TH96_VAL; ++ else ++ value |= SUNXI_GMAC_RX_DMA_TH128_VAL; ++ } ++ ++ /* Forward frames with error and undersized good frame. */ ++ value |= (SUNXI_GMAC_RX_ERR_FRM | SUNXI_GMAC_RX_RUNT_FRM); ++ ++ writel(value, iobase + SUNXI_GMAC_RX_CTL1); ++ ++ return 0; ++} ++ ++static void sunxi_gmac_hash_filter(void *iobase, unsigned long low, unsigned long high) ++{ ++ writel(high, iobase + SUNXI_GMAC_RX_HASH0); ++ writel(low, iobase + SUNXI_GMAC_RX_HASH1); ++} ++ ++/* write macaddr into MAC register */ ++static void sunxi_gmac_set_mac_addr_to_reg(void *iobase, unsigned char *addr, int index) ++{ ++ unsigned long data; ++ ++ /* one char is 8bit, so splice mac address in steps of 8 */ ++ data = (addr[5] << 8) | addr[4]; ++ writel(data, iobase + SUNXI_GMAC_ADDR_HI(index)); ++ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; ++ writel(data, iobase + SUNXI_GMAC_ADDR_LO(index)); ++} ++ ++static void sunxi_gmac_dma_start(void *iobase) ++{ ++ unsigned long value; ++ ++ value = readl(iobase + SUNXI_GMAC_TX_CTL0); ++ value |= (1 << SUNXI_GMAC_TX_DMA_START); ++ writel(value, iobase + SUNXI_GMAC_TX_CTL0); ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL0); ++ value |= (1 << SUNXI_GMAC_RX_DMA_START); ++ writel(value, iobase + SUNXI_GMAC_RX_CTL0); ++} ++ ++static void sunxi_gmac_dma_stop(void *iobase) ++{ ++ unsigned long value; ++ ++ value = readl(iobase + SUNXI_GMAC_TX_CTL0); ++ value &= ~(1 << SUNXI_GMAC_TX_DMA_START); ++ writel(value, iobase + SUNXI_GMAC_TX_CTL0); ++ ++ value = readl(iobase + SUNXI_GMAC_RX_CTL0); ++ value &= ~(1 << SUNXI_GMAC_RX_DMA_START); ++ writel(value, iobase + SUNXI_GMAC_RX_CTL0); ++} ++ ++static void sunxi_gmac_tx_poll(void *iobase) ++{ ++ unsigned int value; ++ ++ value = readl(iobase + SUNXI_GMAC_TX_CTL1); ++ writel(value | (1 << SUNXI_GMAC_TX_DMA_START), iobase + SUNXI_GMAC_TX_CTL1); ++} ++ ++static void sunxi_gmac_irq_enable(void *iobase) ++{ ++ writel(SUNXI_GMAC_RX_INT | SUNXI_GMAC_TX_UNF_INT, iobase + SUNXI_GMAC_INT_EN); ++} ++ ++static void sunxi_gmac_irq_disable(void *iobase) ++{ ++ writel(0, iobase + SUNXI_GMAC_INT_EN); ++} ++ ++static void sunxi_gmac_desc_buf_set(struct sunxi_gmac_dma_desc *desc, unsigned long paddr, int size) ++{ ++ desc->desc1.all &= (~((1 << SUNXI_GMAC_DMA_DESC_BUFSIZE) - 1)); ++ desc->desc1.all |= (size & ((1 << SUNXI_GMAC_DMA_DESC_BUFSIZE) - 1)); ++ desc->desc2 = paddr; ++} ++ ++static void sunxi_gmac_desc_set_own(struct sunxi_gmac_dma_desc *desc) ++{ ++ desc->desc0.all |= SUNXI_GMAC_OWN_DMA; ++} ++ ++static void sunxi_gmac_desc_tx_close(struct sunxi_gmac_dma_desc *first, struct sunxi_gmac_dma_desc *end, int csum_insert) ++{ ++ struct sunxi_gmac_dma_desc *desc = first; ++ ++ first->desc1.tx.first_sg = 1; ++ end->desc1.tx.last_seg = 1; ++ end->desc1.tx.interrupt = 1; ++ ++ if (csum_insert) ++ do { ++ desc->desc1.tx.cic = 3; ++ desc++; ++ } while (desc <= end); ++} ++ ++static void sunxi_gmac_desc_init(struct sunxi_gmac_dma_desc *desc) ++{ ++ desc->desc1.all = 0; ++ desc->desc2 = 0; ++ desc->desc1.all |= (1 << SUNXI_GMAC_CHAIN_MODE_OFFSET); ++} ++ ++static int sunxi_gmac_desc_get_tx_status(struct sunxi_gmac_dma_desc *desc, struct sunxi_gmac_extra_stats *x) ++{ ++ int ret = 0; ++ ++ if (desc->desc0.tx.under_err) { ++ x->tx_underflow++; ++ ret = -EIO; ++ } ++ ++ if (desc->desc0.tx.no_carr) { ++ x->tx_carrier++; ++ ret = -EIO; ++ } ++ ++ if (desc->desc0.tx.loss_carr) { ++ x->tx_losscarrier++; ++ ret = -EIO; ++ } ++ ++ if (desc->desc0.tx.deferred) { ++ x->tx_deferred++; ++ ret = -EIO; ++ } ++ ++ return ret; ++} ++ ++static int sunxi_gmac_desc_buf_get_len(struct sunxi_gmac_dma_desc *desc) ++{ ++ return (desc->desc1.all & ((1 << SUNXI_GMAC_DMA_DESC_BUFSIZE) - 1)); ++} ++ ++static int sunxi_gmac_desc_buf_get_addr(struct sunxi_gmac_dma_desc *desc) ++{ ++ return desc->desc2; ++} ++ ++static int sunxi_gmac_desc_rx_frame_len(struct sunxi_gmac_dma_desc *desc) ++{ ++ return desc->desc0.rx.frm_len; ++} ++ ++static int sunxi_gmac_desc_llc_snap(struct sunxi_gmac_dma_desc *desc) ++{ ++ /* Splice flags as follow: ++ * bits 5 7 0 | Frame status ++ * ---------------------------------------------------------- ++ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects) ++ * 1 0 0 | IPv4/6 No CSUM errorS. ++ * 1 0 1 | IPv4/6 CSUM PAYLOAD error ++ * 1 1 0 | IPv4/6 CSUM IP HR error ++ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS ++ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD ++ * 0 1 1 | COE bypassed.. no IPv4/6 frame ++ * 0 1 0 | Reserved. ++ */ ++ return ((desc->desc0.rx.frm_type << 2 | ++ desc->desc0.rx.ipch_err << 1 | ++ desc->desc0.rx.chsum_err) & 0x7); ++} ++ ++static int sunxi_gmac_desc_get_rx_status(struct sunxi_gmac_dma_desc *desc, struct sunxi_gmac_extra_stats *x) ++{ ++ int ret = good_frame; ++ ++ if (desc->desc0.rx.last_desc == 0) ++ return incomplete_frame; ++ ++ if (desc->desc0.rx.err_sum) { ++ if (desc->desc0.rx.desc_err) ++ x->rx_desc++; ++ ++ if (desc->desc0.rx.sou_filter) ++ x->sa_filter_fail++; ++ ++ if (desc->desc0.rx.over_err) ++ x->overflow_error++; ++ ++ if (desc->desc0.rx.ipch_err) ++ x->ipc_csum_error++; ++ ++ if (desc->desc0.rx.late_coll) ++ x->rx_collision++; ++ ++ if (desc->desc0.rx.crc_err) ++ x->rx_crc++; ++ ++ ret = discard_frame; ++ } ++ ++ if (desc->desc0.rx.len_err) ++ ret = discard_frame; ++ ++ if (desc->desc0.rx.mii_err) ++ ret = discard_frame; ++ ++ if (ret == good_frame) { ++ if (sunxi_gmac_desc_llc_snap(desc) == 0) ++ ret = llc_snap; ++ } ++ ++ return ret; ++} ++ ++static int sunxi_gmac_desc_get_own(struct sunxi_gmac_dma_desc *desc) ++{ ++ return desc->desc0.all & SUNXI_GMAC_OWN_DMA; ++} ++ ++static int sunxi_gmac_desc_get_tx_last_seg(struct sunxi_gmac_dma_desc *desc) ++{ ++ return desc->desc1.tx.last_seg; ++} ++ ++static int sunxi_gmac_reset(void *iobase, int n) ++{ ++ unsigned int value; ++ ++ /* gmac software reset */ ++ value = readl(iobase + SUNXI_GMAC_BASIC_CTL1); ++ value |= SUNXI_GMAC_SOFT_RST; ++ writel(value, iobase + SUNXI_GMAC_BASIC_CTL1); ++ ++ udelay(n); ++ ++ return !!(readl(iobase + SUNXI_GMAC_BASIC_CTL1) & SUNXI_GMAC_SOFT_RST); ++} ++ ++static int sunxi_gmac_stop(struct net_device *ndev); ++ ++static void sunxi_gmac_dump_dma_desc(struct sunxi_gmac_dma_desc *desc, int size) ++{ ++#ifdef DEBUG ++ int i; ++ ++ for (i = 0; i < size; i++) { ++ u32 *x = (u32 *)(desc + i); ++ ++ pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n", ++ i, (unsigned long)(&desc[i]), ++ x[0], x[1], x[2], x[3]); ++ } ++ pr_info("\n"); ++#endif ++} ++ ++static ssize_t sunxi_gmac_extra_tx_stats_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ return sprintf(buf, "tx_underflow: %lu\ntx_carrier: %lu\n" ++ "tx_losscarrier: %lu\nvlan_tag: %lu\n" ++ "tx_deferred: %lu\ntx_vlan: %lu\n" ++ "tx_jabber: %lu\ntx_frame_flushed: %lu\n" ++ "tx_payload_error: %lu\ntx_ip_header_error: %lu\n\n", ++ chip->xstats.tx_underflow, chip->xstats.tx_carrier, ++ chip->xstats.tx_losscarrier, chip->xstats.vlan_tag, ++ chip->xstats.tx_deferred, chip->xstats.tx_vlan, ++ chip->xstats.tx_jabber, chip->xstats.tx_frame_flushed, ++ chip->xstats.tx_payload_error, chip->xstats.tx_ip_header_error); ++} ++/* eg: cat extra_tx_stats */ ++static DEVICE_ATTR(extra_tx_stats, 0444, sunxi_gmac_extra_tx_stats_show, NULL); ++ ++static ssize_t sunxi_gmac_extra_rx_stats_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ return sprintf(buf, "rx_desc: %lu\nsa_filter_fail: %lu\n" ++ "overflow_error: %lu\nipc_csum_error: %lu\n" ++ "rx_collision: %lu\nrx_crc: %lu\n" ++ "dribbling_bit: %lu\nrx_length: %lu\n" ++ "rx_mii: %lu\nrx_multicast: %lu\n" ++ "rx_gmac_overflow: %lu\nrx_watchdog: %lu\n" ++ "da_rx_filter_fail: %lu\nsa_rx_filter_fail: %lu\n" ++ "rx_missed_cntr: %lu\nrx_overflow_cntr: %lu\n" ++ "rx_vlan: %lu\n\n", ++ chip->xstats.rx_desc, chip->xstats.sa_filter_fail, ++ chip->xstats.overflow_error, chip->xstats.ipc_csum_error, ++ chip->xstats.rx_collision, chip->xstats.rx_crc, ++ chip->xstats.dribbling_bit, chip->xstats.rx_length, ++ chip->xstats.rx_mii, chip->xstats.rx_multicast, ++ chip->xstats.rx_gmac_overflow, chip->xstats.rx_length, ++ chip->xstats.da_rx_filter_fail, chip->xstats.sa_rx_filter_fail, ++ chip->xstats.rx_missed_cntr, chip->xstats.rx_overflow_cntr, ++ chip->xstats.rx_vlan); ++} ++/* eg: cat extra_rx_stats */ ++static DEVICE_ATTR(extra_rx_stats, 0444, sunxi_gmac_extra_rx_stats_show, NULL); ++ ++static ssize_t sunxi_gmac_gphy_test_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n" ++ "0 - Normal Mode\n" ++ "1 - Transmit Jitter Test\n" ++ "2 - Transmit Jitter Test(MASTER mode)\n" ++ "3 - Transmit Jitter Test(SLAVE mode)\n" ++ "4 - Transmit Distortion Test\n\n"); ++} ++ ++static ssize_t sunxi_gmac_gphy_test_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ u16 value, phyreg_val; ++ int ret; ++ ++ phyreg_val = phy_read(ndev->phydev, MII_CTRL1000); ++ ++ ret = kstrtou16(buf, 0, &value); ++ if (ret) ++ return ret; ++ ++ if (value >= 0 && value <= 4) { ++ phyreg_val &= ~(SUNXI_GMAC_GPHY_TEST_MASK << ++ SUNXI_GMAC_GPHY_TEST_OFFSET); ++ phyreg_val |= value << SUNXI_GMAC_GPHY_TEST_OFFSET; ++ phy_write(ndev->phydev, MII_CTRL1000, phyreg_val); ++ netdev_info(ndev, "Set MII_CTRL1000(0x09) Reg: 0x%x\n", phyreg_val); ++ } else { ++ netdev_err(ndev, "Error: Unknown value (%d)\n", value); ++ } ++ ++ return count; ++} ++/* eg: echo 0 > gphy_test */ ++static DEVICE_ATTR(gphy_test, 0664, sunxi_gmac_gphy_test_show, sunxi_gmac_gphy_test_store); ++ ++static ssize_t sunxi_gmac_mii_read_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "Usage:\necho PHYREG > mii_read\n"); ++} ++ ++static ssize_t sunxi_gmac_mii_read_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ u16 phyreg, phyreg_val; ++ int ret; ++ ++ if (!netif_running(ndev)) { ++ netdev_err(ndev, "Error: Nic is down\n"); ++ return count; ++ } ++ ++ ret = kstrtou16(buf, 0, &phyreg); ++ if (ret) ++ return ret; ++ ++ phyreg_val = phy_read(ndev->phydev, phyreg); ++ netdev_info(ndev, "PHYREG[0x%02x] = 0x%04x\n", phyreg, phyreg_val); ++ return count; ++} ++/* eg: echo 0x00 > mii_read; cat mii_read */ ++static DEVICE_ATTR(mii_read, 0664, sunxi_gmac_mii_read_show, sunxi_gmac_mii_read_store); ++ ++static ssize_t sunxi_gmac_mii_write_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return sprintf(buf, "Usage:\necho PHYREG PHYVAL > mii_write\n"); ++} ++ ++static ssize_t sunxi_gmac_mii_write_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ u16 phyreg_val_before, phyreg_val_after; ++ int i, ret; ++ /* userspace_cmd[0]: phyreg ++ * userspace_cmd[1]: phyval ++ */ ++ u16 userspace_cmd[2] = {0}; ++ char *ptr1 = (char *)buf; ++ char *ptr2; ++ ++ if (!netif_running(ndev)) { ++ netdev_err(ndev, "Error: Nic is down\n"); ++ return count; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(userspace_cmd); i++) { ++ ptr1 = skip_spaces(ptr1); ++ ptr2 = strchr(ptr1, ' '); ++ if (ptr2) ++ *ptr2 = '\0'; ++ ++ ret = kstrtou16(ptr1, 16, &userspace_cmd[i]); ++ if (!ptr2 || ret) ++ break; ++ ++ ptr1 = ptr2 + 1; ++ } ++ ++ phyreg_val_before = phy_read(ndev->phydev, userspace_cmd[0]); ++ phy_write(ndev->phydev, userspace_cmd[0], userspace_cmd[1]); ++ phyreg_val_after = phy_read(ndev->phydev, userspace_cmd[0]); ++ netdev_info(ndev, "before PHYREG[0x%02x] = 0x%04x, after PHYREG[0x%02x] = 0x%04x\n", ++ userspace_cmd[0], phyreg_val_before, userspace_cmd[0], phyreg_val_after); ++ ++ return count; ++} ++/* eg: echo 0x00 0x1234 > mii_write; cat mii_write */ ++static DEVICE_ATTR(mii_write, 0664, sunxi_gmac_mii_write_show, sunxi_gmac_mii_write_store); ++ ++static ssize_t sunxi_gmac_loopback_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int macreg_val; ++ u16 phyreg_val; ++ ++ phyreg_val = phy_read(ndev->phydev, MII_BMCR); ++ if (phyreg_val & BMCR_LOOPBACK) ++ netdev_dbg(ndev, "Phy loopback enabled\n"); ++ else ++ netdev_dbg(ndev, "Phy loopback disabled\n"); ++ ++ macreg_val = readl(chip->base); ++ if (macreg_val & SUNXI_GMAC_LOOPBACK) ++ netdev_dbg(ndev, "Mac loopback enabled\n"); ++ else ++ netdev_dbg(ndev, "Mac loopback disabled\n"); ++ ++ return sprintf(buf, "Usage:\necho [0/1/2] > loopback\n" ++ "0 - Loopback off\n" ++ "1 - Mac loopback mode\n" ++ "2 - Phy loopback mode\n"); ++} ++ ++static ssize_t sunxi_gmac_loopback_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int phyreg_val, ret; ++ u16 mode; ++ ++ if (!netif_running(ndev)) { ++ netdev_err(ndev, "Error: eth is down\n"); ++ return count; ++ } ++ ++ ret = kstrtou16(buf, 0, &mode); ++ if (ret) ++ return ret; ++ ++ switch (mode) { ++ case SUNXI_GMAC_LOOPBACK_OFF: ++ sunxi_gmac_loopback(chip->base, 0); ++ phyreg_val = phy_read(ndev->phydev, MII_BMCR); ++ phy_write(ndev->phydev, MII_BMCR, phyreg_val & ~BMCR_LOOPBACK); ++ break; ++ case SUNXI_GMAC_MAC_LOOPBACK_ON: ++ phyreg_val = phy_read(ndev->phydev, MII_BMCR); ++ phy_write(ndev->phydev, MII_BMCR, phyreg_val & ~BMCR_LOOPBACK); ++ sunxi_gmac_loopback(chip->base, 1); ++ break; ++ case SUNXI_GMAC_PHY_LOOPBACK_ON: ++ sunxi_gmac_loopback(chip->base, 0); ++ phyreg_val = phy_read(ndev->phydev, MII_BMCR); ++ phy_write(ndev->phydev, MII_BMCR, phyreg_val | BMCR_LOOPBACK); ++ break; ++ default: ++ netdev_err(ndev, "Error: Please echo right value\n"); ++ break; ++ } ++ ++ return count; ++} ++/* eg: echo 1 > loopback */ ++static DEVICE_ATTR(loopback, 0664, sunxi_gmac_loopback_show, sunxi_gmac_loopback_store); ++ ++static ssize_t sunxi_gmac_tx_delay_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ u32 reg_val; ++ u16 tx_delay; ++ ++ reg_val = readl(chip->syscfg_base); ++ tx_delay = (reg_val >> SUNXI_GMAC_TX_DELAY_OFFSET) & SUNXI_GMAC_TX_DELAY_MASK; ++ ++ return sprintf(buf, "Usage:\necho [0~7] > tx_delay\n" ++ "\nnow tx_delay: %d\n", tx_delay); ++} ++ ++static ssize_t sunxi_gmac_tx_delay_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int ret; ++ u32 reg_val; ++ u16 tx_delay; ++ ++ if (!netif_running(ndev)) { ++ netdev_err(ndev, "Error: eth is down\n"); ++ return count; ++ } ++ ++ ret = kstrtou16(buf, 0, &tx_delay); ++ if (ret) ++ return ret; ++ ++ if (tx_delay > 7) ++ return -EINVAL; ++ ++ reg_val = readl(chip->syscfg_base); ++ reg_val &= ~(SUNXI_GMAC_TX_DELAY_MASK << SUNXI_GMAC_TX_DELAY_OFFSET); ++ reg_val |= ((tx_delay & SUNXI_GMAC_TX_DELAY_MASK) << SUNXI_GMAC_TX_DELAY_OFFSET); ++ writel(reg_val, chip->syscfg_base); ++ ++ return count; ++} ++/* eg: echo 1 > tx_delay */ ++static DEVICE_ATTR(tx_delay, 0664, sunxi_gmac_tx_delay_show, sunxi_gmac_tx_delay_store); ++ ++static ssize_t sunxi_gmac_rx_delay_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ u32 reg_val; ++ u16 rx_delay; ++ ++ reg_val = readl(chip->syscfg_base); ++ rx_delay = (reg_val >> SUNXI_GMAC_RX_DELAY_OFFSET) & SUNXI_GMAC_RX_DELAY_MASK; ++ ++ return sprintf(buf, "Usage:\necho [0~31] > rx_delay\n" ++ "\nnow rx_delay: %d\n", rx_delay); ++} ++ ++static ssize_t sunxi_gmac_rx_delay_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int ret; ++ u32 reg_val; ++ u16 rx_delay; ++ ++ if (!netif_running(ndev)) { ++ netdev_err(ndev, "Error: eth is down\n"); ++ return count; ++ } ++ ++ ret = kstrtou16(buf, 0, &rx_delay); ++ if (ret) ++ return ret; ++ ++ if (rx_delay > 31) ++ return -EINVAL; ++ ++ reg_val = readl(chip->syscfg_base); ++ reg_val &= ~(SUNXI_GMAC_RX_DELAY_MASK << SUNXI_GMAC_RX_DELAY_OFFSET); ++ reg_val |= ((rx_delay & SUNXI_GMAC_RX_DELAY_MASK) << SUNXI_GMAC_RX_DELAY_OFFSET); ++ writel(reg_val, chip->syscfg_base); ++ ++ return count; ++} ++/* eg: echo 1 > rx_delay */ ++static DEVICE_ATTR(rx_delay, 0664, sunxi_gmac_rx_delay_show, sunxi_gmac_rx_delay_store); ++ ++/* In phy state machine, we use this func to change link status */ ++static void sunxi_gmac_adjust_link(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct phy_device *phydev = ndev->phydev; ++ unsigned long flags; ++ int new_state = 0; ++ ++ if (!phydev) ++ return; ++ ++ spin_lock_irqsave(&chip->universal_lock, flags); ++ if (phydev->link) { ++ /* Now we make sure that we can be in full duplex mode. ++ * If not, we operate in half-duplex mode. ++ */ ++ if (phydev->duplex != chip->duplex) { ++ new_state = 1; ++ chip->duplex = phydev->duplex; ++ } ++ /* Flow Control operation */ ++ if (phydev->pause) ++ sunxi_gmac_flow_ctrl(chip->base, phydev->duplex, ++ flow_ctrl, pause); ++ ++ if (phydev->speed != chip->speed) { ++ new_state = 1; ++ chip->speed = phydev->speed; ++ } ++ ++ if (chip->link == 0) { ++ new_state = 1; ++ chip->link = phydev->link; ++ } ++ ++ if (new_state) ++ sunxi_gmac_set_link_mode(chip->base, chip->duplex, chip->speed); ++ ++ } else if (chip->link != phydev->link) { ++ new_state = 1; ++ chip->link = 0; ++ chip->speed = 0; ++ chip->duplex = -1; ++ } ++ spin_unlock_irqrestore(&chip->universal_lock, flags); ++ ++ if (new_state) ++ phy_print_status(phydev); ++} ++ ++static int sunxi_gmac_phy_release(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct phy_device *phydev = ndev->phydev; ++ int value; ++ ++ /* Stop and disconnect the PHY */ ++ if (phydev) ++ phy_stop(phydev); ++ ++ chip->link = PHY_DOWN; ++ chip->speed = 0; ++ chip->duplex = -1; ++ ++ if (phydev) { ++ value = phy_read(phydev, MII_BMCR); ++ phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN)); ++ } ++ ++ if (phydev) { ++ phy_disconnect(phydev); ++ ndev->phydev = NULL; ++ } ++ ++ return 0; ++} ++ ++/* Refill rx dma descriptor after using */ ++static void sunxi_gmac_rx_refill(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct sunxi_gmac_dma_desc *desc; ++ struct sk_buff *skb = NULL; ++ dma_addr_t dma_addr; ++ ++ while (circ_space(chip->rx_clean, chip->rx_dirty, sunxi_gmac_dma_desc_rx) > 0) { ++ int entry = chip->rx_clean; ++ ++ /* Find the dirty's desc and clean it */ ++ desc = chip->dma_rx + entry; ++ ++ if (chip->rx_skb[entry] == NULL) { ++ skb = netdev_alloc_skb_ip_align(ndev, chip->buf_sz); ++ ++ if (unlikely(skb == NULL)) ++ break; ++ ++ chip->rx_skb[entry] = skb; ++ dma_addr = dma_map_single(chip->dev, skb->data, ++ chip->buf_sz, DMA_FROM_DEVICE); ++ ++ sunxi_gmac_desc_buf_set(desc, dma_addr, chip->buf_sz); ++ } ++ ++ dma_wmb(); ++ sunxi_gmac_desc_set_own(desc); ++ chip->rx_clean = circ_inc(chip->rx_clean, sunxi_gmac_dma_desc_rx); ++ } ++} ++ ++/* ++ * sunxi_gmac_dma_desc_init - initialize the RX/TX descriptor list ++ * ++ * @ndev: net device structure ++ * Description: initialize the list for dma. ++ */ ++static int sunxi_gmac_dma_desc_init(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct device *dev = &ndev->dev; ++ ++ chip->rx_skb = devm_kzalloc(dev, sizeof(chip->rx_skb[0]) * sunxi_gmac_dma_desc_rx, ++ GFP_KERNEL); ++ if (!chip->rx_skb) { ++ netdev_err(ndev, "Error: Alloc rx_skb failed\n"); ++ goto rx_skb_err; ++ } ++ chip->tx_skb = devm_kzalloc(dev, sizeof(chip->tx_skb[0]) * sunxi_gmac_dma_desc_tx, ++ GFP_KERNEL); ++ if (!chip->tx_skb) { ++ netdev_err(ndev, "Error: Alloc tx_skb failed\n"); ++ goto tx_skb_err; ++ } ++ ++ chip->dma_tx = dma_alloc_coherent(chip->dev, ++ sunxi_gmac_dma_desc_tx * ++ sizeof(struct sunxi_gmac_dma_desc), ++ &chip->dma_tx_phy, ++ GFP_KERNEL); ++ if (!chip->dma_tx) { ++ netdev_err(ndev, "Error: Alloc dma_tx failed\n"); ++ goto dma_tx_err; ++ } ++ ++ chip->dma_rx = dma_alloc_coherent(chip->dev, ++ sunxi_gmac_dma_desc_rx * ++ sizeof(struct sunxi_gmac_dma_desc), ++ &chip->dma_rx_phy, ++ GFP_KERNEL); ++ if (!chip->dma_rx) { ++ netdev_err(ndev, "Error: Alloc dma_rx failed\n"); ++ goto dma_rx_err; ++ } ++ ++ /* Set the size of buffer depend on the MTU & max buf size */ ++ chip->buf_sz = SUNXI_GMAC_MAX_BUF_SZ; ++ return 0; ++ ++dma_rx_err: ++ dma_free_coherent(chip->dev, sunxi_gmac_dma_desc_rx * sizeof(struct sunxi_gmac_dma_desc), ++ chip->dma_tx, chip->dma_tx_phy); ++dma_tx_err: ++ kfree(chip->tx_skb); ++tx_skb_err: ++ kfree(chip->rx_skb); ++rx_skb_err: ++ return -ENOMEM; ++} ++ ++static void sunxi_gmac_free_rx_skb(struct sunxi_gmac *chip) ++{ ++ int i; ++ ++ for (i = 0; i < sunxi_gmac_dma_desc_rx; i++) { ++ if (chip->rx_skb[i] != NULL) { ++ struct sunxi_gmac_dma_desc *desc = chip->dma_rx + i; ++ ++ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc), ++ sunxi_gmac_desc_buf_get_len(desc), ++ DMA_FROM_DEVICE); ++ dev_kfree_skb_any(chip->rx_skb[i]); ++ chip->rx_skb[i] = NULL; ++ } ++ } ++} ++ ++static void sunxi_gmac_free_tx_skb(struct sunxi_gmac *chip) ++{ ++ int i; ++ ++ for (i = 0; i < sunxi_gmac_dma_desc_tx; i++) { ++ if (chip->tx_skb[i] != NULL) { ++ struct sunxi_gmac_dma_desc *desc = chip->dma_tx + i; ++ ++ if (sunxi_gmac_desc_buf_get_addr(desc)) ++ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc), ++ sunxi_gmac_desc_buf_get_len(desc), ++ DMA_TO_DEVICE); ++ dev_kfree_skb_any(chip->tx_skb[i]); ++ chip->tx_skb[i] = NULL; ++ } ++ } ++} ++ ++static void sunxi_gmac_dma_desc_deinit(struct sunxi_gmac *chip) ++{ ++ /* Free the region of consistent memory previously allocated for the DMA */ ++ dma_free_coherent(chip->dev, sunxi_gmac_dma_desc_tx * sizeof(struct sunxi_gmac_dma_desc), ++ chip->dma_tx, chip->dma_tx_phy); ++ dma_free_coherent(chip->dev, sunxi_gmac_dma_desc_rx * sizeof(struct sunxi_gmac_dma_desc), ++ chip->dma_rx, chip->dma_rx_phy); ++ ++ kfree(chip->rx_skb); ++ kfree(chip->tx_skb); ++} ++ ++static int sunxi_gmac_stop(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ netif_stop_queue(ndev); ++ napi_disable(&chip->napi); ++ ++ netif_carrier_off(ndev); ++ ++ sunxi_gmac_phy_release(ndev); ++ ++ sunxi_gmac_dma_stop(chip->base); ++ ++ netif_tx_lock_bh(ndev); ++ /* Release the DMA TX/RX socket buffers */ ++ sunxi_gmac_free_rx_skb(chip); ++ sunxi_gmac_free_tx_skb(chip); ++ netif_tx_unlock_bh(ndev); ++ ++ return 0; ++} ++ ++static int sunxi_gmac_power_on(struct sunxi_gmac *chip) ++{ ++ int ret; ++ ++ if (IS_ERR_OR_NULL(chip->gmac_supply)) ++ return 0; ++ ++ ret = regulator_set_voltage(chip->gmac_supply, 3300000, 3300000); ++ if (ret) { ++ dev_err(chip->dev, "gmac-power set voltage error\n"); ++ return -EINVAL; ++ } ++ ++ ret = regulator_enable(chip->gmac_supply); ++ if (ret) { ++ dev_err(chip->dev, "Error: enable gmac-power failed\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void sunxi_gmac_power_off(struct sunxi_gmac *chip) ++{ ++ if (IS_ERR_OR_NULL(chip->gmac_supply)) ++ return; ++ ++ regulator_disable(chip->gmac_supply); ++} ++ ++/** ++ * sunxi_gmac_open - GMAC device open ++ * @ndev: The Allwinner GMAC network adapter ++ * ++ * Called when system wants to start the interface. We init TX/RX channels ++ * and enable the hardware for packet reception/transmission and start the ++ * network queue. ++ * ++ * Returns 0 for a successful open, or appropriate error code ++ */ ++static int sunxi_gmac_open(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int ret; ++ ++ /* ++ * When changing the configuration of GMAC and PHY, ++ * it is necessary to turn off the carrier on the link. ++ */ ++ netif_carrier_off(ndev); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ if (chip->ac300_np) { ++ chip->ac300_dev = of_phy_find_device(chip->ac300_np); ++ if (!chip->ac300_dev) { ++ netdev_err(ndev, "Error: Could not find ac300 %s\n", ++ chip->ac300_np->full_name); ++ return -ENODEV; ++ } ++ phy_init_hw(chip->ac300_dev); ++ } ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++ if (chip->phy_node) { ++ ndev->phydev = of_phy_connect(ndev, chip->phy_node, ++ &sunxi_gmac_adjust_link, 0, chip->phy_interface); ++ if (!ndev->phydev) { ++ netdev_err(ndev, "Error: Could not connect to phy %s\n", ++ chip->phy_node->full_name); ++ return -ENODEV; ++ } ++ netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n", ++ ndev->name, ndev->phydev->interface, ndev->phydev->phy_id, ++ ndev->phydev->mdio.addr, "poll", dev_name(&ndev->phydev->mdio.dev)); ++ } ++ ret = sunxi_gmac_reset((void *)chip->base, 1000); ++ if (ret) { ++ netdev_err(ndev, "Error: Mac reset failed, please check phy and mac clk\n"); ++ goto mac_reset_err; ++ } ++ ++ sunxi_gmac_init(chip->base, txmode, rxmode); ++ sunxi_gmac_set_mac_addr_to_reg(chip->base, (unsigned char *)ndev->dev_addr, 0); ++ ++ memset(chip->dma_tx, 0, sunxi_gmac_dma_desc_tx * sizeof(struct sunxi_gmac_dma_desc)); ++ memset(chip->dma_rx, 0, sunxi_gmac_dma_desc_rx * sizeof(struct sunxi_gmac_dma_desc)); ++ ++ sunxi_gmac_desc_init_chain(chip->dma_rx, (unsigned long)chip->dma_rx_phy, sunxi_gmac_dma_desc_rx); ++ sunxi_gmac_desc_init_chain(chip->dma_tx, (unsigned long)chip->dma_tx_phy, sunxi_gmac_dma_desc_tx); ++ ++ chip->rx_clean = 0; ++ chip->rx_dirty = 0; ++ chip->tx_clean = 0; ++ chip->tx_dirty = 0; ++ sunxi_gmac_rx_refill(ndev); ++ ++ /* Extra statistics */ ++ memset(&chip->xstats, 0, sizeof(struct sunxi_gmac_extra_stats)); ++ ++ if (ndev->phydev) ++ phy_start(ndev->phydev); ++ ++ sunxi_gmac_enable_rx(chip->base, (unsigned long)((struct sunxi_gmac_dma_desc *) ++ chip->dma_rx_phy + chip->rx_dirty)); ++ sunxi_gmac_enable_tx(chip->base, (unsigned long)((struct sunxi_gmac_dma_desc *) ++ chip->dma_tx_phy + chip->tx_clean)); ++ ++ napi_enable(&chip->napi); ++ netif_start_queue(ndev); ++ ++ /* Start the Rx/Tx */ ++ sunxi_gmac_dma_start(chip->base); ++ ++ /* Indicate that the MAC is responsible for managing PHY PM */ ++ ndev->phydev->mac_managed_pm = true; ++ ++ return 0; ++ ++mac_reset_err: ++ phy_disconnect(ndev->phydev); ++ return ret; ++} ++ ++static int sunxi_gmac_clk_enable(struct sunxi_gmac *chip); ++static void sunxi_gmac_clk_disable(struct sunxi_gmac *chip); ++ ++#if IS_ENABLED(CONFIG_PM) ++static int sunxi_gmac_select_gpio_state(struct pinctrl *pctrl, char *name) ++{ ++ int ret; ++ struct pinctrl_state *pctrl_state; ++ ++ pctrl_state = pinctrl_lookup_state(pctrl, name); ++ if (IS_ERR(pctrl_state)) { ++ pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n", ++ name, pctrl_state); ++ return -EINVAL; ++ } ++ ++ ret = pinctrl_select_state(pctrl, pctrl_state); ++ if (ret < 0) ++ pr_err("gmac pinctrl_select_state(%s) failed! return %d\n", ++ name, ret); ++ ++ return ret; ++} ++ ++static int sunxi_gmac_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ if (!netif_running(ndev)) ++ return 0; ++ ++ sunxi_gmac_power_on(chip); ++ ++ sunxi_gmac_clk_enable(chip); ++ ++ sunxi_gmac_select_gpio_state(chip->pinctrl, PINCTRL_STATE_DEFAULT); ++ ++ netif_device_attach(ndev); ++ ++ sunxi_gmac_open(ndev); ++ ++ /* suspend error workaround */ ++ if (ndev->phydev) ++ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, chip->gmac_uevent_suppress); ++ ++ return 0; ++} ++ ++static int sunxi_gmac_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ if (!ndev || !netif_running(ndev)) ++ return 0; ++ ++ /* suspend error workaround */ ++ if (ndev->phydev) { ++ chip->gmac_uevent_suppress = dev_get_uevent_suppress(&ndev->phydev->mdio.dev); ++ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, true); ++ } ++ ++ netif_device_detach(ndev); ++ ++ sunxi_gmac_stop(ndev); ++ ++ sunxi_gmac_select_gpio_state(chip->pinctrl, PINCTRL_STATE_SLEEP); ++ ++ sunxi_gmac_power_off(chip); ++ ++ sunxi_gmac_clk_disable(chip); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops sunxi_gmac_pm_ops = { ++ .suspend = sunxi_gmac_suspend, ++ .resume = sunxi_gmac_resume, ++}; ++#else ++static const struct dev_pm_ops sunxi_gmac_pm_ops; ++#endif /* CONFIG_PM */ ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++static void sunxi_gmac_shutdown(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ if (chip->ac300_dev) ++ phy_detach(chip->ac300_dev); ++} ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++static void sunxi_gmac_check_addr(struct net_device *ndev, unsigned char *mac) ++{ ++ int i; ++ char *p = mac; ++ unsigned char tmp_addr[6]; ++ ++ if (!is_valid_ether_addr(ndev->dev_addr)) { ++ for (i = 0; i < ETH_ALEN; i++, p++) { ++ tmp_addr[i] = simple_strtoul(p, &p, 16); ++ dev_addr_mod(ndev, i, &tmp_addr[i], sizeof(char)); ++ } ++ ++ if (!is_valid_ether_addr(ndev->dev_addr)) { ++ eth_random_addr((u8 *)ndev->dev_addr); ++ netdev_info(ndev, "Info: Use random mac address\n"); ++ } ++ } ++} ++ ++static int sunxi_gmac_clk_enable(struct sunxi_gmac *chip) ++{ ++ struct net_device *ndev = chip->ndev; ++ int ret; ++ u32 clk_value; ++ ++ ret = reset_control_deassert(chip->reset); ++ if (ret) { ++ netdev_err(ndev, "Error: Try to de-assert gmac rst failed\n"); ++ goto gmac_reset_err; ++ } ++ ++ ret = clk_prepare_enable(chip->gmac_clk); ++ if (ret) { ++ netdev_err(ndev, "Error: Try to enable gmac_clk failed\n"); ++ goto gmac_clk_err; ++ } ++ ++ if (chip->phy_clk_type == SUNXI_PHY_USE_CLK25M) { ++ ret = clk_prepare_enable(chip->phy25m_clk); ++ if (ret) { ++ netdev_err(ndev, "Error: Try to enable phy25m_clk failed\n"); ++ goto phy25m_clk_err; ++ } ++ } ++ ++ clk_value = readl(chip->syscfg_base); ++ /* Only support RGMII/RMII/MII */ ++ if (chip->phy_interface == PHY_INTERFACE_MODE_RGMII) ++ clk_value |= SUNXI_GMAC_PHY_RGMII_MASK; ++ else ++ clk_value &= (~SUNXI_GMAC_PHY_RGMII_MASK); ++ ++ clk_value &= (~SUNXI_GMAC_ETCS_RMII_MASK); ++ if (chip->phy_interface == PHY_INTERFACE_MODE_RGMII ++ || chip->phy_interface == PHY_INTERFACE_MODE_GMII) ++ clk_value |= SUNXI_GMAC_RGMII_INTCLK_MASK; ++ else if (chip->phy_interface == PHY_INTERFACE_MODE_RMII) ++ clk_value |= SUNXI_GMAC_RMII_MASK; ++ ++ /* ++ * Adjust Tx/Rx clock delay ++ * Tx clock delay: 0~7 ++ * Rx clock delay: 0~31 ++ */ ++ clk_value &= ~(SUNXI_GMAC_TX_DELAY_MASK << SUNXI_GMAC_TX_DELAY_OFFSET); ++ clk_value |= ((chip->tx_delay & SUNXI_GMAC_TX_DELAY_MASK) << SUNXI_GMAC_TX_DELAY_OFFSET); ++ clk_value &= ~(SUNXI_GMAC_RX_DELAY_MASK << SUNXI_GMAC_RX_DELAY_OFFSET); ++ clk_value |= ((chip->rx_delay & SUNXI_GMAC_RX_DELAY_MASK) << SUNXI_GMAC_RX_DELAY_OFFSET); ++ ++ if (chip->phy_type == SUNXI_EXTERNAL_PHY) ++ clk_value &= ~(1 << 15); ++ else ++ clk_value |= (1 << 15); ++ ++ writel(clk_value, chip->syscfg_base); ++ ++ return 0; ++ ++phy25m_clk_err: ++ clk_disable(chip->gmac_clk); ++gmac_clk_err: ++ reset_control_assert(chip->reset); ++gmac_reset_err: ++ return ret; ++} ++ ++static void sunxi_gmac_clk_disable(struct sunxi_gmac *chip) ++{ ++ writel(0, chip->syscfg_base); ++ ++ if (chip->phy25m_clk) ++ clk_disable_unprepare(chip->phy25m_clk); ++ ++ if (chip->gmac_clk) ++ clk_disable_unprepare(chip->gmac_clk); ++ ++ if (chip->reset) ++ reset_control_assert(chip->reset); ++} ++ ++static void sunxi_gmac_tx_err(struct sunxi_gmac *chip) ++{ ++ netif_stop_queue(chip->ndev); ++ ++ sunxi_gmac_disable_tx(chip->base); ++ ++ sunxi_gmac_free_tx_skb(chip); ++ memset(chip->dma_tx, 0, sunxi_gmac_dma_desc_tx * sizeof(struct sunxi_gmac_dma_desc)); ++ sunxi_gmac_desc_init_chain(chip->dma_tx, (unsigned long)chip->dma_tx_phy, sunxi_gmac_dma_desc_tx); ++ chip->tx_dirty = 0; ++ chip->tx_clean = 0; ++ sunxi_gmac_enable_tx(chip->base, chip->dma_tx_phy); ++ ++ chip->ndev->stats.tx_errors++; ++ netif_wake_queue(chip->ndev); ++} ++ ++static void sunxi_gmac_schedule(struct sunxi_gmac *chip) ++{ ++ if (likely(napi_schedule_prep(&chip->napi))) { ++ sunxi_gmac_irq_disable(chip->base); ++ __napi_schedule(&chip->napi); ++ } ++} ++ ++static irqreturn_t sunxi_gmac_interrupt(int irq, void *dev_id) ++{ ++ struct net_device *ndev = (struct net_device *)dev_id; ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int status; ++ ++ status = sunxi_gmac_int_status(chip->base, (void *)(&chip->xstats)); ++ ++ if (likely(status == handle_tx_rx)) ++ sunxi_gmac_schedule(chip); ++ else if (unlikely(status == tx_hard_error_bump_tc)) ++ netdev_info(ndev, "Do nothing for bump tc\n"); ++ else if (unlikely(status == tx_hard_error)) ++ sunxi_gmac_tx_err(chip); ++ else ++ netdev_info(ndev, "Do nothing.....\n"); ++ ++ return IRQ_HANDLED; ++} ++ ++static void sunxi_gmac_tx_complete(struct sunxi_gmac *chip) ++{ ++ unsigned int entry = 0; ++ struct sk_buff *skb = NULL; ++ struct sunxi_gmac_dma_desc *desc = NULL; ++ int tx_stat; ++ ++ spin_lock_bh(&chip->tx_lock); ++ while (circ_cnt(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) > 0) { ++ entry = chip->tx_clean; ++ desc = chip->dma_tx + entry; ++ ++ /* Check if the descriptor is owned by the DMA. */ ++ if (sunxi_gmac_desc_get_own(desc)) ++ break; ++ ++ /* Verify tx error by looking at the last segment */ ++ if (sunxi_gmac_desc_get_tx_last_seg(desc)) { ++ tx_stat = sunxi_gmac_desc_get_tx_status(desc, (void *)(&chip->xstats)); ++ ++ /* ++ * These stats will be parsed by net framework layer ++ * use ifconfig -a in linux cmdline to view ++ */ ++ if (likely(!tx_stat)) ++ chip->ndev->stats.tx_packets++; ++ else ++ chip->ndev->stats.tx_errors++; ++ } ++ ++ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc), ++ sunxi_gmac_desc_buf_get_len(desc), DMA_TO_DEVICE); ++ ++ skb = chip->tx_skb[entry]; ++ chip->tx_skb[entry] = NULL; ++ sunxi_gmac_desc_init(desc); ++ ++ /* Find next dirty desc */ ++ chip->tx_clean = circ_inc(entry, sunxi_gmac_dma_desc_tx); ++ ++ if (unlikely(skb == NULL)) ++ continue; ++ ++ dev_kfree_skb(skb); ++ } ++ ++ if (unlikely(netif_queue_stopped(chip->ndev)) && ++ circ_space(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) > ++ SUNXI_GMAC_TX_THRESH) { ++ netif_wake_queue(chip->ndev); ++ } ++ spin_unlock_bh(&chip->tx_lock); ++} ++ ++static netdev_tx_t sunxi_gmac_xmit(struct sk_buff *skb, struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct sunxi_gmac_dma_desc *desc, *first; ++ unsigned int entry, len, tmp_len = 0; ++ unsigned char *data_addr = skb->data; ++ int i, csum_insert; ++ int nfrags = skb_shinfo(skb)->nr_frags; ++ dma_addr_t dma_addr; ++ ++ spin_lock_bh(&chip->tx_lock); ++ if (unlikely(circ_space(chip->tx_dirty, chip->tx_clean, ++ sunxi_gmac_dma_desc_tx) < (nfrags + 1))) { ++ if (!netif_queue_stopped(ndev)) { ++ netdev_err(ndev, "Error: Tx Ring full when queue awake\n"); ++ netif_stop_queue(ndev); ++ } ++ spin_unlock_bh(&chip->tx_lock); ++ ++ return NETDEV_TX_BUSY; ++ } ++ ++ csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL); ++ entry = chip->tx_dirty; ++ first = chip->dma_tx + entry; ++ desc = chip->dma_tx + entry; ++ ++ len = skb_headlen(skb); ++ chip->tx_skb[entry] = skb; ++ ++ /* Every desc max size is 2K */ ++ while (len != 0) { ++ desc = chip->dma_tx + entry; ++ tmp_len = ((len > SUNXI_GMAC_MAX_BUF_SZ) ? SUNXI_GMAC_MAX_BUF_SZ : len); ++ ++ dma_addr = dma_map_single(chip->dev, data_addr, tmp_len, DMA_TO_DEVICE); ++ if (dma_mapping_error(chip->dev, dma_addr)) { ++ ndev->stats.tx_dropped++; ++ dev_kfree_skb(skb); ++ spin_unlock_bh(&chip->tx_lock); ++ return -ENOMEM; ++ } ++ ++ sunxi_gmac_desc_buf_set(desc, dma_addr, tmp_len); ++ /* Don't set the first's own bit, here */ ++ if (first != desc) { ++ chip->tx_skb[entry] = NULL; ++ sunxi_gmac_desc_set_own(desc); ++ } ++ ++ entry = circ_inc(entry, sunxi_gmac_dma_desc_tx); ++ data_addr += tmp_len; ++ len -= tmp_len; ++ } ++ ++ for (i = 0; i < nfrags; i++) { ++ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; ++ ++ len = skb_frag_size(frag); ++ desc = chip->dma_tx + entry; ++ dma_addr = skb_frag_dma_map(chip->dev, frag, 0, len, DMA_TO_DEVICE); ++ if (dma_mapping_error(chip->dev, dma_addr)) { ++ ndev->stats.tx_dropped++; ++ dev_kfree_skb(skb); ++ spin_unlock_bh(&chip->tx_lock); ++ return -ENOMEM; ++ } ++ ++ sunxi_gmac_desc_buf_set(desc, dma_addr, len); ++ sunxi_gmac_desc_set_own(desc); ++ chip->tx_skb[entry] = NULL; ++ entry = circ_inc(entry, sunxi_gmac_dma_desc_tx); ++ } ++ ++ ndev->stats.tx_bytes += skb->len; ++ chip->tx_dirty = entry; ++ sunxi_gmac_desc_tx_close(first, desc, csum_insert); ++ ++ /* ++ * When the own bit, for the first frame, has to be set, all ++ * descriptors for the same frame has to be set before, to ++ * avoid race condition. ++ */ ++ dma_wmb(); ++ ++ sunxi_gmac_desc_set_own(first); ++ spin_unlock_bh(&chip->tx_lock); ++ ++ if (circ_space(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) <= ++ (MAX_SKB_FRAGS + 1)) { ++ netif_stop_queue(ndev); ++ if (circ_space(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) > ++ SUNXI_GMAC_TX_THRESH) ++ netif_wake_queue(ndev); ++ } ++ ++ netdev_dbg(ndev, "TX descripotor DMA: 0x%08x, dirty: %d, clean: %d\n", ++ (unsigned int)chip->dma_tx_phy, chip->tx_dirty, chip->tx_clean); ++ sunxi_gmac_dump_dma_desc(chip->dma_tx, sunxi_gmac_dma_desc_tx); ++ ++ sunxi_gmac_tx_poll(chip->base); ++ sunxi_gmac_tx_complete(chip); ++ ++ return NETDEV_TX_OK; ++} ++ ++static void sunxi_gmac_copy_loopback_data(struct sunxi_gmac *chip, ++ struct sk_buff *skb) ++{ ++ struct net_device *ndev = chip->ndev; ++ int loopback_len = chip->loopback_pkt_len; ++ int pkt_offset, frag_len, i; ++ void *frag_data = NULL; ++ u8 *loopback_buf = chip->loopback_test_rx_buf; ++ ++ if (chip->loopback_test_rx_idx == LOOPBACK_PKT_CNT) { ++ chip->loopback_test_rx_idx = 0; ++ netdev_warn(ndev, "Warning: Loopback test receive too more test pkts\n"); ++ } ++ ++ if (skb->len != chip->loopback_pkt_len) { ++ netdev_warn(ndev, "Warning: Wrong pkt length\n"); ++ chip->loopback_test_rx_idx++; ++ return; ++ } ++ ++ pkt_offset = chip->loopback_test_rx_idx * loopback_len; ++ frag_len = (int)skb_headlen(skb); ++ memcpy(loopback_buf + pkt_offset, skb->data, frag_len); ++ pkt_offset += frag_len; ++ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { ++ frag_data = skb_frag_address(&skb_shinfo(skb)->frags[i]); ++ frag_len = (int)skb_frag_size(&skb_shinfo(skb)->frags[i]); ++ memcpy((loopback_buf + pkt_offset), frag_data, frag_len); ++ pkt_offset += frag_len; ++ } ++ ++ chip->loopback_test_rx_idx++; ++} ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++static int sunxi_gmac_rx_metadata_cmp(struct sk_buff *skb) ++{ ++ const u8 tmp[4] = {0xAA, 0xAA, 0xAA, 0xAA}; ++ u8 *data = skb->data; ++ ++ data = data + (2 * ETH_ALEN + 2); ++ return memcmp(data, tmp, 4); ++} ++#endif ++ ++static int sunxi_gmac_rx(struct sunxi_gmac *chip, int limit) ++{ ++ unsigned int rxcount = 0, offset = 0; ++ unsigned int entry; ++ struct sunxi_gmac_dma_desc *desc = NULL; ++ int status; ++ u32 frame_len; ++ ++ while (rxcount < limit) { ++ entry = chip->rx_dirty; ++ desc = chip->dma_rx + entry; ++ ++ if (sunxi_gmac_desc_get_own(desc)) ++ break; ++ ++ rxcount++; ++ chip->rx_dirty = circ_inc(chip->rx_dirty, sunxi_gmac_dma_desc_rx); ++ ++ /* Get length & status from hardware */ ++ frame_len = sunxi_gmac_desc_rx_frame_len(desc); ++ status = sunxi_gmac_desc_get_rx_status(desc, (void *)(&chip->xstats)); ++ ++ netdev_dbg(chip->ndev, "Rx frame size %d, status: %d\n", ++ frame_len, status); ++ ++ if (unlikely(!chip->rx_skb[entry])) { ++ netdev_err(chip->ndev, "Skb is null\n"); ++ chip->ndev->stats.rx_dropped++; ++ break; ++ } ++ ++ if (status == discard_frame || frame_len > SUNXI_GMAC_MAX_MTU_SZ) { ++ netdev_err(chip->ndev, "Get error pkt\n"); ++ chip->ndev->stats.rx_errors++; ++ if (chip->rx_skb[entry]) { ++ dev_kfree_skb_any(chip->rx_skb[entry]); ++ chip->rx_skb[entry] = NULL; ++ } ++ ++ if (chip->skb) { ++ dev_kfree_skb_any(chip->skb); ++ chip->skb = NULL; ++ } ++ continue; ++ } ++ ++ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc), ++ sunxi_gmac_desc_buf_get_len(desc), DMA_FROM_DEVICE); ++ ++ /* jumbo frame */ ++ if (status == incomplete_frame) { ++ if (!chip->skb) ++ chip->skb = netdev_alloc_skb_ip_align(chip->ndev, SUNXI_GMAC_MAX_MTU_SZ); ++ ++ if (!chip->skb) { ++ netdev_err(chip->ndev, "Failed to alloc skb\n"); ++ if (chip->rx_skb[entry]) { ++ dev_kfree_skb_any(chip->rx_skb[entry]); ++ chip->rx_skb[entry] = NULL; ++ } ++ continue; ++ } ++ ++ skb_copy_to_linear_data_offset(chip->skb, offset, chip->rx_skb[entry]->data, frame_len - offset); ++ /* after copy, release tmp skb */ ++ dev_kfree_skb_any(chip->rx_skb[entry]); ++ chip->rx_skb[entry] = NULL; ++ offset = frame_len; ++ continue; ++ } else { ++ if (!chip->skb) { ++ /* ++ * no need to copy skb, ++ * pass it to protocol stack, ++ * and protocol stack will release skb ++ */ ++ chip->skb = chip->rx_skb[entry]; ++ chip->rx_skb[entry] = NULL; ++ } else { ++ skb_copy_to_linear_data_offset(chip->skb, offset, chip->rx_skb[entry]->data, frame_len - offset); ++ /* after copy, release tmp skb */ ++ dev_kfree_skb_any(chip->rx_skb[entry]); ++ chip->rx_skb[entry] = NULL; ++ } ++ offset = 0; ++ } ++ ++ if (likely(status != llc_snap)) ++ frame_len -= ETH_FCS_LEN; ++ ++ skb_put(chip->skb, frame_len); ++ ++ if (unlikely(chip->is_loopback_test)) ++ sunxi_gmac_copy_loopback_data(chip, chip->skb); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++ if (unlikely(sunxi_gmac_rx_metadata_cmp(chip->skb) == 0)) { ++ frame_len = min(frame_len, chip->metadata_len); ++ memcpy(chip->metadata_buff, chip->skb->data + (2 * ETH_ALEN + 6), frame_len); ++ complete(&chip->metadata_done); ++ dev_kfree_skb_any(chip->skb); ++ continue; ++ } ++#endif ++ ++ chip->skb->protocol = eth_type_trans(chip->skb, chip->ndev); ++ chip->skb->ip_summed = CHECKSUM_UNNECESSARY; ++ ++ napi_gro_receive(&chip->napi, chip->skb); ++ ++ chip->ndev->stats.rx_packets++; ++ chip->ndev->stats.rx_bytes += frame_len; ++ chip->skb = NULL; ++ } ++ ++ if (rxcount > 0) { ++ netdev_dbg(chip->ndev, "RX descriptor DMA: 0x%08x, dirty: %d, clean: %d\n", ++ (unsigned int)chip->dma_rx_phy, chip->rx_dirty, chip->rx_clean); ++ sunxi_gmac_dump_dma_desc(chip->dma_rx, sunxi_gmac_dma_desc_rx); ++ } ++ ++ sunxi_gmac_rx_refill(chip->ndev); ++ ++ return rxcount; ++} ++ ++static int sunxi_gmac_poll(struct napi_struct *napi, int budget) ++{ ++ struct sunxi_gmac *chip = container_of(napi, struct sunxi_gmac, napi); ++ int work_done = 0; ++ ++ sunxi_gmac_tx_complete(chip); ++ work_done = sunxi_gmac_rx(chip, budget); ++ ++ if (work_done < budget) { ++ napi_complete(napi); ++ sunxi_gmac_irq_enable(chip->base); ++ } ++ ++ return work_done; ++} ++ ++static int sunxi_gmac_change_mtu(struct net_device *ndev, int new_mtu) ++{ ++ if (netif_running(ndev)) { ++ netdev_err(ndev, "Error: Nic must be stopped to change its MTU\n"); ++ return -EBUSY; ++ } ++ ++ if (new_mtu < 46) { ++ netdev_err(ndev, "Error: Invalid MTU\n"); ++ return -EINVAL; ++ } ++ ++ ndev->mtu = new_mtu; ++ netdev_update_features(ndev); ++ ++ return 0; ++} ++ ++static netdev_features_t sunxi_gmac_fix_features(struct net_device *ndev, ++ netdev_features_t features) ++{ ++ return features; ++} ++ ++static void sunxi_gmac_set_rx_mode(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ unsigned int value = 0; ++ ++ netdev_dbg(ndev, "%s: # mcasts %d, # unicast %d\n", ++ __func__, netdev_mc_count(ndev), netdev_uc_count(ndev)); ++ ++ spin_lock_bh(&chip->universal_lock); ++ if (ndev->flags & IFF_PROMISC) { ++ value = SUNXI_GMAC_FRAME_FILTER_PR; ++ } else if ((netdev_mc_count(ndev) > SUNXI_GMAC_HASH_TABLE_SIZE) || ++ (ndev->flags & IFF_ALLMULTI)) { ++ value = SUNXI_GMAC_FRAME_FILTER_PM; /* pass all multi */ ++ sunxi_gmac_hash_filter(chip->base, ~0UL, ~0UL); ++ } else if (!netdev_mc_empty(ndev)) { ++ u32 mc_filter[2]; ++ struct netdev_hw_addr *ha; ++ ++ /* Hash filter for multicast */ ++ value = SUNXI_GMAC_FRAME_FILTER_HMC; ++ ++ memset(mc_filter, 0, sizeof(mc_filter)); ++ netdev_for_each_mc_addr(ha, ndev) { ++ /* The upper 6 bits of the calculated CRC are used to ++ * index the contens of the hash table ++ */ ++ int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26; ++ /* The most significant bit determines the register to ++ * use (H/L) while the other 5 bits determine the bit ++ * within the register. ++ */ ++ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31); ++ } ++ sunxi_gmac_hash_filter(chip->base, mc_filter[0], mc_filter[1]); ++ } ++ ++ /* Handle multiple unicast addresses (perfect filtering)*/ ++ if (netdev_uc_count(ndev) > 16) { ++ /* Switch to promiscuous mode is more than 8 addrs are required */ ++ value |= SUNXI_GMAC_FRAME_FILTER_PR; ++ } else { ++ int reg = 1; ++ struct netdev_hw_addr *ha; ++ ++ netdev_for_each_uc_addr(ha, ndev) { ++ sunxi_gmac_set_mac_addr_to_reg(chip->base, ha->addr, reg); ++ reg++; ++ } ++ } ++ ++#ifdef FRAME_FILTER_DEBUG ++ /* Enable Receive all mode (to debug filtering_fail errors) */ ++ value |= SUNXI_GMAC_FRAME_FILTER_RA; ++#endif ++ writel(value, chip->base + SUNXI_GMAC_RX_FRM_FLT); ++ spin_unlock_bh(&chip->universal_lock); ++} ++ ++static void sunxi_gmac_tx_timeout(struct net_device *ndev, unsigned int txqueue) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ sunxi_gmac_tx_err(chip); ++} ++ ++static int sunxi_gmac_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) ++{ ++ if (!netif_running(ndev)) ++ return -EINVAL; ++ ++ if (!ndev->phydev) ++ return -EINVAL; ++ ++ return phy_mii_ioctl(ndev->phydev, rq, cmd); ++} ++ ++/* Configuration changes (passed on by ifconfig) */ ++static int sunxi_gmac_config(struct net_device *ndev, struct ifmap *map) ++{ ++ if (ndev->flags & IFF_UP) /* can't act on a running interface */ ++ return -EBUSY; ++ ++ /* Don't allow changing the I/O address */ ++ if (map->base_addr != ndev->base_addr) { ++ netdev_err(ndev, "Error: Can't change I/O address\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ /* Don't allow changing the IRQ */ ++ if (map->irq != ndev->irq) { ++ netdev_err(ndev, "Error: Can't change IRQ number %d\n", ndev->irq); ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int sunxi_gmac_set_mac_address(struct net_device *ndev, void *p) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct sockaddr *addr = p; ++ ++ if (!is_valid_ether_addr(addr->sa_data)) { ++ netdev_err(ndev, "Error: Set error mac address\n"); ++ return -EADDRNOTAVAIL; ++ } ++ ++ eth_hw_addr_set(ndev, addr->sa_data); ++ sunxi_gmac_set_mac_addr_to_reg(chip->base, (unsigned char *)ndev->dev_addr, 0); ++ ++ return 0; ++} ++ ++static int sunxi_gmac_set_features(struct net_device *ndev, netdev_features_t features) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ if (features & NETIF_F_LOOPBACK && netif_running(ndev)) ++ sunxi_gmac_loopback(chip->base, 1); ++ else ++ sunxi_gmac_loopback(chip->base, 0); ++ ++ return 0; ++} ++ ++#if IS_ENABLED(CONFIG_NET_POLL_CONTROLLER) ++/* Polling receive - used by NETCONSOLE and other diagnostic tools ++ * to allow network I/O with interrupts disabled. ++ */ ++static void sunxi_gmac_poll_controller(struct net_device *dev) ++{ ++ disable_irq(dev->irq); ++ sunxi_gmac_interrupt(dev->irq, dev); ++ enable_irq(dev->irq); ++} ++#endif ++ ++static const struct net_device_ops sunxi_gmac_netdev_ops = { ++ .ndo_init = NULL, ++ .ndo_open = sunxi_gmac_open, ++ .ndo_start_xmit = sunxi_gmac_xmit, ++ .ndo_stop = sunxi_gmac_stop, ++ .ndo_change_mtu = sunxi_gmac_change_mtu, ++ .ndo_fix_features = sunxi_gmac_fix_features, ++ .ndo_set_rx_mode = sunxi_gmac_set_rx_mode, ++ .ndo_tx_timeout = sunxi_gmac_tx_timeout, ++ .ndo_do_ioctl = sunxi_gmac_ioctl, ++ .ndo_set_config = sunxi_gmac_config, ++#if IS_ENABLED(CONFIG_NET_POLL_CONTROLLER) ++ .ndo_poll_controller = sunxi_gmac_poll_controller, ++#endif ++ .ndo_set_mac_address = sunxi_gmac_set_mac_address, ++ .ndo_set_features = sunxi_gmac_set_features, ++}; ++ ++static int sunxi_gmac_check_if_running(struct net_device *ndev) ++{ ++ if (!netif_running(ndev)) ++ return -EBUSY; ++ return 0; ++} ++ ++static int sunxi_gmac_ethtool_get_sset_count(struct net_device *netdev, int sset) ++{ ++ int len; ++ ++ switch (sset) { ++ case ETH_SS_STATS: ++ len = 0; ++ return len; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++ ++/** ++ * sunxi_gmac_ethtool_getdrvinfo - Get various SUNXI GMAC driver information. ++ * @ndev: Pointer to net_device structure ++ * @ed: Pointer to ethtool_drvinfo structure ++ * ++ * This implements ethtool command for getting the driver information. ++ * Issue "ethtool -i ethX" under linux prompt to execute this function. ++ */ ++static void sunxi_gmac_ethtool_getdrvinfo(struct net_device *ndev, ++ struct ethtool_drvinfo *info) ++{ ++ strscpy(info->driver, "sunxi_gmac", sizeof(info->driver)); ++ ++ strcpy(info->version, SUNXI_GMAC_MODULE_VERSION); ++ info->fw_version[0] = '\0'; ++} ++ ++/** ++ * sunxi_gmac_ethool_get_pauseparam - Get the pause parameter setting for Tx/Rx. ++ * ++ * @ndev: Pointer to net_device structure ++ * @epause: Pointer to ethtool_pauseparam structure ++ * ++ * This implements ethtool command for getting sunxi_gmac ethernet pause frame ++ * setting. Issue "ethtool -a ethx" to execute this function. ++ */ ++static void sunxi_gmac_ethtool_get_pauseparam(struct net_device *ndev, ++ struct ethtool_pauseparam *epause) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ /* TODO: need to support autoneg */ ++ epause->tx_pause = sunxi_gmac_read_tx_flowctl(chip->base); ++ epause->rx_pause = sunxi_gmac_read_rx_flowctl(chip->base); ++} ++ ++/** ++ * sunxi_gmac_ethtool_set_pauseparam - Set device pause paramter(flow contrl) ++ * settings. ++ * @ndev: Pointer to net_device structure ++ * @epause: Pointer to ethtool_pauseparam structure ++ * ++ * This implements ethtool command for enabling flow control on Rx and Tx. ++ * Issue "ethtool -A ethx tx on|off" under linux prompt to execute this ++ * function. ++ * ++ */ ++static int sunxi_gmac_ethtool_set_pauseparam(struct net_device *ndev, ++ struct ethtool_pauseparam *epause) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ sunxi_gmac_write_tx_flowctl(chip->base, !!epause->tx_pause); ++ netdev_info(ndev, "Tx flowctrl %s\n", epause->tx_pause ? "ON" : "OFF"); ++ ++ sunxi_gmac_write_rx_flowctl(chip->base, !!epause->rx_pause); ++ netdev_info(ndev, "Rx flowctrl %s\n", epause->rx_pause ? "ON" : "OFF"); ++ ++ return 0; ++} ++ ++/** ++ * sunxi_gmac_ethtool_get_wol - Get device wake-on-lan settings. ++ * ++ * @ndev: Pointer to net_device structure ++ * @wol: Pointer to ethtool_wolinfo structure ++ * ++ * This implements ethtool command for get wake-on-lan settings. ++ * Issue "ethtool -s ethx wol p|u|m|b|a|g|s|d" under linux prompt to execute ++ * this function. ++ */ ++static void sunxi_gmac_ethtool_get_wol(struct net_device *ndev, ++ struct ethtool_wolinfo *wol) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ spin_lock_irq(&chip->universal_lock); ++ /* TODO: need to support wol */ ++ spin_unlock_irq(&chip->universal_lock); ++ ++ netdev_err(ndev, "Error: wakeup-on-lan func is not supported yet\n"); ++} ++ ++/** ++ * sunxi_gmac_ethtool_set_wol - set device wake-on-lan settings. ++ * ++ * @ndev: Pointer to net_device structure ++ * @wol: Pointer to ethtool_wolinfo structure ++ * ++ * This implements ethtool command for set wake-on-lan settings. ++ * Issue "ethtool -s ethx wol p|u|n|b|a|g|s|d" under linux prompt to execute ++ * this function. ++ */ ++static int sunxi_gmac_ethtool_set_wol(struct net_device *ndev, ++ struct ethtool_wolinfo *wol) ++{ ++ /* ++ * TODO: Wake-on-lane function need to be supported. ++ */ ++ ++ return 0; ++} ++ ++static int __sunxi_gmac_loopback_test(struct net_device *ndev) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct sk_buff *skb_tmp = NULL, *skb = NULL; ++ u8 *test_data = NULL; ++ u8 *loopback_test_rx_buf = chip->loopback_test_rx_buf; ++ u32 i, j; ++ ++ skb_tmp = alloc_skb(LOOPBACK_PKT_LEN, GFP_ATOMIC); ++ if (!skb_tmp) ++ return -ENOMEM; ++ ++ test_data = __skb_put(skb_tmp, LOOPBACK_PKT_LEN); ++ ++ memset(test_data, 0xFF, 2 * ETH_ALEN); ++ test_data[ETH_ALEN] = 0xFE; ++ test_data[2 * ETH_ALEN] = 0x08; ++ test_data[2 * ETH_ALEN + 1] = 0x0; ++ ++ for (i = ETH_HLEN; i < LOOPBACK_PKT_LEN; i++) ++ test_data[i] = i & 0xFF; ++ ++ skb_tmp->queue_mapping = 0; ++ skb_tmp->ip_summed = CHECKSUM_COMPLETE; ++ skb_tmp->dev = ndev; ++ ++ for (i = 0; i < LOOPBACK_DEFAULT_TIME; i++) { ++ chip->loopback_test_rx_idx = 0; ++ memset(loopback_test_rx_buf, 0, LOOPBACK_PKT_CNT * LOOPBACK_PKT_LEN); ++ ++ for (j = 0; j < LOOPBACK_PKT_CNT; j++) { ++ skb = pskb_copy(skb_tmp, GFP_ATOMIC); ++ if (!skb) { ++ dev_kfree_skb_any(skb_tmp); ++ netdev_err(ndev, "Error: Copy skb failed for loopback test\n"); ++ return -ENOMEM; ++ } ++ ++ /* mark index for every pkt */ ++ skb->data[LOOPBACK_PKT_LEN - 1] = j; ++ ++ /* xmit loopback skb */ ++ if (sunxi_gmac_xmit(skb, ndev)) { ++ dev_kfree_skb_any(skb); ++ dev_kfree_skb_any(skb_tmp); ++ netdev_err(ndev, "Error: Xmit pkt failed for loopback test\n"); ++ return -EBUSY; ++ } ++ } ++ ++ /* wait till all pkts received to RX buffer */ ++ msleep(200); ++ ++ for (j = 0; j < LOOPBACK_PKT_CNT; j++) { ++ /* compare loopback data */ ++ if (memcmp(loopback_test_rx_buf + j * LOOPBACK_PKT_LEN, ++ skb_tmp->data, LOOPBACK_PKT_LEN - 1) || ++ (*(loopback_test_rx_buf + j * LOOPBACK_PKT_LEN + ++ LOOPBACK_PKT_LEN - 1) != j)) { ++ dev_kfree_skb_any(skb_tmp); ++ netdev_err(ndev, "Error: Compare pkt failed in loopback test (index=0x%02x, data[%d]=0x%02x)\n", ++ j + i * LOOPBACK_PKT_CNT, ++ LOOPBACK_PKT_LEN - 1, ++ *(loopback_test_rx_buf + j * LOOPBACK_PKT_LEN + ++ LOOPBACK_PKT_LEN - 1)); ++ return -EIO; ++ } ++ } ++ } ++ ++ dev_kfree_skb_any(skb_tmp); ++ return 0; ++} ++ ++static int sunxi_gmac_loopback_test(struct net_device *ndev, u32 flags, ++ enum self_test_index *test_index) ++{ ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ u8 *loopback_test_rx_buf = NULL; ++ int err = 0; ++ ++ /* set loopback */ ++ if (!(flags & ETH_TEST_FL_EXTERNAL_LB)) { ++ *test_index = INTERNAL_LOOPBACK_TEST; ++ sunxi_gmac_loopback(chip->base, true); ++ } else { ++ *test_index = EXTERNAL_LOOPBACK_TEST; ++ err |= phy_loopback(ndev->phydev, true); ++ if (err) ++ goto out; ++ } ++ ++ /* only focus data part, so turn off the crc check */ ++ sunxi_gmac_crc(chip->base, false); ++ ++ loopback_test_rx_buf = vmalloc(LOOPBACK_PKT_CNT * LOOPBACK_PKT_LEN); ++ if (!loopback_test_rx_buf) { ++ err |= -ENOMEM; ++ } else { ++ chip->loopback_test_rx_buf = loopback_test_rx_buf; ++ chip->loopback_pkt_len = LOOPBACK_PKT_LEN; ++ chip->is_loopback_test = true; ++ err |= __sunxi_gmac_loopback_test(ndev); ++ chip->is_loopback_test = false; ++ msleep(100); ++ vfree(loopback_test_rx_buf); ++ chip->loopback_test_rx_buf = NULL; ++ } ++ ++ sunxi_gmac_crc(chip->base, true); ++out: ++ if (!(flags & ETH_TEST_FL_EXTERNAL_LB)) ++ sunxi_gmac_loopback(chip->base, false); ++ else ++ err |= phy_loopback(ndev->phydev, false); ++ ++ return err; ++} ++ ++static void sunxi_gmac_self_test(struct net_device *ndev, ++ struct ethtool_test *eth_test, u64 *data) ++{ ++ enum self_test_index test_index = 0; ++ int err; ++ ++ memset(data, 0, SELF_TEST_MAX * sizeof(u64)); ++ ++ if (!netif_running(ndev)) { ++ netdev_err(ndev, "Error: Do not support selftest when ndev is closed\n"); ++ eth_test->flags |= ETH_TEST_FL_FAILED; ++ return; ++ } ++ ++ netif_carrier_off(ndev); ++ netif_tx_disable(ndev); ++ ++ err = sunxi_gmac_loopback_test(ndev, eth_test->flags, &test_index); ++ if (err) { ++ eth_test->flags |= ETH_TEST_FL_FAILED; ++ data[test_index] = 1; /* 0:success, 1:fail */ ++ netdev_err(ndev, "Error: Loopback test failed\n"); ++ } ++ ++ netif_tx_wake_all_queues(ndev); ++ netif_carrier_on(ndev); ++} ++ ++static int sunxi_gmac_get_sset_count(struct net_device *ndev, int sset) ++{ ++ switch (sset) { ++ case ETH_SS_TEST: ++ return ARRAY_SIZE(sunxi_gmac_test_strings); ++ case ETH_SS_STATS: ++ return -EOPNOTSUPP; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void sunxi_gmac_get_strings(struct net_device *netdev, ++ u32 stringset, u8 *data) ++{ ++ switch (stringset) { ++ case ETH_SS_TEST: ++ memcpy(data, *sunxi_gmac_test_strings, sizeof(sunxi_gmac_test_strings)); ++ return; ++ case ETH_SS_STATS: ++ return; ++ default: ++ return; ++ } ++} ++ ++static const struct ethtool_ops sunxi_gmac_ethtool_ops = { ++ .begin = sunxi_gmac_check_if_running, ++ .get_link = ethtool_op_get_link, ++ .get_pauseparam = sunxi_gmac_ethtool_get_pauseparam, ++ .set_pauseparam = sunxi_gmac_ethtool_set_pauseparam, ++ .get_wol = sunxi_gmac_ethtool_get_wol, ++ .set_wol = sunxi_gmac_ethtool_set_wol, ++ .get_sset_count = sunxi_gmac_ethtool_get_sset_count, ++ .get_drvinfo = sunxi_gmac_ethtool_getdrvinfo, ++ .get_link_ksettings = phy_ethtool_get_link_ksettings, ++ .set_link_ksettings = phy_ethtool_set_link_ksettings, ++ .get_sset_count = sunxi_gmac_get_sset_count, ++ .get_strings = sunxi_gmac_get_strings, ++ .self_test = sunxi_gmac_self_test, ++}; ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++static int sunxi_gmac_ephy_v1_hardware_init(struct sunxi_gmac *chip) ++{ ++ int ret; ++ ++ ret = pwm_config(chip->ac300_pwm, PWM_DUTY_NS, PWM_PERIOD_NS); ++ if (ret) { ++ netdev_err(chip->ndev, "Error: Config ac300 pwm failed\n"); ++ return ret; ++ } ++ ++ ret = pwm_enable(chip->ac300_pwm); ++ if (ret) { ++ netdev_err(chip->ndev, "Error: Enable ac300 pwm failed\n"); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++static int sunxi_gmac_hardware_init(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ int ret; ++ ++ ret = sunxi_gmac_power_on(chip); ++ if (ret) { ++ netdev_err(ndev, "Error: Gmac power on failed\n"); ++ ret = -EINVAL; ++ goto power_on_err; ++ } ++ ++ ret = sunxi_gmac_clk_enable(chip); ++ if (ret) { ++ netdev_err(ndev, "Error: Clk enable is failed\n"); ++ ret = -EINVAL; ++ goto clk_enable_err; ++ } ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ ret = chip->ephy_ops->hardware_init(chip); ++ if (ret) { ++ netdev_err(ndev, "Error: ephy init failed\n"); ++ ret = -EINVAL; ++ goto ephy_init_err; ++ } ++ ++ return 0; ++ ++ephy_init_err: ++ sunxi_gmac_clk_disable(chip); ++#endif /* CONFIG_SUNXI55I_EPHY */ ++clk_enable_err: ++ sunxi_gmac_power_off(chip); ++power_on_err: ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++static void sunxi_gmac_ephy_v1_hardware_deinit(struct sunxi_gmac *chip) ++{ ++ pwm_disable(chip->ac300_pwm); ++} ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++static void sunxi_gmac_hardware_deinit(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ sunxi_gmac_power_off(chip); ++ ++ sunxi_gmac_clk_disable(chip); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ chip->ephy_ops->hardware_deinit(chip); ++#endif /* CONFIG_SUNXI55I_EPHY */ ++} ++ ++static void sunxi_gmac_parse_delay_maps(struct sunxi_gmac *chip) ++{ ++ struct platform_device *pdev = to_platform_device(chip->dev); ++ struct device_node *np = pdev->dev.of_node; ++ int ret, maps_cnt; ++ u32 *maps; ++ ++ maps_cnt = of_property_count_elems_of_size(np, "delay-maps", sizeof(u32)); ++ if (maps_cnt <= 0) { ++ dev_info(&pdev->dev, "Info: not found delay-maps in dts\n"); ++ return; ++ } ++ ++ maps = devm_kcalloc(&pdev->dev, maps_cnt, sizeof(u32), GFP_KERNEL); ++ if (!maps) ++ return; ++ ++ ret = of_property_read_u32_array(np, "delay-maps", maps, maps_cnt); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: failed to parse delay-maps\n"); ++ goto err_parse_maps; ++ } ++/* todo ++ const u8 array_size = 3; ++ u16 soc_ver; ++ int i; ++ ++ soc_ver = (u16)sunxi_get_soc_ver(); ++ for (i = 0; i < (maps_cnt / array_size); i++) { ++ if (soc_ver == maps[i * array_size]) { ++ chip->rx_delay = maps[i * array_size + 1]; ++ chip->tx_delay = maps[i * array_size + 2]; ++ dev_info(&pdev->dev, "Info: delay-maps overwrite delay parameters, rx-delay:%d, tx-delay:%d\n", ++ chip->rx_delay, chip->tx_delay); ++ } ++ } ++*/ ++err_parse_maps: ++ devm_kfree(&pdev->dev, maps); ++} ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++static int sunxi_gmac_ephy_v1_resource_get(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct device_node *np = pdev->dev.of_node; ++ int ret; ++ ++ ret = of_property_read_u32(np, "sunxi,pwm-channel", &chip->pwm_channel); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Get ac300 pwm failed\n"); ++ return -EINVAL; ++ } ++ ++ chip->ac300_pwm = pwm_request(chip->pwm_channel, NULL); ++ if (IS_ERR_OR_NULL(chip->ac300_pwm)) { ++ dev_err(&pdev->dev, "Error: Get ac300 pwm failed\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++static int sunxi_gmac_resource_get(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ struct device_node *np = pdev->dev.of_node; ++ struct resource *res; ++ struct cpumask mask; ++ int cpu; ++ phy_interface_t phy_mode; ++ ++ int ret; ++ ++ /* External phy is selected by default */ ++ chip->phy_type = SUNXI_EXTERNAL_PHY; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "Error: Get gmac memory failed\n"); ++ return -ENODEV; ++ } ++ ++ chip->base = devm_ioremap_resource(&pdev->dev, res); ++ if (!chip->base) { ++ dev_err(&pdev->dev, "Error: Gmac memory mapping failed\n"); ++ return -ENOMEM; ++ } ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ if (!res) { ++ dev_err(&pdev->dev, "Error: Get phy memory failed\n"); ++ return -ENODEV; ++ } ++ ++ chip->syscfg_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); ++ if (!chip->syscfg_base) { ++ dev_err(&pdev->dev, "Error: Phy memory mapping failed\n"); ++ return -ENOMEM; ++ } ++ ++ ndev->irq = platform_get_irq_byname(pdev, "gmacirq"); ++ if (ndev->irq < 0) { ++ dev_err(&pdev->dev, "Error: Gmac irq not found\n"); ++ return -ENXIO; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, ndev->irq, sunxi_gmac_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Could not request irq %d\n", ndev->irq); ++ return -EINVAL; ++ } ++ ++ ret = of_property_read_u32(np, "irq-affinity", &chip->irq_affinity); ++ if (ret) { ++ dev_dbg(&pdev->dev, "Info: Get irq-affinity failed, use default\n"); ++ } else { ++ for_each_online_cpu(cpu) { ++ if (chip->irq_affinity & BIT(cpu)) ++ cpumask_set_cpu(cpu, &mask); ++ } ++ irq_set_affinity(ndev->irq, &mask); ++ dev_info(&pdev->dev, "Info: Set irq affinity to cpu%d\n", cpumask_first(&mask)); ++ } ++ ++ chip->reset = devm_reset_control_get(&pdev->dev, NULL); ++ if (IS_ERR(chip->reset)) { ++ dev_err(&pdev->dev, "Error: Get gmac rst failed\n"); ++ return -EINVAL; ++ } ++ ++ chip->pinctrl = devm_pinctrl_get(&pdev->dev); ++ if (IS_ERR(chip->pinctrl)) { ++ dev_err(&pdev->dev, "Error: Get Pin failed\n"); ++ return -EIO; ++ } ++ ++ chip->gmac_clk = devm_clk_get(&pdev->dev, "gmac"); ++ if (!chip->gmac_clk) { ++ dev_err(&pdev->dev, "Error: Get gmac clock failed\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_get_phy_mode(np, &phy_mode); ++ if (!ret) { ++ chip->phy_interface = phy_mode; ++ if (chip->phy_interface != PHY_INTERFACE_MODE_RGMII && ++ chip->phy_interface != PHY_INTERFACE_MODE_RMII && ++ chip->phy_interface != PHY_INTERFACE_MODE_MII) { ++ dev_err(&pdev->dev, "Error: Get gmac phy interface failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ ret = of_property_read_u32(np, "tx-delay", &chip->tx_delay); ++ if (ret) { ++ dev_warn(&pdev->dev, "Warning: Get gmac tx-delay failed, use default 0\n"); ++ chip->tx_delay = 0; ++ } ++ ++ if (user_tx_delay >= 0 && user_tx_delay <= 7) { ++ chip->tx_delay = user_tx_delay; ++ dev_info(&pdev->dev, "Info: user tx-delay: %d\n", chip->tx_delay); ++ } else { ++ dev_info(&pdev->dev, "Info: dts tx-delay: %d\n", chip->tx_delay); ++ } ++ ++ ret = of_property_read_u32(np, "rx-delay", &chip->rx_delay); ++ if (ret) { ++ dev_warn(&pdev->dev, "Warning: Get gmac rx-delay failed, use default 0\n"); ++ chip->rx_delay = 0; ++ } ++ ++ if (user_rx_delay >= 0 && user_rx_delay <= 31) { ++ chip->rx_delay = user_rx_delay; ++ dev_info(&pdev->dev, "Info: user rx-delay: %d\n", chip->rx_delay); ++ } else { ++ dev_info(&pdev->dev, "Info: dts rx-delay: %d\n", chip->rx_delay); ++ } ++ ++ sunxi_gmac_parse_delay_maps(chip); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ chip->ac300_np = of_parse_phandle(np, "ac300-phy-handle", 0); ++ if (!chip->ac300_np) { ++ dev_err(&pdev->dev, "Error: Get gmac ac300-phy-handle failed\n"); ++ return -EINVAL; ++ } ++ ++ ret = chip->ephy_ops->resource_get(pdev); ++ if (ret) ++ return -EINVAL; ++ ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++ chip->phy_node = of_parse_phandle(np, "phy-handle", 0); ++ if (!chip->phy_node) { ++ dev_err(&pdev->dev, "Error: Get gmac phy-handle failed\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_property_read_u32(np, "sunxi,phy-clk-type", &chip->phy_clk_type); ++ if (ret) { ++ dev_warn(&pdev->dev, "Warning: Get gmac phy-clk-type failed, use default OSC or pwm\n"); ++ chip->phy_clk_type = SUNXI_PHY_USE_EXT_OSC; ++ }; ++ ++ if (chip->phy_clk_type == SUNXI_PHY_USE_CLK25M) { ++ chip->phy25m_clk = devm_clk_get(&pdev->dev, "phy25m"); ++ if (IS_ERR_OR_NULL(chip->phy25m_clk)) { ++ dev_err(&pdev->dev, "Error: Get phy25m clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ chip->gmac_supply = devm_regulator_get_optional(&pdev->dev, "gmac3v3"); ++ if (IS_ERR(chip->gmac_supply)) ++ netdev_err(ndev, "Error: Not found gmac3v3-supply\n"); ++ ++ /* ++ * Read mac-address from dts, ++ * it doesn't matter if it's missing ++ */ ++ of_get_ethdev_address(np, ndev); ++ ++ return 0; ++} ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++static void sunxi_gmac_ephy_v1_resource_put(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ pwm_free(chip->ac300_pwm); ++} ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++static void sunxi_gmac_resource_put(struct platform_device *pdev) ++{ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++ chip->ephy_ops->resource_put(pdev); ++#endif /* CONFIG_SUNXI55I_EPHY */ ++} ++ ++static void sunxi_gmac_sysfs_create(struct device *dev) ++{ ++ device_create_file(dev, &dev_attr_gphy_test); ++ device_create_file(dev, &dev_attr_mii_read); ++ device_create_file(dev, &dev_attr_mii_write); ++ device_create_file(dev, &dev_attr_loopback); ++ device_create_file(dev, &dev_attr_tx_delay); ++ device_create_file(dev, &dev_attr_rx_delay); ++ device_create_file(dev, &dev_attr_extra_tx_stats); ++ device_create_file(dev, &dev_attr_extra_rx_stats); ++} ++ ++static void sunxi_gmac_sysfs_destroy(struct device *dev) ++{ ++ device_remove_file(dev, &dev_attr_gphy_test); ++ device_remove_file(dev, &dev_attr_mii_read); ++ device_remove_file(dev, &dev_attr_mii_write); ++ device_remove_file(dev, &dev_attr_loopback); ++ device_remove_file(dev, &dev_attr_tx_delay); ++ device_remove_file(dev, &dev_attr_rx_delay); ++ device_remove_file(dev, &dev_attr_extra_tx_stats); ++ device_remove_file(dev, &dev_attr_extra_rx_stats); ++} ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++ ++#define GMAC_WRITE _IOWR('X', 1, unsigned int) ++#define GMAC_READ _IOWR('X', 2, unsigned int) ++ ++static int sunxi_gmac_fops_open(struct inode *inode, struct file *file) ++{ ++ struct miscdevice *mdev = file->private_data; ++ struct sunxi_gmac *chip = container_of(mdev, struct sunxi_gmac, mdev); ++ ++ file->private_data = chip; ++ ++ return 0; ++} ++ ++static int sunxi_gmac_fops_release(struct inode *inode, struct file *file) ++{ ++ return 0; ++} ++ ++static struct sk_buff *sunxi_gmac_skb_compose(struct sunxi_gmac *chip) ++{ ++ struct sk_buff *skb; ++ u8 *skb_data, *data = chip->metadata_buff; ++ u32 len = chip->ndev->mtu; ++ const u8 markbits = 4; ++ ++ skb = alloc_skb(len, GFP_KERNEL); ++ if (!skb) ++ return NULL; ++ ++ skb_data = __skb_put(skb, len); ++ ++ /* ++ * compose broadcast skb ++ * dest mac : FF-FF-FF-FF-FF-FF ++ * src mac : FE-FF-FF-FF-FF-FF ++ * protocal : 08-00 ++ * metadata mark: AA-AA-AA-AA ++ * */ ++ memset(skb_data, 0, len); ++ memset(skb_data, 0xFF, 2 * ETH_ALEN); ++ skb_data[ETH_ALEN] = 0xFE; ++ skb_data[2 * ETH_ALEN] = 0x08; ++ skb_data[2 * ETH_ALEN + 1] = 0x0; ++ skb_data = skb_data + (2 * ETH_ALEN + 2); ++ memset(skb_data, 0xAA, markbits); ++ ++ skb_data = skb_data + markbits; ++ memcpy(skb_data, data, len); ++ ++ skb->queue_mapping = 0; ++ skb->ip_summed = CHECKSUM_NONE; ++ skb->dev = chip->ndev; ++ ++ return skb; ++} ++ ++static long sunxi_gmac_fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct sunxi_gmac *chip = file->private_data; ++ struct sk_buff *skb; ++ int ret; ++ ++ memset(chip->metadata_buff, 0, chip->metadata_len); ++ switch (cmd) { ++ case GMAC_WRITE: ++ ret = copy_from_user(chip->metadata_buff, (void __user *)arg, chip->metadata_len); ++ if (ret) { ++ dev_err(chip->dev, "metadata copy from user err\n"); ++ return -EFAULT; ++ } ++ ++ skb = sunxi_gmac_skb_compose(chip); ++ if (!skb) ++ return -ENOMEM; ++ ++ if (sunxi_gmac_xmit(skb, skb->dev)) ++ return -EBUSY; ++ ++ break; ++ case GMAC_READ: ++ wait_for_completion(&chip->metadata_done); ++ ++ ret = copy_to_user((void __user *)arg, chip->metadata_buff, chip->metadata_len); ++ if (ret) { ++ dev_err(chip->dev, "metadata copy to user err\n"); ++ return -EFAULT; ++ } ++ break; ++ default: ++ ret = -EFAULT; ++ dev_err(chip->dev, "Unspported cmd\n"); ++ break; ++ } ++ ++ return ret; ++} ++ ++struct file_operations sunxi_gmac_fops = { ++ .owner = THIS_MODULE, ++ .open = sunxi_gmac_fops_open, ++ .release = sunxi_gmac_fops_release, ++ .unlocked_ioctl = sunxi_gmac_fops_ioctl, ++}; ++ ++static int sunxi_gmac_fops_init(struct sunxi_gmac *chip) ++{ ++ chip->mdev.parent = chip->dev; ++ chip->mdev.minor = MISC_DYNAMIC_MINOR; ++ chip->mdev.name = "gmac"; ++ chip->mdev.fops = &sunxi_gmac_fops; ++ ++ /* MTU includes 4 bytes of mark, so the maximum metadata is MTU - 4 bytes in length */ ++ chip->metadata_len = chip->ndev->mtu - 4; ++ ++ chip->metadata_buff = devm_kzalloc(chip->dev, (sizeof(char) * chip->metadata_len), GFP_KERNEL); ++ if (!chip->metadata_buff) ++ return -ENOMEM; ++ ++ init_completion(&chip->metadata_done); ++ ++ return misc_register(&chip->mdev); ++} ++ ++static void sunxi_gmac_fops_exit(struct sunxi_gmac *chip) ++{ ++ kfree(chip->metadata_buff); ++ misc_deregister(&chip->mdev); ++} ++ ++#endif /* CONFIG_SUNXI55I_GMAC_METADATA */ ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++static struct sunxi_gmac_ephy_ops sunxi_gmac_ephy_ops_v1 = { ++ .resource_get = sunxi_gmac_ephy_v1_resource_get, ++ .resource_put = sunxi_gmac_ephy_v1_resource_put, ++ .hardware_init = sunxi_gmac_ephy_v1_hardware_init, ++ .hardware_deinit = sunxi_gmac_ephy_v1_hardware_deinit, ++}; ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ ++static const struct of_device_id sunxi_gmac_of_match[] = { ++ {.compatible = "allwinner,sunxi-gmac",}, ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ {.compatible = "allwinner,sunxi-gmac-ephy-v1", .data = &sunxi_gmac_ephy_ops_v1, }, ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, sunxi_gmac_of_match); ++ ++/** ++ * sunxi_gmac_probe - GMAC device probe ++ * @pdev: The SUNXI GMAC device that we are removing ++ * ++ * Called when probing for GMAC device. We get details of instances and ++ * resource information from platform init and register a network device ++ * and allocate resources necessary for driver to perform ++ * ++ */ ++static int sunxi_gmac_probe(struct platform_device *pdev) ++{ ++ int ret; ++ struct net_device *ndev; ++ struct sunxi_gmac *chip; ++ const struct of_device_id *match; ++ ++ dev_dbg(&pdev->dev, "%s() BEGIN\n", __func__); ++ ++ match = of_match_device(sunxi_gmac_of_match, &pdev->dev); ++ if (!match) { ++ dev_err(&pdev->dev, "gmac probe match device failed\n"); ++ return -EINVAL; ++ } ++ ++ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); ++ pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; ++ ++ ndev = alloc_etherdev(sizeof(*chip)); ++ if (!ndev) { ++ dev_err(&pdev->dev, "Error: Allocate network device failed\n"); ++ ret = -ENOMEM; ++ goto alloc_etherdev_err; ++ } ++ SET_NETDEV_DEV(ndev, &pdev->dev); ++ ++ chip = netdev_priv(ndev); ++ platform_set_drvdata(pdev, ndev); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ chip->ephy_ops = (struct sunxi_gmac_ephy_ops *)match->data; ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ chip->ndev = ndev; ++ chip->dev = &pdev->dev; ++ ret = sunxi_gmac_resource_get(pdev); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Get gmac hardware resource failed\n"); ++ goto resource_get_err; ++ } ++ ++ ret = sunxi_gmac_hardware_init(pdev); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Init gmac hardware resource failed\n"); ++ goto hardware_init_err; ++ } ++ ++ /* ++ * setup the netdevice ++ * fillup netdevice base memory and ops ++ * fillup netdevice ethtool ops ++ */ ++ ether_setup(ndev); ++ ndev->netdev_ops = &sunxi_gmac_netdev_ops; ++ netdev_set_default_ethtool_ops(ndev, &sunxi_gmac_ethtool_ops); ++ ndev->base_addr = (unsigned long)chip->base; ++ ++ /* fillup netdevice features and flags */ ++ ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM | ++ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_GRO; ++ ndev->features |= ndev->hw_features; ++ ndev->hw_features |= NETIF_F_LOOPBACK; ++ ndev->priv_flags |= IFF_UNICAST_FLT; ++ ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ++ ndev->max_mtu = SUNXI_GMAC_MAX_MTU_SZ; ++ ++ /* add napi poll method */ ++ netif_napi_add(ndev, &chip->napi, sunxi_gmac_poll); ++ ++ spin_lock_init(&chip->universal_lock); ++ spin_lock_init(&chip->tx_lock); ++ ++ ret = register_netdev(ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Register %s failed\n", ndev->name); ++ goto register_err; ++ } ++ ++//todo #ifdef MODULE ++// get_custom_mac_address(0, "eth", mac_str); ++//#endif ++ /* Before open the device, the mac address should be set */ ++ sunxi_gmac_check_addr(ndev, mac_str); ++ ++ memcpy(ndev->dev_addr_shadow, ndev->dev_addr, 18); ++ ++ ret = sunxi_gmac_dma_desc_init(ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Init dma descriptor failed\n"); ++ goto init_dma_desc_err; ++ } ++ ++ sunxi_gmac_sysfs_create(&pdev->dev); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++ ret = sunxi_gmac_fops_init(chip); ++ if (ret) { ++ dev_err(&pdev->dev, "Error: Init gmac class failed\n"); ++ goto fops_init_err; ++ } ++#endif ++ ++ dev_dbg(&pdev->dev, "%s() SUCCESS\n", __func__); ++ ++ return 0; ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++fops_init_err: ++ sunxi_gmac_sysfs_destroy(&pdev->dev); ++#endif ++init_dma_desc_err: ++ unregister_netdev(ndev); ++register_err: ++ netif_napi_del(&chip->napi); ++ sunxi_gmac_hardware_deinit(pdev); ++hardware_init_err: ++ sunxi_gmac_resource_put(pdev); ++resource_get_err: ++ platform_set_drvdata(pdev, NULL); ++ free_netdev(ndev); ++alloc_etherdev_err: ++ return ret; ++} ++ ++static void sunxi_gmac_remove(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct sunxi_gmac *chip = netdev_priv(ndev); ++ ++#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA) ++ sunxi_gmac_fops_exit(chip); ++#endif ++ sunxi_gmac_sysfs_destroy(&pdev->dev); ++ sunxi_gmac_dma_desc_deinit(chip); ++ unregister_netdev(ndev); ++ netif_napi_del(&chip->napi); ++ sunxi_gmac_hardware_deinit(pdev); ++ sunxi_gmac_resource_put(pdev); ++ platform_set_drvdata(pdev, NULL); ++ free_netdev(ndev); ++} ++ ++static struct platform_driver sunxi_gmac_driver = { ++ .probe = sunxi_gmac_probe, ++ .remove = sunxi_gmac_remove, ++#if IS_ENABLED(CONFIG_SUNXI55I_EPHY) ++ .shutdown = sunxi_gmac_shutdown, ++#endif /* CONFIG_SUNXI55I_EPHY */ ++ .driver = { ++ .name = "sunxi-gmac", ++ .owner = THIS_MODULE, ++ .pm = &sunxi_gmac_pm_ops, ++ .of_match_table = sunxi_gmac_of_match, ++ }, ++}; ++module_platform_driver(sunxi_gmac_driver); ++ ++#ifndef MODULE ++static int __init sunxi_gmac_set_mac_addr(char *str) ++{ ++ char *p = str; ++ ++ /** ++ * mac address: xx:xx:xx:xx:xx:xx ++ * The reason why memcpy 18 bytes is ++ * the `/0`. ++ */ ++ if (str && strlen(str)) ++ memcpy(mac_str, p, MAC_ADDR_LEN); ++ ++ return 0; ++} ++/* TODO: When used more than one mac, ++ * parsing the mac address becomes a problem. ++ * Maybe use this way: mac0_addr=, mac1_addr= ++ */ ++__setup("mac0_addr=", sunxi_gmac_set_mac_addr); ++#endif /* MODULE */ ++ ++MODULE_DESCRIPTION("Allwinner GMAC driver"); ++MODULE_AUTHOR("xuminghui "); ++MODULE_AUTHOR("Piotr Oniszczuk "); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_VERSION(SUNXI_GMAC_MODULE_VERSION); +diff --git a/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c b/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c +new file mode 100644 +index 000000000000..c1cdb4bb537d +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c +@@ -0,0 +1,434 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Allwinner GMAC MDIO interface driver ++ * ++ * Copyright 2022 Allwinnertech ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++/* #define DEBUG */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SUNXI_MDIO_CONFIG 0x0 ++#define SUNXI_MDIO_DATA 0x4 ++ ++#define SUNXI_MDIO_BUSY 0x00000001 ++#define SUNXI_MDIO_WRITE 0x00000002 ++#define SUNXI_MDIO_PHY_MASK 0x0000FFC0 ++#define SUNXI_MDIO_CR_MASK 0x0000001 ++#define SUNXI_MDIO_CLK 0x00000008 ++#define SUNXI_MDIO_MDC_DIV 0x3 ++ ++/* bits 4 3 2 | AHB1 Clock | MDC Clock ++ * ------------------------------------------------------- ++ * 0 0 0 | 60 ~ 100 MHz | div-42 ++ * 0 0 1 | 100 ~ 150 MHz | div-62 ++ * 0 1 0 | 20 ~ 35 MHz | div-16 ++ * 0 1 1 | 35 ~ 60 MHz | div-26 ++ * 1 0 0 | 150 ~ 250 MHz | div-102 ++ * 1 0 1 | 250 ~ 300 MHz | div-124 ++ * 1 1 x | Reserved | ++ */ ++#define SUNXI_MDIO_MDC_DIV_RATIO_M 0x07 ++#define SUNXI_MDIO_MDC_DIV_RATIO_M_BIT 20 ++#define SUNXI_MDIO_PHY_ADDR 0x0001F000 ++#define SUNXI_MDIO_PHY_ADDR_OFFSET 12 ++#define SUNXI_MDIO_PHY_REG 0x000007F0 ++#define SUNXI_MDIO_PHY_REG_OFFSET 4 ++#define SUNXI_MDIO_RESET 0x4 ++#define SUNXI_MDIO_RESET_OFFSET 2 ++ ++#define SUNXI_MDIO_WR_TIMEOUT 10 /* ms */ ++ ++struct mii_reg_dump { ++ u32 addr; ++ u16 reg; ++ u16 value; ++}; ++ ++struct sunxi_mdio { ++ struct device *dev; ++ void __iomem *base; ++}; ++ ++struct mii_reg_dump mii_reg; ++/** ++ * sunxi_parse_read_str - parse the input string for write attri. ++ * @str: string to be parsed, eg: "0x00 0x01". ++ * @addr: store the reg address. eg: 0x00. ++ * @reg: store the expect value. eg: 0x01. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++static int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg) ++{ ++ char *ptr = str; ++ char *tstr = NULL; ++ int ret; ++ ++ /* ++ * Skip the leading whitespace, find the true split symbol. ++ * And it must be 'address value'. ++ */ ++ tstr = strim(str); ++ ptr = strchr(tstr, ' '); ++ if (!ptr) ++ return -EINVAL; ++ ++ /* ++ * Replaced split symbol with a %NUL-terminator temporary. ++ * Will be fixed at end. ++ */ ++ *ptr = '\0'; ++ ret = kstrtos16(tstr, 16, addr); ++ if (ret) ++ goto out; ++ ++ ret = kstrtos16(skip_spaces(ptr + 1), 16, reg); ++ ++out: ++ return ret; ++} ++ ++/** ++ * sunxi_parse_write_str - parse the input string for compare attri. ++ * @str: string to be parsed, eg: "0x00 0x11 0x11". ++ * @addr: store the address. eg: 0x00. ++ * @reg: store the reg. eg: 0x11. ++ * @val: store the value. eg: 0x11. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++static int sunxi_parse_write_str(char *str, u16 *addr, ++ u16 *reg, u16 *val) ++{ ++ u16 result_addr[3] = { 0 }; ++ char *ptr = str; ++ char *ptr2 = NULL; ++ int i, ret = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(result_addr); i++) { ++ ptr = skip_spaces(ptr); ++ ptr2 = strchr(ptr, ' '); ++ if (ptr2) ++ *ptr2 = '\0'; ++ ++ ret = kstrtou16(ptr, 16, &result_addr[i]); ++ ++ if (!ptr2 || ret) ++ break; ++ ++ ptr = ptr2 + 1; ++ } ++ ++ *addr = result_addr[0]; ++ *reg = result_addr[1]; ++ *val = result_addr[2]; ++ ++ return ret; ++} ++ ++/* ++ * Wait until any existing MII operation is complete ++ * Read 0 indicate finish in read or write operation ++ * Read 1 indicate busy ++ * */ ++static void sunxi_mdio_busy_wait(struct sunxi_mdio *chip) ++{ ++ unsigned long timeout = jiffies + msecs_to_jiffies(SUNXI_MDIO_WR_TIMEOUT); ++ u32 reg; ++ ++ do { ++ reg = readl(chip->base + SUNXI_MDIO_CONFIG); ++ ++ if ((reg & SUNXI_MDIO_BUSY) != 1) ++ break; ++ ++ } while (time_before(jiffies, timeout)); ++} ++ ++/** ++ * sunxi_mdio_read - GMAC MII bus read func ++ * @bus: mii bus struct ++ * @phyaddr: phy address ++ * @phyreg: phy register ++ * ++ * Called when phy_write is used. ++ * ++ * Returns reg value for specific phy register. ++ */ ++static int sunxi_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg) ++{ ++ unsigned int value = 0; ++ struct sunxi_mdio *chip = bus->priv; ++ ++ /* Mask the MDC_DIV_RATIO */ ++ value |= ((SUNXI_MDIO_MDC_DIV & SUNXI_MDIO_MDC_DIV_RATIO_M) << SUNXI_MDIO_MDC_DIV_RATIO_M_BIT); ++ value |= (((phyaddr << SUNXI_MDIO_PHY_ADDR_OFFSET) & (SUNXI_MDIO_PHY_ADDR)) | ++ ((phyreg << SUNXI_MDIO_PHY_REG_OFFSET) & (SUNXI_MDIO_PHY_REG)) | ++ SUNXI_MDIO_BUSY); ++ ++ writel(value, chip->base + SUNXI_MDIO_CONFIG); ++ ++ sunxi_mdio_busy_wait(chip); ++ ++ return (int)readl(chip->base + SUNXI_MDIO_DATA); ++} ++ ++/** ++ * sunxi_mdio_write - GMAC MII bus write func ++ * @bus: mii bus struct ++ * @phyaddr: phy address ++ * @phyreg: phy register ++ * @data: the value to be written to the register ++ * ++ * Called when phy_wirte is used. ++ * ++ * Returns 0 for a successful open, or appropriate error code ++ */ ++static int sunxi_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg, unsigned short data) ++{ ++ unsigned int value; ++ struct sunxi_mdio *chip = bus->priv; ++ ++ value = ((SUNXI_MDIO_MDC_DIV_RATIO_M << SUNXI_MDIO_MDC_DIV_RATIO_M_BIT) & readl(chip->base+ SUNXI_MDIO_CONFIG)) | ++ (SUNXI_MDIO_MDC_DIV << SUNXI_MDIO_MDC_DIV_RATIO_M_BIT); ++ value |= (((phyaddr << SUNXI_MDIO_PHY_ADDR_OFFSET) & (SUNXI_MDIO_PHY_ADDR)) | ++ ((phyreg << SUNXI_MDIO_PHY_REG_OFFSET) & (SUNXI_MDIO_PHY_REG))) | ++ SUNXI_MDIO_WRITE | SUNXI_MDIO_BUSY; ++ ++ sunxi_mdio_busy_wait(chip); ++ ++ /* Set the MII address register to write */ ++ writel(data, chip->base + SUNXI_MDIO_DATA); ++ writel(value, chip->base + SUNXI_MDIO_CONFIG); ++ ++ sunxi_mdio_busy_wait(chip); ++ ++ return 0; ++} ++ ++static int sunxi_mdio_reset(struct mii_bus *bus) ++{ ++ struct sunxi_mdio *chip = bus->priv; ++ ++ writel((SUNXI_MDIO_RESET << SUNXI_MDIO_RESET_OFFSET), chip->base + SUNXI_MDIO_CONFIG); ++ ++ sunxi_mdio_busy_wait(chip); ++ return 0; ++} ++ ++static ssize_t sunxi_mdio_read_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct mii_bus *bus = platform_get_drvdata(pdev); ++ ++ mii_reg.value = sunxi_mdio_read(bus, mii_reg.addr, mii_reg.reg); ++ return sprintf(buf, "ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ mii_reg.addr, mii_reg.reg, mii_reg.value); ++} ++ ++static ssize_t sunxi_mdio_read_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ int ret = 0; ++ u16 reg, addr; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!dev) { ++ pr_err("Argment is invalid\n"); ++ return count; ++ } ++ ++ ret = sunxi_parse_read_str(ptr, &addr, ®); ++ if (ret) ++ return ret; ++ ++ mii_reg.addr = addr; ++ mii_reg.reg = reg; ++ ++ return count; ++} ++ ++static ssize_t sunxi_mdio_write_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct mii_bus *bus = platform_get_drvdata(pdev); ++ u16 bef_val, aft_val; ++ ++ bef_val = sunxi_mdio_read(bus, mii_reg.addr, mii_reg.reg); ++ sunxi_mdio_write(bus, mii_reg.addr, mii_reg.reg, mii_reg.value); ++ aft_val = sunxi_mdio_read(bus, mii_reg.addr, mii_reg.reg); ++ return sprintf(buf, "before ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n" ++ "after ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ mii_reg.addr, mii_reg.reg, bef_val, ++ mii_reg.addr, mii_reg.reg, aft_val); ++} ++ ++static ssize_t sunxi_mdio_write_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ int ret = 0; ++ u16 reg, addr, val; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ ret = sunxi_parse_write_str(ptr, &addr, ®, &val); ++ if (ret) ++ return ret; ++ ++ mii_reg.reg = reg; ++ mii_reg.addr = addr; ++ mii_reg.value = val; ++ ++ return count; ++} ++ ++static DEVICE_ATTR(mii_read, 0664, sunxi_mdio_read_show, sunxi_mdio_read_store); ++static DEVICE_ATTR(mii_write, 0664, sunxi_mdio_write_show, sunxi_mdio_write_store); ++ ++static void sunxi_mdio_sysfs_create(struct device *dev) ++{ ++ device_create_file(dev, &dev_attr_mii_read); ++ device_create_file(dev, &dev_attr_mii_write); ++} ++ ++static void sunxi_mdio_sysfs_destroy(struct device *dev) ++{ ++ device_remove_file(dev, &dev_attr_mii_read); ++ device_remove_file(dev, &dev_attr_mii_write); ++} ++/** ++ * sunxi_mdio_probe - GMAC MII bus probe func ++ * ++ * sunxi mdio probe must run after sunxi emac probe, ++ * because mdio clk was enabled in emac driver. ++ */ ++static int sunxi_mdio_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct mii_bus *bus; ++ struct sunxi_mdio *chip; ++ int ret; ++#ifdef DEBUG ++ struct phy_device *phy; ++ int addr; ++#endif ++ ++ dev_dbg(dev, "%s() BEGIN\n", __func__); ++ ++ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); ++ if (!chip) ++ return -ENOMEM; ++ ++ bus = mdiobus_alloc_size(sizeof(*bus)); ++ if (!bus) { ++ dev_err(dev, "Error: alloc mii bus failed\n"); ++ return -ENOMEM; ++ } ++ ++ bus->name = dev_name(dev); ++ bus->read = sunxi_mdio_read; ++ bus->write = sunxi_mdio_write; ++ bus->reset = sunxi_mdio_reset; ++ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); ++ bus->parent = &pdev->dev; ++ bus->priv = chip; ++ ++ chip->dev = dev; ++ chip->base = of_iomap(np, 0); ++ if (IS_ERR(chip->base)) { ++ ret = PTR_ERR(chip->base); ++ goto iomap_err; ++ } ++ ++ ret = of_mdiobus_register(bus, np); ++ if (ret < 0) ++ goto mdio_register_err; ++ ++ platform_set_drvdata(pdev, bus); ++ ++ sunxi_mdio_sysfs_create(dev); ++ ++#ifdef DEBUG ++ /* scan and dump the bus */ ++ for (addr = 0; addr < PHY_MAX_ADDR; addr++) { ++ phy = mdiobus_get_phy(bus, addr); ++ if (phy) ++ dev_info(dev, "PHY ID: 0x%08x, ADDR: 0x%x, DEVICE: %s, DRIVER: %s\n", ++ phy->phy_id, addr, phydev_name(phy), ++ phy->drv ? phy->drv->name : "Generic PHY"); ++ } ++#endif ++ ++ dev_dbg(dev, "%s() SUCCESS\n", __func__); ++ return 0; ++ ++mdio_register_err: ++ iounmap(chip->base); ++iomap_err: ++ mdiobus_free(bus); ++ return ret; ++} ++ ++static void sunxi_mdio_remove(struct platform_device *pdev) ++{ ++ struct mii_bus *bus = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ struct sunxi_mdio *chip = bus->priv; ++ ++ sunxi_mdio_sysfs_destroy(dev); ++ mdiobus_unregister(bus); ++ iounmap(chip->base); ++ mdiobus_free(bus); ++} ++ ++static const struct of_device_id sunxi_mdio_dt_ids[] = { ++ { .compatible = "allwinner,sunxi-mdio" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, sunxi_mdio_dt_ids); ++ ++static struct platform_driver sunxi_mdio_driver = { ++ .probe = sunxi_mdio_probe, ++ .remove = sunxi_mdio_remove, ++ .driver = { ++ .name = "sunxi-mdio", ++ .of_match_table = sunxi_mdio_dt_ids, ++ }, ++}; ++ ++static int __init sunxi_mdio_init(void) ++{ ++ return platform_driver_register(&sunxi_mdio_driver); ++} ++late_initcall(sunxi_mdio_init); ++ ++static void __exit sunxi_mdio_exit(void) ++{ ++ platform_driver_unregister(&sunxi_mdio_driver); ++} ++module_exit(sunxi_mdio_exit); ++ ++MODULE_DESCRIPTION("Allwinner GMAC MDIO interface driver"); ++MODULE_AUTHOR("xuminghui "); ++MODULE_LICENSE("GPL"); ++MODULE_VERSION("1.0.1"); +diff --git a/drivers/net/ethernet/allwinner/sunxi-stmmac/Kconfig b/drivers/net/ethernet/allwinner/sunxi-stmmac/Kconfig +new file mode 100644 +index 000000000000..f31691719a7f +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-stmmac/Kconfig +@@ -0,0 +1,45 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++menu "Stmmac Drivers" ++ depends on AW_BSP ++ ++config SUN55I_GMAC200 ++ tristate "Allwinner A523 GMAC-200 driver" ++ depends on OF && (ARCH_SUNXI || COMPILE_TEST) ++ select STMMAC_ETH ++ select STMMAC_PLATFORM ++ select SUN55I_STMMAC ++ ++ help ++ Support for Allwinner A523 GMAC-200/GMAC-300 ethernet controllers. ++ ++ This selects Allwinner A523 SoC glue layer support for the ++ stmmac device driver. This driver is used for ++ GMAC-200/GMAC-300 ethernet controller. ++ ++if SUN55I_GMAC200 ++config SUN55I_STMMAC ++ tristate "Allwinner A523 GMAC-200 STMMAC support" ++ depends on OF && (ARCH_SUNXI || COMPILE_TEST) ++ help ++ Support stmmac device driver for Allwinner A523 GMAC-200/GMAC-300. ++ ++config AW_STMMAC_SELFTESTS ++ bool "Support for STMMAC Selftests" ++ depends on AW_STMMAC_ETH ++ select STMMAC_SELFTESTS ++ default n ++ help ++ This adds support for STMMAC Selftests using ethtool. Enable this ++ feature if you are facing problems with your HW and submit the test ++ results to the netdev Mailing List. ++ ++config SUN55I_STMMAC_UIO ++ tristate "Allwinner A523 GMAC-200 UIO ethernet controller" ++ default n ++ select UIO ++ help ++ Say M here if you want to use the sunxi-uio.ko for DPDK on A523. ++ ++endif ++ ++endmenu +diff --git a/drivers/net/ethernet/allwinner/sunxi-stmmac/Makefile b/drivers/net/ethernet/allwinner/sunxi-stmmac/Makefile +new file mode 100644 +index 000000000000..24809fc93961 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-stmmac/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ccflags-y += -I $(srctree)/drivers/net/ethernet/stmicro/stmmac/ ++ccflags-y += -DDYNAMIC_DEBUG_MODULE ++ ++obj-$(CONFIG_SUN55I_STMMAC) += sunxi-stmmac.o ++sunxi-stmmac-objs += dwmac-sunxi.o dwmac-sunxi-sysfs.o ++obj-$(CONFIG_SUN55I_UIO) += sunxi-uio.o +diff --git a/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.c b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.c +new file mode 100644 +index 000000000000..a7a296155ef2 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.c +@@ -0,0 +1,926 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner DWMAC driver sysfs. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++*/ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "stmmac.h" ++ ++#include "dwmac-sunxi-sysfs.h" ++ ++struct sunxi_dwmac_hdr { ++ __be32 version; ++ __be64 magic; ++ u8 id; ++ u32 tx; ++ u32 rx; ++} __packed; ++ ++#define SUNXI_DWMAC_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \ ++ sizeof(struct sunxi_dwmac_hdr)) ++#define SUNXI_DWMAC_PKT_MAGIC 0xdeadcafecafedeadULL ++#define SUNXI_DWMAC_TIMEOUT msecs_to_jiffies(2) ++ ++struct sunxi_dwmac_packet_attr { ++ u32 tx; ++ u32 rx; ++ unsigned char *src; ++ unsigned char *dst; ++ u32 ip_src; ++ u32 ip_dst; ++ int tcp; ++ int sport; ++ int dport; ++ int dont_wait; ++ int timeout; ++ int size; ++ int max_size; ++ u8 id; ++ u16 queue_mapping; ++ u64 timestamp; ++}; ++ ++struct sunxi_dwmac_loop_priv { ++ struct sunxi_dwmac_packet_attr *packet; ++ struct packet_type pt; ++ struct completion comp; ++ int ok; ++}; ++ ++struct sunxi_dwmac_calibrate { ++ u8 id; ++ u32 tx_delay; ++ u32 rx_delay; ++ u32 window_tx; ++ u32 window_rx; ++}; ++ ++/** ++ * sunxi_dwmac_parse_read_str - parse the input string for write attri. ++ * @str: string to be parsed, eg: "0x00 0x01". ++ * @addr: store the phy addr. eg: 0x00. ++ * @reg: store the reg addr. eg: 0x01. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++static int sunxi_dwmac_parse_read_str(char *str, u16 *addr, u16 *reg) ++{ ++ char *ptr = str; ++ char *tstr = NULL; ++ int ret; ++ ++ /** ++ * Skip the leading whitespace, find the true split symbol. ++ * And it must be 'address value'. ++ */ ++ tstr = strim(str); ++ ptr = strchr(tstr, ' '); ++ if (!ptr) ++ return -EINVAL; ++ ++ /** ++ * Replaced split symbol with a %NUL-terminator temporary. ++ * Will be fixed at end. ++ */ ++ *ptr = '\0'; ++ ret = kstrtos16(tstr, 16, addr); ++ if (ret) ++ goto out; ++ ++ ret = kstrtos16(skip_spaces(ptr + 1), 16, reg); ++ ++out: ++ return ret; ++} ++ ++/** ++ * sunxi_dwmac_parse_write_str - parse the input string for compare attri. ++ * @str: string to be parsed, eg: "0x00 0x11 0x11". ++ * @addr: store the phy addr. eg: 0x00. ++ * @reg: store the reg addr. eg: 0x11. ++ * @val: store the value. eg: 0x11. ++ * ++ * return 0 if success, otherwise failed. ++ */ ++static int sunxi_dwmac_parse_write_str(char *str, u16 *addr, ++ u16 *reg, u16 *val) ++{ ++ u16 result_addr[3] = { 0 }; ++ char *ptr = str; ++ char *ptr2 = NULL; ++ int i, ret; ++ ++ for (i = 0; i < ARRAY_SIZE(result_addr); i++) { ++ ptr = skip_spaces(ptr); ++ ptr2 = strchr(ptr, ' '); ++ if (ptr2) ++ *ptr2 = '\0'; ++ ++ ret = kstrtou16(ptr, 16, &result_addr[i]); ++ ++ if (!ptr2 || ret) ++ break; ++ ++ ptr = ptr2 + 1; ++ } ++ ++ *addr = result_addr[0]; ++ *reg = result_addr[1]; ++ *val = result_addr[2]; ++ ++ return ret; ++} ++ ++static struct sk_buff *sunxi_dwmac_get_skb(struct stmmac_priv *priv, ++ struct sunxi_dwmac_packet_attr *attr) ++{ ++ struct sk_buff *skb = NULL; ++ struct udphdr *uhdr = NULL; ++ struct tcphdr *thdr = NULL; ++ struct sunxi_dwmac_hdr *shdr; ++ struct ethhdr *ehdr; ++ struct iphdr *ihdr; ++ int iplen, size; ++ ++ size = attr->size + SUNXI_DWMAC_PKT_SIZE; ++ ++ if (attr->tcp) ++ size += sizeof(*thdr); ++ else ++ size += sizeof(*uhdr); ++ ++ if (attr->max_size && (attr->max_size > size)) ++ size = attr->max_size; ++ ++ skb = netdev_alloc_skb(priv->dev, size); ++ if (!skb) ++ return NULL; ++ ++ prefetchw(skb->data); ++ ++ ehdr = skb_push(skb, ETH_HLEN); ++ skb_reset_mac_header(skb); ++ ++ skb_set_network_header(skb, skb->len); ++ ihdr = skb_put(skb, sizeof(*ihdr)); ++ ++ skb_set_transport_header(skb, skb->len); ++ if (attr->tcp) ++ thdr = skb_put(skb, sizeof(*thdr)); ++ else ++ uhdr = skb_put(skb, sizeof(*uhdr)); ++ ++ eth_zero_addr(ehdr->h_source); ++ eth_zero_addr(ehdr->h_dest); ++ if (attr->src) ++ ether_addr_copy(ehdr->h_source, attr->src); ++ if (attr->dst) ++ ether_addr_copy(ehdr->h_dest, attr->dst); ++ ++ ehdr->h_proto = htons(ETH_P_IP); ++ ++ if (attr->tcp) { ++ thdr->source = htons(attr->sport); ++ thdr->dest = htons(attr->dport); ++ thdr->doff = sizeof(*thdr) / 4; ++ thdr->check = 0; ++ } else { ++ uhdr->source = htons(attr->sport); ++ uhdr->dest = htons(attr->dport); ++ uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); ++ if (attr->max_size) ++ uhdr->len = htons(attr->max_size - ++ (sizeof(*ihdr) + sizeof(*ehdr))); ++ uhdr->check = 0; ++ } ++ ++ ihdr->ihl = 5; ++ ihdr->ttl = 32; ++ ihdr->version = 4; ++ if (attr->tcp) ++ ihdr->protocol = IPPROTO_TCP; ++ else ++ ihdr->protocol = IPPROTO_UDP; ++ iplen = sizeof(*ihdr) + sizeof(*shdr) + attr->size; ++ if (attr->tcp) ++ iplen += sizeof(*thdr); ++ else ++ iplen += sizeof(*uhdr); ++ ++ if (attr->max_size) ++ iplen = attr->max_size - sizeof(*ehdr); ++ ++ ihdr->tot_len = htons(iplen); ++ ihdr->frag_off = 0; ++ ihdr->saddr = htonl(attr->ip_src); ++ ihdr->daddr = htonl(attr->ip_dst); ++ ihdr->tos = 0; ++ ihdr->id = 0; ++ ip_send_check(ihdr); ++ ++ shdr = skb_put(skb, sizeof(*shdr)); ++ shdr->version = 0; ++ shdr->magic = cpu_to_be64(SUNXI_DWMAC_PKT_MAGIC); ++ shdr->id = attr->id; ++ shdr->tx = attr->tx; ++ shdr->rx = attr->rx; ++ ++ if (attr->size) ++ skb_put(skb, attr->size); ++ if (attr->max_size && (attr->max_size > skb->len)) ++ skb_put(skb, attr->max_size - skb->len); ++ ++ skb->csum = 0; ++ skb->ip_summed = CHECKSUM_PARTIAL; ++ if (attr->tcp) { ++ thdr->check = ~tcp_v4_check(skb->len, ihdr->saddr, ihdr->daddr, 0); ++ skb->csum_start = skb_transport_header(skb) - skb->head; ++ skb->csum_offset = offsetof(struct tcphdr, check); ++ } else { ++ udp4_hwcsum(skb, ihdr->saddr, ihdr->daddr); ++ } ++ ++ skb->protocol = htons(ETH_P_IP); ++ skb->pkt_type = PACKET_HOST; ++ skb->dev = priv->dev; ++ ++ if (attr->timestamp) ++ skb->tstamp = ns_to_ktime(attr->timestamp); ++ ++ return skb; ++} ++ ++static int sunxi_dwmac_loopback_validate(struct sk_buff *skb, ++ struct net_device *ndev, ++ struct packet_type *pt, ++ struct net_device *orig_ndev) ++{ ++ struct sunxi_dwmac_loop_priv *tpriv = pt->af_packet_priv; ++ unsigned char *src = tpriv->packet->src; ++ unsigned char *dst = tpriv->packet->dst; ++ struct sunxi_dwmac_hdr *shdr; ++ struct ethhdr *ehdr; ++ struct udphdr *uhdr; ++ struct tcphdr *thdr; ++ struct iphdr *ihdr; ++ ++ skb = skb_unshare(skb, GFP_ATOMIC); ++ if (!skb) ++ goto out; ++ ++ if (skb_linearize(skb)) ++ goto out; ++ if (skb_headlen(skb) < (SUNXI_DWMAC_PKT_SIZE - ETH_HLEN)) ++ goto out; ++ ++ ehdr = (struct ethhdr *)skb_mac_header(skb); ++ if (dst) { ++ if (!ether_addr_equal_unaligned(ehdr->h_dest, dst)) ++ goto out; ++ } ++ if (src) { ++ if (!ether_addr_equal_unaligned(ehdr->h_source, src)) ++ goto out; ++ } ++ ++ ihdr = ip_hdr(skb); ++ ++ if (tpriv->packet->tcp) { ++ if (ihdr->protocol != IPPROTO_TCP) ++ goto out; ++ ++ thdr = (struct tcphdr *)((u8 *)ihdr + 4 * ihdr->ihl); ++ if (thdr->dest != htons(tpriv->packet->dport)) ++ goto out; ++ ++ shdr = (struct sunxi_dwmac_hdr *)((u8 *)thdr + sizeof(*thdr)); ++ } else { ++ if (ihdr->protocol != IPPROTO_UDP) ++ goto out; ++ ++ uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl); ++ if (uhdr->dest != htons(tpriv->packet->dport)) ++ goto out; ++ ++ shdr = (struct sunxi_dwmac_hdr *)((u8 *)uhdr + sizeof(*uhdr)); ++ } ++ ++ if (shdr->magic != cpu_to_be64(SUNXI_DWMAC_PKT_MAGIC)) ++ goto out; ++ if (tpriv->packet->id != shdr->id) ++ goto out; ++ if (tpriv->packet->tx != shdr->tx || tpriv->packet->rx != shdr->rx) ++ goto out; ++ ++ tpriv->ok = true; ++ complete(&tpriv->comp); ++out: ++ kfree_skb(skb); ++ return 0; ++} ++ ++static int sunxi_dwmac_loopback_run(struct stmmac_priv *priv, ++ struct sunxi_dwmac_packet_attr *attr) ++{ ++ struct sunxi_dwmac_loop_priv *tpriv; ++ struct sk_buff *skb = NULL; ++ int ret = 0; ++ ++ tpriv = kzalloc(sizeof(*tpriv), GFP_KERNEL); ++ if (!tpriv) ++ return -ENOMEM; ++ ++ tpriv->ok = false; ++ init_completion(&tpriv->comp); ++ ++ tpriv->pt.type = htons(ETH_P_IP); ++ tpriv->pt.func = sunxi_dwmac_loopback_validate; ++ tpriv->pt.dev = priv->dev; ++ tpriv->pt.af_packet_priv = tpriv; ++ tpriv->packet = attr; ++ ++ if (!attr->dont_wait) ++ dev_add_pack(&tpriv->pt); ++ ++ skb = sunxi_dwmac_get_skb(priv, attr); ++ if (!skb) { ++ ret = -ENOMEM; ++ goto cleanup; ++ } ++ ++ ret = dev_direct_xmit(skb, attr->queue_mapping); ++ if (ret) ++ goto cleanup; ++ ++ if (attr->dont_wait) ++ goto cleanup; ++ ++ if (!attr->timeout) ++ attr->timeout = SUNXI_DWMAC_TIMEOUT; ++ ++ wait_for_completion_timeout(&tpriv->comp, attr->timeout); ++ ret = tpriv->ok ? 0 : -ETIMEDOUT; ++ ++cleanup: ++ if (!attr->dont_wait) ++ dev_remove_pack(&tpriv->pt); ++ kfree(tpriv); ++ return ret; ++} ++ ++static int sunxi_dwmac_test_delaychain(struct sunxi_dwmac *chip, struct sunxi_dwmac_calibrate *cali) ++{ ++ struct net_device *ndev = dev_get_drvdata(chip->dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ unsigned char src[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; ++ unsigned char dst[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; ++ struct sunxi_dwmac_packet_attr attr = { }; ++ ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, cali->tx_delay); ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, cali->rx_delay); ++ ++ attr.src = src; ++ attr.dst = dst; ++ attr.tcp = true; ++ attr.queue_mapping = 0; ++ stmmac_get_systime(priv, priv->ptpaddr, &attr.timestamp); ++ attr.id = cali->id; ++ attr.tx = cali->tx_delay; ++ attr.rx = cali->rx_delay; ++ ++ return sunxi_dwmac_loopback_run(priv, &attr); ++} ++ ++static int sunxi_dwmac_calibrate_scan_window(struct sunxi_dwmac *chip, struct sunxi_dwmac_calibrate *cali) ++{ ++ struct net_device *ndev = dev_get_drvdata(chip->dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ char *buf, *ptr; ++ int tx_sum, rx_sum, count; ++ u32 tx, rx; ++ int ret = 0; ++ ++ buf = devm_kzalloc(chip->dev, PAGE_SIZE, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ netif_testing_on(ndev); ++ ++ ret = phy_loopback(priv->dev->phydev, true); ++ if (ret) ++ goto err; ++ ++ tx_sum = rx_sum = count = 0; ++ ++ for (tx = 0; tx < cali->window_tx; tx++) { ++ ptr = buf; ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "TX(0x%02x): ", tx); ++ for (rx = 0; rx < cali->window_rx; rx++) { ++ cali->id++; ++ cali->tx_delay = tx; ++ cali->rx_delay = rx; ++ if (sunxi_dwmac_test_delaychain(chip, cali) < 0) { ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "X"); ++ } else { ++ tx_sum += tx; ++ rx_sum += rx; ++ count++; ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "-"); ++ } ++ } ++ ptr += scnprintf(ptr, PAGE_SIZE - (ptr - buf), "\n"); ++ printk(buf); ++ } ++ ++ if (tx_sum && rx_sum && count) { ++ cali->tx_delay = tx_sum / count; ++ cali->rx_delay = rx_sum / count; ++ } else { ++ cali->tx_delay = cali->rx_delay = 0; ++ } ++ ++ phy_loopback(priv->dev->phydev, false); ++ ++err: ++ netif_testing_off(ndev); ++ devm_kfree(chip->dev, buf); ++ return ret; ++} ++ ++static ssize_t sunxi_dwmac_calibrate_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ struct phy_device *phydev = priv->dev->phydev; ++ struct sunxi_dwmac_calibrate *cali; ++ u32 old_tx, old_rx; ++ int ret; ++ ++ if (!ndev || !phydev) { ++ sunxi_err(chip->dev, "Not found netdevice or phy\n"); ++ return -EINVAL; ++ } ++ ++ if (!netif_carrier_ok(ndev) || !phydev->link) { ++ sunxi_err(chip->dev, "Netdevice or phy not link\n"); ++ return -EINVAL; ++ } ++ ++ if (phydev->speed < SPEED_1000) { ++ sunxi_err(chip->dev, "Speed %s no need calibrate\n", phy_speed_to_str(phydev->speed)); ++ return -EINVAL; ++ } ++ ++ cali = devm_kzalloc(dev, sizeof(*cali), GFP_KERNEL); ++ if (!cali) ++ return -ENOMEM; ++ ++ old_tx = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX); ++ old_rx = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX); ++ ++ cali->window_tx = chip->variant->tx_delay_max + 1; ++ cali->window_rx = chip->variant->rx_delay_max + 1; ++ ++ ret = sunxi_dwmac_calibrate_scan_window(chip, cali); ++ if (ret) { ++ sunxi_err(dev, "Calibrate scan window tx:%d rx:%d failed\n", cali->window_tx, cali->window_rx); ++ goto err; ++ } ++ ++ if (cali->tx_delay && cali->rx_delay) { ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, cali->tx_delay); ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, cali->rx_delay); ++ sunxi_info(chip->dev, "Calibrate suitable delay tx:%d rx:%d\n", cali->tx_delay, cali->rx_delay); ++ } else { ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, old_tx); ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, old_rx); ++ sunxi_warn(chip->dev, "Calibrate cannot find suitable delay\n"); ++ } ++ ++err: ++ devm_kfree(dev, cali); ++ return count; ++} ++ ++static int sunxi_dwmac_test_ecc_inject(struct stmmac_priv *priv, enum sunxi_dwmac_ecc_fifo_type type, u8 bit) ++{ ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ static const u32 wdata[2] = {0x55555555, 0x55555555}; ++ u32 rdata[ARRAY_SIZE(wdata)]; ++ u32 mtl_dbg_ctl, mtl_dpp_ecc_eic; ++ u32 val; ++ int i, ret = 0; ++ ++ mtl_dbg_ctl = readl(priv->ioaddr + MTL_DBG_CTL); ++ mtl_dpp_ecc_eic = readl(priv->ioaddr + MTL_DPP_ECC_EIC); ++ ++ mtl_dbg_ctl &= ~EIAEE; /* disable ecc error injection on address */ ++ mtl_dbg_ctl |= DBGMOD | FDBGEN; /* ecc debug mode enable */ ++ mtl_dpp_ecc_eic &= ~EIM; /* indicate error injection on data */ ++ mtl_dpp_ecc_eic |= FIELD_PREP(BLEI, 36); /* inject bit location is bit0 and bit36 */ ++ ++ /* ecc select inject bit */ ++ switch (bit) { ++ case 0: ++ mtl_dbg_ctl &= ~EIEE; /* ecc inject error disable */ ++ break; ++ case 1: ++ mtl_dbg_ctl &= ~EIEC; /* ecc inject insert 1-bit error */ ++ mtl_dbg_ctl |= EIEE; /* ecc inject error enable */ ++ break; ++ case 2: ++ mtl_dbg_ctl |= EIEC; /* ecc inject insert 2-bit error */ ++ mtl_dbg_ctl |= EIEE; /* ecc inject error enable */ ++ break; ++ default: ++ ret = -EINVAL; ++ sunxi_err(chip->dev, "test unsupport ecc inject bit %d\n", bit); ++ goto err; ++ } ++ ++ /* ecc select fifo */ ++ mtl_dbg_ctl &= ~FIFOSEL; ++ switch (type) { ++ case SUNXI_DWMAC_ECC_FIFO_TX: ++ mtl_dbg_ctl |= FIELD_PREP(FIFOSEL, 0x0); ++ break; ++ case SUNXI_DWMAC_ECC_FIFO_RX: ++ mtl_dbg_ctl |= FIELD_PREP(FIFOSEL, 0x3); ++ break; ++ default: ++ ret = -EINVAL; ++ sunxi_err(chip->dev, "test unsupport ecc inject fifo type %d\n", type); ++ goto err; ++ } ++ ++ writel(mtl_dpp_ecc_eic, priv->ioaddr + MTL_DPP_ECC_EIC); ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ++ /* write fifo debug data */ ++ mtl_dbg_ctl &= ~FIFORDEN; ++ mtl_dbg_ctl |= FIFOWREN; ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ writel(wdata[i], priv->ioaddr + MTL_FIFO_DEBUG_DATA); ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ret = readl_poll_timeout_atomic(priv->ioaddr + MTL_DBG_STS, val, !(val & FIFOBUSY), 10, 200000); ++ if (ret) { ++ sunxi_err(chip->dev, "timeout with ecc debug fifo write busy (%#x)\n", val); ++ goto err; ++ } ++ } ++ ++ /* read fifo debug data */ ++ mtl_dbg_ctl &= ~FIFOWREN; ++ mtl_dbg_ctl |= FIFORDEN; ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ret = readl_poll_timeout_atomic(priv->ioaddr + MTL_DBG_STS, val, !(val & FIFOBUSY), 10, 200000); ++ if (ret) { ++ sunxi_err(chip->dev, "test timeout with ecc debug fifo read busy (%#x)\n", val); ++ goto err; ++ } ++ rdata[i] = readl(priv->ioaddr + MTL_FIFO_DEBUG_DATA); ++ } ++ ++ /* compare data */ ++ switch (bit) { ++ case 0: ++ case 1: ++ /* for ecc error inject 0/1 bit, read should be same with write */ ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ if (rdata[i] != wdata[i]) { ++ ret = -EINVAL; ++ break; ++ } ++ } ++ break; ++ case 2: ++ /* for ecc error inject 2 bit, read should be different with write */ ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) { ++ if (rdata[i] == wdata[i]) { ++ ret = -EINVAL; ++ break; ++ } ++ } ++ break; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wdata); i++) ++ sunxi_info(chip->dev, "fifo %d write [%#x] -> read [%#x]\n", i, wdata[i], rdata[i]); ++ ++err: ++ /* ecc debug mode disable */ ++ mtl_dbg_ctl &= ~(EIEE | EIEC | FIFOWREN | FIFORDEN); ++ writel(mtl_dbg_ctl, priv->ioaddr + MTL_DBG_CTL); ++ ++ return ret; ++} ++ ++static ssize_t sunxi_dwmac_ecc_inject_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return scnprintf(buf, PAGE_SIZE, ++ "Usage:\n" ++ "echo \"[dir] [inject_bit]\" > ecc_inject\n\n" ++ "[dir] : 0(tx) 1(rx)\n" ++ "[inject_bit] : 0/1/2\n"); ++} ++ ++static ssize_t sunxi_dwmac_ecc_inject_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ struct phy_device *phydev = priv->dev->phydev; ++ static const char *dir_str[] = {"tx", "rx"}; ++ u16 dir, inject_bit; ++ u64 ret; ++ ++ if (!ndev || !phydev) { ++ sunxi_err(chip->dev, "netdevice or phy not found\n"); ++ return -EINVAL; ++ } ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(chip->dev, "netdevice is not running\n"); ++ return -EINVAL; ++ } ++ ++ if (!(chip->variant->flags & SUNXI_DWMAC_MEM_ECC)) { ++ sunxi_err(chip->dev, "ecc not support or enabled\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ ret = sunxi_dwmac_parse_read_str((char *)buf, &dir, &inject_bit); ++ if (ret) ++ return ret; ++ ++ switch (dir) { ++ case 0: ++ dir = SUNXI_DWMAC_ECC_FIFO_TX; ++ break; ++ case 1: ++ dir = SUNXI_DWMAC_ECC_FIFO_RX; ++ break; ++ default: ++ sunxi_err(chip->dev, "test unsupport ecc dir %d\n", dir); ++ return -EINVAL; ++ } ++ ++ netif_testing_on(ndev); ++ ++ /* ecc inject test */ ++ ret = sunxi_dwmac_test_ecc_inject(priv, dir, inject_bit); ++ if (ret) ++ sunxi_info(chip->dev, "test ecc %s inject %d bit : FAILED\n", dir_str[dir], inject_bit); ++ else ++ sunxi_info(chip->dev, "test ecc %s inject %d bit : PASS\n", dir_str[dir], inject_bit); ++ ++ netif_testing_off(ndev); ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_tx_delay_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u32 delay = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX); ++ ++ return scnprintf(buf, PAGE_SIZE, ++ "Usage:\n" ++ "echo [0~%d] > tx_delay\n\n" ++ "now tx_delay: %d\n", ++ chip->variant->tx_delay_max, delay); ++} ++ ++static ssize_t sunxi_dwmac_tx_delay_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u32 delay; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = kstrtou32(buf, 0, &delay); ++ if (ret) ++ return ret; ++ ++ if (delay > chip->variant->tx_delay_max) { ++ sunxi_err(dev, "Tx_delay exceed max %d\n", chip->variant->tx_delay_max); ++ return -EINVAL; ++ } ++ ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, delay); ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_rx_delay_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u32 delay = chip->variant->get_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX); ++ ++ return scnprintf(buf, PAGE_SIZE, ++ "Usage:\n" ++ "echo [0~%d] > rx_delay\n\n" ++ "now rx_delay: %d\n", ++ chip->variant->rx_delay_max, delay); ++} ++ ++static ssize_t sunxi_dwmac_rx_delay_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u32 delay; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = kstrtou32(buf, 0, &delay); ++ if (ret) ++ return ret; ++ ++ if (delay > chip->variant->rx_delay_max) { ++ sunxi_err(dev, "Rx_delay exceed max %d\n", chip->variant->rx_delay_max); ++ return -EINVAL; ++ } ++ ++ chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, delay); ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_mii_read_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return 0; ++ } ++ ++ chip->mii_reg.value = mdiobus_read(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg); ++ return sprintf(buf, "ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ chip->mii_reg.addr, chip->mii_reg.reg, chip->mii_reg.value); ++} ++ ++static ssize_t sunxi_dwmac_mii_read_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u16 reg, addr; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = sunxi_dwmac_parse_read_str(ptr, &addr, ®); ++ if (ret) ++ return ret; ++ ++ chip->mii_reg.addr = addr; ++ chip->mii_reg.reg = reg; ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_mii_write_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u16 bef_val, aft_val; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return 0; ++ } ++ ++ bef_val = mdiobus_read(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg); ++ mdiobus_write(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg, chip->mii_reg.value); ++ aft_val = mdiobus_read(priv->mii, chip->mii_reg.addr, chip->mii_reg.reg); ++ return sprintf(buf, "before ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n" ++ "after ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n", ++ chip->mii_reg.addr, chip->mii_reg.reg, bef_val, ++ chip->mii_reg.addr, chip->mii_reg.reg, aft_val); ++} ++ ++static ssize_t sunxi_dwmac_mii_write_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ u16 reg, addr, val; ++ char *ptr; ++ ++ ptr = (char *)buf; ++ ++ if (!netif_running(ndev)) { ++ sunxi_err(dev, "Eth is not running\n"); ++ return count; ++ } ++ ++ ret = sunxi_dwmac_parse_write_str(ptr, &addr, ®, &val); ++ if (ret) ++ return ret; ++ ++ chip->mii_reg.reg = reg; ++ chip->mii_reg.addr = addr; ++ chip->mii_reg.value = val; ++ ++ return count; ++} ++ ++static ssize_t sunxi_dwmac_version_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ u16 ip_tag, ip_vrm; ++ ssize_t count = 0; ++ ++ if (chip->variant->get_version) { ++ chip->variant->get_version(chip, &ip_tag, &ip_vrm); ++ count = sprintf(buf, "IP TAG: %x\nIP VRM: %x\n", ip_tag, ip_vrm); ++ } ++ ++ return count; ++} ++ ++static struct device_attribute sunxi_dwmac_tool_attr[] = { ++ __ATTR(calibrate, 0220, NULL, sunxi_dwmac_calibrate_store), ++ __ATTR(rx_delay, 0664, sunxi_dwmac_rx_delay_show, sunxi_dwmac_rx_delay_store), ++ __ATTR(tx_delay, 0664, sunxi_dwmac_tx_delay_show, sunxi_dwmac_tx_delay_store), ++ __ATTR(mii_read, 0664, sunxi_dwmac_mii_read_show, sunxi_dwmac_mii_read_store), ++ __ATTR(mii_write, 0664, sunxi_dwmac_mii_write_show, sunxi_dwmac_mii_write_store), ++ __ATTR(ecc_inject, 0664, sunxi_dwmac_ecc_inject_show, sunxi_dwmac_ecc_inject_store), ++ __ATTR(version, 0444, sunxi_dwmac_version_show, NULL), ++}; ++ ++void sunxi_dwmac_sysfs_init(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(sunxi_dwmac_tool_attr); i++) ++ device_create_file(dev, &sunxi_dwmac_tool_attr[i]); ++} ++ ++void sunxi_dwmac_sysfs_exit(struct device *dev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(sunxi_dwmac_tool_attr); i++) ++ device_remove_file(dev, &sunxi_dwmac_tool_attr[i]); ++} +diff --git a/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.h b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.h +new file mode 100644 +index 000000000000..9451618c9ab5 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi-sysfs.h +@@ -0,0 +1,19 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* ++* Allwinner DWMAC driver sysfs haeder. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++*/ ++ ++#ifndef _DWMAC_SUNXI_SYSFS_H_ ++#define _DWMAC_SUNXI_SYSFS_H_ ++ ++#include "dwmac-sunxi.h" ++ ++void sunxi_dwmac_sysfs_init(struct device *dev); ++void sunxi_dwmac_sysfs_exit(struct device *dev); ++ ++#endif /* _DWMAC_SUNXI_SYSFS_H_ */ +diff --git a/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.c b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.c +new file mode 100644 +index 000000000000..1af5d0b93e08 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.c +@@ -0,0 +1,869 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner DWMAC driver. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++* This file is licensed under the terms of the GNU General Public ++* License version 2. This program is licensed "as is" without any ++* warranty of any kind, whether express or implied. ++*/ ++#define SUNXI_MODNAME "stmmac" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stmmac.h" ++#include "stmmac_platform.h" ++ ++#include "dwmac-sunxi.h" ++ ++#define DWMAC_MODULE_VERSION "0.3.0" ++ ++#define MAC_ADDR_LEN 18 ++#define SUNXI_DWMAC_MAC_ADDRESS "00:00:00:00:00:00" ++#define MAC_IRQ_NAME 8 ++ ++static char mac_str[MAC_ADDR_LEN] = SUNXI_DWMAC_MAC_ADDRESS; ++module_param_string(mac_str, mac_str, MAC_ADDR_LEN, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)"); ++ ++#ifdef MODULE ++extern int get_custom_mac_address(int fmt, char *name, char *addr); ++#endif ++ ++static int sunxi_dwmac200_set_syscon(struct sunxi_dwmac *chip) ++{ ++ u32 reg_val = 0; ++ ++ /* Clear interface mode bits */ ++ reg_val &= ~(SUNXI_DWMAC200_SYSCON_ETCS | SUNXI_DWMAC200_SYSCON_EPIT); ++ if (chip->variant->interface & PHY_INTERFACE_MODE_RMII) ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_RMII_EN; ++ ++ switch (chip->interface) { ++ case PHY_INTERFACE_MODE_MII: ++ /* default */ ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ case PHY_INTERFACE_MODE_RGMII_ID: ++ case PHY_INTERFACE_MODE_RGMII_RXID: ++ case PHY_INTERFACE_MODE_RGMII_TXID: ++ reg_val |= SUNXI_DWMAC200_SYSCON_EPIT; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC200_SYSCON_ETCS, ++ chip->rgmii_clk_ext ? SUNXI_DWMAC_ETCS_EXT_GMII : SUNXI_DWMAC_ETCS_INT_GMII); ++ if (chip->rgmii_clk_ext) ++ sunxi_info(chip->dev, "RGMII use external transmit clock\n"); ++ else ++ sunxi_info(chip->dev, "RGMII use internal transmit clock\n"); ++ break; ++ case PHY_INTERFACE_MODE_RMII: ++ reg_val |= SUNXI_DWMAC200_SYSCON_RMII_EN; ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_ETCS; ++ break; ++ default: ++ sunxi_err(chip->dev, "Unsupported interface mode: %s", phy_modes(chip->interface)); ++ return -EINVAL; ++ } ++ ++ writel(reg_val, chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ return 0; ++} ++ ++static int sunxi_dwmac200_set_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir, u32 delay) ++{ ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ int ret = -EINVAL; ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ if (delay <= chip->variant->tx_delay_max) { ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_ETXDC; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC200_SYSCON_ETXDC, delay); ++ ret = 0; ++ } ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ if (delay <= chip->variant->rx_delay_max) { ++ reg_val &= ~SUNXI_DWMAC200_SYSCON_ERXDC; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC200_SYSCON_ERXDC, delay); ++ ret = 0; ++ } ++ break; ++ } ++ ++ if (!ret) ++ writel(reg_val, chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ ++ return ret; ++} ++ ++static u32 sunxi_dwmac200_get_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir) ++{ ++ u32 delay = 0; ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC200_SYSCON_REG); ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ delay = FIELD_GET(SUNXI_DWMAC200_SYSCON_ETXDC, reg_val); ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ delay = FIELD_GET(SUNXI_DWMAC200_SYSCON_ERXDC, reg_val); ++ break; ++ default: ++ sunxi_err(chip->dev, "Unknow delaychain dir %d\n", dir); ++ } ++ ++ return delay; ++} ++ ++static int sunxi_dwmac210_set_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir, u32 delay) ++{ ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC210_CFG_REG); ++ int ret = -EINVAL; ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ if (delay <= chip->variant->tx_delay_max) { ++ reg_val &= ~(SUNXI_DWMAC210_CFG_ETXDC_H | SUNXI_DWMAC210_CFG_ETXDC_L); ++ reg_val |= FIELD_PREP(SUNXI_DWMAC210_CFG_ETXDC_H, delay >> 3); ++ reg_val |= FIELD_PREP(SUNXI_DWMAC210_CFG_ETXDC_L, delay); ++ ret = 0; ++ } ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ if (delay <= chip->variant->rx_delay_max) { ++ reg_val &= ~SUNXI_DWMAC210_CFG_ERXDC; ++ reg_val |= FIELD_PREP(SUNXI_DWMAC210_CFG_ERXDC, delay); ++ ret = 0; ++ } ++ break; ++ } ++ ++ if (!ret) ++ writel(reg_val, chip->syscfg_base + SUNXI_DWMAC210_CFG_REG); ++ ++ return ret; ++} ++ ++static u32 sunxi_dwmac210_get_delaychain(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir) ++{ ++ u32 delay = 0; ++ u32 tx_l, tx_h; ++ u32 reg_val = readl(chip->syscfg_base + SUNXI_DWMAC210_CFG_REG); ++ ++ switch (dir) { ++ case SUNXI_DWMAC_DELAYCHAIN_TX: ++ tx_h = FIELD_GET(SUNXI_DWMAC210_CFG_ETXDC_H, reg_val); ++ tx_l = FIELD_GET(SUNXI_DWMAC210_CFG_ETXDC_L, reg_val); ++ delay = (tx_h << 3 | tx_l); ++ break; ++ case SUNXI_DWMAC_DELAYCHAIN_RX: ++ delay = FIELD_GET(SUNXI_DWMAC210_CFG_ERXDC, reg_val); ++ break; ++ } ++ ++ return delay; ++} ++ ++static int sunxi_dwmac110_get_version(struct sunxi_dwmac *chip, u16 *ip_tag, u16 *ip_vrm) ++{ ++ u32 reg_val; ++ ++ if (!ip_tag || !ip_vrm) ++ return -EINVAL; ++ ++ reg_val = readl(chip->syscfg_base + SUNXI_DWMAC110_VERSION_REG); ++ *ip_tag = FIELD_GET(SUNXI_DWMAC110_VERSION_IP_TAG, reg_val); ++ *ip_vrm = FIELD_GET(SUNXI_DWMAC110_VERSION_IP_VRM, reg_val); ++ return 0; ++} ++ ++static int sunxi_dwmac_power_on(struct sunxi_dwmac *chip) ++{ ++ int ret; ++ ++ /* set dwmac pin bank voltage to 3.3v */ ++ if (!IS_ERR(chip->dwmac3v3_supply)) { ++ ret = regulator_set_voltage(chip->dwmac3v3_supply, 3300000, 3300000); ++ if (ret) { ++ sunxi_err(chip->dev, "Set dwmac3v3-supply voltage 3300000 failed %d\n", ret); ++ goto err_dwmac3v3; ++ } ++ ++ ret = regulator_enable(chip->dwmac3v3_supply); ++ if (ret) { ++ sunxi_err(chip->dev, "Enable dwmac3v3-supply failed %d\n", ret); ++ goto err_dwmac3v3; ++ } ++ } ++ ++ /* set phy voltage to 3.3v */ ++ if (!IS_ERR(chip->phy3v3_supply)) { ++ ret = regulator_set_voltage(chip->phy3v3_supply, 3300000, 3300000); ++ if (ret) { ++ sunxi_err(chip->dev, "Set phy3v3-supply voltage 3300000 failed %d\n", ret); ++ goto err_phy3v3; ++ } ++ ++ ret = regulator_enable(chip->phy3v3_supply); ++ if (ret) { ++ sunxi_err(chip->dev, "Enable phy3v3-supply failed\n"); ++ goto err_phy3v3; ++ } ++ } ++ ++ return 0; ++ ++err_phy3v3: ++ regulator_disable(chip->dwmac3v3_supply); ++err_dwmac3v3: ++ return ret; ++} ++ ++static void sunxi_dwmac_power_off(struct sunxi_dwmac *chip) ++{ ++ if (!IS_ERR(chip->phy3v3_supply)) ++ regulator_disable(chip->phy3v3_supply); ++ if (!IS_ERR(chip->dwmac3v3_supply)) ++ regulator_disable(chip->dwmac3v3_supply); ++} ++ ++static int sunxi_dwmac_clk_init(struct sunxi_dwmac *chip) ++{ ++ int ret; ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) ++ reset_control_deassert(chip->hsi_rst); ++ reset_control_deassert(chip->ahb_rst); ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ ret = clk_prepare_enable(chip->hsi_ahb); ++ if (ret) { ++ sunxi_err(chip->dev, "enable hsi_ahb failed\n"); ++ goto err_ahb; ++ } ++ ret = clk_prepare_enable(chip->hsi_axi); ++ if (ret) { ++ sunxi_err(chip->dev, "enable hsi_axi failed\n"); ++ goto err_axi; ++ } ++ } ++ ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) { ++ ret = clk_prepare_enable(chip->nsi_clk); ++ if (ret) { ++ sunxi_err(chip->dev, "enable nsi clk failed\n"); ++ goto err_nsi; ++ } ++ } ++ ++ if (chip->soc_phy_clk_en) { ++ ret = clk_prepare_enable(chip->phy_clk); ++ if (ret) { ++ sunxi_err(chip->dev, "Enable phy clk failed\n"); ++ goto err_phy; ++ } ++ } ++ ++ return 0; ++ ++err_phy: ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) ++ clk_disable_unprepare(chip->nsi_clk); ++err_nsi: ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ clk_disable_unprepare(chip->hsi_axi); ++err_axi: ++ clk_disable_unprepare(chip->hsi_ahb); ++ } ++err_ahb: ++ reset_control_assert(chip->ahb_rst); ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) ++ reset_control_assert(chip->hsi_rst); ++ return ret; ++} ++ ++static void sunxi_dwmac_clk_exit(struct sunxi_dwmac *chip) ++{ ++ if (chip->soc_phy_clk_en) ++ clk_disable_unprepare(chip->phy_clk); ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) ++ clk_disable_unprepare(chip->nsi_clk); ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ clk_disable_unprepare(chip->hsi_axi); ++ clk_disable_unprepare(chip->hsi_ahb); ++ } ++ reset_control_assert(chip->ahb_rst); ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) ++ reset_control_assert(chip->hsi_rst); ++} ++ ++static int sunxi_dwmac_hw_init(struct sunxi_dwmac *chip) ++{ ++ int ret; ++ ++ ret = chip->variant->set_syscon(chip); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Set syscon failed\n"); ++ goto err; ++ } ++ ++ ret = chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_TX, chip->tx_delay); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Invalid TX clock delay: %d\n", chip->tx_delay); ++ goto err; ++ } ++ ++ ret = chip->variant->set_delaychain(chip, SUNXI_DWMAC_DELAYCHAIN_RX, chip->rx_delay); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Invalid RX clock delay: %d\n", chip->rx_delay); ++ goto err; ++ } ++ ++err: ++ return ret; ++} ++ ++static void sunxi_dwmac_hw_exit(struct sunxi_dwmac *chip) ++{ ++ writel(0, chip->syscfg_base); ++} ++ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++static int sunxi_dwmac_ecc_init(struct sunxi_dwmac *chip) ++{ ++ struct net_device *ndev = dev_get_drvdata(chip->dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct plat_stmmacenet_data *plat_dat = priv->plat; ++ ++ plat_dat->safety_feat_cfg = devm_kzalloc(chip->dev, sizeof(*plat_dat->safety_feat_cfg), GFP_KERNEL); ++ if (!plat_dat->safety_feat_cfg) ++ return -ENOMEM; ++ ++ plat_dat->safety_feat_cfg->tsoee = 0; /* TSO memory ECC Disabled */ ++ plat_dat->safety_feat_cfg->mrxpee = 0; /* MTL Rx Parser ECC Disabled */ ++ plat_dat->safety_feat_cfg->mestee = 0; /* MTL EST ECC Disabled */ ++ plat_dat->safety_feat_cfg->mrxee = 1; /* MTL Rx FIFO ECC Enable */ ++ plat_dat->safety_feat_cfg->mtxee = 1; /* MTL Tx FIFO ECC Enable */ ++ plat_dat->safety_feat_cfg->epsi = 0; /* Not Enable Parity on Slave Interface port */ ++ plat_dat->safety_feat_cfg->edpp = 1; /* Enable Data path Parity Protection */ ++ plat_dat->safety_feat_cfg->prtyen = 1; /* Enable FSM parity feature */ ++ plat_dat->safety_feat_cfg->tmouten = 1; /* Enable FSM timeout feature */ ++ ++ return 0; ++} ++#endif ++ ++static int sunxi_dwmac_init(struct platform_device *pdev, void *priv) ++{ ++ struct sunxi_dwmac *chip = priv; ++ int ret; ++ ++ ret = sunxi_dwmac_power_on(chip); ++ if (ret) { ++ sunxi_err(&pdev->dev, "Power on dwmac failed\n"); ++ return ret; ++ } ++ ++ ret = sunxi_dwmac_clk_init(chip); ++ if (ret) { ++ sunxi_err(&pdev->dev, "Clk init dwmac failed\n"); ++ goto err_clk; ++ } ++ ++ ret = sunxi_dwmac_hw_init(chip); ++ if (ret) ++ sunxi_warn(&pdev->dev, "Hw init dwmac failed\n"); ++ ++ return 0; ++ ++err_clk: ++ sunxi_dwmac_power_off(chip); ++ return ret; ++} ++ ++static void sunxi_dwmac_exit(struct platform_device *pdev, void *priv) ++{ ++ struct sunxi_dwmac *chip = priv; ++ ++ sunxi_dwmac_hw_exit(chip); ++ sunxi_dwmac_clk_exit(chip); ++ sunxi_dwmac_power_off(chip); ++} ++ ++static void sunxi_dwmac_parse_delay_maps(struct sunxi_dwmac *chip) ++{ ++ struct platform_device *pdev = to_platform_device(chip->dev); ++ struct device_node *np = pdev->dev.of_node; ++ int ret, maps_cnt, i; ++ const u8 array_size = 3; ++ u32 *maps; ++ u16 soc_ver; ++ ++ maps_cnt = of_property_count_elems_of_size(np, "delay-maps", sizeof(u32)); ++ if (maps_cnt <= 0) { ++ sunxi_info(&pdev->dev, "Not found delay-maps in dts\n"); ++ return; ++ } ++ ++ maps = devm_kcalloc(&pdev->dev, maps_cnt, sizeof(u32), GFP_KERNEL); ++ if (!maps) ++ return; ++ ++ ret = of_property_read_u32_array(np, "delay-maps", maps, maps_cnt); ++ if (ret) { ++ sunxi_err(&pdev->dev, "Failed to parse delay-maps\n"); ++ goto err_parse_maps; ++ } ++ ++ soc_ver = (u16)sunxi_get_soc_ver(); ++ for (i = 0; i < (maps_cnt / array_size); i++) { ++ if (soc_ver == maps[i * array_size]) { ++ chip->rx_delay = maps[i * array_size + 1]; ++ chip->tx_delay = maps[i * array_size + 2]; ++ sunxi_info(&pdev->dev, "Overwrite delay-maps parameters, rx-delay:%d, tx-delay:%d\n", ++ chip->rx_delay, chip->tx_delay); ++ } ++ } ++ ++err_parse_maps: ++ devm_kfree(&pdev->dev, maps); ++} ++ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++static void sunxi_dwmac_request_mtl_irq(struct platform_device *pdev, struct sunxi_dwmac *chip, ++ struct plat_stmmacenet_data *plat_dat) ++{ ++ u32 queues; ++ char int_name[MAC_IRQ_NAME]; ++ ++ for (queues = 0; queues < plat_dat->tx_queues_to_use; queues++) { ++ sprintf(int_name, "%s%d_%s", "tx", queues, "irq"); ++ chip->res->tx_irq[queues] = platform_get_irq_byname_optional(pdev, int_name); ++ if (chip->res->tx_irq[queues] < 0) ++ chip->res->tx_irq[queues] = 0; ++ } ++ ++ for (queues = 0; queues < plat_dat->rx_queues_to_use; queues++) { ++ sprintf(int_name, "%s%d_%s", "rx", queues, "irq"); ++ chip->res->rx_irq[queues] = platform_get_irq_byname_optional(pdev, int_name); ++ if (chip->res->rx_irq[queues] < 0) ++ chip->res->rx_irq[queues] = 0; ++ } ++} ++#endif ++ ++static int sunxi_dwmac_resource_get(struct platform_device *pdev, struct sunxi_dwmac *chip, ++ struct plat_stmmacenet_data *plat_dat) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ int ret; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ if (!res) { ++ sunxi_err(dev, "Get phy memory failed\n"); ++ return -ENODEV; ++ } ++ ++ chip->syscfg_base = devm_ioremap_resource(dev, res); ++ if (!chip->syscfg_base) { ++ sunxi_err(dev, "Phy memory mapping failed\n"); ++ return -ENOMEM; ++ } ++ ++ chip->rgmii_clk_ext = of_property_read_bool(np, "aw,rgmii-clk-ext"); ++ chip->soc_phy_clk_en = of_property_read_bool(np, "aw,soc-phy-clk-en") || ++ of_property_read_bool(np, "aw,soc-phy25m"); ++ if (chip->soc_phy_clk_en) { ++ chip->phy_clk = devm_clk_get(dev, "phy"); ++ if (IS_ERR(chip->phy_clk)) { ++ chip->phy_clk = devm_clk_get(dev, "phy25m"); ++ if (IS_ERR(chip->phy_clk)) { ++ sunxi_err(dev, "Get phy25m clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ sunxi_info(dev, "Phy use soc fanout\n"); ++ } else ++ sunxi_info(dev, "Phy use ext osc\n"); ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ chip->hsi_ahb = devm_clk_get(dev, "hsi_ahb"); ++ if (IS_ERR(chip->hsi_ahb)) { ++ sunxi_err(dev, "Get hsi_ahb clk failed\n"); ++ return -EINVAL; ++ } ++ chip->hsi_axi = devm_clk_get(dev, "hsi_axi"); ++ if (IS_ERR(chip->hsi_axi)) { ++ sunxi_err(dev, "Get hsi_axi clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (chip->variant->flags & SUNXI_DWMAC_NSI_CLK_GATE) { ++ chip->nsi_clk = devm_clk_get(dev, "nsi"); ++ if (IS_ERR(chip->nsi_clk)) { ++ sunxi_err(dev, "Get nsi clk failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++ if (chip->variant->flags & SUNXI_DWMAC_MEM_ECC) { ++ sunxi_info(dev, "Support mem ecc\n"); ++ chip->res->sfty_ce_irq = platform_get_irq_byname_optional(pdev, "mac_eccirq"); ++ if (chip->res->sfty_ce_irq < 0) { ++ sunxi_err(&pdev->dev, "Get ecc irq failed\n"); ++ return -EINVAL; ++ } ++ } ++#endif ++ ++ if (chip->variant->flags & SUNXI_DWMAC_HSI_CLK_GATE) { ++ chip->hsi_rst = devm_reset_control_get_shared(chip->dev, "hsi"); ++ if (IS_ERR(chip->hsi_rst)) { ++ sunxi_err(dev, "Get hsi reset failed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ chip->ahb_rst = devm_reset_control_get_optional_shared(chip->dev, "ahb"); ++ if (IS_ERR(chip->ahb_rst)) { ++ sunxi_err(dev, "Get mac reset failed\n"); ++ return -EINVAL; ++ } ++ ++ chip->dwmac3v3_supply = devm_regulator_get_optional(&pdev->dev, "dwmac3v3"); ++ if (IS_ERR(chip->dwmac3v3_supply)) ++ sunxi_warn(dev, "Not found dwmac3v3-supply\n"); ++ ++ chip->phy3v3_supply = devm_regulator_get_optional(&pdev->dev, "phy3v3"); ++ if (IS_ERR(chip->phy3v3_supply)) ++ sunxi_warn(dev, "Not found phy3v3-supply\n"); ++ ++ ret = of_property_read_u32(np, "tx-delay", &chip->tx_delay); ++ if (ret) { ++ sunxi_warn(dev, "Get gmac tx-delay failed, use default 0\n"); ++ chip->tx_delay = 0; ++ } ++ ++ ret = of_property_read_u32(np, "rx-delay", &chip->rx_delay); ++ if (ret) { ++ sunxi_warn(dev, "Get gmac rx-delay failed, use default 0\n"); ++ chip->rx_delay = 0; ++ } ++ ++ sunxi_dwmac_parse_delay_maps(chip); ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++ if (chip->variant->flags & SUNXI_DWMAC_MULTI_MSI) ++ sunxi_dwmac_request_mtl_irq(pdev, chip, plat_dat); ++#endif ++ ++ return 0; ++} ++ ++#ifndef MODULE ++static void sunxi_dwmac_set_mac(u8 *dst, u8 *src) ++{ ++ int i; ++ char *p = src; ++ ++ for (i = 0; i < ETH_ALEN; i++, p++) ++ dst[i] = simple_strtoul(p, &p, 16); ++} ++#endif ++ ++static int sunxi_dwmac_probe(struct platform_device *pdev) ++{ ++ struct plat_stmmacenet_data *plat_dat; ++ struct stmmac_resources stmmac_res; ++ struct sunxi_dwmac *chip; ++ struct device *dev = &pdev->dev; ++ int ret; ++#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) ++ char *mac_temp = NULL; ++#endif ++ ++ ret = stmmac_get_platform_resources(pdev, &stmmac_res); ++ if (ret) ++ return ret; ++ ++ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); ++ if (!chip) { ++ sunxi_err(&pdev->dev, "Alloc sunxi dwmac err\n"); ++ return -ENOMEM; ++ } ++ ++ chip->variant = of_device_get_match_data(&pdev->dev); ++ if (!chip->variant) { ++ sunxi_err(&pdev->dev, "Missing dwmac-sunxi variant\n"); ++ return -EINVAL; ++ } ++ ++ chip->dev = dev; ++ chip->res = &stmmac_res; ++ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++ plat_dat = stmmac_probe_config_dt(pdev, stmmac_res.mac); ++#else ++ plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac); ++ if (IS_ERR_OR_NULL(stmmac_res.mac)) { ++ mac_temp = devm_kzalloc(dev, ETH_ALEN, GFP_KERNEL); ++ stmmac_res.mac = mac_temp; ++ } ++#endif ++ if (IS_ERR(plat_dat)) ++ return PTR_ERR(plat_dat); ++ ++ ret = sunxi_dwmac_resource_get(pdev, chip, plat_dat); ++ if (ret < 0) ++ return -EINVAL; ++ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++#ifdef MODULE ++ get_custom_mac_address(1, "eth", stmmac_res.mac); ++#else ++ sunxi_dwmac_set_mac(stmmac_res.mac, mac_str); ++#endif ++#else ++ if (mac_temp) { ++#ifdef MODULE ++ get_custom_mac_address(1, "eth", mac_temp); ++#else ++ sunxi_dwmac_set_mac(mac_temp, mac_str); ++#endif ++ } ++#endif ++ ++ plat_dat->bsp_priv = chip; ++ plat_dat->init = sunxi_dwmac_init; ++ plat_dat->exit = sunxi_dwmac_exit; ++ /* must use 0~4G space */ ++#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)) ++ plat_dat->addr64 = 32; ++#else ++ plat_dat->host_dma_width = 32; ++#endif ++ /* Disable Split Header (SPH) feature for sunxi platfrom as default ++ * The same issue also detect on intel platfrom, see 41eebbf90dfbcc8ad16d4755fe2cdb8328f5d4a7. ++ */ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)) ++ if (chip->variant->flags & SUNXI_DWMAC_SPH_DISABLE) ++ plat_dat->flags |= STMMAC_FLAG_SPH_DISABLE; ++ if (chip->variant->flags & SUNXI_DWMAC_MULTI_MSI) ++ plat_dat->flags |= STMMAC_FLAG_MULTI_MSI_EN; ++ chip->interface = plat_dat->mac_interface; ++#else ++ if (chip->variant->flags & SUNXI_DWMAC_SPH_DISABLE) ++ plat_dat->sph_disable = true; ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++ if (chip->variant->flags & SUNXI_DWMAC_MULTI_MSI) ++ plat_dat->multi_msi_en = true; ++#endif ++ chip->interface = plat_dat->interface; ++#endif ++ plat_dat->clk_csr = 4; /* MDC = AHB(200M)/102 = 2M */ ++ ++ ret = sunxi_dwmac_init(pdev, plat_dat->bsp_priv); ++ if (ret) ++ goto err_init; ++ ++ ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); ++ if (ret) ++ goto err_dvr_probe; ++ ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++ if (chip->variant->flags & SUNXI_DWMAC_MEM_ECC) { ++ ret = sunxi_dwmac_ecc_init(chip); ++ if (ret < 0) { ++ sunxi_err(chip->dev, "Init ecc failed\n"); ++ goto err_cfg; ++ } ++ } ++#endif ++ ++ sunxi_dwmac_sysfs_init(&pdev->dev); ++ ++ sunxi_info(&pdev->dev, "probe success (Version %s)\n", DWMAC_MODULE_VERSION); ++ ++ return 0; ++#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) ++err_cfg: ++ stmmac_dvr_remove(&pdev->dev); ++#endif ++err_dvr_probe: ++ sunxi_dwmac_exit(pdev, chip); ++err_init: ++ stmmac_remove_config_dt(pdev, plat_dat); ++ return ret; ++} ++ ++static int sunxi_dwmac_remove(struct platform_device *pdev) ++{ ++ sunxi_dwmac_sysfs_exit(&pdev->dev); ++ stmmac_pltfr_remove(pdev); ++ return 0; ++} ++ ++static void sunxi_dwmac_shutdown(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ ++ sunxi_dwmac_exit(pdev, chip); ++} ++ ++static int __maybe_unused sunxi_dwmac_suspend(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ ++ /* suspend error workaround */ ++ if (ndev && ndev->phydev) { ++ chip->uevent_suppress = dev_get_uevent_suppress(&ndev->phydev->mdio.dev); ++ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, true); ++ } ++ ++ ret = stmmac_suspend(dev); ++ sunxi_dwmac_exit(pdev, chip); ++ stmmac_bus_clks_config(priv, false); ++ ++ return ret; ++} ++ ++static int __maybe_unused sunxi_dwmac_resume(struct device *dev) ++{ ++ struct net_device *ndev = dev_get_drvdata(dev); ++ struct stmmac_priv *priv = netdev_priv(ndev); ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_dwmac *chip = priv->plat->bsp_priv; ++ int ret; ++ ++ stmmac_bus_clks_config(priv, true); ++ sunxi_dwmac_init(pdev, chip); ++ ret = stmmac_resume(dev); ++ ++ if (ndev && ndev->phydev) { ++ /* State machine change phy state too early before mdio bus resume. ++ * WARN_ON would print in mdio_bus_phy_resume if state not equal to PHY_HALTED/PHY_READY/PHY_UP. ++ * Workaround is change the state back to PHY_UP and modify the state machine work so the judgment can be passed. ++ */ ++ rtnl_lock(); ++ mutex_lock(&ndev->phydev->lock); ++ if (ndev->phydev->state == PHY_UP || ndev->phydev->state == PHY_NOLINK) { ++ if (ndev->phydev->state == PHY_NOLINK) ++ ndev->phydev->state = PHY_UP; ++ phy_queue_state_machine(ndev->phydev, HZ); ++ } ++ mutex_unlock(&ndev->phydev->lock); ++ rtnl_unlock(); ++ ++ /* suspend error workaround */ ++ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, chip->uevent_suppress); ++ } ++ ++ return ret; ++} ++ ++static SIMPLE_DEV_PM_OPS(sunxi_dwmac_pm_ops, sunxi_dwmac_suspend, sunxi_dwmac_resume); ++ ++static const struct sunxi_dwmac_variant dwmac200_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE, ++ .rx_delay_max = 31, ++ .tx_delay_max = 7, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac200_set_delaychain, ++ .get_delaychain = sunxi_dwmac200_get_delaychain, ++}; ++ ++static const struct sunxi_dwmac_variant dwmac210_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE | SUNXI_DWMAC_MULTI_MSI, ++ .rx_delay_max = 31, ++ .tx_delay_max = 31, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac210_set_delaychain, ++ .get_delaychain = sunxi_dwmac210_get_delaychain, ++}; ++ ++static const struct sunxi_dwmac_variant dwmac220_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE | SUNXI_DWMAC_NSI_CLK_GATE | SUNXI_DWMAC_MULTI_MSI | SUNXI_DWMAC_MEM_ECC, ++ .rx_delay_max = 31, ++ .tx_delay_max = 31, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac210_set_delaychain, ++ .get_delaychain = sunxi_dwmac210_get_delaychain, ++}; ++ ++static const struct sunxi_dwmac_variant dwmac110_variant = { ++ .interface = PHY_INTERFACE_MODE_RMII | PHY_INTERFACE_MODE_RGMII, ++ .flags = SUNXI_DWMAC_SPH_DISABLE | SUNXI_DWMAC_NSI_CLK_GATE | SUNXI_DWMAC_HSI_CLK_GATE | SUNXI_DWMAC_MULTI_MSI, ++ .rx_delay_max = 31, ++ .tx_delay_max = 31, ++ .set_syscon = sunxi_dwmac200_set_syscon, ++ .set_delaychain = sunxi_dwmac210_set_delaychain, ++ .get_delaychain = sunxi_dwmac210_get_delaychain, ++ .get_version = sunxi_dwmac110_get_version, ++}; ++ ++static const struct of_device_id sunxi_dwmac_match[] = { ++ { .compatible = "allwinner,sunxi-gmac-200", .data = &dwmac200_variant }, ++ { .compatible = "allwinner,sunxi-gmac-210", .data = &dwmac210_variant }, ++ { .compatible = "allwinner,sunxi-gmac-220", .data = &dwmac220_variant }, ++ { .compatible = "allwinner,sunxi-gmac-110", .data = &dwmac110_variant }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, sunxi_dwmac_match); ++ ++static struct platform_driver sunxi_dwmac_driver = { ++ .probe = sunxi_dwmac_probe, ++ .remove = sunxi_dwmac_remove, ++ .shutdown = sunxi_dwmac_shutdown, ++ .driver = { ++ .name = "dwmac-sunxi", ++ .pm = &sunxi_dwmac_pm_ops, ++ .of_match_table = sunxi_dwmac_match, ++ }, ++}; ++module_platform_driver(sunxi_dwmac_driver); ++ ++#ifndef MODULE ++static int __init sunxi_dwmac_set_mac_addr(char *str) ++{ ++ char *p = str; ++ ++ if (str && strlen(str)) ++ memcpy(mac_str, p, MAC_ADDR_LEN); ++ ++ return 0; ++} ++__setup("mac1_addr=", sunxi_dwmac_set_mac_addr); ++#endif /* MODULE */ ++ ++MODULE_DESCRIPTION("Allwinner DWMAC driver"); ++MODULE_AUTHOR("wujiayi "); ++MODULE_AUTHOR("xuminghui "); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_VERSION(DWMAC_MODULE_VERSION); +diff --git a/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.h b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.h +new file mode 100644 +index 000000000000..ff8faa3afa18 +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sunxi-stmmac/dwmac-sunxi.h +@@ -0,0 +1,176 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++* Allwinner DWMAC driver header. ++* ++* Copyright(c) 2022-2027 Allwinnertech Co., Ltd. ++* ++* This file is licensed under the terms of the GNU General Public ++* License version 2. This program is licensed "as is" without any ++* warranty of any kind, whether express or implied. ++*/ ++ ++#ifndef _DWMAC_SUNXI_H_ ++#define _DWMAC_SUNXI_H_ ++ ++#include ++#include ++ ++/* DWCMAC5 ECC Debug Register ++ * These macro do not defined in mainline code dwmac5.h ++ */ ++#define MTL_DBG_CTL 0x00000c08 ++#define EIEC BIT(18) ++#define EIAEE BIT(17) ++#define EIEE BIT(16) ++#define FIFOSEL GENMASK(13, 12) ++#define FIFOWREN BIT(11) ++#define FIFORDEN BIT(10) ++#define RSTSEL BIT(9) ++#define RSTALL BIT(8) ++#define DBGMOD BIT(1) ++#define FDBGEN BIT(0) ++#define MTL_DBG_STS 0x00000c0c ++#define FIFOBUSY BIT(0) ++#define MTL_FIFO_DEBUG_DATA 0x00000c10 ++#define MTL_ECC_ERR_STS_RCTL 0x00000cd0 ++#define CUES BIT(5) ++#define CCES BIT(4) ++#define EMS GENMASK(3, 1) ++#define EESRE BIT(0) ++#define MTL_ECC_ERR_ADDR_STATUS 0x00000cd4 ++#define EUEAS GENMASK(31, 16) ++#define ECEAS GENMASK(15, 0) ++#define MTL_ECC_ERR_CNTR_STATUS 0x00000cd8 ++#define EUECS GENMASK(19, 16) ++#define ECECS GENMASK(7, 0) ++#define MTL_DPP_ECC_EIC 0x00000ce4 ++#define EIM BIT(16) ++#define BLEI GENMASK(7, 0) ++ ++/* GMAC-200 Register */ ++#define SUNXI_DWMAC200_SYSCON_REG (0x00) ++ #define SUNXI_DWMAC200_SYSCON_BPS_EFUSE GENMASK(31, 28) ++ #define SUNXI_DWMAC200_SYSCON_XMII_SEL BIT(27) ++ #define SUNXI_DWMAC200_SYSCON_EPHY_MODE GENMASK(26, 25) ++ #define SUNXI_DWMAC200_SYSCON_PHY_ADDR GENMASK(24, 20) ++ #define SUNXI_DWMAC200_SYSCON_BIST_CLK_EN BIT(19) ++ #define SUNXI_DWMAC200_SYSCON_CLK_SEL BIT(18) ++ #define SUNXI_DWMAC200_SYSCON_LED_POL BIT(17) ++ #define SUNXI_DWMAC200_SYSCON_SHUTDOWN BIT(16) ++ #define SUNXI_DWMAC200_SYSCON_PHY_SEL BIT(15) ++ #define SUNXI_DWMAC200_SYSCON_ENDIAN_MODE BIT(14) ++ #define SUNXI_DWMAC200_SYSCON_RMII_EN BIT(13) ++ #define SUNXI_DWMAC200_SYSCON_ETXDC GENMASK(12, 10) ++ #define SUNXI_DWMAC200_SYSCON_ERXDC GENMASK(9, 5) ++ #define SUNXI_DWMAC200_SYSCON_ERXIE BIT(4) ++ #define SUNXI_DWMAC200_SYSCON_ETXIE BIT(3) ++ #define SUNXI_DWMAC200_SYSCON_EPIT BIT(2) ++ #define SUNXI_DWMAC200_SYSCON_ETCS GENMASK(1, 0) ++ ++/* GMAC-210 Register */ ++#define SUNXI_DWMAC210_CFG_REG (0x00) ++ #define SUNXI_DWMAC210_CFG_ETXDC_H GENMASK(17, 16) ++ #define SUNXI_DWMAC210_CFG_PHY_SEL BIT(15) ++ #define SUNXI_DWMAC210_CFG_ENDIAN_MODE BIT(14) ++ #define SUNXI_DWMAC210_CFG_RMII_EN BIT(13) ++ #define SUNXI_DWMAC210_CFG_ETXDC_L GENMASK(12, 10) ++ #define SUNXI_DWMAC210_CFG_ERXDC GENMASK(9, 5) ++ #define SUNXI_DWMAC210_CFG_ERXIE BIT(4) ++ #define SUNXI_DWMAC210_CFG_ETXIE BIT(3) ++ #define SUNXI_DWMAC210_CFG_EPIT BIT(2) ++ #define SUNXI_DWMAC210_CFG_ETCS GENMASK(1, 0) ++#define SUNXI_DWMAC210_PTP_TIMESTAMP_L_REG (0x40) ++#define SUNXI_DWMAC210_PTP_TIMESTAMP_H_REG (0x48) ++#define SUNXI_DWMAC210_STAT_INT_REG (0x4C) ++ #define SUNXI_DWMAC210_STAT_PWR_DOWN_ACK BIT(4) ++ #define SUNXI_DWMAC210_STAT_SBD_TX_CLK_GATE BIT(3) ++ #define SUNXI_DWMAC210_STAT_LPI_INT BIT(1) ++ #define SUNXI_DWMAC210_STAT_PMT_INT BIT(0) ++#define SUNXI_DWMAC210_CLK_GATE_CFG_REG (0x80) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_RX BIT(7) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_PTP_REF BIT(6) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_CSR BIT(5) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_TX BIT(4) ++ #define SUNXI_DWMAC210_CLK_GATE_CFG_APP BIT(3) ++ ++/* GMAC-110 Register */ ++#define SUNXI_DWMAC110_CFG_REG SUNXI_DWMAC210_CFG_REG ++ /* SUNXI_DWMAC110_CFG_REG is same with SUNXI_DWMAC210_CFG_REG */ ++#define SUNXI_DWMAC110_CLK_GATE_CFG_REG (0x04) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_RX BIT(3) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_TX BIT(2) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_APP BIT(1) ++ #define SUNXI_DWMAC110_CLK_GATE_CFG_CSR BIT(0) ++#define SUNXI_DWMAC110_VERSION_REG (0xfc) ++ #define SUNXI_DWMAC110_VERSION_IP_TAG GENMASK(31, 16) ++ #define SUNXI_DWMAC110_VERSION_IP_VRM GENMASK(15, 0) ++ ++#define SUNXI_DWMAC_ETCS_MII 0x0 ++#define SUNXI_DWMAC_ETCS_EXT_GMII 0x1 ++#define SUNXI_DWMAC_ETCS_INT_GMII 0x2 ++ ++/* MAC flags defined */ ++#define SUNXI_DWMAC_SPH_DISABLE BIT(0) ++#define SUNXI_DWMAC_NSI_CLK_GATE BIT(1) ++#define SUNXI_DWMAC_MULTI_MSI BIT(2) ++#define SUNXI_DWMAC_MEM_ECC BIT(3) ++#define SUNXI_DWMAC_HSI_CLK_GATE BIT(4) ++ ++struct sunxi_dwmac; ++ ++enum sunxi_dwmac_delaychain_dir { ++ SUNXI_DWMAC_DELAYCHAIN_TX, ++ SUNXI_DWMAC_DELAYCHAIN_RX, ++}; ++ ++enum sunxi_dwmac_ecc_fifo_type { ++ SUNXI_DWMAC_ECC_FIFO_TX, ++ SUNXI_DWMAC_ECC_FIFO_RX, ++}; ++ ++struct sunxi_dwmac_variant { ++ u32 flags; ++ u32 interface; ++ u32 rx_delay_max; ++ u32 tx_delay_max; ++ int (*set_syscon)(struct sunxi_dwmac *chip); ++ int (*set_delaychain)(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir, u32 delay); ++ u32 (*get_delaychain)(struct sunxi_dwmac *chip, enum sunxi_dwmac_delaychain_dir dir); ++ int (*get_version)(struct sunxi_dwmac *chip, u16 *ip_tag, u16 *ip_vrm); ++}; ++ ++struct sunxi_dwmac_mii_reg { ++ u32 addr; ++ u16 reg; ++ u16 value; ++}; ++ ++struct sunxi_dwmac { ++ const struct sunxi_dwmac_variant *variant; ++ struct sunxi_dwmac_mii_reg mii_reg; ++ struct clk *phy_clk; ++ struct clk *nsi_clk; ++ struct clk *hsi_ahb; ++ struct clk *hsi_axi; ++ struct reset_control *ahb_rst; ++ struct reset_control *hsi_rst; ++ struct device *dev; ++ void __iomem *syscfg_base; ++ struct regulator *dwmac3v3_supply; ++ struct regulator *phy3v3_supply; ++ ++ u32 tx_delay; /* adjust transmit clock delay */ ++ u32 rx_delay; /* adjust receive clock delay */ ++ ++ bool rgmii_clk_ext; ++ bool soc_phy_clk_en; ++ int interface; ++ unsigned int uevent_suppress; /* suspend error workaround: control kobject_uevent_env */ ++ ++ struct stmmac_resources *res; ++}; ++ ++#include "dwmac-sunxi-sysfs.h" ++ ++#endif /* _DWMAC_SUNXI_H_ */ +diff --git a/include/dt-bindings/clock/sun55i-a523-ccu.h b/include/dt-bindings/clock/sun55i-a523-ccu.h +index c94669156ffc..8024f110ef51 100644 +--- a/include/dt-bindings/clock/sun55i-a523-ccu.h ++++ b/include/dt-bindings/clock/sun55i-a523-ccu.h +@@ -181,7 +181,8 @@ + #define CLK_FANOUT_27M 172 + #define CLK_FANOUT_PCLK 173 + #define CLK_FANOUT0 174 + #define CLK_FANOUT1 175 + #define CLK_FANOUT2 176 ++#define CLK_MBUS_EMAC1 177 + + #endif /* _DT_BINDINGS_CLK_SUN55I_A523_CCU_H_ */ +-- +Created with Armbian build tools https://github.com/armbian/build + diff --git a/patch/misc/rtw88/6.12/001-drivers-net-wireless-realtek-rtw88-upstream-wireless.patch b/patch/misc/rtw88/6.12/001-drivers-net-realtek-rtw88-upstream-wireless.patch similarity index 99% rename from patch/misc/rtw88/6.12/001-drivers-net-wireless-realtek-rtw88-upstream-wireless.patch rename to patch/misc/rtw88/6.12/001-drivers-net-realtek-rtw88-upstream-wireless.patch index b53fa35e5e92..dd2a14ba6c94 100644 --- a/patch/misc/rtw88/6.12/001-drivers-net-wireless-realtek-rtw88-upstream-wireless.patch +++ b/patch/misc/rtw88/6.12/001-drivers-net-realtek-rtw88-upstream-wireless.patch @@ -12686,4 +12686,3 @@ index 07695294767a..be193c7add77 100644 int rtw_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) -- 2.39.5 -